00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024 #include <stack>
00025
00026 #include <QObject>
00027 #include <QWidget>
00028 #include <QTextEdit>
00029 #include <QTextDocumentFragment>
00030 #include <QTextCursor>
00031
00032 #include "klfconfig.h"
00033 #include "klfmainwin.h"
00034
00035 #include "klflatexedit.h"
00036
00037
00038
00039 KLFLatexEdit::KLFLatexEdit(QWidget *parent)
00040 : QTextEdit(parent), mMainWin(NULL), pHeightHintLines(-1)
00041 {
00042 mSyntaxHighlighter = new KLFLatexSyntaxHighlighter(this, this);
00043
00044 connect(this, SIGNAL(cursorPositionChanged()),
00045 mSyntaxHighlighter, SLOT(refreshAll()));
00046
00047 setContextMenuPolicy(Qt::DefaultContextMenu);
00048 }
00049
00050 KLFLatexEdit::~KLFLatexEdit()
00051 {
00052 }
00053
00054 void KLFLatexEdit::clearLatex()
00055 {
00056 setLatex("");
00057 setFocus();
00058 mSyntaxHighlighter->resetEditing();
00059 }
00060
00061 void KLFLatexEdit::setLatex(const QString& latex)
00062 {
00063
00064 QTextCursor cur = textCursor();
00065 cur.beginEditBlock();
00066 cur.select(QTextCursor::Document);
00067 cur.removeSelectedText();
00068 cur.insertText(latex);
00069 cur.endEditBlock();
00070 }
00071
00072 QSize KLFLatexEdit::sizeHint() const
00073 {
00074 QSize superSizeHint = QTextEdit::sizeHint();
00075 if (pHeightHintLines >= 0) {
00076 return QSize(superSizeHint.width(), 4 + QFontMetrics(font()).height()*pHeightHintLines);
00077 }
00078 return superSizeHint;
00079 }
00080
00081 void KLFLatexEdit::setHeightHintLines(int lines)
00082 {
00083 pHeightHintLines = lines;
00084 updateGeometry();
00085 }
00086
00087
00088 void KLFLatexEdit::contextMenuEvent(QContextMenuEvent *event)
00089 {
00090 QPoint pos = event->pos();
00091
00092 if ( ! textCursor().hasSelection() ) {
00093
00094 setTextCursor(cursorForPosition(pos));
00095 }
00096
00097 QMenu * menu = createStandardContextMenu(mapToGlobal(pos));
00098
00099 menu->addSeparator();
00100
00104 static const struct { const char * instext; int charsback; const char * iconsymb; } delimList[] = {
00105 { "\\frac{}{}", 3, "\\frac{a}{b}" },
00106 { "\\sqrt{}", 1, "\\sqrt{xyz}" },
00107 { "\\sqrt[]{}", 3, "\\sqrt[n]{xyz}" },
00108 { "\\textrm{}", 1, "\\textrm{A}" },
00109 { "\\textit{}", 1, "\\textit{A}" },
00110 { "\\textsl{}", 1, "\\textsl{A}" },
00111 { "\\textbf{}", 1, "\\textbf{A}" },
00112 { "\\mathrm{}", 1, "\\mathrm{A}" },
00113 { "\\mathit{}", 1, "\\mathit{A}" },
00114 { NULL, 0, NULL }
00115 };
00116
00117 QMenu *delimmenu = new QMenu(menu);
00118 int k;
00119 for (k = 0; delimList[k].instext != NULL; ++k) {
00120 QAction *a = new QAction(delimmenu);
00121 a->setText(delimList[k].instext);
00122 QVariantMap v;
00123 v["delim"] = QVariant::fromValue<QString>(QLatin1String(delimList[k].instext));
00124 v["charsBack"] = QVariant::fromValue<int>(delimList[k].charsback);
00125 a->setData(QVariant(v));
00126 a->setIcon(KLFLatexSymbolsCache::theCache()->findSymbolPixmap(QLatin1String(delimList[k].iconsymb)));
00127 delimmenu->addAction(a);
00128 connect(a, SIGNAL(triggered()), this, SLOT(slotInsertFromActionSender()));
00129 }
00130
00131 QAction *delimaction = menu->addAction(tr("Insert Delimiter"));
00132 delimaction->setMenu(delimmenu);
00133
00134 QList<QAction*> actionList;
00135 emit insertContextMenuActions(pos, &actionList);
00136
00137 if (actionList.size()) {
00138 menu->addSeparator();
00139 for (k = 0; k < actionList.size(); ++k) {
00140 menu->addAction(actionList[k]);
00141 }
00142 }
00143
00144 menu->popup(mapToGlobal(pos));
00145 event->accept();
00146 }
00147
00148
00149 bool KLFLatexEdit::canInsertFromMimeData(const QMimeData *data) const
00150 {
00151 klfDbg("formats: "<<data->formats());
00152 if (mMainWin != NULL)
00153 if (mMainWin->canOpenData(data))
00154 return true;
00155
00156
00157 return QTextEdit::canInsertFromMimeData(data);
00158 }
00159
00160 void KLFLatexEdit::insertFromMimeData(const QMimeData *data)
00161 {
00162 bool openerfound = false;
00163 klfDbg("formats: "<<data->formats());
00164 if (mMainWin != NULL)
00165 if (mMainWin->openData(data, &openerfound))
00166 return;
00167 if (openerfound) {
00168
00169 return;
00170 }
00171
00172 klfDbg("mMainWin="<<mMainWin<<" did not handle the paste, doing it ourselves.") ;
00173
00174
00175 QTextEdit::insertFromMimeData(data);
00176 }
00177
00178 void KLFLatexEdit::insertDelimiter(const QString& delim, int charsBack)
00179 {
00180 QTextCursor c1 = textCursor();
00181 c1.beginEditBlock();
00182 QString selected = c1.selection().toPlainText();
00183 QString toinsert = delim;
00184 if (selected.length())
00185 toinsert.insert(toinsert.length()-charsBack, selected);
00186 c1.removeSelectedText();
00187 c1.insertText(toinsert);
00188 c1.endEditBlock();
00189
00190 if (selected.isEmpty())
00191 c1.movePosition(QTextCursor::Left, QTextCursor::MoveAnchor, charsBack);
00192
00193 setTextCursor(c1);
00194
00195 setFocus();
00196 }
00197
00198
00199 void KLFLatexEdit::slotInsertFromActionSender()
00200 {
00201 QObject *obj = sender();
00202 if (obj == NULL || !obj->inherits("QAction")) {
00203 qWarning()<<KLF_FUNC_NAME<<": sender object is not a QAction: "<<obj;
00204 return;
00205 }
00206 QVariant v = qobject_cast<QAction*>(obj)->data();
00207 QVariantMap vdata = v.toMap();
00208 insertDelimiter(vdata["delim"].toString(), vdata["charsBack"].toInt());
00209 }
00210
00211
00212
00213
00214
00215 KLFLatexSyntaxHighlighter::KLFLatexSyntaxHighlighter(QTextEdit *textedit, QObject *parent)
00216 : QSyntaxHighlighter(parent) , _textedit(textedit)
00217 {
00218 setDocument(textedit->document());
00219
00220 _caretpos = 0;
00221 }
00222
00223 KLFLatexSyntaxHighlighter::~KLFLatexSyntaxHighlighter()
00224 {
00225 }
00226
00227 void KLFLatexSyntaxHighlighter::setCaretPos(int position)
00228 {
00229 _caretpos = position;
00230 }
00231
00232 void KLFLatexSyntaxHighlighter::refreshAll()
00233 {
00234 rehighlight();
00235 }
00236
00237 void KLFLatexSyntaxHighlighter::parseEverything()
00238 {
00239 QString text;
00240 int i = 0;
00241 int blockpos;
00242 QList<uint> blocklens;
00243 std::stack<ParenItem> parens;
00244
00245 QTextBlock block = document()->firstBlock();
00246
00247 _rulestoapply.clear();
00248 int k;
00249 while (block.isValid()) {
00250 text = block.text();
00251 i = 0;
00252 blockpos = block.position();
00253 blocklens.append(block.length());
00254
00255 while (text.length() < block.length()) {
00256 text += "\n";
00257 }
00258
00259 static QRegExp bsleft("^\\\\left(?!\\w)");
00260 static QRegExp bsright("^\\\\right(?!\\w)");
00261
00262 i = 0;
00263 while ( i < text.length() ) {
00264 if (text[i] == '%') {
00265 k = 0;
00266 while (i+k < text.length() && text[i+k] != '\n')
00267 ++k;
00268 _rulestoapply.append(FormatRule(blockpos+i, k, FComment));
00269 i += k + 1;
00270 continue;
00271 }
00272 if (bsleft.indexIn(text.mid(i)) != -1 ||
00273 text[i] == '{' || text[i] == '(' || text[i] == '[') {
00274 bool l = (text.mid(i, 5) == "\\left");
00275 if (l)
00276 i += 5;
00277 if (i == text.length())
00278 continue;
00279 if (text.mid(i,2) == "\\{")
00280 ++i;
00281 parens.push(ParenItem(blockpos+i, (_caretpos == blockpos+i), text[i].toAscii(), l));
00282 if (i > 0 && text[i-1] == '\\') {
00283 --i;
00284 }
00285 }
00286 if (bsright.indexIn(text.mid(i)) != -1 || text[i] == '}' || text[i] == ')' || text[i] == ']') {
00287 ParenItem p;
00288 if (!parens.empty()) {
00289 p = parens.top();
00290 parens.pop();
00291 } else {
00292 p = ParenItem(0, false, '!');
00293 if (klfconfig.SyntaxHighlighter.configFlags & HighlightLonelyParen)
00294 _rulestoapply.append(FormatRule(blockpos+i, 1, FLonelyParen));
00295 }
00296 Format col = FParenMatch;
00297 bool l = ( text.mid(i, 6) == "\\right" );
00298 if (l)
00299 i += 6;
00300 if (i == text.length())
00301 continue;
00302 if (text.mid(i,2) == "\\}")
00303 ++i;
00304 if ( (l && text[i] == '.' && p.left) || (l && p.ch == '.' && p.left) ) {
00305
00306 col = FParenMatch;
00307 } else if ((text[i] == '}' && p.ch != '{') ||
00308 (text[i] == ')' && p.ch != '(') ||
00309 (text[i] == ']' && p.ch != '[') ||
00310 (l != p.left) ) {
00311 col = FParenMismatch;
00312 }
00313
00314 if (p.highlight || (_caretpos == blockpos+i+1)) {
00315 if ((klfconfig.SyntaxHighlighter.configFlags & HighlightParensOnly) == 0) {
00316 _rulestoapply.append(FormatRule(p.pos, blockpos+i+1-p.pos, col, true));
00317 } else {
00318 if (p.ch != '!')
00319 _rulestoapply.append(FormatRule(p.pos, 1, col));
00320 _rulestoapply.append(FormatRule(blockpos+i, 1, col, true));
00321 }
00322 }
00323 if (i > 0 && text[i-1] == '\\') {
00324 --i;
00325 }
00326 }
00327
00328 if (text[i] == '\\') {
00329 ++i;
00330 k = 0;
00331 if (i >= text.length())
00332 continue;
00333 while (i+k < text.length() && ( (text[i+k] >= 'a' && text[i+k] <= 'z') ||
00334 (text[i+k] >= 'A' && text[i+k] <= 'Z') ))
00335 ++k;
00336 if (k == 0 && i+1 < text.length())
00337 k = 1;
00338 _rulestoapply.append(FormatRule(blockpos+i-1, k+1, FKeyWord));
00339 QString symbol = text.mid(i-1,k+1);
00340 if (symbol.size() > 1) {
00341 klfDbg("symbol="<<symbol<<" i="<<i<<" k="<<k<<" caretpos="<<_caretpos<<" blockpos="<<blockpos);
00342 if ( (_caretpos < blockpos+i ||_caretpos >= blockpos+i+k+1) &&
00343 !pTypedSymbols.contains(symbol)) {
00344 klfDbg("newSymbolTyped() about to be emitted for : "<<symbol);
00345 emit newSymbolTyped(symbol);
00346 pTypedSymbols.append(symbol);
00347 }
00348 }
00349 i += k;
00350 continue;
00351 }
00352
00353 ++i;
00354 }
00355
00356 block = block.next();
00357 }
00358
00359 QTextBlock lastblock = document()->lastBlock();
00360
00361 while ( ! parens.empty() ) {
00362
00363 ParenItem p = parens.top();
00364 parens.pop();
00365 if (_caretpos == p.pos) {
00366 if ( (klfconfig.SyntaxHighlighter.configFlags & HighlightParensOnly) != 0 )
00367 _rulestoapply.append(FormatRule(p.pos, 1, FParenMismatch, true));
00368 else
00369 _rulestoapply.append(FormatRule(p.pos, lastblock.position()+lastblock.length()-p.pos,
00370 FParenMismatch, true));
00371 } else {
00372 if (klfconfig.SyntaxHighlighter.configFlags & HighlightLonelyParen)
00373 _rulestoapply.append(FormatRule(p.pos, 1, FLonelyParen));
00374 }
00375 }
00376 }
00377
00378 QTextCharFormat KLFLatexSyntaxHighlighter::charfmtForFormat(Format f)
00379 {
00380 QTextCharFormat fmt;
00381 switch (f) {
00382 case FNormal:
00383 fmt = QTextCharFormat();
00384 break;
00385 case FKeyWord:
00386 fmt = klfconfig.SyntaxHighlighter.fmtKeyword;
00387 break;
00388 case FComment:
00389 fmt = klfconfig.SyntaxHighlighter.fmtComment;
00390 break;
00391 case FParenMatch:
00392 fmt = klfconfig.SyntaxHighlighter.fmtParenMatch;
00393 break;
00394 case FParenMismatch:
00395 fmt = klfconfig.SyntaxHighlighter.fmtParenMismatch;
00396 break;
00397 case FLonelyParen:
00398 fmt = klfconfig.SyntaxHighlighter.fmtLonelyParen;
00399 break;
00400 default:
00401 fmt = QTextCharFormat();
00402 break;
00403 };
00404 return fmt;
00405 }
00406
00407
00408 void KLFLatexSyntaxHighlighter::highlightBlock(const QString& text)
00409 {
00410 klfDbg("text is "<<text);
00411
00412 if ( ( klfconfig.SyntaxHighlighter.configFlags & Enabled ) == 0)
00413 return;
00414
00415 QTextBlock block = currentBlock();
00416
00417
00418
00419 if (block.position() == 0) {
00420 setCaretPos(_textedit->textCursor().position());
00421 parseEverything();
00422 }
00423
00424 QList<FormatRule> blockfmtrules;
00425 QVector<QTextCharFormat> charformats;
00426
00427 charformats.resize(text.length());
00428
00429 blockfmtrules.append(FormatRule(0, text.length(), FNormal));
00430
00431 int k, j;
00432 for (k = 0; k < _rulestoapply.size(); ++k) {
00433 int start = _rulestoapply[k].pos - block.position();
00434 int len = _rulestoapply[k].len;
00435
00436 if (start < 0) {
00437 len += start;
00438 start = 0;
00439 }
00440 if (start > text.length())
00441 continue;
00442 if (len > text.length() - start)
00443 len = text.length() - start;
00444
00445 blockfmtrules.append(FormatRule(start, len, _rulestoapply[k].format, _rulestoapply[k].onlyIfFocus));
00446 }
00447
00448 bool hasfocus = _textedit->hasFocus();
00449 for (k = 0; k < blockfmtrules.size(); ++k) {
00450 for (j = blockfmtrules[k].pos; j < blockfmtrules[k].end(); ++j) {
00451 if ( ! blockfmtrules[k].onlyIfFocus || hasfocus )
00452 charformats[j].merge(charfmtForFormat(blockfmtrules[k].format));
00453 }
00454 }
00455 for (j = 0; j < charformats.size(); ++j) {
00456 setFormat(j, 1, charformats[j]);
00457 }
00458
00459 return;
00460 }
00461
00462
00463 void KLFLatexSyntaxHighlighter::resetEditing()
00464 {
00465 pTypedSymbols = QStringList();
00466 }