/***************************************************************************
 *   copyright       : (C) 2008 by Benito van der Zander                   *
 *   http://www.xm1math.net/texmaker/                                      *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

#include "latexeditorview.h"
#include "latexeditorview_config.h"

#include "latexcompleter.h"
#include "latexdocument.h"
#include "smallUsefulFunctions.h"
#include "spellerutility.h"
#include "tablemanipulation.h"

#include "qdocumentline.h"
#include "qdocumentline_p.h"
#include "qdocumentcommand.h"

#include "qlinemarksinfocenter.h"
#include "qformatfactory.h"
#include "qlanguagedefinition.h"
#include "qnfadefinition.h"
#include "qnfa.h"

#include "qcodeedit.h"
#include "qeditor.h"
#include "qeditorinputbinding.h"
#include "qlinemarkpanel.h"
#include "qlinenumberpanel.h"
#include "qfoldpanel.h"
#include "qgotolinepanel.h"
#include "qlinechangepanel.h"
#include "qstatuspanel.h"
#include "qsearchreplacepanel.h"
#include "latexpackages.h"

#include "latexcompleter_config.h"

#include "scriptengine.h"
#include "diffoperations.h"

#include "help.h"

QStringList LatexEditorView::checkedLanguages = QStringList() << "(La)TeX" << "Pweave" << "Sweave" << "TeX dtx file"; // languages for online checking (exact name from qnfa file)

//------------------------------Default Input Binding--------------------------------
class DefaultInputBinding: public QEditorInputBinding {
	//  Q_OBJECT not possible because inputbinding is no qobject
public:
	DefaultInputBinding():completerConfig(0),editorViewConfig(0), contextMenu(0),isDoubleClick(false) {}
	virtual QString id() const {
		return "TXS::DefaultInputBinding";
	}
	virtual QString name() const {
		return "TXS::DefaultInputBinding";
	}
	
	virtual bool keyPressEvent(QKeyEvent *event, QEditor *editor);
	virtual void postKeyPressEvent(QKeyEvent *event, QEditor *editor);
	virtual bool keyReleaseEvent(QKeyEvent *event, QEditor *editor);
	virtual bool mousePressEvent(QMouseEvent *event, QEditor *editor);
	virtual bool mouseReleaseEvent(QMouseEvent *event, QEditor *editor);
	virtual bool mouseDoubleClickEvent(QMouseEvent *event, QEditor *editor);
	virtual bool mouseMoveEvent(QMouseEvent *event, QEditor *editor);
	virtual bool contextMenuEvent(QContextMenuEvent *event, QEditor *editor);
private:
	bool runMacros(QKeyEvent *event, QEditor *editor);
	bool autoInsertLRM(QKeyEvent *event, QEditor *editor);
	void checkLinkOverlay(QPoint mousePos, Qt::KeyboardModifiers modifiers, QEditor *editor);
	friend class LatexEditorView;
	const LatexCompleterConfig* completerConfig;
	const LatexEditorViewConfig* editorViewConfig;
	QList<QAction *> baseActions;
	
	QMenu* contextMenu;
	QString lastSpellCheckedWord;
	
	QPoint lastMousePressLeft;
	bool isDoubleClick;  // event sequence of a double click: press, release, double click, release - this is true on the second release
};

static const QString LRMStr = QChar(LRM);

bool DefaultInputBinding::runMacros(QKeyEvent *event, QEditor *editor) {
	Q_ASSERT(completerConfig);
	QLanguageDefinition *language = editor->document() ? editor->document()->languageDefinition() : 0;
	QDocumentLine line = editor->cursor().selectionStart().line();
	int column = editor->cursor().selectionStart().columnNumber();
	QString prev = line.text().mid(0, column) + event->text(); //TODO: optimize
	foreach (const Macro &m, completerConfig->userMacros) {
		if (!m.isActiveForTrigger(Macro::ST_REGEX)) continue;
		if (!m.isActiveForLanguage(language)) continue;
		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
		QRegExp& r = const_cast<QRegExp&>(m.triggerRegex);//a const qregexp doesn't exist
		if (r.indexIn(prev)!=-1){
			QDocumentCursor c = editor->cursor();
			bool block = false;
			int realMatchLen = r.matchedLength();
			if (m.triggerLookBehind) realMatchLen -= r.cap(1).length();
			if (c.hasSelection() || realMatchLen > 1)
				block = true;
			if (block) editor->document()->beginMacro();
			if (c.hasSelection()) c.removeSelectedText();
			if (m.triggerRegex.matchedLength() > 1) {
				c.movePosition(realMatchLen-1, QDocumentCursor::PreviousCharacter, QDocumentCursor::KeepAnchor);
				c.removeSelectedText();
			}

			LatexEditorView* view = editor->property("latexEditor").value<LatexEditorView*>();
			REQUIRE_RET(view, true);
			view->insertMacro(m.tag, r, Macro::ST_REGEX);
			//editor->insertText(c, m.tag);
			if (block) editor->document()->endMacro();
			editor->emitCursorPositionChanged(); //prevent rogue parenthesis highlightations
			/*			if (editor->languageDefinition())
editor->languageDefinition()->clearMatches(editor->document());
*/
			return true;
		}
	}
	return false;
}

bool DefaultInputBinding::autoInsertLRM(QKeyEvent *event, QEditor *editor) {
	const QString & text = event->text();
	if (editorViewConfig->autoInsertLRM && text.length() == 1 && editor->cursor().isRTL()) {
		if (text.at(0) == '}') {
			bool autoOverride = editor->isAutoOverrideText("}");
			bool previousIsLRM = editor->cursor().previousChar().unicode() == LRM;
			bool block = previousIsLRM || autoOverride;
			if (block) editor->document()->beginMacro();
			if (previousIsLRM) editor->cursor().deletePreviousChar(); //todo mirrors
			if (autoOverride) {
				editor->write("}"); //separated, so autooverride works
				editor->write(LRMStr);
			} else editor->write("}"+LRMStr);
			if (block) editor->document()->endMacro();
			return true;
		}
	}
	return false;
}

void DefaultInputBinding::checkLinkOverlay(QPoint mousePos, Qt::KeyboardModifiers modifiers, QEditor *editor) {
	if (modifiers == Qt::ControlModifier) {
		LatexEditorView *edView=qobject_cast<LatexEditorView *>(editor->parentWidget());
		QDocumentCursor cursor = editor->cursorForPosition(mousePos);
		edView->checkForLinkOverlay(cursor);
	} else {
		// reached for example when Ctrl+Shift is pressed
		LatexEditorView *edView=qobject_cast<LatexEditorView *>(editor->parentWidget()); //a qobject is necessary to retrieve events
		edView->removeLinkOverlay();
	}
}

bool DefaultInputBinding::keyPressEvent(QKeyEvent *event, QEditor *editor) {
	if (LatexEditorView::completer && LatexEditorView::completer->acceptTriggerString(event->text())
			&& (editor->currentPlaceHolder() < 0 || editor->currentPlaceHolder() >= editor->placeHolderCount() || editor->getPlaceHolder(editor->currentPlaceHolder()).mirrors.isEmpty() ||  editor->getPlaceHolder(editor->currentPlaceHolder()).affector != BracketInvertAffector::instance())
			&& !editor->flag(QEditor::Overwrite))  {
		//update completer if necessary
		editor->emitNeedUpdatedCompleter();
		bool autoOverriden = editor->isAutoOverrideText(event->text());
		if (editorViewConfig->autoInsertLRM && event->text() == "\\" && editor->cursor().isRTL())
			editor->write(LRMStr + event->text());
		else
			editor->write(event->text());
		if (autoOverriden) LatexEditorView::completer->complete(editor,LatexCompleter::CF_OVERRIDEN_BACKSLASH);
		else LatexEditorView::completer->complete(editor,0);
		return true;
	}
	if (!event->text().isEmpty()) {
		if (!editor->flag(QEditor::Overwrite) && runMacros(event, editor))
			return true;
		if (autoInsertLRM(event, editor))
			return true;
		if (event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return) {
			// if cursor is at the end of a placeholder, remove that placeholder
			int phId = editor->currentPlaceHolder();
			if (phId >= 0) {
				PlaceHolder ph = editor->getPlaceHolder(phId);
				if (editor->cursor().lineNumber() == ph.cursor.lineNumber() &&
						editor->cursor().columnNumber() == ph.cursor.columnNumber())
				{
					editor->removePlaceHolder(phId);
					return true;
				}
			}
		}
	} else {
		if (event->key() == Qt::Key_Control) {
			editor->setMouseTracking(true);
			QPoint mousePos(editor->mapToFrame(editor->mapFromGlobal(QCursor::pos())));
			checkLinkOverlay(mousePos, event->modifiers(), editor);
		}
	}
	if (LatexEditorView::hideTooltipWhenLeavingLine!=-1 && editor->cursor().lineNumber()!=LatexEditorView::hideTooltipWhenLeavingLine) {
		LatexEditorView::hideTooltipWhenLeavingLine=-1;
		QToolTip::hideText();
	}
	return false;
}

void DefaultInputBinding::postKeyPressEvent(QKeyEvent *event, QEditor *editor) {
	if (event->text() == ",") {
		LatexEditorView* view = editor->property("latexEditor").value<LatexEditorView*>();
		Q_ASSERT(view);
        if(completerConfig && completerConfig->enabled)
            emit view->colonTyped();
	}
}


bool DefaultInputBinding::keyReleaseEvent(QKeyEvent *event, QEditor *editor) {
	if (event->key() == Qt::Key_Control) {
		editor->setMouseTracking(false);
		LatexEditorView *edView=qobject_cast<LatexEditorView *>(editor->parentWidget()); //a qobject is necessary to retrieve events
		edView->removeLinkOverlay();
	}
	return false;
}

bool DefaultInputBinding::mousePressEvent(QMouseEvent *event, QEditor *editor){
	LatexEditorView *edView = 0;

	switch (event->button()) {
	case Qt::XButton1:
		edView=qobject_cast<LatexEditorView *>(editor->parentWidget());
		emit edView->mouseBackPressed();
		return true;
	case Qt::XButton2:
		edView=qobject_cast<LatexEditorView *>(editor->parentWidget());
		emit edView->mouseForwardPressed();
		return true;
	case Qt::LeftButton:
		edView=qobject_cast<LatexEditorView *>(editor->parentWidget());
		emit edView->cursorChangeByMouse();
		lastMousePressLeft = event->pos();
		return false;
	default:
		return false;
	}

	return false;
}

bool DefaultInputBinding::mouseReleaseEvent(QMouseEvent *event, QEditor *editor) {
	if (isDoubleClick) {
		isDoubleClick = false;
		return false;
	}
	isDoubleClick = false;

	if (event->modifiers() == Qt::ControlModifier && event->button() == Qt::LeftButton) {
		// Ctrl+LeftClick
		int distanceSqr = (event->pos().x() - lastMousePressLeft.x())*(event->pos().x() - lastMousePressLeft.x()) + (event->pos().y() - lastMousePressLeft.y())*(event->pos().y() - lastMousePressLeft.y());
		if (distanceSqr > 4) // allow the user to accidentially move the mouse a bit
			return false;

		LatexEditorView *edView=qobject_cast<LatexEditorView *>(editor->parentWidget()); //a qobject is necessary to retrieve events
		if (!edView) return false;
		QDocumentCursor cursor = editor->cursorForPosition(editor->mapToContents(event->pos()));

		if (edView->hasLinkOverlay()) {
			LinkOverlay lo = edView->getLinkOverlay();
			switch (lo.type) {
			case LinkOverlay::RefOverlay:
				emit edView->gotoDefinition(cursor);
				return true;
			case LinkOverlay::FileOverlay:
				edView->openFile(lo.text());
				return true;
			case LinkOverlay::UrlOverlay:
				if (!QDesktopServices::openUrl(lo.text())) {
					txsWarning(LatexEditorView::tr("Could not open url:") + "\n" + lo.text());
				}
				return true;
			case LinkOverlay::UsepackageOverlay:
				edView->openPackageDocumentation(lo.text());
				return true;
			case LinkOverlay::BibFileOverlay:
				edView->openFile(lo.text(), "bib");
				return true;
			case LinkOverlay::CiteOverlay:
				emit edView->gotoDefinition(cursor);
				return true;
			case LinkOverlay::Invalid:
				break;
			}
		}

		if (!editor->languageDefinition()) return false;
		if (editor->languageDefinition()->language() != "(La)TeX")
			return false;
		emit edView->syncPDFRequested(cursor);
		return true;
	}
	return false;
}

bool DefaultInputBinding::mouseDoubleClickEvent(QMouseEvent *event, QEditor *editor) {
	Q_UNUSED(event);
	Q_UNUSED(editor);
	isDoubleClick = true;
	return false;
}

