[KLF Application][KLF Tools][KLF Backend][KLF Home]
KLatexFormula Project

src/klflatexedit.cpp

Go to the documentation of this file.
00001 /***************************************************************************
00002  *   file klflatexedit.cpp
00003  *   This file is part of the KLatexFormula Project.
00004  *   Copyright (C) 2010 by Philippe Faist
00005  *   philippe.faist at bluewin.ch
00006  *                                                                         *
00007  *   This program is free software; you can redistribute it and/or modify  *
00008  *   it under the terms of the GNU General Public License as published by  *
00009  *   the Free Software Foundation; either version 2 of the License, or     *
00010  *   (at your option) any later version.                                   *
00011  *                                                                         *
00012  *   This program is distributed in the hope that it will be useful,       *
00013  *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
00014  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
00015  *   GNU General Public License for more details.                          *
00016  *                                                                         *
00017  *   You should have received a copy of the GNU General Public License     *
00018  *   along with this program; if not, write to the                         *
00019  *   Free Software Foundation, Inc.,                                       *
00020  *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
00021  ***************************************************************************/
00022 /* $Id: klflatexedit.cpp 482 2010-09-09 15:23:44Z philippe $ */
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   // don't call setPlainText(); we want to preserve undo history
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     // move cursor at that point, but not if we have a selection
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; // data can be opened by main window
00155 
00156   // or check if we can insert the data ourselves
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; // data was opened by main window
00167   if (openerfound) {
00168     // failed to open data, don't insist.
00169     return;
00170   }
00171 
00172   klfDbg("mMainWin="<<mMainWin<<" did not handle the paste, doing it ourselves.") ;
00173 
00174   // insert the data ourselves
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; // the length of each block
00243   std::stack<ParenItem> parens; // the parens that we'll meet
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()) // ignore a \left with no following character
00278           continue;
00279         if (text.mid(i,2) == "\\{")
00280           ++i; // focus on the '{' sign, not the \\ sign
00281         parens.push(ParenItem(blockpos+i, (_caretpos == blockpos+i), text[i].toAscii(), l));
00282         if (i > 0 && text[i-1] == '\\') {
00283           --i; // allow the next-next if-block for keywords to highlight this "\\{"
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, '!'); // simulate an item
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()) // ignore a \right with no following character
00301           continue;
00302         if (text.mid(i,2) == "\\}")
00303           ++i; // focus on the '}' sign, not the \\ sign
00304         if ( (l && text[i] == '.' && p.left) || (l && p.ch == '.' && p.left) ) {
00305           // special case with \left( blablabla \right.  or  \left. blablabla \right)
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         // does this rule span multiple paragraphs, and do we need to show it (eg. cursor right after paren)
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 != '!') // simulated item for first pos
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; // allow the next if-block for keywords to highlight this "\\}"
00325         }
00326       }
00327 
00328       if (text[i] == '\\') { // a keyword ("\symbol")
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); // from i-1, length k+1
00340         if (symbol.size() > 1) { // no empty backslash
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)) { // not typing 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     // for each unclosed paren left
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 { // not on caret positions
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; // forget everything about synt highlight if we don't want it.
00414 
00415   QTextBlock block = currentBlock();
00416 
00417   //  printf("\t -- block/position=%d\n", block.position());
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; // +, start being negative
00438       start = 0;
00439     }
00440     if (start > text.length())
00441       continue;
00442     if (len > text.length() - start)
00443       len = text.length() - start;
00444     // apply rule
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 }

Generated by doxygen 1.7.3