"Fossies" - the Fresh Open Source Software Archive

Member "texstudio-2.12.22/src/latexeditorview.cpp" (15 Jan 2020, 118272 Bytes) of package /linux/misc/texstudio-2.12.22.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) C and C++ source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. For more information about "latexeditorview.cpp" see the Fossies "Dox" file reference documentation and the latest Fossies "Diffs" side-by-side code changes report: 2.12.20_vs_2.12.22.

    1 /***************************************************************************
    2  *   copyright       : (C) 2008 by Benito van der Zander                   *
    3  *   http://www.xm1math.net/texmaker/                                      *
    4  *                                                                         *
    5  *   This program is free software; you can redistribute it and/or modify  *
    6  *   it under the terms of the GNU General Public License as published by  *
    7  *   the Free Software Foundation; either version 2 of the License, or     *
    8  *   (at your option) any later version.                                   *
    9  *                                                                         *
   10  ***************************************************************************/
   11 
   12 #include "latexeditorview.h"
   13 #include "latexeditorview_config.h"
   14 
   15 #include "filedialog.h"
   16 #include "latexcompleter.h"
   17 #include "latexdocument.h"
   18 #include "smallUsefulFunctions.h"
   19 #include "spellerutility.h"
   20 #include "tablemanipulation.h"
   21 
   22 #include "qdocumentline.h"
   23 #include "qdocumentline_p.h"
   24 #include "qdocumentcommand.h"
   25 
   26 #include "qlinemarksinfocenter.h"
   27 #include "qformatfactory.h"
   28 #include "qlanguagedefinition.h"
   29 #include "qnfadefinition.h"
   30 #include "qnfa.h"
   31 
   32 #include "qcodeedit.h"
   33 #include "qeditor.h"
   34 #include "qeditorinputbinding.h"
   35 #include "qlinemarkpanel.h"
   36 #include "qlinenumberpanel.h"
   37 #include "qfoldpanel.h"
   38 #include "qgotolinepanel.h"
   39 #include "qlinechangepanel.h"
   40 #include "qstatuspanel.h"
   41 #include "qsearchreplacepanel.h"
   42 #include "latexrepository.h"
   43 
   44 #include "latexparser/latexparsing.h"
   45 
   46 #include "latexcompleter_config.h"
   47 
   48 #include "scriptengine.h"
   49 #include "diffoperations.h"
   50 
   51 #include "help.h"
   52 
   53 #include "bidiextender.h"
   54 
   55 //------------------------------Default Input Binding--------------------------------
   56 /*!
   57  * \brief default keyboard binding for normal operation
   58  */
   59 class DefaultInputBinding: public QEditorInputBinding
   60 {
   61     //  Q_OBJECT not possible because inputbinding is no qobject
   62 public:
   63     DefaultInputBinding(): completerConfig(nullptr), editorViewConfig(nullptr), contextMenu(nullptr), isDoubleClick(false) {}
   64     virtual QString id() const
   65     {
   66         return "TXS::DefaultInputBinding";
   67     }
   68     virtual QString name() const
   69     {
   70         return "TXS::DefaultInputBinding";
   71     }
   72 
   73     virtual bool keyPressEvent(QKeyEvent *event, QEditor *editor);
   74     virtual void postKeyPressEvent(QKeyEvent *event, QEditor *editor);
   75     virtual bool keyReleaseEvent(QKeyEvent *event, QEditor *editor);
   76     virtual bool mousePressEvent(QMouseEvent *event, QEditor *editor);
   77     virtual bool mouseReleaseEvent(QMouseEvent *event, QEditor *editor);
   78     virtual bool mouseDoubleClickEvent(QMouseEvent *event, QEditor *editor);
   79     virtual bool mouseMoveEvent(QMouseEvent *event, QEditor *editor);
   80     virtual bool contextMenuEvent(QContextMenuEvent *event, QEditor *editor);
   81 private:
   82     bool runMacros(QKeyEvent *event, QEditor *editor);
   83     bool autoInsertLRM(QKeyEvent *event, QEditor *editor);
   84     void checkLinkOverlay(QPoint mousePos, Qt::KeyboardModifiers modifiers, QEditor *editor);
   85     friend class LatexEditorView;
   86     const LatexCompleterConfig *completerConfig;
   87     const LatexEditorViewConfig *editorViewConfig;
   88     QList<QAction *> baseActions;
   89 
   90     QMenu *contextMenu;
   91     QString lastSpellCheckedWord;
   92 
   93     QPoint lastMousePressLeft;
   94     bool isDoubleClick;  // event sequence of a double click: press, release, double click, release - this is true on the second release
   95     Qt::KeyboardModifiers modifiersWhenPressed;
   96 };
   97 
   98 static const QString LRMStr = QChar(LRM);
   99 
  100 bool DefaultInputBinding::runMacros(QKeyEvent *event, QEditor *editor)
  101 {
  102     Q_ASSERT(completerConfig);
  103     QLanguageDefinition *language = editor->document() ? editor->document()->languageDefinition() : nullptr;
  104     QDocumentLine line = editor->cursor().selectionStart().line();
  105     int column = editor->cursor().selectionStart().columnNumber();
  106     QString prev = line.text().mid(0, column) + event->text(); //TODO: optimize
  107     foreach (const Macro &m, completerConfig->userMacros) {
  108         if (!m.isActiveForTrigger(Macro::ST_REGEX)) continue;
  109         if (!m.isActiveForLanguage(language)) continue;
  110         if (!(m.isActiveForFormat(line.getFormatAt(column)) || (column > 0 && m.isActiveForFormat(line.getFormatAt(column - 1))))) continue; //two checks, so it works at beginning and end of an environment
  111         QRegExp &r = const_cast<QRegExp &>(m.triggerRegex); //a const qregexp doesn't exist
  112         if (r.indexIn(prev) != -1) {
  113             QDocumentCursor c = editor->cursor();
  114             bool block = false;
  115             int realMatchLen = r.matchedLength();
  116             if (m.triggerLookBehind) realMatchLen -= r.cap(1).length();
  117             if (c.hasSelection() || realMatchLen > 1)
  118                 block = true;
  119             if (block) editor->document()->beginMacro();
  120             if (c.hasSelection()) {
  121                 editor->cutBuffer = c.selectedText();
  122                 c.removeSelectedText();
  123             }
  124             if (m.triggerRegex.matchedLength() > 1) {
  125                 c.movePosition(realMatchLen - 1, QDocumentCursor::PreviousCharacter, QDocumentCursor::KeepAnchor);
  126                 c.removeSelectedText();
  127                 editor->setCursor(c);
  128             }
  129 
  130             LatexEditorView *view = editor->property("latexEditor").value<LatexEditorView *>();
  131             REQUIRE_RET(view, true);
  132             emit view->execMacro(m, MacroExecContext(Macro::ST_REGEX, r.capturedTexts()));
  133             if (block) editor->document()->endMacro();
  134             editor->cutBuffer.clear();
  135             editor->emitCursorPositionChanged(); //prevent rogue parenthesis highlightations
  136             /*          if (editor->languageDefinition())
  137             editor->languageDefinition()->clearMatches(editor->document());
  138             */
  139             return true;
  140         }
  141     }
  142     return false;
  143 }
  144 
  145 bool DefaultInputBinding::autoInsertLRM(QKeyEvent *event, QEditor *editor)
  146 {
  147     const QString &text = event->text();
  148     if (editorViewConfig->autoInsertLRM && text.length() == 1 && editor->cursor().isRTL()) {
  149         if (text.at(0) == '}') {
  150             bool autoOverride = editor->isAutoOverrideText("}");
  151             bool previousIsLRM = editor->cursor().previousChar().unicode() == LRM;
  152             bool block = previousIsLRM || autoOverride;
  153             if (block) editor->document()->beginMacro();
  154             if (previousIsLRM) editor->cursor().deletePreviousChar(); //todo mirrors
  155             if (autoOverride) {
  156                 editor->write("}"); //separated, so autooverride works
  157                 editor->write(LRMStr);
  158             } else editor->write("}" + LRMStr);
  159             if (block) editor->document()->endMacro();
  160             return true;
  161         }
  162     }
  163     return false;
  164 }
  165 
  166 void DefaultInputBinding::checkLinkOverlay(QPoint mousePos, Qt::KeyboardModifiers modifiers, QEditor *editor)
  167 {
  168     if (modifiers == Qt::ControlModifier) {
  169         LatexEditorView *edView = qobject_cast<LatexEditorView *>(editor->parentWidget());
  170         QDocumentCursor cursor = editor->cursorForPosition(mousePos);
  171         edView->checkForLinkOverlay(cursor);
  172     } else {
  173         // reached for example when Ctrl+Shift is pressed
  174         LatexEditorView *edView = qobject_cast<LatexEditorView *>(editor->parentWidget()); //a qobject is necessary to retrieve events
  175         edView->removeLinkOverlay();
  176     }
  177 }
  178 
  179 bool DefaultInputBinding::keyPressEvent(QKeyEvent *event, QEditor *editor)
  180 {
  181     if (LatexEditorView::completer && LatexEditorView::completer->acceptTriggerString(event->text())
  182             && (editor->currentPlaceHolder() < 0 || editor->currentPlaceHolder() >= editor->placeHolderCount() || editor->getPlaceHolder(editor->currentPlaceHolder()).mirrors.isEmpty() ||  editor->getPlaceHolder(editor->currentPlaceHolder()).affector != BracketInvertAffector::instance())
  183             && !editor->flag(QEditor::Overwrite))  {
  184         //update completer if necessary
  185         editor->emitNeedUpdatedCompleter();
  186         bool autoOverriden = editor->isAutoOverrideText(event->text());
  187         if (editorViewConfig->autoInsertLRM && event->text() == "\\" && editor->cursor().isRTL())
  188             editor->write(LRMStr + event->text());
  189         else
  190             editor->write(event->text());
  191         if (autoOverriden) LatexEditorView::completer->complete(editor, LatexCompleter::CF_OVERRIDEN_BACKSLASH);
  192         else {
  193             int flags = Parsing::getCompleterContext(editor->cursor().line().handle(), editor->cursor().columnNumber());
  194             LatexEditorView::completer->complete(editor, LatexCompleter::CompletionFlag(flags));
  195         }
  196         return true;
  197     }
  198     if (!event->text().isEmpty()) {
  199         if (!editor->flag(QEditor::Overwrite) && runMacros(event, editor))
  200             return true;
  201         if (autoInsertLRM(event, editor))
  202             return true;
  203         if (event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return) {
  204             // if cursor is at the end of a placeholder, remove that placeholder
  205             int phId = editor->currentPlaceHolder();
  206             if (phId >= 0) {
  207                 PlaceHolder ph = editor->getPlaceHolder(phId);
  208                 if (editor->cursor().lineNumber() == ph.cursor.lineNumber() &&
  209                         editor->cursor().columnNumber() == ph.cursor.columnNumber()) {
  210                     editor->removePlaceHolder(phId);
  211                     return true;
  212                 }
  213             }
  214         }
  215     } else {
  216         if (event->key() == Qt::Key_Control) {
  217             editor->setMouseTracking(true);
  218             QPoint mousePos(editor->mapToFrame(editor->mapFromGlobal(QCursor::pos())));
  219             checkLinkOverlay(mousePos, event->modifiers(), editor);
  220         }
  221     }
  222     if (LatexEditorView::hideTooltipWhenLeavingLine != -1 && editor->cursor().lineNumber() != LatexEditorView::hideTooltipWhenLeavingLine) {
  223         LatexEditorView::hideTooltipWhenLeavingLine = -1;
  224         QToolTip::hideText();
  225     }
  226     return false;
  227 }
  228 
  229 void DefaultInputBinding::postKeyPressEvent(QKeyEvent *event, QEditor *editor)
  230 {
  231     QString txt=event->text();
  232     if(txt.length()!=1)
  233         return;
  234     QChar c=txt.at(0);
  235     if ( c== ',' || c.isLetter()) {
  236         LatexEditorView *view = editor->property("latexEditor").value<LatexEditorView *>();
  237         Q_ASSERT(view);
  238         if (completerConfig && completerConfig->enabled)
  239             view->mayNeedToOpenCompleter(c!=',');
  240     }
  241 }
  242 
  243 bool DefaultInputBinding::keyReleaseEvent(QKeyEvent *event, QEditor *editor)
  244 {
  245     if (event->key() == Qt::Key_Control) {
  246         editor->setMouseTracking(false);
  247         LatexEditorView *edView = qobject_cast<LatexEditorView *>(editor->parentWidget()); //a qobject is necessary to retrieve events
  248         edView->removeLinkOverlay();
  249     }
  250     return false;
  251 }
  252 
  253 bool DefaultInputBinding::mousePressEvent(QMouseEvent *event, QEditor *editor)
  254 {
  255     LatexEditorView *edView = nullptr;
  256 
  257     switch (event->button()) {
  258     case Qt::XButton1:
  259         edView = qobject_cast<LatexEditorView *>(editor->parentWidget());
  260         emit edView->mouseBackPressed();
  261         return true;
  262     case Qt::XButton2:
  263         edView = qobject_cast<LatexEditorView *>(editor->parentWidget());
  264         emit edView->mouseForwardPressed();
  265         return true;
  266     case Qt::LeftButton:
  267         edView = qobject_cast<LatexEditorView *>(editor->parentWidget());
  268         emit edView->cursorChangeByMouse();
  269         lastMousePressLeft = event->pos();
  270         modifiersWhenPressed = event->modifiers();
  271         return false;
  272     default:
  273         return false;
  274     }
  275 
  276     return false;
  277 }
  278 
  279 bool DefaultInputBinding::mouseReleaseEvent(QMouseEvent *event, QEditor *editor)
  280 {
  281     if (isDoubleClick) {
  282         isDoubleClick = false;
  283         return false;
  284     }
  285     isDoubleClick = false;
  286 
  287     if (event->modifiers() == Qt::ControlModifier && modifiersWhenPressed == event->modifiers() && event->button() == Qt::LeftButton) {
  288         // Ctrl+LeftClick
  289         int distanceSqr = (event->pos().x() - lastMousePressLeft.x()) * (event->pos().x() - lastMousePressLeft.x()) + (event->pos().y() - lastMousePressLeft.y()) * (event->pos().y() - lastMousePressLeft.y());
  290         if (distanceSqr > 4) // allow the user to accidentially move the mouse a bit
  291             return false;
  292 
  293         LatexEditorView *edView = qobject_cast<LatexEditorView *>(editor->parentWidget()); //a qobject is necessary to retrieve events
  294         if (!edView) return false;
  295         QDocumentCursor cursor = editor->cursorForPosition(editor->mapToContents(event->pos()));
  296 
  297         if (edView->hasLinkOverlay()) {
  298             LinkOverlay lo = edView->getLinkOverlay();
  299             switch (lo.type) {
  300             case LinkOverlay::RefOverlay:
  301                 emit edView->gotoDefinition(cursor);
  302                 return true;
  303             case LinkOverlay::FileOverlay:
  304                 edView->openFile(lo.text());
  305                 return true;
  306             case LinkOverlay::UrlOverlay:
  307                 if (!QDesktopServices::openUrl(lo.text())) {
  308                     UtilsUi::txsWarning(LatexEditorView::tr("Could not open url:") + "\n" + lo.text());
  309                 }
  310                 return true;
  311             case LinkOverlay::UsepackageOverlay:
  312                 edView->openPackageDocumentation(lo.text());
  313                 return true;
  314             case LinkOverlay::BibFileOverlay:
  315                 edView->openFile(lo.text(), "bib");
  316                 return true;
  317             case LinkOverlay::CiteOverlay:
  318                 emit edView->gotoDefinition(cursor);
  319                 return true;
  320             case LinkOverlay::CommandOverlay:
  321                 emit edView->gotoDefinition(cursor);
  322                 return true;
  323             case LinkOverlay::EnvOverlay:
  324                 emit edView->gotoDefinition(cursor);
  325                 return true;
  326             case LinkOverlay::Invalid:
  327                 break;
  328             }
  329         }
  330 
  331         if (!editor->languageDefinition()) return false;
  332         if (editor->languageDefinition()->language() != "(La)TeX")
  333             return false;
  334         emit edView->syncPDFRequested(cursor);
  335         return true;
  336     }
  337     return false;
  338 }
  339 
  340 bool DefaultInputBinding::mouseDoubleClickEvent(QMouseEvent *event, QEditor *editor)
  341 {
  342     Q_UNUSED(event)
  343     Q_UNUSED(editor)
  344     isDoubleClick = true;
  345     return false;
  346 }
  347 
  348 bool DefaultInputBinding::contextMenuEvent(QContextMenuEvent *event, QEditor *editor)
  349 {
  350     if (!contextMenu) contextMenu = new QMenu(nullptr);
  351     contextMenu->clear();
  352     contextMenu->setProperty("isSpellingPopulated", QVariant());  // delete information on spelling
  353     QDocumentCursor cursor;
  354     if (event->reason() == QContextMenuEvent::Mouse) cursor = editor->cursorForPosition(editor->mapToContents(event->pos()));
  355     else cursor = editor->cursor();
  356     LatexEditorView *edView = qobject_cast<LatexEditorView *>(editor->parentWidget()); //a qobject is necessary to retrieve events
  357     REQUIRE_RET(edView, false);
  358 
  359     // check for context menu on preview picture
  360     QRect pictRect = cursor.line().getCookie(QDocumentLine::PICTURE_COOKIE_DRAWING_POS).toRect();
  361     if (pictRect.isValid()) {
  362         QPoint posInDocCoordinates(event->pos().x() + edView->editor->horizontalOffset(), event->pos().y() + edView->editor->verticalOffset());
  363         if (pictRect.contains(posInDocCoordinates)) {
  364             // get removePreviewAction
  365             // ok, this is not an ideal way of doing it because (i) it has to be in the baseActions (at least we Q_ASSERT this) and (ii) the iteration over the baseActions
  366             // Alternatives: 1) include configmanager and use ConfigManager->getInstance()->getManagedAction() - Disadvantage: additional dependency
  367             //               2) explicitly pass it to the editorView (like the base actions, but separate) - Disadvantage: code overhead
  368             //               3) Improve the concept of base actions:
  369             //                    LatexEditorView::addContextAction(QAction); called when creating the editorView
  370             //                    LatexEditorView::getContextAction(QString); used here to populate the menu
  371             bool removePreviewActionFound = false;
  372             foreach (QAction *act, baseActions) {
  373                 if (act->objectName().endsWith("removePreviewLatex")) {
  374                     act->setData(posInDocCoordinates);
  375                     contextMenu->addAction(act);
  376                     removePreviewActionFound = true;
  377                     break;
  378                 }
  379             }
  380             Q_ASSERT(removePreviewActionFound);
  381 
  382 
  383             QVariant vPixmap = cursor.line().getCookie(QDocumentLine::PICTURE_COOKIE);
  384             if (vPixmap.isValid()) {
  385                 (contextMenu->addAction("Copy Image", edView, SLOT(copyImageFromAction())))->setData(vPixmap);
  386                 (contextMenu->addAction("Save Image As...", edView, SLOT(saveImageFromAction())))->setData(vPixmap);
  387             }
  388             contextMenu->exec(event->globalPos());
  389             return true;
  390         }
  391     }
  392 
  393     // normal context menu
  394     bool validPosition = cursor.isValid() && cursor.line().isValid();
  395     //LatexParser::ContextType context = LatexParser::Unknown;
  396     QString ctxCommand;
  397     if (validPosition) {
  398         QFormatRange fr;
  399         //spell checking
  400 
  401         if (edView->speller) {
  402             int pos;
  403             if (cursor.hasSelection()) pos = (cursor.columnNumber() + cursor.anchorColumnNumber()) / 2;
  404             else pos = cursor.columnNumber();
  405 
  406             foreach (const int f, edView->grammarFormats) {
  407                 fr = cursor.line().getOverlayAt(pos, f);
  408                 if (fr.length > 0 && fr.format == f) {
  409                     QVariant var = cursor.line().getCookie(QDocumentLine::GRAMMAR_ERROR_COOKIE);
  410                     if (var.isValid()) {
  411                         QDocumentCursor wordSelection(editor->document(), cursor.lineNumber(), fr.offset);
  412                         wordSelection.movePosition(fr.length, QDocumentCursor::NextCharacter, QDocumentCursor::KeepAnchor);
  413                         editor->setCursor(wordSelection);
  414 
  415                         const QList<GrammarError> &errors = var.value<QList<GrammarError> >();
  416                         for (int i = 0; i < errors.size(); i++)
  417                             if (errors[i].offset <= cursor.columnNumber() && errors[i].offset + errors[i].length >= cursor.columnNumber()) {
  418                                 edView->addReplaceActions(contextMenu, errors[i].corrections, true);
  419                                 break;
  420                             }
  421                     }
  422                 }
  423             }
  424 
  425             fr = cursor.line().getOverlayAt(pos, SpellerUtility::spellcheckErrorFormat);
  426             if (fr.length > 0 && fr.format == SpellerUtility::spellcheckErrorFormat) {
  427                 QString word = cursor.line().text().mid(fr.offset, fr.length);
  428                 if (!(editor->cursor().hasSelection() && editor->cursor().selectedText().length() > 0) || editor->cursor().selectedText() == word
  429                         || editor->cursor().selectedText() == lastSpellCheckedWord) {
  430                     lastSpellCheckedWord = word;
  431                     word = latexToPlainWord(word);
  432                     QDocumentCursor wordSelection(editor->document(), cursor.lineNumber(), fr.offset);
  433                     wordSelection.movePosition(fr.length, QDocumentCursor::NextCharacter, QDocumentCursor::KeepAnchor);
  434                     editor->setCursor(wordSelection);
  435 
  436                     if ((editorViewConfig->contextMenuSpellcheckingEntryLocation == 0) ^ (event->modifiers() & editorViewConfig->contextMenuKeyboardModifiers)) {
  437                         edView->addSpellingActions(contextMenu, lastSpellCheckedWord, false);
  438                         contextMenu->addSeparator();
  439                     } else {
  440                         QMenu *spellingMenu = contextMenu->addMenu(LatexEditorView::tr("Spelling"));
  441                         spellingMenu->setProperty("word", lastSpellCheckedWord);
  442                         edView->connect(spellingMenu, SIGNAL(aboutToShow()), edView, SLOT(populateSpellingMenu()));
  443                     }
  444                 }
  445             }
  446         }
  447         //citation checking
  448         int f = edView->citationMissingFormat;
  449         if (cursor.hasSelection()) fr = cursor.line().getOverlayAt((cursor.columnNumber() + cursor.anchorColumnNumber()) / 2, f);
  450         else fr = cursor.line().getOverlayAt(cursor.columnNumber(), f);
  451         if (fr.length > 0 && fr.format == f) {
  452             QString word = cursor.line().text().mid(fr.offset, fr.length);
  453             editor->setCursor(editor->document()->cursor(cursor.lineNumber(), fr.offset, cursor.lineNumber(), fr.offset + fr.length));
  454             QAction *act = new QAction(LatexEditorView::tr("New BibTeX Entry %1").arg(word), contextMenu);
  455             edView->connect(act, SIGNAL(triggered()), edView, SLOT(requestCitation()));
  456             contextMenu->addAction(act);
  457             contextMenu->addSeparator();
  458         }
  459         //check input/include
  460         //find context of cursor
  461         QDocumentLineHandle *dlh = cursor.line().handle();
  462         TokenList tl = dlh->getCookieLocked(QDocumentLine::LEXER_COOKIE).value<TokenList>();
  463         int i = Parsing::getTokenAtCol(tl, cursor.columnNumber());
  464         Token tk;
  465         if (i >= 0)
  466             tk = tl.at(i);
  467 
  468         if (tk.type == Token::file) {
  469             QAction *act = new QAction(LatexEditorView::tr("Open %1").arg(tk.getText()), contextMenu);
  470             act->setData(tk.getText());
  471             edView->connect(act, SIGNAL(triggered()), edView, SLOT(openExternalFile()));
  472             contextMenu->addAction(act);
  473         }
  474         // bibliography command
  475         if (tk.type == Token::bibfile) {
  476             QAction *act = new QAction(LatexEditorView::tr("Open Bibliography"), contextMenu);
  477             QString bibFile;
  478             bibFile = tk.getText() + ".bib";
  479             act->setData(bibFile);
  480             edView->connect(act, SIGNAL(triggered()), edView, SLOT(openExternalFile()));
  481             contextMenu->addAction(act);
  482         }
  483         //package help
  484         if (tk.type == Token::package || tk.type == Token::documentclass) {
  485             QAction *act = new QAction(LatexEditorView::tr("Open package documentation"), contextMenu);
  486             QString packageName = tk.getText();
  487             act->setText(act->text().append(QString(" (%1)").arg(packageName)));
  488             act->setData(packageName);
  489             edView->connect(act, SIGNAL(triggered()), edView, SLOT(openPackageDocumentation()));
  490             contextMenu->addAction(act);
  491         }
  492         // help for any "known" command
  493         if (tk.type == Token::command) {
  494             ctxCommand = tk.getText();
  495             QString command = ctxCommand;
  496             if (ctxCommand == "\\begin" || ctxCommand == "\\end")
  497                 command = ctxCommand + "{" + Parsing::getArg(tl.mid(i + 1), dlh, 0, ArgumentList::Mandatory) + "}";
  498             QString package = edView->document->parent->findPackageByCommand(command);
  499             package.chop(4);
  500             if (!package.isEmpty()) {
  501                 QAction *act = new QAction(LatexEditorView::tr("Open package documentation"), contextMenu);
  502                 act->setText(act->text().append(QString(" (%1)").arg(package)));
  503                 act->setData(package + "#" + command);
  504                 edView->connect(act, SIGNAL(triggered()), edView, SLOT(openPackageDocumentation()));
  505                 contextMenu->addAction(act);
  506             }
  507         }
  508         // help for "known" environments
  509         if (tk.type == Token::beginEnv || tk.type == Token::env) {
  510             QString command = "\\begin{" + tk.getText() + "}";
  511             QString package = edView->document->parent->findPackageByCommand(command);
  512             package.chop(4);
  513             if (!package.isEmpty()) {
  514                 QAction *act = new QAction(LatexEditorView::tr("Open package documentation"), contextMenu);
  515                 act->setText(act->text().append(QString(" (%1)").arg(package)));
  516                 act->setData(package + "#" + command);
  517                 edView->connect(act, SIGNAL(triggered()), edView, SLOT(openPackageDocumentation()));
  518                 contextMenu->addAction(act);
  519             }
  520         }
  521         if (/* tk.type==Tokens::bibRef || TODO: bibliography references not yet handled by token system */ tk.type == Token::labelRef) {
  522             QAction *act = new QAction(LatexEditorView::tr("Go to Definition"), contextMenu);
  523             act->setData(QVariant().fromValue<QDocumentCursor>(cursor));
  524             edView->connect(act, SIGNAL(triggered()), edView, SLOT(emitGotoDefinitionFromAction()));
  525             contextMenu->addAction(act);
  526         }
  527         if (tk.type == Token::label || tk.type == Token::labelRef || tk.type == Token::labelRefList) {
  528             QAction *act = new QAction(LatexEditorView::tr("Find Usages"), contextMenu);
  529             act->setData(tk.getText());
  530             act->setProperty("doc", QVariant::fromValue<LatexDocument *>(edView->document));
  531             edView->connect(act, SIGNAL(triggered()), edView, SLOT(emitFindLabelUsagesFromAction()));
  532             contextMenu->addAction(act);
  533         }
  534         if (tk.type == Token::word) {
  535             QAction *act = new QAction(LatexEditorView::tr("Thesaurus..."), contextMenu);
  536             act->setData(QPoint(cursor.anchorLineNumber(), cursor.anchorColumnNumber()));
  537             edView->connect(act, SIGNAL(triggered()), edView, SLOT(triggeredThesaurus()));
  538             contextMenu->addAction(act);
  539         }
  540 
  541         //resolve differences
  542         if (edView) {
  543             QList<int> fids;
  544             fids << edView->deleteFormat << edView->insertFormat << edView->replaceFormat;
  545             foreach (int fid, fids) {
  546                 if (cursor.hasSelection()) fr = cursor.line().getOverlayAt((cursor.columnNumber() + cursor.anchorColumnNumber()) / 2, fid);
  547                 else fr = cursor.line().getOverlayAt(cursor.columnNumber(), fid);
  548                 if (fr.length > 0 ) {
  549                     QVariant var = cursor.line().getCookie(QDocumentLine::DIFF_LIST_COOCKIE);
  550                     if (var.isValid()) {
  551                         DiffList diffList = var.value<DiffList>();
  552                         //QString word=cursor.line().text().mid(fr.offset,fr.length);
  553                         DiffOp op;
  554                         op.start = -1;
  555                         foreach (op, diffList) {
  556                             if (op.start <= cursor.columnNumber() && op.start + op.length >= cursor.columnNumber()) {
  557                                 break;
  558                             }
  559                             op.start = -1;
  560                         }
  561                         if (op.start >= 0) {
  562                             QAction *act = new QAction(LatexEditorView::tr("use yours"), contextMenu);
  563                             act->setData(QPoint(cursor.lineNumber(), cursor.columnNumber()));
  564                             edView->connect(act, SIGNAL(triggered()), edView, SLOT(emitChangeDiff()));
  565                             contextMenu->addAction(act);
  566                             act = new QAction(LatexEditorView::tr("use other's"), contextMenu);
  567                             act->setData(QPoint(-cursor.lineNumber() - 1, cursor.columnNumber()));
  568                             edView->connect(act, SIGNAL(triggered()), edView, SLOT(emitChangeDiff()));
  569                             contextMenu->addAction(act);
  570                             break;
  571                         }
  572                     }
  573                 }
  574             }
  575         }
  576         contextMenu->addSeparator();
  577     }
  578     contextMenu->addActions(baseActions);
  579     if (validPosition) {
  580         contextMenu->addSeparator();
  581 
  582         QAction *act = new QAction(LatexEditorView::tr("Go to PDF"), contextMenu);
  583         act->setData(QVariant().fromValue<QDocumentCursor>(cursor));
  584         edView->connect(act, SIGNAL(triggered()), edView, SLOT(emitSyncPDFFromAction()));
  585         contextMenu->addAction(act);
  586     }
  587 
  588 
  589     if (event->reason() == QContextMenuEvent::Mouse) contextMenu->exec(event->globalPos());
  590     else {
  591         QPoint curPoint = editor->cursor().documentPosition();
  592         curPoint.ry() += editor->document()->getLineSpacing();
  593         contextMenu->exec(editor->mapToGlobal(editor->mapFromContents(curPoint)));
  594     }
  595     event->accept();
  596 
  597     return true;
  598 }
  599 
  600 bool DefaultInputBinding::mouseMoveEvent(QMouseEvent *event, QEditor *editor)
  601 {
  602     checkLinkOverlay(editor->mapToContents(event->pos()), event->modifiers(), editor);
  603     return false;
  604 }
  605 
  606 DefaultInputBinding *defaultInputBinding = new DefaultInputBinding();
  607 
  608 
  609 
  610 //----------------------------------LatexEditorView-----------------------------------
  611 LatexCompleter *LatexEditorView::completer = nullptr;
  612 int LatexEditorView::hideTooltipWhenLeavingLine = -1;
  613 
  614 //Q_DECLARE_METATYPE(LatexEditorView *)
  615 
  616 LatexEditorView::LatexEditorView(QWidget *parent, LatexEditorViewConfig *aconfig, LatexDocument *doc) : QWidget(parent), document(nullptr), latexPackageList(nullptr), spellerManager(nullptr), speller(nullptr), useDefaultSpeller(true), curChangePos(-1), config(aconfig), bibReader(nullptr)
  617 {
  618     Q_ASSERT(config);
  619 
  620     QVBoxLayout *mainlay = new QVBoxLayout(this);
  621     mainlay->setSpacing(0);
  622     mainlay->setMargin(0);
  623 
  624     codeeditor = new QCodeEdit(false, this, doc);
  625     editor = codeeditor->editor();
  626 
  627     editor->setProperty("latexEditor", QVariant::fromValue<LatexEditorView *>(this));
  628 
  629     lineMarkPanel = new QLineMarkPanel;
  630     lineMarkPanelAction = codeeditor->addPanel(lineMarkPanel, QCodeEdit::West, false);
  631     lineNumberPanel = new QLineNumberPanel;
  632     lineNumberPanelAction = codeeditor->addPanel(lineNumberPanel, QCodeEdit::West, false);
  633     QFoldPanel *foldPanel = new QFoldPanel;
  634     lineFoldPanelAction = codeeditor->addPanel(foldPanel, QCodeEdit::West, false);
  635     lineChangePanelAction = codeeditor->addPanel(new QLineChangePanel, QCodeEdit::West, false);
  636 
  637     statusPanel = new QStatusPanel;
  638     statusPanel->setFont(QApplication::font());
  639     statusPanelAction = codeeditor->addPanel(statusPanel, QCodeEdit::South, false);
  640 
  641     gotoLinePanel = new QGotoLinePanel;
  642     gotoLinePanel->setFont(QApplication::font());
  643     gotoLinePanelAction = codeeditor->addPanel(gotoLinePanel, QCodeEdit::South, false);
  644 
  645     searchReplacePanel = new QSearchReplacePanel;
  646     searchReplacePanel->setFont(QApplication::font());
  647     searchReplacePanelAction = codeeditor->addPanel(searchReplacePanel, QCodeEdit::South, false);
  648     searchReplacePanel->hide();
  649     connect(searchReplacePanel, SIGNAL(showExtendedSearch()), this, SIGNAL(showExtendedSearch()));
  650 
  651     connect(lineMarkPanel, SIGNAL(lineClicked(int)), this, SLOT(lineMarkClicked(int)));
  652     connect(lineMarkPanel, SIGNAL(toolTipRequested(int, int)), this, SLOT(lineMarkToolTip(int, int)));
  653     connect(lineMarkPanel, SIGNAL(contextMenuRequested(int, QPoint)), this, SLOT(lineMarkContextMenuRequested(int, QPoint)));
  654     connect(foldPanel, SIGNAL(contextMenuRequested(int, QPoint)), this, SLOT(foldContextMenuRequested(int, QPoint)));
  655     connect(editor, SIGNAL(hovered(QPoint)), this, SLOT(mouseHovered(QPoint)));
  656     //connect(editor->document(),SIGNAL(contentsChange(int, int)),this,SLOT(documentContentChanged(int, int)));
  657     connect(editor->document(), SIGNAL(lineDeleted(QDocumentLineHandle *)), this, SLOT(lineDeleted(QDocumentLineHandle *)));
  658 
  659     connect(doc, SIGNAL(spellingDictChanged(QString)), this, SLOT(changeSpellingDict(QString)));
  660     connect(doc, SIGNAL(bookmarkRemoved(QDocumentLineHandle *)), this, SIGNAL(bookmarkRemoved(QDocumentLineHandle *)));
  661     connect(doc, SIGNAL(bookmarkAdded(QDocumentLineHandle *, int)), this, SIGNAL(bookmarkAdded(QDocumentLineHandle *, int)));
  662 
  663     //editor->setFlag(QEditor::CursorJumpPastWrap,false);
  664     editor->disableAccentHack(config->hackDisableAccentWorkaround);
  665 
  666     editor->setInputBinding(defaultInputBinding);
  667     defaultInputBinding->completerConfig = completer->getConfig();
  668     defaultInputBinding->editorViewConfig = config;
  669     Q_ASSERT(defaultInputBinding->completerConfig);
  670     editor->document()->setLineEndingDirect(QDocument::Local);
  671     mainlay->addWidget(editor);
  672 
  673     setFocusProxy(editor);
  674 
  675     //containedLabels.setPattern("(\\\\label)\\{(.+)\\}");
  676     //containedReferences.setPattern("(\\\\ref|\\\\pageref)\\{(.+)\\}");
  677     updateSettings();
  678 
  679     lp = LatexParser::getInstance();
  680 }
  681 
  682 LatexEditorView::~LatexEditorView()
  683 {
  684     delete searchReplacePanel; // to force deletion of m_search before document. Otherwise crashes can come up (linux)
  685     delete codeeditor; //explicit call destructor of codeeditor (although it has a parent, it is no qobject itself, but passed it to editor)
  686 
  687     if (bibReader) {
  688         bibReader->quit();
  689         bibReader->wait();
  690     }
  691 }
  692 
  693 void LatexEditorView::updateReplamentList(const LatexParser &cmds, bool forceUpdate)
  694 {
  695     QMap<QString, QString> replacementList;
  696     bool differenceExists = false;
  697     foreach (QString elem, cmds.possibleCommands["%replace"].values()) {
  698         int i = elem.indexOf(" ");
  699         if (i > 0) {
  700             replacementList.insert(elem.left(i), elem.mid(i + 1));
  701             if (mReplacementList.value(elem.left(i)) != elem.mid(i + 1))
  702                 differenceExists = true;
  703         }
  704     }
  705     if (differenceExists || replacementList.count() != mReplacementList.count() || forceUpdate) {
  706         mReplacementList = replacementList;
  707         documentContentChanged(0, editor->document()->lines()); //force complete spellcheck
  708     }
  709 }
  710 
  711 void LatexEditorView::paste()
  712 {
  713     if (completer->isVisible()) {
  714         const QMimeData *d = QApplication::clipboard()->mimeData();
  715 
  716         if ( d ) {
  717             QString txt;
  718             if ( d->hasFormat("text/plain") )
  719                 txt = d->text();
  720             else if ( d->hasFormat("text/html") )
  721                 txt = d->html();
  722 
  723             if (txt.contains("\n"))
  724                 txt.clear();
  725 
  726             if (txt.isEmpty()) {
  727                 completer->close();
  728                 editor->paste();
  729             } else {
  730                 completer->insertText(txt);
  731             }
  732         }
  733     } else {
  734         editor->paste();
  735     }
  736 }
  737 
  738 void LatexEditorView::insertSnippet(QString text)
  739 {
  740     CodeSnippet(text).insert(editor);
  741 }
  742 
  743 void LatexEditorView::deleteLines(bool toStart, bool toEnd)
  744 {
  745     QList<QDocumentCursor> cursors = editor->cursors();
  746     if (cursors.empty()) return;
  747     document->beginMacro();
  748     for (int i=0;i<cursors.size();i++)
  749         cursors[i].removeSelectedText();
  750 
  751     int cursorLine = cursors[0].lineNumber();
  752     QMultiMap< int, QDocumentCursor* > map = getSelectedLines(cursors);
  753     QList<int> lines = map.uniqueKeys();
  754     QList<QDocumentCursor> newMirrors;
  755     for (int i=lines.size()-1;i>=0;i--) {
  756         QList<QDocumentCursor*> cursors = map.values(lines[i]);
  757         REQUIRE(cursors.size());
  758         if (toStart && toEnd) cursors[0]->eraseLine();
  759         else {
  760             int len = document->line(lines[i]).length();
  761             int column = toStart ? 0 : len;
  762             foreach (QDocumentCursor* c, cursors)
  763                 if (toStart) column = qMax(c->columnNumber(), column);
  764                 else column = qMin(c->columnNumber(), column);
  765             QDocumentCursor c = document->cursor(lines[i], column, lines[i], toStart ? 0 : len);
  766             c.removeSelectedText();
  767 
  768             if (!toStart || !toEnd){
  769                 if (lines[i] == cursorLine) editor->setCursor(c);
  770                 else newMirrors << c;
  771             }
  772         }
  773     }
  774     document->endMacro();
  775     editor->setCursor(cursors[0]);
  776     if (!toStart || !toEnd)
  777         for (int i=0;i<newMirrors.size();i++)
  778             editor->addCursorMirror(newMirrors[i]); //one cursor / line
  779 }
  780 
  781 void LatexEditorView::moveLines(int delta)
  782 {
  783     REQUIRE(delta == -1 || delta == +1);
  784     QList<QDocumentCursor> cursors = editor->cursors();
  785     for (int i=0;i<cursors.length();i++)
  786         cursors[i].setAutoUpdated(false);
  787     QList<QPair<int, int> > blocks = getSelectedLineBlocks();
  788     document->beginMacro();
  789     int i = delta < 0 ? blocks.size() - 1 : 0;
  790     while (i >= 0 && i < blocks.size()) {
  791         //edit
  792         QDocumentCursor edit = document->cursor(blocks[i].first, 0, blocks[i].second);
  793         QString text = edit.selectedText();
  794         edit.removeSelectedText();
  795         edit.eraseLine();
  796         if (delta < 0) {
  797             if (blocks[i].second < document->lineCount())
  798                 edit.movePosition(1, QDocumentCursor::PreviousLine);
  799             edit.movePosition(1, QDocumentCursor::StartOfLine);
  800             edit.insertText(text + "\n");
  801         } else {
  802             edit.movePosition(1, QDocumentCursor::EndOfLine);
  803             edit.insertText("\n" + text);
  804         }
  805         i += delta;
  806     }
  807     document->endMacro();
  808     //move cursors
  809     for (int i=0;i<cursors.length();i++) {
  810         cursors[i].setAutoUpdated(true);
  811         if (cursors[i].hasSelection()) {
  812             cursors[i].setAnchorLineNumber(cursors[i].anchorLineNumber() + delta);
  813             cursors[i].setLineNumber(cursors[i].lineNumber() + delta, QDocumentCursor::KeepAnchor);
  814         } else
  815             cursors[i].setLineNumber(cursors[i].lineNumber() + delta);
  816         if (i == 0) editor->setCursor(cursors[i]);
  817         else editor->addCursorMirror(cursors[i]);
  818     }
  819 }
  820 
  821 QList<QPair<int, int> > LatexEditorView::getSelectedLineBlocks()
  822 {
  823     QList<QDocumentCursor> cursors = editor->cursors();
  824     QList<int> lines;
  825     //get affected lines
  826     for (int i=0;i<cursors.length();i++) {
  827         if (cursors[i].hasSelection()) {
  828             QDocumentSelection sel = cursors[i].selection();
  829             for (int l=sel.startLine;l<=sel.endLine;l++)
  830                 lines << l;
  831         } else lines << cursors[i].lineNumber();
  832     }
  833     qSort(lines);
  834     //merge blocks as speed up and to remove duplicates
  835     QList<QPair<int, int> > result;
  836     int i = 0;
  837     while (i < lines.size()) {
  838         int start = lines[i];
  839         int end = lines[i];
  840         i++;
  841         while ( i >= 0 && i < lines.size() && (lines[i] == end || lines[i] == end + 1)) {
  842             end = lines[i];
  843             i++;
  844         }
  845         result << QPair<int,int> (start, end);
  846     }
  847     return result;
  848 }
  849 
  850 QMultiMap<int, QDocumentCursor* > LatexEditorView::getSelectedLines(QList<QDocumentCursor>& cursors)
  851 {
  852     QMultiMap<int, QDocumentCursor* > map;
  853     for (int i=0;i<cursors.length();i++) {
  854         if (cursors[i].hasSelection()) {
  855             QDocumentSelection sel = cursors[i].selection();
  856             for (int l=sel.startLine;l<=sel.endLine;l++)
  857                 map.insert(l, &cursors[i]);
  858         } else map.insert(cursors[i].lineNumber(), &cursors[i]);
  859     }
  860     return map;
  861 }
  862 
  863 bool cursorPointerLessThan(QDocumentCursor* c1, QDocumentCursor* c2)
  864 {
  865     return c1->columnNumber() < c2->columnNumber();
  866 }
  867 
  868 void LatexEditorView::alignMirrors()
  869 {
  870     QList<QDocumentCursor> cursors = editor->cursors();
  871     QMultiMap<int, QDocumentCursor* > map = getSelectedLines(cursors);
  872     QList<int> lines = map.uniqueKeys();
  873     QList<QList<QDocumentCursor*> > cs;
  874     int colCount = 0;
  875     foreach (int l, lines) {
  876         QList<QDocumentCursor*> row = map.values(l);
  877         colCount = qMax(colCount, row.size());
  878         qSort(row.begin(), row.end(), cursorPointerLessThan);
  879         cs.append(row);
  880     }
  881     document->beginMacro();
  882     for (int col=0;col<colCount;col++) {
  883         int pos = 0;
  884         for (int j=0;j<cs.size();j++)
  885             if (col < cs[j].size())
  886                 pos = qMax(pos, cs[j][col]->columnNumber());
  887         for (int j=0;j<cs.size();j++)
  888             if (col < cs[j].size() && pos > cs[j][col]->columnNumber()) {
  889                 cs[j][col]->insertText(QString(pos -  cs[j][col]->columnNumber(), ' '));
  890             }
  891     }
  892     document->endMacro();
  893 }
  894 
  895 void LatexEditorView::checkForLinkOverlay(QDocumentCursor cursor)
  896 {
  897     if (cursor.atBlockEnd()) {
  898         removeLinkOverlay();
  899         return;
  900     }
  901 
  902     bool validPosition = cursor.isValid() && cursor.line().isValid();
  903     if (validPosition) {
  904         QDocumentLineHandle *dlh = cursor.line().handle();
  905 
  906         Token tk = Parsing::getTokenAtCol(dlh, cursor.columnNumber());
  907 
  908         if (tk.type == Token::labelRef || tk.type == Token::labelRefList) {
  909             setLinkOverlay(LinkOverlay(tk, LinkOverlay::RefOverlay));
  910         } else if (tk.type == Token::file) {
  911             setLinkOverlay(LinkOverlay(tk, LinkOverlay::FileOverlay));
  912         } else if (tk.type == Token::url) {
  913             setLinkOverlay(LinkOverlay(tk, LinkOverlay::UrlOverlay));
  914         } else if (tk.type == Token::package) {
  915             setLinkOverlay(LinkOverlay(tk, LinkOverlay::UsepackageOverlay));
  916         } else if (tk.type == Token::bibfile) {
  917             setLinkOverlay(LinkOverlay(tk, LinkOverlay::BibFileOverlay));
  918         } else if (tk.type == Token::bibItem) {
  919             setLinkOverlay(LinkOverlay(tk, LinkOverlay::CiteOverlay));
  920         } else if (tk.type == Token::beginEnv || tk.type == Token::env) {
  921             setLinkOverlay(LinkOverlay(tk, LinkOverlay::EnvOverlay));
  922         } else if (tk.type == Token::commandUnknown) {
  923             setLinkOverlay(LinkOverlay(tk, LinkOverlay::CommandOverlay));
  924         } else if (tk.type == Token::command && tk.getText() != "\\begin" && tk.getText() != "\\end") {
  925             // avoid link overlays on \begin and \end; instead, the user can click the environment name
  926             setLinkOverlay(LinkOverlay(tk, LinkOverlay::CommandOverlay));
  927         } else {
  928             if (linkOverlay.isValid()) removeLinkOverlay();
  929         }
  930     } else {
  931         if (linkOverlay.isValid()) removeLinkOverlay();
  932     }
  933 }
  934 
  935 void LatexEditorView::setLinkOverlay(const LinkOverlay &overlay)
  936 {
  937     if (linkOverlay.isValid()) {
  938         if (overlay == linkOverlay) {
  939             return; // same overlay
  940         } else {
  941             removeLinkOverlay();
  942         }
  943     }
  944 
  945     linkOverlay = overlay;
  946     linkOverlay.docLine.addOverlay(linkOverlay.formatRange);
  947     editor->viewport()->update(); // immediately apply the overlay
  948     linkOverlayStoredCursor = editor->viewport()->cursor();
  949     editor->viewport()->setCursor(Qt::PointingHandCursor);
  950 }
  951 
  952 void LatexEditorView::removeLinkOverlay()
  953 {
  954     if (linkOverlay.isValid()) {
  955         linkOverlay.docLine.removeOverlay(linkOverlay.formatRange);
  956         linkOverlay = LinkOverlay();
  957         editor->viewport()->update(); // immediately apply the overlay
  958         editor->viewport()->setCursor(linkOverlayStoredCursor);
  959     }
  960 }
  961 
  962 bool LatexEditorView::isNonTextFormat(int format)
  963 {
  964     if (format <= 0) return false;
  965     return format == numbersFormat
  966            || format == verbatimFormat
  967            || format == pictureFormat
  968            || format == pweaveDelimiterFormat
  969            || format == pweaveBlockFormat
  970            || format == sweaveDelimiterFormat
  971            || format == sweaveBlockFormat
  972            || format == math_DelimiterFormat
  973            || format == asymptoteBlockFormat;
  974 }
  975 
  976 void LatexEditorView::selectOptionInLatexArg(QDocumentCursor &cur)
  977 {
  978     QString startDelims = "[{, \t\n";
  979     int startCol = cur.columnNumber();
  980     while (!cur.atLineStart() && !startDelims.contains(cur.previousChar())) {
  981         cur.movePosition(1, QDocumentCursor::PreviousCharacter);
  982     }
  983     cur.setColumnNumber(startCol, QDocumentCursor::KeepAnchor);
  984     QString endDelims = "]}, \t\n";
  985     while (!cur.atLineEnd() && !endDelims.contains(cur.nextChar())) {
  986         cur.movePosition(1, QDocumentCursor::NextCharacter, QDocumentCursor::KeepAnchor);
  987     }
  988 }
  989 
  990 void LatexEditorView::temporaryHighlight(QDocumentCursor cur)
  991 {
  992     if (!cur.hasSelection()) return;
  993     REQUIRE(editor->document());
  994 
  995     QDocumentLine docLine(cur.selectionStart().line());
  996     if (cur.endLineNumber() != cur.startLineNumber()) {
  997         // TODO: proper highlighting of selections spanning more than one line. Currently just highlight to the end of the first line:
  998         cur = cur.selectionStart();
  999         cur.movePosition(1, QDocumentCursor::EndOfLine, QDocumentCursor::KeepAnchor);
 1000     }
 1001 
 1002     QFormatRange highlight(cur.startColumnNumber(), cur.endColumnNumber() - cur.startColumnNumber(), editor->document()->getFormatId("search"));
 1003     docLine.addOverlay(highlight);
 1004     tempHighlightQueue.append(QPair<QDocumentLine, QFormatRange>(docLine, highlight));
 1005     QTimer::singleShot(1000, this, SLOT(removeTemporaryHighlight()));
 1006 }
 1007 
 1008 void LatexEditorView::removeTemporaryHighlight()
 1009 {
 1010     if (!tempHighlightQueue.isEmpty()) {
 1011         QDocumentLine docLine(tempHighlightQueue.first().first);
 1012         docLine.removeOverlay(tempHighlightQueue.first().second);
 1013         tempHighlightQueue.removeFirst();
 1014     }
 1015 }
 1016 
 1017 void LatexEditorView::displayLineGrammarErrorsInternal(int lineNr, const QList<GrammarError> &errors)
 1018 {
 1019     QDocumentLine line = document->line(lineNr);
 1020     foreach (const int f, grammarFormats)
 1021         line.clearOverlays(f);
 1022     foreach (const GrammarError &error, errors) {
 1023         int f;
 1024         if (error.error == GET_UNKNOWN) f = grammarMistakeFormat;
 1025         else {
 1026             int index = static_cast<int>(error.error) - 1;
 1027             REQUIRE(index < grammarFormats.size());
 1028             if (grammarFormatsDisabled[index]) continue;
 1029             f = grammarFormats[index];
 1030         }
 1031         if (config->hideNonTextGrammarErrors && (isNonTextFormat(line.getFormatAt(error.offset)) || isNonTextFormat(line.getFormatAt(error.offset + error.length - 1))))
 1032             continue;
 1033         line.addOverlay(QFormatRange(error.offset, error.length, f));
 1034     }
 1035     //todo: check for width changing like if (changed && ff->format(wordRepetitionFormat).widthChanging()) line.handle()->updateWrapAndNotifyDocument(i);
 1036 }
 1037 
 1038 void LatexEditorView::lineGrammarChecked(const void *doc, const void *lineHandle, int lineNr, const QList<GrammarError> &errors)
 1039 {
 1040     if (doc != this->document) return;
 1041     lineNr = document->indexOf(const_cast<QDocumentLineHandle *>(static_cast<const QDocumentLineHandle *>(lineHandle)), lineNr);
 1042     if (lineNr < 0) return; //line already deleted
 1043     displayLineGrammarErrorsInternal(lineNr, errors);
 1044     document->line(lineNr).setCookie(QDocumentLine::GRAMMAR_ERROR_COOKIE, QVariant::fromValue<QList<GrammarError> >(errors));
 1045 }
 1046 
 1047 void LatexEditorView::setGrammarOverlayDisabled(int type, bool newValue)
 1048 {
 1049     REQUIRE(type >= 0 && type < grammarFormatsDisabled.size());
 1050     if (newValue == grammarFormatsDisabled[type]) return;
 1051     grammarFormatsDisabled[type] = newValue;
 1052 }
 1053 
 1054 void LatexEditorView::updateGrammarOverlays()
 1055 {
 1056     for (int i = 0; i < document->lineCount(); i++)
 1057         displayLineGrammarErrorsInternal(i, document->line(i).getCookie(QDocumentLine::GRAMMAR_ERROR_COOKIE).value<QList<GrammarError> >());
 1058     editor->viewport()->update();
 1059 }
 1060 
 1061 void LatexEditorView::viewActivated()
 1062 {
 1063     if (!LatexEditorView::completer) return;
 1064 }
 1065 
 1066 /*!
 1067  * Returns the name to be displayed when a short textual reference to the editor is required
 1068  * such as in the tab or in a list of open documents.
 1069  * This name is not necessarily unique.
 1070  */
 1071 QString LatexEditorView::displayName() const
 1072 {
 1073     return (!editor || editor->fileName().isEmpty() ? tr("untitled") : editor->name());
 1074 }
 1075 
 1076 /*!
 1077  * Returns the displayName() with properly escaped ampersands for UI elements
 1078  * such as tabs and actions.
 1079  */
 1080 QString LatexEditorView::displayNameForUI() const
 1081 {
 1082     return displayName().replace('&', "&&");
 1083 }
 1084 
 1085 void LatexEditorView::complete(int flags)
 1086 {
 1087     if (!LatexEditorView::completer) return;
 1088     setFocus();
 1089     LatexEditorView::completer->complete(editor, LatexCompleter::CompletionFlags(flags));
 1090 }
 1091 
 1092 void LatexEditorView::jumpChangePositionBackward()
 1093 {
 1094     if (changePositions.size() == 0) return;
 1095     for (int i = changePositions.size() - 1; i >= 0; i--)
 1096         if (!changePositions[i].isValid()) {
 1097             changePositions.removeAt(i);
 1098             if (i <= curChangePos) curChangePos--;
 1099         }
 1100     if (curChangePos >= changePositions.size() - 1) curChangePos = changePositions.size() - 1;
 1101     else if (curChangePos >= 0 && curChangePos < changePositions.size() - 1) curChangePos++;
 1102     else if (editor->cursor().line().handle() == changePositions.first().dlh()) curChangePos = 1;
 1103     else curChangePos = 0;
 1104     if (curChangePos >= 0 && curChangePos < changePositions.size())
 1105         editor->setCursorPosition(changePositions[curChangePos].lineNumber(), changePositions[curChangePos].columnNumber());
 1106 }
 1107 
 1108 void LatexEditorView::jumpChangePositionForward()
 1109 {
 1110     for (int i = changePositions.size() - 1; i >= 0; i--)
 1111         if (!changePositions[i].isValid()) {
 1112             changePositions.removeAt(i);
 1113             if (i <= curChangePos) curChangePos--;
 1114         }
 1115     if (curChangePos > 0) {
 1116         curChangePos--;
 1117         editor->setCursorPosition(changePositions[curChangePos].lineNumber(), changePositions[curChangePos].columnNumber());
 1118     }
 1119 }
 1120 
 1121 void LatexEditorView::jumpToBookmark(int bookmarkNumber)
 1122 {
 1123     int markLine = editor->document()->findNextMark(bookMarkId(bookmarkNumber), editor->cursor().lineNumber(), editor->cursor().lineNumber() - 1);
 1124     if (markLine >= 0) {
 1125         emit saveCurrentCursorToHistoryRequested();
 1126         editor->setCursorPosition(markLine, 0, false);
 1127         editor->ensureCursorVisible(QEditor::NavigationToHeader);
 1128         editor->setFocus();
 1129     }
 1130 }
 1131 
 1132 void LatexEditorView::removeBookmark(QDocumentLineHandle *dlh, int bookmarkNumber)
 1133 {
 1134     if (!dlh)
 1135         return;
 1136     int rmid = bookMarkId(bookmarkNumber);
 1137     if (hasBookmark(dlh, bookmarkNumber)) {
 1138         document->removeMark(dlh, rmid);
 1139         editor->removeMark(dlh,"bookmark");
 1140         emit bookmarkRemoved(dlh);
 1141     }
 1142 }
 1143 
 1144 void LatexEditorView::removeBookmark(int lineNr, int bookmarkNumber)
 1145 {
 1146     removeBookmark(document->line(lineNr).handle(), bookmarkNumber);
 1147 }
 1148 
 1149 void LatexEditorView::addBookmark(int lineNr, int bookmarkNumber)
 1150 {
 1151     int rmid = bookMarkId(bookmarkNumber);
 1152     if (bookmarkNumber >= 0){
 1153         int ln=document->findNextMark(rmid);
 1154         document->line(ln).removeMark(rmid);
 1155         editor->removeMark(document->line(ln).handle(),"bookmark");
 1156     }
 1157     if (!document->line(lineNr).hasMark(rmid)){
 1158         document->line(lineNr).addMark(rmid);
 1159         editor->addMark(document->line(lineNr).handle(),Qt::darkMagenta,"bookmark");
 1160     }
 1161 }
 1162 
 1163 bool LatexEditorView::hasBookmark(int lineNr, int bookmarkNumber)
 1164 {
 1165     int rmid = bookMarkId(bookmarkNumber);
 1166     return document->line(lineNr).hasMark(rmid);
 1167 }
 1168 
 1169 bool LatexEditorView::hasBookmark(QDocumentLineHandle *dlh, int bookmarkNumber)
 1170 {
 1171     if (!dlh)
 1172         return false;
 1173     int rmid = bookMarkId(bookmarkNumber);
 1174     QList<int> m_marks = document->marks(dlh);
 1175     return m_marks.contains(rmid);
 1176 }
 1177 
 1178 bool LatexEditorView::toggleBookmark(int bookmarkNumber, QDocumentLine line)
 1179 {
 1180     if (!line.isValid()) line = editor->cursor().line();
 1181     int rmid = bookMarkId(bookmarkNumber);
 1182     if (line.hasMark(rmid)) {
 1183         line.removeMark(rmid);
 1184         editor->removeMark(line.handle(),"bookmark");
 1185         emit bookmarkRemoved(line.handle());
 1186         return false;
 1187     }
 1188     if (bookmarkNumber >= 0) {
 1189         int ln = editor->document()->findNextMark(rmid);
 1190         if (ln >= 0) {
 1191             editor->document()->line(ln).removeMark(rmid);
 1192             editor->removeMark(editor->document()->line(ln).handle(),"bookmark");
 1193             emit bookmarkRemoved(editor->document()->line(ln).handle());
 1194         }
 1195     }
 1196     for (int i = -1; i < 10; i++) {
 1197         int rmid = bookMarkId(i);
 1198         if (line.hasMark(rmid)) {
 1199             line.removeMark(rmid);
 1200             editor->removeMark(line.handle(),"bookmark");
 1201             emit bookmarkRemoved(line.handle());
 1202         }
 1203     }
 1204     line.addMark(rmid);
 1205     editor->addMark(line.handle(),Qt::darkMagenta,"bookmark");
 1206     emit bookmarkAdded(line.handle(), bookmarkNumber);
 1207     return true;
 1208 }
 1209 
 1210 bool LatexEditorView::gotoLineHandleAndSearchCommand(const QDocumentLineHandle *dlh, const QSet<QString> &searchFor, const QString &id)
 1211 {
 1212     if (!dlh) return false;
 1213     int ln = dlh->document()->indexOf(dlh);
 1214     if (ln < 0) return false;
 1215     QString lineText = dlh->document()->line(ln).text();
 1216     int col = 0;
 1217     foreach (const QString &cmd, searchFor) {
 1218         col = lineText.indexOf(cmd + "{" + id);
 1219         if (col < 0) col = lineText.indexOf(QRegExp(QRegExp::escape(cmd) + "\\[[^\\]{}()\\\\]+\\]\\{" + QRegExp::escape(id))); //for \command[options]{id}
 1220         if (col >= 0) {
 1221             col += cmd.length() + 1;
 1222             break;
 1223         }
 1224     }
 1225     //Q_ASSERT(col >= 0);
 1226     bool colFound = (col >= 0);
 1227     if (col < 0) col = 0;
 1228     editor->setCursorPosition(ln, col, false);
 1229     editor->ensureCursorVisible(QEditor::Navigation);
 1230     if (colFound) {
 1231         QDocumentCursor highlightCursor(editor->cursor());
 1232         highlightCursor.movePosition(id.length(), QDocumentCursor::NextCharacter, QDocumentCursor::KeepAnchor);
 1233         temporaryHighlight(highlightCursor);
 1234     }
 1235     return true;
 1236 }
 1237 
 1238 bool LatexEditorView::gotoLineHandleAndSearchString(const QDocumentLineHandle *dlh, const QString &str)
 1239 {
 1240     if (!dlh) return false;
 1241     int ln = dlh->document()->indexOf(dlh);
 1242     if (ln < 0) return false;
 1243     QString lineText = dlh->document()->line(ln).text();
 1244     int col = lineText.indexOf(str);
 1245     bool colFound = (col >= 0);
 1246     if (col < 0) col = 0;
 1247     editor->setCursorPosition(ln, col, false);
 1248     editor->ensureCursorVisible(QEditor::Navigation);
 1249     if (colFound) {
 1250         QDocumentCursor highlightCursor(editor->cursor());
 1251         highlightCursor.movePosition(str.length(), QDocumentCursor::NextCharacter, QDocumentCursor::KeepAnchor);
 1252         temporaryHighlight(highlightCursor);
 1253     }
 1254     return true;
 1255 }
 1256 
 1257 bool LatexEditorView::gotoLineHandleAndSearchLabel(const QDocumentLineHandle *dlh, const QString &label)
 1258 {
 1259     return gotoLineHandleAndSearchCommand(dlh, LatexParser::getInstance().possibleCommands["%label"], label);
 1260 }
 1261 
 1262 bool LatexEditorView::gotoLineHandleAndSearchBibItem(const QDocumentLineHandle *dlh, const QString &bibId)
 1263 {
 1264     return gotoLineHandleAndSearchCommand(dlh, LatexParser::getInstance().possibleCommands["%bibitem"], bibId);
 1265 }
 1266 
 1267 //collapse/expand every possible line
 1268 void LatexEditorView::foldEverything(bool unFold)
 1269 {
 1270     QDocument *doc = editor->document();
 1271     QLanguageDefinition *ld = doc->languageDefinition();
 1272     if (!ld) return;
 1273     QFoldedLineIterator fli = ld->foldedLineIterator(doc, 0);
 1274     for (int i = 0; i < doc->lines(); i++, ++fli)
 1275         if (fli.open) {
 1276             if (unFold) ld->expand(doc, i);
 1277             else ld->collapse(doc, i);
 1278         }
 1279 }
 1280 
 1281 //collapse/expand lines at the top level
 1282 void LatexEditorView::foldLevel(bool unFold, int level)
 1283 {
 1284     QDocument *doc = editor->document();
 1285     QLanguageDefinition *ld = doc->languageDefinition();
 1286     if (!ld) return;
 1287     for (QFoldedLineIterator fli = ld->foldedLineIterator(doc);
 1288             fli.line.isValid(); ++fli) {
 1289         if (fli.openParentheses.size() == level && fli.open) {
 1290             if (unFold) ld->expand(doc, fli.lineNr);
 1291             else ld->collapse(doc, fli.lineNr);
 1292         }
 1293     }/*
 1294  QDocument* doc = editor->document();
 1295  QLanguageDefinition* ld = doc->languageDefinition();
 1296  int depth=0;
 1297  for (int n = 0; n < doc->lines(); ++n) {
 1298   QDocumentLine b=doc->line(n);
 1299   if (b.isHidden()) continue;
 1300 
 1301   int flags = ld->blockFlags(doc, n, depth);
 1302   short open = QCE_FOLD_OPEN_COUNT(flags);
 1303   short close = QCE_FOLD_CLOSE_COUNT(flags);
 1304 
 1305   depth -= close;
 1306 
 1307   if (depth < 0)
 1308    depth = 0;
 1309 
 1310   depth += open;
 1311 
 1312   if (depth==level) {
 1313    if (unFold && (flags & QLanguageDefinition::Collapsed))
 1314     ld->expand(doc,n);
 1315    else if (!unFold && (flags & QLanguageDefinition::Collapsible))
 1316     ld->collapse(doc,n);
 1317   }
 1318   if (ld->blockFlags(doc, n, depth) & QLanguageDefinition::Collapsed)
 1319    depth -= open; // outermost block folded : none of the opening is actually opened
 1320  }*/
 1321 }
 1322 
 1323 //Collapse at the first possible point before/at line
 1324 void LatexEditorView::foldBlockAt(bool unFold, int line)
 1325 {
 1326     editor->document()->foldBlockAt(unFold, line);
 1327 }
 1328 
 1329 void LatexEditorView::zoomIn()
 1330 {
 1331     editor->zoom(1);
 1332 }
 1333 
 1334 void LatexEditorView::zoomOut()
 1335 {
 1336     editor->zoom(-1);
 1337 }
 1338 
 1339 void LatexEditorView::resetZoom()
 1340 {
 1341     editor->resetZoom();
 1342 }
 1343 
 1344 void LatexEditorView::cleanBib()
 1345 {
 1346     QDocument *doc = editor->document();
 1347     for (int i = doc->lines() - 1; i >= 0; i--) {
 1348         QString trimLine = doc->line(i).text().trimmed();
 1349         if (trimLine.startsWith("OPT") || trimLine.startsWith("ALT"))
 1350             doc->execute(new QDocumentEraseCommand(i, 0, i + 1, 0, doc));
 1351     }
 1352     setFocus();
 1353 }
 1354 
 1355 QList<QAction *> LatexEditorView::getBaseActions()
 1356 {
 1357     if (!defaultInputBinding) return QList<QAction *>();
 1358     return defaultInputBinding->baseActions;
 1359 }
 1360 
 1361 void LatexEditorView::setBaseActions(QList<QAction *> baseActions)
 1362 {
 1363     if (!defaultInputBinding) return;
 1364     defaultInputBinding->baseActions = baseActions;
 1365 }
 1366 
 1367 void LatexEditorView::setSpellerManager(SpellerManager *manager)
 1368 {
 1369     spellerManager = manager;
 1370     connect(spellerManager, SIGNAL(defaultSpellerChanged()), this, SLOT(reloadSpeller()));
 1371 }
 1372 
 1373 /*!
 1374  * \brief Set new spelling language
 1375  * No change if old and new are identical
 1376  * The speller engine is forwarded to the syntax checker where the actual online checking is done.
 1377  * \param name of the desired language
 1378  * \return success of operation
 1379  */
 1380 bool LatexEditorView::setSpeller(const QString &name, bool updateComment)
 1381 {
 1382     if (!spellerManager) return false;
 1383 
 1384     useDefaultSpeller = (name == "<default>");
 1385 
 1386     SpellerUtility *su;
 1387     if (spellerManager->hasSpeller(name)) {
 1388         su = spellerManager->getSpeller(name);
 1389         if (!su) return false;
 1390     } else {
 1391         su = spellerManager->getSpeller(spellerManager->defaultSpellerName());
 1392         REQUIRE_RET(su, false);
 1393         useDefaultSpeller = true;
 1394     }
 1395     if (su == speller) return true; // nothing to do
 1396 
 1397     if (speller) {
 1398         disconnect(speller, SIGNAL(aboutToDelete()), this, SLOT(reloadSpeller()));
 1399         disconnect(speller, SIGNAL(ignoredWordAdded(QString)), this, SLOT(spellRemoveMarkers(QString)));
 1400     }
 1401     speller = su;
 1402     connect(speller, SIGNAL(aboutToDelete()), this, SLOT(reloadSpeller()));
 1403     connect(speller, SIGNAL(ignoredWordAdded(QString)), this, SLOT(spellRemoveMarkers(QString)));
 1404     emit spellerChanged(name);
 1405 
 1406     if (document && updateComment) {
 1407         document->updateMagicComment("spellcheck", speller->name());
 1408     }
 1409 
 1410     // force new highlighting
 1411     documentContentChanged(0, editor->document()->lines());
 1412     return true;
 1413 }
 1414 
 1415 void LatexEditorView::reloadSpeller()
 1416 {
 1417     if (useDefaultSpeller) {
 1418         setSpeller("<default>");
 1419         return;
 1420     }
 1421 
 1422     SpellerUtility *su = qobject_cast<SpellerUtility *>(sender());
 1423     if (!su) return;
 1424     setSpeller(su->name());
 1425 }
 1426 
 1427 QString LatexEditorView::getSpeller()
 1428 {
 1429     if (useDefaultSpeller) return QString("<default>");
 1430     return speller->name();
 1431 }
 1432 
 1433 void LatexEditorView::setCompleter(LatexCompleter *newCompleter)
 1434 {
 1435     LatexEditorView::completer = newCompleter;
 1436 }
 1437 
 1438 LatexCompleter *LatexEditorView::getCompleter()
 1439 {
 1440     return LatexEditorView::completer;
 1441 }
 1442 
 1443 void LatexEditorView::updatePackageFormats()
 1444 {
 1445     for (int i = 0; i < editor->document()->lines(); i++) {
 1446         QList<QFormatRange> li = editor->document()->line(i).getOverlays();
 1447         QString curLineText = editor->document()->line(i).text();
 1448         for (int j = 0; j < li.size(); j++)
 1449             if (li[j].format == packagePresentFormat || li[j].format == packageMissingFormat || li[j].format == packageUndefinedFormat) {
 1450                 int newFormat = packageUndefinedFormat;
 1451                 if (!latexPackageList->isEmpty()) {
 1452                     newFormat = latexPackageList->contains(curLineText.mid(li[j].offset, li[j].length)) ? packagePresentFormat : packageMissingFormat;
 1453                 }
 1454                 if (newFormat != li[j].format) {
 1455                     editor->document()->line(i).removeOverlay(li[j]);
 1456                     li[j].format = newFormat;
 1457                     editor->document()->line(i).addOverlay(li[j]);
 1458                 }
 1459             }
 1460     }
 1461 }
 1462 
 1463 void LatexEditorView::clearLogMarks()
 1464 {
 1465     setLogMarksVisible(false);
 1466     logEntryToLine.clear();
 1467     logEntryToMarkID.clear();
 1468     lineToLogEntries.clear();
 1469 }
 1470 
 1471 void LatexEditorView::addLogEntry(int logEntryNumber, int lineNumber, int markID)
 1472 {
 1473     QDocumentLine l = editor->document()->line(lineNumber);
 1474     lineToLogEntries.insert(l.handle(), logEntryNumber);
 1475     logEntryToLine[logEntryNumber] = l.handle();
 1476     logEntryToMarkID[logEntryNumber] = markID;
 1477 }
 1478 
 1479 void LatexEditorView::setLogMarksVisible(bool visible)
 1480 {
 1481     if (visible) {
 1482         foreach (int logEntryNumber, logEntryToMarkID.keys()) {
 1483             int markID = logEntryToMarkID[logEntryNumber];
 1484             if (markID >= 0) {
 1485                 QDocumentLine(logEntryToLine[logEntryNumber]).addMark(markID);
 1486             }
 1487         }
 1488     } else {
 1489         int errorMarkID = QLineMarksInfoCenter::instance()->markTypeId("error");
 1490         int warningMarkID = QLineMarksInfoCenter::instance()->markTypeId("warning");
 1491         int badboxMarkID = QLineMarksInfoCenter::instance()->markTypeId("badbox");
 1492         editor->document()->removeMarks(errorMarkID);
 1493         editor->document()->removeMarks(warningMarkID);
 1494         editor->document()->removeMarks(badboxMarkID);
 1495     }
 1496 }
 1497 
 1498 void LatexEditorView::updateCitationFormats()
 1499 {
 1500     for (int i = 0; i < editor->document()->lines(); i++) {
 1501         QList<QFormatRange> li = editor->document()->line(i).getOverlays();
 1502         QString curLineText = editor->document()->line(i).text();
 1503         for (int j = 0; j < li.size(); j++)
 1504             if (li[j].format == citationPresentFormat || li[j].format == citationMissingFormat) {
 1505                 int newFormat = document->bibIdValid(curLineText.mid(li[j].offset, li[j].length)) ? citationPresentFormat : citationMissingFormat;
 1506                 if (newFormat != li[j].format) {
 1507                     editor->document()->line(i).removeOverlay(li[j]);
 1508                     li[j].format = newFormat;
 1509                     editor->document()->line(i).addOverlay(li[j]);
 1510                 }
 1511             }
 1512     }
 1513 }
 1514 
 1515 bool LatexEditorView::containsBibTeXId(QString id)
 1516 {
 1517     return document->bibIdValid(id);
 1518 }
 1519 
 1520 int LatexEditorView::bookMarkId(int bookmarkNumber)
 1521 {
 1522     if (bookmarkNumber == -1) return  QLineMarksInfoCenter::instance()->markTypeId("bookmark"); //unnumbered mark
 1523     else return QLineMarksInfoCenter::instance()->markTypeId("bookmark" + QString::number(bookmarkNumber));
 1524     //return document->bookMarkId(bookmarkNumber);
 1525 }
 1526 
 1527 void LatexEditorView::setLineMarkToolTip(const QString &tooltip)
 1528 {
 1529     lineMarkPanel->setToolTipForTouchedMark(tooltip);
 1530 }
 1531 
 1532 int LatexEditorView::environmentFormat, LatexEditorView::referencePresentFormat, LatexEditorView::referenceMissingFormat, LatexEditorView::referenceMultipleFormat, LatexEditorView::citationMissingFormat, LatexEditorView::citationPresentFormat, LatexEditorView::structureFormat, LatexEditorView::todoFormat, LatexEditorView::packageMissingFormat, LatexEditorView::packagePresentFormat, LatexEditorView::packageUndefinedFormat,
 1533     LatexEditorView::wordRepetitionFormat, LatexEditorView::wordRepetitionLongRangeFormat, LatexEditorView::badWordFormat, LatexEditorView::grammarMistakeFormat, LatexEditorView::grammarMistakeSpecial1Format, LatexEditorView::grammarMistakeSpecial2Format, LatexEditorView::grammarMistakeSpecial3Format, LatexEditorView::grammarMistakeSpecial4Format,
 1534     LatexEditorView::numbersFormat, LatexEditorView::verbatimFormat, LatexEditorView::commentFormat, LatexEditorView::pictureFormat, LatexEditorView::math_DelimiterFormat, LatexEditorView::math_KeywordFormat,
 1535     LatexEditorView::pweaveDelimiterFormat, LatexEditorView::pweaveBlockFormat, LatexEditorView::sweaveDelimiterFormat, LatexEditorView::sweaveBlockFormat,
 1536     LatexEditorView::asymptoteBlockFormat;
 1537 int LatexEditorView::syntaxErrorFormat, LatexEditorView::preEditFormat;
 1538 int LatexEditorView::deleteFormat, LatexEditorView::insertFormat, LatexEditorView::replaceFormat;
 1539 
 1540 QList<int> LatexEditorView::grammarFormats;
 1541 QVector<bool> LatexEditorView::grammarFormatsDisabled;
 1542 QList<int> LatexEditorView::formatsList;
 1543 
 1544 void LatexEditorView::updateSettings()
 1545 {
 1546     lineNumberPanel->setVerboseMode(config->showlinemultiples != 10);
 1547     editor->setFont(QFont(config->fontFamily, config->fontSize));
 1548     editor->setLineWrapping(config->wordwrap > 0);
 1549     editor->setSoftLimitedLineWrapping(config->wordwrap == 2);
 1550     editor->setHardLineWrapping(config->wordwrap > 2);
 1551     if (config->wordwrap > 1) {
 1552         editor->setWrapAfterNumChars(config->lineWidth);
 1553     } else {
 1554         editor->setWrapAfterNumChars(0);
 1555     }
 1556     editor->setFlag(QEditor::AutoIndent, config->autoindent);
 1557     editor->setFlag(QEditor::WeakIndent, config->weakindent);
 1558     editor->setFlag(QEditor::ReplaceIndentTabs, config->replaceIndentTabs);
 1559     editor->setFlag(QEditor::ReplaceTextTabs, config->replaceTextTabs);
 1560     editor->setFlag(QEditor::RemoveTrailing, config->removeTrailingWsOnSave);
 1561     editor->setFlag(QEditor::AllowDragAndDrop, config->allowDragAndDrop);
 1562     editor->setFlag(QEditor::MouseWheelZoom, config->mouseWheelZoom);
 1563     editor->setFlag(QEditor::SmoothScrolling, config->smoothScrolling);
 1564     editor->setFlag(QEditor::AutoInsertLRM, config->autoInsertLRM);
 1565     editor->setFlag(QEditor::BidiVisualColumnMode, config->visualColumnMode);
 1566     editor->setFlag(QEditor::OverwriteOpeningBracketFollowedByPlaceholder, config->overwriteOpeningBracketFollowedByPlaceholder);
 1567     editor->setFlag(QEditor::OverwriteClosingBracketFollowingPlaceholder, config->overwriteClosingBracketFollowingPlaceholder);
 1568     //TODO: parenmatch
 1569     editor->setFlag(QEditor::AutoCloseChars, config->parenComplete);
 1570     editor->setFlag(QEditor::ShowPlaceholders, config->showPlaceholders);
 1571     editor->setDoubleClickSelectionType(config->doubleClickSelectionIncludeLeadingBackslash ? QDocumentCursor::WordOrCommandUnderCursor : QDocumentCursor::WordUnderCursor);
 1572     editor->setTripleClickSelectionType((QList<QDocumentCursor::SelectionType>()
 1573                                         << QDocumentCursor::WordUnderCursor
 1574                                         << QDocumentCursor::WordOrCommandUnderCursor
 1575                                         << QDocumentCursor::ParenthesesInner
 1576                                         << QDocumentCursor::ParenthesesOuter
 1577                                         << QDocumentCursor::LineUnderCursor).at(qMax(0, qMin(4, config->tripleClickSelectionIndex))));
 1578     editor->setIgnoreExternalChanges(!config->monitorFilesForExternalChanges);
 1579     editor->setSilentReloadOnExternalChanges(config->silentReload);
 1580     editor->setUseQSaveFile(config->useQSaveFile);
 1581     editor->setHidden(false);
 1582     editor->setCursorSurroundingLines(config->cursorSurroundLines);
 1583     editor->setCursorBold(config->boldCursor);
 1584     lineMarkPanelAction->setChecked((config->showlinemultiples != 0) || config->folding || config->showlinestate);
 1585     lineNumberPanelAction->setChecked(config->showlinemultiples != 0);
 1586     lineFoldPanelAction->setChecked(config->folding);
 1587     lineChangePanelAction->setChecked(config->showlinestate);
 1588     statusPanelAction->setChecked(config->showcursorstate);
 1589     editor->setDisplayModifyTime(false);
 1590     searchReplacePanel->setUseLineForSearch(config->useLineForSearch);
 1591     searchReplacePanel->setSearchOnlyInSelection(config->searchOnlyInSelection);
 1592     QDocument::setShowSpaces(config->showWhitespace ? (QDocument::ShowTrailing | QDocument::ShowLeading | QDocument::ShowTabs) : QDocument::ShowNone);
 1593     QDocument::setTabStop(config->tabStop);
 1594     QDocument::setLineSpacingFactor(config->lineSpacingPercent / 100.0);
 1595     QDocument::setCenterDocumentInEditor(config->centerDocumentInEditor);
 1596 
 1597     editor->m_preEditFormat = preEditFormat;
 1598 
 1599     QDocument::setWorkAround(QDocument::DisableFixedPitchMode, config->hackDisableFixedPitch);
 1600     QDocument::setWorkAround(QDocument::DisableWidthCache, config->hackDisableWidthCache);
 1601     QDocument::setWorkAround(QDocument::DisableLineCache, config->hackDisableLineCache);
 1602     QDocument::setWorkAround(QDocument::QImageCache, config->hackQImageCache);
 1603 
 1604     QDocument::setWorkAround(QDocument::ForceQTextLayout, config->hackRenderingMode == 1);
 1605     QDocument::setWorkAround(QDocument::ForceSingleCharacterDrawing, config->hackRenderingMode == 2);
 1606     LatexDocument::syntaxErrorFormat = syntaxErrorFormat;
 1607     if (document)
 1608         document->updateSettings();
 1609 }
 1610 
 1611 void LatexEditorView::updateFormatSettings()
 1612 {
 1613     static bool formatsLoaded = false;
 1614     if (!formatsLoaded) {
 1615         REQUIRE(QDocument::defaultFormatScheme());
 1616 #define F(n) &n##Format, #n,
 1617         const void *formats[] = {F(environment)
 1618                                  F(referenceMultiple) F(referencePresent) F(referenceMissing)
 1619                                  F(citationPresent) F(citationMissing)
 1620                                  F(packageMissing) F(packagePresent)
 1621                                  &packageUndefinedFormat, "normal",
 1622                                  &syntaxErrorFormat, "latexSyntaxMistake", //TODO: rename all to xFormat, "x"
 1623                                  F(structure)
 1624                                  &todoFormat, "commentTodo",
 1625                                  &deleteFormat, "diffDelete",
 1626                                  &insertFormat, "diffAdd",
 1627                                  &replaceFormat, "diffReplace",
 1628                                  F(wordRepetition) F(wordRepetitionLongRange) F(badWord)
 1629                                  F(grammarMistake)
 1630                                  F(grammarMistakeSpecial1) F(grammarMistakeSpecial2) F(grammarMistakeSpecial3) F(grammarMistakeSpecial4)
 1631                                  F(numbers) F(verbatim) F(comment) F(picture)
 1632                                  &pweaveDelimiterFormat, "pweave-delimiter",
 1633                                  &pweaveBlockFormat, "pweave-block",
 1634                                  &sweaveDelimiterFormat, "sweave-delimiter",
 1635                                  &sweaveBlockFormat, "sweave-block",
 1636                                  &math_DelimiterFormat, "math-delimiter",
 1637                                  &math_KeywordFormat, "math-keyword",
 1638                                  &asymptoteBlockFormat, "asymptote:block",
 1639                                  &preEditFormat, "preedit",
 1640                                  nullptr, nullptr
 1641                                 };
 1642 #undef F
 1643         const void **temp = formats;
 1644         while (*temp) {
 1645             int *c = (static_cast<int *>(const_cast<void *>(*temp)));
 1646             Q_ASSERT(c != nullptr);
 1647             *c = QDocument::defaultFormatScheme()->id(QString(static_cast<const char *>(*(temp + 1))));
 1648             temp += 2;
 1649         }
 1650         //int f=QDocument::formatFactory()->id("citationMissing");
 1651         formatsLoaded = true;
 1652         grammarFormats << wordRepetitionFormat << wordRepetitionLongRangeFormat << badWordFormat << grammarMistakeFormat << grammarMistakeSpecial1Format << grammarMistakeSpecial2Format << grammarMistakeSpecial3Format << grammarMistakeSpecial4Format; //don't change the order, it corresponds to GrammarErrorType
 1653         grammarFormatsDisabled.resize(9);
 1654         grammarFormatsDisabled.fill(false);
 1655         formatsList << SpellerUtility::spellcheckErrorFormat << referencePresentFormat << citationPresentFormat << referenceMissingFormat;
 1656         formatsList << referenceMultipleFormat << citationMissingFormat << packageMissingFormat << packagePresentFormat << packageUndefinedFormat << environmentFormat;
 1657         formatsList << wordRepetitionFormat << structureFormat << todoFormat << insertFormat << deleteFormat << replaceFormat;
 1658         LatexDocument::syntaxErrorFormat = syntaxErrorFormat;
 1659     }
 1660 }
 1661 
 1662 void LatexEditorView::requestCitation()
 1663 {
 1664     QString id = editor->cursor().selectedText();
 1665     emit needCitation(id);
 1666 }
 1667 
 1668 void LatexEditorView::openExternalFile()
 1669 {
 1670     QAction *act = qobject_cast<QAction *>(sender());
 1671     QString name = act->data().toString();
 1672     name.replace("\\string~",QDir::homePath());
 1673     if (!name.isEmpty())
 1674         emit openFile(name);
 1675 }
 1676 
 1677 void LatexEditorView::openPackageDocumentation(QString package)
 1678 {
 1679     QString command;
 1680     if (package.isEmpty()) {
 1681         QAction *act = qobject_cast<QAction *>(sender());
 1682         if (!act) return;
 1683         package = act->data().toString();
 1684     }
 1685     if (package.contains("#")) {
 1686         int i = package.indexOf("#");
 1687         command = package.mid(i + 1);
 1688         package = package.left(i);
 1689     }
 1690     // replace some package denominations
 1691     if (package == "latex-document" || package == "latex-dev")
 1692         package = "latex2e";
 1693     if (package == "class-scrartcl,scrreprt,scrbook")
 1694         package = "scrartcl";
 1695     if (package.startsWith("class-"))
 1696         package = package.mid(6);
 1697     if (!package.isEmpty()) {
 1698         if (config->texdocHelpInInternalViewer) {
 1699             QString docfile = Help::packageDocFile(package);
 1700             if (docfile.isEmpty())
 1701                 return;
 1702             if (docfile.endsWith(".pdf"))
 1703                 emit openInternalDocViewer(docfile, command);
 1704             else
 1705                 Help::instance()->viewTexdoc(package); // fallback e.g. for dvi
 1706         } else {
 1707             Help::instance()->viewTexdoc(package);
 1708         }
 1709     }
 1710 }
 1711 
 1712 void LatexEditorView::emitChangeDiff()
 1713 {
 1714     QAction *act = qobject_cast<QAction *>(sender());
 1715     QPoint pt = act->data().toPoint();
 1716     emit changeDiff(pt);
 1717 }
 1718 
 1719 void LatexEditorView::emitGotoDefinitionFromAction()
 1720 {
 1721     QDocumentCursor c;
 1722     QAction *act = qobject_cast<QAction *>(sender());
 1723     if (act) {
 1724         c = act->data().value<QDocumentCursor>();
 1725     }
 1726     emit gotoDefinition(c);
 1727 }
 1728 
 1729 void LatexEditorView::emitFindLabelUsagesFromAction()
 1730 {
 1731     QAction *action = qobject_cast<QAction *>(sender());
 1732     if (!action) return;
 1733     QString labelText = action->data().toString();
 1734     LatexDocument *doc = action->property("doc").value<LatexDocument *>();
 1735     emit findLabelUsages(doc, labelText);
 1736 }
 1737 
 1738 void LatexEditorView::emitSyncPDFFromAction()
 1739 {
 1740     QDocumentCursor c;
 1741     QAction *act = qobject_cast<QAction *>(sender());
 1742     if (act) {
 1743         c = act->data().value<QDocumentCursor>();
 1744     }
 1745     emit syncPDFRequested(c);
 1746 }
 1747 
 1748 void LatexEditorView::lineMarkClicked(int line)
 1749 {
 1750     QDocumentLine l = editor->document()->line(line);
 1751     if (!l.isValid()) return;
 1752     //remove old mark (when possible)
 1753     for (int i = -1; i < 10; i++)
 1754         if (l.hasMark(bookMarkId(i))) {
 1755             l.removeMark(bookMarkId(i));
 1756             editor->removeMark(l.handle(),"bookmark");
 1757             emit bookmarkRemoved(l.handle());
 1758             return;
 1759         }
 1760     // remove error marks
 1761     if (l.hasMark(QLineMarksInfoCenter::instance()->markTypeId("error"))) {
 1762         l.removeMark(QLineMarksInfoCenter::instance()->markTypeId("error"));
 1763         return;
 1764     }
 1765     if (l.hasMark(QLineMarksInfoCenter::instance()->markTypeId("warning"))) {
 1766         l.removeMark(QLineMarksInfoCenter::instance()->markTypeId("warning"));
 1767         return;
 1768     }
 1769     if (l.hasMark(QLineMarksInfoCenter::instance()->markTypeId("badbox"))) {
 1770         l.removeMark(QLineMarksInfoCenter::instance()->markTypeId("badbox"));
 1771         return;
 1772     }
 1773     //add unused mark (1,2 .. 9,0) (when possible)
 1774     for (int i = 1; i <= 10; i++) {
 1775         if (editor->document()->findNextMark(bookMarkId(i % 10)) < 0) {
 1776             l.addMark(bookMarkId(i % 10));
 1777             editor->addMark(l.handle(),Qt::darkMagenta,"bookmark");
 1778             emit bookmarkAdded(l.handle(), i);
 1779             return;
 1780         }
 1781     }
 1782     l.addMark(bookMarkId(-1));
 1783     editor->addMark(l.handle(),Qt::darkMagenta,"bookmark");
 1784     emit bookmarkAdded(l.handle(), -1);
 1785 }
 1786 
 1787 void LatexEditorView::lineMarkToolTip(int line, int mark)
 1788 {
 1789     if (line < 0 || line >= editor->document()->lines()) return;
 1790     int errorMarkID = QLineMarksInfoCenter::instance()->markTypeId("error");
 1791     int warningMarkID = QLineMarksInfoCenter::instance()->markTypeId("warning");
 1792     int badboxMarkID = QLineMarksInfoCenter::instance()->markTypeId("badbox");
 1793     if (mark != errorMarkID && mark != warningMarkID && mark != badboxMarkID) return;
 1794     QList<int> errors = lineToLogEntries.values(editor->document()->line(line).handle());
 1795     if (!errors.isEmpty())
 1796         emit showMarkTooltipForLogMessage(errors);
 1797 }
 1798 
 1799 void LatexEditorView::clearOverlays()
 1800 {
 1801     for (int i = 0; i < editor->document()->lineCount(); i++) {
 1802         QDocumentLine line = editor->document()->line(i);
 1803         if (!line.isValid()) continue;
 1804 
 1805         //remove all overlays used for latex things, in descending frequency
 1806         line.clearOverlays(SpellerUtility::spellcheckErrorFormat);
 1807         line.clearOverlays(referencePresentFormat);
 1808         line.clearOverlays(citationPresentFormat);
 1809         line.clearOverlays(referenceMissingFormat);
 1810         line.clearOverlays(referenceMultipleFormat);
 1811         line.clearOverlays(citationMissingFormat);
 1812         line.clearOverlays(environmentFormat);
 1813         line.clearOverlays(syntaxErrorFormat);
 1814         line.clearOverlays(structureFormat);
 1815         line.clearOverlays(todoFormat);
 1816         foreach (const int f, grammarFormats)
 1817             line.clearOverlays(f);
 1818     }
 1819 }
 1820 
 1821 /*!
 1822     Will be called from certain events, that should maybe result in opening the
 1823     completer. Since the completer depends on the context, the caller doesn't
 1824     have to be sure that a completer is really necassary. The context is checked
 1825     in this function and an appropriate completer is opened if necessary.
 1826     For example, typing a colon within a citation should start the completer.
 1827     Therefore, typing a colon will trigger this function. It's checked in here
 1828     if the context is a citation.
 1829  */
 1830 void LatexEditorView::mayNeedToOpenCompleter(bool fromSingleChar)
 1831 {
 1832     QDocumentCursor c = editor->cursor();
 1833     QDocumentLineHandle *dlh = c.line().handle();
 1834     if (!dlh)
 1835         return;
 1836     TokenStack ts = Parsing::getContext(dlh, c.columnNumber());
 1837     Token tk;
 1838     if (!ts.isEmpty()) {
 1839         tk = ts.top();
 1840         if(fromSingleChar && tk.length!=1){
 1841             return; // only open completer on first char
 1842         }
 1843         if (tk.type == Token::word && tk.subtype == Token::none && ts.size() > 1) {
 1844             // set brace type
 1845             ts.pop();
 1846             tk = ts.top();
 1847         }
 1848     }else{
 1849         return; // no context available
 1850     }
 1851 
 1852     Token::TokenType type = tk.type;
 1853     if (tk.subtype != Token::none)
 1854         type = tk.subtype;
 1855 
 1856     QList<Token::TokenType> lst;
 1857     lst << Token::package << Token::keyValArg << Token::keyVal_val << Token::keyVal_key << Token::bibItem << Token::labelRefList;
 1858     if(fromSingleChar){
 1859         lst << Token::labelRef;
 1860     }
 1861     if (lst.contains(type))
 1862         emit openCompleter();
 1863     if (ts.isEmpty() || fromSingleChar)
 1864         return;
 1865     ts.pop();
 1866     if (!ts.isEmpty()) { // check next level if 1. check fails (e.g. key vals are set to real value)
 1867         tk = ts.top();
 1868         type = tk.type;
 1869         if (lst.contains(type))
 1870             emit openCompleter();
 1871     }
 1872 }
 1873 
 1874 void LatexEditorView::documentContentChanged(int linenr, int count)
 1875 {
 1876     Q_ASSERT(editor);
 1877     QDocumentLine startline = editor->document()->line(linenr);
 1878     if ((linenr >= 0 || count < editor->document()->lines()) && editor->cursor().isValid() &&
 1879             !editor->cursor().atLineStart() && editor->cursor().line().text().trimmed().length() > 0 &&
 1880             startline.isValid()) {
 1881         bool add = false;
 1882         if (changePositions.size() <= 0) add = true;
 1883         else if (curChangePos < 1) {
 1884             if (changePositions.first().dlh() != startline.handle()) add = true;
 1885             else changePositions.first().setColumnNumber(editor->cursor().columnNumber());
 1886         } else if (curChangePos >= changePositions.size() - 1) {
 1887             if (changePositions.last().dlh() != startline.handle()) add = true;
 1888             else changePositions.last().setColumnNumber(editor->cursor().columnNumber());
 1889         }  else if (changePositions[curChangePos].dlh() == startline.handle()) changePositions[curChangePos].setColumnNumber(editor->cursor().columnNumber());
 1890         else if (changePositions[curChangePos + 1].dlh() == startline.handle()) changePositions[curChangePos + 1].setColumnNumber(editor->cursor().columnNumber());
 1891         else add = true;
 1892         if (add) {
 1893             curChangePos = -1;
 1894             changePositions.insert(0, CursorPosition(editor->document()->cursor(linenr, editor->cursor().columnNumber())));
 1895             if (changePositions.size() > 20) changePositions.removeLast();
 1896         }
 1897     }
 1898 
 1899     if (autoPreviewCursor.size() > 0) {
 1900         for (int i = 0; i < autoPreviewCursor.size(); i++) {
 1901             const QDocumentCursor &c = autoPreviewCursor[i];
 1902             if (c.lineNumber() >= linenr && c.anchorLineNumber() < linenr + count)
 1903                 emit showPreview(c); //may modify autoPreviewCursor
 1904         }
 1905 
 1906     }
 1907 
 1908     // checking
 1909     if (!QDocument::defaultFormatScheme()) return;
 1910     if (!config->realtimeChecking) return; //disable all => implicit disable environment color correction (optimization)
 1911     const LatexDocument *ldoc = qobject_cast<const LatexDocument *>(editor->document());
 1912     bool latexLikeChecking = ldoc && ldoc->languageIsLatexLike();
 1913     if (!latexLikeChecking && !config->inlineCheckNonTeXFiles) return;
 1914 
 1915     if (config->inlineGrammarChecking) {
 1916         QList<LineInfo> changedLines;
 1917         int lookBehind = 0;
 1918         for (; linenr - lookBehind >= 0; lookBehind++)
 1919             if (editor->document()->line(linenr - lookBehind).firstChar() == -1) break;
 1920         if (lookBehind > 0) lookBehind--;
 1921         if (lookBehind > linenr) lookBehind = linenr;
 1922 
 1923         LIST_RESERVE(changedLines, linenr + count + lookBehind + 1);
 1924         int truefirst = linenr - lookBehind;
 1925         for (int i = linenr - lookBehind; i < editor->document()->lineCount(); i++) {
 1926             QDocumentLine line = editor->document()->line(i);
 1927             if (!line.isValid()) break;
 1928             LineInfo temp;
 1929             temp.line = line.handle();
 1930             temp.text = line.text();
 1931             // blank irrelevant content, i.e. commands, non-text, comments, verbatim
 1932             QDocumentLineHandle *dlh = line.handle();
 1933             TokenList tl = dlh->getCookie(QDocumentLine::LEXER_COOKIE).value<TokenList>();
 1934             foreach(Token tk,tl){
 1935                 if(tk.type==Token::word && (tk.subtype==Token::none||tk.subtype==Token::text))
 1936                     continue;
 1937                 if(tk.type==Token::punctuation && (tk.subtype==Token::none||tk.subtype==Token::text))
 1938                     continue;
 1939                 if(tk.type==Token::symbol && (tk.subtype==Token::none||tk.subtype==Token::text))
 1940                     continue; // don't blank symbol like '~'
 1941                 if(tk.type==Token::braces && tk.subtype==Token::text){
 1942                     //remove braces around text argument
 1943                     temp.text.replace(tk.start,1,QString(' '));
 1944                     temp.text.replace(tk.start+tk.length-1,1,QString(' '));
 1945                     continue;
 1946                 }
 1947                 temp.text.replace(tk.start,tk.length,QString(tk.length,' '));
 1948             }
 1949 
 1950             changedLines << temp;
 1951             if (line.firstChar() == -1) {
 1952                 emit linesChanged(speller->name(), document, changedLines, truefirst);
 1953                 truefirst += changedLines.size();
 1954                 changedLines.clear();
 1955                 if (i >= linenr + count) break;
 1956             }
 1957         }
 1958         if (!changedLines.isEmpty())
 1959             emit linesChanged(speller->name(), document, changedLines, truefirst);
 1960 
 1961     }
 1962 
 1963     Q_ASSERT(speller);
 1964     for (int i = linenr; i < linenr + count; i++) {
 1965         QDocumentLine line = editor->document()->line(i);
 1966         if (!line.isValid()) continue;
 1967 
 1968         //remove all overlays used for latex things, in descending frequency
 1969         line.clearOverlays(formatsList); //faster as it avoids multiple lock/unlock operations
 1970     }
 1971 
 1972     for (int i = linenr; i < linenr + count; i++) {
 1973         QDocumentLine line = editor->document()->line(i);
 1974         if (!line.isValid()) continue;
 1975 
 1976         bool addedOverlaySpellCheckError = false;
 1977         bool addedOverlayReference = false;
 1978         bool addedOverlayCitation = false;
 1979         bool addedOverlayEnvironment = false;
 1980         bool addedOverlayStructure = false;
 1981         bool addedOverlayTodo = false;
 1982         bool addedOverlayPackage = false;
 1983 
 1984         // diff presentation
 1985         QVariant cookie = line.getCookie(QDocumentLine::DIFF_LIST_COOCKIE);
 1986         if (!cookie.isNull()) {
 1987             DiffList diffList = cookie.value<DiffList>();
 1988             for (int i = 0; i < diffList.size(); i++) {
 1989                 DiffOp op = diffList.at(i);
 1990                 switch (op.type) {
 1991                 case DiffOp::Delete:
 1992                     line.addOverlay(QFormatRange(op.start, op.length, deleteFormat));
 1993                     break;
 1994                 case DiffOp::Insert:
 1995                     line.addOverlay(QFormatRange(op.start, op.length, insertFormat));
 1996                     break;
 1997                 case DiffOp::Replace:
 1998                     line.addOverlay(QFormatRange(op.start, op.length, replaceFormat));
 1999                     break;
 2000                 default:
 2001                     ;
 2002                 }
 2003 
 2004             }
 2005         }
 2006         // handle % TODO
 2007         QLineFormatAnalyzer lineFormatAnaylzer(line.getFormats());
 2008         int col = lineFormatAnaylzer.firstCol(commentFormat);
 2009         if(col>=0){
 2010             QString curLine=line.text();
 2011             QString text = curLine.mid(col, lineFormatAnaylzer.formatLength(col));
 2012             QString regularExpression=ConfigManagerInterface::getInstance()->getOption("Editor/todo comment regExp").toString();
 2013             QRegExp rx(regularExpression);
 2014             if (rx.indexIn(text)==0) {
 2015                 line.addOverlay(QFormatRange(col, lineFormatAnaylzer.formatLength(col), todoFormat));
 2016                 addedOverlayTodo = true;
 2017             }
 2018         }
 2019 
 2020         // alternative context detection
 2021         QDocumentLineHandle *dlh = line.handle();
 2022         TokenList tl = dlh->getCookie(QDocumentLine::LEXER_COOKIE).value<TokenList>();
 2023         for (int tkNr = 0; tkNr < tl.length(); tkNr++) {
 2024             Token tk = tl.at(tkNr);
 2025             if (tk.subtype == Token::verbatim)
 2026                 continue;
 2027             if (tk.type == Token::comment)
 2028                 break;
 2029             if (latexLikeChecking) {
 2030                 if ((tk.subtype == Token::title || tk.subtype == Token::shorttitle) && (tk.type == Token::braces || tk.type == Token::openBrace)) {
 2031                     line.addOverlay(QFormatRange(tk.innerStart(), tk.innerLength(), structureFormat));
 2032                     addedOverlayStructure = true;
 2033                 }
 2034                 if (tk.subtype == Token::todo && (tk.type == Token::braces || tk.type == Token::openBrace)) {
 2035                     line.addOverlay(QFormatRange(tk.innerStart(), tk.innerLength(), todoFormat));
 2036                     addedOverlayTodo = true;
 2037                 }
 2038                 if (tk.type == Token::env || tk.type == Token::beginEnv) {
 2039                     line.addOverlay(QFormatRange(tk.start, tk.length, environmentFormat));
 2040                     addedOverlayEnvironment = true;
 2041                 }
 2042                 if ((tk.type == Token::package || tk.type == Token::beamertheme || tk.type == Token::documentclass) && config->inlinePackageChecking) {
 2043                     // package
 2044                     QString preambel;
 2045                     if (tk.type == Token::beamertheme) { // special treatment for  \usetheme
 2046                         preambel = "beamertheme";
 2047                     }
 2048                     QString text = dlh->text();
 2049                     QString rpck =  trimLeft(text.mid(tk.start, tk.length)); // left spaces are ignored by \cite, right space not
 2050                     //check and highlight
 2051                     if (latexPackageList->isEmpty())
 2052                         dlh->addOverlay(QFormatRange(tk.start, tk.length, packageUndefinedFormat));
 2053                     else if (latexPackageList->contains(preambel + rpck))
 2054                         dlh->addOverlay(QFormatRange(tk.start, tk.length, packagePresentFormat));
 2055                     else
 2056                         dlh->addOverlay(QFormatRange(tk.start, tk.length, packageMissingFormat));
 2057 
 2058                     addedOverlayPackage = true;
 2059                 }
 2060                 if ((tk.type == Token::labelRef || tk.type == Token::labelRefList) && config->inlineReferenceChecking) {
 2061                     QDocumentLineHandle *dlh = tk.dlh;
 2062                     QString ref = dlh->text().mid(tk.start, tk.length);
 2063                     if (ref.contains('#')) continue;  // don't highlight refs in definitions e.g. in \newcommand*{\FigRef}[1]{figure~\ref{#1}}
 2064                     int cnt = document->countLabels(ref);
 2065                     if (cnt > 1) {
 2066                         dlh->addOverlay(QFormatRange(tk.start, tk.length, referenceMultipleFormat));
 2067                     } else if (cnt == 1) dlh->addOverlay(QFormatRange(tk.start, tk.length, referencePresentFormat));
 2068                     else dlh->addOverlay(QFormatRange(tk.start, tk.length, referenceMissingFormat));
 2069                     addedOverlayReference = true;
 2070                 }
 2071                 if (tk.type == Token::label && config->inlineReferenceChecking) {
 2072                     QDocumentLineHandle *dlh = tk.dlh;
 2073                     QString ref = dlh->text().mid(tk.start, tk.length);
 2074                     int cnt = document->countLabels(ref);
 2075                     if (cnt > 1) {
 2076                         dlh->addOverlay(QFormatRange(tk.start, tk.length, referenceMultipleFormat));
 2077                     } else dlh->addOverlay(QFormatRange(tk.start, tk.length, referencePresentFormat));
 2078                     // look for corresponding reeferences and adapt format respectively
 2079                     //containedLabels->updateByKeys(QStringList(ref),containedReferences);
 2080                     document->updateRefsLabels(ref);
 2081                     addedOverlayReference = true;
 2082                 }
 2083                 if (tk.type == Token::bibItem && config->inlineCitationChecking) {
 2084                     QDocumentLineHandle *dlh = tk.dlh;
 2085                     QString text = dlh->text().mid(tk.start, tk.length);
 2086                     if (text.contains('#')) continue; // don't highlight cite in definitions e.g. in \newcommand*{\MyCite}[1]{see~\cite{#1}}
 2087                     QString rcit =  trimLeft(text); // left spaces are ignored by \cite, right space not
 2088                     //check and highlight
 2089                     if (document->bibIdValid(rcit))
 2090                         dlh->addOverlay(QFormatRange(tk.start, tk.length, citationPresentFormat));
 2091                     else
 2092                         dlh->addOverlay(QFormatRange(tk.start, tk.length, citationMissingFormat));
 2093                     addedOverlayCitation = true;
 2094                 }
 2095             }// if latexLineCheking
 2096             int tkLength=tk.length;
 2097             if (tk.type == Token::word && (tk.subtype == Token::text || tk.subtype == Token::title || tk.subtype == Token::shorttitle || tk.subtype == Token::todo || tk.subtype == Token::none)  && config->inlineSpellChecking && tk.length >= 3 && speller) {
 2098                 QString word = tk.getText();
 2099                 if(tkNr+1 < tl.length()){
 2100                     //check if next token is . or -
 2101                     Token tk1 = tl.at(tkNr+1);
 2102                     if(tk1.type==Token::punctuation && tk1.start==(tk.start+tk.length) && !word.endsWith("\"")){
 2103                         QString add=tk1.getText();
 2104                         if(add=="."||add=="-"){
 2105                             word+=add;
 2106                             tkNr++;
 2107                             tkLength+=tk1.length;
 2108                         }
 2109                         if(add=="'"){
 2110                             if(tkNr+2 < tl.length()){
 2111                                 Token tk2 = tl.at(tkNr+2);
 2112                                 if(tk2.type==Token::word && tk2.start==(tk1.start+tk1.length)){
 2113                                     add+=tk2.getText();
 2114                                     word+=add;
 2115                                     tkNr+=2;
 2116                                     tkLength+=tk1.length+tk2.length;
 2117                                 }
 2118                             }
 2119                         }
 2120                     }
 2121                 }
 2122                 word = latexToPlainWordwithReplacementList(word, mReplacementList); //remove special chars
 2123                 if (config->hideNonTextSpellingErrors && (isNonTextFormat(line.getFormatAt(tk.start)) || isNonTextFormat(line.getFormatAt(tk.start + tk.length - 1)) )) // TODO:needs to be adapted
 2124                     continue;
 2125                 if (!speller->check(word) ) {
 2126                     if (word.endsWith('-') && speller->check(word.left(word.length() - 1)))
 2127                         continue; // word ended with '-', without that letter, word is correct (e.g. set-up / german hypehantion)
 2128                     if(word.endsWith('.')){
 2129                         tkLength--; // don't take point into misspelled word
 2130                     }
 2131                     line.addOverlay(QFormatRange(tk.start, tkLength, SpellerUtility::spellcheckErrorFormat));
 2132                     addedOverlaySpellCheckError = true;
 2133                 }
 2134             }
 2135         } // for Tokenslist
 2136 
 2137         //update wrapping if the an overlay changed the width of the text
 2138         //TODO: should be handled by qce to be consistent (with syntax check and search)
 2139         if (!editor->document()->getFixedPitch() && editor->flag(QEditor::LineWrap)) {
 2140             bool updateWrapping = false;
 2141             QFormatScheme *ff = QDocument::defaultFormatScheme();
 2142             REQUIRE(ff);
 2143             updateWrapping |= addedOverlaySpellCheckError && ff->format(SpellerUtility::spellcheckErrorFormat).widthChanging();
 2144             updateWrapping |= addedOverlayReference && (ff->format(referenceMissingFormat).widthChanging() || ff->format(referencePresentFormat).widthChanging() || ff->format(referenceMultipleFormat).widthChanging());
 2145             updateWrapping |= addedOverlayCitation && (ff->format(citationPresentFormat).widthChanging() || ff->format(citationMissingFormat).widthChanging());
 2146             updateWrapping |= addedOverlayPackage && (ff->format(packagePresentFormat).widthChanging() || ff->format(packageMissingFormat).widthChanging());
 2147             updateWrapping |= addedOverlayEnvironment && ff->format(environmentFormat).widthChanging();
 2148             updateWrapping |= addedOverlayStructure && ff->format(structureFormat).widthChanging();
 2149             updateWrapping |= addedOverlayTodo && ff->format(todoFormat).widthChanging();
 2150             if (updateWrapping)
 2151                 line.handle()->updateWrapAndNotifyDocument(i);
 2152 
 2153         }
 2154     }
 2155     editor->document()->markViewDirty();
 2156 }
 2157 
 2158 void LatexEditorView::lineDeleted(QDocumentLineHandle *l)
 2159 {
 2160     QHash<QDocumentLineHandle *, int>::iterator it;
 2161     while ((it = lineToLogEntries.find(l)) != lineToLogEntries.end()) {
 2162         logEntryToLine.remove(it.value());
 2163         lineToLogEntries.erase(it);
 2164     }
 2165 
 2166     //QMessageBox::information(0,QString::number(nr),"",0);
 2167     for (int i = changePositions.size() - 1; i >= 0; i--)
 2168         if (changePositions[i].dlh() == l)
 2169             /*if (QDocumentLine(changePositions[i].first).previous().isValid()) changePositions[i].first=QDocumentLine(changePositions[i].first).previous().handle();
 2170             else if (QDocumentLine(changePositions[i].first).next().isValid()) changePositions[i].first=QDocumentLine(changePositions[i].first).next().handle();
 2171             else  */ //creating a QDocumentLine with a deleted handle is not possible (it will modify the handle reference count which will trigger another delete event, leading to an endless loop)
 2172             changePositions.removeAt(i);
 2173     //    QMessageBox::information(0,"trig",0);
 2174 
 2175     emit lineHandleDeleted(l);
 2176     editor->document()->markViewDirty();
 2177 }
 2178 void LatexEditorView::textReplaceFromAction()
 2179 {
 2180     QAction *action = qobject_cast<QAction *>(QObject::sender());
 2181     if (editor && action) {
 2182         QString replacementText = action->data().toString();
 2183         if (replacementText.isEmpty()) editor->cursor().removeSelectedText();
 2184         else editor->write(replacementText);
 2185         editor->setCursor(editor->cursor()); //to remove selection range
 2186     }
 2187 }
 2188 
 2189 void LatexEditorView::spellCheckingAlwaysIgnore()
 2190 {
 2191     if (speller && editor && editor->cursor().hasSelection() && (editor->cursor().selectedText() == defaultInputBinding->lastSpellCheckedWord)) {
 2192         QString newToIgnore = editor->cursor().selectedText();
 2193         speller->addToIgnoreList(newToIgnore);
 2194     }
 2195 }
 2196 
 2197 void LatexEditorView::addReplaceActions(QMenu *menu, const QStringList &replacements, bool italic)
 2198 {
 2199     if (!menu) return;
 2200     QAction *before = nullptr;
 2201     if (!menu->actions().isEmpty()) before = menu->actions()[0];
 2202 
 2203     foreach (const QString &text, replacements) {
 2204         QAction *replaceAction = new QAction(this);
 2205         if (text.isEmpty()) {
 2206             replaceAction->setText(tr("Delete"));
 2207             QFont deleteFont;
 2208             deleteFont.setItalic(italic);
 2209             replaceAction->setFont(deleteFont);
 2210         } else {
 2211             replaceAction->setText(text);
 2212             QFont correctionFont;
 2213             correctionFont.setBold(true);
 2214             correctionFont.setItalic(italic);
 2215             replaceAction->setFont(correctionFont);
 2216         }
 2217         replaceAction->setData(text);
 2218         connect(replaceAction, SIGNAL(triggered()), this, SLOT(textReplaceFromAction()));
 2219         menu->insertAction(before, replaceAction);
 2220     }
 2221 }
 2222 
 2223 void LatexEditorView::populateSpellingMenu()
 2224 {
 2225     QMenu *menu = qobject_cast<QMenu *>(sender());
 2226     if (!menu) return;
 2227     QString word = menu->property("word").toString();
 2228     if (word.isEmpty()) return;
 2229     addSpellingActions(menu, word, true);
 2230 }
 2231 
 2232 void LatexEditorView::addSpellingActions(QMenu *menu, QString word, bool dedicatedMenu)
 2233 {
 2234     if (menu->property("isSpellingPopulated").toBool()) return;
 2235 
 2236     QStringList suggestions = speller->suggest(word);
 2237     addReplaceActions(menu, suggestions, false);
 2238 
 2239     QAction *act = new QAction(LatexEditorView::tr("Add to Dictionary"), menu);
 2240     connect(act, SIGNAL(triggered()), this, SLOT(spellCheckingAlwaysIgnore()));
 2241     if (dedicatedMenu) {
 2242         menu->addSeparator();
 2243     } else {
 2244         QFont ignoreFont;
 2245         ignoreFont.setItalic(true);
 2246         act->setFont(ignoreFont);
 2247     }
 2248     menu->addAction(act);
 2249     menu->setProperty("isSpellingPopulated", true);
 2250 }
 2251 
 2252 void LatexEditorView::spellRemoveMarkers(const QString &newIgnoredWord)
 2253 {
 2254     REQUIRE(editor);
 2255     QDocument* doc = editor->document();
 2256     if (!doc) return;
 2257     QString newUpperIgnoredWord=newIgnoredWord; //remove upper letter start as well
 2258     if(!newUpperIgnoredWord.isEmpty()){
 2259         newUpperIgnoredWord[0]=newUpperIgnoredWord[0].toUpper();
 2260     }
 2261     //documentContentChanged(editor->cursor().lineNumber(),1);
 2262     for (int i = 0; i < doc->lines(); i++) {
 2263         QList<QFormatRange> li = doc->line(i).getOverlays(SpellerUtility::spellcheckErrorFormat);
 2264         QString curLineText = doc->line(i).text();
 2265         for (int j = 0; j < li.size(); j++)
 2266             if (latexToPlainWord(curLineText.mid(li[j].offset, li[j].length)) == newIgnoredWord || latexToPlainWord(curLineText.mid(li[j].offset, li[j].length)) == newUpperIgnoredWord) {
 2267                 doc->line(i).removeOverlay(li[j]);
 2268                 doc->line(i).setFlag(QDocumentLine::LayoutDirty, true);
 2269             }
 2270     }
 2271     editor->viewport()->update();
 2272 }
 2273 
 2274 void LatexEditorView::closeCompleter()
 2275 {
 2276     completer->close();
 2277 }
 2278 
 2279 /*
 2280  * Extracts the math formula at the given cursor position including math delimiters.
 2281  * Current limitations: the cursor needs to be on one of the delimiters. This does
 2282  * not work for math environments
 2283  * Returns an empty string if there is no math formula.
 2284  */
 2285 QString LatexEditorView::extractMath(QDocumentCursor cursor)
 2286 {
 2287     if (cursor.line().getFormatAt(cursor.columnNumber()) != math_DelimiterFormat)
 2288         return QString();
 2289     int col = cursor.columnNumber();
 2290     while (col > 0 && cursor.line().getFormatAt(col - 1) == math_DelimiterFormat) col--;
 2291     cursor.setColumnNumber(col);
 2292     return parenthizedTextSelection(cursor).selectedText();
 2293 }
 2294 
 2295 bool LatexEditorView::moveToCommandStart (QDocumentCursor &cursor, QString commandPrefix)
 2296 {
 2297     QString line = cursor.line().text();
 2298     int lastOffset = cursor.columnNumber();
 2299     if (lastOffset >= line.length()) {
 2300         lastOffset = -1;
 2301     }
 2302     int foundOffset = line.lastIndexOf(commandPrefix, lastOffset);
 2303     if (foundOffset == -1) {
 2304         return false;
 2305     }
 2306     cursor.moveTo(cursor.lineNumber(), foundOffset);
 2307     return true;
 2308 }
 2309 
 2310 bool LatexEditorView::showMathEnvPreview(QDocumentCursor cursor, QString command, QString environment, QPoint pos)
 2311 {
 2312     QStringList envAliases = document->lp.environmentAliases.values(environment);
 2313     bool found;
 2314     if (((command == "\\begin" || command == "\\end") && envAliases.contains("math")) || command == "\\[" || command == "\\]") {
 2315         found = moveToCommandStart(cursor, "\\");
 2316     } else if (command == "$" || command == "$$") {
 2317         found = moveToCommandStart(cursor, command);
 2318     } else {
 2319         found = false;
 2320     }
 2321     if (!found) {
 2322         QToolTip::hideText();
 2323         return false;
 2324     }
 2325     QString text = parenthizedTextSelection(cursor).selectedText();
 2326     if (text.isEmpty()) {
 2327         QToolTip::hideText();
 2328         return false;
 2329     }
 2330     m_point = editor->mapToGlobal(editor->mapFromFrame(pos));
 2331     emit showPreview(text);
 2332     return true;
 2333 }
 2334 
 2335 void LatexEditorView::mouseHovered(QPoint pos)
 2336 {
 2337     // reimplement to what is necessary
 2338 
 2339     if (pos.x() < 0) return; // hover event on panel
 2340     QDocumentCursor cursor;
 2341     cursor = editor->cursorForPosition(editor->mapToContents(pos));
 2342     QString line = cursor.line().text();
 2343     QDocumentLine l = cursor.line();
 2344 
 2345     QFormatRange fr = cursor.line().getOverlayAt(cursor.columnNumber(), replaceFormat);
 2346     if (fr.length > 0 && fr.format == replaceFormat) {
 2347         QVariant var = l.getCookie(QDocumentLine::DIFF_LIST_COOCKIE);
 2348         if (var.isValid()) {
 2349             DiffList diffList = var.value<DiffList>();
 2350             DiffOp op;
 2351             op.start = -1;
 2352             foreach (op, diffList) {
 2353                 if (op.start <= cursor.columnNumber() && op.start + op.length >= cursor.columnNumber()) {
 2354                     break;
 2355                 }
 2356                 op.start = -1;
 2357             }
 2358 
 2359             if (op.start >= 0 && !op.text.isEmpty()) {
 2360                 QString message = op.text;
 2361                 QToolTip::showText(editor->mapToGlobal(editor->mapFromFrame(pos)), message);
 2362                 return;
 2363             }
 2364         }
 2365     }
 2366     foreach (const int f, grammarFormats) {
 2367         fr = cursor.line().getOverlayAt(cursor.columnNumber(), f);
 2368         if (fr.length > 0 && fr.format == f) {
 2369             QVariant var = l.getCookie(QDocumentLine::GRAMMAR_ERROR_COOKIE);
 2370             if (var.isValid()) {
 2371                 const QList<GrammarError> &errors = var.value<QList<GrammarError> >();
 2372                 for (int i = 0; i < errors.size(); i++)
 2373                     if (errors[i].offset <= cursor.columnNumber() && errors[i].offset + errors[i].length >= cursor.columnNumber()) {
 2374                         QString message = errors[i].message;
 2375                         QToolTip::showText(editor->mapToGlobal(editor->mapFromFrame(pos)), message);
 2376                         return;
 2377                     }
 2378             }
 2379         }
 2380     }
 2381     // check for latex error
 2382     //syntax checking
 2383     fr = cursor.line().getOverlayAt(cursor.columnNumber(), syntaxErrorFormat);
 2384     if (fr.length > 0 && fr.format == syntaxErrorFormat) {
 2385         StackEnvironment env;
 2386         document->getEnv(cursor.lineNumber(), env);
 2387         TokenStack remainder;
 2388         int i = cursor.lineNumber();
 2389         if (document->line(i - 1).handle())
 2390             remainder = document->line(i - 1).handle()->getCookieLocked(QDocumentLine::LEXER_REMAINDER_COOKIE).value<TokenStack >();
 2391         QString text = l.text();
 2392         if (!text.isEmpty()) {
 2393             QString message = document->getErrorAt(l.handle(), cursor.columnNumber(), env, remainder);
 2394             QToolTip::showText(editor->mapToGlobal(editor->mapFromFrame(pos)), message);
 2395             return;
 2396         }
 2397     }
 2398     // new way
 2399     QDocumentLineHandle *dlh = cursor.line().handle();
 2400 
 2401     TokenList tl = dlh ? dlh->getCookieLocked(QDocumentLine::LEXER_COOKIE).value<TokenList>() : TokenList();
 2402 
 2403     //Tokens tk=getTokenAtCol(dlh,cursor.columnNumber());
 2404     TokenStack ts = Parsing::getContext(dlh, cursor.columnNumber());
 2405     Token tk;
 2406     if (!ts.isEmpty()) {
 2407         tk = ts.top();
 2408     }
 2409 
 2410     LatexParser &lp = LatexParser::getInstance();
 2411     QString command, value;
 2412     bool handled = false;
 2413     if (tk.type != Token::none) {
 2414         int tkPos = tl.indexOf(tk);
 2415         if (tk.type == Token::command || tk.type == Token::commandUnknown) {
 2416             handled = true;
 2417             command = line.mid(tk.start, tk.length);
 2418             CommandDescription cd = lp.commandDefs.value(command);
 2419             if (cd.args > 0)
 2420                 value = Parsing::getArg(tl.mid(tkPos + 1), dlh, 0, ArgumentList::Mandatory);
 2421             if (config->toolTipPreview && showMathEnvPreview(cursor, command, value, pos)) {
 2422                 // action is already performed as a side effect
 2423             } else if (config->toolTipHelp && completer->getLatexReference()) {
 2424                 QString topic = completer->getLatexReference()->getTextForTooltip(command);
 2425                 if (!topic.isEmpty()) QToolTip::showText(editor->mapToGlobal(editor->mapFromFrame(pos)), topic);
 2426             }
 2427         }
 2428         value = tk.getText();
 2429         if (tk.type == Token::env || tk.type == Token::beginEnv) {
 2430             handled = true;
 2431             if (config->toolTipPreview && showMathEnvPreview(cursor, "\\begin", value, pos)) {
 2432                 // action is already performed as a side effect
 2433             } else if (config->toolTipHelp && completer->getLatexReference()) {
 2434                 QString topic = completer->getLatexReference()->getTextForTooltip("\\begin{" + value);
 2435                 if (!topic.isEmpty()) QToolTip::showText(editor->mapToGlobal(editor->mapFromFrame(pos)), topic);
 2436             }
 2437         }
 2438         if (tk.type == Token::labelRef || tk.type == Token::labelRefList) {
 2439             handled = true;
 2440             int cnt = document->countLabels(value);
 2441             QString mText = "";
 2442             if (cnt == 0) {
 2443                 mText = tr("label missing!");
 2444             } else if (cnt > 1) {
 2445                 mText = tr("label defined multiple times!");
 2446             } else {
 2447                 QMultiHash<QDocumentLineHandle *, int> result = document->getLabels(value);
 2448                 QDocumentLineHandle *mLine = result.keys().first();
 2449                 int l = mLine->document()->indexOf(mLine);
 2450                 LatexDocument *doc = qobject_cast<LatexDocument *> (editor->document());
 2451                 if (mLine->document() != editor->document()) {
 2452                     doc = document->parent->findDocument(mLine->document());
 2453                     if (doc) mText = tr("<p style='white-space:pre'><b>Filename: %1</b>\n").arg(doc->getFileName());
 2454                 }
 2455                 if (doc)
 2456                     mText += doc->exportAsHtml(doc->cursor(qMax(0, l - 2), 0, l + 2), true, true, 60);
 2457             }
 2458             QToolTip::showText(editor->mapToGlobal(editor->mapFromFrame(pos)), mText);
 2459         }
 2460         if (tk.type == Token::label) {
 2461             handled = true;
 2462             if (document->countLabels(value) > 1) {
 2463                 QToolTip::showText(editor->mapToGlobal(editor->mapFromFrame(pos)), tr("label defined multiple times!"));
 2464             } else {
 2465                 int cnt = document->countRefs(value);
 2466                 QToolTip::showText(editor->mapToGlobal(editor->mapFromFrame(pos)), tr("%n reference(s) to this label", "", cnt));
 2467             }
 2468         }
 2469         if (tk.type == Token::package || tk.type == Token::beamertheme || tk.type == Token::documentclass) {
 2470             handled = true;
 2471             QString type = (tk.type == Token::documentclass) ? tr("Class") : tr("Package");
 2472             QString preambel;
 2473             if (tk.type == Token::beamertheme) { // special treatment for  \usetheme
 2474                 preambel = "beamertheme";
 2475                 type = tr("Beamer Theme");
 2476                 type.replace(' ', "&nbsp;");
 2477             }
 2478             QString text = QString("%1:&nbsp;<b>%2</b>").arg(type).arg(value);
 2479             if (latexPackageList->contains(preambel + value)) {
 2480                 QString description = LatexRepository::instance()->shortDescription(value);
 2481                 if (!description.isEmpty()) text += "<br>" + description;
 2482                 QToolTip::showText(editor->mapToGlobal(editor->mapFromFrame(pos)), text);
 2483             } else {
 2484                 text += "<br><b>(" + tr("not found") + ")";
 2485                 QToolTip::showText(editor->mapToGlobal(editor->mapFromFrame(pos)), text);
 2486             }
 2487         }
 2488         if (tk.subtype == Token::color) {
 2489             QString text;
 2490             if (ts.size() > 1) {
 2491                 ts.pop();
 2492                 tk = ts.top();
 2493             }
 2494             text = QString("\\noident{\\color%1 \\rule{1cm}{1cm} }").arg(tk.getText());
 2495             m_point = editor->mapToGlobal(editor->mapFromFrame(pos));
 2496             emit showPreview(text);
 2497         }
 2498         if (tk.type == Token::bibItem) {
 2499             handled = true;
 2500             QString tooltip(tr("Citation correct (reading ...)"));
 2501             QString bibID;
 2502 
 2503             bibID = value;
 2504 
 2505             if (!document->bibIdValid(bibID)) {
 2506                 tooltip = "<b>" + tr("Citation missing") + ":</b> " + bibID;
 2507 
 2508                 if (!bibID.isEmpty() && bibID[bibID.length() - 1].isSpace()) {
 2509                     tooltip.append("<br><br><i>" + tr("Warning:") + "</i> " + tr("BibTeX ID ends with space. Trailing spaces are not ignored by BibTeX."));
 2510                 }
 2511             } else {
 2512                 if (document->isBibItem(bibID)) {
 2513                     // by bibitem defined citation
 2514                     tooltip.clear();
 2515                     QMultiHash<QDocumentLineHandle *, int> result = document->getBibItems(bibID);
 2516                     if (result.keys().isEmpty())
 2517                         return;
 2518                     QDocumentLineHandle *mLine = result.keys().first();
 2519                     if (!mLine)
 2520                         return;
 2521                     int l = mLine->document()->indexOf(mLine);
 2522                     LatexDocument *doc = qobject_cast<LatexDocument *> (editor->document());
 2523                     if (mLine->document() != editor->document()) {
 2524                         doc = document->parent->findDocument(mLine->document());
 2525                         if (doc) tooltip = tr("<p style='white-space:pre'><b>Filename: %1</b>\n").arg(doc->getFileName());
 2526                     }
 2527                     if (doc)
 2528                         tooltip += doc->exportAsHtml(doc->cursor(l, 0, l + 4), true, true, 60);
 2529                 } else {
 2530                     // read entry in bibtex file
 2531                     if (!bibReader) {
 2532                         bibReader = new bibtexReader(this);
 2533                         connect(bibReader, SIGNAL(sectionFound(QString)), this, SLOT(bibtexSectionFound(QString)));
 2534                         connect(this, SIGNAL(searchBibtexSection(QString, QString)), bibReader, SLOT(searchSection(QString, QString)));
 2535                         bibReader->start(); //The thread is started, but it is doing absolutely nothing! Signals/slots called in the thread object are execute in the emitting thread, not the thread itself.  TODO: fix
 2536                     }
 2537                     QString file = document->findFileFromBibId(bibID);
 2538                     lastPos = pos;
 2539                     if (!file.isEmpty())
 2540                         emit searchBibtexSection(file, bibID);
 2541                     return;
 2542                 }
 2543             }
 2544             QToolTip::showText(editor->mapToGlobal(editor->mapFromFrame(pos)), tooltip);
 2545         }
 2546         if (tk.type == Token::imagefile && config->imageToolTip) {
 2547             handled = true;
 2548             QStringList imageExtensions = QStringList() << "" << "png" << "pdf" << "jpg" << "jpeg";
 2549             QString fname;
 2550             QFileInfo fi;
 2551             QStringList imagePaths = ConfigManagerInterface::getInstance()->getOption("Files/Image Paths").toString().split(getPathListSeparator());
 2552             foreach (const QString &ext, imageExtensions) {
 2553                 fname = getDocument()->getAbsoluteFilePath(value, ext, imagePaths);
 2554                 fi.setFile(fname);
 2555                 if (fi.exists()) break;
 2556             }
 2557             if (!fi.exists()) return;
 2558             m_point = editor->mapToGlobal(editor->mapFromFrame(pos));
 2559             emit showImgPreview(fname);
 2560         }
 2561 
 2562     }//if tk
 2563     if (handled)
 2564         return;
 2565 
 2566     QToolTip::hideText();
 2567 
 2568     /*
 2569         switch (LatexParser::getInstance().findContext(line, cursor.columnNumber(), command, value)){
 2570         case LatexParser::Unknown: // when does this happen ????
 2571             if (config->toolTipPreview) {
 2572                 QString command = extractMath(cursor);
 2573                 if (!command.isEmpty()) {
 2574                     m_point = editor->mapToGlobal(editor->mapFromFrame(pos));
 2575                     emit showPreview(command);
 2576                 } else {
 2577                     QToolTip::hideText();
 2578                 }
 2579             }
 2580             break;
 2581 
 2582 
 2583          }*/
 2584     //QToolTip::showText(editor->mapToGlobal(pos), line);
 2585 }
 2586 
 2587 bool LatexEditorView::closeElement()
 2588 {
 2589     if (completer->close()) return true;
 2590     if (gotoLinePanel->isVisible()) {
 2591         gotoLinePanel->hide();
 2592         editor->setFocus();
 2593         return true;
 2594     }
 2595     if (searchReplacePanel->isVisible()) {
 2596         searchReplacePanel->closeElement(config->closeSearchAndReplace);
 2597         return true;
 2598     }
 2599     return false;
 2600 }
 2601 
 2602 void LatexEditorView::insertHardLineBreaks(int newLength, bool smartScopeSelection, bool joinLines)
 2603 {
 2604     QRegExp breakChars("[ \t\n\r]");
 2605     QDocumentCursor cur = editor->cursor();
 2606     QDocument *doc = editor->document();
 2607     int startLine = 0;
 2608     int endLine = doc->lines() - 1;
 2609 
 2610     if (cur.isValid()) {
 2611         if (cur.hasSelection()) {
 2612             startLine = cur.selectionStart().lineNumber();
 2613             endLine = cur.selectionEnd().lineNumber();
 2614             if (cur.selectionEnd().columnNumber() == 0 && startLine < endLine) endLine--;
 2615         } else if (smartScopeSelection) {
 2616             QDocumentCursor currentCur = cur;
 2617             QDocumentCursor lineCursor = currentCur;
 2618             do {
 2619                 QString lineString  = lineCursor.line().text().trimmed();
 2620                 if ((lineString == QLatin1String("")) ||
 2621                         (lineString.contains("\\begin")) ||
 2622                         (lineString.contains("\\end")) ||
 2623                         (lineString.contains("$$")) ||
 2624                         (lineString.contains("\\[")) ||
 2625                         (lineString.contains("\\]"))) {
 2626                     //qDebug() << lineString;
 2627                     break;
 2628                 }
 2629             } while (lineCursor.movePosition(1, QDocumentCursor::Up, QDocumentCursor::MoveAnchor));
 2630             startLine = lineCursor.lineNumber();
 2631             if (lineCursor.atStart()) startLine--;
 2632 
 2633             lineCursor = currentCur;
 2634             do {
 2635                 QString lineString  = lineCursor.line().text().trimmed();
 2636                 if ((lineString == QLatin1String("")) ||
 2637                         (lineString.contains("\\begin")) ||
 2638                         (lineString.contains("\\end")) ||
 2639                         (lineString.contains("$$")) ||
 2640                         (lineString.contains("\\[")) ||
 2641                         (lineString.contains("\\]"))) {
 2642                     //qDebug() << lineString;
 2643                     break;
 2644                 }
 2645             } while (lineCursor.movePosition(1, QDocumentCursor::Down, QDocumentCursor::MoveAnchor));
 2646             endLine = lineCursor.lineNumber();
 2647             if (lineCursor.atEnd()) endLine++   ;
 2648 
 2649             if ((endLine - startLine) < 2) { // lines near, therefore no need to line break
 2650                 return ;
 2651             }
 2652 
 2653             startLine++;
 2654             endLine--;
 2655         }
 2656     }
 2657     if (joinLines) { // start of smart formatting, similar to what emacs (AucTeX) can do, but much simple
 2658         QStringList lines;
 2659         for (int i = startLine; i <= endLine; i++)
 2660             lines << doc->line(i).text();
 2661         lines = joinLinesExceptCommentsAndEmptyLines(lines);
 2662         lines = splitLines(lines, newLength, breakChars);
 2663 
 2664         QDocumentCursor vCur = doc->cursor(startLine, 0, endLine, doc->line(endLine).length());
 2665         editor->insertText(vCur, lines.join("\n"));
 2666         editor->setCursor(cur);
 2667         return;
 2668     }
 2669 
 2670     bool areThereLinesToBreak = false;
 2671     for (int i = startLine; i <= endLine; i++)
 2672         if (doc->line(i).length() > newLength) {
 2673             areThereLinesToBreak = true;
 2674             break;
 2675         }
 2676     if (!areThereLinesToBreak) return;
 2677     //remove all lines and reinsert them wrapped
 2678     if (endLine + 1 < doc->lines())
 2679         cur = doc->cursor(startLine, 0, endLine + 1, 0); //+1,0);
 2680     else
 2681         cur = doc->cursor(startLine, 0, endLine, doc->line(endLine).length()); //+1,0);
 2682     QStringList lines;
 2683     for (int i = startLine; i <= endLine; i++)
 2684         lines << doc->line(i).text();
 2685     QString insertBlock;
 2686     for (int i = 0; i < lines.count(); i++) {
 2687         QString line = lines[i];
 2688         int commentStart = LatexParser::commentStart(line);
 2689         if (commentStart == -1) commentStart = line.length();
 2690         while (line.length() > newLength) {
 2691             int breakAt = line.lastIndexOf(breakChars, newLength);
 2692             if (breakAt < 0) breakAt = line.indexOf(breakChars, newLength);
 2693             if (breakAt < 0) break;
 2694             if (breakAt >= commentStart && breakAt + 1 > newLength) {
 2695                 int newBreakAt = line.indexOf(breakChars, breakAt - 1);
 2696                 if (newBreakAt > -1) breakAt = newBreakAt;
 2697             }
 2698             insertBlock += line.left(breakAt) + "\n";
 2699             if (breakAt < commentStart) {
 2700                 line = line.mid(breakAt + 1);
 2701                 commentStart -= breakAt + 1;
 2702             } else {
 2703                 line = "%" + line.mid(breakAt + 1);
 2704                 commentStart = 0;
 2705             }
 2706         }
 2707         insertBlock += line + "\n";
 2708     }
 2709     editor->insertText(cur, insertBlock);
 2710 
 2711     editor->setCursor(cur);
 2712 }
 2713 
 2714 QString LatexEditorViewConfig::translateEditOperation(int key)
 2715 {
 2716     return QEditor::translateEditOperation(static_cast<QEditor::EditOperation>(key));
 2717 }
 2718 
 2719 QList<int> LatexEditorViewConfig::possibleEditOperations()
 2720 {
 2721     int  temp[] = {
 2722         QEditor::NoOperation,
 2723         QEditor::Invalid,
 2724 
 2725         QEditor::CursorUp,
 2726         QEditor::CursorDown,
 2727         QEditor::CursorLeft,
 2728         QEditor::CursorRight,
 2729         QEditor::CursorWordLeft,
 2730         QEditor::CursorWordRight,
 2731         QEditor::CursorStartOfLine,
 2732         QEditor::CursorEndOfLine,
 2733         QEditor::CursorStartOfDocument,
 2734         QEditor::CursorEndOfDocument,
 2735 
 2736         QEditor::CursorPageUp,
 2737         QEditor::CursorPageDown,
 2738 
 2739         QEditor::SelectCursorUp,
 2740         QEditor::SelectCursorDown,
 2741         QEditor::SelectCursorLeft,
 2742         QEditor::SelectCursorRight,
 2743         QEditor::SelectCursorWordLeft,
 2744         QEditor::SelectCursorWordRight,
 2745         QEditor::SelectCursorStartOfLine,
 2746         QEditor::SelectCursorEndOfLine,
 2747         QEditor::SelectCursorStartOfDocument,
 2748         QEditor::SelectCursorEndOfDocument,
 2749 
 2750         QEditor::SelectPageUp,
 2751         QEditor::SelectPageDown,
 2752 
 2753         QEditor::EnumForCursorEnd,
 2754 
 2755         QEditor::DeleteLeft,
 2756         QEditor::DeleteRight,
 2757         QEditor::DeleteLeftWord,
 2758         QEditor::DeleteRightWord,
 2759         QEditor::NewLine,
 2760 
 2761         QEditor::ChangeOverwrite,
 2762         QEditor::Undo,
 2763         QEditor::Redo,
 2764         QEditor::Copy,
 2765         QEditor::Paste,
 2766         QEditor::Cut,
 2767         QEditor::Print,
 2768         QEditor::SelectAll,
 2769         QEditor::Find,
 2770         QEditor::FindNext,
 2771         QEditor::FindPrevious,
 2772         QEditor::Replace,
 2773 
 2774         QEditor::CreateMirrorUp,
 2775         QEditor::CreateMirrorDown,
 2776         QEditor::NextPlaceHolder,
 2777         QEditor::PreviousPlaceHolder,
 2778         QEditor::NextPlaceHolderOrWord,
 2779         QEditor::PreviousPlaceHolderOrWord,
 2780         QEditor::NextPlaceHolderOrChar,
 2781         QEditor::PreviousPlaceHolderOrChar,
 2782         QEditor::TabOrIndentSelection,
 2783         QEditor::IndentSelection,
 2784         QEditor::UnindentSelection
 2785     };
 2786     QList<int> res;
 2787     int operationCount = static_cast<int>(sizeof(temp) / sizeof(int)); //sizeof(array) is possible with c-arrays
 2788     for (int i = 0; i < operationCount; i++)
 2789         res << temp[i];
 2790     return res;
 2791 }
 2792 
 2793 /*
 2794  * If the cursor is at the border of a parenthesis, this returns a QDocumentCursor with a selection of the parenthized text.
 2795  * Otherwise, a default QDocumentCursor is returned.
 2796  */
 2797 QDocumentCursor LatexEditorView::parenthizedTextSelection(const QDocumentCursor &cursor, bool includeParentheses)
 2798 {
 2799     QDocumentCursor from, to;
 2800     cursor.getMatchingPair(from, to, includeParentheses);
 2801     if (!from.hasSelection() || !to.hasSelection()) return QDocumentCursor();
 2802     QDocumentCursor::sort(from, to);
 2803     return QDocumentCursor(from.selectionStart(), to.selectionEnd());
 2804 }
 2805 
 2806 /*
 2807  * finds the beginning of the specified allowedFormats
 2808  * additional formats can be allowed at the line end (e.g. comments)
 2809  */
 2810 QDocumentCursor LatexEditorView::findFormatsBegin(const QDocumentCursor &cursor, QSet<int> allowedFormats, QSet<int> allowedLineEndFormats)
 2811 {
 2812     QDocumentCursor c(cursor);
 2813     QVector<int> lineFormats = c.line().getFormats();
 2814     int col = c.columnNumber();
 2815     if ((col > 0 && allowedFormats.contains(lineFormats[col - 1])) // prev char or next char is allowed
 2816             || (col < lineFormats.size() && allowedFormats.contains(lineFormats[col]))
 2817        ) {
 2818         while (true) {
 2819             while (col > 0 && allowedFormats.contains(lineFormats[col - 1])) col--;
 2820             if (col > 0) break;
 2821             // continue on previous line
 2822             c.movePosition(1, QDocumentCursor::PreviousLine);
 2823             c.movePosition(1, QDocumentCursor::EndOfLine);
 2824             lineFormats = c.line().getFormats();
 2825             col = c.columnNumber();
 2826             QString text = c.line().text();
 2827             while (col > 0 && (allowedLineEndFormats.contains(lineFormats[col - 1]) || text[col - 1].isSpace())) col--;
 2828 
 2829         }
 2830         c.setColumnNumber(col);
 2831         return c;
 2832     }
 2833     return QDocumentCursor();
 2834 }
 2835 
 2836 void LatexEditorView::triggeredThesaurus()
 2837 {
 2838     QAction *act = qobject_cast<QAction *>(sender());
 2839     QPoint pt = act->data().toPoint();
 2840     emit thesaurus(pt.x(), pt.y());
 2841 }
 2842 
 2843 void LatexEditorView::changeSpellingDict(const QString &name)
 2844 {
 2845     QString similarName;
 2846     if (spellerManager->hasSpeller(name)) {
 2847         setSpeller(name);
 2848     } else if (spellerManager->hasSimilarSpeller(name, similarName)) {
 2849         setSpeller(similarName);
 2850     }
 2851 }
 2852 
 2853 void LatexEditorView::copyImageFromAction()
 2854 {
 2855     QAction *act = qobject_cast<QAction *>(sender());
 2856     if (!act) return;
 2857 
 2858     QPixmap pm = act->data().value<QPixmap>();
 2859     if (!pm.isNull()) {
 2860         QApplication::clipboard()->setImage(pm.toImage());
 2861     }
 2862 }
 2863 
 2864 void LatexEditorView::saveImageFromAction()
 2865 {
 2866     static QString lastSaveDir;
 2867 
 2868     QAction *act = qobject_cast<QAction *>(sender());
 2869     if (!act) return;
 2870 
 2871     QPixmap pm = act->data().value<QPixmap>();
 2872 
 2873     QString fname = FileDialog::getSaveFileName(this , tr("Save Preview Image"), lastSaveDir, tr("Images") + " (*.png *.jpg *.jpeg)");
 2874     if (fname.isEmpty()) return;
 2875 
 2876     QFileInfo fi(fname);
 2877     lastSaveDir = fi.absolutePath();
 2878     pm.save(fname);
 2879 }
 2880 
 2881 
 2882 void LatexEditorViewConfig::settingsChanged()
 2883 {
 2884     if (!hackAutoChoose) {
 2885         lastFontFamily = "";
 2886         return;
 2887     }
 2888     if (lastFontFamily == fontFamily && lastFontSize == fontSize) return;
 2889 
 2890     QFont f(fontFamily, fontSize);
 2891 #if QT_VERSION >= 0x040700
 2892     f.setStyleHint(QFont::Courier, QFont::ForceIntegerMetrics);
 2893 #else
 2894     const QFont::StyleStrategy ForceIntegerMetrics = (QFont::StyleStrategy)0x0400;
 2895     f.setStyleHint(QFont::Courier, (hasAtLeastQt(4, 7) ? ForceIntegerMetrics : QFont::PreferQuality));
 2896 
 2897 #endif
 2898     f.setKerning(false);
 2899 
 2900     QList<QFontMetrics> fms;
 2901     for (int b = 0; b < 2; b++) for (int i = 0; i < 2; i++) {
 2902             QFont ft(f);
 2903             ft.setBold(b);
 2904             ft.setItalic(i);
 2905             fms << QFontMetrics(ft);
 2906         }
 2907 
 2908     bool lettersHaveDifferentWidth = false, sameLettersHaveDifferentWidth = false;
 2909     int letterWidth = fms.first().width('a');
 2910 
 2911     const QString lettersToCheck("abcdefghijklmnoqrstuvwxyzABCDEFHIJKLMNOQRSTUVWXYZ_+ 123/()=.,;#");
 2912     QVector<QMap<QChar, int> > widths;
 2913     widths.resize(fms.size());
 2914 
 2915     foreach (const QChar &c, lettersToCheck) {
 2916         for (int fmi = 0; fmi < fms.size(); fmi++) {
 2917             const QFontMetrics &fm = fms[fmi];
 2918             int currentWidth = fm.width(c);
 2919             widths[fmi].insert(c, currentWidth);
 2920             if (currentWidth != letterWidth) lettersHaveDifferentWidth = true;
 2921             QString testString;
 2922             for (int i = 1; i < 10; i++) {
 2923                 testString += c;
 2924                 int stringWidth = fm.width(testString);
 2925                 if (stringWidth % i != 0) sameLettersHaveDifferentWidth = true;
 2926                 if (currentWidth != stringWidth / i) sameLettersHaveDifferentWidth = true;
 2927             }
 2928             if (lettersHaveDifferentWidth && sameLettersHaveDifferentWidth) break;
 2929         }
 2930         if (lettersHaveDifferentWidth && sameLettersHaveDifferentWidth) break;
 2931     }
 2932     const QString ligatures[2] = {"aftt", "afit"};
 2933     for (int l = 0; l < 2 && !sameLettersHaveDifferentWidth; l++) {
 2934         for (int fmi = 0; fmi < fms.size(); fmi++) {
 2935             int expectedWidth = 0;
 2936             for (int i = 0; i < ligatures[l].size() && !sameLettersHaveDifferentWidth; i++) {
 2937                 expectedWidth += widths[fmi].value(ligatures[l][i]);
 2938                 if (expectedWidth != fms[fmi].width(ligatures[l].left(i + 1))) sameLettersHaveDifferentWidth = true;
 2939             }
 2940         }
 2941     }
 2942 
 2943     if (!QFontInfo(f).fixedPitch()) hackDisableFixedPitch = false; //won't be enabled anyways
 2944     else hackDisableFixedPitch = lettersHaveDifferentWidth || sameLettersHaveDifferentWidth;
 2945     hackDisableWidthCache = sameLettersHaveDifferentWidth;
 2946 
 2947 #if defined( Q_OS_LINUX ) || defined( Q_OS_WIN )
 2948     hackDisableLineCache = true;
 2949 #else
 2950     hackDisableLineCache = false;
 2951     //hackDisableLineCache = isRetinaMac();
 2952 #endif
 2953     hackRenderingMode = 0; //always use qce, best tested
 2954 
 2955     lastFontFamily = fontFamily;
 2956     lastFontSize = fontSize;
 2957 }
 2958 
 2959 
 2960 QString BracketInvertAffector::affect(const QKeyEvent *, const QString &base, int, int) const
 2961 {
 2962     static const QString &brackets = "<>()[]{}";
 2963     QString after;
 2964     for (int i = 0; i < base.length(); i++)
 2965         if (brackets.indexOf(base[i]) >= 0)
 2966             after += brackets[brackets.indexOf(base[i]) + 1 - 2 * (brackets.indexOf(base[i]) & 1) ];
 2967         else if (base[i] == '\\') {
 2968             if (base.mid(i, 7) == "\\begin{") {
 2969                 after += "\\end{" + base.mid(i + 7);
 2970                 return after;
 2971             } else if (base.mid(i, 5) == "\\end{") {
 2972                 after += "\\begin{" + base.mid(i + 5);
 2973                 return after;
 2974             } else if (base.mid(i, 5) == "\\left") {
 2975                 after += "\\right";
 2976                 i += 4;
 2977             } else if (base.mid(i, 6) == "\\right") {
 2978                 after += "\\left";
 2979                 i += 5;
 2980             } else after += '\\';
 2981         } else after += base[i];
 2982     return after;
 2983 }
 2984 
 2985 BracketInvertAffector *inverterSingleton = nullptr;
 2986 
 2987 BracketInvertAffector *BracketInvertAffector::instance()
 2988 {
 2989     if (!inverterSingleton) inverterSingleton = new BracketInvertAffector();
 2990     return inverterSingleton;
 2991 }
 2992 
 2993 void LatexEditorView::bibtexSectionFound(QString content)
 2994 {
 2995     QToolTip::showText(editor->mapToGlobal(editor->mapFromFrame(lastPos)), content);
 2996 }
 2997 
 2998 void LatexEditorView::lineMarkContextMenuRequested(int lineNumber, QPoint globalPos)
 2999 {
 3000     if (!document) return;
 3001 
 3002     QDocumentLine line(document->line(lineNumber));
 3003     QMenu menu(this);
 3004 
 3005     for (int i = -1; i < 10; i++) {
 3006         int rmid = bookMarkId(i);
 3007         if (line.hasMark(rmid)) {
 3008             QAction *act =  new QAction(tr("Remove Bookmark"), &menu);
 3009             act->setData(-2);
 3010             menu.addAction(act);
 3011             menu.addSeparator();
 3012             break;
 3013         }
 3014     }
 3015 
 3016     QAction *act = new QAction(getRealIconCached("lbook"), tr("Unnamed Bookmark"), &menu);
 3017     act->setData(-1);
 3018     menu.addAction(act);
 3019 
 3020     for (int i = 0; i < 10; i++) {
 3021         QAction *act = new QAction(getRealIconCached(QString("lbook%1").arg(i)), tr("Bookmark") + QString(" %1").arg(i), &menu);
 3022         act->setData(i);
 3023         menu.addAction(act);
 3024     }
 3025 
 3026     act = menu.exec(globalPos);
 3027     if (act) {
 3028         int bookmarkNumber = act->data().toInt();
 3029         if (bookmarkNumber == -2) {
 3030             for (int i = -1; i < 10; i++) {
 3031                 int rmid = bookMarkId(i);
 3032                 if (line.hasMark(rmid)) {
 3033                     removeBookmark(line.handle(), i);
 3034                     return;
 3035                 }
 3036             }
 3037         } else {
 3038             toggleBookmark(bookmarkNumber, line);
 3039         }
 3040     }
 3041 }
 3042 
 3043 void LatexEditorView::foldContextMenuRequested(int lineNumber, QPoint globalPos)
 3044 {
 3045     Q_UNUSED(lineNumber)
 3046 
 3047     QMenu menu;
 3048     QAction *act = new QAction(tr("Collapse All"), &menu);
 3049     act->setData(-5);
 3050     menu.addAction(act);
 3051     for (int i = 1; i <= 4; i++) {
 3052         act = new QAction(QString(tr("Collapse Level %1")).arg(i), &menu);
 3053         act->setData(-i);
 3054         menu.addAction(act);
 3055     }
 3056     menu.addSeparator();
 3057     act = new QAction(tr("Expand All"), &menu);
 3058     act->setData(5);
 3059     menu.addAction(act);
 3060     for (int i = 1; i <= 4; i++) {
 3061         act = new QAction(QString(tr("Expand Level %1")).arg(i), &menu);
 3062         act->setData(i);
 3063         menu.addAction(act);
 3064     }
 3065 
 3066     act = menu.exec(globalPos);
 3067     if (act) {
 3068         int level = act->data().toInt();
 3069         if (qAbs(level) < 5) {
 3070             foldLevel(level > 0, qAbs(level));
 3071         } else {
 3072             foldEverything(level > 0);
 3073         }
 3074     }
 3075 }
 3076 
 3077 LinkOverlay::LinkOverlay(const LinkOverlay &o)
 3078 {
 3079     type = o.type;
 3080     if (o.isValid()) {
 3081         docLine = o.docLine;
 3082         formatRange = o.formatRange;
 3083     }
 3084 }
 3085 
 3086 LinkOverlay::LinkOverlay(const Token &token, LinkOverlay::LinkOverlayType ltype) :
 3087     type(ltype)
 3088 {
 3089     if (type == Invalid) return;
 3090 
 3091     int from = token.start;
 3092     int to = from + token.length - 1;
 3093     if (from < 0 || to < 0 || to <= from)
 3094         return;
 3095 
 3096     REQUIRE(QDocument::defaultFormatScheme());
 3097     formatRange = QFormatRange(from, to - from + 1, QDocument::defaultFormatScheme()->id("link"));
 3098     docLine = QDocumentLine(token.dlh);
 3099 }
 3100 
 3101 QString LinkOverlay::text() const
 3102 {
 3103     if (!isValid()) return QString();
 3104     return docLine.text().mid(formatRange.offset, formatRange.length);
 3105 }
 3106 
 3107 QString LatexEditorView::getSearchText()
 3108 {
 3109     return searchReplacePanel->getSearchText();
 3110 }
 3111 
 3112 QString LatexEditorView::getReplaceText()
 3113 {
 3114     return searchReplacePanel->getReplaceText();
 3115 }
 3116 
 3117 bool LatexEditorView::getSearchIsWords()
 3118 {
 3119     return searchReplacePanel->getSearchIsWords();
 3120 }
 3121 
 3122 bool LatexEditorView::getSearchIsCase()
 3123 {
 3124     return searchReplacePanel->getSearchIsCase();
 3125 }
 3126 
 3127 bool LatexEditorView::getSearchIsRegExp()
 3128 {
 3129     return searchReplacePanel->getSearchIsRegExp();
 3130 }
 3131 
 3132 bool LatexEditorView::isInMathHighlighting(const QDocumentCursor &cursor )
 3133 {
 3134     const QDocumentLine &line = cursor.line();
 3135     if (!line.handle()) return false;
 3136     const QVector<int> &formats = line.handle()->getFormats();
 3137 
 3138     int col = cursor.columnNumber();
 3139 
 3140     bool atDelimiter = false;
 3141 
 3142     if (col >= 0 && col < formats.size()) {
 3143         int f = formats[col];
 3144         if (f == numbersFormat || f == math_KeywordFormat) return true;
 3145         if (f == math_DelimiterFormat) atDelimiter = true;
 3146     }
 3147     if (col > 0 && col <= formats.size()) {
 3148         int f = formats[col - 1];
 3149         if (f == numbersFormat || f == math_KeywordFormat) return true;
 3150         if (f == math_DelimiterFormat) atDelimiter = true;
 3151     }
 3152 
 3153     if (!atDelimiter) return false;
 3154 
 3155     QDocumentCursor from, to;
 3156     cursor.getMatchingPair(from, to, false);
 3157     if (!from.isValid() || !to.isValid())
 3158         return col > 0 && col <= formats.size() && formats.at(col - 1) == math_DelimiterFormat;
 3159     if (cursor <= from.selectionStart()) return false;
 3160     if (cursor >= to.selectionEnd()) return false;
 3161     return true;
 3162 }
 3163 
 3164 void LatexEditorView::checkRTLLTRLanguageSwitching()
 3165 {
 3166 #if defined( Q_OS_WIN ) || defined( Q_OS_LINUX ) || ( defined( Q_OS_UNIX ) && !defined( Q_OS_MAC ) )
 3167 #if QT_VERSION >= 0x040800
 3168     QDocumentCursor cursor = editor->cursor();
 3169     QDocumentLine line = cursor.line();
 3170     InputLanguage language = IL_UNCERTAIN;
 3171     if (line.firstChar() >= 0) { //whitespace lines have no language information
 3172         if (config->switchLanguagesMath) {
 3173             if (isInMathHighlighting(cursor)) language = IL_LTR;
 3174             else language = IL_RTL;
 3175         }
 3176 
 3177         if (config->switchLanguagesDirection && language != IL_LTR) {
 3178             if (line.hasFlag(QDocumentLine::LayoutDirty))
 3179                 if (line.isRTLByLayout() || line.isRTLByText() ) {
 3180                     line.handle()->lockForWrite();
 3181                     line.handle()->layout(cursor.lineNumber());
 3182                     line.handle()->unlock();
 3183                 }
 3184             if (!line.isRTLByLayout())
 3185                 language = IL_LTR;
 3186             else {
 3187                 int c = cursor.columnNumber();
 3188                 int dir = line.rightCursorPosition(c) - c;
 3189                 if (dir < 0) language = IL_RTL;
 3190                 else if (dir > 0) language = IL_LTR;
 3191             }
 3192         }
 3193     }
 3194     setInputLanguage(language);
 3195 #endif
 3196 #endif
 3197 }