bool DefaultInputBinding::contextMenuEvent(QContextMenuEvent *event, QEditor *editor) {
	if (!contextMenu) contextMenu=new QMenu(0);
	contextMenu->clear();
	contextMenu->setProperty("isSpellingPopulated", QVariant());  // delete information on spelling
	QDocumentCursor cursor;
	if (event->reason()==QContextMenuEvent::Mouse) cursor=editor->cursorForPosition(editor->mapToContents(event->pos()));
	else cursor=editor->cursor();
	LatexEditorView *edView=qobject_cast<LatexEditorView *>(editor->parentWidget()); //a qobject is necessary to retrieve events
	REQUIRE_RET(edView, false);

	// check for context menu on preview picture
	QRect pictRect = cursor.line().getCookie(QDocumentLine::PICTURE_COOKIE_DRAWING_POS).toRect();
	if (pictRect.isValid()) {
		QPoint posInDocCoordinates(event->pos().x() + edView->editor->horizontalOffset(), event->pos().y() + edView->editor->verticalOffset());
		if (pictRect.contains(posInDocCoordinates)) {
			// get removePreviewAction
			// 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
			// Alternatives: 1) include configmanager and use ConfigManager->getInstance()->getManagedAction() - Disadvantage: additional dependency
			//               2) explicitly pass it to the editorView (like the base actions, but separate) - Disadvantage: code overhead
			//               3) Improve the concept of base actions:
			//                    LatexEditorView::addContextAction(QAction); called when creating the editorView
			//                    LatexEditorView::getContextAction(QString); used here to populate the menu
			bool removePreviewActionFound = false;
			foreach(QAction *act, baseActions) {
				if (act->objectName().endsWith("removePreviewLatex")) {
					act->setData(posInDocCoordinates);
					contextMenu->addAction(act);
					removePreviewActionFound = true;
					break;
				}
			}
			Q_ASSERT(removePreviewActionFound);


			QVariant vPixmap = cursor.line().getCookie(QDocumentLine::PICTURE_COOKIE);
			if (vPixmap.isValid()) {
				(contextMenu->addAction("Copy Image", edView, SLOT(copyImageFromAction())))->setData(vPixmap);
				(contextMenu->addAction("Save Image As...", edView, SLOT(saveImageFromAction())))->setData(vPixmap);
			}
			contextMenu->exec(event->globalPos());
			return true;
		}
	}

	// normal context menu
	bool validPosition = cursor.isValid() && cursor.line().isValid();
	LatexParser::ContextType context = LatexParser::Unknown;
	QString ctxCommand, ctxValue;
	if (validPosition) {
		QFormatRange fr;
		//spell checking
		
		if (edView->speller){
			int pos;
			if (cursor.hasSelection()) pos = (cursor.columnNumber()+cursor.anchorColumnNumber()) / 2;
			else pos = cursor.columnNumber();

			foreach (const int f, edView->grammarFormats){
				fr = cursor.line().getOverlayAt(pos, f);
				if (fr.length>0 && fr.format==f) {
					QVariant var=cursor.line().getCookie(QDocumentLine::GRAMMAR_ERROR_COOKIE);
					if (var.isValid()){
						QDocumentCursor wordSelection(editor->document(),cursor.lineNumber(),fr.offset);
						wordSelection.movePosition(fr.length,QDocumentCursor::NextCharacter,QDocumentCursor::KeepAnchor);
						editor->setCursor(wordSelection);

						const QList<GrammarError>& errors = var.value<QList<GrammarError> >();
						for (int i=0;i<errors.size();i++)
							if (errors[i].offset <= cursor.columnNumber() && errors[i].offset+errors[i].length >= cursor.columnNumber()) {
								edView->addListToMenu(errors[i].corrections, contextMenu, true, SLOT(spellCheckingReplace()));
								break;
							}
					}
				}
			}
			
			fr = cursor.line().getOverlayAt(pos, SpellerUtility::spellcheckErrorFormat);
			if (fr.length>0 && fr.format==SpellerUtility::spellcheckErrorFormat) {
				QString word=cursor.line().text().mid(fr.offset,fr.length);
				if (!(editor->cursor().hasSelection() && editor->cursor().selectedText().length()>0) || editor->cursor().selectedText()==word
						|| editor->cursor().selectedText()==lastSpellCheckedWord) {
					lastSpellCheckedWord=word;
					word=latexToPlainWord(word);
					QDocumentCursor wordSelection(editor->document(),cursor.lineNumber(),fr.offset);
					wordSelection.movePosition(fr.length,QDocumentCursor::NextCharacter,QDocumentCursor::KeepAnchor);
					editor->setCursor(wordSelection);
					
					if ((editorViewConfig->contextMenuSpellcheckingEntryLocation == 0) ^ (event->modifiers() & editorViewConfig->contextMenuKeyboardModifiers)) {
						edView->addSpellingActions(contextMenu, lastSpellCheckedWord, false);
						contextMenu->addSeparator();
					} else {
						QMenu *spellingMenu = contextMenu->addMenu(LatexEditorView::tr("Spelling"));
						spellingMenu->setProperty("word", lastSpellCheckedWord);
						edView->connect(spellingMenu, SIGNAL(aboutToShow()), edView, SLOT(populateSpellingMenu()));
					}
				}
			}
		}
		//citation checking
		int f=edView->citationMissingFormat;
		if (cursor.hasSelection()) fr= cursor.line().getOverlayAt((cursor.columnNumber()+cursor.anchorColumnNumber()) / 2,f);
		else fr = cursor.line().getOverlayAt(cursor.columnNumber(),f);
		if (fr.length>0 && fr.format==f) {
			QString word=cursor.line().text().mid(fr.offset,fr.length);
			editor->setCursor(editor->document()->cursor(cursor.lineNumber(),fr.offset,cursor.lineNumber(),fr.offset+fr.length));
			QAction* act=new QAction(LatexEditorView::tr("New BibTeX Entry %1").arg(word),contextMenu);
			edView->connect(act,SIGNAL(triggered()),edView,SLOT(requestCitation()));
			contextMenu->addAction(act);
			contextMenu->addSeparator();
		}
		//check input/include
		//find context of cursor
		QString line=cursor.line().text();
		context = LatexParser::getInstance().findContext(line, cursor.columnNumber(), ctxCommand, ctxValue);
		//static const QStringList inputTokens = QStringList() << "\\input" << "\\include" << "\\includeonly";
		LatexParser ltxCommands=LatexParser::getInstance();
		if( (context==LatexParser::Command || context==LatexParser::Option) && ltxCommands.possibleCommands["%include"].contains(ctxCommand)){
			QAction* act=new QAction(LatexEditorView::tr("Open %1").arg(ctxValue),contextMenu);
			act->setData(ctxValue);
			edView->connect(act,SIGNAL(triggered()),edView,SLOT(openExternalFile()));
			contextMenu->addAction(act);
		}
		// bibliography command
		if ( (context==LatexParser::Command || context==LatexParser::Option) && ltxCommands.possibleCommands["%bibliography"].contains(ctxCommand)) {
			QAction *act = new QAction(LatexEditorView::tr("Open Bibliography"),contextMenu);
			QString bibFile;
			if (context == LatexParser::Option) {
				QDocumentCursor c(cursor);
				LatexEditorView::selectOptionInLatexArg(c);
				bibFile = c.selectedText() + ".bib";
			} else {
				// context==LatexParser::Command -> open first entry in arg list.
				bibFile = ctxValue.split(',').first() + ".bib";
			}
			act->setData(bibFile);
			edView->connect(act,SIGNAL(triggered()),edView,SLOT(openExternalFile()));
			contextMenu->addAction(act);
		}
		//package help
		if( (context==LatexParser::Command && ctxCommand=="\\usepackage") || context==LatexParser::Package){
			QAction* act=new QAction(LatexEditorView::tr("Open package documentation"),contextMenu);
			QString packageName;
			if (ctxValue.contains(',')) {
				// multiple packages included in one \usepackage command
				QStringList packages;
				foreach (const QString& pkg, ctxValue.split(',')) {
					packages.append(pkg.simplified());
				}
				QDocumentCursor wordCursor = cursor;
				wordCursor.select(QDocumentCursor::WordUnderCursor);
				if (packages.contains(wordCursor.selectedText())) {
					packageName = wordCursor.selectedText();
				} else {
					packageName = packages.first();
				}
			} else {
				packageName = ctxValue;
			}
			act->setText(act->text().append(QString(" (%1)").arg(packageName)));
			act->setData(packageName);
			edView->connect(act,SIGNAL(triggered()),edView,SLOT(openPackageDocumentation()));
			contextMenu->addAction(act);
		}
		// help for any "known" command
		if( context==LatexParser::Command){
			QString command=ctxCommand;
			if(ctxCommand=="\\begin"||ctxCommand=="\\end")
				command=ctxCommand+"{"+ctxValue+"}";
			QString package=edView->document->parent->findPackageByCommand(command);
			package.chop(4);
			if(!package.isEmpty()){
				QAction* act=new QAction(LatexEditorView::tr("Open package documentation"),contextMenu);
				act->setText(act->text().append(QString(" (%1)").arg(package)));
				act->setData(package+"#"+command);
				edView->connect(act,SIGNAL(triggered()),edView,SLOT(openPackageDocumentation()));
				contextMenu->addAction(act);
			}
		}

		//resolve differences
		if (edView){
			QList<int> fids;
			fids<<edView->deleteFormat<<edView->insertFormat<<edView->replaceFormat;
			foreach(int fid,fids){
				if (cursor.hasSelection()) fr= cursor.line().getOverlayAt((cursor.columnNumber()+cursor.anchorColumnNumber()) / 2,fid);
				else fr = cursor.line().getOverlayAt(cursor.columnNumber(),fid);
				if (fr.length>0 ) {
					QVariant var=cursor.line().getCookie(QDocumentLine::DIFF_LIST_COOCKIE);
					if(var.isValid()){
						DiffList diffList=var.value<DiffList>();
						//QString word=cursor.line().text().mid(fr.offset,fr.length);
						DiffOp op;
						op.start=-1;
						foreach(op,diffList){
							if(op.start<=cursor.columnNumber() && op.start+op.length>=cursor.columnNumber()){
								break;
							}
							op.start=-1;
						}
						if(op.start>=0){
							QAction* act=new QAction(LatexEditorView::tr("use yours"),contextMenu);
							act->setData(QPoint(cursor.lineNumber(),cursor.columnNumber()));
							edView->connect(act,SIGNAL(triggered()),edView,SLOT(emitChangeDiff()));
							contextMenu->addAction(act);
							act=new QAction(LatexEditorView::tr("use other's"),contextMenu);
							act->setData(QPoint(-cursor.lineNumber()-1,cursor.columnNumber()));
							edView->connect(act,SIGNAL(triggered()),edView,SLOT(emitChangeDiff()));
							contextMenu->addAction(act);
							break;
						}
					}
				}
			}
		}
		// add action to thesaurus
		QAction* act=new QAction(LatexEditorView::tr("Thesaurus..."),contextMenu);
		act->setData(QPoint(cursor.anchorLineNumber(),cursor.anchorColumnNumber()));
		edView->connect(act,SIGNAL(triggered()),edView,SLOT(triggeredThesaurus()));
		contextMenu->addAction(act);
		contextMenu->addSeparator();
	}
	contextMenu->addActions(baseActions);
	if (validPosition) {
		contextMenu->addSeparator();

		if (context == LatexParser::Citation || context == LatexParser::Reference) {
			QAction *act = new QAction(LatexEditorView::tr("Go to Definition"), contextMenu);
			act->setData(QVariant().fromValue<QDocumentCursor>(cursor));
			edView->connect(act, SIGNAL(triggered()), edView, SLOT(emitGotoDefinitionFromAction()));
			contextMenu->addAction(act);
		}

		QAction *act = new QAction(LatexEditorView::tr("Go to PDF"), contextMenu);
		act->setData(QVariant().fromValue<QDocumentCursor>(cursor));
		edView->connect(act, SIGNAL(triggered()), edView, SLOT(emitSyncPDFFromAction()));
		contextMenu->addAction(act);
	}


	if (event->reason()==QContextMenuEvent::Mouse) contextMenu->exec(event->globalPos());
	else {
		QPoint curPoint=editor->cursor().documentPosition();
		curPoint.ry() += editor->document()->getLineSpacing();
		contextMenu->exec(editor->mapToGlobal(editor->mapFromContents(curPoint)));
	}
	event->accept();

	return true;
}

bool DefaultInputBinding::mouseMoveEvent(QMouseEvent *event, QEditor *editor) {
	checkLinkOverlay(editor->mapToContents(event->pos()), event->modifiers(), editor);
	return false;
}

DefaultInputBinding *defaultInputBinding = new DefaultInputBinding();



//----------------------------------LatexEditorView-----------------------------------
LatexCompleter* LatexEditorView::completer=0;
int LatexEditorView::hideTooltipWhenLeavingLine = -1;

Q_DECLARE_METATYPE(LatexEditorView*);

LatexEditorView::LatexEditorView(QWidget *parent, LatexEditorViewConfig* aconfig,LatexDocument *doc) : QWidget(parent),document(0),speller(0),curChangePos(-1),config(aconfig),bibReader(0) {
	Q_ASSERT(config);

	QVBoxLayout* mainlay = new QVBoxLayout(this);
	mainlay->setSpacing(0);
	mainlay->setMargin(0);
	
	codeeditor = new QCodeEdit(false,this,doc);
	editor=codeeditor->editor();
	
	editor->setProperty("latexEditor", QVariant::fromValue<LatexEditorView*>(this));
	
	lineMarkPanel=new QLineMarkPanel;
	lineMarkPanelAction=codeeditor->addPanel(lineMarkPanel, QCodeEdit::West, false);
	lineNumberPanel=new QLineNumberPanel;
	lineNumberPanelAction=codeeditor->addPanel(lineNumberPanel, QCodeEdit::West, false);;
	QFoldPanel *foldPanel = new QFoldPanel;
	lineFoldPanelAction=codeeditor->addPanel(foldPanel, QCodeEdit::West, false);
	lineChangePanelAction=codeeditor->addPanel(new QLineChangePanel, QCodeEdit::West, false);
	
	statusPanel=new QStatusPanel;
	statusPanel->setFont(QApplication::font());
	statusPanelAction=codeeditor->addPanel(statusPanel, QCodeEdit::South, false);
	
	gotoLinePanel=new QGotoLinePanel;
	gotoLinePanel->setFont(QApplication::font());
	gotoLinePanelAction=codeeditor->addPanel(gotoLinePanel, QCodeEdit::South, false);
	
	searchReplacePanel=new QSearchReplacePanel;
	searchReplacePanel->setFont(QApplication::font());
	searchReplacePanelAction=codeeditor->addPanel(searchReplacePanel, QCodeEdit::South,false);
	searchReplacePanel->hide();
	connect(searchReplacePanel,SIGNAL(extendToggled(bool)),this,SIGNAL(searchExtendToggled(bool)));
	
	
	connect(lineMarkPanel,SIGNAL(lineClicked(int)),this,SLOT(lineMarkClicked(int)));
	connect(lineMarkPanel,SIGNAL(toolTipRequested(int,int)),this,SLOT(lineMarkToolTip(int,int)));
	connect(lineMarkPanel,SIGNAL(contextMenuRequested(int,QPoint)),this,SLOT(lineMarkContextMenuRequested(int,QPoint)));
	connect(foldPanel, SIGNAL(contextMenuRequested(int,QPoint)),this,SLOT(foldContextMenuRequested(int,QPoint)));
	connect(editor,SIGNAL(hovered(QPoint)),this,SLOT(mouseHovered(QPoint)));
	//connect(editor->document(),SIGNAL(contentsChange(int, int)),this,SLOT(documentContentChanged(int, int)));
	connect(editor->document(),SIGNAL(lineDeleted(QDocumentLineHandle*)),this,SLOT(lineDeleted(QDocumentLineHandle*)));
	
	connect(doc, SIGNAL(spellingDictChanged(QString)), this, SLOT(changeSpellingDict(QString)));
	connect(doc, SIGNAL(bookmarkRemoved(QDocumentLineHandle*)),this,SIGNAL(bookmarkRemoved(QDocumentLineHandle*)));
	connect(doc, SIGNAL(bookmarkAdded(QDocumentLineHandle*,int)),this,SIGNAL(bookmarkAdded(QDocumentLineHandle*,int)));
	
	//editor->setFlag(QEditor::CursorJumpPastWrap,false);
	editor->disableAccentHack(config->hackDisableAccentWorkaround);
	
	editor->setInputBinding(defaultInputBinding);
	defaultInputBinding->completerConfig = completer->getConfig();
	defaultInputBinding->editorViewConfig = config;
	Q_ASSERT(defaultInputBinding->completerConfig);
	editor->document()->setLineEndingDirect(QDocument::Local);
	mainlay->addWidget(editor);
	
	setFocusProxy(editor);
	
	//containedLabels.setPattern("(\\\\label)\\{(.+)\\}");
	//containedReferences.setPattern("(\\\\ref|\\\\pageref)\\{(.+)\\}");
	updateSettings();
	SynChecker.verbatimFormat=editor->document()->getFormatId("verbatim");
	SynChecker.setLtxCommands(LatexParser::getInstance());
	SynChecker.start();
	unclosedEnv.id=-1;
	lp=LatexParser::getInstance();
	
	connect(&SynChecker, SIGNAL(checkNextLine(QDocumentLineHandle*,bool,int)), SLOT(checkNextLine(QDocumentLineHandle *,bool,int)), Qt::QueuedConnection);
}

