[KLF Backend][KLF Tools][KLF Home]
KLatexFormula Project
src/klftools/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) 2011 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$ */
00023 
00024 #include <QObject>
00025 #include <QWidget>
00026 #include <QStack>
00027 #include <QTextEdit>
00028 #include <QTextDocumentFragment>
00029 #include <QTextCursor>
00030 #include <QAction>
00031 #include <QMenu>
00032 
00033 #include <klfguiutil.h>
00034 
00035 
00036 #include "klflatexedit.h"
00037 #include "klflatexedit_p.h"
00038 
00039 
00040 
00041 // --------------------------
00042 
00043 struct KLFLatexParenSpecsPrivate
00044 {
00045   typedef KLFLatexParenSpecs::ParenSpec ParenSpec;
00046   typedef KLFLatexParenSpecs::ParenModifierSpec ParenModifierSpec;
00047 
00048   KLF_PRIVATE_HEAD(KLFLatexParenSpecs)
00049   {
00050   }
00051 
00052   QList<ParenSpec> parens;
00053   QList<ParenModifierSpec> modifiers;
00054 
00055   QStringList openParenListCache;
00056   QStringList closeParenListCache;
00057   QStringList openParenModifiersCache;
00058   QStringList closeParenModifiersCache;
00059 
00060   void load(const QList<ParenSpec>& pl, const QList<ParenModifierSpec>& ml)
00061   {
00062     openParenListCache.clear();
00063     closeParenListCache.clear();
00064     openParenModifiersCache.clear();
00065     closeParenModifiersCache.clear();
00066 
00067     parens = pl;
00068     modifiers = ml;
00069     foreach (ParenSpec p, pl) {
00070       openParenListCache << p.open;
00071       closeParenListCache << p.close;
00072     }
00073     foreach (ParenModifierSpec m, ml) {
00074       openParenModifiersCache << m.openmod;
00075       closeParenModifiersCache << m.closemod;
00076     }
00077   }
00078 };
00079 
00080 static QList<KLFLatexParenSpecs::ParenSpec> default_parens = QList<KLFLatexParenSpecs::ParenSpec>()
00081   << KLFLatexParenSpecs::ParenSpec("(", ")")
00082   << KLFLatexParenSpecs::ParenSpec("[", "]")
00083   << KLFLatexParenSpecs::ParenSpec("{", "}", KLFLatexParenSpecs::ParenSpec::IsLaTeXBrace)
00084   << KLFLatexParenSpecs::ParenSpec("\\{", "\\}")
00085   << KLFLatexParenSpecs::ParenSpec("\\lfloor", "\\rfloor", KLFLatexParenSpecs::ParenSpec::AllowAlone)
00086   << KLFLatexParenSpecs::ParenSpec("\\lceil", "\\rceil", KLFLatexParenSpecs::ParenSpec::AllowAlone)
00087   << KLFLatexParenSpecs::ParenSpec("\\langle", "\\rangle", KLFLatexParenSpecs::ParenSpec::AllowAlone)
00088   << KLFLatexParenSpecs::ParenSpec("\\lvert", "\\rvert", KLFLatexParenSpecs::ParenSpec::AllowAlone)
00089   << KLFLatexParenSpecs::ParenSpec("\\lVert", "\\rVert", KLFLatexParenSpecs::ParenSpec::AllowAlone)
00090   ;
00091 
00092 static QList<KLFLatexParenSpecs::ParenModifierSpec> default_mods = QList<KLFLatexParenSpecs::ParenModifierSpec>()
00093   << KLFLatexParenSpecs::ParenModifierSpec("\\left", "\\right")
00094   << KLFLatexParenSpecs::ParenModifierSpec("\\bigl", "\\bigr")
00095   << KLFLatexParenSpecs::ParenModifierSpec("\\Bigl", "\\Bigr")
00096   ;
00097 
00098 // loads the default paren & paren modifier specs
00099 KLFLatexParenSpecs::KLFLatexParenSpecs()
00100 {
00101   KLF_INIT_PRIVATE(KLFLatexParenSpecs) ;
00102   d->load(default_parens, default_mods);
00103 }
00104 // loads the given paren & paren modifier spec list
00105 KLFLatexParenSpecs::KLFLatexParenSpecs(const QList<ParenSpec>& parens, const QList<ParenModifierSpec>& modifiers)
00106 {
00107   KLF_INIT_PRIVATE(KLFLatexParenSpecs) ;
00108   d->load(parens, modifiers);
00109 }
00110 
00111 KLFLatexParenSpecs::KLFLatexParenSpecs(const KLFLatexParenSpecs& other)
00112 {
00113   KLF_INIT_PRIVATE(KLFLatexParenSpecs) ;
00114   d->load(other.d->parens, other.d->modifiers);
00115 }
00116 KLFLatexParenSpecs::~KLFLatexParenSpecs()
00117 {
00118   KLF_DELETE_PRIVATE ;
00119 }
00120 
00121 
00122 QList<KLFLatexParenSpecs::ParenSpec> KLFLatexParenSpecs::parenSpecList() const
00123 {
00124   return d->parens;
00125 }
00126 QList<KLFLatexParenSpecs::ParenModifierSpec> KLFLatexParenSpecs::parenModifierSpecList() const
00127 {
00128   return d->modifiers;
00129 }
00130 
00131 QStringList KLFLatexParenSpecs::openParenList() const
00132 {
00133   return d->openParenListCache;
00134 }
00135 QStringList KLFLatexParenSpecs::closeParenList() const
00136 {
00137   return d->closeParenListCache;
00138 }
00139 QStringList KLFLatexParenSpecs::openParenModifiers() const
00140 {
00141   return d->openParenModifiersCache;
00142 }
00143 QStringList KLFLatexParenSpecs::closeParenModifiers() const
00144 {
00145   return d->closeParenModifiersCache;
00146 }
00147 
00148 int KLFLatexParenSpecs::identifyParen(const QString& parenstr, uint identflags)
00149 {
00150   KLF_DEBUG_BLOCK(KLF_FUNC_NAME) ;
00151   klfDbg("parenstr="<<parenstr<<", ifl="<<identflags) ;
00152   int k;
00153   for (k = 0; k < d->parens.size(); ++k) {
00154     ParenSpec p = d->parens[k];
00155     if ((identflags & IdentifyFlagOpen) && p.open == parenstr)
00156       return k;
00157     if ((identflags & IdentifyFlagClose) && p.close == parenstr)
00158       return k;
00159   }
00160   klfWarning("Can't find paren "<<parenstr<<" (fl="<<identflags<<") in our specs!") ;
00161   return -1;
00162 }
00163 
00164 int KLFLatexParenSpecs::identifyModifier(const QString& modstr, uint identflags)
00165 {
00166   KLF_DEBUG_BLOCK(KLF_FUNC_NAME) ;
00167   klfDbg("modstr="<<modstr<<", ifl="<<identflags) ;
00168   int k;
00169   for (k = 0; k < d->modifiers.size(); ++k) {
00170     ParenModifierSpec p = d->modifiers[k];
00171     if ((identflags & IdentifyFlagOpen) && p.openmod == modstr)
00172       return k;
00173     if ((identflags & IdentifyFlagClose) && p.closemod == modstr)
00174       return k;
00175   }
00176   klfWarning("Can't find paren modifier "<<modstr<<" (fl="<<identflags<<") in our specs!") ;
00177   return -1;
00178 }
00179 
00180 
00181 // --------------------------
00182 
00183 // static
00184 // this will load the default set of paren specs
00185 KLF_EXPORT KLFLatexParenSpecs KLFLatexSyntaxHighlighter::ParsedBlock::parenSpecs;
00186 
00187 bool KLFLatexSyntaxHighlighter::ParsedBlock::parenIsLatexBrace() const
00188 {
00189   KLF_ASSERT_CONDITION(parenSpecIndex >= 0 && parenSpecIndex < parenSpecs.parenSpecList().size(),
00190                        "parenSpecIndex is not valid! Using heuristic for parenIsLatexBrace().",
00191                        return parenstr=="{" || parenstr=="}"; ) ;
00192 
00193   return parenSpecs.parenSpecList()[parenSpecIndex].flags & KLFLatexParenSpecs::ParenSpec::IsLaTeXBrace;
00194 }
00195 
00196 
00197 // --------------------------
00198 
00199 KLFLatexEdit::KLFLatexEdit(QWidget *parent)
00200   : QTextEdit(parent)
00201 {
00202   KLF_INIT_PRIVATE(KLFLatexEdit) ;
00203 
00204   d->mSyntaxHighlighter = new KLFLatexSyntaxHighlighter(this, this);
00205 
00206   connect(this, SIGNAL(cursorPositionChanged()),
00207           d->mSyntaxHighlighter, SLOT(refreshAll()));
00208 
00209   setContextMenuPolicy(Qt::DefaultContextMenu);
00210 
00211   setProperty("klfDontChange_font", QVariant(true));
00212 
00213   setProperty("paletteDefault", QVariant::fromValue<QPalette>(palette()));
00214   QPalette pal = palette();
00215   pal.setColor(QPalette::Base, QColor(255, 255, 255, 150)); // quite transparent, but lighter
00216   setProperty("paletteMacBrushedMetalLook", QVariant::fromValue<QPalette>(pal));
00217 
00218   setWordWrapMode(QTextOption::WrapAnywhere);
00219 }
00220 
00221 KLFLatexEdit::~KLFLatexEdit()
00222 {
00223   KLF_DELETE_PRIVATE ;
00224 }
00225 
00226 QString KLFLatexEdit::latex() const
00227 {
00228   return toPlainText();
00229 }
00230 int KLFLatexEdit::heightHintLines() const
00231 {
00232   return d->pHeightHintLines;
00233 }
00234 void KLFLatexEdit::setDropDataHandler(KLFDropDataHandler *handler)
00235 {
00236   d->mDropHandler = handler;
00237 }
00238 KLFLatexSyntaxHighlighter *KLFLatexEdit::syntaxHighlighter()
00239 {
00240   return d->mSyntaxHighlighter;
00241 }
00242 
00243 void KLFLatexEdit::clearLatex()
00244 {
00245   setLatex("");
00246   setFocus();
00247   d->mSyntaxHighlighter->resetEditing();
00248 }
00249 
00250 void KLFLatexEdit::setLatex(const QString& latex)
00251 {
00252   // don't call setPlainText(); we want to preserve undo history
00253   QTextCursor cur = textCursor();
00254   cur.beginEditBlock();
00255   cur.select(QTextCursor::Document);
00256   cur.removeSelectedText();
00257   cur.insertText(latex);
00258   cur.endEditBlock();
00259 }
00260 
00261 bool KLFLatexEdit::wrapLines() const
00262 {
00263   return wordWrapMode() != QTextOption::NoWrap;
00264 }
00265 void KLFLatexEdit::setWrapLines(bool wrap)
00266 {
00267   setWordWrapMode(wrap ? QTextOption::WrapAnywhere : QTextOption::NoWrap);
00268 }
00269 
00270 
00271 QSize KLFLatexEdit::sizeHint() const
00272 {
00273   QSize superSizeHint = QTextEdit::sizeHint();
00274   if (d->pHeightHintLines >= 0) {
00275     return QSize(superSizeHint.width(), 4 + QFontMetrics(font()).height()*d->pHeightHintLines);
00276   }
00277   return superSizeHint;
00278 }
00279 
00280 void KLFLatexEdit::setHeightHintLines(int lines)
00281 {
00282   d->pHeightHintLines = lines;
00283   updateGeometry();
00284 }
00285 
00286 
00287 void KLFLatexEdit::contextMenuEvent(QContextMenuEvent *event)
00288 {
00289   QPoint pos = event->pos();
00290   int k;
00291 
00292   if ( ! textCursor().hasSelection() ) {
00293     // move cursor at that point, but not if we have a selection
00294     setTextCursor(cursorForPosition(pos));
00295   }
00296 
00297   QMenu * menu = createStandardContextMenu(mapToGlobal(pos));
00298 
00299   QList<QAction*> actionList;
00300   emit insertContextMenuActions(pos, &actionList);
00301 
00302   if (actionList.size()) {
00303     menu->addSeparator();
00304     for (k = 0; k < actionList.size(); ++k) {
00305       menu->addAction(actionList[k]);
00306     }
00307   }
00308  
00309   menu->popup(mapToGlobal(pos));
00310   event->accept();
00311 }
00312 
00313 
00314 bool KLFLatexEdit::canInsertFromMimeData(const QMimeData *data) const
00315 {
00316   klfDbg("formats: "<<data->formats());
00317   if (d->mDropHandler != NULL)
00318     if (d->mDropHandler->canOpenDropData(data))
00319       return true; // data can be opened by main window
00320 
00321   // or check if we can insert the data ourselves
00322   return QTextEdit::canInsertFromMimeData(data);
00323 }
00324 
00325 void KLFLatexEdit::insertFromMimeData(const QMimeData *data)
00326 {
00327   klfDbg("formats: "<<data->formats());
00328   if (d->mDropHandler != NULL) {
00329     int res = d->mDropHandler->openDropData(data);
00330     if (res == KLFDropDataHandler::OpenDataOk)
00331       return; // data was opened by main window
00332     if (res == KLFDropDataHandler::OpenDataFailed) {
00333       // NO: eg. for plain text, try again with QTextEdit's paste
00334       //      // failed to open data, don't insist.
00335       //      return;
00336     }
00337   }
00338 
00339   klfDbg("mDropHandler="<<d->mDropHandler<<" did not handle the paste, doing it ourselves.") ;
00340 
00341   // insert the data ourselves
00342   QTextEdit::insertFromMimeData(data);
00343 }
00344 
00345 void KLFLatexEdit::insertDelimiter(const QString& delim, int charsBack)
00346 {
00347   QTextCursor c1 = textCursor();
00348   c1.beginEditBlock();
00349   QString selected = c1.selection().toPlainText();
00350   QString toinsert = delim;
00351   if (selected.length())
00352     toinsert.insert(toinsert.length()-charsBack, selected);
00353   c1.removeSelectedText();
00354   c1.insertText(toinsert);
00355   c1.endEditBlock();
00356 
00357   if (selected.isEmpty())
00358     c1.movePosition(QTextCursor::Left, QTextCursor::MoveAnchor, charsBack);
00359 
00360   setTextCursor(c1);
00361 
00362   setFocus();
00363 }
00364 
00365 void KLFLatexEdit::setPalette(const QPalette& pal)
00366 {
00367   QTextEdit::setPalette(pal);
00368 }
00369 
00370 void KLFLatexEditPrivate::slotInsertFromActionSender()
00371 {
00372   QObject *obj = sender();
00373   if (obj == NULL || !obj->inherits("QAction")) {
00374     qWarning()<<KLF_FUNC_NAME<<": sender object is not a QAction: "<<obj;
00375     return;
00376   }
00377   QVariant v = qobject_cast<QAction*>(obj)->data();
00378   QVariantMap vdata = v.toMap();
00379   K->insertDelimiter(vdata["delim"].toString(), vdata["charsBack"].toInt());
00380 }
00381 
00382 
00383 // ------------------------------------
00384 
00385 
00386 KLFLatexSyntaxHighlighter::KLFLatexSyntaxHighlighter(QTextEdit *textedit, QObject *parent)
00387   : QSyntaxHighlighter(parent) , _textedit(textedit)
00388 {
00389   setDocument(textedit->document());
00390 
00391   // some reasonable defaults for our config...
00392   pConf.enabled = true;
00393   pConf.highlightParensOnly = false;
00394   pConf.highlightLonelyParens = true;
00395 
00396   pConf.fmtKeyword.setForeground(QColor(0, 0, 128));
00397   pConf.fmtComment.setForeground(QColor(180, 0, 0));
00398   pConf.fmtComment.setFontItalic(true);
00399   pConf.fmtParenMatch.setBackground(QColor(180, 238, 180));
00400   pConf.fmtParenMismatch.setBackground(QColor(255, 20, 147));
00401   pConf.fmtLonelyParen.setForeground(QColor(255, 0, 255));
00402   pConf.fmtLonelyParen.setFontWeight(QFont::Bold);
00403 
00404   _caretpos = 0;
00405 }
00406 
00407 KLFLatexSyntaxHighlighter::~KLFLatexSyntaxHighlighter()
00408 {
00409 }
00410 
00411 
00412 void KLFLatexSyntaxHighlighter::setHighlightEnabled(bool on)
00413 {
00414   pConf.enabled = on;
00415 }
00416 
00417 void KLFLatexSyntaxHighlighter::setHighlightParensOnly(bool on)
00418 {
00419   pConf.highlightParensOnly = on;
00420 }
00421 void KLFLatexSyntaxHighlighter::setHighlightLonelyParens(bool on)
00422 {
00423   pConf.highlightLonelyParens = on;
00424 }
00425 void KLFLatexSyntaxHighlighter::setFmtKeyword(const QTextFormat& f)
00426 {
00427   KLF_DEBUG_BLOCK(KLF_FUNC_NAME) ;
00428   KLF_ASSERT_CONDITION(f.isCharFormat(), "Format "<<f<<" is not a QTextCharFormat.", return ; ) ;
00429   pConf.fmtKeyword = f.toCharFormat();
00430 }
00431 void KLFLatexSyntaxHighlighter::setFmtComment(const QTextFormat& f)
00432 {
00433   KLF_ASSERT_CONDITION(f.isCharFormat(), "Format "<<f<<" is not a QTextCharFormat.", return ; ) ;
00434   pConf.fmtComment = f.toCharFormat();
00435 }
00436 void KLFLatexSyntaxHighlighter::setFmtParenMatch(const QTextFormat& f)
00437 {
00438   KLF_ASSERT_CONDITION(f.isCharFormat(), "Format "<<f<<" is not a QTextCharFormat.", return ; ) ;
00439   pConf.fmtParenMatch = f.toCharFormat();
00440 }
00441 void KLFLatexSyntaxHighlighter::setFmtParenMismatch(const QTextFormat& f)
00442 {
00443   KLF_ASSERT_CONDITION(f.isCharFormat(), "Format "<<f<<" is not a QTextCharFormat.", return ; ) ;
00444   pConf.fmtParenMismatch = f.toCharFormat();
00445 }
00446 void KLFLatexSyntaxHighlighter::setFmtLonelyParen(const QTextFormat& f)
00447 {
00448   KLF_ASSERT_CONDITION(f.isCharFormat(), "Format "<<f<<" is not a QTextCharFormat.", return ; ) ;
00449   pConf.fmtLonelyParen = f.toCharFormat();
00450 }
00451 
00452 
00453 QList<KLFLatexSyntaxHighlighter::ParsedBlock>
00454 /* */ KLFLatexSyntaxHighlighter::parsedBlocksForPos(int pos, unsigned int filter_mask) const
00455 {
00456   klfDbg("pos="<<pos<<", filter_mask="<<klfFmtCC("%06x", filter_mask)<<"; total # of blocks="
00457          <<pParsedBlocks.size()) ;
00458   int k;
00459   QList<ParsedBlock> blocks;
00460   for (k = 0; k < pParsedBlocks.size(); ++k) {
00461     klfDbg("testing block #"<<k<<": "<<pParsedBlocks[k]<<"; block/pos+block/len="
00462            <<pParsedBlocks[k].pos+pParsedBlocks[k].len<<" compared to pos="<<pos) ;
00463     if (pParsedBlocks[k].pos <= pos  &&  pos <= pParsedBlocks[k].pos+pParsedBlocks[k].len) {
00464       if (filter_mask & (1 << pParsedBlocks[k].type)) {
00465         blocks << pParsedBlocks[k];
00466         klfDbg("... added #"<<k) ;
00467       }
00468     }
00469   }
00470   return blocks; // return only the relevant blocks that intersect with position 'pos'
00471 }
00472 
00473 
00474 
00475 void KLFLatexSyntaxHighlighter::setCaretPos(int position)
00476 {
00477   _caretpos = position;
00478 }
00479 
00480 void KLFLatexSyntaxHighlighter::refreshAll()
00481 {
00482   rehighlight();
00483 }
00484 
00485 void KLFLatexSyntaxHighlighter::parseEverything()
00486 {
00487   KLF_DEBUG_BLOCK(KLF_FUNC_NAME) ;
00488 
00489   QString text;
00490   int i = 0;
00491   int blockpos;
00492   QList<uint> blocklens; // the length of each block
00493   QStack<ParenItem> parens; // the parens that we'll meet
00494   QList<LonelyParenItem> lonelyparens; // extra lonely parens that we can't close within the text
00495 
00496   QTextBlock block = document()->firstBlock();
00497 
00500   QString sopenrx =
00501     "^(?:("+QStringList(klfListMap(ParsedBlock::parenSpecs.openParenModifiers(), &QRegExp::escape)).join("|")+")\\s*)?"
00502     "(" + QStringList(klfListMap(ParsedBlock::parenSpecs.openParenList(), &QRegExp::escape)).join("|")+")";
00503   QString scloserx =
00504     "^(?:("+QStringList(klfListMap(ParsedBlock::parenSpecs.closeParenModifiers(), &QRegExp::escape)).join("|")+")\\s*)?"
00505     "(" + QStringList(klfListMap(ParsedBlock::parenSpecs.closeParenList(), &QRegExp::escape)).join("|")+")";
00506   klfDbg("open-paren-rx string: "<<sopenrx<<"; close-paren-rx string: "<<scloserx);
00507   QRegExp rx_open(sopenrx);
00508   QRegExp rx_close(scloserx);
00509 
00510   // needed to avoid double-parsing of eg. "\\left(" when parsing "\\left(" and then "("
00511   int lastparenparsingendpos = 0;
00512   
00513   _rulestoapply.clear();
00514   pParsedBlocks.clear();
00515   int k;
00516   while (block.isValid()) {
00517     text = block.text();
00518     i = 0;
00519     blockpos = block.position();
00520     blocklens.append(block.length());
00521 
00522     while (text.length() < block.length()) {
00523       text += "\n";
00524     }
00525 
00526     i = 0;
00527     while ( i < text.length() ) {
00528       if (text[i] == '%') {
00529         k = 0;
00530         while (i+k < text.length() && text[i+k] != '\n')
00531           ++k;
00532         _rulestoapply.append(FormatRule(blockpos+i, k, FComment));
00533         pParsedBlocks.append(ParsedBlock(ParsedBlock::Comment, blockpos+i, k));
00534         i += k + 1;
00535         continue;
00536       }
00537       if ( blockpos+i >= lastparenparsingendpos && rx_open.indexIn(text.mid(i)) != -1) {
00538         ParenItem p;
00539         p.isopening = true;
00540         p.parenstr = rx_open.cap(2);
00541         p.modifier = rx_open.cap(1);
00542         p.beginpos = blockpos+i;
00543         p.endpos = blockpos+i+rx_open.matchedLength();
00544         p.pos = blockpos+i+p.modifier.length();
00545         p.highlight = (_caretpos == p.caretHoverPos());
00546         parens.push(p);
00547         lastparenparsingendpos = p.endpos;
00548       }
00549       else if ( blockpos+i >= lastparenparsingendpos && rx_close.indexIn(text.mid(i)) != -1) {
00550         ParenItem cp;
00551         cp.isopening = false;
00552         cp.parenstr = rx_close.cap(2);
00553         cp.modifier = rx_close.cap(1);
00554         cp.beginpos = blockpos+i;
00555         cp.pos = blockpos+i+cp.modifier.length();
00556         cp.endpos = blockpos+i+rx_close.matchedLength();
00557         cp.highlight = (_caretpos == cp.caretHoverPos());
00558         lastparenparsingendpos = cp.endpos;
00559         
00560         ParenItem p;
00561         if (!parens.empty()) {
00562           // first try to match the same paren type, perhaps leaving a lonely unmatched paren between,
00563           // eg. in "sin[\theta(1+t]", match both square brackets leaving the paren lonely.
00564           // Do this on a copy of the stack, in case we don't find a matching paren
00565           QStack<ParenItem> ptrymatch = parens;
00566           QList<LonelyParenItem> extralonelyparens;
00567           while (ptrymatch.size() && !cp.matches(ptrymatch.top())) {
00568             extralonelyparens << LonelyParenItem(ptrymatch.top(), cp.beginpos);
00569             ptrymatch.pop();
00570           }
00571           if (ptrymatch.size()) { // found match
00572             parens = ptrymatch;
00573             lonelyparens << extralonelyparens;
00574             p = parens.top();
00575             parens.pop();
00576           } else {
00577             // No match found, report a lonely paren.
00578             int topparenstackpos = 0;
00579             if (parens.size()) {
00580               topparenstackpos = parens.top().endpos;
00581             }
00582             lonelyparens << LonelyParenItem(cp, topparenstackpos);
00583             continue; // mismatch will be reported when processing lonely parens
00584           }
00585         } else {
00586           lonelyparens << LonelyParenItem(cp, 0);
00587           continue; // mismatch will be reported when processing lonely parens
00588         }
00589         Format col;
00590         if (cp.matches(p))
00591           col = FParenMatch;
00592         else
00593           col = FParenMismatch;
00594 
00595         // does this rule span multiple paragraphs, and do we need to show it (eg. cursor right after paren)
00596         if (p.highlight || cp.highlight) {
00597           if (pConf.highlightParensOnly) {
00598             _rulestoapply.append(FormatRule(p.pos, p.poslength(), col, true));
00599             _rulestoapply.append(FormatRule(cp.pos, cp.poslength(), col, true));
00600           } else {
00601             _rulestoapply.append(FormatRule(p.pos, cp.endpos - p.pos, col, true));
00602           }
00603         }
00604         ParsedBlock pblk1(ParsedBlock::Paren, p.beginpos, p.beginposlength());
00605         ParsedBlock pblk2(ParsedBlock::Paren, cp.beginpos, cp.beginposlength());
00606         pblk1.parenmatch = ((col == FParenMatch) ? ParsedBlock::Matched : ParsedBlock::Mismatched);
00607         pblk1.parenisopening = true;
00608         pblk1.parenSpecIndex =
00609           ParsedBlock::parenSpecs.identifyParen(p.parenstr, KLFLatexParenSpecs::IdentifyFlagOpen);
00610         pblk1.parenstr = p.parenstr;
00611         pblk1.parenmodifier = p.modifier;
00612         pblk1.parenotherpos = cp.beginpos;
00613         pblk2.parenmatch = pblk1.parenmatch;
00614         pblk2.parenisopening = false;
00615         pblk2.parenSpecIndex =
00616           ParsedBlock::parenSpecs.identifyParen(cp.parenstr, KLFLatexParenSpecs::IdentifyFlagClose);
00617         pblk2.parenstr = cp.parenstr;
00618         pblk2.parenmodifier = cp.modifier;
00619         pblk2.parenotherpos = p.beginpos;
00620         pParsedBlocks.append(pblk1);
00621         pParsedBlocks.append(pblk2);
00622       }
00623 
00624       if (text[i] == '\\') { // a keyword ("\symbol")
00625         ++i;
00626         k = 0;
00627         if (i >= text.length())
00628           continue;
00629         while (i+k < text.length() && ( (text[i+k] >= 'a' && text[i+k] <= 'z') ||
00630                                         (text[i+k] >= 'A' && text[i+k] <= 'Z') ))
00631           ++k;
00632         if (k == 0 && i+1 < text.length())
00633           k = 1;
00634 
00635         QString symbol = text.mid(i-1,k+1); // from i-1, length k+1
00636 
00637         _rulestoapply.append(FormatRule(blockpos+i-1, k+1, FKeyWord));
00638         ParsedBlock pblk(ParsedBlock::Keyword, blockpos+i-1, k+1);
00639         pblk.keyword = symbol;
00640         pParsedBlocks.append(pblk);
00641 
00642         if (symbol.size() > 1) { // no empty backslash
00643           klfDbg("symbol="<<symbol<<" i="<<i<<" k="<<k<<" caretpos="<<_caretpos<<" blockpos="<<blockpos);
00644           if ( (_caretpos < blockpos+i ||_caretpos >= blockpos+i+k+1) &&
00645                !pTypedSymbols.contains(symbol)) { // not typing symbol
00646             klfDbg("newSymbolTyped() about to be emitted for : "<<symbol);
00647             emit newSymbolTyped(symbol);
00648             pTypedSymbols.append(symbol);
00649           }
00650         }
00651         i += k;
00652         continue;
00653       }
00654 
00655       if (!text[i].isPrint() && text[i] != '\n' && text[i] != '\t' && text[i] != '\r') {
00657         _rulestoapply.append(FormatRule(blockpos+i-1, blockpos+i+1, FParenMismatch));
00658       }
00659 
00660       ++i;
00661     }
00662 
00663     block = block.next();
00664   }
00665 
00666   QTextBlock lastblock = document()->lastBlock();
00667 
00668   int globendpos = lastblock.position()+lastblock.length();
00669 
00670   klfDbg("maybe have some parens left, that are to be shown as lonely? "<<!parens.empty()) ;
00671 
00672   // collect lonely parens list: all unclosed parens should be added to the list of collected
00673   // unclosed parens.
00674   while (!parens.empty()) {
00675     lonelyparens << LonelyParenItem(parens.top(), globendpos);
00676     parens.pop();
00677   }
00678 
00679   klfDbg("about to treat "<<lonelyparens.size()<<" lonely parens...") ;
00680 
00681   for (k = 0; k < lonelyparens.size(); ++k) {
00682     // for each unclosed paren
00683     LonelyParenItem p = lonelyparens[k];
00684 
00685     ParsedBlock pblk(ParsedBlock::Paren, p.beginpos, p.beginposlength());
00686     pblk.parenmatch = ParsedBlock::Lonely;
00687     pblk.parenisopening = p.isopening;
00688     uint iff = p.isopening ? KLFLatexParenSpecs::IdentifyFlagOpen : KLFLatexParenSpecs::IdentifyFlagClose;
00689     pblk.parenSpecIndex =
00690       ParsedBlock::parenSpecs.identifyParen(p.parenstr, iff);
00691     pblk.parenstr = p.parenstr;
00692     pblk.parenmodifier = p.modifier;
00693     pblk.parenotherpos = -1;
00694 
00695     pParsedBlocks.append(pblk);
00696 
00697     // if the paren was marked with flag that it can be alone, do not report error
00698     if (pblk.parenSpecIndex >= 0 &&
00699         (ParsedBlock::parenSpecs.parenSpecList()[pblk.parenSpecIndex].flags &
00700          KLFLatexParenSpecs::ParenSpec::AllowAlone)) {
00701       continue;
00702     }
00703 
00704     // otherwise, report the lonely paren
00705 
00706     int chp = p.caretHoverPos();
00707     if (chp == _caretpos) {
00708       if (pConf.highlightParensOnly) {
00709         _rulestoapply.append(FormatRule(p.pos, p.poslength(), FParenMismatch, true));
00710       } else {
00711         // FormatRule will accept a negative length
00712         _rulestoapply.append(FormatRule(chp, p.unmatchedpos-chp,
00713                                         FParenMismatch, true));
00714       }
00715     }
00716     // highlight the lonely paren
00717     if (pConf.highlightLonelyParens)
00718       _rulestoapply.append(FormatRule(p.pos, p.poslength(), FLonelyParen));
00719   }
00720 
00721 }
00722 
00723 QTextCharFormat KLFLatexSyntaxHighlighter::charfmtForFormat(Format f)
00724 {
00725   QTextCharFormat fmt;
00726   switch (f) {
00727   case FNormal:
00728     fmt = QTextCharFormat();
00729     break;
00730   case FKeyWord:
00731     fmt = pConf.fmtKeyword;
00732     break;
00733   case FComment:
00734     fmt = pConf.fmtComment;
00735     break;
00736   case FParenMatch:
00737     fmt = pConf.fmtParenMatch;
00738     break;
00739   case FParenMismatch:
00740     fmt = pConf.fmtParenMismatch;
00741     break;
00742   case FLonelyParen:
00743     fmt = pConf.fmtLonelyParen;
00744     break;
00745   default:
00746     fmt = QTextCharFormat();
00747     break;
00748   };
00749   return fmt;
00750 }
00751 
00752 
00753 void KLFLatexSyntaxHighlighter::highlightBlock(const QString& text)
00754 {
00755   KLF_DEBUG_BLOCK(KLF_FUNC_NAME) ;
00756 
00757   klfDbg("text is "<<text);
00758 
00759   if ( ! pConf.enabled )
00760     return; // forget everything about synt highlight if we don't want it.
00761 
00762   QTextBlock block = currentBlock();
00763 
00764   //  printf("\t -- block/position=%d\n", block.position());
00765 
00766   if (block.position() == 0) {
00767     setCaretPos(_textedit->textCursor().position());
00768     parseEverything();
00769   }
00770 
00771   QList<FormatRule> blockfmtrules;
00772 
00773   blockfmtrules.append(FormatRule(0, text.length(), FNormal));
00774 
00775   int k, j;
00776   for (k = 0; k < _rulestoapply.size(); ++k) {
00777     int start = _rulestoapply[k].pos - block.position();
00778     int len = _rulestoapply[k].len;
00779 
00780     if (start < 0) { // the rule starts before current paragraph
00781       len += start; // "+" because start is negative
00782       start = 0;
00783     }
00784     if (start > text.length())
00785       continue;
00786     if (len > text.length() - start)
00787       len = text.length() - start;
00788 
00789     if (len <= 0)
00790       continue; // empty rule...
00791     
00792     // apply rule
00793     klfDbg("Applying rule start="<<start<<", len="<<len<<", ...") ;
00794     blockfmtrules.append(FormatRule(start, len, _rulestoapply[k].format, _rulestoapply[k].onlyIfFocus));
00795   }
00796 
00797   bool hasfocus = _textedit->hasFocus();
00798 
00799   klfDbg("About to merge text formats... text.length()="<<text.length()) ;
00800   QVector<QTextCharFormat> charformats;
00801   charformats.resize(text.length());
00802   for (k = 0; k < blockfmtrules.size(); ++k) {
00803     klfDbg("got block-fmt-rule #"<<k<<"; start="<<blockfmtrules[k].pos<<", len="<<blockfmtrules[k].len
00804            <<", end="<<blockfmtrules[k].end()) ;
00805     for (j = blockfmtrules[k].pos; j < blockfmtrules[k].end(); ++j) {
00806       if ( ! blockfmtrules[k].onlyIfFocus || hasfocus )
00807         charformats[j].merge(charfmtForFormat(blockfmtrules[k].format));
00808     }
00809   }
00810   klfDbg("About to apply char formats...") ;
00811   for (j = 0; j < charformats.size(); ++j) {
00812     setFormat(j, 1, charformats[j]);
00813   }
00814   
00815   return;
00816 }
00817 
00818 
00819 void KLFLatexSyntaxHighlighter::resetEditing()
00820 {
00821   pTypedSymbols = QStringList();
00822 }
00823 
00824 
00825 
00826 
00827 QDebug operator<<(QDebug str, const KLFLatexSyntaxHighlighter::ParsedBlock& p)
00828 {
00829   QString stype;
00830   switch (p.type) {
00831   case KLFLatexSyntaxHighlighter::ParsedBlock::Normal: stype = "-"; break;
00832   case KLFLatexSyntaxHighlighter::ParsedBlock::Keyword: stype = "Keyword"; break;
00833   case KLFLatexSyntaxHighlighter::ParsedBlock::Comment: stype = "Comment"; break;
00834   case KLFLatexSyntaxHighlighter::ParsedBlock::Paren: stype = "Paren"; break;
00835   default: stype = "<error>"; break;
00836   }
00837   QString smatched;
00838   switch (p.parenmatch) {
00839   case KLFLatexSyntaxHighlighter::ParsedBlock::None: smatched = "-"; break;
00840   case KLFLatexSyntaxHighlighter::ParsedBlock::Matched: smatched = "Matched"; break;
00841   case KLFLatexSyntaxHighlighter::ParsedBlock::Mismatched: smatched = "Mismatched"; break;
00842   case KLFLatexSyntaxHighlighter::ParsedBlock::Lonely: smatched = "Lonely"; break;
00843   default: smatched = "<error>"; break;
00844   }
00845   str << "ParsedBlock["<<stype.toLatin1()<<": "<<p.pos<<"+"<<p.len;
00846   if (p.type == KLFLatexSyntaxHighlighter::ParsedBlock::Keyword) {
00847     str << ", "<<p.keyword;
00848   } else if (KLFLatexSyntaxHighlighter::ParsedBlock::Paren) {
00849     str << ", "<<smatched.toLatin1()<<(p.parenisopening?"(opening)":"(closing)")<<"#"<<p.parenSpecIndex<<" "
00850         <<p.parenmodifier<<p.parenstr<<" otherpos="<<p.parenotherpos;
00851   }
00852   return str << "]";
00853 }

Generated by doxygen 1.7.6.1. The KLatexFormula website is hosted on sourceforge.net