LatexEditorView::~LatexEditorView() {
	delete searchReplacePanel; // to force deletion of m_search before document. Otherwise crashes can come up (linux)
	delete codeeditor; //explicit call destructor of codeeditor (although it has a parent, it is no qobject itself, but passed it to editor)
	
	SynChecker.stop();
	SynChecker.wait();
	if(bibReader){
		bibReader->quit();
		bibReader->wait();
	}
}

void LatexEditorView::updateLtxCommands(bool updateAll){
	if(!document)
		return;
	if(!document->parent)
		return;
	
	//LatexParser ltxCommands=LatexParser::getInstance();
	lp.init();
	lp.append(LatexParser::getInstance()); // append commands set in config
	QList<LatexDocument *>listOfDocs=document->getListOfDocs();
	foreach(const LatexDocument *elem,listOfDocs){
		lp.append(elem->ltxCommands);
	}

	if(updateAll){
		foreach(const LatexDocument *elem,listOfDocs){

			LatexEditorView *view=elem->getEditorView();
			if(view){
				view->setLtxCommands(lp);
				view->reCheckSyntax();
			}
		}
        // check if other document have this doc as child as well (reused doc...)
        LatexDocuments* docs=document->parent;
        QList<LatexDocument*>lstOfAllDocs=docs->getDocuments();
        foreach(LatexDocument* elem,lstOfAllDocs){
            if(listOfDocs.contains(elem))
                continue; // already handled
            if(elem->containsChild(document)){
                // unhandled parent/child
                LatexParser lp;
                lp.init();
                lp.append(LatexParser::getInstance()); // append commands set in config
                QList<LatexDocument *>listOfDocs=elem->getListOfDocs();
                foreach(const LatexDocument *elem,listOfDocs){
                    lp.append(elem->ltxCommands);
                }
                foreach(const LatexDocument *elem,listOfDocs){

                    LatexEditorView *view=elem->getEditorView();
                    if(view){
                        view->setLtxCommands(lp);
                        view->reCheckSyntax();
                    }
                }
            }
        }
	}else{
		SynChecker.setLtxCommands(lp);
	}

}

void LatexEditorView::setLtxCommands(const LatexParser& cmds){
     SynChecker.setLtxCommands(cmds);

     QMap<QString,QString> replacementList;
     bool differenceExists=false;
     foreach(QString elem,cmds.possibleCommands["%replace"].values()){
          int i=elem.indexOf(" ");
          if(i>0){
               replacementList.insert(elem.left(i),elem.mid(i+1));
               if(mReplacementList.value(elem.left(i))!=elem.mid(i+1))
                    differenceExists=true;
          }
     }
     if(differenceExists || replacementList.count()!=mReplacementList.count()){
          mReplacementList=replacementList;
          documentContentChanged(0,editor->document()->lines()); //force complete spellcheck
     }
}

void LatexEditorView::paste(){
	if(completer->isVisible()){
		const QMimeData *d = QApplication::clipboard()->mimeData();
		
		if ( d ) {
			QString txt;
			if ( d->hasFormat("text/plain") )
				txt = d->text();
			else if ( d->hasFormat("text/html") )
				txt = d->html();
			
			if(txt.contains("\n"))
				txt.clear();
			
			if(txt.isEmpty()){
				completer->close();
				editor->paste();
			}else{
				completer->insertText(txt);
			}
		}
	}else{
		editor->paste();
	}
}

void LatexEditorView::insertMacro(QString macro, const QRegExp& trigger, int triggerId, bool allowWrite){
	if (macro.isEmpty()) return;
	if (macro.left(8)=="%SCRIPT\n"){
		scriptengine* eng = new scriptengine();
		for (int i=0;i<=trigger.captureCount();i++)
			eng->triggerMatches << trigger.cap(i);
		eng->triggerId = triggerId;
		if (this) eng->setEditorView(this);
		macro=macro.remove(0,8);
		eng->setScript(macro,allowWrite);
		eng->run();
		if (!eng->globalObject) delete eng;
		else QObject::connect(reinterpret_cast<QObject*>(eng->globalObject), SIGNAL(destroyed()), eng, SLOT(deleteLater()));
		return;
	}
	if (!this) return;
	if (macro.size() > 1 && macro.startsWith("%") && !macro.startsWith("%%")) {
		macro=macro.remove(0,1);
		CodeSnippet s("\\begin{"+macro+"}");
		s.insert(editor);
	} else CodeSnippet(macro).insert(editor);
}

void LatexEditorView::checkForLinkOverlay(QDocumentCursor cursor) {
	if (cursor.atBlockEnd()) {
		removeLinkOverlay();
		return;
	}

	bool validPosition = cursor.isValid() && cursor.line().isValid();
	if (validPosition) {
		LatexParser::ContextType context = LatexParser::Unknown;
		QString ctxCommand, ctxValue;
		QString line=cursor.line().text();
		LatexParser &latexParser = LatexParser::getInstance();
		context = latexParser.findContext(line, cursor.columnNumber(), ctxCommand, ctxValue);
		if (context == LatexParser::Reference) {
			setLinkOverlay(LinkOverlay(cursor, context, LinkOverlay::RefOverlay));
		} else if (context==LatexParser::Option && latexParser.possibleCommands["%include"].contains(ctxCommand)) {
			setLinkOverlay(LinkOverlay(cursor, context, LinkOverlay::FileOverlay));
		} else if (context==LatexParser::Option && latexParser.possibleCommands["%url"].contains(ctxCommand)) {
			setLinkOverlay(LinkOverlay(cursor, context, LinkOverlay::UrlOverlay));
		} else if (context==LatexParser::Package) {
			setLinkOverlay(LinkOverlay(cursor, context, LinkOverlay::UsepackageOverlay));
		} else if (context==LatexParser::Option && latexParser.possibleCommands["%bibliography"].contains(ctxCommand)) {
			setLinkOverlay(LinkOverlay(cursor, context, LinkOverlay::BibFileOverlay));
		} else if (context==LatexParser::Citation) {
			setLinkOverlay(LinkOverlay(cursor, context, LinkOverlay::CiteOverlay));
		} else {
			if (linkOverlay.isValid()) removeLinkOverlay();
		}
	} else {
		if (linkOverlay.isValid()) removeLinkOverlay();
	}
}

void LatexEditorView::setLinkOverlay(const LinkOverlay & overlay) {
	if (linkOverlay.isValid()) {
		if (overlay == linkOverlay) {
			return; // same overlay
		} else {
			removeLinkOverlay();
		}
	}

	linkOverlay = overlay;
	linkOverlay.docLine.addOverlay(linkOverlay.formatRange);
	editor->viewport()->update(); // immediately apply the overlay
	linkOverlayStoredCursor = editor->viewport()->cursor();
	editor->viewport()->setCursor(Qt::PointingHandCursor);
}

void LatexEditorView::removeLinkOverlay() {
	if (linkOverlay.isValid()) {
		linkOverlay.docLine.removeOverlay(linkOverlay.formatRange);
		linkOverlay = LinkOverlay();
		editor->viewport()->update(); // immediately apply the overlay
		editor->viewport()->setCursor(linkOverlayStoredCursor);
	}
}

bool LatexEditorView::isNonTextFormat(int format){
	if (format <= 0) return false;
	return format == numbersFormat
			|| format == verbatimFormat
			|| format == pictureFormat
			|| format == pweaveDelimiterFormat
			|| format == pweaveBlockFormat
			|| format == sweaveDelimiterFormat
			|| format == sweaveBlockFormat
			|| format == math_DelimiterFormat
			|| format == asymptoteBlockFormat;
}

void LatexEditorView::selectOptionInLatexArg(QDocumentCursor &cur) {
	QString startDelims = "[{, \t\n";
	int startCol = cur.columnNumber();
	while (!cur.atLineStart() && !startDelims.contains(cur.previousChar())) {
		cur.movePosition(1, QDocumentCursor::PreviousCharacter);
	}
	cur.setColumnNumber(startCol, QDocumentCursor::KeepAnchor);
	QString endDelims = "]}, \t\n";
	while (!cur.atLineEnd() && !endDelims.contains(cur.nextChar())) {
		cur.movePosition(1, QDocumentCursor::NextCharacter, QDocumentCursor::KeepAnchor);
	}
}

void LatexEditorView::temporaryHighlight(QDocumentCursor cur) {
	if (!cur.hasSelection()) return;
	REQUIRE(editor->document());

	QDocumentLine docLine(cur.selectionStart().line());
	if (cur.endLineNumber() != cur.startLineNumber()) {
		// TODO: proper highlighting of selections spanning more than one line. Currently just highlight to the end of the first line:
		cur = cur.selectionStart();
		cur.movePosition(1, QDocumentCursor::EndOfLine, QDocumentCursor::KeepAnchor);
	}

	QFormatRange highlight(cur.startColumnNumber(), cur.endColumnNumber()-cur.startColumnNumber(), editor->document()->getFormatId("search"));
	docLine.addOverlay(highlight);
	tempHighlightQueue.append(QPair<QDocumentLine, QFormatRange>(docLine, highlight));
	QTimer::singleShot(1000, this, SLOT(removeTemporaryHighlight()));
}

void LatexEditorView::removeTemporaryHighlight() {
	if (!tempHighlightQueue.isEmpty()) {
		QDocumentLine docLine(tempHighlightQueue.first().first);
		docLine.removeOverlay(tempHighlightQueue.first().second);
		tempHighlightQueue.removeFirst();
	}
}




void LatexEditorView::displayLineGrammarErrorsInternal(int lineNr, const QList<GrammarError>& errors){
	QDocumentLine line = document->line(lineNr);
	foreach (const int f, grammarFormats)
		line.clearOverlays(f);
	foreach (const GrammarError& error, errors) {
		int f;
		if (error.error == GET_UNKNOWN) f = grammarMistakeFormat;
		else {
			int index = (int)(error.error) - 1;
			REQUIRE(index < grammarFormats.size());
			if (grammarFormatsDisabled[index]) continue;
			f = grammarFormats[index];
		}
		if (config->hideNonTextGrammarErrors && (isNonTextFormat(line.getFormatAt(error.offset)) || isNonTextFormat(line.getFormatAt(error.offset+error.length-1))))
			continue;
		line.addOverlay(QFormatRange(error.offset,error.length,f));
	}
	//todo: check for width changing like if (changed && ff->format(wordRepetitionFormat).widthChanging()) line.handle()->updateWrapAndNotifyDocument(i);
}

void LatexEditorView::lineGrammarChecked(const void* doc, const void* lineHandle, int lineNr, const QList<GrammarError>& errors){
	if (doc != this->document) return;
	lineNr = document->indexOf(const_cast<QDocumentLineHandle*>(static_cast<const QDocumentLineHandle*>(lineHandle)), lineNr);
	if (lineNr < 0) return; //line already deleted
	displayLineGrammarErrorsInternal(lineNr, errors);
	document->line(lineNr).setCookie(QDocumentLine::GRAMMAR_ERROR_COOKIE, QVariant::fromValue<QList<GrammarError> >(errors));
}

void LatexEditorView::setGrammarOverlayDisabled(int type, bool newValue){
	REQUIRE(type >= 0 && type < grammarFormatsDisabled.size());
	if (newValue == grammarFormatsDisabled[type]) return;
	grammarFormatsDisabled[type] = newValue;
}

void LatexEditorView::updateGrammarOverlays(){
	for (int i=0;i<document->lineCount();i++)
		displayLineGrammarErrorsInternal(i, document->line(i).getCookie(QDocumentLine::GRAMMAR_ERROR_COOKIE).value<QList<GrammarError> >());
	editor->viewport()->update();
}

void LatexEditorView::viewActivated(){
	if (!LatexEditorView::completer) return;
}

QString LatexEditorView::displayName() const{
	return (!editor || editor->fileName().isEmpty() ? tr("untitled") : editor->name());
}


void LatexEditorView::complete(int flags) {
	if (!LatexEditorView::completer) return;
	setFocus();
	LatexEditorView::completer->complete(editor,LatexCompleter::CompletionFlags(flags));
}
void LatexEditorView::jumpChangePositionBackward() {
	if (changePositions.size()==0) return;
	for (int i=changePositions.size()-1; i>=0; i--)
		if (!changePositions[i].isValid()) {
			changePositions.removeAt(i);
			if (i<=curChangePos) curChangePos--;
		}
	if (curChangePos >= changePositions.size()-1) curChangePos = changePositions.size()-1;
	else if (curChangePos>=0 && curChangePos < changePositions.size()-1) curChangePos++;
	else if (editor->cursor().line().handle()==changePositions.first().dlh()) curChangePos=1;
	else curChangePos=0;
	if (curChangePos>=0 && curChangePos < changePositions.size())
		editor->setCursorPosition(changePositions[curChangePos].lineNumber(),changePositions[curChangePos].columnNumber());
}
void LatexEditorView::jumpChangePositionForward() {
	for (int i=changePositions.size()-1; i>=0; i--)
		if (!changePositions[i].isValid()) {
			changePositions.removeAt(i);
			if (i<=curChangePos) curChangePos--;
		}
	if (curChangePos>0) {
		curChangePos--;
		editor->setCursorPosition(changePositions[curChangePos].lineNumber(),changePositions[curChangePos].columnNumber());
	}
}
void LatexEditorView::jumpToBookmark(int bookmarkNumber) {
	int markLine=editor->document()->findNextMark(bookMarkId(bookmarkNumber),editor->cursor().lineNumber(),editor->cursor().lineNumber()-1);
	if (markLine>=0) {
		emit saveCurrentCursorToHistoryRequested();
		editor->setCursorPosition(markLine,0);
		editor->setFocus();
	}
}

void LatexEditorView::removeBookmark(QDocumentLineHandle *dlh,int bookmarkNumber){
	if(!dlh)
		return;
	int rmid=bookMarkId(bookmarkNumber);
	if(hasBookmark(dlh,bookmarkNumber)) {
		document->removeMark(dlh,rmid);
		emit bookmarkRemoved(dlh);
	}
}

void LatexEditorView::removeBookmark(int lineNr,int bookmarkNumber){
	removeBookmark(document->line(lineNr).handle(), bookmarkNumber);
}

void LatexEditorView::addBookmark(int lineNr,int bookmarkNumber){
     int rmid=bookMarkId(bookmarkNumber);
     if (bookmarkNumber>=0)
          document->line(document->findNextMark(rmid)).removeMark(rmid);
     if(!document->line(lineNr).hasMark(rmid))
          document->line(lineNr).addMark(rmid);
}
bool LatexEditorView::hasBookmark(int lineNr,int bookmarkNumber){
     int rmid=bookMarkId(bookmarkNumber);
     return document->line(lineNr).hasMark(rmid);
}

bool LatexEditorView::hasBookmark(QDocumentLineHandle *dlh,int bookmarkNumber){
     if(!dlh)
          return false;
     int rmid=bookMarkId(bookmarkNumber);
     QList<int> m_marks=document->marks(dlh);
     return m_marks.contains(rmid);
}

bool LatexEditorView::toggleBookmark(int bookmarkNumber, QDocumentLine line) {
	if (!line.isValid()) line = editor->cursor().line();
	int rmid=bookMarkId(bookmarkNumber);
	if (line.hasMark(rmid)) {
		line.removeMark(rmid);
		emit bookmarkRemoved(line.handle());
		return false;
	}
	if (bookmarkNumber>=0){
		int ln=editor->document()->findNextMark(rmid);
		if(ln>=0){
			editor->document()->line(ln).removeMark(rmid);
			emit bookmarkRemoved(editor->document()->line(ln).handle());
		}
	}
	for (int i=-1; i<10; i++) {
		int rmid=bookMarkId(i);
		if(line.hasMark(rmid)){
			line.removeMark(rmid);
			emit bookmarkRemoved(line.handle());
		}
	}
	line.addMark(rmid);
	emit bookmarkAdded(line.handle(),bookmarkNumber);
	return true;
}
bool LatexEditorView::gotoLineHandleAndSearchCommand(const QDocumentLineHandle* dlh, const QSet<QString>& searchFor, const QString& id){
	if (!dlh) return false;
	int ln=dlh->document()->indexOf(dlh);
	if (ln<0) return false;
	QString lineText = dlh->document()->line(ln).text();
	int col = 0;
	foreach (const QString &cmd, searchFor) {
		col = lineText.indexOf(cmd+"{"+id);
		if (col < 0) col = lineText.indexOf(QRegExp(QRegExp::escape(cmd)+"\\[[^\\]{}()\\\\]+\\]\\{"+QRegExp::escape(id))); //for \command[options]{id}
		if (col >= 0) {
			col += cmd.length()+1;
			break;
		}
	}
	//Q_ASSERT(col >= 0);
	if (col < 0) col = 0;
	editor->setCursorPosition(ln, col, false);
	editor->ensureCursorVisible(QEditor::Navigation);
	return true;

}

bool LatexEditorView::gotoToLabel(const QString& label){
	int cnt=document->countLabels(label);
	if (cnt==0) return false;
	QMultiHash<QDocumentLineHandle*,int> result=document->getLabels(label);
	if (result.isEmpty()) return false;
	return gotoLineHandleAndSearchCommand(result.keys().first(), LatexParser::getInstance().possibleCommands["%label"], label);
}

bool LatexEditorView::gotoToBibItem(const QString &bibId) {
	// only supports local bibitems. BibTeX has to be handled on a higher level
	QMultiHash<QDocumentLineHandle*,int> result=document->getBibItems(bibId);
	if (result.isEmpty()) return false;
	return gotoLineHandleAndSearchCommand(result.keys().first(), LatexParser::getInstance().possibleCommands["%bibitem"], bibId);
}



//collapse/expand every possible line
void LatexEditorView::foldEverything(bool unFold) {
	QDocument* doc = editor->document();
	QLanguageDefinition* ld = doc->languageDefinition();
	QFoldedLineIterator fli = ld->foldedLineIterator(doc, 0);
	for (int i=0; i < doc->lines(); i++, ++fli)
		if (fli.open){
			if (unFold) ld->expand(doc, i);
			else ld->collapse(doc, i);
		}
}
//collapse/expand lines at the top level
void LatexEditorView::foldLevel(bool unFold, int level) {	
	QDocument* doc = editor->document();
	QLanguageDefinition* ld = doc->languageDefinition();
	for (QFoldedLineIterator fli = ld->foldedLineIterator(doc);
		fli.line.isValid(); ++fli){
		if (fli.openParentheses.size()==level && fli.open) {
			if (unFold) ld->expand(doc, fli.lineNr);
			else ld->collapse(doc, fli.lineNr);
		}
	}/*
 QDocument* doc = editor->document();
 QLanguageDefinition* ld = doc->languageDefinition();
 int depth=0;
 for (int n = 0; n < doc->lines(); ++n) {
  QDocumentLine b=doc->line(n);
  if (b.isHidden()) continue;
  
  int flags = ld->blockFlags(doc, n, depth);
  short open = QCE_FOLD_OPEN_COUNT(flags);
  short close = QCE_FOLD_CLOSE_COUNT(flags);
  
  depth -= close;
  
  if (depth < 0)
   depth = 0;
   
  depth += open;
  
  if (depth==level) {
   if (unFold && (flags & QLanguageDefinition::Collapsed))
    ld->expand(doc,n);
   else if (!unFold && (flags & QLanguageDefinition::Collapsible))
    ld->collapse(doc,n);
  }
  if (ld->blockFlags(doc, n, depth) & QLanguageDefinition::Collapsed)
   depth -= open; // outermost block folded : none of the opening is actually opened
 }*/
}
//Collapse at the first possible point before/at line
void LatexEditorView::foldBlockAt(bool unFold, int line) {
     editor->document()->foldBlockAt(unFold,line);
}



void LatexEditorView::cleanBib() {
	QDocument* doc = editor->document();
	for (int i=doc->lines()-1; i>=0; i--) {
		QString trimLine=doc->line(i).text().trimmed();
		if (trimLine.startsWith("OPT") || trimLine.startsWith("ALT"))
			doc->execute(new QDocumentEraseCommand(i,0,i+1,0,doc));
	}
	setFocus();
}


QList<QAction *> LatexEditorView::getBaseActions(){
	if (!defaultInputBinding) return QList<QAction *>();
	return defaultInputBinding->baseActions;
}
void LatexEditorView::setBaseActions(QList<QAction *> baseActions) {
	if (!defaultInputBinding) return;
	defaultInputBinding->baseActions=baseActions;
}
void LatexEditorView::setSpellerManager(SpellerManager* manager) {
	spellerManager = manager;
	connect(spellerManager, SIGNAL(defaultSpellerChanged()), this, SLOT(reloadSpeller()));
}
bool LatexEditorView::setSpeller(const QString &name) {
	if (!spellerManager) return false;
	
	useDefaultSpeller = (name == "<default>");
	
	SpellerUtility* su;
	if (spellerManager->hasSpeller(name)) {
		su = spellerManager->getSpeller(name);
		if (!su) return false;
	} else {
		su = spellerManager->getSpeller(spellerManager->defaultSpellerName());
		REQUIRE_RET(su, false);
		useDefaultSpeller = true;
	}
	if (su == speller) return true; // nothing to do

	if (speller) {
		disconnect(speller, SIGNAL(aboutToDelete()), this, SLOT(reloadSpeller()));
		disconnect(speller, SIGNAL(ignoredWordAdded(QString)), this, SLOT(spellRemoveMarkers(QString)));
	}
	speller = su;
	connect(speller, SIGNAL(aboutToDelete()), this, SLOT(reloadSpeller()));
	connect(speller, SIGNAL(ignoredWordAdded(QString)), this, SLOT(spellRemoveMarkers(QString)));
	emit spellerChanged(name);
	
	if (document) {
		document->updateMagicComment("spellcheck", speller->name());
	}
	
	// force new highlighting
	documentContentChanged(0,editor->document()->lines());
	return true;
}
void LatexEditorView::reloadSpeller() {
	if (useDefaultSpeller) {
		setSpeller("<default>");
		return;
	}
	
	SpellerUtility *su = qobject_cast<SpellerUtility *>(sender());
	if (!su) return;
	setSpeller(su->name());
}
QString LatexEditorView::getSpeller() {
	if (useDefaultSpeller) return QString("<default>");
	return speller->name();
}

void LatexEditorView::setCompleter(LatexCompleter* newCompleter) {
	LatexEditorView::completer=newCompleter;
}
LatexCompleter* LatexEditorView::getCompleter(){
	return LatexEditorView::completer;
}

void LatexEditorView::updatePackageFormats(){
     for (int i=0; i<editor->document()->lines(); i++) {
          QList<QFormatRange> li=editor->document()->line(i).getOverlays();
          QString curLineText=editor->document()->line(i).text();
          for (int j=0; j<li.size(); j++)
               if (li[j].format == packagePresentFormat || li[j].format == packageMissingFormat || li[j].format == packageUndefinedFormat){
                    int newFormat = packageUndefinedFormat;
                    if (!latexPackageList->isEmpty()) {
                         newFormat = latexPackageList->contains(curLineText.mid(li[j].offset,li[j].length))?packagePresentFormat:packageMissingFormat;
                    }
                    if (newFormat!=li[j].format) {
                         editor->document()->line(i).removeOverlay(li[j]);
                         li[j].format=newFormat;
                         editor->document()->line(i).addOverlay(li[j]);
                    }
               }
     }
}

void LatexEditorView::clearLogMarks() {
	setLogMarksVisible(false);
	logEntryToLine.clear();
	logEntryToMarkID.clear();
	lineToLogEntries.clear();
}

void LatexEditorView::addLogEntry(int logEntryNumber, int lineNumber, int markID) {
	QDocumentLine l=editor->document()->line(lineNumber);
	lineToLogEntries.insert(l.handle(), logEntryNumber);
	logEntryToLine[logEntryNumber] = l.handle();
	logEntryToMarkID[logEntryNumber] = markID;
}

void LatexEditorView::setLogMarksVisible(bool visible) {
	if (visible) {
		foreach(int logEntryNumber, logEntryToMarkID.keys()) {
			int markID = logEntryToMarkID[logEntryNumber];
			if (markID >= 0) {
				QDocumentLine(logEntryToLine[logEntryNumber]).addMark(markID);
			}
		}
	} else {
		int errorMarkID = QLineMarksInfoCenter::instance()->markTypeId("error");
		int warningMarkID = QLineMarksInfoCenter::instance()->markTypeId("warning");
		int badboxMarkID = QLineMarksInfoCenter::instance()->markTypeId("badbox");
		editor->document()->removeMarks(errorMarkID);
		editor->document()->removeMarks(warningMarkID);
		editor->document()->removeMarks(badboxMarkID);
	}
}

void LatexEditorView::updateCitationFormats(){
     for (int i=0; i<editor->document()->lines(); i++) {
          QList<QFormatRange> li=editor->document()->line(i).getOverlays();
          QString curLineText=editor->document()->line(i).text();
          for (int j=0; j<li.size(); j++)
               if (li[j].format == citationPresentFormat || li[j].format == citationMissingFormat){
                    int newFormat=document->bibIdValid(curLineText.mid(li[j].offset,li[j].length))?citationPresentFormat:citationMissingFormat;
                    if (newFormat!=li[j].format) {
                         editor->document()->line(i).removeOverlay(li[j]);
                         li[j].format=newFormat;
                         editor->document()->line(i).addOverlay(li[j]);
                    }
               }
     }
}

bool LatexEditorView::containsBibTeXId(QString id){
     return document->bibIdValid(id);
}

int LatexEditorView::bookMarkId(int bookmarkNumber) {
     if (bookmarkNumber==-1) return  QLineMarksInfoCenter::instance()->markTypeId("bookmark"); //unnumbered mark
     else return QLineMarksInfoCenter::instance()->markTypeId("bookmark"+QString::number(bookmarkNumber));
     //return document->bookMarkId(bookmarkNumber);
}

void LatexEditorView::setLineMarkToolTip(const QString& tooltip){
     lineMarkPanel->setToolTipForTouchedMark(tooltip);
}

int LatexEditorView::environmentFormat, LatexEditorView::referencePresentFormat, LatexEditorView::referenceMissingFormat, LatexEditorView::referenceMultipleFormat, LatexEditorView::citationMissingFormat, LatexEditorView::citationPresentFormat,LatexEditorView::structureFormat,LatexEditorView::packageMissingFormat,LatexEditorView::packagePresentFormat,LatexEditorView::packageUndefinedFormat,
LatexEditorView::wordRepetitionFormat, LatexEditorView::wordRepetitionLongRangeFormat, LatexEditorView::badWordFormat, LatexEditorView::grammarMistakeFormat, LatexEditorView::grammarMistakeSpecial1Format, LatexEditorView::grammarMistakeSpecial2Format, LatexEditorView::grammarMistakeSpecial3Format, LatexEditorView::grammarMistakeSpecial4Format,
LatexEditorView::numbersFormat, LatexEditorView::verbatimFormat, LatexEditorView::commentFormat, LatexEditorView::pictureFormat, LatexEditorView::math_DelimiterFormat,
LatexEditorView::pweaveDelimiterFormat, LatexEditorView::pweaveBlockFormat, LatexEditorView::sweaveDelimiterFormat, LatexEditorView::sweaveBlockFormat,
LatexEditorView::asymptoteBlockFormat;
int LatexEditorView::syntaxErrorFormat,LatexEditorView::preEditFormat;
int LatexEditorView::deleteFormat,LatexEditorView::insertFormat,LatexEditorView::replaceFormat;

QList<int> LatexEditorView::grammarFormats;
QVector<bool> LatexEditorView::grammarFormatsDisabled;
QList<int> LatexEditorView::formatsList;

void LatexEditorView::updateSettings(){
	lineNumberPanel->setVerboseMode(config->showlinemultiples!=10);
	editor->setFont(QFont(config->fontFamily, config->fontSize));
	editor->setLineWrapping(config->wordwrap>0);
	editor->setSoftLimitedLineWrapping(config->wordwrap==2);
	editor->setHardLineWrapping(config->wordwrap>2);
	if(config->wordwrap>1){
		editor->setWrapAfterNumChars(config->lineWidth);
	}else{
		editor->setWrapAfterNumChars(0);
	}
	editor->setFlag(QEditor::AutoIndent,config->autoindent);
	editor->setFlag(QEditor::WeakIndent,config->weakindent);
	editor->setFlag(QEditor::ReplaceTabs,config->indentWithSpaces);
	editor->setFlag(QEditor::MouseWheelZoom, config->mouseWheelZoom);
	editor->setFlag(QEditor::SmoothScrolling, config->smoothScrolling);
	editor->setFlag(QEditor::AutoInsertLRM, config->autoInsertLRM);
	editor->setFlag(QEditor::BidiVisualColumnMode, config->visualColumnMode);
	editor->setFlag(QEditor::OverwriteOpeningBracketFollowedByPlaceholder, config->overwriteOpeningBracketFollowedByPlaceholder);
	editor->setFlag(QEditor::OverwriteClosingBracketFollowingPlaceholder, config->overwriteClosingBracketFollowingPlaceholder);
	//TODO: parenmatch
	editor->setFlag(QEditor::AutoCloseChars, config->parenComplete);
	editor->setFlag(QEditor::ShowPlaceholders, config->showPlaceholders);
    editor->setSilentReloadOnExternalChanges(config->silentReload);
    editor->setHidden(false);
	editor->setCursorSurroundingLines(config->cursorSurroundLines);
	editor->setCursorBold(config->boldCursor);
	lineMarkPanelAction->setChecked((config->showlinemultiples!=0) ||config->folding||config->showlinestate);
	lineNumberPanelAction->setChecked(config->showlinemultiples!=0);
	lineFoldPanelAction->setChecked(config->folding);
	lineChangePanelAction->setChecked(config->showlinestate);
	statusPanelAction->setChecked(config->showcursorstate);
	editor->setDisplayModifyTime(false);
	searchReplacePanel->setUseLineForSearch(config->useLineForSearch);
	searchReplacePanel->setSearchOnlyInSelection(config->searchOnlyInSelection);
	QDocument::setShowSpaces(config->showWhitespace?(QDocument::ShowTrailing | QDocument::ShowLeading | QDocument::ShowTabs):QDocument::ShowNone);
	QDocument::setTabStop(config->tabStop);
	QDocument::setLineSpacingFactor(config->lineSpacingPercent / 100.0);

	SynChecker.setErrFormat(syntaxErrorFormat);
	editor->m_preEditFormat=preEditFormat;
	
	QDocument::setWorkAround(QDocument::DisableFixedPitchMode, config->hackDisableFixedPitch);
	QDocument::setWorkAround(QDocument::DisableWidthCache, config->hackDisableWidthCache);
	QDocument::setWorkAround(QDocument::DisableLineCache, config->hackDisableLineCache);

	QDocument::setWorkAround(QDocument::ForceQTextLayout, config->hackRenderingMode == 1);
	QDocument::setWorkAround(QDocument::ForceSingleCharacterDrawing, config->hackRenderingMode == 2);
}

void LatexEditorView::updateFormatSettings(){
	static bool formatsLoaded = false;
	if (!formatsLoaded) {
		REQUIRE(QDocument::defaultFormatScheme());
#define F(n) &n##Format, #n, 
		const void * formats[] = {F(environment)
							 F(referenceMultiple) F(referencePresent) F(referenceMissing)
							 F(citationPresent) F(citationMissing)
							 F(packageMissing) F(packagePresent)
							 &packageUndefinedFormat, "normal",
							 &syntaxErrorFormat, "latexSyntaxMistake", //TODO: rename all to xFormat, "x"
							 F(structure)
							 &deleteFormat, "diffDelete",
							 &insertFormat, "diffAdd",
							 &replaceFormat, "diffReplace",
							 F(wordRepetition) F(wordRepetitionLongRange) F(badWord)
							 F(grammarMistake)
							 F(grammarMistakeSpecial1) F(grammarMistakeSpecial2) F(grammarMistakeSpecial3) F(grammarMistakeSpecial4)
							 F(numbers) F(verbatim) F(comment) F(picture)
							 &pweaveDelimiterFormat, "pweave-delimiter",
							 &pweaveBlockFormat, "pweave-block",
							 &sweaveDelimiterFormat, "sweave-delimiter",
							 &sweaveBlockFormat, "sweave-block",
							 &math_DelimiterFormat, "math-delimiter",
							 &asymptoteBlockFormat, "asymptote:block",
							 &preEditFormat,"preedit",
							 0, 0
							};
#undef F
		const void ** temp = formats;
		while (*temp) {
			int * c = (static_cast<int*>(const_cast<void*>(*temp)));
			Q_ASSERT(c != 0);
			*c = QDocument::defaultFormatScheme()->id(QString(static_cast<const char*>(*(temp+1))));
			temp+=2;
		}
		//int f=QDocument::formatFactory()->id("citationMissing");
		formatsLoaded = true;
		grammarFormats << wordRepetitionFormat << wordRepetitionLongRangeFormat << badWordFormat << grammarMistakeFormat << grammarMistakeSpecial1Format << grammarMistakeSpecial2Format << grammarMistakeSpecial3Format << grammarMistakeSpecial4Format; //don't change the order, it corresponds to GrammarErrorType
		grammarFormatsDisabled.resize(9);
		grammarFormatsDisabled.fill(false);
		formatsList<<SpellerUtility::spellcheckErrorFormat<<referencePresentFormat<<citationPresentFormat<<referenceMissingFormat;
		formatsList<<referenceMultipleFormat<<citationMissingFormat<<packageMissingFormat<<packagePresentFormat<<packageUndefinedFormat<<environmentFormat<<syntaxErrorFormat;
		formatsList<<wordRepetitionFormat<<structureFormat<<insertFormat<<deleteFormat<<replaceFormat;
	}
}

void LatexEditorView::requestCitation(){
	QString id = editor->cursor().selectedText();
	emit needCitation(id);
}

void LatexEditorView::openExternalFile(){
	QAction *act = qobject_cast<QAction*>(sender());
	QString name=act->data().toString();
	if(!name.isEmpty())
		emit openFile(name);
}

void LatexEditorView::openPackageDocumentation(QString package){
	QString command;
	if (package.isEmpty()) {
		QAction *act = qobject_cast<QAction*>(sender());
		if (!act) return;
		package = act->data().toString();
	}
	if (package.contains("#")) {
		int i = package.indexOf("#");
		command = package.mid(i+1);
		package = package.left(i);
	}
	// replace some package denominations
	if (package=="latex-document" || package=="latex-dev" || package=="latex-mathsymbols")
		package = "latex2e";
	if (package=="class-scrartcl,scrreprt,scrbook")
		package = "scrartcl";
	if (package.startsWith("class-"))
		package = package.mid(6);
	if (!package.isEmpty()) {
		if(config->texdocHelpInInternalViewer) {
			QString docfile = Help::packageDocFile(package);
			if (docfile.isEmpty())
				return;
			if (docfile.endsWith(".pdf"))
				emit openInternalDocViewer(docfile, command);
			else
				Help::instance()->viewTexdoc(package); // fallback e.g. for dvi
		}else{
			Help::instance()->viewTexdoc(package);
		}
	}
}

void LatexEditorView::emitChangeDiff(){
	QAction *act = qobject_cast<QAction*>(sender());
	QPoint pt=act->data().toPoint();
	emit changeDiff(pt);
}

void LatexEditorView::emitGotoDefinitionFromAction() {
	QDocumentCursor c;
	QAction *act = qobject_cast<QAction *>(sender());
	if (act) {
		c = act->data().value<QDocumentCursor>();
	}
	emit gotoDefinition(c);
}

void LatexEditorView::emitSyncPDFFromAction() {
	QDocumentCursor c;
	QAction *act = qobject_cast<QAction *>(sender());
	if (act) {
		c = act->data().value<QDocumentCursor>();
	}
	emit syncPDFRequested(c);
}

void LatexEditorView::lineMarkClicked(int line) {
	QDocumentLine l=editor->document()->line(line);
	if (!l.isValid()) return;
	//remove old mark (when possible)
	for (int i=-1; i<10; i++)
		if (l.hasMark(bookMarkId(i))) {
			l.removeMark(bookMarkId(i));
			emit bookmarkRemoved(l.handle());
			return;
		}
	// remove error marks
	if (l.hasMark(QLineMarksInfoCenter::instance()->markTypeId("error"))) {
		l.removeMark(QLineMarksInfoCenter::instance()->markTypeId("error"));
		return;
	}
	if (l.hasMark(QLineMarksInfoCenter::instance()->markTypeId("warning"))) {
		l.removeMark(QLineMarksInfoCenter::instance()->markTypeId("warning"));
		return;
	}
	if (l.hasMark(QLineMarksInfoCenter::instance()->markTypeId("badbox"))) {
		l.removeMark(QLineMarksInfoCenter::instance()->markTypeId("badbox"));
		return;
	}
	//add unused mark (1,2 .. 9,0) (when possible)
	for (int i=1; i<=10; i++) {
		if (editor->document()->findNextMark(bookMarkId(i%10))<0) {
			l.addMark(bookMarkId(i%10));
			emit bookmarkAdded(l.handle(),i);
			return;
		}
	}
	l.addMark(bookMarkId(-1));
	emit bookmarkAdded(l.handle(),-1);
}
void LatexEditorView::lineMarkToolTip(int line, int mark){
	if (line < 0 || line>=editor->document()->lines()) return;
	int errorMarkID = QLineMarksInfoCenter::instance()->markTypeId("error");
	int warningMarkID = QLineMarksInfoCenter::instance()->markTypeId("warning");
	int badboxMarkID = QLineMarksInfoCenter::instance()->markTypeId("badbox");
	if (mark != errorMarkID && mark != warningMarkID && mark != badboxMarkID) return;
	QList<int> errors = lineToLogEntries.values(editor->document()->line(line).handle());
	if (!errors.isEmpty())
		emit showMarkTooltipForLogMessage(errors);
}
void LatexEditorView::checkNextLine(QDocumentLineHandle *dlh,bool clearOverlay,int ticket){
	Q_ASSERT_X(dlh!=0,"checkNextLine","empty dlh used in checkNextLine");
	if(dlh->getRef()>1 && dlh->getCurrentTicket()==ticket){
		StackEnvironment env;
		QVariant envVar=dlh->getCookie(QDocumentLine::STACK_ENVIRONMENT_COOKIE);
		if(envVar.isValid())
			env=envVar.value<StackEnvironment>();
		int index = document->indexOf(dlh);
		if (index == -1) return; //deleted
		REQUIRE(dlh->document() == document);
		if (index + 1 >= document->lines()) {
			//remove old errror marker
			if(unclosedEnv.id!=-1){
				unclosedEnv.id = -1;
				int unclosedEnvIndex = document->indexOf(unclosedEnv.dlh);
				if (unclosedEnvIndex >= 0 && unclosedEnv.dlh->getCookie(QDocumentLine::UNCLOSED_ENVIRONMENT_COOKIE).isValid()){
					StackEnvironment env;
					Environment newEnv;
					newEnv.name="normal";
					newEnv.id=1;
					env.push(newEnv);
					if (unclosedEnvIndex >= 1) {
						QDocumentLineHandle *prev = document->line(unclosedEnvIndex-1).handle();
						QVariant result=prev->getCookie(QDocumentLine::STACK_ENVIRONMENT_COOKIE);
						if(result.isValid())
							env=result.value<StackEnvironment>();
					}
					SynChecker.putLine(unclosedEnv.dlh, env, true);
				}
			}
			if(env.size()>1){
				//at least one env has not been closed
				Environment environment=env.top();
				unclosedEnv=env.top();
				SynChecker.markUnclosedEnv(environment);
			}
			return;
		}
		SynChecker.putLine(document->line(index+1).handle(), env, clearOverlay);
	}
	dlh->deref();
}

void LatexEditorView::clearOverlays(){
	for (int i=0; i<editor->document()->lineCount(); i++) {
		QDocumentLine line = editor->document()->line(i);
		if (!line.isValid()) continue;
		
		//remove all overlays used for latex things, in descending frequency
		line.clearOverlays(SpellerUtility::spellcheckErrorFormat);
		line.clearOverlays(referencePresentFormat);
		line.clearOverlays(citationPresentFormat);
		line.clearOverlays(referenceMissingFormat);
		line.clearOverlays(referenceMultipleFormat);
		line.clearOverlays(citationMissingFormat);
		line.clearOverlays(environmentFormat);
		line.clearOverlays(syntaxErrorFormat);
		line.clearOverlays(structureFormat);
		foreach (const int f, grammarFormats)
			line.clearOverlays(f);
	}
}

void LatexEditorView::documentContentChanged(int linenr, int count) {
	Q_ASSERT(editor);
	QDocumentLine startline=editor->document()->line(linenr);
	if ((linenr>=0 || count<editor->document()->lines()) && editor->cursor().isValid() &&
			!editor->cursor().atLineStart() && editor->cursor().line().text().trimmed().length()>0 &&
			startline.isValid()) {
		bool add=false;
		if (changePositions.size()<=0) add=true;
		else if (curChangePos<1) {
			if (changePositions.first().dlh()!=startline.handle()) add=true;
			else changePositions.first().setColumnNumber(editor->cursor().columnNumber());
		} else if (curChangePos>=changePositions.size()-1) {
			if (changePositions.last().dlh()!=startline.handle()) add=true;
			else changePositions.last().setColumnNumber(editor->cursor().columnNumber());
		}  else if (changePositions[curChangePos].dlh()==startline.handle()) changePositions[curChangePos].setColumnNumber(editor->cursor().columnNumber());
		else if (changePositions[curChangePos+1].dlh()==startline.handle()) changePositions[curChangePos+1].setColumnNumber(editor->cursor().columnNumber());
		else add=true;
		if (add) {
			curChangePos=-1;
			changePositions.insert(0,CursorPosition(editor->document()->cursor(linenr, editor->cursor().columnNumber())));
			if (changePositions.size()>20) changePositions.removeLast();
		}
	}
	
	if (autoPreviewCursor.size() > 0) {
		for (int i=0;i<autoPreviewCursor.size();i++) {
			const QDocumentCursor& c = autoPreviewCursor[i];
			if (c.lineNumber() >= linenr && c.anchorLineNumber() < linenr+count)
				emit showPreview(c); //may modify autoPreviewCursor
		}
		
	}
	
	// checking
	if (!QDocument::defaultFormatScheme()) return;
	if (!config->realtimeChecking) return; //disable all => implicit disable environment color correction (optimization)
	bool latexLikeChecking = editor->languageDefinition() && checkedLanguages.contains(editor->languageDefinition()->language());
	if (!latexLikeChecking && !config->inlineCheckNonTeXFiles) return;
	
	if (config->inlineGrammarChecking) {
		QList<LineInfo> changedLines;
		int lookBehind = 0;
		for (;linenr - lookBehind >= 0; lookBehind++)
			if (editor->document()->line(linenr - lookBehind).firstChar() == -1) break;
		if (lookBehind > 0) lookBehind--;
		if (lookBehind > linenr) lookBehind = linenr;
		
		LIST_RESERVE(changedLines, linenr+count+lookBehind+1);
		int truefirst = linenr - lookBehind;
		for (int i=linenr - lookBehind; i<editor->document()->lineCount(); i++) {
			QDocumentLine line = editor->document()->line(i);
			if (!line.isValid()) break;
			LineInfo temp;
			temp.line = line.handle();
			temp.text = line.text();
			changedLines << temp;
			if (line.firstChar() == -1) {
				emit linesChanged(speller->name(), document, changedLines, truefirst);
				truefirst += changedLines.size();
				changedLines.clear();
				if (i >= linenr+count) break;
			}
		}
		if (!changedLines.isEmpty())
			emit linesChanged(speller->name(), document, changedLines, truefirst);
		
	}
	
	Q_ASSERT(speller);
	for (int i=linenr; i<linenr+count; i++) {
		QDocumentLine line = editor->document()->line(i);
		if (!line.isValid()) continue;
		
		//remove all overlays used for latex things, in descending frequency
		line.clearOverlays(formatsList); //faster as it avoids multiple lock/unlock operations
		
		bool addedOverlaySpellCheckError = false;
		bool addedOverlayReference = false;
		bool addedOverlayCitation = false;
		bool addedOverlayEnvironment = false;
		bool addedOverlayStructure = false;
		bool addedOverlayPackage = false;
		
		// diff presentation
		QVariant cookie=line.getCookie(QDocumentLine::DIFF_LIST_COOCKIE);
		if(!cookie.isNull()){
			DiffList diffList=cookie.value<DiffList>();
			for(int i=0;i<diffList.size();i++){
				DiffOp op=diffList.at(i);
				switch (op.type){
				case DiffOp::Delete:
					line.addOverlay(QFormatRange(op.start,op.length,deleteFormat));
					break;
				case DiffOp::Insert:
					line.addOverlay(QFormatRange(op.start,op.length,insertFormat));
					break;
				case DiffOp::Replace:
					line.addOverlay(QFormatRange(op.start,op.length,replaceFormat));
					break;
				default:
					;
				}
				
			}
		}
		
		// start syntax checking
		if(latexLikeChecking && config->inlineSyntaxChecking) {
			StackEnvironment env;
			getEnv(i,env);
			QString text=line.text();

			QVector<int>fmts=line.getFormats();
			for(int i=0;i<text.length() && i < fmts.size();i++){
				if(fmts[i]==verbatimFormat || fmts[i]==commentFormat){
					text[i]=QChar(' ');
				}
			}
			SynChecker.putLine(line.handle(),env,false);
		}
		
		
		if (line.length()<=3) continue;
		//if (!config->realtimespellchecking) continue;
		
		
		QString lineText = line.text();
		int status;
		const LatexParser& lp = LatexParser::getInstance();
		LatexReader lr(LatexParser::getInstance(), lineText, mReplacementList);
		while ((status=lr.nextWord(false))){
			if (latexLikeChecking) {
				int format = line.getFormatAt(lr.wordStartIndex);
				if(format==verbatimFormat || format==commentFormat) continue;
				// hack to color the environment given in \begin{environment}...
				if (lp.structureCommandLevel(lr.lastCommand) >= 0){
					//QString secName=extractSectionName(lineText.mid(lr.lr.wordStartIndex),true);
					//line.addOverlay(QFormatRange(lr.wordStartIndex,secName.length(),structureFormat));
					QStringList result;
					QList<int> starts;

					int optStart = lr.wordStartIndex-1;
					QString stopChars = "{["; // find start of option (wordStartIndex is already inside)
					for (; optStart > 0; optStart--) {
						if (stopChars.contains(lineText.at(optStart))) break;
					}
					LatexParser::resolveCommandOptions(lineText,optStart,result,&starts);
					for(int j=0;j<starts.count() && j<2;j++){
						QString text=result.at(j);
						line.addOverlay(QFormatRange(starts.at(j)+1,text.length()-2,structureFormat));
						if(text.startsWith("{")) break;
					}
					addedOverlayStructure = true;
				}
				if (status==LatexReader::NW_ENVIRONMENT) {
					line.addOverlay(QFormatRange(lr.wordStartIndex,lr.index-lr.wordStartIndex,environmentFormat));
					QRegExp rx("[ ]*(\\[.*\\])*\\{.+\\}");
					rx.setMinimal(true);
					int l=rx.indexIn(lineText,lr.index);
					if (l==lr.index+1) lr.index=lr.index+rx.cap(0).length();
					addedOverlayEnvironment = true;
				}
				if (status==LatexReader::NW_REFERENCE && config->inlineReferenceChecking) {
					QString ref=lr.word;//lineText.mid(lr.wordStartIndex,lr.index-lr.wordStartIndex);
					if (ref.contains('#')) continue;  // don't highlight refs in definitions e.g. in \newcommand*{\FigRef}[1]{figure~\ref{#1}}
					int cnt=document->countLabels(ref);
					if(cnt>1) {
						line.addOverlay(QFormatRange(lr.wordStartIndex,lr.index-lr.wordStartIndex,referenceMultipleFormat));
					}else if (cnt==1) line.addOverlay(QFormatRange(lr.wordStartIndex,lr.index-lr.wordStartIndex,referencePresentFormat));
					else line.addOverlay(QFormatRange(lr.wordStartIndex,lr.index-lr.wordStartIndex,referenceMissingFormat));
					addedOverlayReference = true;
				}
				if (status==LatexReader::NW_LABEL && config->inlineReferenceChecking) {
					QString ref=lr.word;//lineText.mid(lr.wordStartIndex,lr.index-lr.wordStartIndex);

					int cnt=document->countLabels(ref);
					if(cnt>1) {
						line.addOverlay(QFormatRange(lr.wordStartIndex,lr.index-lr.wordStartIndex,referenceMultipleFormat));
					}else line.addOverlay(QFormatRange(lr.wordStartIndex,lr.index-lr.wordStartIndex,referencePresentFormat));
					// look for corresponding reeferences and adapt format respectively
					//containedLabels->updateByKeys(QStringList(ref),containedReferences);
					document->updateRefsLabels(ref);
					addedOverlayReference = true;
				}
				if (status==LatexReader::NW_CITATION && config->inlineCitationChecking) {
					if (lr.word.contains('#')) continue;  // don't highlight cite in definitions e.g. in \newcommand*{\MyCite}[1]{see~\cite{#1}}
					QStringList citations=lr.word.split(",");
					int pos=lr.wordStartIndex;
					foreach ( const QString &cit, citations) {
						QString rcit =  trimLeft(cit); // left spaces are ignored by \cite, right space not
						//check and highlight
						if(document->bibIdValid(rcit))
							line.addOverlay(QFormatRange(pos+cit.length()-rcit.length(),rcit.length(),citationPresentFormat));
						else
							line.addOverlay(QFormatRange(pos+cit.length()-rcit.length(),rcit.length(),citationMissingFormat));
						pos+=cit.length()+1;
					}
					addedOverlayCitation = true;
				}
				if (status==LatexReader::NW_PACKAGE && config->inlinePackageChecking) {
					QStringList packages=lr.word.split(",");
					int pos=lr.wordStartIndex;
					QString preambel;
					if(lr.lastCommand.endsWith("theme")){ // special treatment for  \usetheme
						preambel=lr.lastCommand;
						preambel.remove(0,4);
						preambel.prepend("beamer");
					}
					foreach ( const QString &pck, packages) {
						QString rpck =  trimLeft(pck); // left spaces are ignored by \cite, right space not
						//check and highlight
						if (latexPackageList->isEmpty())
							line.addOverlay(QFormatRange(pos+pck.length()-rpck.length(),rpck.length(),packageUndefinedFormat));
						else if(latexPackageList->contains(preambel+rpck))
							line.addOverlay(QFormatRange(pos+pck.length()-rpck.length(),rpck.length(),packagePresentFormat));
						else
							line.addOverlay(QFormatRange(pos+pck.length()-rpck.length(),rpck.length(),packageMissingFormat));
						pos+=pck.length()+1;
					}
					addedOverlayPackage = true;
				}
				if (status==LatexReader::NW_COMMENT) break;
			}
			if (status==LatexReader::NW_TEXT && config->inlineSpellChecking && lr.word.length()>=3 && speller
					&& (!config->hideNonTextSpellingErrors || (!isNonTextFormat(line.getFormatAt(lr.wordStartIndex)) && !isNonTextFormat(line.getFormatAt(lr.index-1)) ))
					&& !speller->check(lr.word) ) {
				if(lr.word.endsWith('-') && speller->check(lr.word.left(lr.word.length()-1)))
					continue; // word ended with '-', without that letter, word is correct (e.g. set-up / german hypehantion)
				if(lr.word.endsWith('.')) lr.index--;
				line.addOverlay(QFormatRange(lr.wordStartIndex,lr.index-lr.wordStartIndex,SpellerUtility::spellcheckErrorFormat));
				addedOverlaySpellCheckError = true;
			}
		}// while
		
		//update wrapping if the an overlay changed the width of the text
		//TODO: should be handled by qce to be consistent (with syntax check and search)
		if (!editor->document()->getFixedPitch() && editor->flag(QEditor::LineWrap)) {
			bool updateWrapping = false;
			QFormatScheme* ff = QDocument::defaultFormatScheme();
			REQUIRE(ff);
			updateWrapping |= addedOverlaySpellCheckError && ff->format(SpellerUtility::spellcheckErrorFormat).widthChanging();
			updateWrapping |= addedOverlayReference && (ff->format(referenceMissingFormat).widthChanging() || ff->format(referencePresentFormat).widthChanging() || ff->format(referenceMultipleFormat).widthChanging());
			updateWrapping |= addedOverlayCitation && (ff->format(citationPresentFormat).widthChanging() || ff->format(citationMissingFormat).widthChanging());
			updateWrapping |= addedOverlayPackage && (ff->format(packagePresentFormat).widthChanging() || ff->format(packageMissingFormat).widthChanging());
			updateWrapping |= addedOverlayEnvironment && ff->format(environmentFormat).widthChanging();
			updateWrapping |= addedOverlayStructure && ff->format(structureFormat).widthChanging();
			if (updateWrapping)
				line.handle()->updateWrapAndNotifyDocument(i);
			
		}
	}
	editor->document()->markViewDirty();
}


void LatexEditorView::lineDeleted(QDocumentLineHandle* l) {
	
	QHash<QDocumentLineHandle*, int>::iterator it;
	while ((it=lineToLogEntries.find(l))!=lineToLogEntries.end()) {
		logEntryToLine.remove(it.value());
		lineToLogEntries.erase(it);
	}
	
	QPair<int, int> p;
	//QMessageBox::information(0,QString::number(nr),"",0);
	for (int i=changePositions.size()-1; i>=0; i--)
		if (changePositions[i].dlh()==l)
			/*if (QDocumentLine(changePositions[i].first).previous().isValid()) changePositions[i].first=QDocumentLine(changePositions[i].first).previous().handle();
   else if (QDocumentLine(changePositions[i].first).next().isValid()) changePositions[i].first=QDocumentLine(changePositions[i].first).next().handle();
   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)
               changePositions.removeAt(i);
     //    QMessageBox::information(0,"trig",0);

     emit lineHandleDeleted(l);
     editor->document()->markViewDirty();
}
void LatexEditorView::spellCheckingReplace() {
	QAction* action = qobject_cast<QAction*>(QObject::sender());
	if (editor && action) {
		if (action->text().isEmpty()) editor->cursor().removeSelectedText();
		else editor->write(action->text());
		editor->setCursor(editor->cursor()); //to remove selection range
	}
}
void LatexEditorView::spellCheckingAlwaysIgnore() {
	if (speller && editor && editor->cursor().hasSelection() && (editor->cursor().selectedText()==defaultInputBinding->lastSpellCheckedWord)) {
		QString newToIgnore = editor->cursor().selectedText();
		speller->addToIgnoreList(newToIgnore);
	}
}
void LatexEditorView::addListToMenu(const QStringList& list, QMenu *menu, bool italic, const char* action){
	if (list.isEmpty()) return;
	if (!menu) return;
	QAction* before=0;
	if (!menu->actions().isEmpty()) before=menu->actions()[0];

	QFont correctionFont;
	correctionFont.setBold(true);
	correctionFont.setItalic(italic);
	foreach (const QString &text, list) {
		QAction* aReplacement=new QAction(text, menu);
		aReplacement->setFont(correctionFont);
		connect(aReplacement, SIGNAL(triggered()), this, action);
		menu->insertAction(before, aReplacement);
	}
}

void LatexEditorView::populateSpellingMenu() {
	QMenu *menu = qobject_cast<QMenu *>(sender());
	if (!menu) return;
	QString word = menu->property("word").toString();
	if (word.isEmpty()) return;
	addSpellingActions(menu, word, true);
}
void LatexEditorView::addSpellingActions(QMenu *menu, QString word, bool dedicatedMenu) {
	if (menu->property("isSpellingPopulated").toBool()) return;

	QStringList suggestions = speller->suggest(word);
	addListToMenu(suggestions, menu, false, SLOT(spellCheckingReplace()));

	QAction* act = new QAction(LatexEditorView::tr("Add to Dictionary"), menu);
	connect(act, SIGNAL(triggered()), this, SLOT(spellCheckingAlwaysIgnore()));
	if (dedicatedMenu) {
		menu->addSeparator();
	} else {
		QFont ignoreFont;
		ignoreFont.setItalic(true);
		act->setFont(ignoreFont);
	}
	menu->addAction(act);
	menu->setProperty("isSpellingPopulated", true);
}
void LatexEditorView::spellRemoveMarkers(const QString& newIgnoredWord){
	REQUIRE(editor);
	//documentContentChanged(editor->cursor().lineNumber(),1);
	for (int i=0; i<editor->document()->lines(); i++) {
		QList<QFormatRange> li=editor->document()->line(i).getOverlays(SpellerUtility::spellcheckErrorFormat);
		QString curLineText=editor->document()->line(i).text();
		for (int j=0; j<li.size(); j++)
			if (latexToPlainWord(curLineText.mid(li[j].offset,li[j].length))==newIgnoredWord){
				editor->document()->line(i).removeOverlay(li[j]);
				editor->document()->line(i).setFlag(QDocumentLine::LayoutDirty,true);
			}
	}
	editor->viewport()->update();
}

void LatexEditorView::closeCompleter(){
	completer->close();
}

void LatexEditorView::reCheckSyntax(int linenr, int count){
	// expensive function ... however if \newcommand is changed valid commands become invalid and vice versa
	if(!config->inlineSyntaxChecking || !config->realtimeChecking) return;
	if(!editor->languageDefinition())
		return;
	if (!checkedLanguages.contains(editor->languageDefinition()->language())) return; // no online checking in other files than tex

	if(linenr<0 || linenr>=editor->document()->lineCount()) linenr=0;
	QDocumentLine line=editor->document()->line(linenr);
	QDocumentLine prev;
	if (linenr > 0) prev = editor->document()->line(linenr - 1);
	int lineNrEnd = count < 0 ? editor->document()->lineCount() : qMin(count + linenr, editor->document()->lineCount());
	for (int i=linenr; i < lineNrEnd; i++) {
		Q_ASSERT(line.isValid());
		StackEnvironment env;
		getEnv(i,env);
		SynChecker.putLine(line.handle(),env,true);
		prev = line;
		line = editor->document()->line(i+1);
	}
}


/*
 * Extracts the math formula at the given cursor position including math delimiters.
 * Current limitations: the cursor needs to be on one of the delimiters. This does
 * not work for math environments
 * Returns an empty string if there is no math formula.
 */
QString LatexEditorView::extractMath(QDocumentCursor cursor) {
	if (cursor.line().getFormatAt(cursor.columnNumber())!=math_DelimiterFormat)
		return QString();
	int col = cursor.columnNumber();
	while (col > 0 && cursor.line().getFormatAt(col-1)==math_DelimiterFormat) col--;
	cursor.setColumnNumber(col);
	return parenthizedTextSelection(cursor).selectedText();
}

bool LatexEditorView::showMathEnvPreview(QDocumentCursor cursor, QString command, QString environment, QPoint pos) {
     QStringList envAliases = document->ltxCommands.environmentAliases.values(environment);
     if (((command=="\\begin" || command=="\\end") && envAliases.contains("math")) || command=="\\[" || command=="\\]") {
          while (!cursor.atLineStart() && cursor.nextChar()!='\\') {
               cursor.movePosition(1, QDocumentCursor::PreviousCharacter);
          }
          QString text = parenthizedTextSelection(cursor).selectedText();
          if (!text.isEmpty()) {
               m_point=editor->mapToGlobal(editor->mapFromFrame(pos));
               emit showPreview(text);
               return true;
          }
     } else {
          QToolTip::hideText();
     }
     return false;
}

void LatexEditorView::mouseHovered(QPoint pos){
	// reimplement to what is necessary
	
	if(pos.x()<0) return;  // hover event on panel
	QDocumentCursor cursor;
	cursor=editor->cursorForPosition(editor->mapToContents(pos));
	QString line=cursor.line().text();
	QDocumentLine l=cursor.line();
	
	QFormatRange fr = cursor.line().getOverlayAt(cursor.columnNumber(),replaceFormat);
	if (fr.length>0 && fr.format==replaceFormat) {
		QVariant var=l.getCookie(QDocumentLine::DIFF_LIST_COOCKIE);
		if(var.isValid()){
			DiffList diffList=var.value<DiffList>();
			DiffOp op;
			op.start=-1;
			foreach(op,diffList){
				if(op.start<=cursor.columnNumber() && op.start+op.length>=cursor.columnNumber()){
					break;
				}
				op.start=-1;
			}
			
			if(op.start>=0 && !op.text.isEmpty()){
				QString message=op.text;
				QToolTip::showText(editor->mapToGlobal(editor->mapFromFrame(pos)),message);
				return;
			}
		}
	}
	foreach (const int f, grammarFormats){
		fr = cursor.line().getOverlayAt(cursor.columnNumber(), f);
		if (fr.length>0 && fr.format==f) {
			QVariant var=l.getCookie(QDocumentLine::GRAMMAR_ERROR_COOKIE);
			if (var.isValid()){
				const QList<GrammarError>& errors = var.value<QList<GrammarError> >();
				for (int i=0;i<errors.size();i++)
					if (errors[i].offset <= cursor.columnNumber() && errors[i].offset+errors[i].length >= cursor.columnNumber()) {
						QString message=errors[i].message;
						QToolTip::showText(editor->mapToGlobal(editor->mapFromFrame(pos)),message);
						return;
					}
			}
		}
	}
	// check for latex error
	//syntax checking
	fr = cursor.line().getOverlayAt(cursor.columnNumber(),syntaxErrorFormat);
	if (fr.length>0 && fr.format==syntaxErrorFormat) {
		StackEnvironment env;
		getEnv(cursor.lineNumber(),env);
		QString text=l.text();
		if(!text.isEmpty()){
			QString message=SynChecker.getErrorAt(l.handle(),cursor.columnNumber(),env);
			QToolTip::showText(editor->mapToGlobal(editor->mapFromFrame(pos)),message);
			return;
		}
	}
	
	// do rest
	QString command, value;
	switch (LatexParser::getInstance().findContext(line, cursor.columnNumber(), command, value)){
	case LatexParser::Unknown:
		if (config->toolTipPreview) {
			QString command = extractMath(cursor);
			if (!command.isEmpty()) {
				m_point = editor->mapToGlobal(editor->mapFromFrame(pos));
				emit showPreview(command);
			} else {
				QToolTip::hideText();
			}
		}
		break;
	case LatexParser::Command:
		// workaround: The latex parser returns "\" as command if the cursor is on the brackets of \[ or \]
		// TODO: should be fixed in the parser itself
		if (command == "\\") {
			if (cursor.nextChar()=='[') command = "\\[";
			else if (cursor.nextChar()==']') command = "\\]";
		}

		if (config->toolTipPreview && showMathEnvPreview(cursor, command, value, pos)) {
			; // action is already performed as a side effect
		} else if (config->toolTipHelp && completer->getLatexReference()) {
			QString topic = completer->getLatexReference()->getTextForTooltip(command);
			if (!topic.isEmpty()) QToolTip::showText(editor->mapToGlobal(editor->mapFromFrame(pos)), topic);
		}
		break;
	case LatexParser::Environment:
		if (config->toolTipPreview && showMathEnvPreview(cursor, command, value, pos)) {
			; // action is already performed as a side effect
		} else if (config->toolTipHelp && completer->getLatexReference()) {
			QString topic = completer->getLatexReference()->getTextForTooltip("\\begin{"+value);
			if (!topic.isEmpty()) QToolTip::showText(editor->mapToGlobal(editor->mapFromFrame(pos)), topic);
		}
		break;
	case LatexParser::Reference:
	{
		//l=editor->document()->findLineContaining("\\label{"+ref+"}",0,Qt::CaseSensitive);
		//QList<QDocumentLineHandle*> lst=containedLabels->values(value);
		int cnt=document->countLabels(value);
		QString mText="";
		if(cnt==0){
			mText=tr("label missing!");
		} else if(cnt>1) {
			mText=tr("label multiple times defined!");
		} else {
			QMultiHash<QDocumentLineHandle*,int> result=document->getLabels(value);
			QDocumentLineHandle *mLine=result.keys().first();
			int l=mLine->document()->indexOf(mLine);
			LatexDocument *doc=qobject_cast<LatexDocument*> (editor->document());
			if(mLine->document()!=editor->document()){
				doc=document->parent->findDocument(mLine->document());
				if(doc) mText=tr("<p style='white-space:pre'><b>Filename: %1</b>\n").arg(doc->getFileName());
			}
			if(doc)
				mText+=doc->exportAsHtml(doc->cursor(qMax(0,l-2), 0, l+2),true,true,60);
			/*for(int i=qMax(0,l-2);i<qMin(mLine->document()->lines(),l+3);i++){
  mText+=mLine->document()->line(i).text();
  if(i<l+2) mText+="\n";
  }*/
          }
          QToolTip::showText(editor->mapToGlobal(editor->mapFromFrame(pos)), mText);
          break;
     }
     case LatexParser::Label:
          if(document->countLabels(value)>1){
               QToolTip::showText(editor->mapToGlobal(editor->mapFromFrame(pos)),tr("label multiple times defined!"));
          } else {
               int cnt=document->countRefs(value);
               QToolTip::showText(editor->mapToGlobal(editor->mapFromFrame(pos)),tr("%n reference(s) to this label","",cnt));
          }
          break;
     case LatexParser::Package:
     {
		  QString type = (command=="\\documentclass") ? tr("Class") : tr("Package");
		  QString preambel;
          if(command.endsWith("theme")){ // special treatment for  \usetheme
               preambel=command;
               preambel.remove(0,4);
               preambel.prepend("beamer");
			   type = tr("Beamer Theme");
			   type.replace(' ', "&nbsp;");
          }
		  QString text = QString("%1:&nbsp;<b>%2</b>").arg(type).arg(value);
		  if(latexPackageList->contains(preambel+value)){
			  QString description = LatexPackages::instance()->shortDescription(value);
			  if (!description.isEmpty()) text += "<br>" + description;
			  QToolTip::showText(editor->mapToGlobal(editor->mapFromFrame(pos)), text);
          } else {
			  text += "<br><b>(" + tr("not found") + ")";
			   QToolTip::showText(editor->mapToGlobal(editor->mapFromFrame(pos)), text);
          }
     }
          break;
     case LatexParser::Citation:;
     case LatexParser::Citation_Ext:
     {
          QString tooltip(tr("Citation correct (reading ...)"));
          QString bibID;
          // get bibID at cursor
          int col_start=cursor.columnNumber();
          if(col_start>=line.length())
               col_start=line.length()-1;
          if (QString("{,}").contains(line[col_start])) break;

          int col_stop=col_start;
          QString eow="{,";
          while(col_start>=0 && !eow.contains(line[col_start]))
               col_start--;
          eow="},";
          while(col_stop<line.length() && !eow.contains(line[col_stop]))
               col_stop++;
          bibID = trimLeft(line.mid(col_start+1,col_stop-col_start-1));

          if(!document->bibIdValid(bibID)) {
               tooltip = "<b>" + tr("Citation missing") + ":</b> " + bibID;

               if (!bibID.isEmpty() && bibID[bibID.length()-1].isSpace()) {
                    tooltip.append("<br><br><i>" + tr("Warning:") +"</i> " +tr("BibTeX ID ends with space. Trailing spaces are not ignored by BibTeX."));
               }
          } else {
               if (document->isBibItem(bibID)) {
                    // by bibitem defined citation
                    tooltip.clear();
                    QMultiHash<QDocumentLineHandle*,int> result=document->getBibItems(bibID);
                    if(result.keys().isEmpty())
                         return;
                    QDocumentLineHandle *mLine=result.keys().first();
                    if(!mLine)
                         return;
                    int l=mLine->document()->indexOf(mLine);
                    LatexDocument *doc=qobject_cast<LatexDocument*> (editor->document());
                    if (mLine->document()!=editor->document()) {
                         doc=document->parent->findDocument(mLine->document());
                         if (doc) tooltip=tr("<p style='white-space:pre'><b>Filename: %1</b>\n").arg(doc->getFileName());
                    }
                    if (doc)
                         tooltip+=doc->exportAsHtml(doc->cursor(l, 0, l+4),true,true,60);
               } else {
                    // read entry in bibtex file
                    if (!bibReader) {
                         bibReader=new bibtexReader(this);
                         connect(bibReader,SIGNAL(sectionFound(QString)),this,SLOT(bibtexSectionFound(QString)));
                         connect(this,SIGNAL(searchBibtexSection(QString,QString)),bibReader,SLOT(searchSection(QString,QString)));
                         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
                    }
                    QString file=document->findFileFromBibId(bibID);
                    lastPos=pos;
                    if(!file.isEmpty())
                         emit searchBibtexSection(file,bibID);
                    return;
               }
          }
          QToolTip::showText(editor->mapToGlobal(editor->mapFromFrame(pos)), tooltip);
     }
          break;
     case LatexParser::Graphics:
          if(config->toolTipPreview){
               QStringList imageExtensions = QStringList() << "" << "png" << "pdf" << "jpg" << "jpeg";
               QString fname;
               QFileInfo fi;
               QStringList imagePaths = ConfigManagerInterface::getInstance()->getOption("Files/Image Paths").toString().split(getPathListSeparator());
               foreach (const QString &ext, imageExtensions) {
                    fname=getDocument()->getAbsoluteFilePath(value, ext, imagePaths);
                    fi.setFile(fname);
                    if (fi.exists()) break;
               }
               if (!fi.exists()) return;
               m_point=editor->mapToGlobal(editor->mapFromFrame(pos));
               emit showImgPreview(fname);
          }
          break;
     default:
          QToolTip::hideText();
     }
     //QToolTip::showText(editor->mapToGlobal(pos), line);
}

bool LatexEditorView::closeSomething(){
	if (completer->close()) return true;
	if (gotoLinePanel->isVisible()) {
		gotoLinePanel->hide();
		editor->setFocus();
		return true;
	}
	if (searchReplacePanel->isVisible()) {
		searchReplacePanel->closeSomething(config->closeSearchAndReplace);
		return true;
	}
	return false;
}

void LatexEditorView::insertHardLineBreaks(int newLength, bool smartScopeSelection, bool joinLines){
	QRegExp breakChars("[ \t\n\r]");
	QDocumentCursor cur=editor->cursor();
	QDocument* doc=editor->document();
	int startLine=0; int endLine=doc->lines()-1;
	
	if (cur.isValid()) {
		if (cur.hasSelection()) {
			startLine=cur.selectionStart().lineNumber();
			endLine=cur.selectionEnd().lineNumber();
			if (cur.selectionEnd().columnNumber()==0 && startLine<endLine) endLine--;
		} else if (smartScopeSelection){
			QDocumentCursor currentCur=cur;
			QDocumentCursor lineCursor=currentCur;
			do {
				QString lineString  = lineCursor.line().text().trimmed();
				if ((lineString == QLatin1String("")) ||
						(lineString.contains("\\begin")) ||
						(lineString.contains("\\end")) ||
						(lineString.contains("$$")) ||
						(lineString.contains("\\[")) ||
						(lineString.contains("\\]"))) {
					//qDebug() << lineString;
					break;
				}
			} while (lineCursor.movePosition(1, QDocumentCursor::Up, QDocumentCursor::MoveAnchor));
			startLine = lineCursor.lineNumber();
			if (lineCursor.atStart()) startLine--;
			
			lineCursor = currentCur;
			do {
				QString lineString  = lineCursor.line().text().trimmed();
				if ((lineString == QLatin1String("")) ||
						(lineString.contains("\\begin")) ||
						(lineString.contains("\\end")) ||
						(lineString.contains("$$")) ||
						(lineString.contains("\\[")) ||
						(lineString.contains("\\]"))) {
					//qDebug() << lineString;
					break;
				}
			} while (lineCursor.movePosition(1, QDocumentCursor::Down, QDocumentCursor::MoveAnchor));
			endLine = lineCursor.lineNumber();
			if (lineCursor.atEnd()) endLine++	;
			
			if ((endLine - startLine) < 2) { // lines near, therefore no need to line break
				return ;
			}
			
			startLine++;
			endLine--;
		}
	}
	if (joinLines) { // start of smart formatting, similar to what emacs (AucTeX) can do, but much simple
		QStringList lines;
		for (int i=startLine; i <= endLine; i++)
			lines << doc->line(i).text();
		lines = joinLinesExceptCommentsAndEmptyLines(lines);
		lines = splitLines(lines, newLength, breakChars);

		QDocumentCursor vCur = doc->cursor(startLine, 0, endLine, doc->line(endLine).length());
		editor->insertText(vCur, lines.join("\n"));
		editor->setCursor(cur);
		return;
	}
	
	bool areThereLinesToBreak=false;
	for (int i=startLine;i<=endLine;i++)
		if (doc->line(i).length()>newLength) {
			areThereLinesToBreak=true;
			break;
		}
	if (!areThereLinesToBreak) return;
	//remove all lines and reinsert them wrapped
	if (endLine+1<doc->lines())
		cur = doc->cursor(startLine,0,endLine+1,0);//+1,0);
	else
		cur = doc->cursor(startLine,0,endLine,doc->line(endLine).length());//+1,0);
	QStringList lines;
	for (int i=startLine;i<=endLine;i++)
		lines<<doc->line(i).text();
	QString insertBlock;
	for (int i=0;i<lines.count();i++){
		QString line=lines[i];
		int commentStart=LatexParser::commentStart(line);
		if (commentStart==-1) commentStart=line.length();
		while (line.length()>newLength) {
			int breakAt=line.lastIndexOf(breakChars,newLength);
			if (breakAt<0) breakAt=line.indexOf(breakChars,newLength);
			if (breakAt<0) break;
			if (breakAt>=commentStart && breakAt+1 > newLength) {
				int newBreakAt=line.indexOf(breakChars,breakAt-1);
				if (newBreakAt >-1) breakAt=newBreakAt;
			}
			insertBlock+=line.left(breakAt)+"\n";
			if (breakAt<commentStart) {
				line=line.mid(breakAt+1);
				commentStart-=breakAt+1;
			} else {
				line="%"+line.mid(breakAt+1);
				commentStart=0;
			}
		}
		insertBlock+=line+"\n";
	}
	editor->insertText(cur,insertBlock);
	
	editor->setCursor(cur);
}

QString LatexEditorViewConfig::translateEditOperation(int key){
	return QEditor::translateEditOperation((QEditor::EditOperation)key);
}

QList<int> LatexEditorViewConfig::possibleEditOperations(){
	int  temp[] = {
		QEditor::NoOperation,
		QEditor::Invalid,

		QEditor::CursorUp,
		QEditor::CursorDown,
		QEditor::CursorLeft,
		QEditor::CursorRight,
		QEditor::CursorWordLeft,
		QEditor::CursorWordRight,
		QEditor::CursorStartOfLine,
		QEditor::CursorEndOfLine,
		QEditor::CursorStartOfDocument,
		QEditor::CursorEndOfDocument,

		QEditor::CursorPageUp,
		QEditor::CursorPageDown,

		QEditor::SelectCursorUp,
		QEditor::SelectCursorDown,
		QEditor::SelectCursorLeft,
		QEditor::SelectCursorRight,
		QEditor::SelectCursorWordLeft,
		QEditor::SelectCursorWordRight,
		QEditor::SelectCursorStartOfLine,
		QEditor::SelectCursorEndOfLine,
		QEditor::SelectCursorStartOfDocument,
		QEditor::SelectCursorEndOfDocument,

		QEditor::SelectPageUp,
		QEditor::SelectPageDown,

		QEditor::EnumForCursorEnd,

		QEditor::DeleteLeft,
		QEditor::DeleteRight,
		QEditor::DeleteLeftWord,
		QEditor::DeleteRightWord,
		QEditor::NewLine,

		QEditor::ChangeOverwrite,
		QEditor::Undo,
		QEditor::Redo,
		QEditor::Copy,
		QEditor::Paste,
		QEditor::Cut,
		QEditor::Print,
		QEditor::SelectAll,
		QEditor::Find,
		QEditor::FindNext,
		QEditor::FindPrevious,
		QEditor::Replace,

		QEditor::CreateMirrorUp,
		QEditor::CreateMirrorDown,
		QEditor::NextPlaceHolder,
		QEditor::PreviousPlaceHolder,
		QEditor::NextPlaceHolderOrWord,
		QEditor::PreviousPlaceHolderOrWord,
        QEditor::TabOrIndentSelection,
		QEditor::IndentSelection,
		QEditor::UnindentSelection};
	QList<int> res;
	int operationCount = (int)(sizeof(temp)/sizeof(int)); //sizeof(array) is possible with c-arrays
	for (int i=0;i<operationCount;i++)
		res << temp[i];
	return res;
}

void LatexEditorView::getEnv(int lineNumber,StackEnvironment &env){
	Environment newEnv;
	newEnv.name="normal";
	newEnv.id=1;
	env.push(newEnv);
	if (lineNumber > 0) {
		QDocumentLine prev = editor->document()->line(lineNumber - 1);
		REQUIRE(prev.isValid());
		QVariant result=prev.getCookie(QDocumentLine::STACK_ENVIRONMENT_COOKIE);
		if(result.isValid())
			env=result.value<StackEnvironment>();
	}
}

/*
 * If the cursor is at the border of a parenthesis, this returns a QDocumentCursor with a selection of the parenthized text.
 * Otherwise, a default QDocumentCursor is returned.
 */
QDocumentCursor LatexEditorView::parenthizedTextSelection(const QDocumentCursor &cursor, bool includeParentheses) {
	QDocumentCursor from, to;
	cursor.getMatchingPair(from, to, includeParentheses);
	if (!from.hasSelection() || !to.hasSelection()) return QDocumentCursor();
	QDocumentCursor::sort(from, to);
	return QDocumentCursor(from.selectionStart(), to.selectionEnd());
}

/*
 * finds the beginning of the specified allowedFormats
 * additional formats can be allowed at the line end (e.g. comments)
 */
QDocumentCursor LatexEditorView::findFormatsBegin(const QDocumentCursor &cursor, QSet<int> allowedFormats, QSet<int> allowedLineEndFormats)
{
	QDocumentCursor c(cursor);
	QVector<int> lineFormats = c.line().getFormats();
	int col = c.columnNumber();
	if ((col > 0 && allowedFormats.contains(lineFormats[col-1]))  // prev char or next char is allowed
        || (col<lineFormats.size() && allowedFormats.contains(lineFormats[col]))
		) {
		while (true) {
			while (col>0 && allowedFormats.contains(lineFormats[col-1])) col--;
			if (col > 0) break;
			// continue on previous line
			c.movePosition(1, QDocumentCursor::PreviousLine);
			c.movePosition(1, QDocumentCursor::EndOfLine);
			lineFormats = c.line().getFormats();
			col = c.columnNumber();
			QString text = c.line().text();
			while (col>0 && (allowedLineEndFormats.contains(lineFormats[col-1]) || text[col-1].isSpace())) col--;

		}
		c.setColumnNumber(col);
		return c;
	}
	return QDocumentCursor();
}

void LatexEditorView::triggeredThesaurus(){
	QAction *act = qobject_cast<QAction*>(sender());
	QPoint pt=act->data().toPoint();
	emit thesaurus(pt.x(),pt.y());
}

void LatexEditorView::changeSpellingDict(const QString &name) {
	QString similarName;
	if (spellerManager->hasSpeller(name)) {
		setSpeller(name);
	} else if (spellerManager->hasSimilarSpeller(name, similarName)) {
		setSpeller(similarName);
	}
}

void LatexEditorView::copyImageFromAction() {
	QAction *act = qobject_cast<QAction *>(sender());
	if (!act) return;

	QPixmap pm = act->data().value<QPixmap>();
	if (!pm.isNull()) {
		QApplication::clipboard()->setImage(pm.toImage());
	}
}

void LatexEditorView::saveImageFromAction() {
	static QString lastSaveDir;

	QAction *act = qobject_cast<QAction *>(sender());
	if (!act) return;

	QPixmap pm = act->data().value<QPixmap>();

	QString fname = QFileDialog::getSaveFileName(this , tr("Save Preview Image"), lastSaveDir, tr("Images")+" (*.png *.jpg *.jpeg)");
	if (fname.isEmpty()) return;

	QFileInfo fi(fname);
	lastSaveDir = fi.absolutePath();
	pm.save(fname);
}


void LatexEditorViewConfig::settingsChanged(){
	if (!hackAutoChoose) { lastFontFamily = ""; return; }
	if (lastFontFamily == fontFamily && lastFontSize == fontSize) return;
	
	QFont f(fontFamily, fontSize);
#if QT_VERSION >= 0x040700
	f.setStyleHint(QFont::Courier, QFont::ForceIntegerMetrics);
#else
	const QFont::StyleStrategy ForceIntegerMetrics = (QFont::StyleStrategy)0x0400;
	f.setStyleHint(QFont::Courier, (hasAtLeastQt(4,7) ? ForceIntegerMetrics : QFont::PreferQuality));
	
#endif
	f.setKerning(false);
	
	QList<QFontMetrics> fms;
	for (int b = 0; b<2;b++) for (int i = 0; i<2;i++) {
		QFont ft(f);
		ft.setBold(b);
		ft.setItalic(i);
		fms << QFontMetrics(ft);
	}
	
	bool lettersHaveDifferentWidth = false, sameLettersHaveDifferentWidth = false;
	int letterWidth = fms.first().width('a');
	
	const QString lettersToCheck("abcdefghijklmnoqrstuvwxyzABCDEFHIJKLMNOQRSTUVWXYZ_+ 123/()=.,;#");
	QVector<QMap<QChar, int> > widths;
	widths.resize(fms.size());

	foreach (const QChar& c, lettersToCheck) {
		for (int fmi = 0; fmi < fms.size(); fmi++) {
			const QFontMetrics& fm = fms[fmi];
			int currentWidth = fm.width(c);
			widths[fmi].insert(c, currentWidth);
			if (currentWidth != letterWidth) lettersHaveDifferentWidth = true;
			QString testString;
			for (int i=1;i<10;i++) {
				testString += c;
				int stringWidth = fm.width(testString);
				if (stringWidth % i != 0) sameLettersHaveDifferentWidth = true;
				if (currentWidth != stringWidth / i) sameLettersHaveDifferentWidth = true;
			}
			if (lettersHaveDifferentWidth && sameLettersHaveDifferentWidth) break;
		}
		if (lettersHaveDifferentWidth && sameLettersHaveDifferentWidth) break;
	}
	const QString ligatures[2] = {"aftt", "afit"}; 
	for (int l=0;l<2 && !sameLettersHaveDifferentWidth;l++) {
		for (int fmi = 0; fmi < fms.size(); fmi++) {
			int expectedWidth = 0;
			for (int i=0;i<ligatures[l].size() && !sameLettersHaveDifferentWidth;i++){
				expectedWidth += widths[fmi].value(ligatures[l][i]);
				if (expectedWidth != fms[fmi].width(ligatures[l].left(i+1))) sameLettersHaveDifferentWidth = true;
			}
		}
	}

	
	if (!QFontInfo(f).fixedPitch()) hackDisableFixedPitch = false; //won't be enabled anyways
	else hackDisableFixedPitch = lettersHaveDifferentWidth || sameLettersHaveDifferentWidth;
	hackDisableWidthCache = sameLettersHaveDifferentWidth;

	hackDisableLineCache = isRetinaMac();
	hackRenderingMode = 0; //always use qce, best tested
	
	lastFontFamily = fontFamily;
	lastFontSize = fontSize;
}



QString BracketInvertAffector::affect(const QKeyEvent *, const QString& base, int, int) const{
	static const QString& brackets = "<>()[]{}";
	QString after;
	for (int i=0; i < base.length(); i++)
		if (brackets.indexOf(base[i]) >= 0)
			after += brackets[brackets.indexOf(base[i]) + 1 - 2*(brackets.indexOf(base[i]) & 1) ];
		else if (base[i] == '\\') {
			if (base.mid(i, 7) == "\\begin{") {
				after += "\\end{" + base.mid(i+7);
				return after;
			} else if (base.mid(i, 5) == "\\end{") {
				after += "\\begin{" + base.mid(i+5);
				return after;
			} else if (base.mid(i, 5) == "\\left") {
				after += "\\right";
				i+=4;
			} else if (base.mid(i, 6) == "\\right") {
				after += "\\left";
				i+=5;
			}
			else after += '\\';
		} else after += base[i];
	return after;
}
BracketInvertAffector* inverterSingleton = 0;
BracketInvertAffector* BracketInvertAffector::instance(){
	if (!inverterSingleton) inverterSingleton = new BracketInvertAffector();
	return inverterSingleton;
}

void LatexEditorView::bibtexSectionFound(QString content){
	QToolTip::showText(editor->mapToGlobal(editor->mapFromFrame(lastPos)), content);
}

void LatexEditorView::lineMarkContextMenuRequested(int lineNumber, QPoint globalPos) {
	if (!document) return;

	QDocumentLine line(document->line(lineNumber));
	QMenu menu(this);

	for (int i=-1; i<10; i++) {
		int rmid=bookMarkId(i);
		if(line.hasMark(rmid)){
			QAction *act =  new QAction(tr("Remove Bookmark"), &menu);
			act->setData(-2);
			menu.addAction(act);
			menu.addSeparator();
			break;
		}
	}

	QAction *act = new QAction(getRealIconCached("lbook"),tr("Unnamed Bookmark"), &menu);
	act->setData(-1);
	menu.addAction(act);

	for (int i=0; i<10; i++) {
		QAction *act = new QAction(getRealIconCached(QString("lbook%1").arg(i)), tr("Bookmark")+QString(" %1").arg(i), &menu);
		act->setData(i);
		menu.addAction(act);
	}

	act = menu.exec(globalPos);
	if (act) {
		int bookmarkNumber = act->data().toInt();
		if (bookmarkNumber == -2) {
			for (int i=-1; i<10; i++) {
				int rmid=bookMarkId(i);
				if(line.hasMark(rmid)){
					removeBookmark(line.handle(), i);
					return;
				}
			}
		} else {
			toggleBookmark(bookmarkNumber, line);
		}
	}
}

void LatexEditorView::foldContextMenuRequested(int lineNumber, QPoint globalPos) {
	Q_UNUSED(lineNumber);

	QMenu menu;
	QAction *act = new QAction(tr("Collapse All"), &menu);
	act->setData(-5);
	menu.addAction(act);
	for (int i=1; i<=4; i++) {
		act = new QAction(QString(tr("Collapse Level %1")).arg(i), &menu);
		act->setData(-i);
		menu.addAction(act);
	}
	menu.addSeparator();
	act = new QAction(tr("Expand All"), &menu);
	act->setData(5);
	menu.addAction(act);
	for (int i=1; i<=4; i++) {
		act = new QAction(QString(tr("Expand Level %1")).arg(i), &menu);
		act->setData(i);
		menu.addAction(act);
	}

	act = menu.exec(globalPos);
	if (act) {
		int level = act->data().toInt();
		if (qAbs(level)<5) {
			foldLevel(level>0, qAbs(level));
		} else {
			foldEverything(level>0);
		}
	}
}

LinkOverlay::LinkOverlay(const LinkOverlay &o) {
	type = o.type;
	if (o.isValid()) {
		docLine = o.docLine;
		context = o.context;
		formatRange = o.formatRange;
	}
}

LinkOverlay::LinkOverlay(const QDocumentCursor &cur, LatexParser::ContextType ctx, LinkOverlay::LinkOverlayType ltype) :
	type(ltype), context(ctx)
{
	if (type == Invalid) return;

	int from, to;

	if (type == UsepackageOverlay || type == BibFileOverlay || type == CiteOverlay) {
		// link one of the colon separated options
		QDocumentCursor c(cur);
		LatexEditorView::selectOptionInLatexArg(c);
		from = c.anchorColumnNumber();
		to = c.columnNumber()-1;
		if (from<0 || to<0 || to<=from)
			return;
	} else {
		// link everything between {}
		QString text = cur.line().text();
		int col = cur.columnNumber();
		from = findOpeningBracket(text, col);
		to = findClosingBracket(text, col);
		if (from<0 || to<0)
			return;
		from +=1; to -= 1; // leave out brackets
	}

	REQUIRE(QDocument::defaultFormatScheme());
	formatRange = QFormatRange(from, to-from+1, QDocument::defaultFormatScheme()->id("link"));
	docLine = cur.line();
}

QString LinkOverlay::text() const {
	if (!isValid()) return QString();
	return docLine.text().mid(formatRange.offset, formatRange.length);
}

QString LatexEditorView::getSearchText(){
	return searchReplacePanel->getSearchText();
}

QString LatexEditorView::getReplaceText(){
	return searchReplacePanel->getReplaceText();
}

bool LatexEditorView::getSearchIsWords(){
	return searchReplacePanel->getSearchIsWords();
}

bool LatexEditorView::getSearchIsCase(){
    return searchReplacePanel->getSearchIsCase();
}

bool LatexEditorView::getSearchIsRegExp(){
    return searchReplacePanel->getSearchIsRegExp();
}



