1 /***************************************************************************
2  *   copyright       : (C) 2003-2008 by Pascal Brachet                     *
3  *   addons by Luis Silvestre                                              *
4  *   http://www.xm1math.net/texmaker/                                      *
5  *                                                                         *
6  *   This program is free software; you can redistribute it and/or modify  *
7  *   it under the terms of the GNU General Public License as published by  *
8  *   the Free Software Foundation  either version 2 of the License, or     *
9  *   (at your option) any later version.                                   *
10  *                                                                         *
11  ***************************************************************************/
12 //#include <stdlib.h>
13 //#include "/usr/include/valgrind/callgrind.h"
14 
15 #include "texstudio.h"
16 #include "latexeditorview.h"
17 
18 #include "smallUsefulFunctions.h"
19 
20 #include "cleandialog.h"
21 
22 #include "debughelper.h"
23 #include "debuglogger.h"
24 
25 #include "dblclickmenubar.h"
26 #include "filechooser.h"
27 #include "filedialog.h"
28 #include "findindirs.h"
29 #include "tabdialog.h"
30 #include "arraydialog.h"
31 #include "bibtexdialog.h"
32 #include "tabbingdialog.h"
33 #include "letterdialog.h"
34 #include "quickdocumentdialog.h"
35 #include "quickbeamerdialog.h"
36 #include "mathassistant.h"
37 #include "maketemplatedialog.h"
38 #include "templateselector.h"
39 #include "templatemanager.h"
40 #include "usermenudialog.h"
41 #include "aboutdialog.h"
42 #include "encodingdialog.h"
43 #include "encoding.h"
44 #include "randomtextgenerator.h"
45 #include "webpublishdialog.h"
46 #include "thesaurusdialog.h"
47 #include "qsearchreplacepanel.h"
48 #include "latexcompleter_config.h"
49 #include "universalinputdialog.h"
50 #include "insertgraphics.h"
51 #include "latexeditorview_config.h"
52 #include "scriptengine.h"
53 #include "grammarcheck.h"
54 #include "qmetautils.h"
55 #include "updatechecker.h"
56 #include "session.h"
57 #include "searchquery.h"
58 #include "fileselector.h"
59 #include "utilsUI.h"
60 #include "utilsSystem.h"
61 #include "minisplitter.h"
62 #include "latexpackage.h"
63 #include "latexparser/argumentlist.h"
64 #include "latexparser/latextokens.h"
65 #include "latexparser/latexparser.h"
66 #include "latexparser/latexparsing.h"
67 #include "latexstructure.h"
68 #include "symbollistmodel.h"
69 #include "symbolwidget.h"
70 #include "execprogram.h"
71 
72 #include <QScreen>
73 
74 #ifndef QT_NO_DEBUG
75 #include "tests/testmanager.h"
76 #endif
77 
78 #include "qdocument.h"
79 #include "qdocumentcursor.h"
80 #include "qdocumentline.h"
81 #include "qdocumentline_p.h"
82 
83 #include "qnfadefinition.h"
84 
85 #include "PDFDocument_config.h"
86 #include <set>
87 
88 #ifdef Q_OS_WIN
89 #include <windows.h>
90 #endif
91 
92 /*! \file texstudio.cpp
93  * contains the GUI definition as well as some helper functions
94  */
95 
96 /*!
97     \defgroup txs Mainwindow
98 	\ingroup txs
99 	@{
100 */
101 
102 /*! \class Texstudio
103  * This class sets up the GUI and handles the GUI interaction (menus and toolbar).
104  * It uses QEditor with LatexDocument as actual text editor and PDFDocument for viewing pdf.
105  *
106  * \see QEditor
107  * \see LatexDocument
108  * \see PDFDocument
109  */
110 
111 
112 
113 const QString APPICON(":appicon");
114 
115 bool programStopped = false;
116 Texstudio *txsInstance = nullptr;
117 QCache<QString, QIcon> iconCache;
118 
119 // workaround needed on OSX due to https://bugreports.qt.io/browse/QTBUG-49576
hideSplash()120 void hideSplash()
121 {
122 #ifdef Q_OS_MAC
123 	if (txsInstance)
124 		txsInstance->hideSplash();
125 #endif
126 }
127 /*!
128  * \brief constructor
129  *
130  * set-up GUI
131  *
132  * \param parent
133  * \param flags
134  * \param splash
135  */
Texstudio(QWidget * parent,Qt::WindowFlags flags,QSplashScreen * splash)136 Texstudio::Texstudio(QWidget *parent, Qt::WindowFlags flags, QSplashScreen *splash)
137         : QMainWindow(parent, flags), textAnalysisDlg(nullptr), spellDlg(nullptr), mDontScrollToItem(false), runBibliographyIfNecessaryEntered(false)
138 {
139 
140 	splashscreen = splash;
141 	programStopped = false;
142 	spellLanguageActions = nullptr;
143 	currentLine = -1;
144 	svndlg = nullptr;
145 	userMacroDialog = nullptr;
146 	mCompleterNeedsUpdate = false;
147 	latexStyleParser = nullptr;
148 	packageListReader = nullptr;
149 	bibtexEntryActions = nullptr;
150 	biblatexEntryActions = nullptr;
151 	bibTypeActions = nullptr;
152 	highlightLanguageActions = nullptr;
153 	runningPDFCommands = runningPDFAsyncCommands = 0;
154 	previewEditorPending = nullptr;
155 	previewIsAutoCompiling = false;
156 	completerPreview = false;
157 	recheckLabels = true;
158 	cursorHistory = nullptr;
159 	recentSessionList = nullptr;
160 	editors = nullptr;
161 	m_languages = nullptr; //initial state to avoid crash on OSX
162     currentSection=nullptr;
163 
164 	connect(&buildManager, SIGNAL(hideSplash()), this, SLOT(hideSplash()));
165 
166 	readSettings();
167 
168 #ifdef Q_OS_WIN
169     // work-around for ´+t bug
170     QCoreApplication::instance()->installEventFilter(this);
171 #endif
172 
173 	latexReference = new LatexReference();
174 	latexReference->setFile(findResourceFile("latex2e.html"));
175 
176 	qRegisterMetaType<QSet<QString> >();
177     qRegisterMetaType<std::set<QString> >();
178 
179 	txsInstance = this;
180 	static int crashHandlerType = 1;
181 	configManager.registerOption("Crash Handler Type", &crashHandlerType, 1);
182 
183 	initCrashHandler(crashHandlerType);
184 
185 	QTimer *t  = new QTimer(this);
186 	connect(t, SIGNAL(timeout()), SLOT(iamalive()));
187 	t->start(9500);
188 
189 	setCorner(Qt::TopLeftCorner, Qt::LeftDockWidgetArea);
190 	setCorner(Qt::TopRightCorner, Qt::RightDockWidgetArea);
191 	setCorner(Qt::BottomLeftCorner, Qt::LeftDockWidgetArea);
192 	setCorner(Qt::BottomRightCorner, Qt::RightDockWidgetArea);
193 
194 	QFile styleSheetFile(configManager.configBaseDir + "stylesheet.qss");
195 	if (styleSheetFile.exists()) {
196         if(styleSheetFile.open(QFile::ReadOnly)){
197             setStyleSheet(styleSheetFile.readAll());
198             styleSheetFile.close();
199         }
200 	}
201 
202     // dpi aware icon scaling
203     // screen dpi is read and the icon are scaled up in reference to 96 dpi
204     // this should be helpful on X11 (Xresouces) and possibly windows
205     double dpi=QGuiApplication::primaryScreen()->logicalDotsPerInch();
206     double scale=dpi/96;
207 
208 	setWindowIcon(QIcon(":/images/logo128.png"));
209 
210     int iconSize = qRound(qMax(16, configManager.guiToolbarIconSize)*scale);
211 	setIconSize(QSize(iconSize, iconSize));
212 
213 	leftPanel = nullptr;
214 	sidePanel = nullptr;
215     //structureTreeView = nullptr;
216     structureTreeWidget = nullptr;
217     topTOCTreeWidget = nullptr;
218 	outputView = nullptr;
219 
220 	qRegisterMetaType<LatexParser>();
221 	latexParser.importCwlAliases(findResourceFile("completion/cwlAliases.dat"));
222 
223 	m_formatsOldDefault = QDocument::defaultFormatScheme();
224 	QDocument::setDefaultFormatScheme(m_formats);
225 	SpellerUtility::spellcheckErrorFormat = m_formats->id("spellingMistake");
226 
227 	qRegisterMetaType<QList<LineInfo> >();
228 	qRegisterMetaType<QList<GrammarError> >();
229 	qRegisterMetaType<LatexParser>();
230 	qRegisterMetaType<GrammarCheckerConfig>();
231     qRegisterMetaType<QDocumentLineHandle*>();
232 
233 	grammarCheck = new GrammarCheck();
234 	grammarCheck->moveToThread(&grammarCheckThread);
235 	GrammarCheck::staticMetaObject.invokeMethod(grammarCheck, "init", Qt::QueuedConnection, Q_ARG(LatexParser, latexParser), Q_ARG(GrammarCheckerConfig, *configManager.grammarCheckerConfig));
236     //connect(grammarCheck, SIGNAL(checked(LatexDocument*,QDocumentLineHandle*,int,QList<GrammarError>)), &documents, SLOT(lineGrammarChecked(LatexDocument*,QDocumentLineHandle*,int,QList<GrammarError>)));
237     connect(grammarCheck, &GrammarCheck::checked, &documents, &LatexDocuments::lineGrammarChecked);
238     connect(grammarCheck, SIGNAL(errorMessage(QString)),this,SLOT(LTErrorMessage(QString)));
239 	if (configManager.autoLoadChildren)
240 		connect(&documents, SIGNAL(docToLoad(QString)), this, SLOT(addDocToLoad(QString)));
241 	connect(&documents, SIGNAL(updateQNFA()), this, SLOT(updateTexQNFA()));
242 
243 	grammarCheckThread.start();
244 
245 	if (configManager.autoDetectEncodingFromLatex || configManager.autoDetectEncodingFromChars) QDocument::setDefaultCodec(nullptr);
246 	else QDocument::setDefaultCodec(configManager.newFileEncoding);
247 	if (configManager.autoDetectEncodingFromLatex)
248 		QDocument::addGuessEncodingCallback(&Encoding::guessEncoding); // encodingcallbacks before restoer session !!!
249 	if (configManager.autoDetectEncodingFromChars)
250 		QDocument::addGuessEncodingCallback(&ConfigManager::getDefaultEncoding);
251 
252 	QString qxsPath = QFileInfo(findResourceFile("qxs/tex.qnfa")).path();
253 	m_languages = new QLanguageFactory(m_formats, this);
254 	m_languages->addDefinitionPath(qxsPath);
255 	m_languages->addDefinitionPath(configManager.configBaseDir + "languages");  // definitions here overwrite previous ones
256 
257 	// custom evironments & structure commands
258 	updateTexQNFA();
259 
260 	QLineMarksInfoCenter::instance()->loadMarkTypes(qxsPath + "/marks.qxm");
261 	QList<QLineMarkType> &marks = QLineMarksInfoCenter::instance()->markTypes();
262 	for (int i = 0; i < marks.size(); i++)
263 		if (m_formats->format("line:" + marks[i].id).background.isValid())
264 			marks[i].color = m_formats->format("line:" + marks[i].id).background;
265 		else
266 			marks[i].color = Qt::transparent;
267 
268 	LatexEditorView::updateFormatSettings();
269 
270 	// TAB WIDGET EDITEUR
271 	documents.indentationInStructure = configManager.indentationInStructure;
272 	documents.showCommentedElementsInStructure = configManager.showCommentedElementsInStructure;
273 	documents.indentIncludesInStructure = configManager.indentIncludesInStructure;
274 	documents.markStructureElementsBeyondEnd = configManager.markStructureElementsBeyondEnd;
275 	documents.markStructureElementsInAppendix = configManager.markStructureElementsInAppendix;
276 	documents.showLineNumbersInStructure = configManager.showLineNumbersInStructure;
277     connect(&documents, SIGNAL(masterDocumentChanged(LatexDocument*)), SLOT(masterDocumentChanged(LatexDocument*)));
278     connect(&documents, SIGNAL(aboutToDeleteDocument(LatexDocument*)), SLOT(aboutToDeleteDocument(LatexDocument*)));
279 
280 	centralFrame = new QFrame(this);
281 	centralFrame->setLineWidth(0);
282 	centralFrame->setFrameShape(QFrame::NoFrame);
283 	centralFrame->setFrameShadow(QFrame::Plain);
284 
285 
286 
287 	//edit
288 	centralToolBar = new QToolBar(tr("Central"), this);
289 	centralToolBar->setFloatable(false);
290 	centralToolBar->setOrientation(Qt::Vertical);
291 	centralToolBar->setMovable(false);
292 	iconSize = qRound(configManager.guiSecondaryToolbarIconSize*scale);
293 	centralToolBar->setIconSize(QSize(iconSize, iconSize));
294 
295 	editors = new Editors(centralFrame);
296 	editors->setFocus();
297 
298 	TxsTabWidget *leftEditors = new TxsTabWidget(this);
299 	leftEditors->setActive(true);
300 	editors->addTabWidget(leftEditors);
301 	TxsTabWidget *rightEditors = new TxsTabWidget(this);
302 	editors->addTabWidget(rightEditors);
303 
304     connect(&documents, SIGNAL(docToHide(LatexEditorView*)), editors, SLOT(removeEditor(LatexEditorView*)));
305 	connect(editors, SIGNAL(currentEditorChanged()), SLOT(currentEditorChanged()));
306 	connect(editors, SIGNAL(listOfEditorsChanged()), SLOT(updateOpenDocumentMenu()));
307 	connect(editors, SIGNAL(editorsReordered()), SLOT(onEditorsReordered()));
308 	connect(editors, SIGNAL(closeCurrentEditorRequested()), this, SLOT(fileClose()));
309     connect(editors, SIGNAL(editorAboutToChangeByTabClick(LatexEditorView*,LatexEditorView*)), this, SLOT(editorAboutToChangeByTabClick(LatexEditorView*,LatexEditorView*)));
310 
311 	cursorHistory = new CursorHistory(&documents);
312 	bookmarks = new Bookmarks(&documents, this);
313 
314 	QLayout *centralLayout = new QHBoxLayout(centralFrame);
315 	centralLayout->setSpacing(0);
316 #if QT_VERSION < QT_VERSION_CHECK(6,0,0)
317     centralLayout->setMargin(0);
318 #else
319     centralLayout->setContentsMargins(0,0,0,0);
320 #endif
321 	centralLayout->addWidget(centralToolBar);
322 	centralLayout->addWidget(editors);
323 
324 	centralVSplitter = new MiniSplitter(Qt::Vertical, this);
325 	centralVSplitter->setChildrenCollapsible(false);
326 	centralVSplitter->addWidget(centralFrame);
327 	centralVSplitter->setStretchFactor(0, 1);  // all stretch goes to the editor (0th widget)
328 
329 	sidePanelSplitter = new MiniSplitter(Qt::Horizontal, this);
330 	sidePanelSplitter->addWidget(centralVSplitter);
331 
332 	mainHSplitter = new MiniSplitter(Qt::Horizontal, this);  // top-level element: splits: [ everything else | PDF ]
333 	mainHSplitter->addWidget(sidePanelSplitter);
334 	mainHSplitter->setChildrenCollapsible(false);
335 	setCentralWidget(mainHSplitter);
336 
337 	setContextMenuPolicy(Qt::ActionsContextMenu);
338 
339 	setupDockWidgets();
340 
341 	setMenuBar(new DblClickMenuBar());
342 	setupMenus();
343 	TitledPanelPage *logPage = outputView->pageFromId(outputView->LOG_PAGE);
344 	if (logPage) {
345 		logPage->addToolbarAction(getManagedAction("main/tools/logmarkers"));
346 		logPage->addToolbarAction(getManagedAction("main/edit2/goto/errorprev"));
347 		logPage->addToolbarAction(getManagedAction("main/edit2/goto/errornext"));
348 	}
349 
350 	setupToolBars();
351 	connect(&configManager, SIGNAL(watchedMenuChanged(QString)), SLOT(updateToolBarMenu(QString)));
352 
353 	restoreState(windowstate, 0);
354 	//workaround as toolbar central seems not be be handled by windowstate
355 	centralToolBar->setVisible(configManager.centralVisible);
356 
357 	createStatusBar();
358 	completer = nullptr;
359 	updateCaption();
360 	updateMasterDocumentCaption();
361 	setStatusMessageProcess(QString(" %1 ").arg(tr("Ready")));
362 
363 	if (tobefullscreen) {
364 		showFullScreen();
365 		restoreState(stateFullScreen, 1);
366 		fullscreenModeAction->setChecked(true);
367 	} else if (tobemaximized) {
368 #ifdef Q_OS_WIN
369 		// Workaround a Qt/Windows bug which prevents too small windows from maximizing
370 		// For more details see:
371 		// https://stackoverflow.com/questions/27157312/qt-showmaximized-not-working-in-windows
372 		// https://bugreports.qt.io/browse/QTBUG-77077
373 		resize(800, 600);
374 #endif
375 		showMaximized();
376 	} else {
377 		show();
378 	}
379 	if (splash)
380 		splash->raise();
381 
382 	setAcceptDrops(true);
383 	//installEventFilter(this);
384 
385     completer = new LatexCompleter(latexParser, this);
386     completer->setConfig(configManager.completerConfig);
387     completer->setPackageList(&latexPackageList);
388     connect(completer, &LatexCompleter::showImagePreview, this, &Texstudio::showImgPreview);
389     connect(completer, SIGNAL(showPreview(QString)), this, SLOT(showPreview(QString)));
390     connect(this, &Texstudio::imgPreview, completer, &LatexCompleter::bibtexSectionFound);
391     //updateCompleter();
392     LatexEditorView::setCompleter(completer);
393     completer->setLatexReference(latexReference);
394     completer->updateAbbreviations();
395 
396 	TemplateManager::setConfigBaseDir(configManager.configBaseDir);
397 	TemplateManager::ensureUserTemplateDirExists();
398 	TemplateManager::checkForOldUserTemplates();
399 
400 	/* The encoding detection works as follow:
401 		If QDocument detects the file is UTF16LE/BE, use that encoding
402 		Else If QDocument detects UTF-8 {
403 	If LatexParser::guessEncoding finds an encoding, use that
404 	Else use UTF-8
405 	} Else {
406 	  If LatexParser::guessEncoding finds an encoding use that
407 	  Else if QDocument detects ascii (only 7bit characters) {
408 	if default encoding == utf16: use utf-8 as fallback (because utf16 can be reliable detected and the user seems to like unicode)
409 	else use default encoding
410 	  }
411 	  Else {
412 	if default encoding == utf16/8: use latin1 (because the file contains invalid unicode characters )
413 	else use default encoding
414 	  }
415 	}
416 
417 	*/
418 
419     connect(&svn, &SVN::statusMessage, this, &Texstudio::setStatusMessageProcess);
420     connect(&svn, SIGNAL(runCommand(QString,QString*)), this, SLOT(runCommandNoSpecialChars(QString,QString*)));
421     connect(&git, &GIT::statusMessage, this, &Texstudio::setStatusMessageProcess);
422     connect(&git, SIGNAL(runCommand(QString,QString*)), this, SLOT(runCommandNoSpecialChars(QString,QString*)));
423 
424     connect(&help, &Help::statusMessage, this, &Texstudio::setStatusMessageProcess);
425     connect(&help, SIGNAL(runCommand(QString,QString*)), this, SLOT(runCommandNoSpecialChars(QString,QString*)));
426     connect(&help, SIGNAL(runCommandAsync(QString,const char*)), this, SLOT(runCommandAsync(QString,const char*)));
427 
428     connect(static_cast<QGuiApplication *>(QGuiApplication::instance()),&QGuiApplication::paletteChanged,this,&Texstudio::paletteChanged);
429 
430 	QStringList filters;
431 	filters << tr("TeX files") + " (*.tex *.bib *.sty *.cls *.mp *.dtx *.cfg *.ins *.ltx *.tikz *.pdf_tex *.ctx)";
432 	filters << tr("LilyPond files") + " (*.lytex)";
433 	filters << tr("Plaintext files") + " (*.txt)";
434 	filters << tr("Pweave files") + " (*.Pnw)";
435 	filters << tr("Sweave files") + " (*.Snw *.Rnw)";
436 	filters << tr("Asymptote files") + " (*.asy)";
437 	filters << tr("PDF files") + " (*.pdf)";
438 	filters << tr("All files") + " (*)";
439 	fileFilters = filters.join(";;");
440 	if (!configManager.rememberFileFilter)
441 		selectedFileFilter = filters.first();
442 
443     enlargedViewer=false;
444 
445 	//setup autosave timer
446     connect(&autosaveTimer, SIGNAL(timeout()), this, SLOT(fileSaveAllFromTimer()));
447 	if (configManager.autosaveEveryMinutes > 0) {
448 		autosaveTimer.start(configManager.autosaveEveryMinutes * 1000 * 60);
449 	}
450 	connect(&previewDelayTimer,SIGNAL(timeout()),this,SLOT(showPreviewQueue()));
451 	previewDelayTimer.setSingleShot(true);
452 	connect(&previewFullCompileDelayTimer,SIGNAL(timeout()),this,SLOT(recompileForPreviewNow()));
453 	previewFullCompileDelayTimer.setSingleShot(true);
454 
455     connect(this, SIGNAL(infoFileSaved(QString,int)), this, SLOT(checkinAfterSave(QString,int)));
456 
457 	//script things
458 	setProperty("applicationName", TEXSTUDIO);
459 	QTimer::singleShot(500, this, SLOT(autoRunScripts()));
460 	connectWithAdditionalArguments(this, SIGNAL(infoNewFile()), this, "runScripts", QList<QVariant>() << Macro::ST_NEW_FILE);
461 	connectWithAdditionalArguments(this, SIGNAL(infoNewFromTemplate()), this, "runScripts", QList<QVariant>() << Macro::ST_NEW_FROM_TEMPLATE);
462 	connectWithAdditionalArguments(this, SIGNAL(infoLoadFile(QString)), this, "runScripts", QList<QVariant>() << Macro::ST_LOAD_FILE);
463 	connectWithAdditionalArguments(this, SIGNAL(infoFileSaved(QString)), this, "runScripts", QList<QVariant>() << Macro::ST_FILE_SAVED);
464 	connectWithAdditionalArguments(this, SIGNAL(infoFileClosed()), this, "runScripts", QList<QVariant>() << Macro::ST_FILE_CLOSED);
465 	connectWithAdditionalArguments(&documents, SIGNAL(masterDocumentChanged(LatexDocument *)), this, "runScripts", QList<QVariant>() << Macro::ST_MASTER_CHANGED);
466 	connectWithAdditionalArguments(this, SIGNAL(infoAfterTypeset()), this, "runScripts", QList<QVariant>() << Macro::ST_AFTER_TYPESET);
467 	connectWithAdditionalArguments(&buildManager, SIGNAL(endRunningCommands(QString, bool, bool, bool)), this, "runScripts", QList<QVariant>() << Macro::ST_AFTER_COMMAND_RUN);
468 
469 	if (configManager.sessionRestore && !ConfigManager::dontRestoreSession) {
470 		fileRestoreSession(false, false);
471 	}
472 	splashscreen = nullptr;
473 }
474 /*!
475  * \brief destructor
476  */
~Texstudio()477 Texstudio::~Texstudio()
478 {
479     //structureTreeView->setModel(nullptr);
480 	iconCache.clear();
481 	QDocument::setDefaultFormatScheme(m_formatsOldDefault); //prevents crash when deleted latexeditorview accesses the default format scheme, as m_format is going to be deleted
482 
483 	programStopped = true;
484 
485 	Guardian::shutdown();
486 
487 	if (latexStyleParser) latexStyleParser->stop();
488 	if (packageListReader) packageListReader->stop();
489 	GrammarCheck::staticMetaObject.invokeMethod(grammarCheck, "shutdown", Qt::BlockingQueuedConnection);
490 	grammarCheckThread.quit();
491 
492 	if (latexStyleParser) latexStyleParser->wait();
493 	if (packageListReader) packageListReader->wait();
494 	grammarCheckThread.wait(5000); //TODO: timeout causes sigsegv, is there any better solution?
495 }
496 
497 /*!
498  * \brief code to be executed at end of start-up
499  *
500  * Check for Latex installation.
501  * Read in all package names for usepackage completion.
502  */
startupCompleted()503 void Texstudio::startupCompleted()
504 {
505 	if (configManager.checkLatexConfiguration) {
506 		bool noWarnAgain = false;
507 		buildManager.checkLatexConfiguration(noWarnAgain);
508 		configManager.checkLatexConfiguration = !noWarnAgain;
509 	}
510 	UpdateChecker::instance()->autoCheck();
511 	// package reading (at least with Miktex) apparently slows down the startup
512 	// the first rendering of lines in QDocumentPrivate::draw() gets very slow
513 	// therefore we defer it until the main window is completely loaded
514 	readinAllPackageNames(); // asynchrnous read in of all available sty/cls
515 }
516 
newManagedAction(QWidget * menu,const QString & id,const QString & text,const char * slotName,const QKeySequence & shortCut,const QString & iconFile,const QList<QVariant> & args)517 QAction *Texstudio::newManagedAction(QWidget *menu, const QString &id, const QString &text, const char *slotName, const QKeySequence &shortCut, const QString &iconFile, const QList<QVariant> &args)
518 {
519 	QAction *tmp = configManager.newManagedAction(menu, id, text, args.isEmpty() ? slotName : SLOT(relayToOwnSlot()), QList<QKeySequence>() << shortCut, iconFile);
520 	if (!args.isEmpty()) {
521 		QString slot = QString(slotName).left(QString(slotName).indexOf("("));
522 		Q_ASSERT(staticMetaObject.indexOfSlot(createMethodSignature(qPrintable(slot), args)) != -1);
523 		tmp->setProperty("slot", qPrintable(slot));
524 		tmp->setProperty("args", QVariant::fromValue<QList<QVariant> >(args));
525 	}
526 	return tmp;
527 }
528 
newManagedAction(QWidget * menu,const QString & id,const QString & text,const char * slotName,const QList<QKeySequence> & shortCuts,const QString & iconFile,const QList<QVariant> & args)529 QAction *Texstudio::newManagedAction(QWidget *menu, const QString &id, const QString &text, const char *slotName, const QList<QKeySequence> &shortCuts, const QString &iconFile, const QList<QVariant> &args)
530 {
531 	QAction *tmp = configManager.newManagedAction(menu, id, text, args.isEmpty() ? slotName : SLOT(relayToOwnSlot()), shortCuts, iconFile);
532 	if (!args.isEmpty()) {
533 		QString slot = QString(slotName).left(QString(slotName).indexOf("("));
534 		Q_ASSERT(staticMetaObject.indexOfSlot(createMethodSignature(qPrintable(slot), args)) != -1);
535 		tmp->setProperty("slot", qPrintable(slot));
536 		tmp->setProperty("args", QVariant::fromValue<QList<QVariant> >(args));
537 	}
538 	return tmp;
539 }
540 
newManagedEditorAction(QWidget * menu,const QString & id,const QString & text,const char * slotName,const QKeySequence & shortCut,const QString & iconFile,const QList<QVariant> & args)541 QAction *Texstudio::newManagedEditorAction(QWidget *menu, const QString &id, const QString &text, const char *slotName, const QKeySequence &shortCut, const QString &iconFile, const QList<QVariant> &args)
542 {
543         QAction *tmp = configManager.newManagedAction(menu, id, text, nullptr, QList<QKeySequence>() << shortCut, iconFile);
544 	linkToEditorSlot(tmp, slotName, args);
545 	return tmp;
546 }
547 
newManagedEditorAction(QWidget * menu,const QString & id,const QString & text,const char * slotName,const QList<QKeySequence> & shortCuts,const QString & iconFile,const QList<QVariant> & args)548 QAction *Texstudio::newManagedEditorAction(QWidget *menu, const QString &id, const QString &text, const char *slotName, const QList<QKeySequence> &shortCuts, const QString &iconFile, const QList<QVariant> &args)
549 {
550         QAction *tmp = configManager.newManagedAction(menu, id, text, nullptr, shortCuts, iconFile);
551 	linkToEditorSlot(tmp, slotName, args);
552 	return tmp;
553 }
554 
insertManagedAction(QAction * before,const QString & id,const QString & text,const char * slotName,const QKeySequence & shortCut,const QString & iconFile)555 QAction *Texstudio::insertManagedAction(QAction *before, const QString &id, const QString &text, const char *slotName, const QKeySequence &shortCut, const QString &iconFile)
556 {
557 	QMenu *menu = before->menu();
558 	REQUIRE_RET(menu, nullptr);
559 	QAction *inserted = newManagedAction(menu, id, text, slotName, shortCut, iconFile);
560 	menu->removeAction(inserted);
561 	menu->insertAction(before, inserted);
562 	return inserted;
563 }
564 
565 /*!
566  * \brief add TagList to side panel
567  *
568  * add Taglist to side panel.
569  *
570  * \param id
571  * \param iconName icon used for selecting taglist
572  * \param text name of taglist
573  * \param tagFile file to be read as tag list
574  */
addTagList(const QString & id,const QString & iconName,const QString & text,const QString & tagFile)575 void Texstudio::addTagList(const QString &id, const QString &iconName, const QString &text, const QString &tagFile)
576 {
577 	XmlTagsListWidget *list = qobject_cast<XmlTagsListWidget *>(leftPanel->widget(id));
578 	if (!list) {
579 		list = new XmlTagsListWidget(this, ":/tags/" + tagFile);
580 		list->setObjectName("tags/" + tagFile.left(tagFile.indexOf("_tags.xml")));
581 		UtilsUi::enableTouchScrolling(list);
582         connect(list, SIGNAL(itemClicked(QListWidgetItem*)), this, SLOT(insertXmlTag(QListWidgetItem*)));
583 		leftPanel->addWidget(list, id, text, iconName);
584 		//(*list)->setProperty("mType",2);
585 	} else leftPanel->setWidgetText(list, text);
586 }
587 
588 /*!
589  * \brief add all macros as TagList to side panel
590  *
591  * add Macros as Taglist to side panel as an alternative way to call them.
592  * This may be helpful if the number of macros becomes large and overcrowd the menu or are too many for generic keyboard shortcuts
593  *
594  */
addMacrosAsTagList()595 void Texstudio::addMacrosAsTagList()
596 {
597     bool addToPanel=true;
598     QListWidget *list = qobject_cast<QListWidget *>(leftPanel->widget("txs-macros"));
599     if (!list) {
600         list = new QListWidget(this);
601         list->setObjectName("tags/txs-macros");
602     }else{
603         list->clear();
604         addToPanel=false;
605     }
606     // add elements
607     for(const auto &m:configManager.completerConfig->userMacros) {
608         if (m.name == "TMX:Replace Quote Open" || m.name == "TMX:Replace Quote Close" || m.document)
609             continue;
610         QListWidgetItem* item=new QListWidgetItem(m.name);
611         item->setData(Qt::UserRole, m.typedTag());
612         list->addItem(item);
613     }
614     UtilsUi::enableTouchScrolling(list);
615     connect(list, SIGNAL(itemClicked(QListWidgetItem*)), this, SLOT(insertFromTagList(QListWidgetItem*)),Qt::UniqueConnection);
616     if(addToPanel)
617         leftPanel->addWidget(list, "txs-macros", tr("Macros"), getRealIconFile("executeMacro"));
618 }
619 
620 /*! set-up side- and bottom-panel
621  */
setupDockWidgets()622 void Texstudio::setupDockWidgets()
623 {
624     //to allow retranslate this function must be able to be called multiple times
625 
626     // adapt icon size to dpi
627     double dpi=QGuiApplication::primaryScreen()->logicalDotsPerInch();
628     double scale=dpi/96;
629 
630     if (!sidePanel) {
631         sidePanel = new SidePanel(this);
632         sidePanel->toggleViewAction()->setIcon(getRealIcon("sidebar"));
633         sidePanel->toggleViewAction()->setText(tr("Side Panel"));
634         sidePanel->toggleViewAction()->setChecked(configManager.getOption("GUI/sidePanel/visible", true).toBool());
635         addAction(sidePanel->toggleViewAction());
636 
637         sidePanelSplitter->insertWidget(0, sidePanel);
638         sidePanelSplitter->setStretchFactor(0, 0);  // panel does not get rescaled
639         sidePanelSplitter->setStretchFactor(1, 1);
640     }
641 
642     //Structure panel
643     if (!leftPanel) {
644         leftPanel = new CustomWidgetList(this);
645         leftPanel->setObjectName("leftPanel");
646         TitledPanelPage *page = new TitledPanelPage(leftPanel, "leftPanel", "TODO");
647         sidePanel->appendPage(page);
648         if (hiddenLeftPanelWidgets != "") {
649             leftPanel->setHiddenWidgets(hiddenLeftPanelWidgets);
650             hiddenLeftPanelWidgets = ""; //not needed anymore after the first call
651         }
652         connect(leftPanel, SIGNAL(titleChanged(QString)), page, SLOT(setTitle(QString)));
653         connect(leftPanel, SIGNAL(currentWidgetChanged(QWidget*)), this, SLOT(leftPanelChanged(QWidget*)));
654     }
655 
656     // load icons for structure view
657     QStringList structureIconNames = QStringList() << "part" << "chapter" << "section" << "subsection" << "subsubsection" << "paragraph" << "subparagraph";
658     iconSection.resize(structureIconNames.length());
659     for (int i = 0; i < structureIconNames.length(); i++)
660         iconSection[i] = getRealIconCached(structureIconNames[i]);
661 
662     if(!structureTreeWidget){
663         structureTreeWidget = new QTreeWidget();
664         connect(structureTreeWidget, SIGNAL(itemClicked(QTreeWidgetItem*,int)), this, SLOT(gotoLine(QTreeWidgetItem*,int)));
665         connect(structureTreeWidget, &QTreeWidget::itemExpanded, this, &Texstudio::syncExpanded);
666         connect(structureTreeWidget, &QTreeWidget::itemCollapsed, this, &Texstudio::syncCollapsed);
667         connect(structureTreeWidget, &QTreeWidget::customContextMenuRequested, this, &Texstudio::customMenuStructure);
668         structureTreeWidget->setHeaderHidden(true);
669         structureTreeWidget->setContextMenuPolicy(Qt::CustomContextMenu);
670         structureTreeWidget->installEventFilter(this);
671         leftPanel->addWidget(structureTreeWidget, "structureTreeWidget", tr("Structure"), getRealIconFile("structure"));
672     } else leftPanel->setWidgetText(topTOCTreeWidget, tr("TOC"));
673     if(!topTOCTreeWidget){
674         topTOCTreeWidget = new QTreeWidget();
675         connect(topTOCTreeWidget, SIGNAL(itemClicked(QTreeWidgetItem*,int)), this, SLOT(gotoLine(QTreeWidgetItem*,int)));
676         connect(topTOCTreeWidget, &QTreeWidget::itemExpanded, this, &Texstudio::syncExpanded);
677         connect(topTOCTreeWidget, &QTreeWidget::itemCollapsed, this, &Texstudio::syncCollapsed);
678         connect(topTOCTreeWidget, &QTreeWidget::customContextMenuRequested, this, &Texstudio::customMenuStructure);
679         topTOCTreeWidget->setHeaderHidden(true);
680         topTOCTreeWidget->setContextMenuPolicy(Qt::CustomContextMenu);
681         topTOCTreeWidget->installEventFilter(this);
682         leftPanel->addWidget(topTOCTreeWidget, "topTOCTreeWidget", tr("TOC"), getRealIconFile("toc"));
683     } else leftPanel->setWidgetText(topTOCTreeWidget, tr("TOC"));
684     if (!leftPanel->widget("bookmarks")) {
685         QListWidget *bookmarksWidget = bookmarks->widget();
686         bookmarks->setDarkMode(darkMode);
687         connect(bookmarks, SIGNAL(loadFileRequest(QString)), this, SLOT(load(QString)));
688         connect(bookmarks, SIGNAL(gotoLineRequest(int,int,LatexEditorView*)), this, SLOT(gotoLine(int,int,LatexEditorView*)));
689         leftPanel->addWidget(bookmarksWidget, "bookmarks", tr("Bookmarks"), getRealIconFile("bookmarks"));
690     } else leftPanel->setWidgetText("bookmarks", tr("Bookmarks"));
691 
692     if (!leftPanel->widget("symbols")) {
693         symbolWidget = new SymbolWidget(symbolListModel, configManager.insertSymbolsAsUnicode, this);
694         symbolWidget->restoreSplitter(configManager.stateSymbolsWidget);
695         symbolWidget->setSymbolSize(qRound(configManager.guiSymbolGridIconSize*scale));
696         connect(symbolWidget, SIGNAL(insertSymbol(QString)), this, SLOT(insertSymbol(QString)));
697         leftPanel->addWidget(symbolWidget, "symbols", tr("Symbols"), getRealIconFile("symbols"));
698     } else leftPanel->setWidgetText("symbols", tr("Symbols"));
699 
700     addTagList("brackets", getRealIconFile("leftright"), tr("Left/Right Brackets"), "brackets_tags.xml");
701     addTagList("pstricks", getRealIconFile("pstricks"), tr("PSTricks Commands"), "pstricks_tags.xml");
702     addTagList("metapost", getRealIconFile("metapost"), tr("MetaPost Commands"), "metapost_tags.xml");
703     addTagList("tikz", getRealIconFile("tikz"), tr("TikZ Commands"), "tikz_tags.xml");
704     addTagList("asymptote", getRealIconFile("asymptote"), tr("Asymptote Commands"), "asymptote_tags.xml");
705     addTagList("beamer", getRealIconFile("beamer"), tr("Beamer Commands"), "beamer_tags.xml");
706     addTagList("xymatrix", getRealIconFile("xy"), tr("XY Commands"), "xymatrix_tags.xml");
707     addMacrosAsTagList();
708 
709     leftPanel->showWidgets();
710     // restore selected view in sidepanel
711     int viewNr=configManager.getOption("GUI/sidePanel/currentPage", 0).toInt();
712     leftPanel->setCurrentWidget(leftPanel->widget(viewNr));
713 
714     // OUTPUT WIDGETS
715     if (!outputView) {
716 			  outputView = new OutputViewWidget(this, configManager.terminalConfig);
717         outputView->setObjectName("OutputView");
718         centralVSplitter->addWidget(outputView);
719         outputView->toggleViewAction()->setChecked(configManager.getOption("GUI/outputView/visible", true).toBool());
720         centralVSplitter->setStretchFactor(1, 0);
721         centralVSplitter->restoreState(configManager.getOption("centralVSplitterState").toByteArray());
722 
723         connect(outputView->getLogWidget(), SIGNAL(logEntryActivated(int)), this, SLOT(gotoLogEntryEditorOnly(int)));
724         connect(outputView->getLogWidget(), SIGNAL(logLoaded()), this, SLOT(updateLogEntriesInEditors()));
725         connect(outputView->getLogWidget(), SIGNAL(logResetted()), this, SLOT(clearLogEntriesInEditors()));
726         connect(outputView, SIGNAL(pageChanged(QString)), this, SLOT(outputPageChanged(QString)));
727         connect(outputView->getSearchResultWidget(), SIGNAL(jumpToSearchResult(QDocument*,int,const SearchQuery*)), this, SLOT(jumpToSearchResult(QDocument*,int,const SearchQuery*)));
728         connect(outputView->getSearchResultWidget(), SIGNAL(runSearch(SearchQuery*)), this, SLOT(runSearch(SearchQuery*)));
729 
730         connect(&buildManager, SIGNAL(previewAvailable(const QString&,const PreviewSource&)), this, SLOT(previewAvailable(const QString&,const PreviewSource&)));
731         connect(&buildManager, SIGNAL(processNotification(QString)), SLOT(processNotification(QString)));
732         connect(&buildManager, SIGNAL(clearLogs()), SLOT(clearLogs()));
733 
734         connect(&buildManager, SIGNAL(beginRunningCommands(QString,bool,bool,bool)), SLOT(beginRunningCommand(QString,bool,bool,bool)));
735         connect(&buildManager, SIGNAL(beginRunningSubCommand(ProcessX*,QString,QString,RunCommandFlags)), SLOT(beginRunningSubCommand(ProcessX*,QString,QString,RunCommandFlags)));
736         connect(&buildManager, SIGNAL(endRunningSubCommand(ProcessX*,QString,QString,RunCommandFlags)), SLOT(endRunningSubCommand(ProcessX*,QString,QString,RunCommandFlags)));
737         connect(&buildManager, SIGNAL(endRunningCommands(QString,bool,bool,bool)), SLOT(endRunningCommand(QString,bool,bool,bool)));
738         connect(&buildManager, SIGNAL(latexCompiled(LatexCompileResult*)), SLOT(viewLogOrReRun(LatexCompileResult*)));
739         connect(&buildManager, SIGNAL(runInternalCommand(QString,QFileInfo,QString)), SLOT(runInternalCommand(QString,QFileInfo,QString)));
740         connect(&buildManager, SIGNAL(commandLineRequested(QString,QString*,bool*)), SLOT(commandLineRequested(QString,QString*,bool*)));
741 
742         addAction(outputView->toggleViewAction());
743         QAction *temp = new QAction(this);
744         temp->setSeparator(true);
745         addAction(temp);
746     }
747     sidePanelSplitter->restoreState(configManager.getOption("GUI/sidePanelSplitter/state").toByteArray());
748 }
749 
updateToolBarMenu(const QString & menuName)750 void Texstudio::updateToolBarMenu(const QString &menuName)
751 {
752 	QMenu *menu = configManager.getManagedMenu(menuName);
753 	if (!menu) return;
754 	LatexEditorView *edView = currentEditorView();
755     foreach (const ManagedToolBar &tb, configManager.managedToolBars){
756         if (tb.toolbar && tb.actualActions.contains(menuName)){
757             foreach (QObject *w, tb.toolbar->children()){
758 				if (w->property("menuID").toString() == menuName) {
759 					QToolButton *combo = qobject_cast<QToolButton *>(w);
760 					REQUIRE(combo);
761 
762 					QStringList actionTexts;
763 					QList<QIcon> actionIcons;
764 					int defaultIndex = -1;
765 					foreach (const QAction *act, menu->actions())
766 						if (!act->isSeparator()) {
767 							actionTexts.append(act->text());
768 							actionIcons.append(act->icon());
769 							if (menuName == "main/view/documents" && edView == act->data().value<LatexEditorView *>()) {
770 								defaultIndex = actionTexts.length() - 1;
771 							}
772 						}
773 
774 					//qDebug() << "**" << actionTexts;
775 					UtilsUi::createComboToolButton(tb.toolbar, actionTexts, actionIcons, -1, this, SLOT(callToolButtonAction()), defaultIndex, combo);
776 
777 					if (menuName == "main/view/documents") {
778 						// workaround to select the current document
779 						// combobox uses separate actions. So we have to get the current action from the menu (by comparing its data()
780 						// attribute to the currentEditorView(). Then map it to a combobox action using the index.
781 						// TODO: should this menu be provided by Editors?
782 						LatexEditorView *edView = currentEditorView();
783 						foreach (QAction* act, menu->actions()) {
784 							qDebug() << act->data().value<LatexEditorView *>() << combo;
785 							if (edView == act->data().value<LatexEditorView *>()) {
786 								int i = menu->actions().indexOf(act);
787 								qDebug() << i << combo->menu()->actions().length();
788 								if (i < 0 || i>= combo->menu()->actions().length()) continue;
789 								foreach (QAction *act, menu->actions()) {
790 									qDebug() << "menu" << act->text();
791 								}
792 								foreach (QAction *act, combo->menu()->actions()) {
793 									qDebug() << "cmb" << act->text();
794 								}
795 
796 								combo->setDefaultAction(combo->menu()->actions()[i]);
797 							}
798 						}
799 					}
800 				}
801             }
802         }
803     }
804 }
805 
806 // we different native shortcuts on OSX and Win/Linux
807 // note: in particular many key combination with arrows are reserved for text navigation in OSX
808 // and we already set them in QEditor. Don't overwrite them here.
809 #ifdef Q_OS_MAC
810     #define MAC_OR_DEFAULT(shortcutMac, shortcutOther) shortcutMac
811 #else
812     #define MAC_OR_DEFAULT(shortcutMac, shortcutOther) shortcutOther
813 #endif
814 /*! \brief set-up all menus in the menu-bar
815  *
816  * This function is called whenever the menu changes (= start and retranslation)
817  * This means if you call it repeatedly with the same language setting it should not change anything
818  * Currently this is not true, because it adds additional separator, which are invisible
819  * creates new action groups and new context menu, although all invisible, they are a memory leak
820  * But not a bad one, because no one is expected to change the language multiple times
821  */
setupMenus()822 void Texstudio::setupMenus()
823 {
824 	//This function is called whenever the menu changes (= start and retranslation)
825 	//This means if you call it repeatedly with the same language setting it should not change anything
826 	//Currently this is not true, because it adds additional separator, which are invisible
827 	//creates new action groups and new context menu, although all invisible, they are a memory leak
828 	//But not a bad one, because no one is expected to change the language multiple times
829 	//TODO: correct somewhen
830 
831 	configManager.menuParent = this;
832     if(configManager.menuParents.isEmpty()){
833         configManager.menuParents.append(this);
834     }
835 	configManager.menuParentsBar = menuBar();
836 
837 	//file
838 	QMenu *menu = newManagedMenu("main/file", tr("&File"));
839     //getManagedMenu("main/file");
840 	newManagedAction(menu, "new", tr("&New"), SLOT(fileNew()), QKeySequence::New, "document-new");
841 	newManagedAction(menu, "newfromtemplate", tr("New From &Template..."), SLOT(fileNewFromTemplate()));
842 	newManagedAction(menu, "open", tr("&Open..."), SLOT(fileOpen()), QKeySequence::Open, "document-open");
843 
844 	QMenu *submenu = newManagedMenu(menu, "openrecent", tr("Open &Recent")); //only create the menu here, actions are created by config manager
845 
846 	submenu = newManagedMenu(menu, "session", tr("Session"));
847 	newManagedAction(submenu, "loadsession", tr("Load Session..."), SLOT(fileLoadSession()));
848 	newManagedAction(submenu, "savesession", tr("Save Session..."), SLOT(fileSaveSession()));
849 	newManagedAction(submenu, "restoresession", tr("Restore Previous Session"), SLOT(fileRestoreSession()));
850 	submenu->addSeparator();
851 	if (!recentSessionList) {
852 		recentSessionList = new SessionList(submenu, this);
853 		connect(recentSessionList, SIGNAL(loadSessionRequest(QString)), this, SLOT(loadSession(QString)));
854 	}
855 	recentSessionList->updateMostRecentMenu();
856 
857 	menu->addSeparator();
858 	actSave = newManagedAction(menu, "save", tr("&Save"), SLOT(fileSave()), QKeySequence::Save, "document-save");
859     newManagedAction(menu, "saveas", tr("Save &As..."), SLOT(fileSaveAs()), filterLocaleShortcut(Qt::CTRL | Qt::ALT | Qt::Key_S));
860     newManagedAction(menu, "saveall", tr("Save A&ll"), SLOT(fileSaveAll()), Qt::CTRL | Qt::SHIFT | Qt::Key_S);
861 	newManagedAction(menu, "maketemplate", tr("&Make Template..."), SLOT(fileMakeTemplate()));
862 
863 
864 	submenu = newManagedMenu(menu, "utilities", tr("Fifi&x"));
865 	newManagedAction(submenu, "rename", tr("Save renamed/&moved file..."), "fileUtilCopyMove", 0, QString(), QList<QVariant>() << true);
866 	newManagedAction(submenu, "copy", tr("Save copied file..."), "fileUtilCopyMove", 0, QString(), QList<QVariant>() << false);
867 	newManagedAction(submenu, "delete", tr("&Delete file"), SLOT(fileUtilDelete()));
868 	newManagedAction(submenu, "chmod", tr("Set &permissions..."), SLOT(fileUtilPermissions()));
869 	submenu->addSeparator();
870 	newManagedAction(submenu, "revert", tr("&Revert to saved..."), SLOT(fileUtilRevert()));
871 	submenu->addSeparator();
872 	newManagedAction(submenu, "copyfilename", tr("Copy filename to &clipboard"), SLOT(fileUtilCopyFileName()));
873 	newManagedAction(submenu, "copymasterfilename", tr("Copy master filename to clipboard"), SLOT(fileUtilCopyMasterFileName()));
874 
875     QMenu *svnSubmenu = newManagedMenu(menu, "svn", tr("S&VN/GIT..."));
876 	newManagedAction(svnSubmenu, "checkin", tr("Check &in..."), SLOT(fileCheckin()));
877 	newManagedAction(svnSubmenu, "svnupdate", tr("SVN &update..."), SLOT(fileUpdate()));
878 	newManagedAction(svnSubmenu, "svnupdatecwd", tr("SVN update &work directory"), SLOT(fileUpdateCWD()));
879 	newManagedAction(svnSubmenu, "showrevisions", tr("Sh&ow old Revisions"), SLOT(showOldRevisions()));
880 	newManagedAction(svnSubmenu, "lockpdf", tr("Lock &PDF"), SLOT(fileLockPdf()));
881 	newManagedAction(svnSubmenu, "checkinpdf", tr("Check in P&DF"), SLOT(fileCheckinPdf()));
882 	newManagedAction(svnSubmenu, "difffiles", tr("Show difference between two files"), SLOT(fileDiff()));
883 	newManagedAction(svnSubmenu, "diff3files", tr("Show difference between two files in relation to base file"), SLOT(fileDiff3()));
884 	newManagedAction(svnSubmenu, "checksvnconflict", tr("Check SVN Conflict"), SLOT(checkSVNConflicted()));
885 	newManagedAction(svnSubmenu, "mergediff", tr("Try to merge differences"), SLOT(fileDiffMerge()));
886 	newManagedAction(svnSubmenu, "removediffmakers", tr("Remove Difference-Markers"), SLOT(removeDiffMarkers()));
887 	newManagedAction(svnSubmenu, "declareresolved", tr("Declare Conflict Resolved"), SLOT(declareConflictResolved()));
888 	newManagedAction(svnSubmenu, "nextdiff", tr("Jump to next difference"), SLOT(jumpNextDiff()), 0, "go-next-diff");
889 	newManagedAction(svnSubmenu, "prevdiff", tr("Jump to previous difference"), SLOT(jumpPrevDiff()), 0, "go-previous-diff");
890 
891 	menu->addSeparator();
892     newManagedAction(menu, "close", tr("&Close"), SLOT(fileClose()), Qt::CTRL | Qt::Key_W, "document-close");
893 	newManagedAction(menu, "closeall", tr("Clos&e All"), SLOT(fileCloseAll()));
894 
895 	menu->addSeparator();
896     newManagedEditorAction(menu, "print", tr("Print Source Code..."), "print", Qt::CTRL | Qt::Key_P);
897 
898 	menu->addSeparator();
899     newManagedAction(menu, "exit", tr("Exit"), SLOT(fileExit()), Qt::CTRL | Qt::Key_Q)->setMenuRole(QAction::QuitRole);
900 
901 	//edit
902 	menu = newManagedMenu("main/edit", tr("&Edit"));
903 	actUndo = newManagedAction(menu, "undo", tr("&Undo"), SLOT(editUndo()), QKeySequence::Undo, "edit-undo");
904 	actRedo = newManagedAction(menu, "redo", tr("&Redo"), SLOT(editRedo()), QKeySequence::Redo, "edit-redo");
905 #ifndef QT_NO_DEBUG
906 	newManagedAction(menu, "debughistory", tr("Debug undo stack"), SLOT(editDebugUndoStack()));
907 #endif
908 	menu->addSeparator();
909     newManagedEditorAction(menu, "cut", tr("C&ut"), "cut", QKeySequence::Cut, "edit-cut");
910 	newManagedAction(menu, "copy", tr("&Copy"), SLOT(editCopy()), QKeySequence::Copy, "edit-copy");
911 	newManagedAction(menu, "paste", tr("&Paste"), SLOT(editPaste()), QKeySequence::Paste, "edit-paste");
912 
913 	submenu = newManagedMenu(menu, "selection", tr("&Selection"));
914     newManagedEditorAction(submenu, "selectAll", tr("Select &All"), "selectAll", Qt::CTRL | Qt::Key_A);
915 	newManagedEditorAction(submenu, "selectAllOccurences", tr("Select All &Occurrences"), "selectAllOccurences");
916 	newManagedEditorAction(submenu, "selectPrevOccurence", tr("Select &Prev Occurrence"), "selectPrevOccurence");
917 	newManagedEditorAction(submenu, "selectNextOccurence", tr("Select &Next Occurrence"), "selectNextOccurence");
918 	newManagedEditorAction(submenu, "selectPrevOccurenceKeepMirror", tr("Also Select Prev Occurrence"), "selectPrevOccurenceKeepMirror");
919 	newManagedEditorAction(submenu, "selectNextOccurenceKeepMirror", tr("Also Select Next Occurrence"), "selectNextOccurenceKeepMirror");
920     newManagedEditorAction(submenu, "expandSelectionToWord", tr("Expand Selection to Word"), "selectExpandToNextWord", Qt::CTRL | Qt::Key_D);
921     newManagedEditorAction(submenu, "expandSelectionToLine", tr("Expand Selection to Line"), "selectExpandToNextLine", Qt::CTRL | Qt::Key_L);
922 
923 	submenu = newManagedMenu(menu, "lineoperations", tr("&Line Operations"));
924     newManagedAction(submenu, "deleteLine", tr("Delete &Line"), SLOT(editDeleteLine()), Qt::CTRL | Qt::Key_K);
925 #if QT_VERSION>=QT_VERSION_CHECK(6,0,0)
926     newManagedAction(submenu, "deleteToEndOfLine", tr("Delete To &End Of Line"), SLOT(editDeleteToEndOfLine()), MAC_OR_DEFAULT(Qt::CTRL | Qt::Key_Delete,  Qt::AltModifier | Qt::Key_K));
927 #else
928     newManagedAction(submenu, "deleteToEndOfLine", tr("Delete To &End Of Line"), SLOT(editDeleteToEndOfLine()), MAC_OR_DEFAULT(Qt::CTRL | Qt::Key_Delete,  Qt::AltModifier + Qt::Key_K));
929 #endif
930     newManagedAction(submenu, "deleteFromStartOfLine", tr("Delete From &Start Of Line"), SLOT(editDeleteFromStartOfLine()), MAC_OR_DEFAULT(Qt::CTRL | Qt::Key_Backspace, 0));
931 	newManagedAction(submenu, "moveLineUp", tr("Move Line &Up"), SLOT(editMoveLineUp()));
932 	newManagedAction(submenu, "moveLineDown", tr("Move Line &Down"), SLOT(editMoveLineDown()));
933 	newManagedAction(submenu, "duplicateLine", tr("Du&plicate Line"), SLOT(editDuplicateLine()));
934 	newManagedAction(submenu, "sortLines", tr("S&ort Lines"), SLOT(editSortLines()));
935 	newManagedAction(submenu, "alignMirrors", tr("&Align Cursors"), SLOT(editAlignMirrors()));
936 
937 	submenu = newManagedMenu(menu, "textoperations", tr("&Text Operations"));
938 	newManagedAction(submenu, "textToLowercase", tr("To Lowercase"), SLOT(editTextToLowercase()));
939 	newManagedAction(submenu, "textToUppercase", tr("To Uppercase"), SLOT(editTextToUppercase()));
940 	newManagedAction(submenu, "textToTitlecaseStrict", tr("To Titlecase (strict)"), SLOT(editTextToTitlecase()));
941 	newManagedAction(submenu, "textToTitlecaseSmart", tr("To Titlecase (smart)"), SLOT(editTextToTitlecaseSmart()));
942 
943 
944 	menu->addSeparator();
945 	submenu = newManagedMenu(menu, "searching", tr("&Searching"));
946 	newManagedAction(submenu, "find", tr("&Find"), SLOT(editFind()), QKeySequence::Find);
947     newManagedEditorAction(submenu, "findnext", tr("Find &Next"), "findNext", MAC_OR_DEFAULT(Qt::CTRL | Qt::Key_G, Qt::Key_F3));
948     newManagedEditorAction(submenu, "findprev", tr("Find &Prev"), "findPrev", MAC_OR_DEFAULT(Qt::CTRL | Qt::SHIFT | Qt::Key_G, Qt::SHIFT | Qt::Key_F3));
949     newManagedEditorAction(submenu, "findinsamedir", tr("Continue F&ind"), "findInSameDir");
950 	newManagedEditorAction(submenu, "findcount", tr("&Count"), "findCount");
951 	newManagedEditorAction(submenu, "select", tr("&Select all matches..."), "selectAllMatches");
952 	submenu->addSeparator();
953     newManagedEditorAction(submenu, "replace", tr("&Replace"), "replacePanel", Qt::CTRL | Qt::Key_R);
954 	newManagedEditorAction(submenu, "replacenext", tr("Replace Next"), "replaceNext");
955 	newManagedEditorAction(submenu, "replaceprev", tr("Replace Prev"), "replacePrev");
956 	newManagedEditorAction(submenu, "replaceall", tr("Replace &All"), "replaceAll");
957 
958 	menu->addSeparator();
959 	submenu = newManagedMenu(menu, "goto", tr("Go to"));
960 
961     newManagedEditorAction(submenu, "line", tr("Line"), "gotoLine", MAC_OR_DEFAULT(Qt::CTRL + Qt::Key_L, Qt::CTRL | Qt::Key_G), "goto");
962     newManagedEditorAction(submenu, "lastchange", tr("Previous Change"), "jumpChangePositionBackward", Qt::CTRL | Qt::Key_H);
963 #if (QT_VERSION>=QT_VERSION_CHECK(6,0,0))
964     newManagedEditorAction(submenu, "nextchange", tr("Next Change"), "jumpChangePositionForward", QKeyCombination(Qt::CTRL | Qt::SHIFT , Qt::Key_H));
965 #else
966     newManagedEditorAction(submenu, "nextchange", tr("Next Change"), "jumpChangePositionForward", Qt::CTRL | Qt::SHIFT | Qt::Key_H);
967 #endif
968 	submenu->addSeparator();
969     newManagedAction(submenu, "markprev", tr("Previous mark"), "gotoMark", MAC_OR_DEFAULT(0, Qt::CTRL | Qt::Key_Up), "", QList<QVariant>() << true << -1); //, ":/images/errorprev.png");
970     newManagedAction(submenu, "marknext", tr("Next mark"), "gotoMark", MAC_OR_DEFAULT(0, Qt::CTRL | Qt::Key_Down), "", QList<QVariant>() << false << -1); //, ":/images/errornext.png");
971 	submenu->addSeparator();
972 	if (cursorHistory) {
973         cursorHistory->setBackAction(newManagedAction(submenu, "goback", tr("Go Back"), SLOT(goBack()), MAC_OR_DEFAULT(0, Qt::ALT | Qt::Key_Left), "back"));
974         cursorHistory->setForwardAction(newManagedAction(submenu, "goforward", tr("Go Forward"), SLOT(goForward()), MAC_OR_DEFAULT(0, Qt::ALT | Qt::Key_Right), "forward"));
975 	}
976 
977 	submenu = newManagedMenu(menu, "gotoBookmark", tr("Goto Bookmark"));
978 	QList<int> bookmarkIndicies = QList<int>() << 1 << 2 << 3 << 4 << 5 << 6 << 7 << 8 << 9 << 0;
979     foreach (int i, bookmarkIndicies) {
980         QKeySequence shortcut;
981 #if (QT_VERSION>=QT_VERSION_CHECK(6,0,0))
982         if (i != 0){
983             shortcut = Qt::CTRL | static_cast<Qt::Key>(static_cast<int>(Qt::Key_0) + i);
984         }
985 #else
986         if (i != 0){
987             shortcut = Qt::CTRL + Qt::Key_0 + i;
988         }
989 #endif
990         newManagedEditorAction(submenu, QString("bookmark%1").arg(i), tr("Bookmark %1").arg(i), "jumpToBookmark", shortcut, "", QList<QVariant>() << i);
991     }
992 
993 
994 	submenu = newManagedMenu(menu, "toggleBookmark", tr("Toggle Bookmark"));
995 
996 #if (QT_VERSION>=QT_VERSION_CHECK(6,0,0))
997     newManagedEditorAction(submenu, QString("bookmark"), tr("Unnamed Bookmark"), "toggleBookmark", QKeyCombination(Qt::CTRL | Qt::SHIFT , Qt::Key_B), "", QList<QVariant>() << -1);
998         foreach (int i, bookmarkIndicies)
999             newManagedEditorAction(submenu, QString("bookmark%1").arg(i), tr("Bookmark %1").arg(i), "toggleBookmark", QKeyCombination(Qt::CTRL | Qt::SHIFT , static_cast<Qt::Key>(static_cast<int>(Qt::Key_0) + i)), "", QList<QVariant>() << i);
1000 #else
1001     newManagedEditorAction(submenu, QString("bookmark"), tr("Unnamed Bookmark"), "toggleBookmark", Qt::CTRL | Qt::SHIFT | Qt::Key_B, "", QList<QVariant>() << -1);
1002     foreach (int i, bookmarkIndicies)
1003         newManagedEditorAction(submenu, QString("bookmark%1").arg(i), tr("Bookmark %1").arg(i), "toggleBookmark", Qt::CTRL + Qt::SHIFT + Qt::Key_0 + i, "", QList<QVariant>() << i);
1004 #endif
1005 
1006 
1007 	menu->addSeparator();
1008 	submenu = newManagedMenu(menu, "lineend", tr("Line Ending"));
1009 	QActionGroup *lineEndingGroup = new QActionGroup(this);
1010 	QAction *act = newManagedAction(submenu, "crlf", tr("DOS/Windows (CR LF)"), SLOT(editChangeLineEnding()));
1011 	act->setData(QDocument::Windows);
1012 	act->setCheckable(true);
1013 	lineEndingGroup->addAction(act);
1014 	act = newManagedAction(submenu, "lf", tr("Unix (LF)"), SLOT(editChangeLineEnding()));
1015 	act->setData(QDocument::Unix);
1016 	act->setCheckable(true);
1017 	lineEndingGroup->addAction(act);
1018 	act = newManagedAction(submenu, "cr", tr("Old Mac (CR)"), SLOT(editChangeLineEnding()));
1019 	act->setData(QDocument::Mac);
1020 	act->setCheckable(true);
1021 	lineEndingGroup->addAction(act);
1022 
1023 
1024 	newManagedAction(menu, "encoding", tr("Setup Encoding..."), SLOT(editSetupEncoding()))->setMenuRole(QAction::NoRole); // with the default "QAction::TextHeuristicRole" this was interperted as Preferences on OSX
1025 #if (QT_VERSION>=QT_VERSION_CHECK(6,0,0))
1026     newManagedAction(menu, "unicodeChar", tr("Insert Unicode Character..."), SLOT(editInsertUnicode()), filterLocaleShortcut(QKeyCombination(Qt::ALT | Qt::CTRL , Qt::Key_U)));
1027 #else
1028     newManagedAction(menu, "unicodeChar", tr("Insert Unicode Character..."), SLOT(editInsertUnicode()), filterLocaleShortcut(Qt::ALT | Qt::CTRL | Qt::Key_U));
1029 #endif
1030 
1031 
1032 
1033 	//Edit 2 (for LaTeX related things)
1034 	menu = newManagedMenu("main/edit2", tr("&Idefix"));
1035     newManagedAction(menu, "eraseWord", tr("Erase &Word/Cmd/Env"), SLOT(editEraseWordCmdEnv()), MAC_OR_DEFAULT(0, Qt::ALT | Qt::Key_Delete));
1036 
1037 	menu->addSeparator();
1038     newManagedAction(menu, "pasteAsLatex", tr("Pas&te as LaTeX"), SLOT(editPasteLatex()), QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_V), "editpaste");
1039 	newManagedAction(menu, "convertTo", tr("Co&nvert to LaTeX"), SLOT(convertToLatex()));
1040     newManagedAction(menu, "previewLatex", tr("Pre&view Selection/Parentheses"), SLOT(previewLatex()), Qt::ALT | Qt::Key_P);
1041 	newManagedAction(menu, "removePreviewLatex", tr("C&lear Inline Preview"), SLOT(clearPreview()));
1042 
1043 	menu->addSeparator();
1044     newManagedEditorAction(menu, "togglecomment", tr("Toggle &Comment"), "toggleCommentSelection", Qt::CTRL | Qt::Key_T);
1045 	newManagedEditorAction(menu, "comment", tr("&Comment"), "commentSelection");
1046     newManagedEditorAction(menu, "uncomment", tr("&Uncomment"), "uncommentSelection", Qt::CTRL | Qt::Key_U);
1047 	newManagedEditorAction(menu, "indent", tr("&Indent"), "indentSelection");
1048 	newManagedEditorAction(menu, "unindent", tr("Unin&dent"), "unindentSelection");
1049 	newManagedAction(menu, "hardbreak", tr("Hard Line &Break..."), SLOT(editHardLineBreak()));
1050 	newManagedAction(menu, "hardbreakrepeat", tr("R&epeat Hard Line Break"), SLOT(editHardLineBreakRepeat()));
1051 
1052 	menu->addSeparator();
1053 	submenu = newManagedMenu(menu, "goto", tr("&Go to"));
1054 
1055     newManagedAction(submenu, "errorprev", tr("Previous Error"), "gotoNearLogEntry", MAC_OR_DEFAULT(0, QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_Up)), "errorprev", QList<QVariant>() << LT_ERROR << true << tr("No LaTeX errors detected !"));
1056     newManagedAction(submenu, "errornext", tr("Next Error"), "gotoNearLogEntry", MAC_OR_DEFAULT(0, QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_Down)), "errornext", QList<QVariant>() << LT_ERROR << false << tr("No LaTeX errors detected !"));
1057 	newManagedAction(submenu, "warningprev", tr("Previous Warning"), "gotoNearLogEntry", QKeySequence(), "", QList<QVariant>() << LT_WARNING << true << tr("No LaTeX warnings detected !")); //, ":/images/errorprev.png");
1058 	newManagedAction(submenu, "warningnext", tr("Next Warning"), "gotoNearLogEntry", QKeySequence(), "", QList<QVariant>() << LT_WARNING << false << tr("No LaTeX warnings detected !")); //, ":/images/errornext.png");
1059     newManagedAction(submenu, "badboxprev", tr("Previous Bad Box"), "gotoNearLogEntry", MAC_OR_DEFAULT(0, QKeySequence(Qt::SHIFT | Qt::ALT | Qt::Key_Up)), "", QList<QVariant>() << LT_BADBOX << true << tr("No bad boxes detected !")); //, ":/images/errorprev.png");
1060     newManagedAction(submenu, "badboxnext", tr("Next Bad Box"), "gotoNearLogEntry", MAC_OR_DEFAULT(0, QKeySequence(Qt::SHIFT | Qt::ALT | Qt::Key_Down)), "", QList<QVariant>() << LT_BADBOX << false << tr("No bad boxes detected !")); //, ":/images/errornext.png");
1061 
1062 	submenu->addSeparator();
1063     newManagedAction(submenu, "definition", tr("Definition"), SLOT(editGotoDefinition()), filterLocaleShortcut(QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key_F)));
1064 
1065 	menu->addSeparator();
1066 	newManagedAction(menu, "generateMirror", tr("Re&name Environment"), SLOT(generateMirror()));
1067 
1068 	submenu = newManagedMenu(menu, "parens", tr("Parenthesis"));
1069 #if (QT_VERSION>=QT_VERSION_CHECK(6,0,0))
1070     newManagedAction(submenu, "jump", tr("Jump to Match"), SLOT(jumpToBracket()), QKeySequence(QKeyCombination(Qt::SHIFT | Qt::CTRL , Qt::Key_P), QKeyCombination(Qt::Key_J)));
1071     newManagedAction(submenu, "selectBracketInner", tr("Select Inner"), SLOT(selectBracket()), QKeySequence(QKeyCombination(Qt::SHIFT | Qt::CTRL , Qt::Key_P), QKeyCombination(Qt::Key_I)))->setProperty("type", "inner");
1072     newManagedAction(submenu, "selectBracketOuter", tr("Select Outer"), SLOT(selectBracket()), QKeySequence(QKeyCombination(Qt::SHIFT | Qt::CTRL , Qt::Key_P), QKeyCombination(Qt::Key_O)))->setProperty("type", "outer");
1073     newManagedAction(submenu, "selectBracketCommand", tr("Select Command"), SLOT(selectBracket()), QKeySequence(QKeyCombination(Qt::SHIFT | Qt::CTRL , Qt::Key_P), QKeyCombination(Qt::Key_C)))->setProperty("type", "command");
1074     newManagedAction(submenu, "selectBracketLine", tr("Select Line"), SLOT(selectBracket()), QKeySequence(QKeyCombination(Qt::SHIFT | Qt::CTRL , Qt::Key_P), QKeyCombination(Qt::Key_L)))->setProperty("type", "line");
1075     newManagedAction(submenu, "generateInvertedBracketMirror", tr("Select Inverting"), SLOT(generateBracketInverterMirror()), QKeySequence(QKeyCombination(Qt::SHIFT | Qt::CTRL , Qt::Key_P), QKeyCombination(Qt::Key_S)));
1076 
1077     submenu->addSeparator();
1078     newManagedAction(submenu, "findMissingBracket", tr("Find Mismatch"), SLOT(findMissingBracket()), QKeySequence(QKeyCombination(Qt::SHIFT | Qt::CTRL , Qt::Key_P), QKeyCombination(Qt::Key_M)));
1079 #else
1080     newManagedAction(submenu, "jump", tr("Jump to Match"), SLOT(jumpToBracket()), QKeySequence(Qt::SHIFT | Qt::CTRL | Qt::Key_P, Qt::Key_J));
1081     newManagedAction(submenu, "selectBracketInner", tr("Select Inner"), SLOT(selectBracket()), QKeySequence(Qt::SHIFT | Qt::CTRL | Qt::Key_P, Qt::Key_I))->setProperty("type", "inner");
1082     newManagedAction(submenu, "selectBracketOuter", tr("Select Outer"), SLOT(selectBracket()), QKeySequence(Qt::SHIFT | Qt::CTRL | Qt::Key_P, Qt::Key_O))->setProperty("type", "outer");
1083     newManagedAction(submenu, "selectBracketCommand", tr("Select Command"), SLOT(selectBracket()), QKeySequence(Qt::SHIFT | Qt::CTRL | Qt::Key_P, Qt::Key_C))->setProperty("type", "command");
1084     newManagedAction(submenu, "selectBracketLine", tr("Select Line"), SLOT(selectBracket()), QKeySequence(Qt::SHIFT | Qt::CTRL |Qt::Key_P, Qt::Key_L))->setProperty("type", "line");
1085     newManagedAction(submenu, "generateInvertedBracketMirror", tr("Select Inverting"), SLOT(generateBracketInverterMirror()), QKeySequence(QKeySequence(Qt::SHIFT | Qt::CTRL | Qt::Key_P, Qt::Key_S)));
1086 
1087 	submenu->addSeparator();
1088     newManagedAction(submenu, "findMissingBracket", tr("Find Mismatch"), SLOT(findMissingBracket()), QKeySequence(Qt::SHIFT | Qt::CTRL | Qt::Key_P, Qt::Key_M));
1089 #endif
1090 
1091 	submenu = newManagedMenu(menu, "complete", tr("Complete"));
1092     newManagedAction(submenu, "normal", tr("Normal"), SLOT(normalCompletion()), MAC_OR_DEFAULT(QKeySequence(Qt::META | Qt::Key_Space), QKeySequence(Qt::CTRL | Qt::Key_Space)));
1093     newManagedAction(submenu, "environment", tr("\\begin{ Completion"), SLOT(insertEnvironmentCompletion()), QKeySequence(Qt::CTRL | Qt::ALT | Qt::Key_Space));
1094     newManagedAction(submenu, "text", tr("Normal Text"), SLOT(insertTextCompletion()), QKeySequence(Qt::SHIFT | Qt::ALT | Qt::Key_Space));
1095     newManagedAction(submenu, "closeEnvironment", tr("Close latest open environment"), SLOT(closeEnvironment()), QKeySequence(Qt::ALT | Qt::Key_Return));
1096 
1097 	menu->addSeparator();
1098     newManagedAction(menu, "updateTOC", tr("update TOC"), SLOT(updateTOCs()));
1099 	newManagedAction(menu, "reparse", tr("Refresh Structure"), SLOT(updateStructure()));
1100 	act = newManagedAction(menu, "refreshQNFA", tr("Refresh Language Model"), SLOT(updateTexQNFA()));
1101 	act->setStatusTip(tr("Force an update of the dynamic language model used for highlighting and folding. Likely, you do not need to call this because updates are usually automatic."));
1102     newManagedAction(menu, "removePlaceHolders", tr("Remove Placeholders"), SLOT(editRemovePlaceHolders()), QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_K));
1103 	newManagedAction(menu, "removeCurrentPlaceHolder", tr("Remove Current Placeholder"), SLOT(editRemoveCurrentPlaceHolder()));
1104 
1105 	//tools
1106 
1107 
1108 	menu = newManagedMenu("main/tools", tr("&Tools"));
1109 	menu->setProperty("defaultSlot", QByteArray(SLOT(commandFromAction())));
1110     newManagedAction(menu, "quickbuild", tr("&Build && View"), SLOT(commandFromAction()), (QList<QKeySequence>() << Qt::Key_F5 << Qt::Key_F1), "build")->setData(BuildManager::CMD_QUICK);
1111     newManagedAction(menu, "compile", tr("&Compile"), SLOT(commandFromAction()), Qt::Key_F6, "compile")->setData(BuildManager::CMD_COMPILE);
1112     QAction *stopAction = new QAction(getRealIcon("stop"), tr("Stop Compile"), menu);
1113     connect(stopAction, SIGNAL(triggered()), &buildManager, SLOT(killCurrentProcess()));
1114     newManagedAction(menu, "stopcompile", stopAction)->setEnabled(false);
1115     connect(&buildManager,SIGNAL(buildRunning(bool)),this,SLOT(setBuildButtonsDisabled(bool)));
1116 	newManagedAction(menu, "view", tr("&View"), SLOT(commandFromAction()), Qt::Key_F7, "viewer")->setData(BuildManager::CMD_VIEW);
1117 	newManagedAction(menu, "bibtex", tr("&Bibliography"), SLOT(commandFromAction()), Qt::Key_F8)->setData(BuildManager::CMD_BIBLIOGRAPHY);
1118 	newManagedAction(menu, "glossary", tr("&Glossary"), SLOT(commandFromAction()), Qt::Key_F9)->setData(BuildManager::CMD_GLOSSARY);
1119 	newManagedAction(menu, "index", tr("&Index"), SLOT(commandFromAction()))->setData(BuildManager::CMD_INDEX);
1120 
1121 	menu->addSeparator();
1122 	submenu = newManagedMenu(menu, "commands", tr("&Commands", "menu"));
1123 	newManagedAction(submenu, "latexmk", tr("&Latexmk"), SLOT(commandFromAction()))->setData(BuildManager::CMD_LATEXMK);
1124 	submenu->addSeparator();
1125 	newManagedAction(submenu, "latex", tr("&LaTeX"), SLOT(commandFromAction()), QKeySequence(), "compile-latex")->setData(BuildManager::CMD_LATEX);
1126 	newManagedAction(submenu, "pdflatex", tr("&PDFLaTeX"), SLOT(commandFromAction()), QKeySequence(), "compile-pdf")->setData(BuildManager::CMD_PDFLATEX);
1127 	newManagedAction(submenu, "xelatex", "&XeLaTeX", SLOT(commandFromAction()), QKeySequence(), "compile-xelatex")->setData(BuildManager::CMD_XELATEX);
1128 	newManagedAction(submenu, "lualatex", "L&uaLaTeX", SLOT(commandFromAction()), QKeySequence(), "compile-lua")->setData(BuildManager::CMD_LUALATEX);
1129 	submenu->addSeparator();
1130 	newManagedAction(submenu, "dvi2ps", tr("DVI->PS"), SLOT(commandFromAction()), QKeySequence(), "convert-dvips")->setData(BuildManager::CMD_DVIPS);
1131 	newManagedAction(submenu, "ps2pdf", tr("P&S->PDF"), SLOT(commandFromAction()), QKeySequence(), "convert-pspdf")->setData(BuildManager::CMD_PS2PDF);
1132 	newManagedAction(submenu, "dvipdf", tr("DV&I->PDF"), SLOT(commandFromAction()), QKeySequence(), "convert-dvipdf")->setData(BuildManager::CMD_DVIPDF);
1133 	submenu->addSeparator();
1134 	newManagedAction(submenu, "viewdvi", tr("View &DVI"), SLOT(commandFromAction()), QKeySequence(), "view-doc-dvi")->setData(BuildManager::CMD_VIEW_DVI);
1135 	newManagedAction(submenu, "viewps", tr("Vie&w PS"), SLOT(commandFromAction()), QKeySequence(), "view-doc-ps")->setData(BuildManager::CMD_VIEW_PS);
1136 	newManagedAction(submenu, "viewpdf", tr("View PD&F"), SLOT(commandFromAction()), QKeySequence(), "view-doc-pdf")->setData(BuildManager::CMD_VIEW_PDF);
1137 	submenu->addSeparator();
1138 	newManagedAction(submenu, "bibtex", tr("&Bibtex"), SLOT(commandFromAction()))->setData(BuildManager::CMD_BIBTEX);
1139 	newManagedAction(submenu, "bibtex8", tr("&Bibtex 8-Bit"), SLOT(commandFromAction()))->setData(BuildManager::CMD_BIBTEX8);
1140 	newManagedAction(submenu, "biber", tr("Bibe&r"), SLOT(commandFromAction()))->setData(BuildManager::CMD_BIBER);
1141 	submenu->addSeparator();
1142 	newManagedAction(submenu, "makeindex", tr("&MakeIndex"), SLOT(commandFromAction()))->setData(BuildManager::CMD_MAKEINDEX);
1143 	newManagedAction(submenu, "texindy", tr("&TexIndy"), SLOT(commandFromAction()), QKeySequence())->setData(BuildManager::CMD_TEXINDY);
1144 	newManagedAction(submenu, "makeglossaries", tr("&Makeglossaries"), SLOT(commandFromAction()), QKeySequence())->setData(BuildManager::CMD_MAKEGLOSSARIES);
1145 	submenu->addSeparator();
1146 	newManagedAction(submenu, "metapost", tr("&MetaPost"), SLOT(commandFromAction()))->setData(BuildManager::CMD_METAPOST);
1147 	newManagedAction(submenu, "asymptote", tr("&Asymptote"), SLOT(commandFromAction()))->setData(BuildManager::CMD_ASY);
1148 
1149 	submenu = newManagedMenu(menu, "user", tr("&User", "menu"));
1150 	updateUserToolMenu();
1151 	menu->addSeparator();
1152 	newManagedAction(menu, "clean", tr("Cle&an Auxiliary Files..."), SLOT(cleanAll()));
1153 	newManagedAction(menu, "terminal", tr("Open External &Terminal"), SLOT(openExternalTerminal()));
1154 	menu->addSeparator();
1155 	newManagedAction(menu, "viewlog", tr("View &Log"), SLOT(commandFromAction()), QKeySequence(), "viewlog")->setData(BuildManager::CMD_VIEW_LOG);
1156 	act = newManagedAction(menu, "logmarkers", tr("Show Log Markers"), nullptr, 0, "logmarkers");
1157 	act->setCheckable(true);
1158 	connect(act, SIGNAL(triggered(bool)), SLOT(setLogMarksVisible(bool)));
1159 	menu->addSeparator();
1160 	newManagedAction(menu, "htmlexport", tr("C&onvert to Html..."), SLOT(webPublish()));
1161 	newManagedAction(menu, "htmlsourceexport", tr("C&onvert Source to Html..."), SLOT(webPublishSource()));
1162 	menu->addSeparator();
1163 	newManagedAction(menu, "analysetext", tr("A&nalyse Text..."), SLOT(analyseText()));
1164 	newManagedAction(menu, "generaterandomtext", tr("Generate &Random Text..."), SLOT(generateRandomText()));
1165 	menu->addSeparator();
1166     newManagedAction(menu, "spelling", tr("Check Spelling..."), SLOT(editSpell()), MAC_OR_DEFAULT(Qt::CTRL | Qt::SHIFT | Qt::Key_F7, Qt::CTRL | Qt::Key_Colon));
1167     newManagedAction(menu, "thesaurus", tr("Thesaurus..."), SLOT(editThesaurus()), Qt::CTRL | Qt::SHIFT | Qt::Key_F8);
1168 	newManagedAction(menu, "wordrepetions", tr("Find Word Repetitions..."), SLOT(findWordRepetions()));
1169 
1170 	//  Latex/Math external
1171 	configManager.loadManagedMenus(":/uiconfig.xml");
1172 	// add some additional items
1173 	menu = newManagedMenu("main/latex", tr("&LaTeX"));
1174 	menu->setProperty("defaultSlot", QByteArray(SLOT(insertFromAction())));
1175     newManagedAction(menu, "insertrefnextlabel", tr("Insert \\ref to Next Label"), SLOT(editInsertRefToNextLabel()), filterLocaleShortcut(Qt::ALT | Qt::CTRL | Qt::Key_R));
1176 	newManagedAction(menu, "insertrefprevlabel", tr("Insert \\ref to Previous Label"), SLOT(editInsertRefToPrevLabel()));
1177 	submenu = newManagedMenu(menu, "tabularmanipulation", tr("Manipulate Tables", "table"));
1178 	newManagedAction(submenu, "addRow", tr("Add Row", "table"), SLOT(addRowCB()), QKeySequence(), "addRow");
1179 	newManagedAction(submenu, "addColumn", tr("Add Column", "table"), SLOT(addColumnCB()), QKeySequence(), "addCol");
1180 	newManagedAction(submenu, "removeRow", tr("Remove Row", "table"), SLOT(removeRowCB()), QKeySequence(), "remRow");
1181 	newManagedAction(submenu, "removeColumn", tr("Remove Column", "table"), SLOT(removeColumnCB()), QKeySequence(), "remCol");
1182 	newManagedAction(submenu, "cutColumn", tr("Cut Column", "table"), SLOT(cutColumnCB()), QKeySequence(), "cutCol");
1183 	newManagedAction(submenu, "pasteColumn", tr("Paste Column", "table"), SLOT(pasteColumnCB()), QKeySequence(), "pasteCol");
1184 	newManagedAction(submenu, "addHLine", tr("Add \\hline", "table"), SLOT(addHLineCB()));
1185 	newManagedAction(submenu, "remHLine", tr("Remove \\hline", "table"), SLOT(remHLineCB()));
1186 	newManagedAction(submenu, "insertTableTemplate", tr("Remodel Table Using Template", "table"), SLOT(insertTableTemplate()));
1187 	newManagedAction(submenu, "alignColumns", tr("Align Columns"), SLOT(alignTableCols()), QKeySequence(), "alignCols");
1188 	submenu = newManagedMenu(menu, "magicComments", tr("Add magic comments ..."));
1189 	newManagedAction(submenu, "addMagicRoot", tr("Insert root document name as TeX comment"), SLOT(addMagicRoot()));
1190 	newManagedAction(submenu, "addMagicLang", tr("Insert language as TeX comment"), SLOT(insertSpellcheckMagicComment()));
1191 	newManagedAction(submenu, "addMagicCoding", tr("Insert document coding as TeX comment"), SLOT(addMagicCoding()));
1192 	newManagedAction(submenu, "addMagicProgram", tr("Insert program as TeX comment"), SLOT(addMagicProgram()));
1193 	newManagedAction(submenu, "addMagicBibliography", tr("Insert bibliography tool as TeX comment"), SLOT(addMagicBibliography()));
1194 
1195 	menu = newManagedMenu("main/math", tr("&Math"));
1196 	menu->setProperty("defaultSlot", QByteArray(SLOT(insertFromAction())));
1197 	//wizards
1198 
1199 	menu = newManagedMenu("main/wizards", tr("&Wizards"));
1200 	newManagedAction(menu, "start", tr("Quick &Start..."), SLOT(quickDocument()));
1201 	newManagedAction(menu, "beamer", tr("Quick &Beamer Presentation..."), SLOT(quickBeamer()));
1202 	newManagedAction(menu, "letter", tr("Quick &Letter..."), SLOT(quickLetter()));
1203 
1204 	menu->addSeparator();
1205 	newManagedAction(menu, "tabular", tr("Quick &Tabular..."), SLOT(quickTabular()));
1206 	newManagedAction(menu, "tabbing", tr("Quick T&abbing..."), SLOT(quickTabbing()));
1207 	newManagedAction(menu, "array", tr("Quick &Array..."), SLOT(quickArray()));
1208 	newManagedAction(menu, "graphic", tr("Insert &Graphic..."), SLOT(quickGraphics()), QKeySequence(), "image");
1209 #ifdef Q_OS_WIN
1210 	newManagedAction(menu, "math", tr("Math Assistant..."), SLOT(quickMath()), QKeySequence(), "TexTablet");
1211 #endif
1212 
1213 	menu = newManagedMenu("main/bibliography", tr("&Bibliography"));
1214 	if (!bibtexEntryActions) {
1215 		bibtexEntryActions = new QActionGroup(this);
1216 		foreach (const BibTeXType &bt, BibTeXDialog::getPossibleEntryTypes(BibTeXDialog::BIBTEX)) {
1217 			QAction *act = newManagedAction(menu, "bibtex/" + bt.name.mid(1), bt.description, SLOT(insertBibEntryFromAction()));
1218 			act->setData(bt.name);
1219 			act->setActionGroup(bibtexEntryActions);
1220 		}
1221 	} else {
1222 		foreach (const BibTeXType &bt, BibTeXDialog::getPossibleEntryTypes(BibTeXDialog::BIBTEX))
1223 			newManagedAction(menu, "bibtex/" + bt.name.mid(1), bt.description, SLOT(insertBibEntryFromAction()))->setData(bt.name);
1224 	}
1225 
1226 	if (!biblatexEntryActions) {
1227 		biblatexEntryActions = new QActionGroup(this);
1228 		foreach (const BibTeXType &bt, BibTeXDialog::getPossibleEntryTypes(BibTeXDialog::BIBLATEX)) {
1229 			QAction *act = newManagedAction(menu, "biblatex/" + bt.name.mid(1), bt.description, SLOT(insertBibEntryFromAction()));
1230 			act->setData(bt.name);
1231 			act->setActionGroup(biblatexEntryActions);
1232 		}
1233 	} else {
1234 		foreach (const BibTeXType &bt, BibTeXDialog::getPossibleEntryTypes(BibTeXDialog::BIBLATEX))
1235 			newManagedAction(menu, "biblatex/" + bt.name.mid(1), bt.description, SLOT(insertBibEntryFromAction()))->setData(bt.name);
1236 	}
1237 	menu->addSeparator();
1238 	newManagedEditorAction(menu, "clean", tr("&Clean"), "cleanBib");
1239 	menu->addSeparator();
1240 	newManagedAction(menu, "dialog", tr("&Insert Bibliography Entry..."), SLOT(insertBibEntry()));
1241 	menu->addSeparator();
1242 	QMenu *bibTypeMenu = newManagedMenu(menu, "type", tr("Type"));
1243 	if (!bibTypeActions) {
1244 		bibTypeActions = new QActionGroup(this);
1245 		bibTypeActions->setExclusive(true);
1246 		act = newManagedAction(bibTypeMenu, "bibtex", tr("BibTeX"), SLOT(setBibTypeFromAction()));
1247 		act->setData("bibtex");
1248 		act->setCheckable(true);
1249 		act->setChecked(true);
1250 		bibTypeActions->addAction(act);
1251 		act = newManagedAction(bibTypeMenu, "biblatex", tr("BibLaTeX"), SLOT(setBibTypeFromAction()));
1252 		act->setData("biblatex");
1253 		act->setCheckable(true);
1254 		bibTypeActions->addAction(act);
1255 	}
1256 	act = newManagedAction(bibTypeMenu, "bibtex", tr("BibTeX"), SLOT(setBibTypeFromAction()));
1257 	act = newManagedAction(bibTypeMenu, "biblatex", tr("BibLaTeX"), SLOT(setBibTypeFromAction()));
1258 	act->trigger(); // initialize menu for specified type
1259 
1260 	//  User
1261         newManagedMenu("main/macros", tr("Ma&cros"));
1262 	updateUserMacros();
1263 	scriptengine::macros = &configManager.completerConfig->userMacros;
1264 
1265 	//---view---
1266 	menu = newManagedMenu("main/view", tr("&View"));
1267     newManagedAction(menu, "prevdocument", tr("Previous Document"), SLOT(gotoPrevDocument()), QList<QKeySequence>() << QKeySequence(Qt::CTRL | Qt::Key_PageUp) << MAC_OR_DEFAULT(QKeySequence(Qt::META  | Qt::SHIFT | Qt::Key_Tab),QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_Tab)));
1268     newManagedAction(menu, "nextdocument", tr("Next Document"), SLOT(gotoNextDocument()), QList<QKeySequence>() << QKeySequence(Qt::CTRL | Qt::Key_PageDown) << MAC_OR_DEFAULT(QKeySequence(Qt::META | Qt::Key_Tab),QKeySequence(Qt::CTRL | Qt::Key_Tab)));
1269 	newManagedMenu(menu, "documents", tr("Open Documents"));
1270 	newManagedAction(menu, "documentlist", tr("List Of Open Documents"), SLOT(viewDocumentList()));
1271 	newManagedAction(menu, "documentlisthidden", tr("List Of Hidden Documents"), SLOT(viewDocumentListHidden()));
1272 
1273     newManagedAction(menu, "focuseditor", tr("Focus Editor"), SLOT(focusEditor()), QList<QKeySequence>() << QKeySequence(Qt::ALT | Qt::CTRL | Qt::Key_Left));
1274     newManagedAction(menu, "focusviewer", tr("Focus Viewer"), SLOT(focusViewer()), QList<QKeySequence>() << QKeySequence(Qt::ALT | Qt::CTRL | Qt::Key_Right));
1275 
1276 	menu->addSeparator();
1277 	submenu = newManagedMenu(menu, "show", tr("Show"));
1278 	newManagedAction(submenu, "structureview", sidePanel->toggleViewAction());
1279 	newManagedAction(submenu, "outputview", outputView->toggleViewAction());
1280 	act = newManagedAction(submenu, "statusbar", tr("Statusbar"), SLOT(showStatusbar()));
1281 	act->setCheckable(true);
1282 	act->setChecked(configManager.getOption("View/ShowStatusbar").toBool());
1283 
1284 	newManagedAction(menu, "enlargePDF", tr("Show embedded PDF large"), SLOT(enlargeEmbeddedPDFViewer()));
1285 	newManagedAction(menu, "shrinkPDF", tr("Show embedded PDF small"), SLOT(shrinkEmbeddedPDFViewer()));
1286 
1287 	newManagedAction(menu, "closeelement", tr("Close Element"), SLOT(viewCloseElement()), Qt::Key_Escape);
1288 
1289 	menu->addSeparator();
1290 	submenu = newManagedMenu(menu, "collapse", tr("Collapse"));
1291 	newManagedEditorAction(submenu, "all", tr("Everything"), "foldEverything", 0, "", QList<QVariant>() << false);
1292 	newManagedAction(submenu, "block", tr("Nearest Block"), SLOT(viewCollapseBlock()));
1293 	for (int i = 1; i <= 4; i++)
1294 		newManagedEditorAction(submenu, QString::number(i), tr("Level %1").arg(i), "foldLevel", 0, "", QList<QVariant>() << false << i);
1295 	submenu = newManagedMenu(menu, "expand", tr("Expand"));
1296 	newManagedEditorAction(submenu, "all", tr("Everything"), "foldEverything", 0, "", QList<QVariant>() << true);
1297 	newManagedAction(submenu, "block", tr("Nearest Block"), SLOT(viewExpandBlock()));
1298 	for (int i = 1; i <= 4; i++)
1299 		newManagedEditorAction(submenu, QString::number(i), tr("Level %1").arg(i), "foldLevel", 0, "", QList<QVariant>() << true << i);
1300 
1301 	submenu = newManagedMenu(menu, "grammar", tr("Grammar errors"));
1302 	static bool showGrammarType[8] = {false};
1303 	for (int i = 0; i < 8; i++) configManager.registerOption(QString("Grammar/Display Error %1").arg(i), &showGrammarType[i], true);
1304 	newManagedAction(submenu, "0", tr("Word Repetition"), "toggleGrammar", 0, "", QList<QVariant>() << 0);
1305 	newManagedAction(submenu, "1", tr("Long-range Word Repetition"), "toggleGrammar", 0, "", QList<QVariant>() << 1);
1306 	newManagedAction(submenu, "2", tr("Bad words"), "toggleGrammar", 0, "", QList<QVariant>() << 2);
1307 	newManagedAction(submenu, "3", tr("Grammar Mistake"), "toggleGrammar", 0, "", QList<QVariant>() << 3);
1308 	for (int i = 4; i < 8; i++)
1309 		newManagedAction(submenu, QString("%1").arg(i), tr("Grammar Mistake Special %1").arg(i - 3), "toggleGrammar", 0, "", QList<QVariant>() << i);
1310 	for (int i = 0; i < submenu->actions().size(); i++)
1311         if (!submenu->actions().at(i)->isCheckable()) {
1312             submenu->actions().at(i)->setCheckable(true);
1313             configManager.linkOptionToObject(&showGrammarType[i], submenu->actions().at(i), LinkOptions());
1314             LatexEditorView::setGrammarOverlayDisabled(i, !submenu->actions().at(i)->isChecked());
1315 		}
1316 
1317 	menu->addSeparator();
1318 	submenu = newManagedMenu(menu, "editorZoom", tr("Editor Zoom"));
1319     newManagedEditorAction(submenu, "zoomIn", tr("Zoom In"), "zoomIn", QKeySequence(Qt::CTRL | Qt::Key_Plus));
1320     newManagedEditorAction(submenu, "zoomOut", tr("Zoom Out"), "zoomOut", QKeySequence(Qt::CTRL | Qt::Key_Minus));
1321     newManagedEditorAction(submenu, "resetZoom", tr("Reset Zoom"), "resetZoom", QKeySequence(Qt::CTRL | Qt::Key_0));
1322 
1323 	fullscreenModeAction = newManagedAction(menu, "fullscreenmode", tr("Full &Screen"), nullptr, QKeySequence::FullScreen);
1324 
1325 	fullscreenModeAction->setCheckable(true);
1326 	connectUnique(fullscreenModeAction, SIGNAL(toggled(bool)), this, SLOT(setFullScreenMode()));
1327 	connectUnique(menuBar(), SIGNAL(doubleClicked()), fullscreenModeAction, SLOT(toggle()));
1328 
1329 	menu->addSeparator();
1330 	QMenu *hlMenu = newManagedMenu(menu, "highlighting", tr("Highlighting"));
1331 	if (!highlightLanguageActions) {
1332 		highlightLanguageActions = new QActionGroup(this);
1333 		highlightLanguageActions->setExclusive(true);
1334         connect(highlightLanguageActions, SIGNAL(triggered(QAction*)), this, SLOT(viewSetHighlighting(QAction*)));
1335 		connect(hlMenu, SIGNAL(aboutToShow()), this, SLOT(showHighlightingMenu()));
1336 		int id = 0;
1337 		foreach (const QString &s, m_languages->languages()) {
1338 #ifdef QT_NO_DEBUG
1339 			if (s == "TXS Test Results") continue;
1340 #endif
1341 			QAction *act = newManagedAction(hlMenu, QString::number(id++), tr(qPrintable(s)));
1342 			act->setData(s);
1343 			act->setCheckable(true);
1344 			hlMenu->addAction(act);
1345 			highlightLanguageActions->addAction(act);
1346 		}
1347 	} else {
1348 		int id = 0;
1349 		foreach (const QString &s, m_languages->languages())
1350 			newManagedAction(hlMenu, QString::number(id++), tr(qPrintable(s)));
1351 	}
1352 
1353 	//---options---
1354 	menu = newManagedMenu("main/options", tr("&Options"));
1355 	newManagedAction(menu, "config", tr("&Configure TeXstudio..."), SLOT(generalOptions()), 0, "configure")->setMenuRole(QAction::PreferencesRole);
1356 
1357 	menu->addSeparator();
1358 	newManagedAction(menu, "loadProfile", tr("Load &Profile..."), SLOT(loadProfile()));
1359 	newManagedAction(menu, "saveProfile", tr("S&ave Profile..."), SLOT(saveProfile()));
1360 	newManagedAction(menu, "saveSettings", tr("Save &Current Settings", "menu"), SLOT(saveSettings()));
1361 	newManagedAction(menu, "restoreDefaultSettings", tr("Restore &Default Settings..."), SLOT(restoreDefaultSettings()));
1362 	menu->addSeparator();
1363 
1364 	submenu = newManagedMenu(menu, "rootdoc", tr("Root Document", "menu"));
1365 	actgroupRootDocMode = new QActionGroup(this);
1366 	actgroupRootDocMode->setExclusive(true);
1367 	actRootDocAutomatic = newManagedAction(submenu, "auto", tr("Detect &Automatically"), SLOT(setAutomaticRootDetection()));
1368 	actRootDocAutomatic->setCheckable(true);
1369 	actRootDocAutomatic->setChecked(true);
1370 	actgroupRootDocMode->addAction(actRootDocAutomatic);
1371 	actRootDocExplicit = newManagedAction(submenu, "currentExplicit", "Shows Current Explicit Root");
1372 	actRootDocExplicit->setCheckable(true);
1373 	actRootDocExplicit->setVisible(false);
1374 	actgroupRootDocMode->addAction(actRootDocExplicit);
1375 	actRootDocSetExplicit = newManagedAction(submenu, "setExplicit", tr("Set Current Document As Explicit Root"), SLOT(setCurrentDocAsExplicitRoot()));
1376 
1377 	//---help---
1378 	menu = newManagedMenu("main/help", tr("&Help"));
1379 	newManagedAction(menu, "latexreference", tr("LaTeX Reference..."), SLOT(latexHelp()), 0, "help-contents");
1380 	newManagedAction(menu, "usermanual", tr("User Manual..."), SLOT(userManualHelp()), 0, "help-contents");
1381 	newManagedAction(menu, "texdocdialog", tr("Packages Help..."), SLOT(texdocHelp()));
1382 
1383 	menu->addSeparator();
1384 	newManagedAction(menu, "checkinstall", tr("Check LaTeX Installation"), SLOT(checkLatexInstall()));
1385 	newManagedAction(menu, "checkcwls", tr("Check Active Completion Files"), SLOT(checkCWLs()));
1386     newManagedAction(menu, "checklt", tr("Check LanguageTool"), SLOT(checkLanguageTool()));
1387 	newManagedAction(menu, "bugreport", tr("Bugs Report/Feature Request"), SLOT(openBugsAndFeatures()));
1388 	newManagedAction(menu, "appinfo", tr("About TeXstudio..."), SLOT(helpAbout()), 0, APPICON)->setMenuRole(QAction::AboutRole);
1389 
1390 	//additional elements for development
1391 
1392 
1393 	//-----context menus-----
1394 	if (LatexEditorView::getBaseActions().empty()) { //only called at first menu created
1395 		QList<QAction *> baseContextActions;
1396 		QAction *sep = new QAction(menu);
1397 		sep->setSeparator(true);
1398         baseContextActions << getManagedActions(QStringList() << "cut" << "copy" << "paste", "main/edit/");
1399 		baseContextActions << getManagedActions(QStringList() << "main/edit2/pasteAsLatex" << "main/edit2/convertTo" << "main/edit/selection/selectAll");
1400 		baseContextActions << sep;
1401 		baseContextActions << getManagedActions(QStringList() << "previewLatex" << "removePreviewLatex", "main/edit2/");
1402 		LatexEditorView::setBaseActions(baseContextActions);
1403 	}
1404 
1405 	configManager.updateRecentFiles(true);
1406 
1407 	configManager.modifyMenuContents();
1408 	configManager.modifyManagedShortcuts();
1409 }
1410 /*! \brief set-up all tool-bars
1411  */
setupToolBars()1412 void Texstudio::setupToolBars()
1413 {
1414 	//This method will be called multiple times and must not create something if this something already exists
1415 
1416 	configManager.watchedMenus.clear();
1417 
1418 	//customizable toolbars
1419 	//first apply custom icons
1420 	QMap<QString, QVariant>::const_iterator i = configManager.replacedIconsOnMenus.constBegin();
1421 	while (i != configManager.replacedIconsOnMenus.constEnd()) {
1422 		QString id = i.key();
1423 		QString iconFilename = configManager.parseDir(i.value().toString());
1424 		QObject *obj = configManager.menuParent->findChild<QObject *>(id);
1425 		QAction *act = qobject_cast<QAction *>(obj);
1426 		if (act && !iconFilename.isEmpty()) act->setIcon(QIcon(iconFilename));
1427 		++i;
1428 	}
1429 	//setup customizable toolbars
1430 	for (int i = 0; i < configManager.managedToolBars.size(); i++) {
1431 		ManagedToolBar &mtb = configManager.managedToolBars[i];
1432 		if (!mtb.toolbar) { //create actual toolbar on first call
1433 			if (mtb.name == "Central") mtb.toolbar = centralToolBar;
1434 			else mtb.toolbar = addToolBar(tr(qPrintable(mtb.name)));
1435 			mtb.toolbar->setObjectName(mtb.name);
1436 			addAction(mtb.toolbar->toggleViewAction());
1437 			if (mtb.name == "Spelling") addToolBarBreak();
1438 		} else mtb.toolbar->clear();
1439 		foreach (const QString &actionName, mtb.actualActions) {
1440 			if (actionName == "separator") mtb.toolbar->addSeparator(); //Case 1: Separator
1441 			else if (actionName.startsWith("tags/")) {
1442 				//Case 2: One of the xml tag widgets mapped on a toolbutton
1443 				int tagCategorySep = actionName.indexOf("/", 5);
1444 				XmlTagsListWidget *tagsWidget = findChild<XmlTagsListWidget *>(actionName.left(tagCategorySep));
1445 				if (!tagsWidget) continue;
1446 				if (!tagsWidget->isPopulated())
1447 					tagsWidget->populate();
1448 				QStringList list = tagsWidget->tagsTxtFromCategory(actionName.mid(tagCategorySep + 1));
1449 				if (list.isEmpty()) continue;
1450 				QToolButton *combo = UtilsUi::createComboToolButton(mtb.toolbar, list, QList<QIcon>(), 0, this, SLOT(insertXmlTagFromToolButtonAction()));
1451 				combo->setProperty("tagsID", actionName);
1452 				mtb.toolbar->addWidget(combo);
1453 			} else {
1454 				QObject *obj = configManager.menuParent->findChild<QObject *>(actionName);
1455 				QAction *act = qobject_cast<QAction *>(obj);
1456 				if (act) {
1457 					//Case 3: A normal QAction
1458 					if (act->icon().isNull())
1459 						act->setIcon(QIcon(APPICON));
1460 					UtilsUi::updateToolTipWithShortcut(act, configManager.showShortcutsInTooltips);
1461 					mtb.toolbar->addAction(act);
1462 				} else {
1463 					QMenu *menu = qobject_cast<QMenu *>(obj);
1464 					if (!menu) {
1465 						qWarning("Unknown toolbar command %s", qPrintable(actionName));
1466 						continue;
1467 					}
1468 					//Case 4: A submenu mapped on a toolbutton
1469 					configManager.watchedMenus << actionName;
1470 					QStringList list;
1471 					QList<QIcon> icons;
1472 					foreach (const QAction *act, menu->actions())
1473 						if (!act->isSeparator()) {
1474 							list.append(act->text());
1475 							icons.append(act->icon());
1476 						}
1477 					//TODO: Is the callToolButtonAction()-slot really needed? Can't we just add the menu itself as the menu of the qtoolbutton, without creating a copy? (should be much faster)
1478 					QToolButton *combo = UtilsUi::createComboToolButton(mtb.toolbar, list, icons, 0, this, SLOT(callToolButtonAction()));
1479 					combo->setProperty("menuID", actionName);
1480 					mtb.toolbar->addWidget(combo);
1481 				}
1482 			}
1483 		}
1484 		if (mtb.actualActions.empty()) mtb.toolbar->setVisible(false);
1485 	}
1486 }
1487 
updateAvailableLanguages()1488 void Texstudio::updateAvailableLanguages()
1489 {
1490 	delete spellLanguageActions;
1491 
1492 	spellLanguageActions = new QActionGroup(statusTbLanguage);
1493 	spellLanguageActions->setExclusive(true);
1494 
1495 	foreach (const QString &s, spellerManager.availableDicts()) {
1496 		QAction *act = new QAction(spellLanguageActions);
1497 		act->setText(spellerManager.prettyName(s));
1498 		act->setData(QVariant(s));
1499 		act->setCheckable(true);
1500 		connect(act, SIGNAL(triggered()), this, SLOT(changeEditorSpeller()));
1501 	}
1502 
1503 	QAction *act = new QAction(spellLanguageActions);
1504 	act->setSeparator(true);
1505 	act = new QAction(spellLanguageActions);
1506 	act->setText(tr("Default") + QString(": %1").arg(spellerManager.prettyName(spellerManager.defaultSpellerName())));
1507 	act->setData(QVariant("<default>"));
1508 	connect(act, SIGNAL(triggered()), this, SLOT(changeEditorSpeller()));
1509 	act->setCheckable(true);
1510 	act->setChecked(true);
1511 
1512 	act = new QAction(spellLanguageActions);
1513 	act->setSeparator(true);
1514 	act = new QAction(spellLanguageActions);
1515 	act->setText(tr("Insert language as TeX comment"));
1516 	connect(act, SIGNAL(triggered()), this, SLOT(insertSpellcheckMagicComment()));
1517 
1518 	statusTbLanguage->addActions(spellLanguageActions->actions());
1519 
1520 	if (currentEditorView()) {
1521 		editorSpellerChanged(currentEditorView()->getSpeller());
1522 	} else {
1523 		editorSpellerChanged("<default>");
1524 	}
1525 }
1526 
updateLanguageToolStatus()1527 void Texstudio::updateLanguageToolStatus()
1528 {
1529     // adapt icon size to dpi
1530     double dpi=QGuiApplication::primaryScreen()->logicalDotsPerInch();
1531     double scale=dpi/96;
1532 
1533     int iconWidth=qRound(configManager.guiSecondaryToolbarIconSize*scale);
1534 
1535 	QIcon icon = getRealIconCached("languagetool");
1536     QSize iconSize = QSize(iconWidth, iconWidth);
1537 	switch (grammarCheck->languageToolStatus()) {
1538 		case GrammarCheck::LTS_Working:
1539 			statusLabelLanguageTool->setPixmap(icon.pixmap(iconSize));
1540 			statusLabelLanguageTool->setToolTip(QString(tr("Connected to LanguageTool at %1")).arg(grammarCheck->serverUrl()));
1541 			break;
1542 		case GrammarCheck::LTS_Error:
1543 			statusLabelLanguageTool->setPixmap(icon.pixmap(iconSize, QIcon::Disabled));
1544 			statusLabelLanguageTool->setToolTip(QString(tr("No LanguageTool server found at %1")).arg(grammarCheck->serverUrl()));
1545 			break;
1546 		case GrammarCheck::LTS_Unknown:
1547 			statusLabelLanguageTool->setPixmap(icon.pixmap(iconSize, QIcon::Disabled));
1548 			statusLabelLanguageTool->setToolTip(tr("LanguageTool status unknown"));
1549 	}
1550     if (!configManager.editorConfig->realtimeChecking || !configManager.editorConfig->inlineGrammarChecking) {
1551         statusLabelLanguageTool->setPixmap(icon.pixmap(iconSize, QIcon::Disabled));
1552         statusLabelLanguageTool->setToolTip(tr("Inline grammar checking disabled by user!"));
1553     }
1554 }
1555 
1556 /*! \brief set-up status bar
1557  */
createStatusBar()1558 void Texstudio::createStatusBar()
1559 {
1560 	QStatusBar *status = statusBar();
1561 	status->setContextMenuPolicy(Qt::PreventContextMenu);
1562 	status->setVisible(configManager.getOption("View/ShowStatusbar").toBool());
1563 
1564     // adapt icon size to dpi
1565     double dpi=QGuiApplication::primaryScreen()->logicalDotsPerInch();
1566     double scale=dpi/96;
1567 
1568     int iconWidth=qRound(configManager.guiSecondaryToolbarIconSize*scale);
1569 
1570     QSize iconSize = QSize(iconWidth, iconWidth);
1571 	QAction *act;
1572 	QToolButton *tb;
1573 	act = getManagedAction("main/view/show/structureview");
1574 	if (act) {
1575 		tb = new QToolButton(status);
1576 		tb->setCheckable(true);
1577 		tb->setChecked(act->isChecked());
1578 		tb->setAutoRaise(true);
1579 		tb->setIcon(act->icon());
1580 		tb->setIconSize(iconSize);
1581 		tb->setToolTip(act->toolTip());
1582 		connect(tb, SIGNAL(clicked()), act, SLOT(trigger()));
1583 		connect(act, SIGNAL(toggled(bool)), tb, SLOT(setChecked(bool)));
1584 		status->addPermanentWidget(tb, 0);
1585 	}
1586 	act = getManagedAction("main/view/show/outputview");
1587 	if (act) {
1588 		tb = new QToolButton(status);
1589 		tb->setCheckable(true);
1590 		tb->setChecked(act->isChecked());
1591 		tb->setAutoRaise(true);
1592 		tb->setIcon(act->icon());
1593 		tb->setIconSize(iconSize);
1594 		tb->setToolTip(act->toolTip());
1595 		connect(tb, SIGNAL(clicked()), act, SLOT(trigger()));
1596 		connect(act, SIGNAL(toggled(bool)), tb, SLOT(setChecked(bool)));
1597 		status->addPermanentWidget(tb, 0);
1598 	}
1599 
1600 	// spacer eating up all the space between "left" and "right" permanent widgets.
1601 	QLabel *messageArea = new QLabel(status);
1602 	connect(status, SIGNAL(messageChanged(QString)), messageArea, SLOT(setText(QString)));
1603 	status->addPermanentWidget(messageArea, 1);
1604 
1605 	// LanguageTool
1606 	connect(grammarCheck, SIGNAL(languageToolStatusChanged()), this, SLOT(updateLanguageToolStatus()));
1607 	statusLabelLanguageTool = new QLabel();
1608 	updateLanguageToolStatus();
1609 	status->addPermanentWidget(statusLabelLanguageTool);
1610 
1611 	// language
1612 	statusTbLanguage = new QToolButton(status);
1613 	statusTbLanguage->setToolTip(tr("Language"));
1614 	statusTbLanguage->setPopupMode(QToolButton::InstantPopup);
1615 	statusTbLanguage->setAutoRaise(true);
1616 	statusTbLanguage->setMinimumWidth(UtilsUi::getFmWidth(status->fontMetrics(), "OOOOOOO"));
1617 	connect(&spellerManager, SIGNAL(dictPathChanged()), this, SLOT(updateAvailableLanguages()));
1618 	connect(&spellerManager, SIGNAL(defaultSpellerChanged()), this, SLOT(updateAvailableLanguages()));
1619 	updateAvailableLanguages();
1620 	statusTbLanguage->setText(spellerManager.defaultSpellerName());
1621 	status->addPermanentWidget(statusTbLanguage, 0);
1622 
1623 	// encoding
1624 	statusTbEncoding = new QToolButton(status);
1625 	statusTbEncoding->setToolTip(tr("Encoding"));
1626 	statusTbEncoding->setText(tr("Encoding") + "  ");
1627 	statusTbEncoding->setPopupMode(QToolButton::InstantPopup);
1628 	statusTbEncoding->setAutoRaise(true);
1629 	statusTbEncoding->setMinimumWidth(UtilsUi::getFmWidth(status->fontMetrics(), "OOOOO"));
1630 
1631 	QSet<int> encodingMibs;
1632 	foreach (const QString &s, configManager.commonEncodings) {
1633 		QTextCodec *codec = QTextCodec::codecForName(s.toLocal8Bit());
1634 		if (!codec) continue;
1635 		encodingMibs.insert(codec->mibEnum());
1636 	}
1637 	foreach (int mib, encodingMibs) {
1638 		QAction *act = new QAction(statusTbEncoding);
1639 		act->setText(QTextCodec::codecForMib(mib)->name());
1640 		act->setData(mib);
1641 		statusTbEncoding->addAction(act);
1642 		connect(act, SIGNAL(triggered()), this, SLOT(changeTextCodec()));
1643 	}
1644 	act = new QAction(statusTbEncoding);
1645 	act->setSeparator(true);
1646 	statusTbEncoding->addAction(act);
1647 	act = new QAction(statusTbEncoding);
1648 	act->setText(tr("More Encodings..."));
1649 	statusTbEncoding->addAction(act);
1650 	connect(act, SIGNAL(triggered()), this, SLOT(editSetupEncoding()));
1651 	act = new QAction(statusTbEncoding);
1652 	act->setSeparator(true);
1653 	statusTbEncoding->addAction(act);
1654 
1655 	act = new QAction(statusTbEncoding);
1656 	act->setText(tr("Insert encoding as TeX comment"));
1657 	statusTbEncoding->addAction(act);
1658 	connect(act, SIGNAL(triggered()), this, SLOT(addMagicCoding()));
1659 
1660 	status->addPermanentWidget(statusTbEncoding, 0);
1661 
1662 
1663 	statusLabelMode = new QLabel(status);
1664 	statusLabelProcess = new QLabel(status);
1665 	status->addPermanentWidget(statusLabelProcess, 0);
1666 	status->addPermanentWidget(statusLabelMode, 0);
1667 	for (int i = 1; i <= 3; i++) {
1668 		QPushButton *pb = new QPushButton(getRealIcon(QString("bookmark%1").arg(i)), "", status);
1669 		pb->setIconSize(iconSize);
1670 		pb->setToolTip(tr("Go to bookmark") + QString(" %1").arg(i));
1671 		connect(pb, SIGNAL(clicked()), getManagedAction(QString("main/edit/gotoBookmark/bookmark%1").arg(i)), SIGNAL(triggered()));
1672 		pb->setFlat(true);
1673 		status->addPermanentWidget(pb, 0);
1674 	}
1675 }
1676 
updateCaption()1677 void Texstudio::updateCaption()
1678 {
1679 	if (!currentEditorView()) documents.currentDocument = nullptr;
1680 	else {
1681 		documents.currentDocument = currentEditorView()->document;
1682         //structureTreeView->setExpanded(documents.model->index(documents.currentDocument->baseStructure), true);
1683 	}
1684 	if (completer && completer->isVisible()) completer->close();
1685 	QString title;
1686 	if (!currentEditorView()) {
1687 		title = TEXSTUDIO;
1688 	} else {
1689 		QString file = QDir::toNativeSeparators(getCurrentFileName());
1690 		if (file.isEmpty())
1691 			file = currentEditorView()->displayNameForUI();
1692 		title = file + " - " + TEXSTUDIO;
1693 		updateStatusBarEncoding();
1694 		updateOpenDocumentMenu(true);
1695 		newDocumentLineEnding();
1696 	}
1697 	setWindowTitle(title);
1698 	//updateStructure();
1699 	updateUndoRedoStatus();
1700 	cursorPositionChanged();
1701 	if (documents.singleMode()) {
1702 		//outputView->resetMessagesAndLog();
1703 		if (currentEditorView()) completerNeedsUpdate();
1704 	}
1705 	QString finame = getCurrentFileName();
1706 	if (finame != "") configManager.lastDocument = finame;
1707 }
1708 
updateMasterDocumentCaption()1709 void Texstudio::updateMasterDocumentCaption()
1710 {
1711 	if (documents.singleMode()) {
1712 		actRootDocAutomatic->setChecked(true);
1713 		actRootDocExplicit->setVisible(false);
1714 		statusLabelMode->setText(QString(" %1 ").arg(tr("Automatic")));
1715 		statusLabelMode->setToolTip(tr("Automatic root document detection active"));
1716 	} else {
1717 		QString shortName = documents.masterDocument->getFileInfo().fileName();
1718 		actRootDocExplicit->setChecked(true);
1719 		actRootDocExplicit->setVisible(true);
1720 		actRootDocExplicit->setText(tr("&Explicit") + ": " + shortName);
1721 		statusLabelMode->setText(QString(" %1 ").arg(tr("Root", "explicit root document") + ": " + shortName));
1722 		statusLabelMode->setToolTip(QString(tr("Explict root document:\n%1")).arg(shortName));
1723 	}
1724 }
1725 
currentEditorChanged()1726 void Texstudio::currentEditorChanged()
1727 {
1728 	updateCaption();
1729 #ifdef INTERNAL_TERMINAL
1730 	outputView->getTerminalWidget()->setCurrentFileName(getCurrentFileName());
1731 #endif
1732 	if (!currentEditorView()) return;
1733 	if (configManager.watchedMenus.contains("main/view/documents"))
1734 		updateToolBarMenu("main/view/documents");
1735 	editorSpellerChanged(currentEditorView()->getSpeller());
1736 	currentEditorView()->lastUsageTime = QDateTime::currentDateTime();
1737 	currentEditorView()->checkRTLLTRLanguageSwitching();
1738     // update global toc
1739     updateTOCs();
1740 }
1741 
1742 /*!
1743  * \brief called when a editor tab is moved in position
1744  * \param from starting position
1745  * \param to ending position
1746  */
editorTabMoved(int from,int to)1747 void Texstudio::editorTabMoved(int from, int to)
1748 {
1749 	documents.move(from, to);
1750     // update structure
1751     updateStructureLocally();
1752 }
1753 
editorAboutToChangeByTabClick(LatexEditorView * edFrom,LatexEditorView * edTo)1754 void Texstudio::editorAboutToChangeByTabClick(LatexEditorView *edFrom, LatexEditorView *edTo)
1755 {
1756 	Q_UNUSED(edTo)
1757 	saveEditorCursorToHistory(edFrom);
1758 }
1759 
showMarkTooltipForLogMessage(QList<int> errors)1760 void Texstudio::showMarkTooltipForLogMessage(QList<int> errors)
1761 {
1762 	if (!currentEditorView()) return;
1763 	REQUIRE(outputView->getLogWidget());
1764 	REQUIRE(outputView->getLogWidget()->getLogModel());
1765 	QString msg = outputView->getLogWidget()->getLogModel()->htmlErrorTable(errors);
1766 	currentEditorView()->setLineMarkToolTip(msg);
1767 }
1768 
newDocumentLineEnding()1769 void Texstudio::newDocumentLineEnding()
1770 {
1771 	if (!currentEditorView()) return;
1772 	QDocument::LineEnding le = currentEditorView()->editor->document()->lineEnding();
1773 	if (le == QDocument::Conservative) le = currentEditorView()->editor->document()->originalLineEnding();
1774 	switch (le) {
1775 #ifdef Q_OS_WIN32
1776 	case QDocument::Local:
1777 #endif
1778 	case QDocument::Windows:
1779 		getManagedAction("main/edit/lineend/crlf")->setChecked(true);
1780 		break;
1781 	case QDocument::Mac:
1782 		getManagedAction("main/edit/lineend/cr")->setChecked(true);
1783 		break;
1784 	default:
1785 		getManagedAction("main/edit/lineend/lf")->setChecked(true);
1786 	}
1787 }
1788 
updateUndoRedoStatus()1789 void Texstudio::updateUndoRedoStatus()
1790 {
1791 	if (currentEditor()) {
1792 		actSave->setEnabled(!currentEditor()->document()->isClean() || currentEditor()->fileName().isEmpty());
1793 		bool canUndo = currentEditor()->document()->canUndo();
1794 		if (!canUndo && configManager.svnUndo) {
1795 			QVariant zw = currentEditor()->property("undoRevision");
1796 			int undoRevision = zw.canConvert<int>() ? zw.toInt() : 0;
1797 			if (undoRevision >= 0)
1798 				canUndo = true;
1799 		}
1800 		actUndo->setEnabled(canUndo);
1801 		actRedo->setEnabled(currentEditor()->document()->canRedo());
1802 	} else {
1803 		actSave->setEnabled(false);
1804 		actUndo->setEnabled(false);
1805 		actRedo->setEnabled(false);
1806 	}
1807 }
1808 /*!
1809  * \brief return current editor
1810  *
1811  * return current editorview
1812  * \return current editor (LatexEditorView)
1813  */
currentEditorView() const1814 LatexEditorView *Texstudio::currentEditorView() const
1815 {
1816 	return editors->currentEditor();
1817 }
1818 
1819 /*!
1820  * \brief return current editor
1821  *
1822  * return current editor
1823  * \return current editor (QEditor)
1824  */
currentEditor() const1825 QEditor *Texstudio::currentEditor() const
1826 {
1827     LatexEditorView *edView = currentEditorView();
1828     if (!edView) return nullptr;
1829     return edView->editor;
1830 }
1831 
configureNewEditorView(LatexEditorView * edit)1832 void Texstudio::configureNewEditorView(LatexEditorView *edit)
1833 {
1834     REQUIRE(m_languages);
1835     REQUIRE(edit->codeeditor);
1836     m_languages->setLanguage(edit->codeeditor->editor(), ".tex");
1837 
1838     connect(edit->editor, SIGNAL(undoAvailable(bool)), this, SLOT(updateUndoRedoStatus()));
1839     connect(edit->editor, SIGNAL(requestClose()), &documents, SLOT(requestedClose()));
1840     connect(edit->editor, SIGNAL(redoAvailable(bool)), this, SLOT(updateUndoRedoStatus()));
1841     connect(edit->editor->document(), SIGNAL(lineEndingChanged(int)), this, SLOT(newDocumentLineEnding()));
1842     connect(edit->editor, SIGNAL(cursorPositionChanged()), this, SLOT(cursorPositionChanged()));
1843     connect(edit->editor, SIGNAL(cursorHovered()), this, SLOT(cursorHovered()));
1844     connect(edit->editor, SIGNAL(emitWordDoubleClicked()), this, SLOT(cursorHovered()));
1845     connect(edit, SIGNAL(showMarkTooltipForLogMessage(QList<int>)), this, SLOT(showMarkTooltipForLogMessage(QList<int>)));
1846     connect(edit, SIGNAL(needCitation(const QString&)), this, SLOT(insertBibEntry(const QString&)));
1847     connect(edit, SIGNAL(showPreview(QString)), this, SLOT(showPreview(QString)));
1848     connect(edit, SIGNAL(showImgPreview(QString)), this, SLOT(showImgPreview(QString)));
1849     connect(edit, SIGNAL(showPreview(QDocumentCursor)), this, SLOT(showPreview(QDocumentCursor)));
1850     connect(edit, SIGNAL(showFullPreview()), this, SLOT(recompileForPreview()));
1851     connect(edit, SIGNAL(gotoDefinition(QDocumentCursor)), this, SLOT(editGotoDefinition(QDocumentCursor)));
1852     connect(edit, SIGNAL(findLabelUsages(LatexDocument*,QString)), this, SLOT(findLabelUsages(LatexDocument*,QString)));
1853     connect(edit, SIGNAL(syncPDFRequested(QDocumentCursor)), this, SLOT(syncPDFViewer(QDocumentCursor)));
1854     connect(edit, SIGNAL(openFile(QString)), this, SLOT(openExternalFile(QString)));
1855     connect(edit, SIGNAL(openFile(QString,QString)), this, SLOT(openExternalFile(QString,QString)));
1856     connect(edit, SIGNAL(bookmarkRemoved(QDocumentLineHandle*)), bookmarks, SLOT(bookmarkDeleted(QDocumentLineHandle*)));
1857     connect(edit, SIGNAL(bookmarkAdded(QDocumentLineHandle*,int)), bookmarks, SLOT(bookmarkAdded(QDocumentLineHandle*,int)));
1858     connect(edit, SIGNAL(mouseBackPressed()), this, SLOT(goBack()));
1859     connect(edit, SIGNAL(mouseForwardPressed()), this, SLOT(goForward()));
1860     connect(edit, SIGNAL(cursorChangeByMouse()), this, SLOT(saveCurrentCursorToHistory()));
1861     connect(edit, SIGNAL(openCompleter()), this, SLOT(normalCompletion()));
1862     connect(edit, SIGNAL(openInternalDocViewer(QString,QString)), this, SLOT(openInternalDocViewer(QString,QString)));
1863     connect(edit, SIGNAL(showExtendedSearch()), this, SLOT(showExtendedSearch()));
1864     connect(edit, SIGNAL(execMacro(Macro,MacroExecContext)), this, SLOT(execMacro(Macro,MacroExecContext)));
1865 
1866     connect(edit->editor, SIGNAL(fileReloaded()), this, SLOT(fileReloaded()));
1867     connect(edit->editor, SIGNAL(fileInConflictShowDiff()), this, SLOT(fileInConflictShowDiff()));
1868     connect(edit->editor, SIGNAL(fileAutoReloading(QString)), this, SLOT(fileAutoReloading(QString)));
1869 
1870     if (Guardian::instance()) { // Guardian is not yet there when this is called at program startup
1871         connect(edit->editor, SIGNAL(slowOperationStarted()), Guardian::instance(), SLOT(slowOperationStarted()));
1872         connect(edit->editor, SIGNAL(slowOperationEnded()), Guardian::instance(), SLOT(slowOperationEnded()));
1873     }
1874     connect(edit, SIGNAL(linesChanged(QString,LatexDocument *,QList<LineInfo>,int)), grammarCheck, SLOT(check(QString,LatexDocument *,QList<LineInfo>,int)));
1875 
1876     connect(edit, SIGNAL(spellerChanged(QString)), this, SLOT(editorSpellerChanged(QString)));
1877     connect(edit->editor, SIGNAL(focusReceived()), edit, SIGNAL(focusReceived()));
1878 }
1879 
1880 /*!
1881  * \brief complete the new editor view configuration (edit->document is set)
1882  * \param edit used editorview
1883  * \param reloadFromDoc
1884  * \param hidden if editor is not shown
1885  */
configureNewEditorViewEnd(LatexEditorView * edit,bool reloadFromDoc,bool hidden)1886 void Texstudio::configureNewEditorViewEnd(LatexEditorView *edit, bool reloadFromDoc, bool hidden)
1887 {
1888     REQUIRE(edit->document);
1889     // set speller here as document is needed
1890     edit->setSpellerManager(&spellerManager);
1891     edit->setSpeller("<default>");
1892     //patch Structure
1893     //disconnect(edit->editor->document(),SIGNAL(contentsChange(int, int))); // force order of contentsChange update
1894     connect(edit->editor->document(), SIGNAL(contentsChange(int,int)), edit->document, SLOT(patchStructure(int,int)));
1895     //connect(edit->editor->document(),SIGNAL(contentsChange(int, int)),edit,SLOT(documentContentChanged(int,int))); now directly called by patchStructure
1896     connect(edit->editor->document(), SIGNAL(lineRemoved(QDocumentLineHandle*)), edit->document, SLOT(patchStructureRemoval(QDocumentLineHandle*)));
1897     connect(edit->editor->document(), SIGNAL(lineDeleted(QDocumentLineHandle*,int)), edit->document, SLOT(patchStructureRemoval(QDocumentLineHandle*,int)));
1898     connect(edit->document, SIGNAL(updateCompleter()), this, SLOT(completerNeedsUpdate()));
1899     connect(edit->editor, SIGNAL(needUpdatedCompleter()), this, SLOT(needUpdatedCompleter()));
1900     connect(edit->document, SIGNAL(importPackage(QString)), this, SLOT(importPackage(QString)));
1901     connect(edit->document, SIGNAL(bookmarkLineUpdated(int)), bookmarks, SLOT(updateLineWithBookmark(int)));
1902     connect(edit->document, SIGNAL(encodingChanged()), this, SLOT(updateStatusBarEncoding()));
1903     connect(edit, SIGNAL(thesaurus(int,int)), this, SLOT(editThesaurus(int,int)));
1904     connect(edit, SIGNAL(changeDiff(QPoint)), this, SLOT(editChangeDiff(QPoint)));
1905     connect(edit, SIGNAL(saveCurrentCursorToHistoryRequested()), this, SLOT(saveCurrentCursorToHistory()));
1906     connect(edit->document,SIGNAL(structureUpdated(LatexDocument*)),this,SLOT(updateTOCs()));
1907     edit->document->saveLineSnapshot(); // best guess of the lines used during last latex compilation
1908 
1909     if (!hidden) {
1910         int index = reloadFromDoc ? documents.documents.indexOf(edit->document, 0) : -1; // index: we still assume here that the order of documents and editors is synchronized
1911         editors->insertEditor(edit, index);
1912         edit->editor->setFocus();
1913         updateCaption();
1914     }
1915 }
1916 /*!
1917  * \brief get editor which handles FileName
1918  *
1919  * get editor which handles FileName
1920  *
1921  * \param fileName
1922  * \param checkTemporaryNames
1923  * \return editorview, 0 if no editor matches
1924  */
getEditorViewFromFileName(const QString & fileName,bool checkTemporaryNames)1925 LatexEditorView *Texstudio::getEditorViewFromFileName(const QString &fileName, bool checkTemporaryNames)
1926 {
1927 	LatexDocument *document = documents.findDocument(fileName, checkTemporaryNames);
1928 	if (!document) return nullptr;
1929 	return document->getEditorView();
1930 }
1931 
1932 /*!
1933  * \brief get the editor referenced by a given line handle
1934  * \param dlh the line handle
1935  * \return the editor view, null if the handle is null
1936  */
getEditorViewFromHandle(const QDocumentLineHandle * dlh)1937 LatexEditorView *Texstudio::getEditorViewFromHandle(const QDocumentLineHandle *dlh)
1938 {
1939 	if (!dlh) return nullptr;
1940 	LatexDocument *targetDoc = qobject_cast<LatexDocument *>(dlh->document());
1941 	REQUIRE_RET(targetDoc, nullptr);
1942 	return qobject_cast<LatexEditorView *>(targetDoc->getEditorView());
1943 }
1944 
1945 /*!
1946  * \brief get filename of current editor
1947  *
1948  * get filename of current editor
1949  * \return filename
1950  */
getCurrentFileName()1951 QString Texstudio::getCurrentFileName()
1952 {
1953 	return documents.getCurrentFileName();
1954 }
1955 
getAbsoluteFilePath(const QString & relName,const QString & extension)1956 QString Texstudio::getAbsoluteFilePath(const QString &relName, const QString &extension)
1957 {
1958 	return documents.getAbsoluteFilePath(relName, extension);
1959 }
1960 
getRelativeFileName(const QString & file,QString basepath,bool keepSuffix)1961 QString Texstudio::getRelativeFileName(const QString &file, QString basepath, bool keepSuffix)
1962 {
1963 	return getRelativeBaseNameToPath(file, basepath, true, keepSuffix);
1964 }
fileExists(const QString & file)1965 bool Texstudio::fileExists(const QString &file){
1966     return QFileInfo::exists(file);
1967 }
1968 
activateEditorForFile(QString f,bool checkTemporaryNames,bool setFocus)1969 bool Texstudio::activateEditorForFile(QString f, bool checkTemporaryNames, bool setFocus)
1970 {
1971 	LatexEditorView *edView = getEditorViewFromFileName(f, checkTemporaryNames);
1972 	if (!edView) return false;
1973 	saveCurrentCursorToHistory();
1974 	if (!editors->containsEditor(edView)) return false;
1975 	editors->setCurrentEditor(edView, setFocus);
1976 	return true;
1977 }
1978 
1979 ///////////////////FILE//////////////////////////////////////
1980 
guessLanguageFromContent(QLanguageFactory * m_languages,QEditor * e)1981 void guessLanguageFromContent(QLanguageFactory *m_languages, QEditor *e)
1982 {
1983 	QDocument *doc = e->document();
1984 	if (doc->lineCount() == 0) return;
1985 	if (doc->line(0).text().startsWith("<?xml") ||
1986 	        doc->line(0).text().startsWith("<!DOCTYPE"))
1987 		m_languages->setLanguage(e, ".xml");
1988 }
1989 /*!
1990  * \brief load file
1991  *
1992  * load file from disc
1993  * \param f filename
1994  * \param asProject load file as master-file
1995  * \param hidden hide editor
1996  * \param recheck
1997  * \param dontAsk
1998  * \return
1999  */
load(const QString & f,bool asProject,bool hidden,bool recheck,bool dontAsk)2000 LatexEditorView *Texstudio::load(const QString &f , bool asProject, bool hidden, bool recheck, bool dontAsk)
2001 {
2002     QString f_real = f;
2003 #ifdef Q_OS_WIN32
2004     QRegExp regcheck("/([a-zA-Z]:[/\\\\].*)");
2005     if (regcheck.exactMatch(f)) f_real = regcheck.cap(1);
2006 #endif
2007 
2008 #ifndef NO_POPPLER_PREVIEW
2009     if (f_real.endsWith(".pdf", Qt::CaseInsensitive)) {
2010         if (PDFDocument::documentList().isEmpty())
2011             newPdfPreviewer();
2012         PDFDocument::documentList().at(0)->loadFile(f_real);
2013         PDFDocument::documentList().at(0)->show();
2014         PDFDocument::documentList().at(0)->setFocus();
2015         return nullptr;
2016     }
2017     if ((f_real.endsWith(".synctex.gz", Qt::CaseInsensitive) ||
2018          f_real.endsWith(".synctex", Qt::CaseInsensitive))
2019             && UtilsUi::txsConfirm(tr("Do you want to debug a SyncTeX file?"))) {
2020         fileNewInternal();
2021         currentEditor()->document()->setText(PDFDocument::debugSyncTeX(f_real), false);
2022         return currentEditorView();
2023     }
2024 #endif
2025 
2026     if (f_real.endsWith(".log", Qt::CaseInsensitive) &&
2027             UtilsUi::txsConfirm(QString("Do you want to load file %1 as LaTeX log file?").arg(QFileInfo(f).completeBaseName()))) {
2028         outputView->getLogWidget()->loadLogFile(f, documents.getTemporaryCompileFileName(), QTextCodec::codecForName(configManager.logFileEncoding.toLatin1()));
2029         setLogMarksVisible(true);
2030         return nullptr;
2031     }
2032 
2033     if (!hidden)
2034         raise();
2035 
2036     //test is already opened
2037     LatexEditorView *existingView = getEditorViewFromFileName(f_real);
2038     LatexDocument *doc=nullptr;
2039     if (!existingView) {
2040         doc = documents.findDocumentFromName(f_real);
2041         if (doc) existingView = doc->getEditorView();
2042     }
2043     if (existingView) {
2044         if (hidden)
2045             return existingView;
2046         if (asProject) documents.setMasterDocument(existingView->document);
2047         if (existingView->document->isHidden()) {
2048             // clear baseStructure outside treeview context
2049             /*foreach(StructureEntry *elem,existingView->document->baseStructure->children){
2050                     delete elem;
2051                 }
2052                 existingView->document->baseStructure->children.clear();*/
2053             //
2054             existingView->editor->setLineWrapping(configManager.editorConfig->wordwrap > 0);
2055             documents.deleteDocument(existingView->document, true);
2056             existingView->editor->setSilentReloadOnExternalChanges(existingView->document->remeberAutoReload);
2057             existingView->editor->setHidden(false);
2058             documents.addDocument(existingView->document, false);
2059             editors->addEditor(existingView);
2060             if(asProject)
2061                 editors->moveEditor(existingView,Editors::AbsoluteFront); // somewhat redundant, but we run into that problem with issue #899
2062             //updateStructure(false, existingView->document, true);
2063             existingView->editor->setFocus();
2064             updateCaption();
2065             return existingView;
2066         }
2067         editors->setCurrentEditor(existingView);
2068         return existingView;
2069     }
2070 
2071     // find closed master doc
2072     if (doc) {
2073         LatexEditorView *edit = new LatexEditorView(nullptr, configManager.editorConfig, doc);
2074         edit->setLatexPackageList(&latexPackageList);
2075         edit->document = doc;
2076         edit->editor->setFileName(doc->getFileName());
2077         edit->setHelp(&help);
2078         disconnect(edit->editor->document(), SIGNAL(contentsChange(int, int)), edit->document, SLOT(patchStructure(int, int)));
2079         configureNewEditorView(edit);
2080         if (edit->editor->fileInfo().suffix().toLower() != "tex")
2081             m_languages->setLanguage(edit->editor, f_real);
2082         if (!edit->editor->languageDefinition())
2083             guessLanguageFromContent(m_languages, edit->editor);
2084 
2085         doc->setLineEnding(edit->editor->document()->originalLineEnding());
2086         doc->setEditorView(edit); //update file name (if document didn't exist)
2087 
2088         configureNewEditorViewEnd(edit, !hidden, hidden);
2089 
2090         if (!hidden) {
2091             bookmarks->restoreBookmarks(edit);
2092         }
2093         return edit;
2094     }
2095 
2096     //load it otherwise
2097     if (!QFile::exists(f_real)) return nullptr;
2098     QFile file(f_real);
2099     if (!file.open(QIODevice::ReadOnly)) {
2100         if (!hidden && !dontAsk)
2101             QMessageBox::warning(this, tr("Error"), tr("You do not have read permission to the file %1.").arg(f_real));
2102         return nullptr;
2103     }
2104     file.close();
2105 
2106     bool bibTeXmodified = documents.bibTeXFilesModified;
2107 
2108     doc = new LatexDocument(this);
2109     doc->setCenterDocumentInEditor(configManager.editorConfig->centerDocumentInEditor);
2110     doc->enableSyntaxCheck(configManager.editorConfig->inlineSyntaxChecking && configManager.editorConfig->realtimeChecking);
2111     LatexEditorView *edit = new LatexEditorView(nullptr, configManager.editorConfig, doc);
2112     edit->setLatexPackageList(&latexPackageList);
2113     edit->setHelp(&help);
2114     if (hidden) {
2115         edit->editor->setLineWrapping(false); //disable linewrapping in hidden docs to speed-up updates
2116         doc->clearWidthConstraint();
2117     }
2118     configureNewEditorView(edit);
2119 
2120     edit->document = documents.findDocument(f_real);
2121     if (!edit->document) {
2122         edit->document = doc;
2123         edit->document->setEditorView(edit);
2124         documents.addDocument(edit->document, hidden);
2125     } else edit->document->setEditorView(edit);
2126 
2127     if (configManager.recentFileHighlightLanguage.contains(f_real))
2128         m_languages->setLanguage(edit->editor, configManager.recentFileHighlightLanguage.value(f_real));
2129     else if (edit->editor->fileInfo().suffix().toLower() != "tex")
2130         m_languages->setLanguage(edit->editor, f_real);
2131 
2132     edit->editor->load(f_real, QDocument::defaultCodec());
2133 
2134     if (!edit->editor->languageDefinition())
2135         guessLanguageFromContent(m_languages, edit->editor);
2136 
2137     edit->editor->document()->setLineEndingDirect(edit->editor->document()->originalLineEnding());
2138 
2139     edit->document->setEditorView(edit); //update file name (if document didn't exist)
2140 
2141     configureNewEditorViewEnd(edit, asProject, hidden);
2142 
2143     //check for svn conflict
2144 	if (!hidden) {
2145 		checkSVNConflicted();
2146 
2147 		MarkCurrentFileAsRecent();
2148 	}
2149 
2150     documents.updateMasterSlaveRelations(doc, recheck);
2151 
2152     if (recheck || hidden) {
2153 		doc->updateLtxCommands();
2154 	}
2155 
2156 	if (!hidden) {
2157 		if (QFile::exists(f_real + ".recover.bak~")
2158 		        && QFileInfo(f_real + ".recover.bak~").lastModified() > QFileInfo(f_real).lastModified()) {
2159             if (UtilsUi::txsConfirm(tr("A crash recover file from %1 has been found for \"%2\".\nDo you want to restore it?").arg(QFileInfo(f_real + ".recover.bak~").lastModified().toString(),f_real))) {
2160 				QFile f(f_real + ".recover.bak~");
2161 				if (f.open(QFile::ReadOnly)) {
2162 					QByteArray ba = f.readAll();
2163 					QString recovered = QTextCodec::codecForName("UTF-8")->toUnicode(ba); //TODO: chunk loading?
2164 					edit->document->setText(recovered, true);
2165 				} else UtilsUi::txsWarning(tr("Failed to open recover file \"%1\".").arg(f_real + ".recover.bak~"));
2166 			}
2167 		}
2168 
2169 	}
2170 
2171 	updateStructure(true, doc, true);
2172 
2173 	bookmarks->restoreBookmarks(edit);
2174 
2175     if (asProject) documents.setMasterDocument(edit->document);
2176 
2177 	if (outputView->getLogWidget()->logPresent()) {
2178 		updateLogEntriesInEditors();
2179 		setLogMarksVisible(true);
2180 	}
2181 	if (!bibTeXmodified)
2182 		documents.bibTeXFilesModified = false; //loading a file can change the list of included bib files, but we won't consider that as a modification of them, because then they don't have to be recompiled
2183 	LatexDocument *rootDoc = edit->document->getRootDocument();
2184     if (rootDoc) {
2185         foreach (const FileNamePair &fnp, edit->document->mentionedBibTeXFiles().values()) {
2186 			Q_ASSERT(!fnp.absolute.isEmpty());
2187 			rootDoc->lastCompiledBibTeXFiles.insert(fnp.absolute);
2188 		}
2189     }
2190 
2191 #ifndef Q_OS_MAC
2192 	if (!hidden) {
2193 		if (windowState() == Qt::WindowMinimized || !isVisible() || !QApplication::activeWindow()) {
2194 			show();
2195 			if (windowState() == Qt::WindowMinimized)
2196 				setWindowState((windowState() & ~Qt::WindowMinimized) | Qt::WindowActive);
2197 			show();
2198 			raise();
2199 			QApplication::setActiveWindow(this);
2200 			activateWindow();
2201 			setFocus();
2202 			edit->editor->setFocus();
2203 		}
2204 	}
2205 #endif
2206 
2207 	runScriptsInList(Macro::ST_LOAD_THIS_FILE, doc->localMacros);
2208 
2209 	emit infoLoadFile(f_real);
2210 
2211 	return edit;
2212 }
2213 
completerNeedsUpdate()2214 void Texstudio::completerNeedsUpdate()
2215 {
2216 	mCompleterNeedsUpdate = true;
2217 }
2218 
needUpdatedCompleter()2219 void Texstudio::needUpdatedCompleter()
2220 {
2221 	if (mCompleterNeedsUpdate)
2222 		updateCompleter();
2223 }
2224 
updateUserToolMenu()2225 void Texstudio::updateUserToolMenu()
2226 {
2227 	CommandMapping cmds = buildManager.getAllCommands();
2228 	QStringList order = buildManager.getCommandsOrder();
2229 	QStringList ids;
2230 	QStringList displayName;
2231 	for (int i = 0; i < order.size(); i++) {
2232 		const CommandInfo &ci = cmds.value(order[i]);
2233 		if (!ci.user) continue;
2234 		ids << ci.id;
2235 		displayName << ci.displayName;
2236 	}
2237     configManager.updateListMenu("main/tools/user", displayName, "cmd", true, SLOT(commandFromAction()), Qt::ALT | Qt::SHIFT | Qt::Key_F1, false, 0);
2238 	QMenu *m = getManagedMenu("main/tools/user");
2239 	REQUIRE(m);
2240 	QList<QAction *> actions = m->actions();
2241 	for (int i = 0; i < actions.size(); i++)
2242 		actions[i]->setData(BuildManager::TXS_CMD_PREFIX + ids[i]);
2243 }
2244 
2245 #include "QMetaMethod"
linkToEditorSlot(QAction * act,const char * methodName,const QList<QVariant> & args)2246 void Texstudio::linkToEditorSlot(QAction *act, const char *methodName, const QList<QVariant> &args)
2247 {
2248 	REQUIRE(act);
2249 	connectUnique(act, SIGNAL(triggered()), this, SLOT(relayToEditorSlot()));
2250 	act->setProperty("primarySlot", QString(SLOT(relayToEditorSlot())));
2251 	QByteArray signature = createMethodSignature(methodName, args);
2252 	if (!args.isEmpty())
2253 		act->setProperty("args", QVariant::fromValue<QList<QVariant> >(args));
2254 	for (int i = 0; i < LatexEditorView::staticMetaObject.methodCount(); i++)
2255 		if (signature == LatexEditorView::staticMetaObject.method(i).methodSignature()) {
2256 			act->setProperty("editorViewSlot", methodName);
2257 			return;
2258 		} //else qDebug() << LatexEditorView::staticMetaObject.method(i).signature();
2259 	for (int i = 0; i < QEditor::staticMetaObject.methodCount(); i++)
2260 		if (signature == QEditor::staticMetaObject.method(i).methodSignature()) {
2261 			act->setProperty("editorSlot", methodName);
2262 			return;
2263 		}
2264 
2265 	qDebug() << methodName << signature;
2266 	Q_ASSERT(false);
2267 }
2268 
relayToEditorSlot()2269 void Texstudio::relayToEditorSlot()
2270 {
2271 	if (!currentEditorView()) return;
2272 	QAction *act = qobject_cast<QAction *>(sender());
2273 	REQUIRE(act);
2274 	if (act->property("editorViewSlot").isValid()) QMetaObjectInvokeMethod(currentEditorView(), qPrintable(act->property("editorViewSlot").toString()), act->property("args").value<QList<QVariant> >());
2275 	else if (act->property("editorSlot").isValid()) QMetaObjectInvokeMethod(currentEditor(), qPrintable(act->property("editorSlot").toString()), act->property("args").value<QList<QVariant> >());
2276 }
2277 
relayToOwnSlot()2278 void Texstudio::relayToOwnSlot()
2279 {
2280 	QAction *act = qobject_cast<QAction *>(sender());
2281 	REQUIRE(act && act->property("slot").isValid());
2282 	QMetaObjectInvokeMethod(this, qPrintable(act->property("slot").toString()), act->property("args").value<QList<QVariant> >());
2283 }
2284 
autoRunScripts()2285 void Texstudio::autoRunScripts()
2286 {
2287 	QStringList vers = QString(QT_VERSION_STR).split('.');
2288 	Q_ASSERT(vers.length() >= 2);
2289 	int major = vers.at(0).toInt();
2290 	int minor = vers.at(1).toInt();
2291 	if (!hasAtLeastQt(major, minor))
2292 		UtilsUi::txsWarning(tr("%1 has been compiled with Qt %2, but is running with Qt %3.\nPlease get the correct runtime library (e.g. .dll or .so files).\nOtherwise there might be random errors and crashes.")
2293                    .arg(TEXSTUDIO,QT_VERSION_STR,qVersion()));
2294 	runScripts(Macro::ST_TXS_START);
2295 }
2296 
runScripts(int trigger)2297 void Texstudio::runScripts(int trigger)
2298 {
2299 	runScriptsInList(trigger, configManager.completerConfig->userMacros);
2300 }
2301 
runScriptsInList(int trigger,const QList<Macro> & scripts)2302 void Texstudio::runScriptsInList(int trigger, const QList<Macro> &scripts)
2303 {
2304 	foreach (const Macro &macro, scripts) {
2305         if (macro.type == Macro::Script && macro.isActiveForTrigger(static_cast<Macro::SpecialTrigger>(trigger) ))
2306 			runScript(macro.script(), MacroExecContext(trigger));
2307 	}
2308 }
2309 
fileNewInternal(QString fileName)2310 void Texstudio::fileNewInternal(QString fileName)
2311 {
2312 	LatexDocument *doc = new LatexDocument(this);
2313 	doc->enableSyntaxCheck(configManager.editorConfig->inlineSyntaxChecking);
2314 	LatexEditorView *edit = new LatexEditorView (nullptr, configManager.editorConfig, doc);
2315 	edit->setLatexPackageList(&latexPackageList);
2316     edit->setHelp(&help);
2317 	if (configManager.newFileEncoding)
2318 		edit->editor->setFileCodec(configManager.newFileEncoding);
2319 	else
2320 		edit->editor->setFileCodec(QTextCodec::codecForName("utf-8"));
2321 	doc->clearUndo(); // inital file codec setting should not be undoable
2322 	edit->editor->setFileName(fileName);
2323 
2324 	configureNewEditorView(edit);
2325 
2326 	edit->document = doc;
2327 	edit->document->setEditorView(edit);
2328 	documents.addDocument(edit->document);
2329 
2330 	configureNewEditorViewEnd(edit);
2331 	doc->updateLtxCommands();
2332 	if (!fileName.isEmpty())
2333 		fileSave(true);
2334 }
2335 
fileNew(QString fileName)2336 void Texstudio::fileNew(QString fileName)
2337 {
2338 	fileNewInternal(fileName);
2339 	emit infoNewFile();
2340 }
2341 
fileAutoReloading(QString fname)2342 void Texstudio::fileAutoReloading(QString fname)
2343 {
2344 	LatexDocument *document = documents.findDocument(fname);
2345 	if (!document) return;
2346 	document->initClearStructure();
2347 }
2348 /* \brief called when file has been reloaded from disc
2349  */
fileReloaded()2350 void Texstudio::fileReloaded()
2351 {
2352 	QEditor *mEditor = qobject_cast<QEditor *>(sender());
2353 	if (mEditor == currentEditor()) {
2354 		currentEditorView()->document->initClearStructure();
2355 		updateStructure(true);
2356 	} else {
2357 		LatexDocument *document = documents.findDocument(mEditor->fileName());
2358 		if (!document) return;
2359 		document->initClearStructure();
2360 		document->patchStructure(0, -1);
2361 	}
2362 }
2363 /*!
2364  * \brief make a template from current editor
2365  */
fileMakeTemplate()2366 void Texstudio::fileMakeTemplate()
2367 {
2368 	if (!currentEditorView())
2369 		return;
2370 
2371 	MakeTemplateDialog templateDialog(TemplateManager::userTemplateDir(), currentEditor()->fileName());
2372 	if (templateDialog.exec()) {
2373 		// save file
2374 		QString fn = templateDialog.suggestedFile();
2375 		LatexDocument *doc = currentEditorView()->document;
2376 		QString txt = doc->text();
2377 		//txt.replace("%","%%"); not necessary any more
2378 		QFile file_txt(fn);
2379 		if (!file_txt.open(QIODevice::WriteOnly | QIODevice::Text)) {
2380 			UtilsUi::txsInformation(tr("Could not write template data:") + "\n" + fn);
2381 			return;
2382 		} else {
2383 #ifdef Q_OS_WIN
2384 			txt.replace("\r\n", "\n"); //on Windows QTextStream corrupts line endings by replacing "\n" with "\r\n", so "\r\n" becomes "\r\r\n"
2385 #endif
2386 			QTextStream out(&file_txt);
2387 #if QT_VERSION < QT_VERSION_CHECK(6,0,0)
2388             out.setCodec("UTF-8");
2389 #endif
2390 
2391 			out << txt;
2392 			file_txt.close();
2393 		}
2394 
2395 		/* alternate code in order to double %
2396 
2397 		QString old_name=currentEditor()->fileName();
2398 		QTextCodec *mCodec=currentEditor()->getFileCodec();
2399 		currentEditor()->setFileCodec(QTextCodec::codecForName("utf-8"));
2400 		currentEditor()->save(fn);
2401 		currentEditor()->setFileName(old_name);
2402 		currentEditor()->setFileCodec(mCodec);
2403 		*/
2404 
2405 		// save metaData
2406 		QFileInfo fi(fn);
2407 		fn = fi.absoluteFilePath();
2408 		fn.remove(fn.lastIndexOf('.'), 4);
2409 		fn.append(".json");
2410 		QFile file(fn);
2411 		if (!file.open(QIODevice::WriteOnly | QIODevice::Text)) {
2412 			UtilsUi::txsInformation(tr("Could not write template meta data:") + "\n" + fn);
2413 		} else {
2414 			QTextStream out(&file);
2415 #if QT_VERSION < QT_VERSION_CHECK(6,0,0)
2416             out.setCodec("UTF-8");
2417 #endif
2418 			out << templateDialog.generateMetaData();
2419 			file.close();
2420 		}
2421 	}
2422 }
2423 /*!
2424  * \brief load template file for editing
2425  * \param fname filename
2426  */
templateEdit(const QString & fname)2427 void Texstudio::templateEdit(const QString &fname)
2428 {
2429 	load(fname, false);
2430 }
2431 /*!
2432  * \brief generate new file from template
2433  */
fileNewFromTemplate()2434 void Texstudio::fileNewFromTemplate()
2435 {
2436 	TemplateManager tmplMgr;
2437 	connectUnique(&tmplMgr, SIGNAL(editRequested(QString)), this, SLOT(templateEdit(QString)));
2438 
2439 	TemplateSelector *dialog = tmplMgr.createLatexTemplateDialog();
2440 	if (!dialog->exec()) return;
2441 
2442 	TemplateHandle th = dialog->selectedTemplate();
2443 	if (!th.isValid()) return;
2444 
2445 	if (dialog->createInFolder()) {
2446 		th.createInFolder(dialog->creationFolder());
2447 		if (th.isMultifile()) {
2448 			QDir dir(dialog->creationFolder());
2449 			foreach (const QString &f, th.filesToOpen()) {
2450 				QFileInfo fi(dir, f);
2451 				if (fi.exists() && fi.isFile())
2452 					load(fi.absoluteFilePath());
2453 			}
2454 		} else {
2455 			QDir dir(dialog->creationFolder());
2456 			QFileInfo fi(dir, QFileInfo(th.file()).fileName());
2457 			load(fi.absoluteFilePath());
2458 		}
2459 	} else {
2460 		QString fname = th.file();
2461 		QFile file(fname);
2462 		if (!file.exists()) {
2463 			UtilsUi::txsWarning(tr("File not found:") + QString("\n%1").arg(fname));
2464 			return;
2465 		}
2466 		if (!file.open(QIODevice::ReadOnly)) {
2467 			UtilsUi::txsWarning(tr("You do not have read permission to this file:") + QString("\n%1").arg(fname));
2468 			return;
2469 		}
2470 
2471 		//set up new editor with template
2472 		fileNewInternal();
2473 		LatexEditorView *edit = currentEditorView();
2474 
2475 		QString mTemplate;
2476 		bool loadAsSnippet = false;
2477 		QTextStream in(&file);
2478 #if QT_VERSION < QT_VERSION_CHECK(6,0,0)
2479         in.setCodec("UTF-8");
2480 #endif
2481 		QString line = in.readLine();
2482         if (line.contains(QRegularExpression("^%\\s*!TXS\\s+template"))) {
2483 			loadAsSnippet = true;
2484 		} else {
2485 			mTemplate += line + '\n';
2486 		}
2487 		while (!in.atEnd()) {
2488 			line = in.readLine();
2489 			mTemplate += line + "\n";
2490 		}
2491 		if (loadAsSnippet) {
2492 			bool flag = edit->editor->flag(QEditor::AutoIndent);
2493 			edit->editor->setFlag(QEditor::AutoIndent, false);
2494 			CodeSnippet toInsert(mTemplate, false);
2495 			toInsert.insert(edit->editor);
2496 			edit->editor->setFlag(QEditor::AutoIndent, flag);
2497 			edit->editor->setCursorPosition(0, 0, false);
2498 			edit->editor->nextPlaceHolder();
2499 			edit->editor->ensureCursorVisible(QEditor::KeepSurrounding);
2500 		} else {
2501 			edit->editor->setText(mTemplate, false);
2502 		}
2503 
2504 		emit infoNewFromTemplate();
2505 	}
2506 	delete dialog;
2507 }
2508 /*!
2509  * \brief insert table template
2510  */
insertTableTemplate()2511 void Texstudio::insertTableTemplate()
2512 {
2513 	QEditor *m_edit = currentEditor();
2514 	if (!m_edit)
2515 		return;
2516 	QDocumentCursor c = m_edit->cursor();
2517 	if (!LatexTables::inTableEnv(c))
2518 		return;
2519 
2520 	// select Template
2521 	TemplateManager tmplMgr;
2522 	connectUnique(&tmplMgr, SIGNAL(editRequested(QString)), this, SLOT(templateEdit(QString)));
2523 	if (tmplMgr.tableTemplateDialogExec()) {
2524 		QString fname = tmplMgr.selectedTemplateFile();
2525 		QFile file(fname);
2526 		if (!file.exists()) {
2527 			UtilsUi::txsWarning(tr("File not found:") + QString("\n%1").arg(fname));
2528 			return;
2529 		}
2530 		if (!file.open(QIODevice::ReadOnly)) {
2531 			UtilsUi::txsWarning(tr("You do not have read permission to this file:") + QString("\n%1").arg(fname));
2532 			return;
2533 		}
2534 		QString tableDef = LatexTables::getSimplifiedDef(c);
2535 		QString tableText = LatexTables::getTableText(c);
2536 		QString widthDef;
2537 		//remove table
2538 		c.removeSelectedText();
2539 		m_edit->setCursor(c);
2540 		// split table text into line/column list
2541 		QStringList values;
2542 		QList<int> starts;
2543 		QString env;
2544 		//tableText.remove("\n");
2545 		tableText.remove("\\hline");
2546 		if (tableText.startsWith("\\begin")) {
2547 			LatexParser::resolveCommandOptions(tableText, 0, values, &starts);
2548 			env = values.takeFirst();
2549 			env.remove(0, 1);
2550 			env.remove(env.length() - 1, 1);
2551 			// special treatment for tabu/longtabu
2552 			if ((env == "tabu" || env == "longtabu") && values.count() < 1) {
2553 				int startExtra = env.length() + 8;
2554 				int endExtra = tableText.indexOf("{", startExtra);
2555 				if (endExtra >= 0 && endExtra > startExtra) {
2556 					QString textHelper = tableText;
2557 					widthDef = textHelper.mid(startExtra, endExtra - startExtra);
2558 					textHelper.remove(startExtra, endExtra - startExtra); // remove to/spread definition
2559 					values.clear();
2560 					starts.clear();
2561 					LatexParser::resolveCommandOptions(textHelper, 0, values, &starts);
2562 					for (int i = 1; i < starts.count(); i++) {
2563 						starts[i] += endExtra - startExtra;
2564 					}
2565 				}
2566 				if (!values.isEmpty())
2567 					values.takeFirst();
2568 			}
2569 			if (LatexTables::tabularNames.contains(env)) {
2570 				if (!values.isEmpty()) {
2571 					int i = starts.at(1);
2572 					i += values.first().length();
2573 					tableText.remove(0, i);
2574 				}
2575 			}
2576 			if (LatexTables::tabularNamesWithOneOption.contains(env)) {
2577 				if (values.size() > 1) {
2578 					int i = starts.at(2);
2579 					i += values.at(1).length();
2580 					tableText.remove(0, i);
2581 				}
2582 			}
2583             tableText.remove(QRegularExpression("\\\\end\\{" + env + "\\}$"));
2584 		}
2585 		tableText.replace("\\endhead", "\\\\");
2586 		QStringList lines = tableText.split("\\\\");
2587 		QList<QStringList> tableContent;
2588 		foreach (QString line, lines) {
2589 			//line=line.simplified();
2590 			if (line.simplified().isEmpty())
2591 				continue;
2592             QStringList elems = line.split(QRegularExpression("&"));
2593 			if (elems.count() > 0) {
2594 				if (elems[0].startsWith("\n"))
2595 					elems[0] = elems[0].mid(1);
2596 			}
2597 			//QList<QString>::iterator i;
2598 			/*for(i=elems.begin();i!=elems.end();i++){
2599 				QString elem=*i;
2600 			    *i=elem.simplified();
2601 			}*/
2602 
2603 			// handle \& correctly
2604 			for (int i = elems.size() - 1; i >= 0; --i) {
2605 				if (elems.at(i).endsWith("\\")) {
2606 					QString add = elems.at(i) + elems.at(i + 1);
2607 					elems.replace(i, add);
2608 					elems.removeAt(i + 1);
2609 				}
2610 			}
2611 			tableContent << elems;
2612 		}
2613 		LatexTables::generateTableFromTemplate(currentEditorView(), fname, tableDef, tableContent, env, widthDef);
2614 	}
2615 }
2616 /*! \brief align columns of latex table in editor
2617  */
alignTableCols()2618 void Texstudio::alignTableCols()
2619 {
2620 	if (!currentEditor()) return;
2621 	QDocumentCursor cur(currentEditor()->cursor());
2622 	int linenr = cur.lineNumber();
2623 	int col = cur.columnNumber();
2624 	if (!cur.isValid())
2625 		return;
2626     LatexDocument *doc=currentEditorView()->getDocument();
2627     LatexParser lp=doc->lp;
2628     QStringList keys=lp.environmentAliases.uniqueKeys();
2629     QSet<QString> results;
2630     foreach(const QString &elem,keys){
2631         if(lp.environmentAliases.values(elem).contains("array")){
2632             results<<elem;
2633         }
2634     }
2635     LatexTables::mathTables.unite(results);
2636 	LatexTables::alignTableCols(cur);
2637 	cur.setLineNumber(linenr);
2638 	cur.setColumnNumber(col);
2639 	currentEditor()->setCursor(cur);
2640 }
2641 /*! \brief open file
2642  *
2643  * open file is triggered from menu action.
2644  * It opens a file dialog and lets the user to select a file.
2645  * If the file is already open, the apropriate editor subwindow is brought to front.
2646  * If the file is open as hidden, an editor is created and brought to front.
2647  * pdf files are handled as well and they are forwarded to the pdf viewer.
2648  */
fileOpen()2649 void Texstudio::fileOpen()
2650 {
2651 	QString currentDir = QDir::homePath();
2652 	if (!configManager.lastDocument.isEmpty()) {
2653 		QFileInfo fi(configManager.lastDocument);
2654 		if (fi.exists() && fi.isReadable()) {
2655 			currentDir = fi.absolutePath();
2656 		}
2657 	}
2658 	QStringList files = FileDialog::getOpenFileNames(this, tr("Open Files"), currentDir, fileFilters,  &selectedFileFilter);
2659 
2660 	recheckLabels = false; // impede label rechecking on hidden docs
2661 	QList<LatexEditorView *>listViews;
2662 	foreach (const QString &fn, files)
2663 		listViews << load(fn);
2664 
2665 	// update ref/labels in one go;
2666 	QList<LatexDocument *> completedDocs;
2667 	foreach (LatexEditorView *edView, listViews) {
2668 		if (!edView)
2669 			continue;
2670 		LatexDocument *docBase = edView->getDocument();
2671 		foreach (LatexDocument *doc, docBase->getListOfDocs()) {
2672 			doc->recheckRefsLabels();
2673 			if (completedDocs.contains(doc))
2674 				continue;
2675 			doc->updateLtxCommands(true);
2676 			completedDocs << doc->getListOfDocs();
2677 		}
2678 	}
2679 	recheckLabels = true;
2680 	// update completer
2681 	if (currentEditorView())
2682 		updateCompleter(currentEditorView());
2683 }
2684 
fileRestoreSession(bool showProgress,bool warnMissing)2685 void Texstudio::fileRestoreSession(bool showProgress, bool warnMissing)
2686 {
2687 
2688     QFileInfo f(QDir(configManager.configBaseDir), "lastSession.txss2");
2689 	Session s;
2690 	if (f.exists()) {
2691 		if (!s.load(f.filePath())) {
2692 			UtilsUi::txsCritical(tr("Loading of last session failed."));
2693 		}
2694     }else{
2695         // restore v1 format if it exists
2696         QFileInfo f(QDir(configManager.configBaseDir), "lastSession.txss");
2697         if (f.exists()) {
2698             if (!s.load(f.filePath())) {
2699                 UtilsUi::txsCritical(tr("Loading of last session failed."));
2700             }
2701         }
2702     }
2703 	restoreSession(s, showProgress, warnMissing);
2704 }
2705 /*!
2706  * \brief save current editor content
2707  *
2708  * \param saveSilently
2709  */
fileSave(const bool saveSilently)2710 void Texstudio::fileSave(const bool saveSilently)
2711 {
2712 	if (!currentEditor())
2713 		return;
2714 
2715     if (currentEditor()->fileName() == "" || !QFileInfo::exists(currentEditor()->fileName())) {
2716 		removeDiffMarkers();// clean document from diff markers first
2717 		fileSaveAs(currentEditor()->fileName(), saveSilently);
2718 	} else {
2719 		removeDiffMarkers();// clean document from diff markers first
2720 		currentEditor()->save();
2721 		currentEditor()->document()->markViewDirty();//force repaint of line markers (yellow -> green)
2722 		MarkCurrentFileAsRecent();
2723 		int checkIn = (configManager.autoCheckinAfterSaveLevel > 0 && !saveSilently) ? 2 : 1;
2724 		emit infoFileSaved(currentEditor()->fileName(), checkIn);
2725 	}
2726 	updateCaption();
2727 }
2728 /*!
2729  * \brief save current editor content to new filename
2730  *
2731  * save current editor content to new filename
2732  * \param fileName
2733  * \param saveSilently don't ask for new filename if fileName is empty
2734  */
fileSaveAs(const QString & fileName,const bool saveSilently)2735 void Texstudio::fileSaveAs(const QString &fileName, const bool saveSilently)
2736 {
2737 	if (!currentEditorView())
2738 		return;
2739 
2740 	// select a directory/filepath
2741 	QString currentDir = QDir::homePath();
2742 	if (fileName.isEmpty()) {
2743 		if (currentEditor()->fileInfo().isFile()) {
2744 			currentDir = currentEditor()->fileInfo().absoluteFilePath();
2745 		} else {
2746 			if (!configManager.lastDocument.isEmpty())
2747 				currentDir = configManager.lastDocument;
2748 			static QString saveAsDefault;
2749 			configManager.registerOption("Files/Save As Default", &saveAsDefault, "?a)/document");
2750             currentDir = buildManager.parseExtendedCommandLine(saveAsDefault, QFileInfo(currentDir), QFileInfo(currentDir)).value(0, currentDir);
2751 		}
2752 	} else {
2753 		currentDir = fileName;
2754 	}
2755 
2756 	// get a file name
2757 	QString fn = fileName;
2758 	if (!saveSilently || fn.isEmpty()) {
2759 		fn = FileDialog::getSaveFileName(this, tr("Save As"), currentDir, fileFilters, &selectedFileFilter);
2760 		if (!fn.isEmpty()) {
2761 			static QRegExp fileExt("\\*(\\.[^ )]+)");
2762 			if (fileExt.indexIn(selectedFileFilter) > -1) {
2763 				//add
2764 				int lastsep = qMax(fn.lastIndexOf("/"), fn.lastIndexOf("\\"));
2765 				int lastpoint = fn.lastIndexOf(".");
2766 				if (lastpoint <= lastsep) //if both aren't found or point is in directory name
2767 					fn.append(fileExt.cap(1));
2768 			}
2769 		}
2770 	}
2771 	if (!fn.isEmpty()) {
2772 		if (getEditorViewFromFileName(fn) && getEditorViewFromFileName(fn) != currentEditorView()) {
2773 			// trying to save with same name as another already existing file
2774 			LatexEditorView *otherEdView = getEditorViewFromFileName(fn);
2775 			if (!otherEdView->document->isClean()) {
2776 				UtilsUi::txsWarning(tr("Saving under the name\n"
2777 				              "%1\n"
2778 				              "is currently not possible because a modified version of a file\n"
2779 				              "with this name is open in TeXstudio. You have to save or close\n"
2780 				              "this other file before you can overwrite it.").arg(fn));
2781 				return;
2782 			}
2783 			// other editor does not contain unsaved changes, so it can be closed
2784 			LatexEditorView *currentEdView = currentEditorView();
2785 			editors->setCurrentEditor(otherEdView);
2786 			fileClose();
2787 			editors->setCurrentEditor(currentEdView);
2788 		}
2789 #ifndef NO_POPPLER_PREVIEW
2790 		// show message in viewer
2791 		if (currentEditor()->fileInfo() != QFileInfo(fn)) {
2792 			foreach (PDFDocument *viewer, PDFDocument::documentList())
2793 				if (viewer->getMasterFile() == currentEditor()->fileInfo())
2794 					viewer->showMessage(tr("This pdf cannot be synchronized with the tex source any more because the source file has been renamed due to a Save As operation. You should recompile the renamed file and view its result."));
2795 		}
2796 #endif
2797 		// save file
2798 		removeDiffMarkers();// clean document from diff markers first
2799 		currentEditor()->save(fn);
2800 		currentEditorView()->document->setEditorView(currentEditorView()); //update file name
2801 		MarkCurrentFileAsRecent();
2802 
2803 		//update Master/Child relations
2804 		LatexDocument *doc = currentEditorView()->document;
2805 		documents.updateMasterSlaveRelations(doc);
2806 
2807 		updateOpenDocumentMenu(true);  // TODO: currently duplicate functionality with updateCaption() below
2808 		if (currentEditor()->fileInfo().suffix().toLower() != "tex")
2809 			m_languages->setLanguage(currentEditor(), fn);
2810 
2811 		emit infoFileSaved(currentEditor()->fileName());
2812 	}
2813 
2814 	updateCaption();
2815 }
2816 /*!
2817  * \brief save all files
2818  *
2819  * This functions is called from menu-action.
2820  */
fileSaveAll()2821 void Texstudio::fileSaveAll()
2822 {
2823 	fileSaveAll(true, true);
2824 }
2825 /*!
2826  * \brief save all files
2827  *
2828  * This functions is called from timer (auto save).
2829  * It does *not* save unnamed files.
2830  */
fileSaveAllFromTimer()2831 void Texstudio::fileSaveAllFromTimer()
2832 {
2833     fileSaveAll(false, false);
2834 }
2835 /*!
2836  * \brief save all files
2837  *
2838  * \param alsoUnnamedFiles
2839  * \param alwaysCurrentFile
2840  */
fileSaveAll(bool alsoUnnamedFiles,bool alwaysCurrentFile)2841 void Texstudio::fileSaveAll(bool alsoUnnamedFiles, bool alwaysCurrentFile)
2842 {
2843 	//LatexEditorView *temp = new LatexEditorView(EditorView,colorMath,colorCommand,colorKeyword);
2844 	//temp=currentEditorView();
2845 	REQUIRE(editors);
2846 
2847 	LatexEditorView *currentEdView = currentEditorView();
2848 
2849 	foreach (LatexEditorView *edView, editors->editors()) {
2850 		REQUIRE(edView->editor);
2851 
2852 		if (edView->editor->fileName().isEmpty()) {
2853 			if ((alsoUnnamedFiles || (alwaysCurrentFile && edView == currentEdView) ) ) {
2854 				editors->setCurrentEditor(edView);
2855 				fileSaveAs();
2856 			} else if (!edView->document->getTemporaryFileName().isEmpty())
2857 				edView->editor->saveCopy(edView->document->getTemporaryFileName());
2858 			//else if (edView->editor->isInConflict()) {
2859 			//edView->editor->save();
2860 			//}
2861 		} else if (edView->editor->isContentModified() || edView->editor->isInConflict()) {
2862 			removeDiffMarkers();// clean document from diff markers first
2863 			edView->editor->save(); //only save modified documents
2864 
2865 			if (edView->editor->fileName().endsWith(".bib")) {
2866 				QString temp = edView->editor->fileName();
2867 				temp = temp.replace(QDir::separator(), "/");
2868 				documents.bibTeXFilesModified = documents.bibTeXFilesModified  || documents.mentionedBibTeXFiles.contains(temp);//call bibtex on next compilation (this would also set as soon as the user switch to a tex file, but he could compile before switching)
2869 			}
2870 
2871 			emit infoFileSaved(edView->editor->fileName());
2872 		}
2873 	}
2874     // save hidden files (in case that they are changed via replace in all docs
2875     foreach (LatexDocument *d, documents.hiddenDocuments){
2876         if(d->getEditorView()->editor->isContentModified())
2877             d->getEditorView()->editor->save();
2878     }
2879 
2880 
2881 	if (currentEditorView() != currentEdView)
2882 		editors->setCurrentEditor(currentEdView);
2883 	updateUndoRedoStatus();
2884 }
2885 
2886 //TODO: handle svn in all these methods
2887 
fileUtilCopyMove(bool move)2888 void Texstudio::fileUtilCopyMove(bool move)
2889 {
2890 	QString fn = documents.getCurrentFileName();
2891 	if (fn.isEmpty()) return;
2892 	QString newfn = FileDialog::getSaveFileName(this, move ? tr("Rename/Move") : tr("Copy"), fn, fileFilters, &selectedFileFilter);
2893 	if (newfn.isEmpty()) return;
2894 	if (fn == newfn) return;
2895 	QFile::Permissions permissions = QFile(fn).permissions();
2896 	if (move) fileSaveAs(newfn, true);
2897 	else currentEditor()->saveCopy(newfn);
2898 	if (documents.getCurrentFileName() != newfn) return;
2899 	if (move) QFile(fn).remove();
2900 	QFile(newfn).setPermissions(permissions); //keep permissions. (better: actually move the file, keeping the inode. but then all that stuff (e.g. master/slave) has to be updated here
2901 }
2902 
fileUtilDelete()2903 void Texstudio::fileUtilDelete()
2904 {
2905 	QString fn = documents.getCurrentFileName();
2906 	if (fn.isEmpty()) return;
2907 	if (UtilsUi::txsConfirmWarning(tr("Do you really want to delete the file \"%1\"?").arg(fn)))
2908 		QFile(fn).remove();
2909 }
2910 
fileUtilRevert()2911 void Texstudio::fileUtilRevert()
2912 {
2913 	if (!currentEditor()) return;
2914 	QString fn = documents.getCurrentFileName();
2915 	if (fn.isEmpty()) return;
2916 	if (UtilsUi::txsConfirmWarning(tr("Do you really want to revert the file \"%1\"?").arg(documents.getCurrentFileName())))
2917 		currentEditor()->reload();
2918 }
2919 
fileUtilPermissions()2920 void Texstudio::fileUtilPermissions()
2921 {
2922 	QString fn = documents.getCurrentFileName();
2923 	if (fn.isEmpty()) return;
2924 
2925 	QFile f(fn);
2926 
2927 	int permissionsRaw = f.permissions();
2928 	int permissionsUnixLike = ((permissionsRaw & 0x7000) >> 4) | (permissionsRaw & 0x0077); //ignore qt "user" flags
2929 	QString permissionsUnixLikeHex = QString::number(permissionsUnixLike, 16);
2930 	QString permissions;
2931 	const QString PERMISSIONSCODES = "rwx";
2932 	int flag = QFile::ReadUser;
2933 	REQUIRE(QFile::ReadUser == 0x400);
2934 	int temp = permissionsUnixLike;
2935 	for (int b = 0; b < 3; b++) {
2936 		for (int i = 0; i < 3; i++, flag >>= 1)
2937 			permissions += (temp & flag) ? PERMISSIONSCODES[i] : QChar('-');
2938 		flag >>= 1;
2939 	}
2940 	QString oldPermissionsUnixLikeHex = permissionsUnixLikeHex, oldPermissions = permissions;
2941 
2942 	UniversalInputDialog uid;
2943 	uid.addVariable(&permissionsUnixLikeHex, tr("Numeric permissions"));
2944 	uid.addVariable(&permissions, tr("Verbose permissions"));
2945 	if (uid.exec() == QDialog::Accepted && (permissionsUnixLikeHex != oldPermissionsUnixLikeHex || permissions != oldPermissions)) {
2946 		if (permissionsUnixLikeHex != oldPermissionsUnixLikeHex)
2947 		        permissionsRaw = permissionsUnixLikeHex.toInt(nullptr, 16);
2948 		else {
2949 			permissionsRaw = 0;
2950 			int flag = QFile::ReadUser;
2951 			int p = 0;
2952 			for (int b = 0; b < 3; b++) {
2953 				for (int i = 0; i < 3; i++, flag >>= 1) {
2954 					if (permissions[p] == '-') p++;
2955 					else if (permissions[p] == PERMISSIONSCODES[i]) {
2956 						permissionsRaw |= flag;
2957 						p++;
2958 					} else if (!QString("rwx").contains(permissions[p])) {
2959                         UtilsUi::txsWarning(QString("invalid character in permission: ") + permissions[p]);
2960 						return;
2961 					}
2962 					if (p >= permissions.length()) p = 0; //wrap around
2963 				}
2964 				flag >>= 1;
2965 			}
2966 		}
2967 		permissionsRaw = ((permissionsRaw << 4) & 0x7000) | permissionsRaw; //use qt "user" as owner flags
2968 		f.setPermissions(QFile::Permissions(permissionsRaw)) ;
2969 	}
2970 }
2971 
fileUtilCopyFileName()2972 void Texstudio::fileUtilCopyFileName()
2973 {
2974 	QApplication::clipboard()->setText(documents.getCurrentFileName());
2975 }
2976 
fileUtilCopyMasterFileName()2977 void Texstudio::fileUtilCopyMasterFileName()
2978 {
2979 	QApplication::clipboard()->setText(documents.getCompileFileName());
2980 }
2981 
fileClose()2982 void Texstudio::fileClose()
2983 {
2984 	if (!currentEditorView())	return;
2985 	bookmarks->updateBookmarks(currentEditorView());
2986 	QFileInfo fi = currentEditorView()->document->getFileInfo();
2987 
2988 repeatAfterFileSavingFailed:
2989 	if (currentEditorView()->editor->isContentModified()) {
2990 		switch (QMessageBox::warning(this, TEXSTUDIO,
2991 		                             tr("The document \"%1\" contains unsaved work. "
2992 		                                "Do you want to save it before closing?").arg(currentEditorView()->displayName()),
2993 		                             tr("Save and Close"), tr("Close without Saving"), tr("Cancel"),
2994 		                             0,
2995 		                             2)) {
2996 		case 0:
2997 			fileSave();
2998 			if (currentEditorView()->editor->isContentModified())
2999 				goto repeatAfterFileSavingFailed;
3000 			documents.deleteDocument(currentEditorView()->document);
3001 			break;
3002 		case 1:
3003 			documents.deleteDocument(currentEditorView()->document);
3004 			break;
3005 		case 2:
3006 		default:
3007 			return;
3008 		}
3009 	} else documents.deleteDocument(currentEditorView()->document);
3010 	//UpdateCaption(); unnecessary as called by tabChanged (signal)
3011     updateTOCs();
3012 
3013 #ifndef NO_POPPLER_PREVIEW
3014 	//close associated embedded pdf viewer
3015 	foreach (PDFDocument *viewer, PDFDocument::documentList())
3016 		if (viewer->autoClose && viewer->getMasterFile() == fi)
3017 			viewer->close();
3018 #endif
3019 }
3020 
fileCloseAll()3021 void Texstudio::fileCloseAll()
3022 {
3023 	bool accept = saveAllFilesForClosing();
3024 	if (accept) {
3025 		closeAllFiles();
3026 	}
3027 }
3028 
fileExit()3029 void Texstudio::fileExit()
3030 {
3031     if (canCloseNow())
3032 	qApp->quit();
3033 }
3034 
fileExitWithError()3035 void Texstudio::fileExitWithError()
3036 {
3037     if (canCloseNow()){
3038         qApp->exit(1);
3039     }
3040 }
3041 
saveAllFilesForClosing()3042 bool Texstudio::saveAllFilesForClosing()
3043 {
3044     return saveFilesForClosing(documents.getDocuments());
3045 }
3046 
saveFilesForClosing(const QList<LatexDocument * > & documentList)3047 bool Texstudio::saveFilesForClosing(const QList<LatexDocument *> &documentList)
3048 {
3049 	LatexEditorView *savedCurrentEditorView = currentEditorView();
3050     foreach (LatexDocument *doc, documentList) {
3051 repeatAfterFileSavingFailed:
3052         LatexEditorView *edView=doc->getEditorView();
3053         if(!edView) continue;
3054 		if (edView->editor->isContentModified()) {
3055             if(!doc->isHidden())
3056                 editors->setCurrentEditor(edView);
3057 			switch (QMessageBox::warning(this, TEXSTUDIO,
3058 			                             tr("The document \"%1\" contains unsaved work. "
3059 			                                "Do you want to save it before closing?").arg(edView->displayName()),
3060 			                             tr("Save and Close"), tr("Close without Saving"), tr("Cancel"),
3061 			                             0,
3062 			                             2)) {
3063 			case 0:
3064 				fileSave();
3065 				if (currentEditorView()->editor->isContentModified())
3066 					goto repeatAfterFileSavingFailed;
3067 				break;
3068 			case 1:
3069 				break;
3070 			case 2:
3071 			default:
3072 				editors->setCurrentEditor(savedCurrentEditorView);
3073 				return false;
3074 			}
3075 		}
3076 	}
3077 	editors->setCurrentEditor(savedCurrentEditorView);
3078 	return true;
3079 }
3080 /*! \brief close all files
3081  */
closeAllFiles()3082 void Texstudio::closeAllFiles()
3083 {
3084 	while (currentEditorView())
3085 		documents.deleteDocument(currentEditorView()->document);
3086 #ifndef NO_POPPLER_PREVIEW
3087 	foreach (PDFDocument *viewer, PDFDocument::documentList())
3088 		viewer->close();
3089 #endif
3090 	documents.setMasterDocument(nullptr);
3091 	updateCaption();
3092     updateTOCs();
3093 }
3094 
canCloseNow(bool saveSettings)3095 bool Texstudio::canCloseNow(bool saveSettings)
3096 {
3097     if(programStopped) return true; // avoid running through here twice. (Qt6/OSX)
3098 	if (!saveAllFilesForClosing()) return false;
3099 #ifndef NO_POPPLER_PREVIEW
3100 	foreach (PDFDocument *viewer, PDFDocument::documentList())
3101 		viewer->saveGeometryToConfig();
3102 #endif
3103 	if (saveSettings)
3104 		this->saveSettings();
3105 	closeAllFiles();
3106 	delete userMacroDialog;
3107 	spellerManager.unloadAll();  //this saves the ignore list
3108 	programStopped = true;
3109 	Guardian::shutdown();
3110 	return true;
3111 }
3112 /*!
3113  * \brief closeEvent
3114  * \param e event
3115  */
closeEvent(QCloseEvent * e)3116 void Texstudio::closeEvent(QCloseEvent *e)
3117 {
3118     if (canCloseNow()) {
3119         e->accept();
3120     } else {
3121         e->ignore();
3122     }
3123 }
3124 
updateUserMacros(bool updateMenu)3125 void Texstudio::updateUserMacros(bool updateMenu)
3126 {
3127 	if (updateMenu) configManager.updateUserMacroMenu();
3128 	for (int i = 0; i < configManager.completerConfig->userMacros.size(); i++) {
3129 		configManager.completerConfig->userMacros[i].parseTriggerLanguage(m_languages);
3130 	}
3131 }
3132 
3133 /*!
3134  * \brief open file from recent list
3135  *
3136  * The filename is determind from the sender-action, where it is encoded in data.
3137  */
fileOpenRecent()3138 void Texstudio::fileOpenRecent()
3139 {
3140 	QAction *action = qobject_cast<QAction *>(sender());
3141 	if (!action) return;
3142 	QString fn = action->data().toString();
3143 	if (!QFile::exists(fn)) {
3144 		if (UtilsUi::txsConfirmWarning(tr("The file \"%1\" does not exist anymore. Do you want to remove it from the recent file list?").arg(fn))) {
3145 			if (configManager.recentFilesList.removeAll(fn))
3146 				configManager.updateRecentFiles();
3147 			return;
3148 		}
3149 	}
3150 	load(fn);
3151 }
3152 
fileOpenAllRecent()3153 void Texstudio::fileOpenAllRecent()
3154 {
3155 	foreach (const QString &s, configManager.recentFilesList)
3156 		load(s);
3157 }
3158 
appendToBottom(QRect r,const QRect & s)3159 QRect appendToBottom(QRect r, const QRect &s)
3160 {
3161 	r.setBottom(r.bottom() + s.height());
3162 	return r;
3163 }
3164 
fileRecentList()3165 void Texstudio::fileRecentList()
3166 {
3167 	if (fileSelector) fileSelector.data()->deleteLater();
3168 	fileSelector = new FileSelector(editors, true);
3169 
3170 	fileSelector.data()->init(QStringList() << configManager.recentProjectList << configManager.recentFilesList, 0);
3171 
3172     connect(fileSelector.data(), SIGNAL(fileChoosen(QString,int,int,int)), SLOT(fileDocumentOpenFromChoosen(QString,int,int,int)));
3173 	fileSelector.data()->setVisible(true);
3174 }
3175 
viewDocumentListHidden()3176 void Texstudio::viewDocumentListHidden()
3177 {
3178 	if (fileSelector) fileSelector.data()->deleteLater();
3179 	fileSelector = new FileSelector(editors, true);
3180 
3181 	QStringList hiddenDocs;
3182 	foreach (LatexDocument *d, documents.hiddenDocuments)
3183 		hiddenDocs << d->getFileName();
3184 	fileSelector.data()->init(hiddenDocs, 0);
3185 
3186     connect(fileSelector.data(), SIGNAL(fileChoosen(QString,int,int,int)), SLOT(fileDocumentOpenFromChoosen(QString,int,int,int)));
3187 	fileSelector.data()->setVisible(true);
3188 }
3189 
fileDocumentOpenFromChoosen(const QString & doc,int duplicate,int lineNr,int column)3190 void Texstudio::fileDocumentOpenFromChoosen(const QString &doc, int duplicate, int lineNr, int column)
3191 {
3192 	Q_UNUSED(duplicate)
3193 	if (!QFile::exists(doc)) {
3194 		if (UtilsUi::txsConfirmWarning(tr("The file \"%1\" does not exist anymore. Do you want to remove it from the recent file list?").arg(doc))) {
3195 			if (configManager.recentFilesList.removeAll(doc) + configManager.recentProjectList.removeAll(doc) > 0)
3196 				configManager.updateRecentFiles();
3197 			return;
3198 		}
3199 	}
3200 
3201 	if (!load(doc, duplicate < configManager.recentProjectList.count(doc))) return;
3202 	if (lineNr < 0) return;
3203 	REQUIRE(currentEditor());
3204 	currentEditor()->setCursorPosition(lineNr, column);
3205 }
3206 
mruEditorViewLessThan(const LatexEditorView * e1,const LatexEditorView * e2)3207 bool mruEditorViewLessThan(const LatexEditorView *e1, const LatexEditorView *e2)
3208 {
3209 	return e1->lastUsageTime > e2->lastUsageTime;
3210 }
3211 
viewDocumentList()3212 void Texstudio::viewDocumentList()
3213 {
3214 	if (fileSelector) fileSelector.data()->deleteLater();
3215 	fileSelector = new FileSelector(editors, false);
3216 
3217 	LatexEditorView *curEdView = currentEditorView();
3218 	int curIndex = 0;
3219 	QList<LatexEditorView *> editorList = editors->editors();
3220 
3221 	if (configManager.mruDocumentChooser) {
3222 		std::sort(editorList.begin(), editorList.end(), mruEditorViewLessThan);
3223 		if (editorList.size() > 1)
3224 			if (editorList.first() == currentEditorView())
3225 				curIndex = 1;
3226 	}
3227 
3228 	int i = 0;
3229 	QStringList names;
3230 	foreach (LatexEditorView *edView, editorList) {
3231 		names << edView->displayName();
3232 		if (!configManager.mruDocumentChooser && edView == curEdView) curIndex = i;
3233 		i++;
3234 	}
3235 
3236 	fileSelector.data()->init(names, curIndex);
3237     connect(fileSelector.data(), SIGNAL(fileChoosen(QString,int,int,int)), SLOT(viewDocumentOpenFromChoosen(QString,int,int,int)));
3238 	fileSelector.data()->setVisible(true);
3239 }
3240 
viewDocumentOpenFromChoosen(const QString & doc,int duplicate,int lineNr,int column)3241 void Texstudio::viewDocumentOpenFromChoosen(const QString &doc, int duplicate, int lineNr, int column)
3242 {
3243 	if (duplicate < 0) return;
3244 	foreach (LatexEditorView *edView, editors->editors()) {
3245 		QString  name = edView->displayName();
3246 		if (name == doc) {
3247 			duplicate -= 1;
3248 			if (duplicate < 0) {
3249 				editors->setCurrentEditor(edView);
3250 				if (lineNr >= 0)
3251 					edView->editor->setCursorPosition(lineNr, column);
3252 				edView->setFocus();
3253 				return;
3254 			}
3255 		}
3256 	}
3257 }
3258 
fileOpenFirstNonOpen()3259 void Texstudio::fileOpenFirstNonOpen()
3260 {
3261 	foreach (const QString &f, configManager.recentFilesList)
3262 		if (!getEditorViewFromFileName(f)) {
3263 			load(f);
3264 			break;
3265 		}
3266 }
3267 
fileOpenRecentProject()3268 void Texstudio::fileOpenRecentProject()
3269 {
3270 	QAction *action = qobject_cast<QAction *>(sender());
3271 	if (!action) return;
3272 	QString fn = action->data().toString();
3273 	if (!QFile::exists(fn)) {
3274 		if (UtilsUi::txsConfirmWarning(tr("The file \"%1\" does not exist anymore. Do you want to remove it from the recent file list?").arg(fn))) {
3275 			if (configManager.recentProjectList.removeAll(fn))
3276 				configManager.updateRecentFiles();
3277 			return;
3278 		}
3279 	}
3280 	load(fn, true);
3281 }
3282 
loadSession(const QString & fileName)3283 void Texstudio::loadSession(const QString &fileName)
3284 {
3285 	Session s;
3286 	if (!s.load(fileName)) {
3287 		UtilsUi::txsCritical(tr("Loading of session failed."));
3288 		return;
3289 	}
3290 	restoreSession(s);
3291 }
3292 
fileLoadSession()3293 void Texstudio::fileLoadSession()
3294 {
3295 	QString openDir = QDir::homePath();
3296 	if (currentEditorView()) {
3297 		LatexDocument *doc = currentEditorView()->document;
3298 		if (doc->getMasterDocument()) {
3299 			openDir = doc->getMasterDocument()->getFileInfo().path();
3300 		} else {
3301 			openDir = doc->getFileInfo().path();
3302 		}
3303 	}
3304     QString fn = FileDialog::getOpenFileName(this, tr("Load Session"), openDir, tr("TeXstudio Session") + " (*.txss2 *.txss)");
3305 	if (fn.isNull()) return;
3306 	loadSession(fn);
3307 	recentSessionList->addFilenameToList(fn);
3308 }
3309 
fileSaveSession()3310 void Texstudio::fileSaveSession()
3311 {
3312 	QString openDir = QDir::homePath();
3313 	if (currentEditorView()) {
3314 		LatexDocument *doc = currentEditorView()->document;
3315 		if (doc->getMasterDocument()) {
3316 			openDir = replaceFileExtension(doc->getMasterDocument()->getFileName(), Session::fileExtension());
3317 		} else {
3318 			openDir = replaceFileExtension(doc->getFileName(), Session::fileExtension());
3319 		}
3320 	}
3321 
3322 	QString fn = FileDialog::getSaveFileName(this, tr("Save Session"), openDir, tr("TeXstudio Session") + " (*." + Session::fileExtension() + ")");
3323 	if (fn.isNull()) return;
3324 	if (!getCurrentSession().save(fn, configManager.sessionStoreRelativePaths)) {
3325 		UtilsUi::txsCritical(tr("Saving of session failed."));
3326 		return;
3327 	}
3328 	recentSessionList->addFilenameToList(fn);
3329 }
3330 /*!
3331  * \brief restore session s
3332  *
3333  * closes all files and loads all files given in session s
3334  * \param s session
3335  * \param showProgress
3336  * \param warnMissing give warning if files are missing
3337  */
restoreSession(const Session & s,bool showProgress,bool warnMissing)3338 void Texstudio::restoreSession(const Session &s, bool showProgress, bool warnMissing)
3339 {
3340     fileCloseAll();
3341 
3342     cursorHistory->setInsertionEnabled(false);
3343     QProgressDialog progress(this);
3344     if (showProgress) {
3345         progress.setMaximum(s.files().size());
3346         progress.setCancelButton(nullptr);
3347         progress.setMinimumDuration(3000);
3348         progress.setLabel(new QLabel());
3349     }
3350     recheckLabels = false; // impede label rechecking on hidden docs
3351 
3352     bookmarks->setBookmarks(s.bookmarks()); // set before loading, so that bookmarks are automatically restored on load
3353 
3354     QStringList missingFiles;
3355     for (int i = 0; i < s.files().size(); i++) {
3356         FileInSession f = s.files().at(i);
3357 
3358         if (showProgress) {
3359             progress.setValue(i);
3360             progress.setLabelText(QFileInfo(f.fileName).fileName());
3361         }
3362         LatexEditorView *edView = load(f.fileName, f.fileName == s.masterFile(), false, false, true);
3363         if (edView) {
3364             int line = f.cursorLine;
3365             int col = f.cursorCol;
3366             if (line >= edView->document->lineCount()) {
3367                 line = 0;
3368                 col = 0;
3369             } else {
3370                 if (edView->document->line(line).length() < col) {
3371                     col = 0;
3372                 }
3373             }
3374             edView->editor->setCursorPosition(line, col);
3375             edView->editor->scrollToFirstLine(f.firstLine);
3376             edView->document->foldLines(f.foldedLines);
3377             editors->moveToTabGroup(edView, f.editorGroup, -1);
3378         } else {
3379             missingFiles.append(f.fileName);
3380         }
3381     }
3382     //qDebug()<<"loaded:"<<tm.elapsed();
3383     // update ref/labels in one go;
3384     QList<LatexDocument *> completedDocs;
3385     foreach (LatexDocument *doc, documents.getDocuments()) {
3386         doc->recheckRefsLabels();
3387         if (completedDocs.contains(doc))
3388             continue;
3389 
3390         doc->updateLtxCommands(true);
3391         completedDocs << doc->getListOfDocs();
3392     }
3393     recheckLabels = true;
3394     //qDebug()<<"labels:"<<tm.elapsed();
3395 
3396     if (showProgress) {
3397         progress.setValue(progress.maximum());
3398     }
3399     activateEditorForFile(s.currentFile());
3400     cursorHistory->setInsertionEnabled(true);
3401 
3402     if (!s.PDFFile().isEmpty()) {
3403         runInternalCommand("txs:///view-pdf-internal", QFileInfo(s.PDFFile()), enquoteStr(s.PDFFile()) +" "+ (s.PDFEmbedded() ? "--embedded" : "--windowed"));
3404     }
3405     // update completer
3406     if (currentEditorView())
3407         updateCompleter(currentEditorView());
3408 
3409     if (warnMissing && !missingFiles.isEmpty()) {
3410         UtilsUi::txsInformation(tr("The following files could not be loaded:") + "\n" + missingFiles.join("\n"));
3411     }
3412 }
3413 
getCurrentSession()3414 Session Texstudio::getCurrentSession()
3415 {
3416 	Session s;
3417 
3418 	foreach (LatexEditorView *edView, editors->editors()) {
3419 		FileInSession f;
3420 		f.fileName = edView->editor->fileName();
3421 		f.editorGroup = editors->tabGroupIndexFromEditor(edView);
3422 		f.cursorLine = edView->editor->cursor().lineNumber();
3423 		f.cursorCol = edView->editor->cursor().columnNumber();
3424 		f.firstLine = edView->editor->getFirstVisibleLine();
3425 		f.foldedLines = edView->document->foldedLines();
3426 		if (!f.fileName.isEmpty())
3427 			s.addFile(f);
3428 	}
3429 	s.setMasterFile(documents.masterDocument ? documents.masterDocument->getFileName() : "");
3430 	s.setCurrentFile(currentEditorView() ? currentEditor()->fileName() : "");
3431 
3432 	s.setBookmarks(bookmarks->getBookmarks());
3433 #ifndef NO_POPPLER_PREVIEW
3434 	if (!PDFDocument::documentList().isEmpty()) {
3435 		PDFDocument *doc = PDFDocument::documentList().at(0);
3436 		s.setPDFEmbedded(doc->embeddedMode);
3437 		s.setPDFFile(doc->fileName());
3438 	}
3439 #endif
3440 
3441 	return s;
3442 }
3443 
MarkCurrentFileAsRecent()3444 void Texstudio::MarkCurrentFileAsRecent()
3445 {
3446 	configManager.addRecentFile(getCurrentFileName(), documents.masterDocument == currentEditorView()->document);
3447 }
3448 
3449 //////////////////////////// EDIT ///////////////////////
editUndo()3450 void Texstudio::editUndo()
3451 {
3452 	if (!currentEditorView()) return;
3453 
3454 	QVariant zw = currentEditor()->property("undoRevision");
3455 	int undoRevision = zw.canConvert<int>() ? zw.toInt() : 0;
3456 
3457 	if (currentEditor()->document()->canUndo()) {
3458 		currentEditor()->undo();
3459 		if (undoRevision > 0) {
3460 			undoRevision = -1;
3461 			currentEditor()->setProperty("undoRevision", undoRevision);
3462 		}
3463 	} else {
3464 		if (configManager.svnUndo && (undoRevision >= 0)) {
3465 			svnUndo();
3466 		}
3467 	}
3468 }
3469 
editRedo()3470 void Texstudio::editRedo()
3471 {
3472 	if (!currentEditorView()) return;
3473 
3474 	QVariant zw = currentEditor()->property("undoRevision");
3475 	int undoRevision = zw.canConvert<int>() ? zw.toInt() : 0;
3476 
3477 	if (currentEditor()->document()->canRedo()) {
3478 		currentEditorView()->editor->redo();
3479 	} else {
3480 		if (configManager.svnUndo && (undoRevision > 0)) {
3481 			svnUndo(true);
3482 		}
3483 	}
3484 }
3485 
editDebugUndoStack()3486 void Texstudio::editDebugUndoStack()
3487 {
3488 	if (!currentEditor()) return;
3489 	QString history = currentEditor()->document()->debugUndoStack();
3490 	fileNew();
3491 	currentEditor()->document()->setText(history, false);
3492 }
3493 
editCopy()3494 void Texstudio::editCopy()
3495 {
3496 	if ((!currentEditor() || !currentEditor()->hasFocus()) &&
3497 	        outputView->childHasFocus() ) {
3498 		outputView->copy();
3499 		return;
3500 	}
3501 	if (!currentEditorView()) return;
3502 	currentEditorView()->editor->copy();
3503 }
3504 
editPaste()3505 void Texstudio::editPaste()
3506 {
3507 	if (!currentEditorView()) return;
3508 
3509 	const QMimeData *d = QApplication::clipboard()->mimeData();
3510     if ((d->hasFormat("application/x-openoffice-embed-source-xml;windows_formatname=\"Star Embed Source (XML)\"")||d->hasFormat("application/x-qt-windows-mime;value=\"Star Embed Source (XML)\"")) && d->hasFormat("text/plain")) {
3511 		// workaround for LibreOffice (im "application/x-qt-image" has a higher priority for them than "text/plain")
3512 		currentEditorView()->paste();
3513 		return;
3514 	}
3515 
3516 	foreach (const QString &format, d->formats()) {
3517 		// formats is a priority order. Use the first (== most suitable) format
3518 		if (format == "text/uri-list") {
3519 			QList<QUrl> uris = d->urls();
3520 
3521 			bool onlyLocalImages = true;
3522 			for (int i = 0; i < uris.length(); i++) {
3523 				QFileInfo fi = QFileInfo(uris.at(i).toLocalFile());
3524 				if (!fi.exists() || !InsertGraphics::imageFormats().contains(fi.suffix())) {
3525 					onlyLocalImages = false;
3526 					break;
3527 				}
3528 			}
3529 
3530 			if (onlyLocalImages) {
3531 				for (int i = 0; i < uris.length(); i++) {
3532 					quickGraphics(uris.at(i).toLocalFile());
3533 				}
3534 			} else {
3535 				currentEditorView()->paste();
3536 			}
3537 			return;
3538 		} else if (format == "text/plain" || format == "text/html") {
3539 			currentEditorView()->paste();
3540 			return;
3541 		} else if ((format == "application/x-qt-image" || format.startsWith("image/")) && d->hasImage()) {
3542 			editPasteImage(qvariant_cast<QImage>(d->imageData()));
3543 			return;
3544 		}
3545 	}
3546 	currentEditorView()->paste();  // fallback
3547 }
3548 
editPasteImage(QImage image)3549 void Texstudio::editPasteImage(QImage image)
3550 {
3551 	if (!currentEditorView()) return;
3552 	static QString filenameSuggestion;  // keep for future calls
3553 	QString rootDir = currentEditorView()->document->getRootDocument()->getFileInfo().absolutePath();
3554 	if (filenameSuggestion.isEmpty()) {
3555 		filenameSuggestion = rootDir + "/screenshot001.png";
3556 	}
3557 	QStringList filters;
3558     filters << "*.png";
3559 	QString filter = tr("Image Formats (%1)").arg(filters.join(" "));
3560 	filenameSuggestion = getNonextistentFilename(filenameSuggestion, rootDir);
3561 	QString filename = FileDialog::getSaveFileName(this, tr("Save Image"), filenameSuggestion, filter, &filter);
3562 	if (filename.isEmpty()) return;
3563 	filenameSuggestion = filename;
3564 
3565 	if (!image.save(filename)) {
3566 		UtilsUi::txsCritical(tr("Could not save the image file."));
3567 		return;
3568 	}
3569 	quickGraphics(filename);
3570 }
3571 
editPasteLatex()3572 void Texstudio::editPasteLatex()
3573 {
3574 	if (!currentEditorView()) return;
3575 	// manipulate clipboard text
3576 	QClipboard *clipboard = QApplication::clipboard();
3577 	QString originalText = clipboard->text();
3578 	QString newText = textToLatex(originalText);
3579 	//clipboard->setText(newText);
3580 	// insert
3581 	//currentEditorView()->editor->paste();
3582 	QMimeData md;
3583 	md.setText(newText);
3584 	currentEditorView()->editor->insertFromMimeData(&md);
3585 }
3586 
convertToLatex()3587 void Texstudio::convertToLatex()
3588 {
3589 	if (!currentEditorView()) return;
3590 	// get selection and change it
3591 	QString originalText = currentEditor()->cursor().selectedText();
3592 	QString newText = textToLatex(originalText);
3593 	// insert
3594 	currentEditor()->write(newText);
3595 }
3596 
editDeleteLine()3597 void Texstudio::editDeleteLine()
3598 {
3599 	if (!currentEditorView()) return;
3600 	currentEditorView()->deleteLines(true, true);
3601 }
3602 
editDeleteToEndOfLine()3603 void Texstudio::editDeleteToEndOfLine()
3604 {
3605 	if (!currentEditorView()) return;
3606 	currentEditorView()->deleteLines(false, true);
3607 }
3608 
editDeleteFromStartOfLine()3609 void Texstudio::editDeleteFromStartOfLine()
3610 {
3611 	if (!currentEditorView()) return;
3612 	currentEditorView()->deleteLines(true, false);
3613 }
3614 
editMoveLineUp()3615 void Texstudio::editMoveLineUp()
3616 {
3617 	if (!currentEditorView()) return;
3618 	currentEditorView()->moveLines(-1);
3619 }
3620 
editMoveLineDown()3621 void Texstudio::editMoveLineDown()
3622 {
3623 	if (!currentEditorView()) return;
3624 	currentEditorView()->moveLines(1);
3625 }
3626 
editDuplicateLine()3627 void Texstudio::editDuplicateLine()
3628 {
3629 	if (!currentEditor()) return;
3630 	QEditor *ed = currentEditor();
3631 	QList<QDocumentCursor> cursors = ed->cursors();
3632     for (int i = 0; i < cursors.length(); i++){
3633         cursors[i].setAutoUpdated(false);
3634     }
3635 	QList<QPair<int, int> > blocks = currentEditorView()->getSelectedLineBlocks();
3636 	for (int i = blocks.size() - 1; i >= 0; i--) {
3637 		QDocumentCursor edit = ed->document()->cursor(blocks[i].first, 0, blocks[i].second);
3638 		QString text = edit.selectedText();
3639         edit.selectionEnd().insertText("\n" + text);
3640 	}
3641     if(cursors.length()>0)
3642         ed->setCursor(cursors[0]);
3643 }
3644 
editSortLines()3645 void Texstudio::editSortLines()
3646 {
3647 	if (!currentEditorView()) return;
3648 	QStringList sortingOptions = QStringList() << tr("Ascending") << tr("Descending") << tr("No Sorting") << tr("Random (Shuffle)");
3649 	static int sorting; configManager.registerOption("Editor/Sort Lines/Method", &sorting, 0);
3650 	static bool completelines; configManager.registerOption("Editor/Sort Lines/Complete Lines", &completelines, false);
3651 	static bool casesensitive; configManager.registerOption("Editor/Sort Lines/Case Sensitive", &casesensitive, false);
3652 	static bool removeduplicates; configManager.registerOption("Editor/Sort Lines/Remove Duplicates", &removeduplicates, false);
3653 	UniversalInputDialog dialog;
3654 	dialog.addVariable(&sorting, sortingOptions, tr("Sorting"));
3655 	dialog.addVariable(&completelines, tr("Complete Lines"));
3656 	dialog.addVariable(&casesensitive, tr("Case Sensitive"));
3657 	dialog.addVariable(&removeduplicates, tr("Remove Duplicates"));
3658 	if (dialog.exec() == QDialog::Accepted)
3659 		currentEditorView()->sortSelectedLines(static_cast<LatexEditorView::LineSorting>(sorting), casesensitive ? Qt::CaseSensitive : Qt::CaseInsensitive, completelines, removeduplicates);
3660 }
3661 
editAlignMirrors()3662 void Texstudio::editAlignMirrors()
3663 {
3664 	if (!currentEditor()) return;
3665 	currentEditorView()->alignMirrors();
3666 }
3667 
editEraseWordCmdEnv()3668 void Texstudio::editEraseWordCmdEnv()
3669 {
3670 	if (!currentEditorView()) return;
3671 	QDocumentCursor cursor = currentEditorView()->editor->cursor();
3672 	if (cursor.isNull()) return;
3673 	QString line = cursor.line().text();
3674 	QDocumentLineHandle *dlh = cursor.line().handle();
3675 	QString command, value;
3676 
3677 	// Hack to fix problem problem reported in bug report 3443336 (see also detailed description there):
3678 	// findContext does not work at beginning of commands. Actually it would need
3679 	// pre-context and post-context otherwise, what context is either ambigous, like in
3680 	// \cmd|\cmd or it cannot work simultaneously at beginning and and (you cannot assign
3681 	// the context for |\cmd and \cmd| even if \cmd is surrounded by spaces. The latter
3682 	// both assignments make sense for editEraseWordCmdEnv().
3683 	//
3684 	// pre-context and post-context may be added when revising the latex parser
3685 	//
3686 	// Prelimiary solution part I:
3687 	// Predictable behaviour on selections: do nothing except in easy cases
3688 	if (cursor.hasSelection()) {
3689 		QRegExp partOfWordOrCmd("\\\\?\\w*");
3690 		if (!partOfWordOrCmd.exactMatch(cursor.selectedText()))
3691 			return;
3692 	}
3693 	// Prelimiary solution part II:
3694 	// If the case |\cmd is encountered, we shift the
3695 	// cursor by one to \|cmd so the regular erase approach works.
3696 	int col = cursor.columnNumber();
3697 	if ((col < line.count())						// not at end of line
3698 	        && (line.at(col) == '\\')					// likely a command/env - check further
3699 	        && (col == 0 || line.at(col - 1).isSpace()))	// cmd is at start or previous char is space: non-ambiguous situation like word|\cmd
3700 		// don't need to finally check for command |\c should be handled like \|c for any c (even space and empty)
3701 	{
3702 		cursor.movePosition(1);
3703 	}
3704 
3705 	TokenList tl = dlh->getCookieLocked(QDocumentLine::LEXER_COOKIE).value<TokenList>();
3706 	int tkPos = Parsing::getTokenAtCol(tl, cursor.columnNumber());
3707 	Token tk;
3708 	if (tkPos > -1)
3709 		tk = tl.at(tkPos);
3710 
3711 	switch (tk.type) {
3712     case Token::commandUnknown:
3713         [[gnu::fallthrough]];
3714 	case Token::command:
3715 		command = tk.getText();
3716 		if (command == "\\begin" || command == "\\end") {
3717 			value = Parsing::getArg(tl.mid(tkPos + 1), dlh, 0, ArgumentList::Mandatory);
3718 			//remove environment (surrounding)
3719 			currentEditorView()->editor->document()->beginMacro();
3720 			cursor.select(QDocumentCursor::WordOrCommandUnderCursor);
3721 			cursor.removeSelectedText();
3722 			// remove curly brakets as well
3723 			if (cursor.nextChar() == QChar('{')) {
3724 				cursor.deleteChar();
3725 				line = cursor.line().text();
3726 				int col = cursor.columnNumber();
3727 				int i = findClosingBracket(line, col);
3728 				if (i > -1) {
3729 					cursor.movePosition(i - col + 1, QDocumentCursor::NextCharacter, QDocumentCursor::KeepAnchor);
3730 					cursor.removeSelectedText();
3731 					QDocument *doc = currentEditorView()->editor->document();
3732 					QString searchWord = "\\end{" + value + "}";
3733 					QString inhibitor = "\\begin{" + value + "}";
3734 					bool backward = (command == "\\end");
3735 					int step = 1;
3736 					if (backward) {
3737 						qSwap(searchWord, inhibitor);
3738 						step = -1;
3739 					}
3740 					int startLine = cursor.lineNumber();
3741 					int startCol = cursor.columnNumber();
3742 					int endLine = doc->findLineContaining(searchWord, startLine, Qt::CaseSensitive, backward);
3743 					int inhibitLine = doc->findLineContaining(inhibitor, startLine, Qt::CaseSensitive, backward); // not perfect (same line end/start ...)
3744 					while (inhibitLine > 0 && endLine > 0 && inhibitLine * step < endLine * step) {
3745 						endLine = doc->findLineContaining(searchWord, endLine + step, Qt::CaseSensitive, backward); // not perfect (same line end/start ...)
3746 						inhibitLine = doc->findLineContaining(inhibitor, inhibitLine + step, Qt::CaseSensitive, backward);
3747 					}
3748 					if (endLine > -1) {
3749 						line = doc->line(endLine).text();
3750 						int start = line.indexOf(searchWord);
3751 						cursor.moveTo(endLine, start);
3752 						cursor.movePosition(searchWord.length(), QDocumentCursor::NextCharacter, QDocumentCursor::KeepAnchor);
3753 						cursor.removeSelectedText();
3754 						cursor.moveTo(startLine, startCol); // move cursor back to text edit pos
3755 					}
3756 				}
3757 			}
3758 
3759 			currentEditorView()->editor->document()->endMacro();
3760 		} else {
3761 			currentEditorView()->editor->document()->beginMacro();
3762 			cursor.select(QDocumentCursor::WordOrCommandUnderCursor);
3763 			cursor.removeSelectedText();
3764 			// remove curly brakets as well
3765 			if (cursor.nextChar() == QChar('{')) {
3766 				cursor.deleteChar();
3767 				line = cursor.line().text();
3768 				int col = cursor.columnNumber();
3769 				int i = findClosingBracket(line, col);
3770 				if (i > -1) {
3771 					cursor.moveTo(cursor.lineNumber(), i);
3772 					cursor.deleteChar();
3773 					cursor.moveTo(cursor.lineNumber(), col);
3774 				}
3775 			}
3776 			currentEditorView()->editor->document()->endMacro();
3777 		}
3778 		break;
3779 
3780 	default:
3781 		cursor.select(QDocumentCursor::WordUnderCursor);
3782 		cursor.removeSelectedText();
3783 		break;
3784 	}
3785 	currentEditorView()->editor->setCursor(cursor);
3786 }
3787 
editGotoDefinition(QDocumentCursor c)3788 void Texstudio::editGotoDefinition(QDocumentCursor c)
3789 {
3790 	if (!currentEditorView()) return;
3791 	if (!c.isValid()) c = currentEditor()->cursor();
3792 	saveCurrentCursorToHistory();
3793 
3794 	LatexDocument *doc = qobject_cast<LatexDocument *>(c.document());
3795 	Token tk = Parsing::getTokenAtCol(c.line().handle(), c.columnNumber());
3796 
3797 	switch (tk.type) {
3798 	case Token::labelRef:
3799 	case Token::labelRefList: {
3800 		QMultiHash<QDocumentLineHandle *, int> defs = doc->getLabels(tk.getText());
3801 		if (defs.isEmpty()) return;
3802         QDocumentLineHandle *target = defs.keys().constFirst();
3803 		LatexEditorView *edView = getEditorViewFromHandle(target);
3804 		if (!edView) return;
3805 		if (edView != currentEditorView()) {
3806 			editors->setCurrentEditor(edView);
3807 		}
3808 		edView->gotoLineHandleAndSearchLabel(target, tk.getText());
3809 		break;
3810 	}
3811 	case Token::bibItem: {
3812 		QString bibID = trimLeft(tk.getText());
3813 		// try local \bibitems
3814 		QMultiHash<QDocumentLineHandle *, int> defs = doc->getBibItems(bibID);
3815 		if (!defs.isEmpty()) {
3816             QDocumentLineHandle *target = defs.keys().constFirst();
3817 			bool found = currentEditorView()->gotoLineHandleAndSearchBibItem(target, bibID);
3818 			if (found) break;
3819 		}
3820 		// try bib files
3821 		QString bibFile = currentEditorView()->document->findFileFromBibId(bibID);
3822 		LatexEditorView *edView = getEditorViewFromFileName(bibFile);
3823 		if (!edView) {
3824 			if (!load(bibFile)) return;
3825 			edView = currentEditorView();
3826 		}
3827 		int line = edView->document->findLineRegExp("@\\w+{\\s*" + bibID, 0, Qt::CaseSensitive, true, true);
3828 		if (line < 0) {
3829 			line = edView->document->findLineContaining(bibID); // fallback in case the above regexp does not reflect the most general case
3830 			if (line < 0) return;
3831 		}
3832 		int col = edView->document->line(line).text().indexOf(bibID);
3833 		if (col < 0) col = 0;
3834 		gotoLine(line, col, edView);
3835 		break;
3836 	}
3837 	case Token::commandUnknown:
3838 	case Token::command:
3839 	case Token::beginEnv:
3840 	case Token::env: {
3841 		QDocumentLineHandle *target = doc->findCommandDefinition(tk.getText());
3842 		if (target) {
3843 			// command is user-defined, jump to definition
3844 			LatexEditorView *edView = getEditorViewFromHandle(target);
3845 			if (edView) {
3846 				if (edView != currentEditorView()) {
3847 					editors->setCurrentEditor(edView);
3848 				}
3849 				edView->gotoLineHandleAndSearchString(target, tk.getText());
3850 				break;
3851 			}
3852 		}
3853 		// command might be defined by a package, jump to \usepackage (if possible)
3854 		QString command = tk.getText();
3855 		if (tk.type == Token::beginEnv || tk.type == Token::env) {
3856 			command = "\\begin{" + command + "}";
3857 		}
3858 		QString package = doc->parent->findPackageByCommand(command);
3859 		package.chop(4);
3860 		// skip builtin packages (we cannot goto the \usepackage in this case)
3861 		if (package == "tex" || package == "latex-document")
3862 			return;
3863 		target = doc->findUsePackage(package);
3864 		if (!target) return;
3865 		LatexEditorView *edView = getEditorViewFromHandle(target);
3866 		if (!edView) return;
3867 		if (edView != currentEditorView()) {
3868 			editors->setCurrentEditor(edView);
3869 		}
3870 		edView->gotoLineHandleAndSearchString(target, tk.getText());
3871 		break;
3872 	}
3873 	default:;
3874 	}
3875 }
3876 
editHardLineBreak()3877 void Texstudio::editHardLineBreak()
3878 {
3879 	if (!currentEditorView()) return;
3880 	UniversalInputDialog dialog;
3881 	dialog.addVariable(&configManager.lastHardWrapColumn, tr("Insert hard line breaks after so many characters:"));
3882 	dialog.addVariable(&configManager.lastHardWrapSmartScopeSelection, tr("Smart scope selecting"));
3883 	dialog.addVariable(&configManager.lastHardWrapJoinLines, tr("Join lines before wrapping"));
3884 	if (dialog.exec() == QDialog::Accepted)
3885 		currentEditorView()->insertHardLineBreaks(configManager.lastHardWrapColumn, configManager.lastHardWrapSmartScopeSelection, configManager.lastHardWrapJoinLines);
3886 }
3887 
editHardLineBreakRepeat()3888 void Texstudio::editHardLineBreakRepeat()
3889 {
3890 	if (!currentEditorView()) return;
3891 	currentEditorView()->insertHardLineBreaks(configManager.lastHardWrapColumn, configManager.lastHardWrapSmartScopeSelection, configManager.lastHardWrapJoinLines);
3892 }
3893 
editSpell()3894 void Texstudio::editSpell()
3895 {
3896 	if (!currentEditorView()) {
3897 		UtilsUi::txsWarning(tr("No document open"));
3898 		return;
3899 	}
3900 	SpellerUtility *su = spellerManager.getSpeller(currentEditorView()->getSpeller());
3901 	if (!su) return; // getSpeller already gives a warning message
3902 	if (su->name() == "<none>") {
3903 		UtilsUi::txsWarning(tr("No dictionary available."));
3904 		return;
3905 	}
3906 	if (!spellDlg) spellDlg = new SpellerDialog(this, su);
3907 	spellDlg->setEditorView(currentEditorView());
3908 	spellDlg->startSpelling();
3909 }
3910 
editThesaurus(int line,int col)3911 void Texstudio::editThesaurus(int line, int col)
3912 {
3913 	if (!ThesaurusDialog::retrieveDatabase()) {
3914 		QMessageBox::warning(this, tr("Error"), tr("Can't load Thesaurus Database"));
3915 		return;
3916 	}
3917 	ThesaurusDialog *thesaurusDialog = new ThesaurusDialog(this);
3918 	QString word;
3919 	if (currentEditorView()) {
3920 		QDocumentCursor m_cursor = currentEditorView()->editor->cursor();
3921 		if (line > -1 && col > -1) {
3922 			m_cursor.moveTo(line, col);
3923 		}
3924 		if (m_cursor.hasSelection()) word = m_cursor.selectedText();
3925 		else {
3926 			m_cursor.select(QDocumentCursor::WordUnderCursor);
3927 			word = m_cursor.selectedText();
3928 		}
3929 		word = latexToPlainWord(word);
3930 		thesaurusDialog->setSearchWord(word);
3931 		if (thesaurusDialog->exec()) {
3932 			QString replace = thesaurusDialog->getReplaceWord();
3933 			m_cursor.document()->clearLanguageMatches();
3934 			m_cursor.insertText(replace);
3935 		}
3936 	}
3937 	delete thesaurusDialog;
3938 }
3939 
editChangeLineEnding()3940 void Texstudio::editChangeLineEnding()
3941 {
3942 	if (!currentEditorView()) return;
3943 	QAction *action = qobject_cast<QAction *>(sender());
3944 	if (!action) return;
3945 	currentEditorView()->editor->document()->setLineEnding(QDocument::LineEnding(action->data().toInt()));
3946 	updateCaption();
3947 }
3948 
editSetupEncoding()3949 void Texstudio::editSetupEncoding()
3950 {
3951 	if (!currentEditorView()) return;
3952 	EncodingDialog enc(this, currentEditorView()->editor);
3953 	enc.exec();
3954 	updateCaption();
3955 }
3956 
editInsertUnicode()3957 void Texstudio::editInsertUnicode()
3958 {
3959 	if (!currentEditorView()) return;
3960 	QDocumentCursor c = currentEditor()->cursor();
3961 	if (!c.isValid()) return;
3962 	uint curPoint = 0;
3963 	if (c.hasSelection()) {
3964 		QString sel = c.selectedText();
3965 		if (sel.length() == 1) curPoint = sel[0].unicode();
3966 		else if (sel.length() == 2 && sel.at(0).isHighSurrogate() && sel.at(1).isLowSurrogate()) {
3967 			curPoint = sel.toUcs4().value(0, 0);
3968 		} else c.setAnchorColumnNumber(c.columnNumber());
3969 		currentEditor()->setCursor(c);
3970 	}
3971     QPointF offset;
3972 	UnicodeInsertion *uid = new UnicodeInsertion (currentEditorView(), curPoint);
3973 	if (!currentEditor()->getPositionBelowCursor(offset, uid->width(), uid->height())) {
3974 		delete uid;
3975 		return;
3976 	}
3977 	connect(uid, SIGNAL(insertCharacter(QString)), currentEditor(), SLOT(insertText(QString)));
3978 	connect(uid, SIGNAL(destroyed()), currentEditor(), SLOT(setFocus()));
3979 	connect(currentEditor(), SIGNAL(cursorPositionChanged()), uid, SLOT(close()));
3980 	connect(currentEditor(), SIGNAL(visibleLinesChanged()), uid, SLOT(close()));
3981 	connect(currentEditor()->document(), SIGNAL(contentsChanged()), uid, SLOT(close()));
3982 
3983     uid->move(currentEditor()->mapTo(uid->parentWidget(), offset.toPoint()));
3984 	this->unicodeInsertionDialog = uid;
3985 	uid->show();
3986 	uid->setFocus();
3987 }
3988 
changeCase(QEditor * editor,QString (* method)(QString))3989 void changeCase(QEditor *editor, QString(*method)(QString))
3990 {
3991 	if (!editor) return;
3992 	QList<QDocumentCursor> cs = editor->cursors();
3993 	bool allEmpty = true;
3994 	foreach (QDocumentCursor c, cs)
3995 		if (!c.selectedText().isEmpty()) {
3996 			allEmpty = false;
3997 			break;
3998 		}
3999 	if (allEmpty) return;
4000 
4001 	editor->document()->beginMacro();
4002 	foreach (QDocumentCursor c, editor->cursors())
4003 		c.replaceSelectedText( method(c.selectedText()) );
4004 	editor->document()->endMacro();
4005 }
4006 /*!
4007  * Helperfunction to convert a string to lower case.
4008  * \param in input string
4009  * \result string in lower case
4010  */
txsToLower(QString in)4011 QString txsToLower(QString in)
4012 {
4013 	return in.toLower();
4014 }
4015 
4016 /*!
4017  * Converts the selected text to lower case.
4018  */
editTextToLowercase()4019 void Texstudio::editTextToLowercase()
4020 {
4021 	changeCase(currentEditor(), &txsToLower);
4022 }
4023 /*!
4024  * Helperfunction to convert a string to upper case.
4025  * \param in input string
4026  * \result string in upper case
4027  */
4028 
txsToUpper(QString in)4029 QString txsToUpper(QString in)
4030 {
4031 	return in.toUpper();
4032 }
4033 
4034 /*!
4035  * Converts the selected text to upper case.
4036  */
editTextToUppercase()4037 void Texstudio::editTextToUppercase()
4038 {
4039 	changeCase(currentEditor(), &txsToUpper);
4040 }
4041 
4042 /*!
4043  * Converts the selected text to title case. Small words like a,an etc. are not converted.
4044  * \param smart: Words containing capital letters are not converted because the are assumed to be acronymes.
4045  */
editTextToTitlecase(bool smart)4046 void Texstudio::editTextToTitlecase(bool smart)
4047 {
4048 	if (!currentEditorView()) return;
4049 	QDocumentCursor m_cursor = currentEditorView()->editor->cursor();
4050 	QString text = m_cursor.selectedText();
4051 	if (text.isEmpty()) return;
4052 	m_cursor.beginEditBlock();
4053 	// easier to be done in javascript
4054 	scriptengine *eng = new scriptengine();
4055 	eng->setEditorView(currentEditorView());
4056 	QString script =
4057 	    "/* \n" \
4058 	    "	* To Title Case 2.1 – http://individed.com/code/to-title-case/ \n" \
4059 	    "	* Copyright © 2008–2013 David Gouch. Licensed under the MIT License.\n" \
4060 	    "*/ \n" \
4061 	    "toTitleCase = function(text){\n" \
4062 	    "var smallWords = /^(a|an|and|as|at|but|by|en|for|if|in|nor|of|on|or|per|the|to|vs?\\.?|via)$/i;\n" \
4063 	    "return text.replace(/[A-Za-z0-9\\u00C0-\\u00FF]+[^\\s-]*/g, function(match, index, title){\n" \
4064 	    "if (index > 0 && index + match.length !== title.length &&\n" \
4065 	    "  match.search(smallWords) > -1 && title.charAt(index - 2) !== \":\" &&\n" \
4066 	    "  (title.charAt(index + match.length) !== '-' || title.charAt(index - 1) === '-') &&\n" \
4067 	    "  title.charAt(index - 1).search(/[^\\s-]/) < 0) {\n" \
4068 	    "    return match.toLowerCase();\n" \
4069 	    "}\n";
4070 	if (smart) {
4071 		script +=
4072 		    "if (match.substr(1).search(/[A-Z]|\\../) > -1) {\n" \
4073 		    "return match;\n" \
4074 		    "}\n" \
4075 		    "return match.charAt(0).toUpperCase() + match.substr(1);\n";
4076 	} else {
4077 		script +=
4078 		    "return match.charAt(0).toUpperCase() + match.substr(1).toLowerCase();\n";
4079 	}
4080 	script +=
4081 	    "});\n" \
4082 	    "};\n" \
4083 	    "editor.replaceSelectedText(toTitleCase)";
4084 	eng->setScript(script);
4085 	eng->run();
4086 
4087 	m_cursor.endEditBlock();
4088 
4089 }
4090 
editTextToTitlecaseSmart()4091 void Texstudio::editTextToTitlecaseSmart()
4092 {
4093 	editTextToTitlecase(true);
4094 }
4095 
editFind()4096 void Texstudio::editFind()
4097 {
4098 #ifndef NO_POPPLER_PREVIEW
4099     QWidget *w = QApplication::focusWidget();
4100     while (w && !qobject_cast<PDFDocument *>(w))
4101         w = w->parentWidget();
4102 
4103     if (qobject_cast<PDFDocument *>(w)) {
4104         PDFDocument *focusedPdf = qobject_cast<PDFDocument *>(w);
4105         if (focusedPdf->embeddedMode) {
4106             focusedPdf->search();
4107             return;
4108         }
4109     }
4110 #endif
4111     if (!currentEditor()) return;
4112     currentEditor()->find();
4113 }
4114 
4115 /////////////// CONFIG ////////////////////
readSettings(bool reread)4116 void Texstudio::readSettings(bool reread)
4117 {
4118     QuickDocumentDialog::registerOptions(configManager);
4119     QuickBeamerDialog::registerOptions(configManager);
4120     buildManager.registerOptions(configManager);
4121     configManager.registerOption("Files/Default File Filter", &selectedFileFilter);
4122     configManager.registerOption("PDFSplitter", &pdfSplitterRel, 0.5);
4123 
4124     configManager.buildManager = &buildManager;
4125     scriptengine::buildManager = &buildManager;
4126     scriptengine::app = this;
4127     QSettings *config = configManager.readSettings(reread);
4128     completionBaseCommandsUpdated = true;
4129 
4130     config->beginGroup("texmaker");
4131 
4132     QRect screen = QGuiApplication::primaryScreen()->availableGeometry();
4133     int w = config->value("Geometries/MainwindowWidth", screen.width() - 100).toInt();
4134     int h = config->value("Geometries/MainwindowHeight", screen.height() - 100).toInt() ;
4135     int x = config->value("Geometries/MainwindowX", screen.x() + 10).toInt();
4136     int y = config->value("Geometries/MainwindowY", screen.y() + 10).toInt() ;
4137     screen = UtilsUi::getAvailableGeometryAt(QPoint(x, y));
4138     if (!screen.contains(x, y)) {
4139         // top left is not on screen
4140         x = screen.x() + 10;
4141         y = screen.y() + 10;
4142         if (x + w > screen.right()) w = screen.width() - 100;
4143         if (y + h > screen.height()) h = screen.height() - 100;
4144     }
4145     resize(w, h);
4146     move(x, y);
4147     windowstate = config->value("MainWindowState").toByteArray();
4148     stateFullScreen = config->value("MainWindowFullssscreenState").toByteArray();
4149     tobemaximized = config->value("MainWindow/Maximized", false).toBool();
4150     tobefullscreen = config->value("MainWindow/FullScreen", false).toBool();
4151 
4152     //dark mode menu
4153     if(darkMode && configManager.useTexmakerPalette){
4154         QString ownStyle;
4155         ownStyle="QMenuBar {background: #404040 }";
4156         ownStyle+="QMenuBar::item { background: transparent;}";
4157         ownStyle+="QMenuBar::item:selected { background: #808080;}";
4158         ownStyle+="QMenuBar::item:pressed { background: #888888; }";
4159         ownStyle+="QCheckBox::indicator:unchecked { background: #404040; }";
4160         ownStyle+="QTableView::indicator:unchecked { background-color: #404040 }";
4161         ownStyle+="QListView::indicator:unchecked { background-color: #404040 }";
4162         ownStyle+="QTabBar::tab {background-color: #404040 }";
4163         ownStyle+="QTabBar::tab:selected {background-color: #606060 }";
4164         setStyleSheet(ownStyle);
4165     }
4166 
4167     spellerManager.setIgnoreFilePrefix(configManager.configFileNameBase);
4168     spellerManager.setDictPaths(configManager.parseDirList(configManager.spellDictDir));
4169     spellerManager.setDefaultSpeller(configManager.spellLanguage);
4170 
4171     ThesaurusDialog::setUserPath(configManager.configFileNameBase);
4172     ThesaurusDialog::prepareDatabase(configManager.parseDir(configManager.thesaurus_database));
4173 
4174     symbolListModel = new SymbolListModel(config->value("Symbols/UsageCount").toMap(),
4175                                           config->value("Symbols/FavoriteIDs").toStringList());
4176     symbolListModel->setDarkmode(darkMode);
4177     hiddenLeftPanelWidgets = config->value("Symbols/hiddenlists", "").toString();  // TODO: still needed?
4178 
4179     configManager.editorKeys = QEditor::getEditOperations(false); //this will also initialize the default keys
4180     configManager.editorAvailableOperations = QEditor::getAvailableOperations();
4181     if (config->value("Editor/Use Tab for Move to Placeholder", false).toBool()) {
4182         //import deprecated option
4183         QEditor::addEditOperation(QEditor::NextPlaceHolder, Qt::ControlModifier, Qt::Key_Tab);
4184         QEditor::addEditOperation(QEditor::PreviousPlaceHolder, Qt::ShiftModifier | Qt::ControlModifier, Qt::Key_Backtab);
4185         QEditor::addEditOperation(QEditor::CursorWordLeft, Qt::ControlModifier, Qt::Key_Left);
4186         QEditor::addEditOperation(QEditor::CursorWordRight, Qt::ControlModifier, Qt::Key_Right);
4187     }
4188     // import and remove old key mapping
4189     {
4190         config->beginGroup("Editor Key Mapping");
4191         QStringList sl = config->childKeys();
4192         if (!sl.empty()) {
4193             foreach (const QString &key, sl) {
4194                 int k = key.toInt();
4195                 if (k == 0) continue;
4196                 int operationID = config->value(key).toInt();
4197                 QString defaultKey = configManager.editorKeys.key(operationID);
4198                 if (!defaultKey.isNull()) {
4199                     configManager.editorKeys.remove(defaultKey);
4200                 }
4201                 configManager.editorKeys.insert(QKeySequence(k).toString(), config->value(key).toInt());
4202             }
4203             QEditor::setEditOperations(configManager.editorKeys);
4204             config->remove("");
4205         }
4206         config->endGroup();
4207     }
4208     config->beginGroup("Editor Key Mapping New");
4209     QStringList sl = config->childKeys();
4210     if (!sl.empty()) {
4211         foreach (const QString &key, sl) {
4212             if (key.isEmpty()) continue;
4213             int operationID = config->value(key).toInt();
4214             if (key.startsWith("#")) {
4215                 // remove predefined key
4216                 QString realKey = key.mid(1);
4217                 if (configManager.editorKeys.value(realKey) == operationID) {
4218                     configManager.editorKeys.remove(realKey);
4219                 }
4220             } else {
4221                 // replacement of keys needs to add/remove a key explicitely, as otherwise a simple addition can't be saved into .ini
4222                 configManager.editorKeys.insert(key, operationID);
4223             }
4224         }
4225         QEditor::setEditOperations(configManager.editorKeys);
4226     }
4227     config->endGroup();
4228     config->endGroup();
4229 
4230     if(darkMode){
4231         config->beginGroup("formatsDark");
4232         m_formats = new QFormatFactory(":/qxs/defaultFormatsDark.qxf", this); //load default formats from resource file
4233         m_formats->load(*config, true); //load customized formats
4234         config->endGroup();
4235     }else{
4236         config->beginGroup("formats");
4237         m_formats = new QFormatFactory(":/qxs/defaultFormats.qxf", this); //load default formats from resource file
4238         if (config->contains("data/styleHint/bold")) {
4239             //rename data/styleHint/* => data/wordRepetition/*
4240             config->beginGroup("data");
4241             config->beginGroup("styleHint");
4242             QStringList temp = config->childKeys();
4243             config->endGroup();
4244             foreach (const QString & s, temp) config->setValue("wordRepetition/" + s, config->value("styleHint/" + s));
4245             config->remove("styleHint");
4246             config->endGroup();
4247         }
4248 
4249         m_formats->load(*config, true); //load customized formats
4250         config->endGroup();
4251     }
4252 
4253     documents.settingsRead();
4254 
4255     configManager.editorConfig->settingsChanged();
4256 }
4257 
saveSettings(const QString & configName)4258 void Texstudio::saveSettings(const QString &configName)
4259 {
4260 	bool asProfile = !configName.isEmpty();
4261 	configManager.centralVisible = centralToolBar->isVisible();
4262 	// update completion usage
4263 	LatexCompleterConfig *conf = configManager.completerConfig;
4264 #ifndef NO_POPPLER_PREVIEW
4265 	//pdf viewer embedded open ?
4266 	if (!PDFDocument::documentList().isEmpty()) {
4267         PDFDocument *doc = PDFDocument::documentList().constFirst();
4268 		if (doc->embeddedMode) {
4269 			QList<int> sz = mainHSplitter->sizes(); // set widths to 50%, eventually restore user setting
4270 			int sum = 0;
4271 			int last = 0;
4272 			foreach (int i, sz) {
4273 				sum += i;
4274 				last = i;
4275 			}
4276 			if (last > 10)
4277 				pdfSplitterRel = 1.0 * last / sum;
4278 		}
4279 	}
4280 #endif
4281     symbolWidget->saveSplitterState(configManager.stateSymbolsWidget); // readout splitter state from symbol widget
4282 
4283 	QSettings *config = configManager.saveSettings(configName);
4284 
4285 	config->beginGroup("texmaker");
4286 	if (!asProfile) {
4287 		if (isFullScreen()) {
4288 			config->setValue("MainWindowState", windowstate);
4289 			config->setValue("MainWindowFullssscreenState", saveState(1));
4290 		} else {
4291 			config->setValue("MainWindowState", saveState(0));
4292 			config->setValue("MainWindowFullssscreenState", stateFullScreen);
4293 		}
4294 		config->setValue("MainWindow/Maximized", isMaximized());
4295 		config->setValue("MainWindow/FullScreen", isFullScreen());
4296 
4297 		config->setValue("Geometries/MainwindowWidth", width());
4298 
4299 		config->setValue("Geometries/MainwindowHeight", height());
4300 		config->setValue("Geometries/MainwindowX", x());
4301 		config->setValue("Geometries/MainwindowY", y());
4302 
4303 		config->setValue("GUI/sidePanelSplitter/state", sidePanelSplitter->saveState());
4304 		config->setValue("centralVSplitterState", centralVSplitter->saveState());
4305 		config->setValue("GUI/outputView/visible", outputView->isVisible());
4306 		config->setValue("GUI/sidePanel/visible", sidePanel->isVisible());
4307         config->setValue("GUI/sidePanel/currentPage", leftPanel->currentIndex());
4308 
4309 		if (!ConfigManager::dontRestoreSession) { // don't save session when using --no-restore as this is used for single doc handling
4310 			Session s = getCurrentSession();
4311             QFileInfo f(QDir(configManager.configBaseDir), "lastSession.txss2");
4312             bool ok=false;
4313             if(!f.exists() || (f.exists() && f.isWritable())){
4314                 ok=s.save(f.filePath(), configManager.sessionStoreRelativePaths);
4315             }
4316             if(!ok){
4317                 QMessageBox::warning(this,tr("Storing session failed"),tr("Storing session information into %1 failed. File exists but is not writeable.").arg(f.filePath()));
4318             }
4319 		}
4320 	}
4321 
4322 
4323 	for (int i = 0; i < struct_level.count(); i++)
4324 		config->setValue("Structure/Structure Level " + QString::number(i + 1), struct_level[i]);
4325 
4326 	config->setValue("Symbols/UsageCount", symbolWidget->model()->usageCountAsQVariantMap());
4327 	config->setValue("Symbols/FavoriteIDs", symbolWidget->model()->favorites());
4328 
4329 	// TODO: parse old "Symbols/Favorite IDs"
4330 
4331 	config->setValue("Symbols/hiddenlists", leftPanel->hiddenWidgets());
4332 
4333 	QHash<QString, int> keys = QEditor::getEditOperations(true);
4334 	config->remove("Editor/Use Tab for Move to Placeholder");
4335 	config->beginGroup("Editor Key Mapping New");
4336 	if (!keys.empty() || !config->childKeys().empty()) {
4337 		config->remove("");
4338         QHash<QString, int>::const_iterator i = keys.constBegin();
4339 		while (i != keys.constEnd()) {
4340 			if (!i.key().isEmpty()) //avoid crash
4341 				config->setValue(i.key(), i.value());
4342 			++i;
4343 		}
4344 	}
4345 	config->endGroup();
4346 
4347 	config->endGroup();
4348 
4349         // separate light/dark highlight formats
4350         if(darkMode){
4351             config->beginGroup("formatsDark");
4352 
4353             if(asProfile){
4354                 // save all color info, don't remove default values
4355                 m_formats->save(*config, nullptr);
4356             }else{
4357                 QFormatFactory defaultFormats(":/qxs/defaultFormatsDark.qxf", this); //load default formats from resource file
4358                 m_formats->save(*config, &defaultFormats);
4359             }
4360             config->endGroup();
4361         }else{
4362             config->beginGroup("formats");
4363             if(asProfile){
4364                 // save all color info, don't remove default values
4365                 m_formats->save(*config, nullptr);
4366             }else{
4367                 QFormatFactory defaultFormats(":/qxs/defaultFormats.qxf", this); //load default formats from resource file
4368                 m_formats->save(*config, &defaultFormats);
4369             }
4370             config->endGroup();
4371         }
4372 
4373 	searchResultWidget()->saveConfig();
4374 
4375 	// save usageCount in file of its own.
4376 	if (!asProfile) {
4377 		QFile file(configManager.configBaseDir + "wordCount.usage");
4378 		if (file.open(QIODevice::WriteOnly)) {
4379 			QDataStream out(&file);
4380             out << static_cast<quint32>(0xA0B0C0D0);  //magic number
4381             out << static_cast<qint32>(1); //version
4382 			out.setVersion(QDataStream::Qt_4_0);
4383                         QMultiMap<uint, QPair<int, int> >::const_iterator i = conf->usage.constBegin();
4384 			while (i != conf->usage.constEnd()) {
4385 				QPair<int, int> elem = i.value();
4386 				if (elem.second > 0) {
4387 					out << i.key();
4388 					out << elem.first;
4389 					out << elem.second;
4390 				}
4391 				++i;
4392 			}
4393 		}
4394 	}
4395 
4396 	if (asProfile)
4397 		delete config;
4398 }
4399 
restoreDefaultSettings()4400 void Texstudio::restoreDefaultSettings()
4401 {
4402 	if (!UtilsUi::txsConfirmWarning("This will reset all settings to their defaults. At the end, TeXstudio will be closed. Please start TeXstudio manually anew afterwards.\n\nDo you want to continue?")) {
4403 		return;
4404 	}
4405 	if (canCloseNow(false)) {
4406 		QFile f(configManager.configFileName);
4407 		if (f.exists()) {
4408 			if (f.open(QFile::WriteOnly)) {
4409 				f.write("\n");  // delete contents of settings file
4410 				f.close();
4411 			} else {
4412 				UtilsUi::txsWarning(tr("Unable to write to settings file %1").arg(QDir::toNativeSeparators(f.fileName())));
4413 			}
4414 		}
4415 		qApp->exit(0);
4416 	}
4417 }
4418 
4419 ////////////////// STRUCTURE ///////////////////
updateStructure(bool initial,LatexDocument * doc,bool hidden)4420 void Texstudio::updateStructure(bool initial, LatexDocument *doc, bool hidden)
4421 {
4422 	// collect user define tex commands for completer
4423 	// initialize List
4424 	if ((!currentEditorView() || !currentEditorView()->document) && !doc) return;
4425 	if (!doc)
4426 		doc = currentEditorView()->document;
4427 	if (initial) {
4428 		doc->patchStructure(0, -1);
4429 		// execute QCE highlting
4430 		doc->parent->enablePatch(false);
4431 		doc->highlight();
4432 		doc->parent->enablePatch(true);
4433 
4434 		bool previouslyEmpty=doc->localMacros.isEmpty();
4435 		doc->updateMagicCommentScripts();
4436 		configManager.completerConfig->userMacros << doc->localMacros;
4437 		if(!doc->localMacros.isEmpty() || !previouslyEmpty)
4438 			updateUserMacros();
4439     }
4440 
4441 	if (!hidden) {
4442 		updateCompleter(doc->getEditorView());
4443 		cursorPositionChanged();
4444 	}
4445 
4446 	//structureTreeView->reset();
4447 }
4448 
structureContextMenuToggleMasterDocument(LatexDocument * document)4449 void Texstudio::structureContextMenuToggleMasterDocument(LatexDocument *document)
4450 {
4451 	if (!document) return;
4452 	if (document == documents.masterDocument) setAutomaticRootDetection();
4453 	else setExplicitRootDocument(document);
4454 }
4455 
editRemovePlaceHolders()4456 void Texstudio::editRemovePlaceHolders()
4457 {
4458 	if (!currentEditor()) return;
4459 	for (int i = currentEditor()->placeHolderCount(); i >= 0; i--)
4460 		currentEditor()->removePlaceHolder(i);
4461 	currentEditor()->viewport()->update();
4462 }
4463 
editRemoveCurrentPlaceHolder()4464 void Texstudio::editRemoveCurrentPlaceHolder()
4465 {
4466 	if (!currentEditor()) return;
4467 	currentEditor()->removePlaceHolder(currentEditor()->currentPlaceHolder());
4468 }
4469 
4470 //////////TAGS////////////////
normalCompletion()4471 void Texstudio::normalCompletion()
4472 {
4473 	if (!currentEditorView())	return;
4474 
4475 	QString command;
4476 	QDocumentCursor c = currentEditorView()->editor->cursor();
4477 	QDocumentLineHandle *dlh = c.line().handle();
4478 	//LatexParser::ContextType ctx=view->lp.findContext(word, c.columnNumber(), command, value);
4479 	TokenStack ts = Parsing::getContext(dlh, c.columnNumber());
4480 	Token tk;
4481 	if (!ts.isEmpty()) {
4482 		tk = ts.top();
4483 		if (tk.type == Token::word && tk.subtype == Token::none && ts.size() > 1) {
4484 			// set brace type
4485 			ts.pop();
4486 			tk = ts.top();
4487 		}
4488 	}
4489 
4490 	Token::TokenType type = tk.type;
4491 	if (tk.subtype != Token::none && type!=Token::command && type!=Token::commandUnknown){
4492 		type = tk.subtype;
4493 	}
4494 	if (type == Token::specialArg) {
4495 		int df = int(type - Token::specialArg);
4496 		QString cmd = latexParser.mapSpecialArgs.value(df);
4497 		if (mCompleterNeedsUpdate) updateCompleter();
4498 		completer->setWorkPath(cmd);
4499         currentEditorView()->complete(LatexCompleter::CF_FORCE_VISIBLE_LIST | LatexCompleter::CF_FORCE_SPECIALOPTION);
4500 	}
4501 	switch (type) {
4502 	case Token::command:
4503 	case Token::commandUnknown:
4504 		if (mCompleterNeedsUpdate) updateCompleter();
4505 		currentEditorView()->complete(LatexCompleter::CF_FORCE_VISIBLE_LIST);
4506 		break;
4507 	case Token::env:
4508 	case Token::beginEnv:
4509 		if (mCompleterNeedsUpdate) updateCompleter();
4510         generateMirror(true);
4511 		currentEditorView()->complete(LatexCompleter::CF_FORCE_VISIBLE_LIST);
4512 		break;
4513 	case Token::labelRef:
4514 		if (mCompleterNeedsUpdate) updateCompleter();
4515 		currentEditorView()->complete(LatexCompleter::CF_FORCE_VISIBLE_LIST | LatexCompleter::CF_FORCE_REF);
4516 		break;
4517 	case Token::labelRefList:
4518 		if (mCompleterNeedsUpdate) updateCompleter();
4519 		currentEditorView()->complete(LatexCompleter::CF_FORCE_VISIBLE_LIST | LatexCompleter::CF_FORCE_REF | LatexCompleter::CF_FORCE_REFLIST);
4520 		break;
4521 	case Token::bibItem:
4522 		if (mCompleterNeedsUpdate) updateCompleter();
4523 		currentEditorView()->complete(LatexCompleter::CF_FORCE_VISIBLE_LIST | LatexCompleter::CF_FORCE_CITE);
4524 		break;
4525 	case Token::width:
4526 		if (mCompleterNeedsUpdate) updateCompleter();
4527 		currentEditorView()->complete(LatexCompleter::CF_FORCE_VISIBLE_LIST | LatexCompleter::CF_FORCE_LENGTH);
4528 		break;
4529 	case Token::imagefile: {
4530 		QString fn = documents.getCompileFileName();
4531 		QFileInfo fi(fn);
4532 		completer->setWorkPath(fi.absolutePath());
4533 		currentEditorView()->complete(LatexCompleter::CF_FORCE_VISIBLE_LIST | LatexCompleter::CF_FORCE_GRAPHIC);
4534 	}
4535 	break;
4536 	case Token::file: {
4537 		QString fn = documents.getCompileFileName();
4538 		QFileInfo fi(fn);
4539 		completer->setWorkPath(fi.absolutePath());
4540 		currentEditorView()->complete(LatexCompleter::CF_FORCE_VISIBLE_LIST | LatexCompleter::CF_FORCE_GRAPHIC);
4541 	}
4542 	break;
4543 	case Token::color:
4544 		if (mCompleterNeedsUpdate) updateCompleter();
4545 		completer->setWorkPath("%color");
4546 		currentEditorView()->complete(LatexCompleter::CF_FORCE_VISIBLE_LIST | LatexCompleter::CF_FORCE_SPECIALOPTION); //TODO: complete support for special opt
4547 		break;
4548 	case Token::keyValArg:
4549 	case Token::keyVal_key:
4550 	case Token::keyVal_val: {
4551         if (mCompleterNeedsUpdate) updateCompleter();
4552 		QString word = c.line().text();
4553 		int col = c.columnNumber();
4554         command = Parsing::getCommandFromToken(tk);
4555         if(command=="\\begin"){ // special treatment for begin as it is only meaningful with the env-name
4556             TokenList tl = dlh->getCookieLocked(QDocumentLine::LEXER_COOKIE).value<TokenList>();
4557             Token tkCmd=Parsing::getCommandTokenFromToken(tl,tk);
4558             int k = tl.indexOf(tkCmd) + 1;
4559             Token tk2=tl.value(k);
4560             QString subcommand=tk2.getText();
4561             command+=subcommand;
4562         }
4563 
4564 		completer->setWorkPath(command);
4565 		if (!completer->existValues()) {
4566 			// no keys found for command
4567 			// command/arg structure ? (yathesis)
4568 			TokenList tl = dlh->getCookieLocked(QDocumentLine::LEXER_COOKIE).value<TokenList>();
4569 			QString subcommand;
4570             int add = (type == Token::keyVal_val) ? 1 : 0;
4571 			if (tk.type == Token::braces || tk.type == Token::squareBracket)
4572 				add = 0;
4573 			for (int k = tl.indexOf(tk) + 1; k < tl.length(); k++) {
4574 				Token tk_elem = tl.at(k);
4575 				if (tk_elem.level > tk.level - add)
4576 					continue;
4577 				if (tk_elem.level < tk.level - add)
4578 					break;
4579 				if (tk_elem.type == Token::braces) {
4580 					subcommand = word.mid(tk_elem.start + 1, tk_elem.length - 2);
4581 					break;
4582 				}
4583 			}
4584 			if (!subcommand.isEmpty())
4585 				command = command + "/" + subcommand;
4586 			completer->setWorkPath(command);
4587 		}
4588 
4589 		bool existValues = completer->existValues();
4590 		// check if c is after keyval
4591 		if (col > tk.start + tk.length) {
4592 			QString interposer = word.mid(tk.start + tk.length, col - tk.start - tk.length);
4593 			if (!interposer.contains(",") && interposer.contains("=")) {
4594 				//assume val for being after key
4595 				command = command + "/" + tk.getText();
4596 				completer->setWorkPath(command);
4597 				existValues = completer->existValues();
4598 			}
4599 		} else {
4600 			if (ts.size() > 1) {
4601 				Token elem = ts.at(ts.size() - 2);
4602 				if (elem.type == Token::keyVal_key && elem.level == tk.level - 1) {
4603 					command = command + "/" + elem.getText();
4604 					completer->setWorkPath(command);
4605 					existValues = completer->existValues();
4606 				}
4607 			}
4608 		}
4609 		completer->setWorkPath(command);
4610 		if (existValues)
4611 			currentEditorView()->complete(LatexCompleter::CF_FORCE_VISIBLE_LIST | LatexCompleter::CF_FORCE_KEYVAL);
4612 	}
4613 	break;
4614 	case Token::beamertheme: {
4615 		QString preambel = "beamertheme";
4616 		currentPackageList.clear();
4617         for(const QString &elem: latexPackageList) {
4618 			if (elem.startsWith(preambel))
4619                 currentPackageList.insert(elem.mid(preambel.length()));
4620 		}
4621 	}
4622 	completer->setPackageList(&currentPackageList);
4623 	currentEditorView()->complete(LatexCompleter::CF_FORCE_VISIBLE_LIST | LatexCompleter::CF_FORCE_PACKAGE);
4624 	break;
4625 	case Token::package:
4626 		completer->setPackageList(&latexPackageList);
4627 		currentEditorView()->complete(LatexCompleter::CF_FORCE_VISIBLE_LIST | LatexCompleter::CF_FORCE_PACKAGE);
4628 		break;
4629 
4630 	default:
4631 		insertTextCompletion();
4632 	}
4633 }
4634 
insertEnvironmentCompletion()4635 void Texstudio::insertEnvironmentCompletion()
4636 {
4637 	if (!currentEditorView())	return;
4638 	if (mCompleterNeedsUpdate) updateCompleter();
4639 	QDocumentCursor c = currentEditorView()->editor->cursor();
4640 	if (c.hasSelection()) {
4641 		currentEditor()->cutBuffer = c.selectedText();
4642 		c.removeSelectedText();
4643 	}
4644 	QString eow = getCommonEOW();
4645 	while (c.columnNumber() > 0 && !eow.contains(c.previousChar())) c.movePosition(1, QDocumentCursor::PreviousCharacter);
4646 
4647 	static const QString environmentStart = "\\begin{";
4648 
4649 	currentEditor()->document()->clearLanguageMatches();
4650 	if (!c.line().text().left(c.columnNumber()).endsWith(environmentStart)) {
4651 		c.insertText(environmentStart);//remaining part is up to the completion engine
4652 	}
4653 
4654 	currentEditorView()->complete(LatexCompleter::CF_FORCE_VISIBLE_LIST);
4655 }
4656 
4657 // tries to complete normal text
4658 // only starts up if already 2 characters have been typed in
insertTextCompletion()4659 void Texstudio::insertTextCompletion()
4660 {
4661 	if (!currentEditorView())    return;
4662 	QDocumentCursor c = currentEditorView()->editor->cursor();
4663 	QString eow = getCommonEOW();
4664 
4665     if (c.columnNumber() == 0 || eow.contains(c.previousChar()) )
4666 		return;
4667 
4668 	int col = c.columnNumber();
4669 	QString line = c.line().text();
4670     if(col>line.length()){
4671         col=line.length(); // avoid crash, should not happen but did
4672     }
4673 	for (; col > 0 && !eow.contains(line[col - 1]); col-- )
4674 		;
4675 
4676     QString word = line.mid(col, c.columnNumber() - col);
4677     QSet<QString> words;
4678 
4679     QDocument *doc=currentEditor()->document();
4680     // generate regexp for getting fuzzy results
4681     // here the first letter must match, the rest can be fuzzy
4682 #if (QT_VERSION>=QT_VERSION_CHECK(5,14,0))
4683     QStringList chars=word.split("",Qt::SkipEmptyParts);
4684 #else
4685     QStringList chars=word.split("",QString::SkipEmptyParts);
4686 #endif
4687     QString regExpression=chars.join(".*");
4688     QRegExp rx("^"+regExpression);
4689 
4690     for(int i=0;i<doc->lineCount();i++){
4691         QDocumentLineHandle *dlh=doc->line(i).handle();
4692         TokenList tl = dlh->getCookieLocked(QDocumentLine::LEXER_COOKIE).value<TokenList>();
4693         QString txt;
4694         for(int k=0;k<tl.size();k++) {
4695             Token tk=tl.at(k);
4696             if(!txt.isEmpty() || (tk.type==Token::word && (tk.subtype==Token::none || tk.subtype==Token::text || tk.subtype==Token::generalArg || tk.subtype==Token::title || tk.subtype==Token::shorttitle || tk.subtype==Token::todo))){
4697                 txt+=tk.getText();
4698                 if(txt.startsWith(word)){
4699                     if(word.length()<txt.length()){
4700                         words<<txt;
4701                     }
4702                     // advance k if tk comprehends several sub-tokens (braces)
4703                     while(k+1<tl.size() && tl.at(k+1).start<(tk.start+tk.length)){
4704                         k++;
4705                     }
4706                     // add more variants for variable-name like constructions
4707                     if(k+2<tl.size()){
4708                         Token tk2=tl.at(k+1);
4709                         Token tk3=tl.at(k+2);
4710                         if(tk2.length==1 && tk2.start==tk.start+tk.length && tk2.type==Token::punctuation&&tk3.start==tk2.start+tk2.length){
4711                             // next token is directly adjacent and of length 1
4712                             QString txt2=tk2.getText();
4713                             if(txt2=="_" || txt2=="-"){
4714                                 txt.append(txt2);
4715                                 k++;
4716                                 continue;
4717                             }
4718                             if(txt2=="'" && tk3.type==Token::word){ // e.g. don't but not abc''
4719                                 txt.append(txt2);
4720                                 k++;
4721                                 continue;
4722                             }
4723                         }
4724                         // combine abc\_def
4725                         if(tk2.length==2 && tk2.start==tk.start+tk.length && (tk2.type==Token::command||tk2.type==Token::commandUnknown)&&tk3.start==tk2.start+tk2.length){
4726                             // next token is directly adjacent and of length 1
4727                             QString txt2=tk2.getText();
4728                             if(txt2=="\\_" ){
4729                                 txt.append(txt2);
4730                                 k++;
4731                                 continue;
4732                             }
4733                         }
4734                         // previous was an already appended command, check if argument is present
4735                         if(tk.type==Token::command){
4736                             if(tk2.level==tk.level && tk2.subtype!=Token::none){
4737                                 txt.append(tk2.getText());
4738                                 words<<txt;
4739                                 k++;
4740                             }
4741                         }
4742                     }
4743                 }else{
4744                     if(rx.indexIn(txt)!=-1){
4745                         words<<txt;
4746                     }
4747                 }
4748             }
4749             txt.clear();
4750         }
4751 
4752     }
4753     /*QString my_text = currentEditorView()->editor->text();
4754 	int end = 0;
4755 	int k = 0; // number of occurences of search word.
4756 	QString word = line.mid(col, c.columnNumber() - col);
4757 	//TODO: Boundary needs to specified more exactly
4758 	//TODO: type in text needs to be excluded, if not already present
4759 	//TODO: editor->text() is far too slow
4760 	QSet<QString> words;
4761 	int i;
4762 	while ((i = my_text.indexOf(QRegExp("\\b" + word), end)) > 0) {
4763 		end = my_text.indexOf(QRegExp("\\b"), i + 1);
4764 		if (end > i) {
4765             bool addVar=false;
4766             do {
4767                 addVar=false;
4768                 if (word == my_text.mid(i, end - i)) {
4769                     k = k + 1;
4770                     if (k == 2) words << my_text.mid(i, end - i);
4771                 } else {
4772                     QString txt=my_text.mid(i, end - i);
4773                     if(txt.endsWith("_")){
4774                         if(my_text.mid(end,1)=="\\"||my_text.mid(end,1)=="{"){
4775                             // special handling for abc_\cmd{dsfsdf}
4776                             QRegExp rx("(\\\\[a-zA-Z]+)?(\\{\\w+\\})?"); // better solution would be employing tokens ...
4777                             int zw=rx.indexIn(my_text,end);
4778                             if(zw==end){
4779                                 txt.append(rx.cap());
4780                                 words << txt;
4781                             }
4782                         }
4783                     }else{
4784                         if (!words.contains(txt))
4785                             words << txt;
4786                     }
4787                 }
4788                 // add more variants if word boundary is \_ \- or -
4789                 if(my_text.length()>end+1){
4790                     addVar|=(my_text.mid(end,2)=="\\_");
4791                     addVar|=(my_text.mid(end,2)=="\\-");
4792                     if(addVar)
4793                         end++;
4794                 }
4795                 if(my_text.mid(end,1)=="-")
4796                     addVar=true;
4797                 if(addVar){
4798                     end = my_text.indexOf(QRegExp("\\b"), end+2);
4799                     addVar=(end>i);
4800                 }
4801             } while(addVar);
4802 		} else {
4803 			if (word == my_text.mid(i, end - i)) {
4804 				k = k + 1;
4805 				if (k == 2) words << my_text.mid(i, end - i);
4806 			} else {
4807 				if (!words.contains(my_text.mid(i, end - i)))
4808 					words << my_text.mid(i, my_text.length() - i);
4809 			}
4810 		}
4811 	}
4812     */
4813 	completer->setAdditionalWords(words, CT_NORMALTEXT);
4814 	currentEditorView()->complete(LatexCompleter::CF_FORCE_VISIBLE_LIST | LatexCompleter::CF_NORMAL_TEXT);
4815 }
4816 
insertText(const QString & text)4817 void Texstudio::insertText(const QString &text)
4818 {
4819 	currentEditor()->write(text);
4820 	currentEditorView()->setFocus();
4821 }
4822 
4823 /*! TODO: this API and its defaults are a bit weird. Needs refactoring. */
insertTag(const QString & Entity,int dx,int dy)4824 void Texstudio::insertTag(const QString &Entity, int dx, int dy)
4825 {
4826 	if (!currentEditorView()) return;
4827 	int curline, curindex;
4828 	currentEditor()->getCursorPosition(curline, curindex);
4829 	currentEditor()->write(Entity);
4830 	if (dy == 0) currentEditor()->setCursorPosition(curline, curindex + dx);
4831 	else if (dx == 0) currentEditor()->setCursorPosition(curline + dy, 0);
4832 	else currentEditor()->setCursorPosition(curline + dy, curindex + dx);
4833 	currentEditor()->setFocus();
4834 	//	outputView->setMessage("");
4835 	//logViewerTabBar->setCurrentIndex(0);
4836 	//OutputTable->hide();
4837 	//logpresent=false;
4838 }
4839 
4840 /*!
4841   \brief Inserts a citation at the cursor position.
4842 
4843   \a text may be either a complete (also custom) command like \mycite{key} or just the key. In the
4844   latter case it is expanded to \cite{key}. Key may also be a comma separated list of keys.
4845   The cursor context is evaluated: If it is within a citation command only the key is inserted at the correct
4846   position within the existing citation. If not, the whole command is inserted.
4847 */
insertCitation(const QString & text)4848 void Texstudio::insertCitation(const QString &text)
4849 {
4850 	QString citeCmd, citeKey;
4851 
4852 	if (text.length() > 1 && text.at(0) == '\\') {
4853 		LatexParser::ContextType ctx = latexParser.findContext(text, 1, citeCmd, citeKey);
4854 		if (LatexParser::Command != ctx) {
4855 			citeCmd = "";
4856 			citeKey = text;
4857 		}
4858 	} else {
4859 		citeCmd = "";
4860 		citeKey = text;
4861 	}
4862 
4863 	if (!currentEditorView()) return;
4864 	QDocumentCursor c = currentEditor()->cursor();
4865 	QString line = c.line().text();
4866 	int cursorCol = c.columnNumber();
4867 	QString command, value;
4868 	LatexParser::ContextType context = latexParser.findContext(line, cursorCol, command, value);
4869 
4870 	// Workaround: findContext yields Citation for \cite{..}|\n, but cursorCol is beyond the line,
4871 	// which causes a crash when determining the insertCol later on.
4872 	if (context == LatexParser::Citation && cursorCol == line.length() && cursorCol > 0) cursorCol--;
4873 
4874     // if cursor is directly behind a cite command, insert into that command
4875 	if (context != LatexParser::Citation && cursorCol > 0) {
4876 		LatexParser::ContextType prevContext = LatexParser::Unknown;
4877 		prevContext = latexParser.findContext(line, cursorCol - 1, command, value);
4878 		if (prevContext == LatexParser::Citation) {
4879 			cursorCol--;
4880 			context = prevContext;
4881 		}
4882 	}
4883 
4884 
4885 	int insertCol = -1;
4886 	if (context == LatexParser::Command && latexParser.possibleCommands["%cite"].contains(command)) {
4887 		insertCol = line.indexOf('{', cursorCol) + 1;
4888 	} else if (context == LatexParser::Citation) {
4889 		if (cursorCol <= 0) return; // should not be possible,
4890 		if (line.at(cursorCol) == '{' || line.at(cursorCol) == ',') {
4891 			insertCol = cursorCol + 1;
4892 		} else if (line.at(cursorCol - 1) == '{' || line.at(cursorCol - 1) == ',') {
4893 			insertCol = cursorCol;
4894 		} else {
4895 			int nextComma = line.indexOf(',', cursorCol);
4896 			int closingBracket = line.indexOf('}', cursorCol);
4897 			if (nextComma >= 0 && (closingBracket == -1 || closingBracket > nextComma)) {
4898 				insertCol = nextComma + 1;
4899 			} else if (closingBracket >= 0) {
4900 				insertCol = closingBracket;
4901 			}
4902 		}
4903 	} else {
4904 		QString tag;
4905 		if (citeCmd.isEmpty())
4906             tag = citeCmd = configManager.citeCommand+"{" + citeKey + "}";
4907 		else
4908 			tag = text;
4909 		insertTag(tag, tag.length());
4910 		return;
4911 	}
4912 
4913 	if (insertCol < 0 || insertCol >= line.length()) return;
4914 
4915 	currentEditor()->setCursorPosition(c.lineNumber(), insertCol);
4916 	// now the insertCol is either behind '{', behind ',' or at '}'
4917 	if (insertCol > 0 && line.at(insertCol - 1) == '{') {
4918 		if (line.at(insertCol) == '}') {
4919 			insertTag(citeKey, citeKey.length());
4920 		} else {
4921 			insertTag(citeKey + ",", citeKey.length() + 1);
4922 		}
4923 	} else if (insertCol > 0 && line.at(insertCol - 1) == ',') {
4924 		insertTag(citeKey + ",", citeKey.length() + 1);
4925 	} else {
4926 		insertTag("," + citeKey, citeKey.length() + 1);
4927 	}
4928 }
4929 
insertFormula(const QString & formula)4930 void Texstudio::insertFormula(const QString &formula)
4931 {
4932 	if (!currentEditorView()) return;
4933 
4934 	QString fm = formula;
4935 	QDocumentCursor cur = currentEditorView()->editor->cursor();
4936 
4937 	//TODO: Is there a more elegant solution to determine if the cursor is inside a math environment.
4938 	QDocumentLine dl = cur.line();
4939 	int col = cur.columnNumber();
4940 	QList<int> mathFormats = QList<int>() << m_formats->id("numbers") << m_formats->id("math-keyword");
4941 	int mathDelimiter = m_formats->id("math-delimiter");
4942 	mathFormats.removeAll(0); // keep only valid entries in list
4943 	if (mathFormats.contains(dl.getFormatAt(col))) {     // inside math
4944 		fm = fm.mid(1, fm.length() - 2);                 // removes surrounding $...$
4945 	} else if (dl.getFormatAt(col) == mathDelimiter) {   // on delimiter
4946 		while (col > 0 && dl.getFormatAt(col - 1) == mathDelimiter) col--;
4947 		if (mathFormats.contains(dl.getFormatAt(col))) { // was an end-delimiter
4948 			cur.setColumnNumber(col);
4949 			currentEditorView()->editor->setCursor(cur);
4950 			fm = fm.mid(1, fm.length() - 2);             // removes surrounding $...$
4951 		} else {
4952             //TODO is there a better way than hard coding? Since there is no difference in the formats between
4953 			//start and end tags. \[|\] is hard identify without.
4954 			QString editorFormula = dl.text().mid(col);
4955 			if (editorFormula.startsWith("\\[")) {
4956                 //col += 2; unused code
4957 				currentEditorView()->editor->setCursor(cur);
4958 				fm = fm.mid(1, fm.length() - 2);         // removes surrounding $...$
4959 			} else if (editorFormula.startsWith("$")) {
4960                 // col += 1; unused code
4961 				currentEditorView()->editor->setCursor(cur);
4962 				fm = fm.mid(1, fm.length() - 2);         // removes surrounding $...$
4963 			} else {
4964 				qDebug() << "Unknown math formula tag. Giving up trying to locate formula boundaries.";
4965 			}
4966 		}
4967 	}
4968 
4969 	insertTag(fm, fm.length());
4970 }
4971 
insertSymbol(const QString & text)4972 void Texstudio::insertSymbol(const QString &text)
4973 {
4974 	insertTag(text, text.length());
4975 }
4976 
insertXmlTag(QListWidgetItem * item)4977 void Texstudio::insertXmlTag(QListWidgetItem *item)
4978 {
4979 	if (!currentEditorView())	return;
4980 	if (item  && !item->font().bold()) {
4981 		QString code = item->data(Qt::UserRole).toString();
4982 		QDocumentCursor c = currentEditorView()->editor->cursor();
4983 		CodeSnippet(code).insertAt(currentEditorView()->editor, &c);
4984 		currentEditorView()->editor->setFocus();
4985 	}
4986 }
4987 
insertXmlTagFromToolButtonAction()4988 void Texstudio::insertXmlTagFromToolButtonAction()
4989 {
4990 	if (!currentEditorView()) return;
4991 	QAction *action = qobject_cast<QAction *>(sender());
4992 	if (!action) return;
4993 	QToolButton *button = UtilsUi::comboToolButtonFromAction(action);
4994 	if (!button) return;
4995 	button->setDefaultAction(action);
4996 
4997 	QString tagsID = button->property("tagsID").toString();
4998 	int tagCategorySep = tagsID.indexOf("/", 5);
4999 	XmlTagsListWidget *tagsWidget = findChild<XmlTagsListWidget *>(tagsID.left(tagCategorySep));
5000 	if (!tagsWidget) return;
5001 	QString code = tagsWidget->tagsFromTagTxt(action->text());
5002 	CodeSnippet(code).insert(currentEditorView()->editor);
5003 	currentEditorView()->editor->setFocus();
5004 }
5005 
callToolButtonAction()5006 void Texstudio::callToolButtonAction()
5007 {
5008 	QAction *action = qobject_cast<QAction *>(sender());
5009 	QToolButton *button = UtilsUi::comboToolButtonFromAction(action);
5010 	REQUIRE(button && button->defaultAction() && button->menu());
5011 	button->setDefaultAction(action);
5012 
5013 	QString menuID = button->property("menuID").toString();
5014 	QMenu *menu = configManager.getManagedMenu(menuID);
5015 	if (!menu) return;
5016 
5017 	int index = button->menu()->actions().indexOf(action);
5018 	REQUIRE(index >= 0);
5019 	REQUIRE(index < menu->actions().size());
5020 	QList<QAction *> actions = menu->actions();
5021 	for (int i = 0; i < actions.size(); i++) {
5022 		if (actions[i]->isSeparator()) continue;
5023 		if (index == 0) {
5024 			actions[i]->trigger();
5025 			break;
5026 		} else index--;
5027 	}
5028 }
5029 
insertFromAction()5030 void Texstudio::insertFromAction()
5031 {
5032 	LatexEditorView *edView = currentEditorView();
5033 	if (!edView)	return;
5034 	QAction *action = qobject_cast<QAction *>(sender());
5035 	if (action)	{
5036 		if (completer->isVisible())
5037 			completer->close();
5038 		execMacro(Macro::fromTypedTag(action->data().toString()), MacroExecContext(), true);
5039 		generateMirror();
5040 		outputView->setMessage(CodeSnippet(action->whatsThis(), false).lines.join("\n"));
5041     }
5042 }
5043 
insertTextFromAction()5044 void Texstudio::insertTextFromAction()
5045 {
5046     QAction *action = qobject_cast<QAction *>(sender());
5047     if (!action) return;
5048     insertText(action->data().toString());
5049 }
5050 
insertFromTagList(QListWidgetItem * item)5051 void Texstudio::insertFromTagList(QListWidgetItem *item)
5052 {
5053     LatexEditorView *edView = currentEditorView();
5054     if (!edView)	return;
5055     if (item)	{
5056         if (completer->isVisible())
5057             completer->close();
5058         execMacro(Macro::fromTypedTag(item->data(Qt::UserRole).toString()), MacroExecContext(), true);
5059         generateMirror();
5060     }
5061 }
5062 
insertBib()5063 void Texstudio::insertBib()
5064 {
5065 	if (!currentEditorView())	return;
5066 	//currentEditorView()->editor->viewport()->setFocus();
5067 	QString tag;
5068 	tag = QString("\\bibliography{");
5069 	tag += currentEditor()->fileInfo().completeBaseName();
5070 	tag += QString("}\n");
5071 	insertTag(tag, 0, 1);
5072 	outputView->setMessage(QString("The argument to \\bibliography refers to the bib file (without extension)\n") +
5073 	                       "which should contain your database in BibTeX format.\n" +
5074 	                       "TeXstudio inserts automatically the base name of the TeX file");
5075 }
5076 
quickTabular()5077 void Texstudio::quickTabular()
5078 {
5079 	if ( !currentEditorView() )	return;
5080 	TabDialog *tabDialog = new TabDialog(this, "Tabular");
5081 	if ( tabDialog->exec() ) {
5082 		QString latexText = tabDialog->getLatexText();
5083 		QSet<QString> usedPackages = currentEditorView()->document->usedPackages();
5084 		foreach (const QString &package, TabDialog::getRequiredPackages(latexText)) {
5085 			if (!usedPackages.contains(package)) {
5086 				latexText.prepend("% TODO: \\usepackage{" + package + "} required\n");
5087 			}
5088 		}
5089 		insertTag(latexText, 0, 0);
5090 	}
5091 }
5092 
quickArray()5093 void Texstudio::quickArray()
5094 {
5095 	if (!currentEditorView())	return;
5096 	ArrayDialog *arrayDlg = new ArrayDialog(this, "Array");
5097 	if (arrayDlg->exec()) {
5098 		insertTag(arrayDlg->getLatexText(), 0, 0);
5099 	}
5100 }
5101 
5102 
5103 // returns true if line is inside in the specified environment. In that case start and end lines of the environment are supplied
findEnvironmentLines(const QDocument * doc,const QString & env,int line,int & startLine,int & endLine,int scanRange)5104 bool findEnvironmentLines(const QDocument *doc, const QString &env, int line, int &startLine, int &endLine, int scanRange)
5105 {
5106 	QString name, arg;
5107 
5108 	startLine = -1;
5109 	for (int l = line; l >= 0; l--) {
5110 		if (scanRange > 0 && line - l > scanRange) break;
5111 		if (findTokenWithArg(doc->line(l).text(), "\\end{", name, arg) && name == env) {
5112 			if (l < line) return false;
5113 		}
5114 		if (findTokenWithArg(doc->line(l).text(), "\\begin{", name, arg) && name == env) {
5115 			startLine = l;
5116 			break;
5117 		}
5118 	}
5119 	if (startLine == -1) return false;
5120 
5121 	endLine = -1;
5122 	for (int l = line; l < doc->lineCount(); l++) {
5123 		if (scanRange > 0 && l - line > scanRange) break;
5124 		if (findTokenWithArg(doc->line(l).text(), "\\end{", name, arg) && name == env) {
5125 			endLine = l;
5126 			break;
5127 		}
5128 		if (findTokenWithArg(doc->line(l).text(), "\\begin{", name, arg) && name == env) {
5129 			if (l > line) return false; //second begin without end
5130 		}
5131 	}
5132 	if (endLine == -1) return false;
5133 	return true;
5134 }
5135 
quickGraphics(const QString & graphicsFile)5136 void Texstudio::quickGraphics(const QString &graphicsFile)
5137 {
5138 	if (!currentEditorView()) return;
5139 
5140 	InsertGraphics *graphicsDlg = new InsertGraphics(this, configManager.insertGraphicsConfig);
5141 
5142 	QEditor *editor = currentEditor();
5143 
5144 	int startLine, endLine, cursorLine, cursorCol;
5145 	editor->getCursorPosition(cursorLine, cursorCol);
5146 	QDocument *doc = editor->document();
5147 
5148 	QDocumentCursor cur = currentEditor()->cursor();
5149 	QDocumentCursor origCur = cur;
5150 	origCur.setAutoUpdated(true);
5151 
5152 	bool hasCode = false;
5153 	if (findEnvironmentLines(doc, "figure", cursorLine, startLine, endLine, 20)) {
5154 		cur.moveTo(startLine, 0, QDocumentCursor::MoveAnchor);
5155 		cur.moveTo(endLine + 1, 0, QDocumentCursor::KeepAnchor);
5156 		hasCode = true;
5157 	} else if (findEnvironmentLines(doc, "figure*", cursorLine, startLine, endLine, 20)) {
5158 		cur.moveTo(startLine, 0, QDocumentCursor::MoveAnchor);
5159 		cur.moveTo(endLine + 1, 0, QDocumentCursor::KeepAnchor);
5160 		hasCode = true;
5161 	} else if (findEnvironmentLines(doc, "center", cursorLine, startLine, endLine, 3)) {
5162 		cur.moveTo(startLine, 0, QDocumentCursor::MoveAnchor);
5163 		cur.moveTo(endLine + 1, 0, QDocumentCursor::KeepAnchor);
5164 		hasCode = true;
5165 	} else if (currentEditor()->text(cursorLine).contains("\\includegraphics")) {
5166 		cur.moveTo(cursorLine, 0, QDocumentCursor::MoveAnchor);
5167 		cur.moveTo(cursorLine + 1, 0, QDocumentCursor::KeepAnchor);
5168 		hasCode = true;
5169 	}
5170 
5171 	if (hasCode) {
5172 		editor->setCursor(cur);
5173 		graphicsDlg->setCode(cur.selectedText());
5174 	}
5175 
5176 	QFileInfo docInfo = currentEditorView()->document->getFileInfo();
5177 	graphicsDlg->setTexFile(docInfo);
5178     graphicsDlg->setMasterTexFile(QFileInfo(currentEditorView()->document->parent->getCompileFileName()));
5179 	if (!graphicsFile.isNull()) graphicsDlg->setGraphicsFile(graphicsFile);
5180 
5181 	if (graphicsDlg->exec()) {
5182 		QString latexText = graphicsDlg->getLatexText();
5183 		if (!currentEditorView()->document->usedPackages().contains("graphicx")) {
5184 			// simplified static version. See quickTabular() for a more generic version.
5185 			latexText.prepend("% TODO: \\usepackage{graphicx} required\n");
5186 		}
5187 		editor->insertText(cur, latexText);
5188 	} else {
5189 		editor->setCursor(origCur);
5190 	}
5191 
5192 	delete graphicsDlg;
5193 }
5194 
quickMath()5195 void Texstudio::quickMath()
5196 {
5197 #ifdef Q_OS_WIN
5198 	connectUnique(MathAssistant::instance(), SIGNAL(formulaReceived(QString)), this, SLOT(insertFormula(QString)));
5199 	MathAssistant::instance()->exec();
5200 #endif
5201 }
5202 
quickTabbing()5203 void Texstudio::quickTabbing()
5204 {
5205 	if (!currentEditorView()) return;
5206 	TabbingDialog *tabDlg = new TabbingDialog(this, "Tabbing");
5207 	if (tabDlg->exec()) {
5208 		int	x = tabDlg->ui.spinBoxColumns->value();
5209 		int	y = tabDlg->ui.spinBoxRows->value();
5210 		QString s = tabDlg->ui.lineEdit->text();
5211 		QString tag = QString("\\begin{tabbing}\n");
5212 		for (int j = 1; j < x; j++) {
5213 			tag += "\\hspace{" + s + "}\\=";
5214 		}
5215 		tag += "\\kill\n";
5216 		for (int i = 0; i < y - 1; i++) {
5217 			for (int j = 1; j < x; j++) {
5218 				tag += " \\> ";
5219 			}
5220 			tag += "\\\\ \n";
5221 		}
5222 		for (int j = 1; j < x; j++) {
5223 			tag += " \\> ";
5224 		}
5225 		tag += QString("\n\\end{tabbing} ");
5226 		insertTag(tag, 0, 2);
5227 	}
5228 }
5229 
quickLetter()5230 void Texstudio::quickLetter()
5231 {
5232 	QString tag = QString("\\documentclass[");
5233 	LetterDialog *ltDlg = new LetterDialog(this, "Letter");
5234 	if (ltDlg->exec()) {
5235 		if (!currentEditorView() ||
5236 		        currentEditorView()->getDocument()->lineCount() > 1 || // first faster than text().isEmpty on large documents
5237 		        !currentEditorView()->getDocument()->text().isEmpty()) {
5238 			fileNew();
5239 			Q_ASSERT(currentEditorView());
5240 		}
5241 		tag += ltDlg->ui.comboBoxPt->currentText() + QString(",");
5242 		tag += ltDlg->ui.comboBoxPaper->currentText() + QString("]{letter}");
5243 		tag += QString("\n");
5244 		if (ltDlg->ui.comboBoxEncoding->currentText() != "NONE") tag += QString("\\usepackage[") + ltDlg->ui.comboBoxEncoding->currentText() + QString("]{inputenc}");
5245 		if (ltDlg->ui.comboBoxEncoding->currentText().startsWith("utf8x")) tag += QString(" \\usepackage{ucs}");
5246 		tag += QString("\n");
5247 		if (ltDlg->ui.checkBox->isChecked()) tag += QString("\\usepackage{amsmath}\n\\usepackage{amssymb}\n");
5248 		tag += "\\address{your name and address} \n";
5249 		tag += "\\signature{your signature} \n";
5250 		tag += "\\begin{document} \n";
5251 		tag += "\\begin{letter}{name and address of the recipient} \n";
5252 		tag += "\\opening{saying hello} \n \n";
5253 		tag += "write your letter here \n \n";
5254 		tag += "\\closing{saying goodbye} \n";
5255 		tag += "%\\cc{Cclist} \n";
5256 		tag += "%\\ps{adding a postscript} \n";
5257 		tag += "%\\encl{list of enclosed material} \n";
5258 		tag += "\\end{letter} \n";
5259 		tag += "\\end{document}";
5260 		if (ltDlg->ui.checkBox->isChecked()) {
5261 			insertTag(tag, 9, 5);
5262 		} else {
5263 			insertTag(tag, 9, 2);
5264 		}
5265 	}
5266 }
5267 
quickDocument()5268 void Texstudio::quickDocument()
5269 {
5270 	QuickDocumentDialog *startDlg = new QuickDocumentDialog(this, tr("Quick Start"));
5271 	startDlg->Init();
5272 	if (startDlg->exec()) {
5273 		if (!currentEditorView() ||
5274 		        currentEditorView()->getDocument()->lineCount() > 1 || // first faster than text().isEmpty on large documents
5275 		        !currentEditorView()->getDocument()->text().isEmpty()) {
5276 			fileNew();
5277 			Q_ASSERT(currentEditorView());
5278 		}
5279 		Q_ASSERT(currentEditor());
5280 		currentEditorView()->insertSnippet(startDlg->getNewDocumentText());
5281 		QTextCodec *codec = Encoding::QTextCodecForLatexName(startDlg->document_encoding);
5282 		if (codec && codec != currentEditor()->document()->codec()) {
5283 			currentEditor()->document()->setCodec(codec);
5284 			updateCaption();
5285 		}
5286 	}
5287 	delete startDlg;
5288 }
5289 
quickBeamer()5290 void Texstudio::quickBeamer()
5291 {
5292 	QuickBeamerDialog *startDlg = new QuickBeamerDialog(this, tr("Quick Beamer Presentation"));
5293 	startDlg->Init();
5294 	if (startDlg->exec()) {
5295 		if (!currentEditorView() ||
5296 		        currentEditorView()->getDocument()->lineCount() > 1 || // first faster than text().isEmpty on large documents
5297 		        !currentEditorView()->getDocument()->text().isEmpty()) {
5298 			fileNew();
5299 			Q_ASSERT(currentEditorView());
5300 		}
5301 		Q_ASSERT(currentEditor());
5302 		currentEditorView()->insertSnippet(startDlg->getNewDocumentText());
5303 		QTextCodec *codec = Encoding::QTextCodecForLatexName(startDlg->document_encoding);
5304 		if (codec && codec != currentEditor()->document()->codec()) {
5305 			currentEditor()->document()->setCodec(codec);
5306 			updateCaption();
5307 		}
5308 	}
5309 	delete startDlg;
5310 }
5311 
insertBibEntryFromAction()5312 void Texstudio::insertBibEntryFromAction()
5313 {
5314 	if (!currentEditorView()) return;
5315 	QAction *action = qobject_cast<QAction *>(sender());
5316 	if (!action) return;
5317 
5318 	QString insertText = BibTeXDialog::textToInsert(action->data().toString());
5319 	if (!insertText.isEmpty())
5320 		CodeSnippet(insertText, false).insert(currentEditor());
5321 }
5322 
insertBibEntry(const QString & id)5323 void Texstudio::insertBibEntry(const QString &id)
5324 {
5325 	QStringList possibleBibFiles;
5326 	int usedFile = 0;
5327 	if (currentEditor()) {
5328 		if (currentEditor()->fileName().isEmpty())
5329 			possibleBibFiles.prepend(tr("<Current File>"));
5330 		else {
5331 			usedFile = documents.mentionedBibTeXFiles.indexOf(currentEditor()->fileName());
5332 			if (usedFile < 0 && !documents.mentionedBibTeXFiles.empty()) usedFile = 0;
5333 		}
5334 	}
5335 	foreach (const QString &s, documents.mentionedBibTeXFiles)
5336 		possibleBibFiles << QFileInfo(s).fileName();
5337 	BibTeXDialog *bd = new BibTeXDialog(nullptr, possibleBibFiles, usedFile, id);
5338 	if (bd->exec()) {
5339 		usedFile = bd->resultFileId;
5340 		if (usedFile < 0 || usedFile >= possibleBibFiles.count()) fileNew();
5341 		else if (currentEditor()->fileName().isEmpty() && usedFile == 0); //stay in current editor
5342 		else if (QFileInfo(currentEditor()->fileName()) == QFileInfo(possibleBibFiles[usedFile])); //stay in current editor
5343 		else {
5344 			if (currentEditor()->fileName().isEmpty()) usedFile--;
5345 			load(documents.mentionedBibTeXFiles[usedFile]);
5346 			currentEditor()->setCursorPosition(currentEditor()->document()->lines() - 1, 0);
5347 			bd->resultString = "\n" + bd->resultString;
5348 		}
5349 
5350 		CodeSnippet(bd->resultString, false).insert(currentEditor());
5351 	}
5352 	delete bd;
5353 }
5354 
setBibTypeFromAction()5355 void Texstudio::setBibTypeFromAction()
5356 {
5357 	QMenu *menu = getManagedMenu("main/bibliography/type");
5358 	QAction *act = qobject_cast<QAction *>(sender());
5359 	if (!act) return;
5360 	if (menu) {
5361 		menu->setTitle(QString(tr("Type: %1")).arg(act->text()));
5362 	}
5363 
5364 	bool isBibtex = (act->data().toString() == "bibtex");
5365 	bibtexEntryActions->setVisible(isBibtex);
5366 	biblatexEntryActions->setVisible(!isBibtex);
5367 	BibTeXDialog::setBibType(isBibtex ? BibTeXDialog::BIBTEX : BibTeXDialog::BIBLATEX);
5368 }
5369 
insertUserTag()5370 void Texstudio::insertUserTag()
5371 {
5372 	QAction *action = qobject_cast<QAction *>(sender());
5373 	if (!action) return;
5374 	int id = action->data().toInt();
5375 	execMacro(configManager.completerConfig->userMacros.value(id, Macro()));
5376 }
5377 
execMacro(const Macro & m,const MacroExecContext & context,bool allowWrite)5378 void Texstudio::execMacro(const Macro &m, const MacroExecContext &context, bool allowWrite)
5379 {
5380 	if (m.type == Macro::Script) {
5381 		runScript(m.script(), context, allowWrite);
5382 	} else {
5383 		if (currentEditorView()) {
5384 			currentEditorView()->insertSnippet(m.snippet());
5385 		}
5386 	}
5387 }
5388 
runScript(const QString & script,const MacroExecContext & context,bool allowWrite)5389 void Texstudio::runScript(const QString &script, const MacroExecContext &context, bool allowWrite)
5390 {
5391 	scriptengine *eng = new scriptengine();
5392 	eng->triggerMatches = context.triggerMatches;
5393 	eng->triggerId = context.triggerId;
5394 	if (currentEditorView()) eng->setEditorView(currentEditorView());
5395 
5396 	eng->setScript(script, allowWrite);
5397 	eng->run();
5398 }
5399 
editMacros()5400 void Texstudio::editMacros()
5401 {
5402     if (!userMacroDialog)  {
5403         userMacroDialog = new UserMenuDialog(nullptr, tr("Edit User &Tags"), m_languages);
5404         bool atLeastOneAdded=false;
5405         foreach (const Macro &m, configManager.completerConfig->userMacros) {
5406             if (m.name == "TMX:Replace Quote Open" || m.name == "TMX:Replace Quote Close" || m.document)
5407                 continue;
5408             userMacroDialog->addMacro(m);
5409             atLeastOneAdded=true;
5410         }
5411         if(!atLeastOneAdded){
5412             // add one empty macro in case of empty macro least
5413             Macro m;
5414             userMacroDialog->addMacro(m);
5415         }
5416         userMacroDialog->selectFirst();
5417         connect(userMacroDialog, SIGNAL(accepted()), SLOT(macroDialogAccepted()));
5418         connect(userMacroDialog, SIGNAL(rejected()), SLOT(macroDialogRejected()));
5419         connect(userMacroDialog, SIGNAL(runScript(QString)), SLOT(runScript(QString)));
5420         // persistent setting like wrap
5421         userMacroDialog->setLineWrap(configManager.macroEditorUsesLineWrap);
5422     }
5423     userMacroDialog->show();
5424     userMacroDialog->raise();
5425     userMacroDialog->setFocus();
5426 }
5427 
macroDialogAccepted()5428 void Texstudio::macroDialogAccepted()
5429 {
5430 	configManager.completerConfig->userMacros.clear();
5431 
5432         configManager.completerConfig->userMacros << userMacroDialog->getMacros();
5433 
5434 	for (int i = 0; i < documents.documents.size(); i++)
5435 		configManager.completerConfig->userMacros << documents.documents[i]->localMacros;
5436 	updateUserMacros();
5437         configManager.saveMacros();
5438 	completer->updateAbbreviations();
5439 	addMacrosAsTagList();
5440     // read out wrap setting to make it persistent
5441     configManager.macroEditorUsesLineWrap=userMacroDialog->getLineWrap();
5442 
5443 	userMacroDialog->deleteLater();
5444 	userMacroDialog = nullptr;
5445 }
5446 
macroDialogRejected()5447 void Texstudio::macroDialogRejected()
5448 {
5449 	userMacroDialog->deleteLater();
5450 	userMacroDialog = nullptr;
5451 }
5452 
insertRef(const QString & refCmd)5453 void Texstudio::insertRef(const QString &refCmd)
5454 {
5455 	//updateStructure();
5456 
5457 	LatexEditorView *edView = currentEditorView();
5458 	QStringList labels;
5459 	if (edView && edView->document) {
5460 		QList<LatexDocument *> docs;
5461 		docs << edView->document->getListOfDocs();
5462 		foreach (const LatexDocument *doc, docs)
5463 			labels << doc->labelItems();
5464 	} else return;
5465 	labels.sort();
5466 	UniversalInputDialog dialog;
5467 	dialog.addVariable(&labels, tr("Labels:"));
5468 	if (dialog.exec() && !labels.isEmpty()) {
5469 		QString tag = refCmd + "{" + labels.first() + "}";
5470 		insertTag(tag, tag.length(), 0);
5471 	} else
5472 		insertTag(refCmd + "{}", refCmd.length() + 1, 0);
5473 }
5474 
insertRef()5475 void Texstudio::insertRef()
5476 {
5477 	insertRef("\\ref");
5478 }
5479 
insertEqRef()5480 void Texstudio::insertEqRef()
5481 {
5482 	insertRef("\\eqref");
5483 }
5484 
insertPageRef()5485 void Texstudio::insertPageRef()
5486 {
5487 	insertRef("\\pageref");
5488 }
5489 
changeTextCodec()5490 void Texstudio::changeTextCodec()
5491 {
5492 	QAction *action = qobject_cast<QAction *>(sender());
5493 	if (!action) return;
5494 	bool ok;
5495 	int mib = action->data().toInt(&ok);
5496 	if (!ok) return;
5497 	if (!currentEditorView()) return;
5498 
5499 	currentEditorView()->editor->setFileCodec(QTextCodec::codecForMib(mib));
5500 	updateCaption();
5501 }
5502 
editorSpellerChanged(const QString & name)5503 void Texstudio::editorSpellerChanged(const QString &name)
5504 {
5505 	foreach (QAction *act, statusTbLanguage->actions()) {
5506 		if (act->data().toString() == name) {
5507 			act->setChecked(true);
5508 			break;
5509 		}
5510 	}
5511 	if (name == "<default>") {
5512 		statusTbLanguage->setText(spellerManager.defaultSpellerName());
5513 	} else {
5514 		statusTbLanguage->setText(name);
5515 	}
5516 }
5517 
changeEditorSpeller()5518 void Texstudio::changeEditorSpeller()
5519 {
5520 	QAction *action = qobject_cast<QAction *>(sender());
5521 	if (!action) return;
5522 	if (!currentEditorView()) return;
5523 
5524     if (!currentEditorView()->setSpeller(action->data().toString(),true)) {
5525 		// restore activity of previous action
5526 		foreach (QAction *act, statusTbLanguage->actions()) {
5527 			if (act->data().toString() == currentEditorView()->getSpeller()) {
5528 				act->setChecked(true);
5529 				break;
5530 			}
5531 		}
5532 	}
5533 }
5534 
insertSpellcheckMagicComment()5535 void Texstudio::insertSpellcheckMagicComment()
5536 {
5537 	if (currentEditorView()) {
5538 		QString name = currentEditorView()->getSpeller();
5539 		if (name == "<default>") {
5540 			name = spellerManager.defaultSpellerName();
5541 		}
5542 		currentEditorView()->document->updateMagicComment("spellcheck", name, true);
5543 	}
5544 }
5545 
updateStatusBarEncoding()5546 void Texstudio::updateStatusBarEncoding()
5547 {
5548 	if (currentEditorView() && currentEditorView()->editor->getFileCodec()) {
5549 		QTextCodec *codec = currentEditorView()->editor->getFileCodec();
5550 		statusTbEncoding->setText(codec->name() + "  ");
5551 		QStringList aliases;
5552 		foreach (const QByteArray &b, codec->aliases()) {
5553 			aliases << QString(b);
5554 		}
5555 		if (!aliases.isEmpty()) {
5556 			statusTbEncoding->setToolTip(tr("Encoding Aliases: ") + aliases.join(", "));
5557 		} else {
5558 			statusTbEncoding->setToolTip(tr("Encoding"));
5559 		}
5560 	} else {
5561 		statusTbEncoding->setText(tr("Encoding") + "  ");
5562 		statusTbEncoding->setToolTip(tr("Encoding"));
5563 	}
5564 }
5565 
addMagicRoot()5566 void Texstudio::addMagicRoot()
5567 {
5568 	if (currentEditorView()) {
5569 		LatexDocument *doc = currentEditorView()->getDocument();
5570 		if (!doc) return;
5571 		QString name = doc->getRootDocument()->getFileName();
5572 		name = getRelativeFileName(name, doc->getFileName(), true);
5573 		currentEditorView()->document->updateMagicComment("root", name, true);
5574 	}
5575 }
5576 
addMagicCoding()5577 void Texstudio::addMagicCoding()
5578 {
5579 	if (currentEditorView()) {
5580 		QString name = currentEditor()->getFileCodec()->name();
5581 		currentEditorView()->document->updateMagicComment("encoding", name, true);
5582 	}
5583 }
5584 
addMagicBibliography()5585 void Texstudio::addMagicBibliography()
5586 {
5587     if (currentEditorView()) {
5588         currentEditorView()->document->updateMagicComment("TS-program", "", true,"!BIB");
5589     }
5590 }
5591 
addMagicProgram()5592 void Texstudio::addMagicProgram()
5593 {
5594     if (currentEditorView()) {
5595         currentEditorView()->document->updateMagicComment("TS-program", "", true);
5596     }
5597 }
5598 
5599 ///////////////TOOLS////////////////////
runCommand(const QString & commandline,QString * buffer,QTextCodec * codecForBuffer,bool saveAll)5600 bool Texstudio::runCommand(const QString &commandline, QString *buffer, QTextCodec *codecForBuffer, bool saveAll)
5601 {
5602     if(saveAll){
5603         fileSaveAll(buildManager.saveFilesBeforeCompiling == BuildManager::SFBC_ALWAYS, buildManager.saveFilesBeforeCompiling == BuildManager::SFBC_ONLY_CURRENT_OR_NAMED);
5604     }
5605 	if (documents.getTemporaryCompileFileName() == "") {
5606 		if (buildManager.saveFilesBeforeCompiling == BuildManager::SFBC_ONLY_NAMED && currentEditorView()) {
5607 			QString tmpName = buildManager.createTemporaryFileName();
5608 			currentEditor()->saveCopy(tmpName);
5609 			currentEditorView()->document->setTemporaryFileName(tmpName);
5610 		} else {
5611 			QMessageBox::warning(this, tr("Error"), tr("Can't detect the file name.\nYou have to save a document before you can compile it."));
5612 			return false;
5613 		}
5614 	}
5615 
5616 	QString finame = documents.getTemporaryCompileFileName();
5617 	if (finame == "") {
5618 		UtilsUi::txsWarning(tr("Can't detect the file name"));
5619 		return false;
5620 	}
5621 
5622 	int ln = currentEditorView() ? currentEditorView()->editor->cursor().lineNumber() + 1 : 0;
5623     // unified error/stdout into *buffer
5624     return buildManager.runCommand(commandline, QFileInfo(finame), QFileInfo(getCurrentFileName()), ln, buffer, codecForBuffer,buffer);
5625 }
5626 
5627 /*!
5628  * Workaround method to use runCommand without interpreting special chars in the commandline.
5629  * TODO: Maybe these kind of calls should be decoupled from the buildmanager altogether, or
5630  * this should become a separate method of the buildmanager.
5631  */
runCommandNoSpecialChars(QString commandline,QString * buffer,QTextCodec * codecForBuffer)5632 bool Texstudio::runCommandNoSpecialChars(QString commandline, QString *buffer, QTextCodec *codecForBuffer) {
5633 	commandline.replace('@', "@@");
5634 	commandline.replace('%', "%%");
5635 	commandline.replace('?', "??");
5636     return runCommand(commandline, buffer, codecForBuffer,false);
5637 }
5638 /*!
5639  * \brief set StatusMessage for a process
5640  * \param message
5641  */
setStatusMessageProcess(const QString & message)5642 void Texstudio::setStatusMessageProcess(const QString &message)
5643 {
5644 	statusLabelProcess->setText(message);
5645 }
5646 /*!
5647  * \brief run the command asynchronously. When finished, SLOT returnCMD is called.
5648  * See Help::texdocAvailableRequest for an example.
5649  * \param commandline
5650  * \param returnCMD provide a SLOT which is called when finishing the process
5651  * \return true when start works
5652  */
runCommandAsync(const QString & commandline,const char * returnCMD)5653 bool Texstudio::runCommandAsync(const QString &commandline, const char * returnCMD){
5654     QObject *obj=sender();
5655     QString finame = documents.getTemporaryCompileFileName();
5656     ProcessX *proc = buildManager.firstProcessOfDirectExpansion(commandline, QFileInfo(finame));
5657     setStatusMessageProcess(tr("  Running this command: ") + proc->getCommandLine());
5658     connect(proc, SIGNAL(finished(int,QProcess::ExitStatus)), obj, returnCMD);
5659     QString *buffer=new QString();
5660     proc->setStdoutBuffer(buffer);
5661     proc->startCommand();
5662     if (!proc->waitForStarted(1000)) {
5663         setStatusMessageProcess(tr("Error") + " : " + tr("could not start the command"));
5664         return false;
5665     }
5666     return true;
5667 }
5668 
runInternalPdfViewer(const QFileInfo & master,const QString & options)5669 void Texstudio::runInternalPdfViewer(const QFileInfo &master, const QString &options)
5670 {
5671 #ifndef NO_POPPLER_PREVIEW
5672 	QStringList ol = BuildManager::splitOptions(options);
5673 	QString pdfFile;
5674 	for (int i = ol.size() - 1; i >= 0; i--) {
5675 		if (!ol[i].startsWith("-")) {
5676 			if (pdfFile.isNull()) pdfFile = dequoteStr(ol[i]);
5677 			ol.removeAt(i);
5678 		} else if (ol[i].startsWith("--")) ol[i] = ol[i].mid(2);
5679 		else ol[i] = ol[i].mid(1);
5680 	}
5681 	bool preserveExisting = ol.contains("preserve-existing");                  //completely ignore all existing internal viewers
5682 	bool preserveExistingEmbedded = ol.contains("preserve-existing-embedded"); //completely ignore all existing embedded internal viewers
5683 	bool preserveExistingWindowed = ol.contains("preserve-existing-windowed"); //completely ignore all existing windowed internal viewers
5684 	bool preserveDuplicates = ol.contains("preserve-duplicates");              //open the document only in one pdf viewer
5685 	bool embedded = ol.contains("embedded");                                   //if a new pdf viewer is opened, use the embedded mode
5686 	bool windowed = ol.contains("windowed");                                   //if a new pdf viewer is opened, use the windowed mode (default)
5687 	bool closeAll = ol.contains("close-all");                                  //close all existing viewers
5688 	bool closeEmbedded = closeAll || ol.contains("close-embedded");            //close all embedded existing viewer
5689 	bool closeWindowed = closeAll || ol.contains("close-windowed");            //close all windowed existing viewer
5690 
5691 	bool autoClose;
5692 	if (embedded) autoClose = ! ol.contains("no-auto-close");                  //Don't close the viewer, if the corresponding document is closed
5693 	else autoClose = ol.contains("auto-close");                                //Close the viewer, if the corresponding document is closed
5694 
5695 	PDFDocument::DisplayFlags displayPolicy = PDFDocument::FocusWindowed | PDFDocument::Raise;
5696 	if (ol.contains("no-focus")) displayPolicy &= ~PDFDocument::Focus;
5697 	else if (ol.contains("focus")) displayPolicy |= PDFDocument::Focus;
5698 	if (ol.contains("no-foreground")) displayPolicy &= ~PDFDocument::Raise;
5699 	else if (ol.contains("foreground")) displayPolicy |= PDFDocument::Raise;
5700 
5701 	if (!(embedded || windowed || closeEmbedded || closeWindowed)) windowed = true; //default
5702 
5703 	//embedded/windowed are mutual exclusive
5704 	//no viewer will be opened, if one already exist (unless it was closed by a explicitely given close command)
5705 
5706 
5707 	QList<PDFDocument *> oldPDFs = PDFDocument::documentList();
5708 
5709 	if (preserveExisting) oldPDFs.clear();
5710 	if (preserveExistingWindowed)
5711 		for (int i = oldPDFs.size() - 1; i >= 0; i--)
5712 			if (!oldPDFs[i]->embeddedMode)
5713 				oldPDFs.removeAt(i);
5714 	if (preserveExistingEmbedded)
5715 		for (int i = oldPDFs.size() - 1; i >= 0; i--)
5716 			if (oldPDFs[i]->embeddedMode)
5717 				oldPDFs.removeAt(i);
5718 	for (int i = oldPDFs.size() - 1; i >= 0; i--)
5719 		if (oldPDFs[i]->ignoreSynchronization())
5720 			oldPDFs.removeAt(i);
5721 
5722 	//if closing and opening is set, reuse the first document (reuse = optimization, so it does not close a viewer and creates an equal viewer afterwards)
5723 	PDFDocument *reuse = nullptr;
5724 	if ((embedded || windowed) && (closeEmbedded || closeWindowed) && !oldPDFs.isEmpty() ) {
5725 		for (int i = 0; i < oldPDFs.size(); i++)
5726 			if (oldPDFs[i]->embeddedMode == embedded) {
5727 				reuse = oldPDFs.takeAt(i);
5728 				break;
5729 			}
5730 	}
5731 
5732 	//close old
5733 	for (int i = oldPDFs.size() - 1; i >= 0; i--)
5734 		if ( (oldPDFs[i]->embeddedMode && closeEmbedded) ||
5735 		     (!oldPDFs[i]->embeddedMode && closeWindowed) ){
5736 		        oldPDFs[i]->close();
5737 			oldPDFs.removeAt(i);
5738 		}
5739 
5740 
5741 	//open new
5742 	if (!embedded && !windowed) return;
5743 
5744 	if (reuse) oldPDFs.insert(0, reuse);
5745 	if (oldPDFs.isEmpty()) {
5746 		PDFDocument *doc = qobject_cast<PDFDocument *>(newPdfPreviewer(embedded));
5747 		REQUIRE(doc);
5748 		doc->autoClose = autoClose;
5749 		oldPDFs << doc;
5750                 changePDFIconSize(configManager.guiPDFToolbarIconSize);
5751 	}
5752 
5753 	if (pdfFile.isNull()) {
5754 		pdfFile = master.completeBaseName() + ".pdf";
5755 	}
5756     if(!QFileInfo(pdfFile).isAbsolute() || !QFileInfo(pdfFile).isReadable()){
5757         pdfFile = buildManager.findCompiledFile(pdfFile, master); // don't search if file name is given as absolutue path and exists
5758     }
5759 	int ln = 0;
5760 	int col = 0;
5761 	if (currentEditorView()) {
5762 		col = currentEditorView()->editor->cursor().columnNumber();
5763 		ln = currentEditorView()->editor->cursor().lineNumber();
5764 		int originalLineNumber = currentEditorView()->document->lineToLineSnapshotLineNumber(currentEditorView()->editor->cursor().line());
5765 		if (originalLineNumber >= 0) ln = originalLineNumber;
5766 	}
5767 	foreach (PDFDocument *viewer, oldPDFs) {
5768 		viewer->loadFile(pdfFile, master, displayPolicy);
5769 		int pg = viewer->syncFromSource(getCurrentFileName(), ln, col, displayPolicy);
5770 		viewer->fillRenderCache(pg);
5771         if (viewer->embeddedMode && configManager.viewerEnlarged) {
5772             sidePanelSplitter->hide();
5773 			viewer->setStateEnlarged(true);
5774             //centralVSplitter->hide();
5775 		}
5776 
5777 		if (preserveDuplicates) break;
5778 	}
5779 #if defined Q_OS_MAC
5780 	if (embedded)
5781 		setMenuBar(configManager.menuParentsBar);
5782 #endif
5783 
5784 #else
5785 	Q_UNUSED(master)
5786 	Q_UNUSED(options)
5787 	UtilsUi::txsCritical(tr("You have called the command to open the internal pdf viewer.\nHowever, you are using a version of TeXstudio that was compiled without the internal pdf viewer."));
5788 #endif
5789 
5790 }
5791 
checkProgramPermission(const QString & program,const QString & cmdId,LatexDocument * master)5792 bool Texstudio::checkProgramPermission(const QString &program, const QString &cmdId, LatexDocument *master)
5793 {
5794 	static const QRegExp txsCmd(QRegExp::escape(BuildManager::TXS_CMD_PREFIX) + "([^/ [{]+))");
5795 	if (txsCmd.exactMatch(program)) return true;
5796 	static QStringList programWhiteList;
5797 	configManager.registerOption("Tools/Program Whitelist", &programWhiteList, QStringList() << "latex" << "pdflatex");
5798 	if (programWhiteList.contains(program)) return true;
5799 	if (buildManager.hasCommandLine(program)) return true;
5800 	if (!master) return false;
5801 	QString id = master->getMagicComment("document-id");
5802 	if (id.contains("=")) return false;
5803 	static QStringList individualProgramWhiteList;
5804 	configManager.registerOption("Tools/Individual Program Whitelist", &individualProgramWhiteList, QStringList());
5805 	if (!id.isEmpty() && individualProgramWhiteList.contains(id + "=" + program)) return true;
5806 	int t = QMessageBox::warning(nullptr, TEXSTUDIO,
5807 	                             tr("The document \"%1\" wants to override the command \"%2\" with \"%3\".\n\n"
5808 	                                "Do you want to allow and run the new, overriding command?\n\n"
5809 	                                "(a) Yes, allow the new command for this document (only if you trust this document)\n"
5810 	                                "(b) Yes, allow the new command to be used for all documents (only if you trust the new command to handle arbitrary documents)\n"
5811 	                                "(c) No, do not use the command \"%3\" and run the default \"%2\" command"
5812                                    ).arg(master ? master->getFileName() : "",cmdId,program),
5813 	                             tr("(a) allow for this document"),
5814 	                             tr("(b) allow for all documents"),
5815 	                             tr("(c) use the default command"), 0, 2);
5816 	if (t == 2) return false;
5817 	if (t == 1) {
5818 		programWhiteList.append(program);
5819 		return true;
5820 	}
5821 	if (id.isEmpty()) {
5822 		id = QUuid::createUuid().toString();
5823 		master->updateMagicComment("document-id", id, true);
5824 		if (master->getMagicComment("document-id") != id) return false;
5825 	}
5826 	individualProgramWhiteList.append(id + "=" + program);
5827 	return true;
5828 }
5829 
runBibliographyIfNecessary(const QFileInfo & mainFile)5830 void Texstudio::runBibliographyIfNecessary(const QFileInfo &mainFile)
5831 {
5832 	if (!configManager.runLaTeXBibTeXLaTeX) return;
5833 	if (runBibliographyIfNecessaryEntered) return;
5834 
5835 	LatexDocument *rootDoc = documents.getRootDocumentForDoc();
5836 	REQUIRE(rootDoc);
5837 
5838 	QList<LatexDocument *> docs = rootDoc->getListOfDocs();
5839 	QSet<QString> bibFiles;
5840 	foreach (const LatexDocument *doc, docs) {
5841 		foreach (const FileNamePair &bf, doc->mentionedBibTeXFiles()) {
5842 			bibFiles.insert(bf.absolute);
5843 		}
5844 	}
5845 	if(bibFiles.isEmpty()) {
5846 		return; // don't try to compile bibtex files if there none
5847 	}
5848 	if (bibFiles == rootDoc->lastCompiledBibTeXFiles) {
5849 		QDateTime bblLastModified = GetBblLastModified();
5850 		if (bblLastModified.isValid()) {
5851 			bool bibFilesChanged = false;
5852 			foreach (const QString &bf, bibFiles) {
5853 				//qDebug() << bf << ": "<<QFileInfo(bf).lastModified()<<" "<<bblLastModified;
5854                 if (QFileInfo::exists(bf) && QFileInfo(bf).lastModified() > bblLastModified) {
5855 					bibFilesChanged = true;
5856 					break;
5857 				}
5858 			}
5859 			if (!bibFilesChanged) return;
5860 		}
5861 	} else rootDoc->lastCompiledBibTeXFiles = bibFiles;
5862 
5863 	runBibliographyIfNecessaryEntered = true;
5864 	buildManager.runCommand(BuildManager::CMD_RECOMPILE_BIBLIOGRAPHY, mainFile);
5865 	runBibliographyIfNecessaryEntered = false;
5866 }
5867 
GetBblLastModified(void)5868 QDateTime Texstudio::GetBblLastModified(void)
5869 {
5870 	QFileInfo compileFile (documents.getTemporaryCompileFileName());
5871 	QString compileDir(compileFile.absolutePath());
5872 	FindInDirs findInDirs(true, false, compileDir);
5873 	findInDirs.loadDirs(compileDir);
5874 	findInDirs.loadDirs(BuildManager::resolvePaths(buildManager.additionalLogPaths));
5875 	QString bblPathname = findInDirs.findAbsolute(compileFile.completeBaseName() + ".bbl");
5876 	if (bblPathname == "") {
5877 		return QDateTime();
5878 	}
5879 	return QFileInfo(bblPathname).lastModified();
5880 }
5881 
runInternalCommand(const QString & cmd,const QFileInfo & mainfile,const QString & options)5882 void Texstudio::runInternalCommand(const QString &cmd, const QFileInfo &mainfile, const QString &options)
5883 {
5884 	if (cmd == BuildManager::CMD_VIEW_PDF_INTERNAL || (cmd.startsWith(BuildManager::CMD_VIEW_PDF_INTERNAL) && cmd[BuildManager::CMD_VIEW_PDF_INTERNAL.length()] == ' '))
5885 		runInternalPdfViewer(mainfile, options);
5886 	else if (cmd == BuildManager::CMD_CONDITIONALLY_RECOMPILE_BIBLIOGRAPHY)
5887 		runBibliographyIfNecessary(mainfile);
5888 	else if (cmd == BuildManager::CMD_VIEW_LOG) {
5889 		loadLog();
5890 		viewLog();
5891 	} else UtilsUi::txsWarning(tr("Unknown internal command: %1").arg(cmd));
5892 }
5893 
runInternalCommand(const QString & cmd,const QString & mainfile,const QString & options)5894 void Texstudio::runInternalCommand(const QString &cmd, const QString &mainfile, const QString &options){
5895     runInternalCommand(cmd,QFileInfo(mainfile),options);
5896 }
5897 
commandLineRequested(const QString & cmdId,QString * result,bool *)5898 void Texstudio::commandLineRequested(const QString &cmdId, QString *result, bool *)
5899 {
5900 	if (!buildManager.m_interpetCommandDefinitionInMagicComment) return;
5901 	LatexDocument *rootDoc = documents.getRootDocumentForDoc();
5902 	if (!rootDoc) return;
5903 	QString magic = rootDoc->getMagicComment("TXS-program:" + cmdId);
5904 	if (!magic.isEmpty()) {
5905 		if (!checkProgramPermission(magic, cmdId, rootDoc)) return;
5906 		*result = magic;
5907 		return;
5908 	}
5909 	QString program = rootDoc->getMagicComment("program");
5910 	if (program.isEmpty()) program = rootDoc->getMagicComment("TS-program");
5911 	if (program.isEmpty()) return;
5912 
5913 	if (cmdId == "quick") {
5914 		QString compiler = buildManager.guessCompilerFromProgramMagicComment(program);
5915 		QString viewer = buildManager.guessViewerFromProgramMagicComment(program);
5916 		if (!viewer.isEmpty() && !compiler.isEmpty()) {
5917 			*result = BuildManager::chainCommands(compiler, viewer);
5918         } else if (checkProgramPermission(program, cmdId, rootDoc)) {  // directly execute whatever program is. Why does quick behave differently from compile ?
5919 			*result = program;
5920 		}
5921 	} else if (cmdId == "compile") {
5922         QString compiler = buildManager.guessCompilerFromProgramMagicComment(program);
5923         if(!compiler.isEmpty()){
5924             *result = compiler;
5925             // notify used magic comment
5926             outputView->insertMessageLine(tr("%!TeX program used: %1").arg(program));
5927         }else{
5928             // warn about unused magic comment
5929             outputView->insertMessageLine(tr("%!TeX program not recognized! (%1). Using default.").arg(program));
5930         }
5931 	} else if (cmdId == "view") {
5932         QString viewer = buildManager.guessViewerFromProgramMagicComment(program);
5933         if(!viewer.isEmpty()){
5934             *result = viewer;
5935         }
5936 	}
5937 }
5938 
beginRunningCommand(const QString & commandMain,bool latex,bool pdf,bool async)5939 void Texstudio::beginRunningCommand(const QString &commandMain, bool latex, bool pdf, bool async)
5940 {
5941 	if (pdf) {
5942 		runningPDFCommands++;
5943 		if (async && pdf) runningPDFAsyncCommands++;
5944 #ifndef NO_POPPLER_PREVIEW
5945 		PDFDocument::isCompiling = true;
5946 		PDFDocument::isMaybeCompiling |= runningPDFAsyncCommands > 0;
5947 #endif
5948 
5949 		if (configManager.autoCheckinAfterSaveLevel > 0) {
5950 			QFileInfo fi(documents.getTemporaryCompileFileName());
5951 			fi.setFile(fi.path() + "/" + fi.baseName() + ".pdf");
5952 			if (fi.exists() && !fi.isWritable()) {
5953 				//pdf not writeable, needs locking ?
5954 				svn.lock(fi.filePath());
5955 			}
5956 		}
5957 	}
5958     //outputView->resetMessagesAndLog(!configManager.showMessagesWhenCompiling);
5959 	if (latex) {
5960 		foreach (LatexEditorView *edView, editors->editors()) {
5961 			edView->document->saveLineSnapshot();
5962 		}
5963     }
5964 	setStatusMessageProcess(QString(" %1 ").arg(buildManager.getCommandInfo(commandMain).displayName));
5965 }
5966 
connectSubCommand(ProcessX * p,bool showStdoutLocally)5967 void Texstudio::connectSubCommand(ProcessX *p, bool showStdoutLocally)
5968 {
5969 	connect(p, SIGNAL(standardErrorRead(QString)), outputView, SLOT(insertMessageLine(QString)));
5970 	if (p->showStdout()) {
5971 		p->setShowStdout((configManager.showStdoutOption == 2) || (showStdoutLocally && configManager.showStdoutOption == 1));
5972 		connect(p, SIGNAL(standardOutputRead(QString)), outputView, SLOT(insertMessageLine(QString)));
5973 	}
5974 }
5975 
beginRunningSubCommand(ProcessX * p,const QString & commandMain,const QString & subCommand,const RunCommandFlags & flags)5976 void Texstudio::beginRunningSubCommand(ProcessX *p, const QString &commandMain, const QString &subCommand, const RunCommandFlags &flags)
5977 {
5978 	if (commandMain != subCommand)
5979         setStatusMessageProcess(QString(" %1: %2 ").arg(buildManager.getCommandInfo(commandMain).displayName,buildManager.getCommandInfo(subCommand).displayName));
5980 	if (flags & RCF_COMPILES_TEX)
5981 		clearLogEntriesInEditors();
5982 	//outputView->resetMessages();
5983 	connectSubCommand(p, (RCF_SHOW_STDOUT & flags));
5984 }
5985 
endRunningSubCommand(ProcessX * p,const QString & commandMain,const QString & subCommand,const RunCommandFlags & flags)5986 void Texstudio::endRunningSubCommand(ProcessX *p, const QString &commandMain, const QString &subCommand, const RunCommandFlags &flags)
5987 {
5988 	if (p->exitCode() && (flags & RCF_COMPILES_TEX) && !logExists()) {
5989 		if (!QFileInfo(QFileInfo(documents.getTemporaryCompileFileName()).absolutePath()).isWritable())
5990 			UtilsUi::txsWarning(tr("You cannot compile the document in a non writable directory."));
5991 		else
5992 			UtilsUi::txsWarning(tr("Could not start %1.").arg( buildManager.getCommandInfo(commandMain).displayName + ":" + buildManager.getCommandInfo(subCommand).displayName + ":\n" + p->getCommandLine()));
5993 	}
5994 	if ((flags & RCF_CHANGE_PDF)  && !(flags & RCF_WAITFORFINISHED) && (runningPDFAsyncCommands > 0)) {
5995 		runningPDFAsyncCommands--;
5996 #ifndef NO_POPPLER_PREVIEW
5997 		if (runningPDFAsyncCommands <= 0) PDFDocument::isMaybeCompiling = false;
5998 #endif
5999 	}
6000 
6001 }
6002 
endRunningCommand(const QString & commandMain,bool latex,bool pdf,bool async)6003 void Texstudio::endRunningCommand(const QString &commandMain, bool latex, bool pdf, bool async)
6004 {
6005 	Q_UNUSED(commandMain)
6006 	Q_UNUSED(async)
6007 	if (pdf) {
6008 		runningPDFCommands--;
6009 #ifndef NO_POPPLER_PREVIEW
6010 		if (runningPDFCommands <= 0)
6011 			PDFDocument::isCompiling = false;
6012 #endif
6013 	}
6014 	setStatusMessageProcess(QString(" %1 ").arg(tr("Ready")));
6015 	if (latex) emit infoAfterTypeset();
6016 	previewIsAutoCompiling = false;
6017 }
6018 
processNotification(const QString & message)6019 void Texstudio::processNotification(const QString &message)
6020 {
6021 	if (message.startsWith(tr("Error:")))
6022 		outputView->showPage(outputView->MESSAGES_PAGE);
6023 	outputView->insertMessageLine(message + "\n");
6024 }
6025 /*!
6026  * \brief clear log view in panel
6027  */
clearLogs()6028 void Texstudio::clearLogs(){
6029     outputView->resetMessagesAndLog(!configManager.showMessagesWhenCompiling);
6030 }
6031 
6032 /*!
6033  * \brief Opens a new external terminal
6034  */
openExternalTerminal(void)6035 void Texstudio::openExternalTerminal(void)
6036 {
6037 	QString fileMain, fileCurrent;
6038 
6039 	if ((fileMain = documents.getTemporaryCompileFileName()) == "") {
6040 		fileMain = getUserDocumentFolder() + QDir::separator() + "none.tex";
6041 	}
6042 	if ((fileCurrent = getCurrentFileName()) == "") {
6043 		fileCurrent = fileMain;
6044 	}
6045 	ExpandingOptions expOptions(
6046         QFileInfo(fileMain),
6047         QFileInfo(fileCurrent),
6048 		currentEditorView() ? currentEditorView()->editor->cursor().lineNumber() + 1 : 0
6049 	);
6050 	ExpandedCommands expCommands = buildManager.expandCommandLine(
6051 		BuildManager::CMD_TERMINAL_EXTERNAL,
6052 		expOptions
6053 	);
6054 	if (expCommands.commands.isEmpty()) {
6055 		return;
6056 	}
6057 	QString commandLine(expCommands.commands.first().command);
6058 	ExecProgram execProgram(
6059 		commandLine,
6060 		"",
6061 		QFileInfo(fileCurrent).absolutePath()
6062 	);
6063 #ifdef Q_OS_WIN
6064 	execProgram.m_winProcModifier = [] (QProcess::CreateProcessArguments *args) {
6065 		args->flags |= CREATE_NEW_CONSOLE;
6066 	};
6067 #endif
6068 	bool execResult = execProgram.execDetached();
6069 	outputView->insertMessageLine(
6070 		execResult ?
6071 		QString("Started external terminal program %1").arg(commandLine) :
6072 		QString("Could not start external terminal program %1").arg(commandLine)
6073 	);
6074 }
6075 
6076 /*!
6077  * \brief run a command which was triggered from a Qaction (menu or toolbar)
6078  * The actual command is stored as data in the action.
6079  * runCommand is used
6080  */
commandFromAction()6081 void Texstudio::commandFromAction()
6082 {
6083 	QAction *act = qobject_cast<QAction *>(sender());
6084 	if (!act) return;
6085 	runCommand(act->data().toString());
6086 }
6087 /*!
6088  * \brief clean auxilliary files
6089  * Uses CleanDialog for actual functionality
6090  */
cleanAll()6091 void Texstudio::cleanAll()
6092 {
6093 	CleanDialog cleanDlg(this);
6094 	if (cleanDlg.checkClean(documents)) {
6095 		cleanDlg.exec();
6096 	} else {
6097 		UtilsUi::txsInformation(tr("No open project or tex file to clean."));
6098 	}
6099 }
6100 /*!
6101  * \brief export document as html
6102  * Use WebPublishDialog for actual functionality
6103  */
webPublish()6104 void Texstudio::webPublish()
6105 {
6106 	if (!currentEditorView()) {
6107 		UtilsUi::txsWarning(tr("No document open"));
6108 		return;
6109 	}
6110 	if (!currentEditorView()->editor->getFileCodec()) return;
6111 	fileSave();
6112 	QString finame = documents.getCompileFileName();
6113 
6114 	WebPublishDialog *ttwpDlg = new WebPublishDialog(this, configManager.webPublishDialogConfig, &buildManager,
6115 	        currentEditorView()->editor->getFileCodec());
6116 	ttwpDlg->ui.inputfileEdit->setText(finame);
6117 	ttwpDlg->exec();
6118 	delete ttwpDlg;
6119 }
6120 /*!
6121  * \brief export current document as html
6122  * Use document->exportAsHtml
6123  */
webPublishSource()6124 void Texstudio::webPublishSource()
6125 {
6126 	if (!currentEditor()) return;
6127 	QDocumentCursor cur = currentEditor()->cursor();
6128 	QString	html = currentEditor()->document()->exportAsHtml(cur.hasSelection() ? cur : QDocumentCursor(), true);
6129 	fileNew(getCurrentFileName() + ".html");
6130 	currentEditor()->insertText(html);
6131 	/*QLabel* htmll = new QLabel(html, this);
6132 	htmll->show();
6133 	htmll->resize(300,300);*/
6134 }
6135 /*!
6136  * \brief open analyse text dialog
6137  * Makes use of TextAnalysisDialog
6138  */
analyseText()6139 void Texstudio::analyseText()
6140 {
6141 	if (!currentEditorView()) {
6142 		UtilsUi::txsWarning(tr("No document open"));
6143 		return;
6144 	}
6145 	if (!textAnalysisDlg) {
6146 		textAnalysisDlg = new TextAnalysisDialog(this, tr("Text Analysis"));
6147 		connect(textAnalysisDlg, SIGNAL(destroyed()), this, SLOT(analyseTextFormDestroyed()));
6148 	}
6149 	if (!textAnalysisDlg) return;
6150 	textAnalysisDlg->setEditor(currentEditorView()->editor);//->document(), currentEditorView()->editor->cursor());
6151 	textAnalysisDlg->init();
6152 	textAnalysisDlg->interpretStructureTree(currentEditorView()->document->baseStructure);
6153 
6154 	textAnalysisDlg->show();
6155 	textAnalysisDlg->raise(); //not really necessary, since the dlg is always on top
6156 	textAnalysisDlg->activateWindow();
6157 }
6158 
analyseTextFormDestroyed()6159 void Texstudio::analyseTextFormDestroyed()
6160 {
6161         textAnalysisDlg = nullptr;
6162 }
6163 /*!
6164  * \brief generate random text
6165  * convienience function
6166  */
generateRandomText()6167 void Texstudio::generateRandomText()
6168 {
6169 	if (!currentEditorView()) {
6170 		UtilsUi::txsWarning(tr("The random text generator constructs new texts from existing words, so you have to open some text files"));
6171 		return;
6172 	}
6173 
6174 	QStringList allLines;
6175 	foreach (LatexEditorView *edView, editors->editors())
6176 		allLines << edView->editor->document()->textLines();
6177 	RandomTextGenerator generator(this, allLines);
6178 	generator.exec();
6179 }
6180 
6181 //////////////// MESSAGES - LOG FILE///////////////////////
6182 
6183 /// \brief check if log exists
6184 /// \return true if log is found
6185 ///
logExists()6186 bool Texstudio::logExists()
6187 {
6188 	QString finame = documents.getTemporaryCompileFileName();
6189 	if (finame == "")
6190 		return false;
6191 	QString logPathname(getAbsoluteFilePath(documents.getLogFileName()));
6192 	FindInDirs findInDirs(
6193 		true,
6194 		true,
6195 		QFileInfo(logPathname).absolutePath(),
6196 		BuildManager::resolvePaths(buildManager.additionalLogPaths)
6197 	);
6198 	return findInDirs.findAbsolute(logPathname) != "";
6199 }
6200 /*!
6201  * \brief load log from latex compilation
6202  * Try to find log in build dir as well as in additional log paths
6203  * Assume latin1 as text codec for log.
6204  * \return operation successful
6205  */
loadLog()6206 bool Texstudio::loadLog()
6207 {
6208 	outputView->getLogWidget()->resetLog();
6209 	if (!documents.getCurrentDocument()) return false;
6210 	QString compileFileName = documents.getTemporaryCompileFileName();
6211 	if (compileFileName == "") {
6212 		QMessageBox::warning(this, tr("Error"), tr("File must be saved and compiling before you can view the log"));
6213 		return false;
6214 	}
6215 	QString logPathname(getAbsoluteFilePath(documents.getLogFileName()));
6216 	FindInDirs findInDirs(
6217 		true,
6218 		true,
6219 		QFileInfo(logPathname).absolutePath(),
6220 		BuildManager::resolvePaths(buildManager.additionalLogPaths)
6221 	);
6222 	QString foundPathname = findInDirs.findAbsolute(logPathname);
6223 	if (foundPathname == "") {
6224 		return false;
6225 	}
6226 	QTextCodec * codec = QTextCodec::codecForName(configManager.logFileEncoding.toLatin1());
6227 	return outputView->getLogWidget()->loadLogFile(
6228 		foundPathname,
6229 		compileFileName,
6230 		codec ? codec : documents.getCurrentDocument()->codec()
6231 	);
6232 }
6233 /// open log page on panel
showLog()6234 void Texstudio::showLog()
6235 {
6236 	outputView->showPage(outputView->LOG_PAGE);
6237 }
6238 
6239 ///shows the log (even if it is empty)
viewLog()6240 void Texstudio::viewLog()
6241 {
6242 	showLog();
6243 	setLogMarksVisible(true);
6244 	if (configManager.goToErrorWhenDisplayingLog && hasLatexErrors()) {
6245 		int errorMarkID = outputView->getLogWidget()->getLogModel()->markID(LT_ERROR);
6246 		if (!gotoMark(false, errorMarkID)) {
6247 			gotoMark(true, errorMarkID);
6248 		}
6249 	}
6250 }
6251 
viewLogOrReRun(LatexCompileResult * result)6252 void Texstudio::viewLogOrReRun(LatexCompileResult *result)
6253 {
6254 	loadLog();
6255 	REQUIRE(result);
6256 	if (hasLatexErrors()) {
6257 		onCompileError();
6258 		*result = LCR_ERROR;
6259 	} else {
6260 		*result = LCR_NORMAL;
6261 		if (outputView->getLogWidget()->getLogModel()->existsReRunWarning())
6262 			*result = LCR_RERUN;
6263 		else if (configManager.runLaTeXBibTeXLaTeX) {
6264 			//run bibtex if citation is unknown to bibtex but contained in an included bib file
6265 			QStringList missingCitations = outputView->getLogWidget()->getLogModel()->getMissingCitations();
6266 			bool runBibTeX = false;
6267 			foreach (const QString &s, missingCitations) {
6268 				for (int i = 0; i < documents.mentionedBibTeXFiles.count(); i++) {
6269 					if (!documents.bibTeXFiles.contains(documents.mentionedBibTeXFiles[i])) continue;
6270 					BibTeXFileInfo &bibTex = documents.bibTeXFiles[documents.mentionedBibTeXFiles[i]];
6271 					if (bibTex.ids.contains(s)) {
6272 						runBibTeX = true;
6273 						break;
6274 					}
6275 				}
6276 				if (runBibTeX) break;
6277 			}
6278 			if (runBibTeX)
6279 				*result = LCR_RERUN_WITH_BIBLIOGRAPHY;
6280 		}
6281 	}
6282 }
6283 
6284 ////////////////////////// ERRORS /////////////////////////////
6285 /*!
6286  * \brief post processing after latex compilation errors are detected
6287  */
onCompileError()6288 void Texstudio::onCompileError()
6289 {
6290 	if (!previewIsAutoCompiling && configManager.getOption("Tools/ShowLogInCaseOfCompileError").toBool()) {
6291 		viewLog();
6292 	} else {
6293 		setLogMarksVisible(true);
6294 	}
6295 }
6296 
6297 /// changes visibilita of log markers in all editors
setLogMarksVisible(bool visible)6298 void Texstudio::setLogMarksVisible(bool visible)
6299 {
6300 	foreach (LatexEditorView *edView, editors->editors()) {
6301 		edView->setLogMarksVisible(visible);
6302 	}
6303 	QAction *act = getManagedAction("main/tools/logmarkers");
6304 	if (act) act->setChecked(visible);
6305 }
6306 
6307 /// removes the log entries from all editors
clearLogEntriesInEditors()6308 void Texstudio::clearLogEntriesInEditors()
6309 {
6310 	foreach (LatexEditorView *edView, editors->editors()) {
6311 		edView->clearLogMarks();
6312 	}
6313 }
6314 
6315 /// adds the current log entries to all editors
updateLogEntriesInEditors()6316 void Texstudio::updateLogEntriesInEditors()
6317 {
6318 	clearLogEntriesInEditors();
6319 	LatexLogModel *logModel = outputView->getLogWidget()->getLogModel();
6320 	QHash<QString, LatexEditorView *> tempFilenames; //temporary maps the filenames (as they appear in this log!) to the editor
6321 	int errorMarkID = QLineMarksInfoCenter::instance()->markTypeId("error");
6322 	int warningMarkID = QLineMarksInfoCenter::instance()->markTypeId("warning");
6323 	int badboxMarkID = QLineMarksInfoCenter::instance()->markTypeId("badbox");
6324 
6325 	for (int i = logModel->count() - 1; i >= 0; i--) {
6326 		if (logModel->at(i).oldline != -1) {
6327 			LatexEditorView *edView;
6328 			if (tempFilenames.contains(logModel->at(i).file)) edView = tempFilenames.value(logModel->at(i).file);
6329 			else {
6330 				edView = getEditorViewFromFileName(logModel->at(i).file, true);
6331 				tempFilenames[logModel->at(i).file] = edView;
6332 			}
6333 			if (edView) {
6334 				int markID;
6335 				switch (logModel->at(i).type) {
6336 				case LT_ERROR:
6337 					markID = errorMarkID;
6338 					break;
6339 				case LT_WARNING:
6340 					markID = warningMarkID;
6341 					break;
6342 				case LT_BADBOX:
6343 					markID = badboxMarkID;
6344 					break;
6345 				default:
6346 					markID = -1;
6347 				}
6348 				edView->addLogEntry(i, logModel->at(i).oldline - 1, markID);
6349 			}
6350 		}
6351 	}
6352 }
6353 /*!
6354  * \brief check if the log viewer contains latex errors
6355  * \return true if latex errors present
6356  */
hasLatexErrors()6357 bool Texstudio::hasLatexErrors()
6358 {
6359 	return outputView->getLogWidget()->getLogModel()->found(LT_ERROR);
6360 }
6361 
gotoNearLogEntry(int lt,bool backward,QString notFoundMessage)6362 bool Texstudio::gotoNearLogEntry(int lt, bool backward, QString notFoundMessage)
6363 {
6364 	if (!outputView->getLogWidget()->logPresent()) {
6365 		loadLog();
6366 	}
6367 	if (outputView->getLogWidget()->logPresent()) {
6368         if (outputView->getLogWidget()->getLogModel()->found(static_cast<LogType>(lt) )) {
6369 			showLog();
6370 			setLogMarksVisible(true);
6371             return gotoMark(backward, outputView->getLogWidget()->getLogModel()->markID(static_cast<LogType>(lt) ));
6372 		} else {
6373 			UtilsUi::txsInformation(notFoundMessage);
6374 		}
6375 	}
6376 	return false;
6377 }
6378 
clearMarkers()6379 void Texstudio::clearMarkers()
6380 {
6381 	setLogMarksVisible(false);
6382 }
6383 //////////////// HELP /////////////////
6384 /*!
6385  * \brief opem latex2e.html in external browser
6386  * The latex2e help file is present as html. An external browser is called via QDesktopService to open that file.
6387  */
6388 
latexHelp()6389 void Texstudio::latexHelp()
6390 {
6391 	QString latexHelp = findResourceFile("latex2e.html");
6392 	if (latexHelp == "")
6393 		QMessageBox::warning(this, tr("Error"), tr("File not found"));
6394 	else if (!QDesktopServices::openUrl("file:///" + latexHelp))
6395 		QMessageBox::warning(this, tr("Error"), tr("Could not open browser"));
6396 }
6397 /*!
6398  * \brief open user manual in external browser
6399  * The usermanual is present as html. An external browser is called via QDesktopService to open that file.
6400  */
userManualHelp()6401 void Texstudio::userManualHelp()
6402 {
6403 	QString latexHelp = findResourceFile("usermanual_en.html");
6404 	if (latexHelp == "")
6405 		QMessageBox::warning(this, tr("Error"), tr("File not found"));
6406 	else if (!QDesktopServices::openUrl("file:///" + latexHelp))
6407 		QMessageBox::warning(this, tr("Error"), tr("Could not open browser"));
6408 }
6409 /*!
6410  * \brief exec Help
6411  * First compile a list of all packages (which txs has detected)
6412  * Present that list via a simple selection dialog which in term calls texdoc to present package help.
6413  * txs internal package names are filtered out.
6414  */
texdocHelp()6415 void Texstudio::texdocHelp()
6416 {
6417 	QString selection;
6418 	QStringList packages;
6419 	if (currentEditorView()) {
6420 		selection = currentEditorView()->editor->cursor().selectedText();
6421 		foreach (const QString &key, currentEditorView()->document->parent->cachedPackages.keys()) {
6422 			if (currentEditorView()->document->parent->cachedPackages[key].completionWords.isEmpty())
6423 				// remove empty packages which probably do not exist
6424 				continue;
6425 			packages << LatexPackage::keyToPackageName(key);
6426 		}
6427 
6428 		packages.removeDuplicates();
6429 		packages.removeAll("latex-209");
6430 		packages.removeAll("latex-dev");
6431 		packages.removeAll("latex-l2tabu");
6432 		packages.removeAll("latex-document");
6433 		packages.removeAll("tex");
6434 	}
6435 
6436     help.execTexdocDialog(packages, selection);
6437 }
6438 /*!
6439  * \brief show about dialog
6440  * About dialog is produced in AboutDialog
6441  */
helpAbout()6442 void Texstudio::helpAbout()
6443 {
6444 	// The focus will return to the parent. Therefore we have to provide the correct caller (may be a viewer window).
6445 	QWidget *parentWindow = UtilsUi::windowForObject(sender(), this);
6446 	AboutDialog *abDlg = new AboutDialog(parentWindow);
6447 	abDlg->exec();
6448 	delete abDlg;
6449 }
6450 ////////////// OPTIONS //////////////////////////////////////
6451 
6452 /*!
6453  * \brief Show the general options dialog and activate changed options in the program
6454  * The method tries to detect some changes in order to redo with changed settings only when necessary.
6455  * Among otheres these areas include style, dark mode and online sytax check.
6456  */
generalOptions()6457 void Texstudio::generalOptions()
6458 {
6459     bool oldDarkMode = darkMode;
6460     int oldModernStyle = modernStyle;
6461     bool oldSystemTheme = useSystemTheme;
6462     int oldReplaceQuotes = configManager.replaceQuotes;
6463     autosaveTimer.stop();
6464     m_formats->modified = false;
6465     bool realtimeChecking = configManager.editorConfig->realtimeChecking;
6466     bool inlineSpellChecking = configManager.editorConfig->inlineSpellChecking;
6467     bool inlineCitationChecking = configManager.editorConfig->inlineCitationChecking;
6468     bool inlineReferenceChecking = configManager.editorConfig->inlineReferenceChecking;
6469     bool inlineSyntaxChecking = configManager.editorConfig->inlineSyntaxChecking;
6470     QString additionalBibPaths = configManager.additionalBibPaths;
6471     QStringList loadFiles = configManager.completerConfig->getLoadedFiles();
6472 
6473     // init pdf shortcuts if pdfviewer is not open
6474 #ifndef NO_POPPLER_PREVIEW
6475     PDFDocument *pdfviewerWindow=nullptr;
6476     if(PDFDocument::documentList().isEmpty()){
6477         pdfviewerWindow = new PDFDocument(configManager.pdfDocumentConfig, false);
6478         pdfviewerWindow->hide();
6479     }
6480 #endif
6481 
6482 
6483     if (configManager.possibleMenuSlots.isEmpty()) {
6484         for (int i = 0; i < staticMetaObject.methodCount(); i++) configManager.possibleMenuSlots.append(staticMetaObject.method(i).methodSignature());
6485         for (int i = 0; i < QEditor::staticMetaObject.methodCount(); i++) configManager.possibleMenuSlots.append("editor:" + QString(QEditor::staticMetaObject.method(i).methodSignature()));
6486         for (int i = 0; i < LatexEditorView::staticMetaObject.methodCount(); i++) configManager.possibleMenuSlots.append("editorView:" + QString(LatexEditorView::staticMetaObject.method(i).methodSignature()));
6487         configManager.possibleMenuSlots = configManager.possibleMenuSlots.filter(QRegularExpression("^[^*]+$"));
6488     }
6489     // GUI scaling
6490     connect(&configManager, &ConfigManager::iconSizeChanged, this, &Texstudio::changeIconSize);
6491     connect(&configManager, &ConfigManager::secondaryIconSizeChanged, this, &Texstudio::changeSecondaryIconSize);
6492     connect(&configManager, &ConfigManager::pdfIconSizeChanged , this, &Texstudio::changePDFIconSize);
6493     connect(&configManager, &ConfigManager::symbolGridIconSizeChanged, this, [=](int size) { changeSymbolGridIconSize(size); });
6494 
6495     // The focus will return to the parent. Therefore we have to provide the correct caller (may be a viewer window).
6496     QWidget *parentWindow = UtilsUi::windowForObject(sender(), this);
6497 
6498     if (configManager.execConfigDialog(parentWindow)) {
6499         QApplication::setOverrideCursor(Qt::WaitCursor);
6500 
6501         configManager.editorConfig->settingsChanged();
6502 
6503         spellerManager.setDictPaths(configManager.parseDirList(configManager.spellDictDir));
6504         spellerManager.setDefaultSpeller(configManager.spellLanguage);
6505 
6506         GrammarCheck::staticMetaObject.invokeMethod(grammarCheck, "init", Qt::QueuedConnection, Q_ARG(LatexParser, latexParser), Q_ARG(GrammarCheckerConfig, *configManager.grammarCheckerConfig));
6507 
6508         if (configManager.autoDetectEncodingFromLatex || configManager.autoDetectEncodingFromChars) QDocument::setDefaultCodec(nullptr);
6509         else QDocument::setDefaultCodec(configManager.newFileEncoding);
6510         QDocument::removeGuessEncodingCallback(&ConfigManager::getDefaultEncoding);
6511         QDocument::removeGuessEncodingCallback(&Encoding::guessEncoding);
6512         if (configManager.autoDetectEncodingFromLatex)
6513             QDocument::addGuessEncodingCallback(&Encoding::guessEncoding);
6514         if (configManager.autoDetectEncodingFromChars)
6515             QDocument::addGuessEncodingCallback(&ConfigManager::getDefaultEncoding);
6516 
6517         symbolListModel->setDarkmode(darkMode);
6518 
6519         ThesaurusDialog::prepareDatabase(configManager.parseDir(configManager.thesaurus_database));
6520         if (additionalBibPaths != configManager.additionalBibPaths) documents.updateBibFiles(true);
6521 
6522         // update syntaxChecking with alls docs
6523         foreach (LatexDocument *doc, documents.getDocuments()) {
6524             doc->enableSyntaxCheck(configManager.editorConfig->inlineSyntaxChecking && configManager.editorConfig->realtimeChecking);
6525         }
6526 
6527         //update highlighting ???
6528         bool updateHighlighting = (inlineSpellChecking != configManager.editorConfig->inlineSpellChecking);
6529         updateHighlighting |= (inlineCitationChecking != configManager.editorConfig->inlineCitationChecking);
6530         updateHighlighting |= (inlineReferenceChecking != configManager.editorConfig->inlineReferenceChecking);
6531         updateHighlighting |= (inlineSyntaxChecking != configManager.editorConfig->inlineSyntaxChecking);
6532         updateHighlighting |= (realtimeChecking != configManager.editorConfig->realtimeChecking);
6533         updateHighlighting |= (additionalBibPaths != configManager.additionalBibPaths);
6534         // recheck syntax when spellchecking and/or syntaxchecking has been effectively turned on
6535         bool recheckSyntax=(configManager.editorConfig->realtimeChecking &&(configManager.editorConfig->inlineSyntaxChecking || configManager.editorConfig->inlineSpellChecking)) || ((configManager.editorConfig->inlineSyntaxChecking && !inlineSyntaxChecking)||(configManager.editorConfig->inlineSpellChecking && !inlineSpellChecking));
6536 
6537         // activate/deactivate speller ...
6538         SpellerUtility::inlineSpellChecking= configManager.editorConfig->inlineSpellChecking && configManager.editorConfig->realtimeChecking;
6539 
6540         // dark/light-mode switch
6541         if(oldDarkMode != darkMode){
6542             // reload other formats
6543             QSettings *config=configManager.getSettings();
6544             config->beginGroup(darkMode ? "formatsDark" : "formats");
6545             m_formats = new QFormatFactory(darkMode ? ":/qxs/defaultFormatsDark.qxf" : ":/qxs/defaultFormats.qxf", this); //load default formats from resource file
6546             m_formats->load(*config, true); //load customized formats
6547             QDocument::setDefaultFormatScheme(m_formats);
6548             //m_formats->modified=true;
6549             config->endGroup();
6550             updateHighlighting=true;
6551         }
6552         // check for change in load completion files
6553         QStringList newLoadedFiles = configManager.completerConfig->getLoadedFiles();
6554         foreach (const QString &elem, newLoadedFiles) {
6555             if (loadFiles.removeAll(elem) == 0)
6556                 updateHighlighting = true;
6557             if (updateHighlighting)
6558                 break;
6559         }
6560         if (!loadFiles.isEmpty())
6561             updateHighlighting = true;
6562         buildManager.clearPreviewPreambleCache();//possible changed latex command / preview behaviour
6563 
6564         if (currentEditorView()) {
6565             foreach (LatexEditorView *edView, editors->editors()) {
6566                 edView->updateSettings();
6567                 if (updateHighlighting) {
6568                     edView->clearOverlays(); // for disabled syntax check
6569                     if (configManager.editorConfig->realtimeChecking) {
6570                         edView->document->updateLtxCommands();
6571                         edView->documentContentChanged(0, edView->document->lines());
6572                         edView->document->updateCompletionFiles(false, true);
6573                         if(recheckSyntax){
6574                             edView->reCheckSyntax(0);
6575                         }
6576                     } else {
6577                         edView->clearOverlays();
6578                     }
6579                 }
6580                 QSearchReplacePanel *searchpanel = qobject_cast<QSearchReplacePanel *>(edView->codeeditor->panels("Search")[0]);
6581                 searchpanel->updateIcon();
6582             }
6583             if (m_formats->modified)
6584                 QDocument::setBaseFont(QDocument::baseFont(), true);
6585             updateCaption();
6586 
6587             if (documents.indentIncludesInStructure != configManager.indentIncludesInStructure ||
6588                     documents.showCommentedElementsInStructure != configManager.showCommentedElementsInStructure ||
6589                     documents.markStructureElementsBeyondEnd != configManager.markStructureElementsBeyondEnd ||
6590                     documents.markStructureElementsInAppendix != configManager.markStructureElementsInAppendix) {
6591                 documents.indentIncludesInStructure = configManager.indentIncludesInStructure;
6592                 documents.showCommentedElementsInStructure = configManager.showCommentedElementsInStructure;
6593                 documents.markStructureElementsBeyondEnd = configManager.markStructureElementsBeyondEnd;
6594                 documents.markStructureElementsInAppendix = configManager.markStructureElementsInAppendix;
6595                 foreach (LatexDocument *doc, documents.documents)
6596                     updateStructure(false, doc);
6597             }
6598         }
6599         if (oldReplaceQuotes != configManager.replaceQuotes)
6600             updateUserMacros();
6601         // scale GUI
6602         changeIconSize(configManager.guiToolbarIconSize);
6603         changeSecondaryIconSize(configManager.guiSecondaryToolbarIconSize);
6604         changePDFIconSize(configManager.guiPDFToolbarIconSize);
6605         changeSymbolGridIconSize(configManager.guiSymbolGridIconSize, false);
6606         //custom toolbar
6607         setupToolBars();
6608         //completion
6609         completionBaseCommandsUpdated = true;
6610         completerNeedsUpdate();
6611         completer->setConfig(configManager.completerConfig);
6612         //update changed line mark colors
6613         QList<QLineMarkType> &marks = QLineMarksInfoCenter::instance()->markTypes();
6614         for (int i = 0; i < marks.size(); i++)
6615             if (m_formats->format("line:" + marks[i].id).background.isValid())
6616                 marks[i].color = m_formats->format("line:" + marks[i].id).background;
6617             else
6618                 marks[i].color = Qt::transparent;
6619         // update all docuemnts views as spellcheck may be different
6620         QEditor::setEditOperations(configManager.editorKeys, false); // true -> false, otherwise edit operation can't be removed, e.g. tab for indentSelection
6621         foreach (LatexEditorView *edView, editors->editors()) {
6622             QEditor *ed = edView->editor;
6623             if(configManager.interfaceStyle!="Orion Dark")
6624                 edView->updatePalette(QApplication::palette());
6625             ed->document()->markFormatCacheDirty();
6626             ed->update();
6627         }
6628         if (oldModernStyle != modernStyle || oldSystemTheme != useSystemTheme) {
6629             iconCache.clear();
6630             setupMenus();
6631             setupDockWidgets();
6632         }
6633         updateUserToolMenu();
6634         QApplication::restoreOverrideCursor();
6635     }
6636     if (configManager.autosaveEveryMinutes > 0) {
6637         autosaveTimer.start(configManager.autosaveEveryMinutes * 1000 * 60);
6638     }
6639 #ifndef NO_POPPLER_PREVIEW
6640     foreach (PDFDocument *viewer, PDFDocument::documentList()) {
6641         viewer->reloadSettings();
6642     }
6643     if(pdfviewerWindow){
6644         pdfviewerWindow->close();
6645         delete pdfviewerWindow;
6646     }
6647 #endif
6648 #ifdef INTERNAL_TERMINAL
6649     outputView->getTerminalWidget()->updateSettings();
6650 #endif
6651 }
6652 
6653 /*!
6654  * \brief execute commandLine arguments
6655  * txs can be started with command line arguments.
6656  * Most of them are interpreted here as they interact with documents
6657  * \param detected command line arguments as string list
6658  * \param realCmdLine
6659  */
executeCommandLine(const QStringList & args,bool realCmdLine)6660 void Texstudio::executeCommandLine(const QStringList &args, bool realCmdLine)
6661 {
6662 	// parse command line
6663 	QStringList filesToLoad;
6664 	bool hasExplicitRoot = false;
6665 
6666 	int line = -1;
6667 	int col = 0;
6668 	QString cite;
6669 #ifndef NO_POPPLER_PREVIEW
6670 	int page = -1;
6671 	bool pdfViewerOnly = false;
6672 #endif
6673 	for (int i = 0; i < args.size(); ++i) {
6674 		if (args[i] == "") continue;
6675 		if (args[i][0] != '-')  filesToLoad << args[i];
6676 		//-form is for backward compatibility
6677 		if (args[i] == "--root" || args[i] == "--master") hasExplicitRoot = true;
6678 		if (args[i] == "--line" && i + 1 < args.size()) {
6679 			QStringList lineCol = args[++i].split(":");
6680 			line = lineCol.at(0).toInt() - 1;
6681 			if (lineCol.count() >= 2) {
6682 				col = lineCol.at(1).toInt();
6683 				if ((col) < 0) col = 0;
6684 			}
6685 		}
6686 		if (args[i] == "--insert-cite" && i + 1 < args.size()) {
6687 			cite = args[++i];
6688 		}
6689         if (args[i] == "--texpath" && i + 1 < args.size()) {
6690             QString texPath=args[++i];
6691             buildManager.resetDefaultCommands(texPath);
6692         }
6693 #ifndef NO_POPPLER_PREVIEW
6694 		if (args[i] == "--pdf-viewer-only") pdfViewerOnly = true;
6695 		if (args[i] == "--page") page = args[++i].toInt() - 1;
6696 #endif
6697 	}
6698 
6699 #ifndef NO_POPPLER_PREVIEW
6700 	if (pdfViewerOnly) {
6701 		if (PDFDocument::documentList().isEmpty())
6702 			newPdfPreviewer();
6703 		foreach (PDFDocument *viewer, PDFDocument::documentList()) {
6704 			if (!filesToLoad.isEmpty()) viewer->loadFile(filesToLoad.first());
6705 			connect(viewer, SIGNAL(destroyed()), SLOT(deleteLater()));
6706 			viewer->show();
6707 			viewer->setFocus();
6708 			if (page != -1) viewer->goToPage(page);
6709 		}
6710 		hide();
6711         return;
6712 	}
6713 #endif
6714 
6715 	// execute command line
6716 	foreach (const QString &fileToLoad, filesToLoad) {
6717 		QFileInfo ftl(fileToLoad);
6718 		if (fileToLoad != "") {
6719 			if (ftl.exists()) {
6720 				if (ftl.suffix() == Session::fileExtension()) {
6721 					loadSession(ftl.filePath());
6722 				} else {
6723 					load(fileToLoad, hasExplicitRoot);
6724 				}
6725 			} else if (ftl.absoluteDir().exists()) {
6726 				fileNew(ftl.absoluteFilePath());
6727 				if (hasExplicitRoot) {
6728 					setExplicitRootDocument(currentEditorView()->getDocument());
6729 				}
6730 				//return ;
6731 			}
6732 		}
6733 	}
6734 
6735 	if (line != -1) {
6736 	        gotoLine(line, col, nullptr, QEditor::KeepSurrounding | QEditor::ExpandFold);
6737 		QTimer::singleShot(500, currentEditor(), SLOT(ensureCursorVisible())); //reshow cursor in case the windows size changes
6738 	}
6739 
6740 	if (!cite.isNull()) {
6741 		insertCitation(cite);
6742 	}
6743 
6744 #ifndef QT_NO_DEBUG
6745 	//execute test after command line is known
6746 	if (realCmdLine) { //only at start
6747         bool result=executeTests(args);
6748 
6749 	if (args.contains("--update-translations")) {
6750 	    generateAddtionalTranslations();
6751 	}
6752         if (args.contains("--auto-tests")) {
6753             if(result){
6754                 QTimer::singleShot(4000, this, SLOT(fileExit()));
6755             }else{
6756                 QTimer::singleShot(4000, this, SLOT(fileExitWithError()));
6757             }
6758         }
6759 	}
6760 #endif
6761 
6762 	if (realCmdLine) Guardian::summon();
6763     return;
6764 }
6765 /*!
6766  * \brief hide splash screen again
6767  */
hideSplash()6768 void Texstudio::hideSplash()
6769 {
6770 	if (splashscreen) splashscreen->hide();
6771 }
6772 /*!
6773  * \brief execute self tests
6774  * \param command line arguments which may influence the behavior of this method
6775  * options are:
6776  * --disable-tests  : no tests are run
6777  * --execute-tests  : tests are run even if they were executed already in a previous run
6778  * --execute-all-tests  : run tests, including some more time consuming ones
6779  * --auto-tests  : run a subset of tests which work on travis-ci
6780  * \return false if some tests failed
6781  */
executeTests(const QStringList & args)6782 bool Texstudio::executeTests(const QStringList &args)
6783 {
6784     QFileInfo myself(QCoreApplication::applicationFilePath());
6785     if (args.contains("--disable-tests")) return true; // pass
6786 #if !defined(QT_NO_DEBUG) && !defined(NO_TESTS)
6787     bool testResult=true; // pass, false -> fail
6788     bool autoTests = args.contains("--auto-tests");
6789     bool allTests = args.contains("--execute-all-tests")
6790             //execute all tests once a week or if command paramter is set
6791             || (configManager.debugLastFullTestRun.daysTo(myself.lastModified()) > 6);
6792     if(autoTests)
6793         allTests=false;
6794     if (args.contains("--execute-tests") || (configManager.debugLastFileModification.isValid() && myself.lastModified() != configManager.debugLastFileModification) || allTests || autoTests) {
6795         fileNew();
6796         if (!currentEditorView() || !currentEditorView()->editor){
6797             if(autoTests){
6798                 qDebug()<<"Autotest execution failed!";
6799                 return false;
6800             }else{
6801                 QMessageBox::critical(nullptr, "wtf?", "test failed", QMessageBox::Ok);
6802             }
6803         }
6804         if (allTests) configManager.debugLastFullTestRun = myself.lastModified();
6805 
6806         TestManager testManager;
6807         connect(&testManager, SIGNAL(newMessage(QString)), this, SLOT(showTestProgress(QString)));
6808         TestManager::TestLevel testLevel=allTests ? TestManager::TL_ALL : TestManager::TL_FAST;
6809         if(autoTests){
6810             testLevel=TestManager::TL_AUTO;
6811         }
6812         QString result = testManager.execute(testLevel, currentEditorView(), currentEditorView()->codeeditor, currentEditorView()->editor, &buildManager);
6813         if(autoTests){
6814             currentEditorView()->close();
6815             QStringList lines=result.split("\n");
6816             int cnt=0;
6817             foreach(const QString line,lines){
6818                 qDebug()<<line;
6819                 if(line.startsWith("FAIL")){
6820                     cnt++;
6821                 }
6822             }
6823             if(cnt>0){
6824                 qDebug()<<QString("%1 tests failed!").arg(cnt);
6825                 return false;
6826             }
6827         }else{
6828             m_languages->setLanguageFromName(currentEditorView()->editor, "TXS Test Results");
6829             currentEditorView()->editor->setText(result, false);
6830             if (result.startsWith("*** THERE SEEM TO BE FAILED TESTS! ***")) {
6831                 QSearchReplacePanel *searchpanel = qobject_cast<QSearchReplacePanel *>(currentEditorView()->codeeditor->panels("Search")[0]);
6832                 if (searchpanel) {
6833                     searchpanel->find("FAIL!", false, false, false, false, true);
6834                 }
6835             }
6836         }
6837         configManager.debugLastFileModification = QFileInfo(QCoreApplication::applicationFilePath()).lastModified();
6838     }
6839     return testResult; // pass
6840 #else
6841     return false;  // trying to execute tests, but tests are not available.
6842 #endif
6843 }
6844 
showTestProgress(const QString & message)6845 void Texstudio::showTestProgress(const QString &message)
6846 {
6847 	outputView->insertMessageLine(message);
6848     QApplication::processEvents(QEventLoop::ExcludeUserInputEvents | QEventLoop::ExcludeSocketNotifiers);
6849 }
6850 /*!
6851  * \brief notfication when left panel was switched
6852  * Mainly used to notice when global TOC/structureWidget becomes visible
6853  * \param widget
6854  */
leftPanelChanged(QWidget * widget)6855 void Texstudio::leftPanelChanged(QWidget *widget)
6856 {
6857        if(widget==topTOCTreeWidget){
6858            // update TOC when the TOC first becomes visisble
6859            updateTOC();
6860        }
6861        if(widget==structureTreeWidget){
6862            // update StructureWidget when the structure first becomes visisble
6863            updateStructureLocally();
6864        }
6865 }
6866 /*!
6867  * \brief generate translations for definition files
6868  * some command insertions are control via definition files, not c++ source code
6869  * This method reads in those commands and generate a pseudo sorce code (additionaltranslations.cpp) which can be used to generate translations
6870  * The translation for the pseudo code are used to do the translation of the commands in the definition files
6871  */
generateAddtionalTranslations()6872 void Texstudio::generateAddtionalTranslations()
6873 {
6874     qDebug()<<"writing translations for uiconfig.xml";
6875 	QStringList translations;
6876 	translations << "/******************************************************************************";
6877 	translations << " * Do not manually edit this file. It is automatically generated by a call to";
6878 	translations << " * texstudio --update-translations";
6879 	translations << " * This generates some additional translations which lupdate doesn't find";
6880         translations << " * (e.g. from uiconfig.xml, color names, qnfa format names and tags) ";
6881 	translations << " ******************************************************************************/";
6882 
6883 	translations << "#undef UNDEFINED";
6884 	translations << "#ifdef UNDEFINED";
6885 	translations << "static const char* translations[] = {";
6886 
6887 	QRegExp commandOnly("\\\\['`^\"~=.^]?[a-zA-Z]*(\\{\\})* *"); //latex command
6888 	//copy menu item text
6889 	QFile xmlFile(":/uiconfig.xml");
6890 	xmlFile.open(QIODevice::ReadOnly);
6891 	QDomDocument xml;
6892 	xml.setContent(&xmlFile);
6893 
6894 	CodeSnippet::debugDisableAutoTranslate = true;
6895 	QStringList tagNames = QStringList() << "menu" << "insert" << "action";
6896 	foreach (const QString &tag, tagNames) {
6897 		QDomNodeList nodes = xml.elementsByTagName(tag);
6898 		for(int i = 0; i < nodes.count(); i++)
6899 		{
6900 			QDomNode current = nodes.at(i);
6901 			QDomNamedNodeMap attribs = current.attributes();
6902 			QString text = attribs.namedItem("text").nodeValue();
6903 			if (!text.isEmpty() && !commandOnly.exactMatch(text))
6904 				translations << "QT_TRANSLATE_NOOP(\"ConfigManager\", \"" + text.replace("\\", "\\\\").replace("\"", "\\\"") + "\"), ";
6905 			QString insert = attribs.namedItem("insert").nodeValue();
6906 			if (!insert.isEmpty()) {
6907 				CodeSnippet cs(insert, false);
6908 				for (int i = 0; i < cs.placeHolders.size(); i++)
6909 					for (int j = 0; j < cs.placeHolders[i].size(); j++)
6910 						if (cs.placeHolders[i][j].flags & CodeSnippetPlaceHolder::Translatable)
6911 							translations << "QT_TRANSLATE_NOOP(\"CodeSnippet_PlaceHolder\", \"" + cs.lines[i].mid(cs.placeHolders[i][j].offset, cs.placeHolders[i][j].length) + "\"), ";
6912 			}
6913 		}
6914 	}
6915         // default formats
6916 	QFile xmlFile2(":/qxs/defaultFormats.qxf");
6917 	xmlFile2.open(QIODevice::ReadOnly);
6918 	xml.setContent(&xmlFile2);
6919 	QDomNodeList formats = xml.documentElement().elementsByTagName("format");
6920 	for (int i = 0; i < formats.size(); i++)
6921 		translations << "QT_TRANSLATE_NOOP(\"QFormatConfig\", \"" + formats.at(i).attributes().namedItem("id").nodeValue() + "\"), ";
6922 	translations << "QT_TRANSLATE_NOOP(\"QFormatConfig\", \"normal\"),";
6923 	for (int i = 0; i < configManager.managedToolBars.size(); i++)
6924 		translations << "QT_TRANSLATE_NOOP(\"Texstudio\",\"" + configManager.managedToolBars[i].name + "\"),";
6925 
6926         // Tags
6927         QDir dir("tags");
6928         QStringList l_fn=dir.entryList({"*.xml"});
6929         for(const QString &fn: l_fn){
6930             QFile xmlFile3("tags/"+fn);
6931             xmlFile3.open(QIODevice::ReadOnly);
6932             xml.setContent(&xmlFile3);
6933 
6934             QStringList tagNames = QStringList() << "section" << "item";
6935             foreach (const QString &tag, tagNames) {
6936                     QDomNodeList nodes = xml.elementsByTagName(tag);
6937                     for(int i = 0; i < nodes.count(); i++)
6938                     {
6939                             QDomNode current = nodes.at(i);
6940                             QDomNamedNodeMap attribs = current.attributes();
6941                             QString text = attribs.namedItem("txt").nodeValue();
6942                             if (!text.isEmpty() && !commandOnly.exactMatch(text)){
6943                                     translations << "QT_TRANSLATE_NOOP(\"XmlTagsListWidget\", \"" + text.replace("\\", "\\\\").replace("\"", "\\\"") + "\"), ";
6944                             }else{
6945                                 text = attribs.namedItem("title").nodeValue();
6946                                 if (!text.isEmpty()){
6947                                         translations << "QT_TRANSLATE_NOOP(\"XmlTagsListWidget\", \"" + text.replace("\\", "\\\\").replace("\"", "\\\"") + "\"), ";
6948                                 }
6949                             }
6950                             QString insert = attribs.namedItem("insert").nodeValue();
6951                             if (!insert.isEmpty()) {
6952                                     CodeSnippet cs(insert, false);
6953                                     for (int i = 0; i < cs.placeHolders.size(); i++)
6954                                             for (int j = 0; j < cs.placeHolders[i].size(); j++)
6955                                                     if (cs.placeHolders[i][j].flags & CodeSnippetPlaceHolder::Translatable)
6956                                                             translations << "QT_TRANSLATE_NOOP(\"CodeSnippet_PlaceHolder\", \"" + cs.lines[i].mid(cs.placeHolders[i][j].offset, cs.placeHolders[i][j].length) + "\"), ";
6957                             }
6958                     }
6959             }
6960         }
6961         CodeSnippet::debugDisableAutoTranslate = false;
6962         // format names
6963 	foreach (const QString &s, m_languages->languages())
6964 		translations << "QT_TRANSLATE_NOOP(\"Texstudio\", \"" + s + "\", \"Format name of language definition \"), ";
6965 
6966 	translations << "\"\"};";
6967 	translations << "#endif\n\n";
6968 
6969     QFile translationFile("src/additionaltranslations.cpp");
6970 	if (translationFile.open(QIODevice::WriteOnly)) {
6971 		translationFile.write(translations.join("\n").toLatin1());
6972 		translationFile.close();
6973 	}
6974     QFileInfo fi(translationFile);
6975     qDebug()<<"path:"<<fi.absoluteFilePath();
6976 }
6977 
onOtherInstanceMessage(const QString & msg)6978 void Texstudio::onOtherInstanceMessage(const QString &msg)   // Added slot for messages to the single instance
6979 {
6980 	show();
6981 	activateWindow();
6982 	executeCommandLine(msg.split("#!#"), false);
6983 }
6984 
setAutomaticRootDetection()6985 void Texstudio::setAutomaticRootDetection()
6986 {
6987         documents.setMasterDocument(nullptr);
6988 }
6989 
setExplicitRootDocument(LatexDocument * doc)6990 void Texstudio::setExplicitRootDocument(LatexDocument *doc)
6991 {
6992 	if (!doc) {
6993 		setAutomaticRootDetection();
6994 		return;
6995 	}
6996 	if (doc->getFileName().isEmpty() && doc->getEditorView()) {
6997 		editors->setCurrentEditor(doc->getEditorView());
6998 		fileSave();
6999 	}
7000 	if (doc->getFileName().isEmpty()) {
7001 		UtilsUi::txsWarning(tr("You have to save the file before it can be defined as root document."));
7002 		return;
7003 	}
7004 	documents.setMasterDocument(doc);
7005 }
7006 
setCurrentDocAsExplicitRoot()7007 void Texstudio::setCurrentDocAsExplicitRoot()
7008 {
7009 	if (currentEditorView()) {
7010 		setExplicitRootDocument(currentEditorView()->document);
7011 	}
7012 }
7013 
7014 ////////////////// VIEW ////////////////
gotoNextDocument()7015 void Texstudio::gotoNextDocument()
7016 {
7017 	// TODO check: can we have managed action connecting to the Editors slot directly? Then we could remove this slot
7018 	editors->activateNextEditor();
7019 }
7020 
gotoPrevDocument()7021 void Texstudio::gotoPrevDocument()
7022 {
7023 	// TODO check: can we have managed action connecting to the Editors slot directly? Then we could remove this slot
7024 	editors->activatePreviousEditor();
7025 }
7026 
gotoOpenDocument()7027 void Texstudio::gotoOpenDocument()
7028 {
7029 	QAction *act = qobject_cast<QAction *>(sender());
7030 	REQUIRE(act);
7031 	editors->setCurrentEditor(act->data().value<LatexEditorView *>());
7032 }
7033 
7034 /*!
7035  * Update the document menu. If only the name of the current file changed, use localChange = true
7036  * for a faster update.
7037  * Note: localChange is a low-cost variant which is basically there because updateCaption() calls this
7038  * far too often even when it's not necessary. The calling logic (in particular updateCaption and its
7039  * uses should be refactored).
7040  */
updateOpenDocumentMenu(bool localChange)7041 void Texstudio::updateOpenDocumentMenu(bool localChange)
7042 {
7043 	if (localChange) {
7044 		LatexEditorView *edView = currentEditorView();
7045 		QMenu *menu = configManager.getManagedMenu("main/view/documents");
7046 		if (!menu) return;
7047 		foreach (QAction *act, menu->actions()) {
7048 			if (edView == act->data().value<LatexEditorView *>()) {
7049 				act->setText(edView->displayNameForUI());
7050 				//qDebug() << "local SUCCESS" << act->text() << edView->displayName();
7051 				return;
7052 			}
7053 		}
7054 		//qDebug() << "local updateOpenDocumentMenu failed. Falling back to complete update.";
7055 		// if there was no editor for a local change, fall back to a complete update of the menu
7056 	}
7057 	QStringList names;
7058 	QList<QVariant> data;
7059 	foreach (LatexEditorView *edView, editors->editors()) {
7060 		names << edView->displayNameForUI();
7061 		data << QVariant::fromValue<LatexEditorView *>(edView);
7062 	}
7063 	//qDebug() << "complete" << names;
7064 	configManager.updateListMenu("main/view/documents", names, "doc", false, SLOT(gotoOpenDocument()), 0, true, 0, data);
7065 }
7066 
onEditorsReordered()7067 void Texstudio::onEditorsReordered()
7068 {
7069 	// we currently reorder the documents so that their order matches the order of editors
7070 	// this is purely conventional now (structure view inherits the order of the documents.)
7071 	// There is no technical necessity to align the order of editors and documents. We could drop
7072 	// this behavior in the future
7073 	QList<LatexDocument *> docs;
7074 	foreach (const LatexEditorView *edView, editors->editors()) {
7075 		docs.append(edView->getDocument());
7076 	}
7077 	documents.reorder(docs);
7078 }
7079 
focusEditor()7080 void Texstudio::focusEditor()
7081 {
7082 	raise();
7083 	activateWindow();
7084 	if (currentEditorView())
7085 		currentEditorView()->setFocus();
7086 }
7087 
focusViewer()7088 void Texstudio::focusViewer()
7089 {
7090 #ifndef NO_POPPLER_PREVIEW
7091 	QList<PDFDocument *> viewers = PDFDocument::documentList();
7092 	if (viewers.isEmpty()) return;
7093 
7094 	if (viewers.count() > 1 && currentEditorView()) {
7095 		// try: PDF for current file
7096 		QFileInfo currentFile = currentEditorView()->getDocument()->getFileInfo();
7097 		foreach (PDFDocument *viewer, viewers) {
7098 			if (viewer->getMasterFile() == currentFile) {
7099 				viewer->focus();
7100 				return;
7101 			}
7102 		}
7103 		// try: PDF for master file
7104 		LatexDocument *rootDoc = documents.getRootDocumentForDoc(nullptr);
7105 		if (rootDoc) {
7106 			QFileInfo masterFile = rootDoc->getFileInfo();
7107 			foreach (PDFDocument *viewer, viewers) {
7108 				if (viewer->getMasterFile() == masterFile) {
7109 					viewer->focus();
7110 					return;
7111 				}
7112 			}
7113 		}
7114 	}
7115 	// fall back to first
7116 	viewers.at(0)->focus();
7117 #endif
7118 }
7119 
viewCloseElement()7120 void Texstudio::viewCloseElement()
7121 {
7122 	if (fileSelector) {
7123 		fileSelector.data()->deleteLater();
7124 		return;
7125 	}
7126 	if (unicodeInsertionDialog) {
7127 		unicodeInsertionDialog->close();
7128 		return;
7129 	}
7130 	if (completer && completer->isVisible() && completer->close()) {
7131 		return;
7132 	}
7133     if (currentEditorView() && currentEditorView()->closeElement())
7134         return;
7135     if (getManagedAction("main/tools/stopcompile")->isEnabled()) {
7136         getManagedAction("main/tools/stopcompile")->trigger();
7137         return;
7138     }
7139 
7140 #ifndef NO_POPPLER_PREVIEW
7141 	// close element in focussed viewer
7142 	QWidget *w = QApplication::focusWidget();
7143 	while (w && !qobject_cast<PDFDocument *>(w))
7144 		w = w->parentWidget();
7145 
7146 	if (qobject_cast<PDFDocument *>(w)) {
7147 		PDFDocument *focusedPdf = qobject_cast<PDFDocument *>(w);
7148 		if (focusedPdf->embeddedMode) {
7149 			bool pdfClosed = focusedPdf->closeElement();
7150 			if (pdfClosed) {
7151 				focusEditor();
7152 			} else {
7153 				focusedPdf->widget()->setFocus();
7154 			}
7155 			return;
7156 		}
7157 
7158 	}
7159 #endif
7160 
7161 	if (textAnalysisDlg) {
7162 		textAnalysisDlg->close();
7163 		return;
7164 	}
7165 	if (outputView->isVisible() && configManager.useEscForClosingLog) {
7166 		outputView->hide();
7167 		return;
7168 	}
7169 #ifndef NO_POPPLER_PREVIEW
7170 	if (configManager.useEscForClosingEmbeddedViewer) {
7171 		foreach (PDFDocument *doc, PDFDocument::documentList()) {
7172 			if (doc->embeddedMode) {
7173 				doc->close();
7174 				return;
7175 			}
7176 		}
7177 	}
7178 #endif
7179 	if (windowState() == Qt::WindowFullScreen && configManager.useEscForClosingFullscreen) {
7180 		stateFullScreen = saveState(1);
7181 		setWindowState(Qt::WindowNoState);
7182 		restoreState(windowstate, 0);
7183 		fullscreenModeAction->setChecked(false);
7184 		return;
7185 	}
7186 
7187 	// easter egg
7188 	QTime ct = QTime::currentTime();
7189 	if (ct.second() % 5 != 0) return;
7190 	for (int i = 2; i < 63; i++) if (ct.minute() != i && ct.minute() % i  == 0) return;
7191 	QDate cd = QDate::currentDate();
7192 	const char * mode = "";
7193 	if (cd.month() == 12 && cd.day() >= 20) mode = "-santa";
7194 	else if ( (cd.month() == 10 && cd.day() >= 30) || (cd.month() == 11 && cd.day() == 1)) mode = "-witch";
7195     else if ( (cd.month() == 3 && cd.day() >= 22) || (cd.month() == 4 && cd.day() <= 25)) mode = "-easter";
7196 	UtilsUi::txsInformation(QString("<html><head></head><body><img src=':/images/egg%1.png'></body></html>").arg(mode));
7197 }
7198 
setFullScreenMode()7199 void Texstudio::setFullScreenMode()
7200 {
7201 	if (!fullscreenModeAction->isChecked()) {
7202 		stateFullScreen = saveState(1);
7203         if(tobemaximized){
7204             showMaximized();
7205         }else{
7206             showNormal();
7207         }
7208 		restoreState(windowstate, 0);
7209 	} else {
7210 		windowstate = saveState(0);
7211         tobemaximized=isMaximized();
7212 		showFullScreen();
7213 		restoreState(stateFullScreen, 1);
7214 	}
7215 }
7216 
viewSetHighlighting(QAction * act)7217 void Texstudio::viewSetHighlighting(QAction *act)
7218 {
7219 	if (!currentEditor()) return;
7220 	if (!m_languages->setLanguageFromName(currentEditor(), act->data().toString())) return;
7221 	currentEditorView()->clearOverlays();
7222 	configManager.recentFileHighlightLanguage.insert(getCurrentFileName(), act->data().toString());
7223 	if (configManager.recentFileHighlightLanguage.size() > configManager.recentFilesList.size()) {
7224 		QMap<QString, QString> recentFileHighlightLanguageNew;
7225 		foreach (QString fn, configManager.recentFilesList)
7226 			if (configManager.recentFileHighlightLanguage.contains(fn))
7227 				recentFileHighlightLanguageNew.insert(fn, configManager.recentFileHighlightLanguage.value(fn));
7228 		configManager.recentFileHighlightLanguage = recentFileHighlightLanguageNew;
7229 	}
7230 	// TODO: Check if reCheckSyntax is really necessary. Setting the language emits (among others) contentsChange(0, lines)
7231 	currentEditorView()->document->reCheckSyntax();
7232 }
7233 
showHighlightingMenu()7234 void Texstudio::showHighlightingMenu()
7235 {
7236 	// check active item just before showing the menu. So we don't have to keep track of the languages, e.g. when switching editors
7237 	if (!currentEditor()) return;
7238 	QLanguageDefinition *ld = currentEditor()->languageDefinition();
7239 	if (ld) {
7240 		foreach (QAction *act, highlightLanguageActions->actions()) {
7241 			if (act->data().toString() == ld->language()) {
7242 				act->setChecked(true);
7243 				break;
7244 			}
7245 		}
7246 	}
7247 }
7248 
viewCollapseBlock()7249 void Texstudio::viewCollapseBlock()
7250 {
7251 	if (!currentEditorView()) return;
7252 	currentEditorView()->foldBlockAt(false, currentEditorView()->editor->cursor().lineNumber());
7253 }
7254 
viewExpandBlock()7255 void Texstudio::viewExpandBlock()
7256 {
7257 	if (!currentEditorView()) return;
7258 	currentEditorView()->foldBlockAt(true, currentEditorView()->editor->cursor().lineNumber());
7259 }
7260 
pdfClosed()7261 void Texstudio::pdfClosed()
7262 {
7263 #ifndef NO_POPPLER_PREVIEW
7264 	PDFDocument *from = qobject_cast<PDFDocument *>(sender());
7265 	if (from) {
7266 		if (from->embeddedMode) {
7267 			shrinkEmbeddedPDFViewer(true);
7268 			QList<int> sz = mainHSplitter->sizes(); // set widths to 50%, eventually restore user setting
7269 			int sum = 0;
7270 			int last = 0;
7271 			foreach (int i, sz) {
7272 				sum += i;
7273 				last = i;
7274 			}
7275 			if (sum > 0)
7276 				pdfSplitterRel = 1.0 * last / sum;
7277 
7278 		}
7279 	}
7280 #endif
7281 }
7282 
7283 
7284 #ifndef NO_POPPLER_PREVIEW
newPdfPreviewer(bool embedded)7285 QObject *Texstudio::newPdfPreviewer(bool embedded)
7286 {
7287 	PDFDocument *pdfviewerWindow = new PDFDocument(configManager.pdfDocumentConfig, embedded);
7288     pdfviewerWindow->setToolbarIconSize(pdfviewerWindow->embeddedMode ? configManager.guiPDFToolbarIconSize : configManager.guiToolbarIconSize);
7289 	if (embedded) {
7290 		mainHSplitter->addWidget(pdfviewerWindow);
7291 		QList<int> sz = mainHSplitter->sizes(); // set widths to 50%, eventually restore user setting
7292 		int sum = 0;
7293 		foreach (int i, sz) {
7294 			sum += i;
7295 		}
7296 		sz.clear();
7297 		if (pdfSplitterRel < 0.1 || pdfSplitterRel > 0.9) //sanity check
7298 			pdfSplitterRel = 0.5;
7299 		sz << sum - qRound(pdfSplitterRel * sum);
7300 		sz << qRound(pdfSplitterRel * sum);
7301 		mainHSplitter->setSizes(sz);
7302 	}
7303 	connect(pdfviewerWindow, SIGNAL(triggeredAbout()), SLOT(helpAbout()));
7304 	connect(pdfviewerWindow, SIGNAL(triggeredEnlarge()), SLOT(enlargeEmbeddedPDFViewer()));
7305 	connect(pdfviewerWindow, SIGNAL(triggeredShrink()), SLOT(shrinkEmbeddedPDFViewer()));
7306 	connect(pdfviewerWindow, SIGNAL(triggeredManual()), SLOT(userManualHelp()));
7307 	connect(pdfviewerWindow, SIGNAL(documentClosed()), SLOT(pdfClosed()));
7308 	connect(pdfviewerWindow, SIGNAL(triggeredQuit()), SLOT(fileExit()));
7309 	connect(pdfviewerWindow, SIGNAL(triggeredConfigure()), SLOT(generalOptions()));
7310     connect(pdfviewerWindow, SIGNAL(syncSource(const QString&,int,bool,QString)), SLOT(syncFromViewer(const QString&,int,bool,QString)));
7311 	connect(pdfviewerWindow, SIGNAL(focusEditor()), SLOT(focusEditor()));
7312     connect(pdfviewerWindow, SIGNAL(runCommand(const QString&,const QFileInfo&,const QFileInfo&,int)), &buildManager, SLOT(runCommand(const QString&,const QFileInfo&,const QFileInfo&,int)));
7313 	connect(pdfviewerWindow, SIGNAL(triggeredClone()), SLOT(newPdfPreviewer()));
7314 
7315 	PDFDocument *from = qobject_cast<PDFDocument *>(sender());
7316 	if (from) {
7317 		pdfviewerWindow->loadFile(from->fileName(), from->getMasterFile(), PDFDocument::Raise | PDFDocument::Focus);
7318 		pdfviewerWindow->goToPage(from->widget()->getPageIndex());
7319 	}//load file before enabling sync or it will jump to the first page
7320 
7321 	foreach (PDFDocument *doc, PDFDocument::documentList()) {
7322 		if (doc == pdfviewerWindow) continue;
7323         connect(doc, SIGNAL(syncView(QString,QFileInfo,int)), pdfviewerWindow, SLOT(syncFromView(QString,QFileInfo,int)));
7324         connect(pdfviewerWindow, SIGNAL(syncView(QString,QFileInfo,int)), doc, SLOT(syncFromView(QString,QFileInfo,int)));
7325 	}
7326 	return pdfviewerWindow;
7327 }
7328 #endif
7329 
masterDocumentChanged(LatexDocument * doc)7330 void Texstudio::masterDocumentChanged(LatexDocument *doc)
7331 {
7332 	Q_UNUSED(doc)
7333 	Q_ASSERT(documents.singleMode() == !documents.masterDocument);
7334 	if (documents.singleMode()) {
7335 		outputView->resetMessagesAndLog();
7336 	} else {
7337 		configManager.addRecentFile(documents.masterDocument->getFileName(), true);
7338 		editors->moveEditor(doc->getEditorView(), Editors::GroupFront);
7339 	}
7340 
7341 	updateMasterDocumentCaption();
7342     updateStructureLocally();
7343 	completerNeedsUpdate();
7344 }
7345 
aboutToDeleteDocument(LatexDocument * doc)7346 void Texstudio::aboutToDeleteDocument(LatexDocument *doc)
7347 {
7348 	emit infoFileClosed();
7349 	editors->removeEditor(doc->getEditorView());
7350 	for (int i = configManager.completerConfig->userMacros.size() - 1; i >= 0; i--)
7351 		if (configManager.completerConfig->userMacros[i].document == doc)
7352 			configManager.completerConfig->userMacros.removeAt(i);
7353 }
7354 
7355 //*********************************
dragEnterEvent(QDragEnterEvent * event)7356 void Texstudio::dragEnterEvent(QDragEnterEvent *event)
7357 {
7358 	if (event->mimeData()->hasFormat("text/uri-list")) event->acceptProposedAction();
7359 }
7360 
dropEvent(QDropEvent * event)7361 void Texstudio::dropEvent(QDropEvent *event)
7362 {
7363 	QList<QUrl> uris = event->mimeData()->urls();
7364 
7365 	QStringList imageFormats = InsertGraphics::imageFormats();
7366 	saveCurrentCursorToHistory();
7367 
7368 	bool alreadyMovedCursor = false;
7369 	for (int i = 0; i < uris.length(); i++) {
7370 		QFileInfo fi = QFileInfo(uris.at(i).toLocalFile());
7371 		if (imageFormats.contains(fi.suffix().toLower()) && currentEditor()) {
7372 			if (!alreadyMovedCursor) {
7373 				QPoint p = currentEditor()->mapToContents(currentEditor()->mapToFrame(currentEditor()->mapFrom(this, event->pos())));
7374 				QDocumentCursor cur = currentEditor()->cursorForPosition(p);
7375 				cur.beginEditBlock();
7376 				if (!cur.atLineStart()) {
7377 					if (!cur.movePosition(1, QDocumentCursor::NextBlock, QDocumentCursor::MoveAnchor)) {
7378 						cur.movePosition(1, QDocumentCursor::EndOfBlock, QDocumentCursor::MoveAnchor);
7379 						cur.insertLine();
7380 					}
7381 				}
7382 				currentEditor()->setCursor(cur);
7383 				cur.endEditBlock();
7384 				alreadyMovedCursor = true;
7385 			}
7386 			quickGraphics(uris.at(i).toLocalFile());
7387 		} else if (fi.suffix() == Session::fileExtension()) {
7388 			loadSession(fi.filePath());
7389 		} else
7390 			load(fi.filePath());
7391 	}
7392 	event->acceptProposedAction();
7393 	raise();
7394 }
7395 
changeEvent(QEvent * e)7396 void Texstudio::changeEvent(QEvent *e)
7397 {
7398 	switch (e->type()) {
7399 	case QEvent::LanguageChange:
7400 		if (configManager.lastLanguage == configManager.language) return; //don't update if config not changed
7401 		//QMessageBox::information(0,"rt","retranslate",0);
7402 		if (!splashscreen) {
7403 			setupMenus();
7404 			setupDockWidgets();
7405 			updateCaption();
7406 			updateMasterDocumentCaption();
7407 		}
7408 		break;
7409 	default:
7410 		break;
7411 	}
7412 }
7413 
eventFilter(QObject * obj,QEvent * event)7414 bool Texstudio::eventFilter(QObject *obj, QEvent *event)
7415 {
7416     static const QColor beyondEndColor(255, 170, 0);
7417     static const QColor inAppendixColor(200, 230, 200);
7418     static const QColor missingFileColor(Qt::red);
7419 
7420 #ifdef Q_OS_WIN
7421     // workaround for ´+t bug
7422     if (event->type() == QEvent::ShortcutOverride) {
7423         QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
7424         QString key = keyEvent->text();
7425         if(keyEvent->modifiers()==Qt::NoModifier && key.length()==1 && (key=="´"||key.at(0).isLetter())){
7426             event->accept();
7427             return true;
7428         }
7429     }
7430 #endif
7431 
7432     if (event->type() == QEvent::ToolTip) {
7433         if(obj==structureTreeWidget || obj==topTOCTreeWidget){
7434             QHelpEvent *helpEvent = dynamic_cast<QHelpEvent *>(event);
7435             QTreeWidgetItem *item=structureTreeWidget->itemAt(helpEvent->pos());
7436             if(item){
7437                 StructureEntry *entry = item->data(0,Qt::UserRole).value<StructureEntry *>();
7438                 if(!entry)
7439                     return false;
7440                 QString text;
7441                 if (!entry->tooltip.isNull()) {
7442                     text=entry->tooltip;
7443                 }
7444                 if (entry->type == StructureEntry::SE_DOCUMENT_ROOT) {
7445                     text=QDir::toNativeSeparators(entry->document->getFileName());
7446                 }
7447                 if (entry->type == StructureEntry::SE_SECTION) {
7448 
7449                     QString htmlTitle = entry->title.toHtmlEscaped();
7450 
7451                     htmlTitle.replace(' ', "&nbsp;");  // repleacement: prevent line break
7452                     QString tooltip("<html><b>" + htmlTitle + "</b>");
7453                     if (entry->getCachedLineNumber() > -1)
7454                         tooltip.append("<br><i>" + tr("Line") + QString("</i>: %1").arg(entry->getRealLineNumber() + 1));
7455                     StructureEntry *se = labelForStructureEntry(entry);
7456                     if (se)
7457                         tooltip.append("<br><i>" + tr("Label") + "</i>: " + se->title);
7458                     if (documents.markStructureElementsBeyondEnd && entry->hasContext(StructureEntry::BeyondEnd)){
7459                         tooltip.append(QString("<br><font color=\"%1\">%2</font>").arg(beyondEndColor.darker(120).name(), tr("Beyond end of document.")));
7460                     }else{
7461                         if (documents.markStructureElementsInAppendix && entry->hasContext(StructureEntry::InAppendix))
7462                             tooltip.append(QString("<br><font color=\"%1\">%2</font>").arg(inAppendixColor.darker(120).name(), tr("In Appendix.")));
7463                     }
7464                     // show preview if file is loaded
7465                     if(LatexDocument *doc=entry->document){
7466                         int l=entry->getRealLineNumber();
7467                         tooltip += doc->exportAsHtml(doc->cursor(qMax(0, l - 2), 0, l + 2), true, true, 60);
7468                     }
7469                     tooltip.append("</html>");
7470                     text=tooltip;
7471                 }
7472                 if (entry->type == StructureEntry::SE_LABEL) {
7473 
7474                     QString htmlTitle = entry->title.toHtmlEscaped();
7475 
7476                     htmlTitle.replace(' ', "&nbsp;");  // repleacement: prevent line break
7477                     QString tooltip("<html><b>" + htmlTitle + "</b>");
7478                     if (entry->getCachedLineNumber() > -1)
7479                         tooltip.append("<br><i>" + tr("Line") + QString("</i>: %1").arg(entry->getRealLineNumber() + 1));
7480                     if (documents.markStructureElementsBeyondEnd && entry->hasContext(StructureEntry::BeyondEnd))
7481                         tooltip.append(QString("<br><font color=\"%1\">%2</font>").arg(beyondEndColor.darker(120).name(), tr("Beyond end of document.")));
7482                     if (documents.markStructureElementsInAppendix && entry->hasContext(StructureEntry::InAppendix))
7483                         tooltip.append(QString("<br><font color=\"%1\">%2</font>").arg(inAppendixColor.darker(120).name(), tr("In Appendix.")));
7484                     // show preview if file is loaded
7485                     if(LatexDocument *doc=entry->document){
7486                         int l=entry->getRealLineNumber();
7487                         tooltip += doc->exportAsHtml(doc->cursor(qMax(0, l - 2), 0, l + 2), true, true, 60);
7488                     }
7489                     tooltip.append("</html>");
7490                     text=tooltip;
7491                 }
7492                 if (entry->type == StructureEntry::SE_INCLUDE) {
7493 
7494                     QString htmlTitle = entry->title.toHtmlEscaped();
7495 
7496                     htmlTitle.replace(' ', "&nbsp;").replace('-', "&#8209;");  // repleacement: prevent line break
7497                     QString tooltip("<html><b>" + htmlTitle + "</b>");
7498                     if (entry->getCachedLineNumber() > -1)
7499                         tooltip.append("<br><i>" + tr("Line") + QString("</i>: %1").arg(entry->getRealLineNumber() + 1));
7500                     if (!entry->valid){
7501                         tooltip.append(QString("<br><font color=\"%1\">%2</font>").arg(missingFileColor.name(), tr("File not found.")));
7502                     }else{
7503                         // show preview if file is loaded
7504                         if(LatexDocument *doc=entry->document){
7505                             QString fileName=entry->title;
7506                             fileName=doc->getAbsoluteFilePath(fileName,".tex");
7507                             LatexDocument *incDoc = documents.findDocument(fileName);
7508                             if(incDoc){
7509                                 tooltip += incDoc->exportAsHtml(incDoc->cursor(0, 0,qMin(5,incDoc->lines()-1)), true, true, 60);
7510                             }
7511                         }
7512                     }
7513                     text=tooltip;
7514                 }
7515                 if (text.isEmpty() && entry->getCachedLineNumber() > -1)
7516                     text=entry->title + QString(tr(" (Line %1)").arg(entry->getRealLineNumber() + 1));
7517                 if(!text.isEmpty()){
7518                     QToolTip::showText(helpEvent->globalPos(),text);
7519                     event->accept();
7520                     return true;
7521                 }
7522             }
7523         }
7524 
7525 
7526     }
7527     return false;
7528 }
7529 
7530 typedef QPair<int, int> PairIntInt;
7531 
updateCompleter(LatexEditorView * edView)7532 void Texstudio::updateCompleter(LatexEditorView *edView)
7533 {
7534     CodeSnippetList words;
7535 
7536     if (configManager.parseBibTeX) documents.updateBibFiles();
7537 
7538     if (!edView)
7539         edView = currentEditorView();
7540 
7541     QList<LatexDocument *> docs;
7542     LatexParser ltxCommands = LatexParser::getInstance();
7543     LatexCompleterConfig *config = completer->getConfig();
7544 
7545     if (edView && edView->document) {
7546         // determine from which docs data needs to be collected
7547         docs = edView->document->getListOfDocs();
7548 
7549         // collect user commands and references
7550         foreach (LatexDocument *doc, docs) {
7551             QList<CodeSnippet> userList=doc->userCommandList();
7552             if(config){
7553                 CodeSnippetList::iterator it;
7554                 for(it=userList.begin();it!=userList.end();++it){
7555                     QList<QPair<int, int> >res = config->usage.values(it->index);
7556                     foreach (const PairIntInt &elem, res) {
7557                         if (elem.first == it->snippetLength) {
7558                             (*it).usageCount = elem.second;
7559                             break;
7560                         }
7561                     }
7562                 }
7563             }
7564             words.unite(userList);
7565             words.unite(doc->additionalCommandsList());
7566 
7567             ltxCommands.append(doc->ltxCommands);
7568         }
7569     }
7570 
7571     // collect user commands and references
7572     std::set<QString> collected_labels;
7573     foreach (const LatexDocument *doc, docs) {
7574         if(doc->labelItems().isEmpty())
7575             continue;
7576         QStringList lst=doc->labelItems();
7577         collected_labels.insert(lst.cbegin(),lst.cend());
7578     }
7579 
7580     /*foreach (const QString &refCommand, latexParser.possibleCommands["%ref"]) {
7581         QString temp = refCommand + "{%1}";
7582         CodeSnippetList wordsList;
7583         foreach (const QString &l, collected_labels)
7584             wordsList.insert(temp.arg(l));
7585 
7586 
7587         words.unite(wordsList);
7588     }*/
7589     if (configManager.parseBibTeX) {
7590         std::set<QString> bibIds;
7591 
7592         QStringList collected_mentionedBibTeXFiles;
7593         foreach (const LatexDocument *doc, docs) {
7594             collected_mentionedBibTeXFiles << doc->listOfMentionedBibTeXFiles();
7595         }
7596 
7597         for (int i = 0; i < collected_mentionedBibTeXFiles.count(); i++) {
7598             if (!documents.bibTeXFiles.contains(collected_mentionedBibTeXFiles[i])) {
7599                 qDebug("BibTeX-File %s not loaded", collected_mentionedBibTeXFiles[i].toLatin1().constData());
7600                 continue; //wtf?s
7601             }
7602             BibTeXFileInfo &bibTex = documents.bibTeXFiles[collected_mentionedBibTeXFiles[i]];
7603 
7604             // add citation to completer for direct citation completion
7605             bibIds.insert(bibTex.ids.cbegin(),bibTex.ids.cend());
7606         }
7607         //handle bibitem definitions
7608         foreach (const LatexDocument *doc, docs) {
7609             QStringList ids=doc->bibItems();
7610             bibIds.insert(ids.cbegin(),ids.cend());
7611         }
7612         completer->setAdditionalWords(bibIds, CT_CITATIONS);
7613     }
7614 
7615     completer->setAdditionalWords(collected_labels, CT_LABELS);
7616 
7617     completionBaseCommandsUpdated = false;
7618 
7619 
7620     completer->setAdditionalWords(words, CT_COMMANDS);
7621 
7622     // add keyval completion, add special lists
7623     foreach (const QString &elem, ltxCommands.possibleCommands.keys()) {
7624         if (elem.startsWith("key%")) {
7625             QString name = elem.mid(4);
7626             if (name.endsWith("#c"))
7627                 name.chop(2);
7628             if (!name.isEmpty()) {
7629                 completer->setKeyValWords(name, ltxCommands.possibleCommands[elem]);
7630             }
7631         }
7632         if (elem.startsWith("%") && latexParser.mapSpecialArgs.values().contains(elem)) {
7633             completer->setKeyValWords(elem, ltxCommands.possibleCommands[elem]);
7634         }
7635     }
7636     // add context completion
7637     if (config) {
7638         foreach (const QString &elem, config->specialCompletionKeys) {
7639             completer->setContextWords(ltxCommands.possibleCommands[elem], elem);
7640         }
7641     }
7642 
7643 
7644     if (edView) edView->viewActivated();
7645 
7646     GrammarCheck::staticMetaObject.invokeMethod(grammarCheck, "init", Qt::QueuedConnection, Q_ARG(LatexParser, latexParser), Q_ARG(GrammarCheckerConfig, *configManager.grammarCheckerConfig));
7647 
7648     mCompleterNeedsUpdate = false;
7649 }
7650 
outputPageChanged(const QString & id)7651 void Texstudio::outputPageChanged(const QString &id)
7652 {
7653 	if (id == outputView->LOG_PAGE && !outputView->getLogWidget()->logPresent()) {
7654 		if (!loadLog())
7655 			return;
7656 		if (hasLatexErrors())
7657 			viewLog();
7658 	}
7659 }
7660 
jumpToSearchResult(QDocument * doc,int lineNumber,const SearchQuery * query)7661 void Texstudio::jumpToSearchResult(QDocument *doc, int lineNumber, const SearchQuery *query)
7662 {
7663     REQUIRE(qobject_cast<LatexDocument *>(doc));
7664     if (currentEditor() && currentEditor()->document() == doc && currentEditor()->cursor().lineNumber() == lineNumber) {
7665         QDocumentCursor c = currentEditor()->cursor();
7666         int col = c.columnNumber();
7667         col = query->getNextSearchResultColumn(c.line().text() , col + 1);
7668         gotoLine(lineNumber, col);
7669     } else {
7670         gotoLine(lineNumber, doc->getFileName().size() ? doc->getFileName() : qobject_cast<LatexDocument *>(doc)->getTemporaryFileName());
7671         int col = query->getNextSearchResultColumn(currentEditor()->document()->line(lineNumber).text(), 0);
7672         gotoLine(lineNumber, col);
7673         outputView->showPage(outputView->SEARCH_RESULT_PAGE);
7674     }
7675     QDocumentCursor highlight = currentEditor()->cursor();
7676     highlight.movePosition(query->searchExpression().length(), QDocumentCursor::NextCharacter, QDocumentCursor::KeepAnchor);
7677     currentEditorView()->temporaryHighlight(highlight);
7678 }
7679 
gotoLine(int line,int col,LatexEditorView * edView,QEditor::MoveFlags mflags,bool setFocus)7680 void Texstudio::gotoLine(int line, int col, LatexEditorView *edView, QEditor::MoveFlags mflags, bool setFocus)
7681 {
7682     bool changeCurrentEditor = (edView != currentEditorView());
7683     if (!edView)
7684         edView = currentEditorView(); // default
7685 
7686     if (!edView || line < 0) return;
7687 
7688     saveCurrentCursorToHistory();
7689 
7690     if (changeCurrentEditor) {
7691         if (editors->containsEditor(edView)) {
7692             editors->setCurrentEditor(edView);
7693             mflags &= ~QEditor::Animated;
7694         } else {
7695             load(edView->getDocument()->getFileName());
7696         }
7697     }
7698     edView->editor->setCursorPosition(line, col, false);
7699     edView->editor->ensureCursorVisible(mflags);
7700     if (setFocus) {
7701         edView->editor->setFocus();
7702     }
7703 }
7704 
gotoLine(int line,const QString & fileName)7705 bool Texstudio::gotoLine(int line, const QString &fileName)
7706 {
7707     LatexEditorView *edView = getEditorViewFromFileName(fileName, true);
7708     QEditor::MoveFlags mflags = QEditor::Navigation;
7709     if (!edView) {
7710         if (!load(fileName)) return false;
7711         mflags &= ~QEditor::Animated;
7712     }
7713     gotoLine(line, 0, edView, mflags);
7714     return true;
7715 }
7716 
gotoLine(LatexDocument * doc,int line,int col)7717 void Texstudio::gotoLine(LatexDocument *doc, int line, int col)
7718 {
7719     if (!doc) return;
7720 
7721     LatexEditorView *edView = doc->getEditorView();
7722     if (edView) {
7723         gotoLine(line, col, edView);
7724     }
7725 }
7726 
7727 /*!
7728  * \brief jump to line given by TOC entry (topTOCTreeWidget)
7729  * \param item
7730  * \param col
7731  */
gotoLine(QTreeWidgetItem * item,int)7732 void Texstudio::gotoLine(QTreeWidgetItem *item, int)
7733 {
7734     StructureEntry *se=item->data(0,Qt::UserRole).value<StructureEntry *>();
7735     if(!se) return;
7736     const QList<StructureEntry::Type> lineTypes={StructureEntry::SE_SECTION,StructureEntry::SE_TODO,StructureEntry::SE_LABEL,StructureEntry::SE_MAGICCOMMENT};
7737     if(lineTypes.contains(se->type)){
7738         LatexEditorView *edView = se->document->getEditorView();
7739         if (edView) {
7740             gotoLine(se->getRealLineNumber(), 0, edView);
7741         }else{
7742             // going to hidden doc
7743             // relevant for hidden master document
7744             bool unmodified=se->document->isClean();
7745             openExternalFile(se->document->getFileName(),"tex",se->document);
7746             if(unmodified)
7747                 se->document->setClean(); // work-around, unclear where that state is reset during load
7748             LatexEditorView *edView = se->document->getEditorView();
7749             if (edView) {
7750                 gotoLine(se->getRealLineNumber(), 0, edView);
7751             }
7752         }
7753     }else{
7754         // unresolved include, go to open file
7755         if(se->type == StructureEntry::SE_DOCUMENT_ROOT){
7756             LatexEditorView *edView = se->document->getEditorView();
7757             if (!edView) return;
7758             editors->setCurrentEditor(edView);
7759         }
7760         if(se->type==StructureEntry::SE_INCLUDE || se->type==StructureEntry::SE_BIBTEX){
7761             saveCurrentCursorToHistory();
7762             QString defaultExt = se->type == StructureEntry::SE_BIBTEX ? ".bib" : ".tex";
7763             QString name=se->title;
7764             name.replace("\\string~",QDir::homePath());
7765             openExternalFile(name,defaultExt,se->document);
7766         }
7767     }
7768 }
7769 
gotoLogEntryEditorOnly(int logEntryNumber)7770 void Texstudio::gotoLogEntryEditorOnly(int logEntryNumber)
7771 {
7772 	if (logEntryNumber < 0 || logEntryNumber >= outputView->getLogWidget()->getLogModel()->count()) return;
7773 	LatexLogEntry entry = outputView->getLogWidget()->getLogModel()->at(logEntryNumber);
7774 	QString fileName = entry.file;
7775 	if (!activateEditorForFile(fileName, true))
7776 		if (!load(fileName)) return;
7777 	if (currentEditorView()->logEntryToLine.isEmpty()) {
7778 		updateLogEntriesInEditors();
7779 	}
7780 	if (configManager.showLogMarkersWhenClickingLogEntry) {
7781 		setLogMarksVisible(true);
7782 	}
7783 	//get line
7784 	QDocumentLineHandle *dlh = currentEditorView()->logEntryToLine.value(logEntryNumber, nullptr);
7785 	if (!dlh) return;
7786 	//goto
7787 	gotoLine(currentEditor()->document()->indexOf(dlh));
7788 	QDocumentCursor c = getLogEntryContextCursor(dlh, entry);
7789 	if (c.isValid()) {
7790 		currentEditorView()->editor->setCursor(c, false);
7791 	}
7792 }
7793 
7794 /*!
7795  * Returns a cursor marking the part of the line which the log entry is referring to.
7796  * This assumes that the cursor was already set to the correct line before calling the function.
7797  */
getLogEntryContextCursor(const QDocumentLineHandle * dlh,const LatexLogEntry & entry)7798 QDocumentCursor Texstudio::getLogEntryContextCursor(const QDocumentLineHandle *dlh, const LatexLogEntry &entry)
7799 {
7800     QRegularExpression rxUndefinedControlSequence("^Undefined\\ control\\ sequence.*(\\\\\\w+)$");
7801     QRegularExpression rxEnvironmentUndefined("^Environment (\\w+) undefined\\.");
7802     QRegularExpression rxReferenceMissing("^Reference `(\\w+)' on page (\\d+) undefined");
7803     QRegularExpression rxCitationMissing("^Citation `(\\w+)' on page (\\d+) undefined");
7804     QRegularExpressionMatch match;
7805     if (entry.message.indexOf(rxUndefinedControlSequence,0,&match) == 0) {
7806         QString cmd = match.captured(1);
7807 		int startCol = dlh->text().indexOf(cmd);
7808 		if (startCol >= 0) {
7809 			QDocumentCursor cursor = currentEditorView()->editor->cursor();
7810 			cursor.selectColumns(startCol, startCol + cmd.length());
7811 			return cursor;
7812 		}
7813     } else if (entry.message.indexOf(rxEnvironmentUndefined,0,&match) == 0) {
7814         QString env = match.captured(1);
7815 		int startCol = dlh->text().indexOf("\\begin{" + env + "}");
7816 		if (startCol >= 0) {
7817 			startCol += 7;  // length of \begin{
7818 			QDocumentCursor cursor = currentEditorView()->editor->cursor();
7819 			cursor.selectColumns(startCol, startCol + env.length());
7820 			return cursor;
7821 		}
7822     } else if (entry.message.indexOf(rxReferenceMissing,0,&match) == 0) {
7823 		int fid = currentEditorView()->document->getFormatId("referenceMissing");
7824 		foreach (const QFormatRange &fmtRange, dlh->getOverlays(fid)) {
7825             if (dlh->text().mid(fmtRange.offset, fmtRange.length) == match.captured(1)) {
7826 				QDocumentCursor cursor = currentEditorView()->editor->cursor();
7827 				cursor.selectColumns(fmtRange.offset, fmtRange.offset + fmtRange.length);
7828 				return cursor;
7829 			}
7830 		}
7831     } else if (entry.message.indexOf(rxCitationMissing,0,&match) == 0) {
7832 		int fid = currentEditorView()->document->getFormatId("citationMissing");
7833 		foreach (const QFormatRange &fmtRange, dlh->getOverlays(fid)) {
7834             if (dlh->text().mid(fmtRange.offset, fmtRange.length) == match.captured(1)) {
7835 				QDocumentCursor cursor = currentEditorView()->editor->cursor();
7836 				cursor.selectColumns(fmtRange.offset, fmtRange.offset + fmtRange.length);
7837 				return cursor;
7838 			}
7839 		}
7840 	} else {
7841 		// error messages that are followed by the context; e.g. Too many }'s. \textit{}}
7842 		QStringList messageStarts = QStringList() << "Too many }'s. " << "Missing $ inserted. ";
7843 		foreach (const QString &messageStart, messageStarts) {
7844 			if (entry.message.startsWith(messageStart)) {
7845 				QString context = entry.message.mid(messageStart.length());
7846 				int startCol = dlh->text().indexOf(context);
7847 				if (startCol >= 0) {
7848 					QDocumentCursor cursor = currentEditorView()->editor->cursor();
7849 					cursor.setColumnNumber(startCol += context.length());
7850 					return cursor;
7851 				}
7852 			}
7853 		}
7854 	}
7855 	return QDocumentCursor();
7856 }
7857 
gotoLogEntryAt(int newLineNumber)7858 bool Texstudio::gotoLogEntryAt(int newLineNumber)
7859 {
7860 	//goto line
7861 	if (newLineNumber < 0) return false;
7862 	gotoLine(newLineNumber);
7863 	//find error number
7864 	QDocumentLineHandle *lh = currentEditorView()->editor->document()->line(newLineNumber).handle();
7865 	int logEntryNumber = currentEditorView()->lineToLogEntries.value(lh, -1);
7866 	if (logEntryNumber == -1) return false;
7867 	//goto log entry
7868 	outputView->selectLogEntry(logEntryNumber);
7869 
7870     QPointF p = currentEditorView()->editor->mapToGlobal(currentEditorView()->editor->mapFromContents(currentEditorView()->editor->cursor().documentPosition().toPoint()));
7871 	//  p.ry()+=2*currentEditorView()->editor->document()->fontMetrics().lineSpacing();
7872 
7873 	REQUIRE_RET(outputView->getLogWidget()->getLogModel(), true);
7874 	QList<int> errors = currentEditorView()->lineToLogEntries.values(lh);
7875 	QString msg = outputView->getLogWidget()->getLogModel()->htmlErrorTable(errors);
7876 
7877     QToolTip::showText(p.toPoint(), msg, nullptr);
7878 	LatexEditorView::hideTooltipWhenLeavingLine = newLineNumber;
7879 	return true;
7880 }
7881 
gotoMark(bool backward,int id)7882 bool Texstudio::gotoMark(bool backward, int id)
7883 {
7884 	if (!currentEditorView()) return false;
7885 	if (backward)
7886 		return gotoLogEntryAt(currentEditorView()->editor->document()->findPreviousMark(id, qMax(0, currentEditorView()->editor->cursor().lineNumber() - 1), 0));
7887 	else
7888 		return gotoLogEntryAt(currentEditorView()->editor->document()->findNextMark(id, currentEditorView()->editor->cursor().lineNumber() + 1));
7889 }
7890 
findOccurencesApproximate(QString line,const QString & guessedWord)7891 QList<int> Texstudio::findOccurencesApproximate(QString line, const QString &guessedWord)
7892 {
7893 	QList<int> columns;
7894 
7895 	// exact match
7896 	columns = indicesOf(line, guessedWord);
7897 	if (columns.isEmpty()) columns = indicesOf(line, guessedWord, Qt::CaseSensitive);
7898 
7899 	QString changedWord = guessedWord;
7900 	if (columns.isEmpty()) {
7901 		//search again and ignore useless characters
7902 
7903 		QString regex;
7904 		for (int i = 0; i < changedWord.size(); i++)
7905 			if (changedWord[i].category() == QChar::Other_Control || changedWord[i].category() == QChar::Other_Format)
7906 				changedWord[i] = '\1';
7907 #if (QT_VERSION>=QT_VERSION_CHECK(5,14,0))
7908         foreach (const QString &x, changedWord.split('\1', Qt::SkipEmptyParts)){
7909             if (regex.isEmpty())
7910                 regex += QRegularExpression::escape(x);
7911             else
7912                 regex += ".{0,2}" + QRegularExpression::escape(x);
7913         }
7914 #else
7915 		foreach (const QString &x, changedWord.split('\1', QString::SkipEmptyParts))
7916 			if (regex.isEmpty()) regex += QRegExp::escape(x);
7917 			else regex += ".{0,2}" + QRegExp::escape(x);
7918 #endif
7919         QRegularExpression rx = QRegularExpression(regex);
7920 		columns = indicesOf(line, rx);
7921 		if (columns.isEmpty()) {
7922             rx.setPatternOptions(QRegularExpression::NoPatternOption);
7923 			columns = indicesOf(line, rx);
7924 		}
7925 	}
7926 	if (columns.isEmpty()) {
7927 		//search again and allow additional whitespace
7928 		QString regex;
7929 #if (QT_VERSION>=QT_VERSION_CHECK(5,14,0))
7930         foreach (const QString &x , changedWord.split(" ", Qt::SkipEmptyParts))
7931             if (regex.isEmpty()) regex = QRegExp::escape(x);
7932             else regex += "\\s+" + QRegExp::escape(x);
7933 #else
7934 		foreach (const QString &x , changedWord.split(" ", QString::SkipEmptyParts))
7935 			if (regex.isEmpty()) regex = QRegExp::escape(x);
7936 			else regex += "\\s+" + QRegExp::escape(x);
7937 #endif
7938         QRegularExpression rx = QRegularExpression(regex);
7939 		columns = indicesOf(line, rx);
7940 		if (columns.isEmpty()) {
7941             rx.setPatternOptions(QRegularExpression::NoPatternOption);
7942 			columns = indicesOf(line, rx);
7943 		}
7944 	}
7945 	if (columns.isEmpty()) {
7946 		int bestMatch = -1, bestScore = 0;
7947 		for (int i = 0; i < line.size() - guessedWord.size(); i++) {
7948 			int score = 0;
7949 			for (int c = i, s = 0; c < line.size() && s < guessedWord.size(); c++, s++) {
7950 				QChar C = line[c], S = guessedWord[s];
7951 				if (C == S) score += 5; //perfect match
7952 				else if (C.toLower() == S.toLower()) score += 2; //ok match
7953 				else if (C.isSpace()) s--; //skip spaces
7954 				else if (S.isSpace()) c--; //skip spaces
7955 				else if (S.category() == QChar::Other_Control || S.category() == QChar::Other_Format) {
7956 					for (s++; s < guessedWord.size() && (guessedWord[s].category() == QChar::Other_Control || guessedWord[s].category() == QChar::Other_Format); s++); //skip nonsense
7957 					if (s >= guessedWord.size()) continue;
7958 					if (guessedWord[s] == C) {
7959 						score += 5;
7960 						continue;
7961 					}
7962 					if (c + 1 < line.size() && guessedWord[s] == line[c + 1]) {
7963 						score += 5;
7964 						c++;
7965 						continue;
7966 					}
7967 					//also skip next character after that nonsense
7968 				}
7969 			}
7970 			if (score > bestScore){
7971 			    bestScore = score;
7972 			    bestMatch = i;
7973 			}
7974 		}
7975 		if (bestScore > guessedWord.size() * 5 / 3) columns.append(bestMatch); //accept if 0.33 similarity
7976 	}
7977 	return columns;
7978 }
7979 
syncFromViewer(const QString & fileName,int line,bool activate,const QString & guessedWord)7980 void Texstudio::syncFromViewer(const QString &fileName, int line, bool activate, const QString &guessedWord)
7981 {
7982 	if (!activateEditorForFile(fileName, true, activate)) {
7983 		QWidget *w = focusWidget();
7984 		bool success = load(fileName);
7985 		if (!activate)
7986 			w->setFocus();  // restore focus
7987 		if (!success) return;
7988 	}
7989 	shrinkEmbeddedPDFViewer();
7990 
7991 	QDocumentLine l = currentEditorView()->document->lineFromLineSnapshot(line);
7992 	if (l.isValid()) {
7993 		int originalLineNumber = currentEditorView()->document->indexOf(l, line);
7994 		if (originalLineNumber >= 0) line = originalLineNumber;
7995 	}
7996 
7997 	gotoLine(line, 0, nullptr, QEditor::Navigation, activate);
7998 	Q_ASSERT(currentEditor());
7999 
8000 	// guessedWord may appear multiple times -> we highlight them all
8001 	QList<int> columns = findOccurencesApproximate(currentEditor()->cursor().line().text(), guessedWord);
8002 
8003 	if (columns.isEmpty() || guessedWord.isEmpty()) {
8004 		// highlight complete line
8005 		QDocumentCursor highlight(currentEditor()->document(), line, 0);
8006 		highlight.movePosition(1, QDocumentCursor::EndOfLine, QDocumentCursor::KeepAnchor);
8007 		currentEditorView()->temporaryHighlight(highlight);
8008 	} else {
8009 		// highlight all found positions
8010 		QDocumentCursor highlight(currentEditor()->document(), line, 0);
8011 		foreach (int col, columns) {
8012 			int cursorCol = col + guessedWord.length() / 2;
8013 			highlight.setColumnNumber(cursorCol);
8014 			highlight.movePosition(1, QDocumentCursor::StartOfWord, QDocumentCursor::MoveAnchor);
8015 			highlight.movePosition(1, QDocumentCursor::EndOfWord, QDocumentCursor::KeepAnchor);
8016 			if (!highlight.hasSelection()) { // fallback, if we are not at a word
8017 				highlight.setColumnNumber(cursorCol);
8018 				highlight.movePosition(1, QDocumentCursor::PreviousCharacter, QDocumentCursor::MoveAnchor);
8019 				highlight.movePosition(1, QDocumentCursor::NextCharacter, QDocumentCursor::KeepAnchor);
8020 			}
8021 			currentEditor()->setCursorPosition(currentEditor()->cursor().lineNumber(), cursorCol, false);
8022 			currentEditor()->ensureCursorVisible(QEditor::KeepSurrounding | QEditor::ExpandFold);
8023 			currentEditorView()->temporaryHighlight(highlight);
8024 		}
8025 	}
8026 
8027 	if (activate) {
8028 		raise();
8029 		show();
8030 		activateWindow();
8031 		if (isMinimized()) showNormal();
8032 	}
8033 
8034 }
8035 
goBack()8036 void Texstudio::goBack()
8037 {
8038 	QDocumentCursor currentCur;
8039 	if (currentEditorView()) currentCur = currentEditorView()->editor->cursor();
8040 	setGlobalCursor(cursorHistory->back(currentCur));
8041 }
8042 
goForward()8043 void Texstudio::goForward()
8044 {
8045 	QDocumentCursor currentCur;
8046 	if (currentEditorView()) currentCur = currentEditorView()->editor->cursor();
8047 	setGlobalCursor(cursorHistory->forward(currentCur));
8048 }
8049 
setGlobalCursor(const QDocumentCursor & c)8050 void Texstudio::setGlobalCursor(const QDocumentCursor &c)
8051 {
8052 	if (c.isValid()) {
8053 		LatexDocument *doc = qobject_cast<LatexDocument *>(c.document());
8054 		if (doc && doc->getEditorView()) {
8055 			LatexEditorView *edView = doc->getEditorView();
8056 			QEditor::MoveFlags mflags = QEditor::KeepSurrounding | QEditor::ExpandFold;
8057 			if (edView == currentEditorView()) mflags |= QEditor::Animated;
8058 			editors->setCurrentEditor(edView);
8059 			edView->editor->setFocus();
8060 			edView->editor->setCursor(c, false);
8061 			edView->editor->ensureCursorVisible(mflags);
8062 		}
8063 	}
8064 }
8065 
fuzzBackForward()8066 void Texstudio::fuzzBackForward()
8067 {
8068 #ifdef NOT_DEFINED__FUZZER_NEEDED_ONLY_FOR_DEBUGGING_RANDOM_CRASH_OF_CURSOR_HISTORY
8069 	int rep = random() % (1 + cursorHistory->count());
8070 	for (int j = 0; j < rep; j++) goBack();
8071 	rep = random() % (1 + cursorHistory->count());
8072 	for (int j = 0; j < rep; j++) goForward();
8073 #endif
8074 }
8075 
setBuildButtonsDisabled(bool c)8076 void Texstudio::setBuildButtonsDisabled(bool c)
8077 {
8078     getManagedAction("main/tools/stopcompile")->setEnabled(c);
8079     getManagedAction("main/tools/quickbuild")->setEnabled(!c);
8080     getManagedAction("main/tools/compile")->setEnabled(!c);
8081 }
8082 
fuzzCursorHistory()8083 void Texstudio::fuzzCursorHistory()
8084 {
8085 #ifdef NOT_DEFINED__FUZZER_NEEDED_ONLY_FOR_DEBUGGING_RANDOM_CRASH_OF_CURSOR_HISTORY
8086 	QString fillText;
8087 	for (int i = 0; i < 100; i++)
8088 		fillText += "\n" + QString("foobar abc xyz").repeated(random() % 100);
8089 	for (int i = 0; i < 100; i++) {
8090 		if (!documents.documents.isEmpty()) {
8091 			if (random() % 1000 < 500) documents.deleteDocument(documents.documents[random() % documents.documents.length()]);
8092 			fuzzBackForward();
8093 		}
8094 		if (!documents.documents.isEmpty()) {
8095 			QApplication::processEvents();
8096 			if (random() % 1000 < 500) documents.deleteDocument(documents.documents[random() % documents.documents.length()]);
8097 			fuzzBackForward();
8098 		}
8099 		fileNew();
8100 		currentEditor()->setText(fillText);
8101 		QApplication::processEvents();
8102 		int rep = random() % 100;
8103 		for (int j = 0; j < rep; j++) {
8104 			EditorView->setCurrentIndex(EditorView->count());
8105 			int l =  random() % currentEditor()->document()->lineCount();
8106 			int c = random() % (currentEditor()->document()->line(l).length() + 100);
8107 			currentEditor()->setCursor(currentEditor()->document()->cursor(l, c, random() % 100, random() % 100));
8108 			saveCurrentCursorToHistory();
8109 			fuzzBackForward();
8110 		}
8111 	}
8112 #endif
8113 }
8114 
saveCurrentCursorToHistory()8115 void Texstudio::saveCurrentCursorToHistory()
8116 {
8117 	saveEditorCursorToHistory(currentEditorView());
8118 }
8119 
saveEditorCursorToHistory(LatexEditorView * edView)8120 void Texstudio::saveEditorCursorToHistory(LatexEditorView *edView)
8121 {
8122 	if (!edView) return;
8123 	cursorHistory->insertPos(edView->editor->cursor());
8124 }
8125 
previewLatex()8126 void Texstudio::previewLatex()
8127 {
8128 	if (!currentEditorView()) return;
8129 	// get selection
8130 	QDocumentCursor c = currentEditorView()->editor->cursor();
8131 	QDocumentCursor previewc;
8132 	if (c.hasSelection()) {
8133 		previewc = c; //X o riginalText = c.selectedText();
8134 	} else {
8135 		// math context
8136 		QSet<int> mathFormats = QSet<int>() << m_formats->id("numbers") << m_formats->id("math-keyword") << m_formats->id("align-ampersand");
8137 		QSet<int> lineEndFormats = QSet<int>() << m_formats->id("keyword") /* newline char */ << m_formats->id("comment");
8138 		mathFormats.remove(0); // keep only valid entries in list
8139 		lineEndFormats.remove(0);
8140 		previewc = currentEditorView()->findFormatsBegin(c, mathFormats, lineEndFormats);
8141 		previewc = currentEditorView()->parenthizedTextSelection(previewc);
8142 	}
8143 	if (!previewc.hasSelection()) {
8144 		// special handling for cusor in the middle of \[ or \]
8145 		if (c.previousChar() == '\\' && (c.nextChar() == '[' || c.nextChar() == ']')) {
8146 			c.movePosition(1, QDocumentCursor::PreviousCharacter);
8147 			previewc = currentEditorView()->parenthizedTextSelection(c);
8148 		}
8149 	}
8150         if (!previewc.hasSelection()) {
8151             // in environment delimiter (\begin{env} or \end{env})
8152             QString command;
8153             Token tk = Parsing::getTokenAtCol(c.line().handle(), c.columnNumber());
8154             if (tk.type != Token::none)
8155                 command = tk.getText();
8156             if (tk.type == Token::env || tk.type == Token::beginEnv ) {
8157                 TokenList tl = c.line().handle()->getCookieLocked(QDocumentLine::LEXER_COOKIE).value<TokenList>();
8158                 tk=Parsing::getCommandTokenFromToken(tl,tk);
8159                 c.setColumnNumber(tk.start);
8160                 previewc = currentEditorView()->parenthizedTextSelection(c);
8161             }
8162             if (tk.type == Token::command && (command == "\\begin" || command == "\\end")) {
8163                 c.setColumnNumber(tk.start);
8164                 previewc = currentEditorView()->parenthizedTextSelection(c);
8165             }
8166         }
8167         if (!previewc.hasSelection()) {
8168             // already at parenthesis
8169             previewc = currentEditorView()->parenthizedTextSelection(currentEditorView()->editor->cursor());
8170         }
8171 	if (!previewc.hasSelection()) return;
8172 
8173 	showPreview(previewc, true);
8174 
8175 }
8176 
previewAvailable(const QString & imageFile,const PreviewSource & source)8177 void Texstudio::previewAvailable(const QString &imageFile, const PreviewSource &source)
8178 {
8179 	QPixmap pixmap;
8180     qreal devPixelRatio = 1.0;
8181 
8182     devPixelRatio = devicePixelRatio();
8183 
8184 	double scale = configManager.segmentPreviewScalePercent / 100.;
8185 	double min = 0.2;
8186 	double max = 100;
8187 	scale = qMax(min, qMin(max, scale)) * devPixelRatio;
8188 	bool fromPDF = false;
8189 
8190 #ifndef NO_POPPLER_PREVIEW
8191 	fromPDF = imageFile.toLower().endsWith(".pdf");
8192 	if (fromPDF) {
8193 		// special treatment for pdf files (embedded pdf mode)
8194 		if (configManager.previewMode == ConfigManager::PM_EMBEDDED) {
8195 			runInternalCommand("txs:///view-pdf-internal", QFileInfo(imageFile), "--embedded");
8196 			if (currentEditorView())
8197 				currentEditorView()->setFocus();
8198 			return;
8199 		} else {
8200             std::unique_ptr<Poppler::Document> document(Poppler::Document::load(imageFile));
8201             if (!document)
8202                 return;
8203             std::unique_ptr<Poppler::Page> page(document->page(0));
8204             if (!page)
8205                 return;
8206 			document->setRenderHint(Poppler::Document::Antialiasing);
8207 			document->setRenderHint(Poppler::Document::TextAntialiasing);
8208 			double c = 1.25;  // empirical correction factor because pdf images are smaller than dvipng images. TODO: is logicalDpiX correct?
8209             pixmap = QPixmap::fromImage(page->renderToImage(qRound(scale*logicalDpiX() * c), qRound(scale*logicalDpiY() * c)));
8210             previewCache.insert(source.text,pixmap);
8211 		}
8212 	}
8213 #endif
8214 	if (!fromPDF) {
8215         if(imageFile.isEmpty()){
8216             // cached
8217             previewCache.find(source.text,&pixmap);
8218             fromPDF=true;
8219         }else{
8220             pixmap.load(imageFile);
8221             previewCache.insert(source.text,pixmap);
8222             if (scale < 0.99 || 1.01 < scale) {
8223                 // TODO: this does scale the pixmaps, but it would be better to render higher resolution images directly in the compilation process.
8224                 pixmap = pixmap.scaledToWidth(qRound(scale*pixmap.width()), Qt::SmoothTransformation);
8225             }
8226         }
8227 	}
8228 
8229     if (devPixelRatio > 1.01 || devPixelRatio<0.99) {
8230 		pixmap.setDevicePixelRatio(devPixelRatio);
8231 	}
8232 
8233 	if (configManager.previewMode == ConfigManager::PM_BOTH ||
8234 	        configManager.previewMode == ConfigManager::PM_PANEL ||
8235 	        (configManager.previewMode == ConfigManager::PM_TOOLTIP_AS_FALLBACK && outputView->isPreviewPanelVisible())) {
8236 		outputView->showPage(outputView->PREVIEW_PAGE);
8237 		outputView->previewLatex(pixmap);
8238 	}
8239 	if (configManager.previewMode == ConfigManager::PM_BOTH ||
8240 	        configManager.previewMode == ConfigManager::PM_TOOLTIP ||
8241             (source.atCursor && configManager.previewMode == ConfigManager::PM_INLINE)  || // respect preview setting, except for INLINE
8242 	        (configManager.previewMode == ConfigManager::PM_TOOLTIP_AS_FALLBACK && !outputView->isPreviewPanelVisible()) ||
8243             (source.fromLine < 0 && !source.atCursor)) { // completer preview
8244         QPointF p;
8245 		if (source.atCursor)
8246 			p = currentEditorView()->getHoverPosistion();
8247 		else
8248             p = currentEditorView()->editor->mapToGlobal(currentEditorView()->editor->mapFromContents(currentEditorView()->editor->cursor().documentPosition().toPoint()));
8249 
8250 		QRect screen = QGuiApplication::primaryScreen()->geometry();
8251 		int w = pixmap.width();
8252 		if (w > screen.width()) w = screen.width() - 2;
8253         int w_calculated=qRound(1.0*w / devPixelRatio); //pixmap shown with reduced width to be pixel perfect again
8254 		if (!fromPDF) {
8255                 QToolTip::showText(p.toPoint(), QString("<img src=\"" + imageFile + "\" width=%1 />").arg(w_calculated), nullptr);
8256 		} else {
8257 			QString text;
8258 
8259             text = getImageAsText(pixmap, w_calculated);
8260 
8261 			if (completerPreview) {
8262 				completerPreview = false;
8263 				completer->showTooltip(text);
8264 			} else {
8265                     QToolTip::showText(p.toPoint(), text, nullptr);
8266 			}
8267 		}
8268 		LatexEditorView::hideTooltipWhenLeavingLine = currentEditorView()->editor->cursor().lineNumber();
8269 	}
8270 	if (configManager.previewMode == ConfigManager::PM_INLINE && source.fromLine >= 0) {
8271 		QDocument *doc = currentEditor()->document();
8272 		doc->setForceLineWrapCalculation(true);
8273 		int toLine = qBound(0, source.toLine, doc->lines() - 1);
8274 		for (int l = source.fromLine; l <= toLine; l++ )
8275 			if (doc->line(l).getCookie(QDocumentLine::PICTURE_COOKIE).isValid()) {
8276 				doc->line(l).removeCookie(QDocumentLine::PICTURE_COOKIE);
8277 				doc->line(l).removeCookie(QDocumentLine::PICTURE_COOKIE_DRAWING_POS);
8278 				doc->line(l).setFlag(QDocumentLine::LayoutDirty);
8279 				if (l != toLine) //must not adjust line toLine here, or will recalculate the document height without preview and scroll away if the preview is very height
8280 					doc->adjustWidth(l);
8281 			}
8282 		doc->line(toLine).setCookie(QDocumentLine::PICTURE_COOKIE, QVariant::fromValue<QPixmap>(pixmap));
8283 		doc->line(toLine).setFlag(QDocumentLine::LayoutDirty);
8284 		doc->adjustWidth(toLine);
8285 	}
8286 }
8287 
clearPreview()8288 void Texstudio::clearPreview()
8289 {
8290 	QEditor *edit = currentEditor();
8291 	if (!edit) return;
8292 
8293 	int startLine = 0;
8294 	int endLine = 0;
8295 
8296 	QAction *act = qobject_cast<QAction *>(sender());
8297 	if (act && act->data().isValid()) {
8298 		// inline preview context menu supplies the calling point in doc coordinates as data
8299 		startLine = edit->document()->indexOf(edit->lineAtPosition(act->data().toPoint()));
8300 		// slight performance penalty for use of lineNumber(), which is not stictly necessary because
8301 		// we convert it back to a QDocumentLine, but easier to handle together with the other cases
8302 		endLine = startLine;
8303 		act->setData(QVariant());
8304 	} else if (edit->cursor().hasSelection()) {
8305 		startLine = edit->cursor().selectionStart().lineNumber();
8306 		endLine = edit->cursor().selectionEnd().lineNumber();
8307 	} else {
8308 		startLine = edit->cursor().lineNumber();
8309 		endLine = startLine;
8310 	}
8311 
8312         for (int i = startLine; i <= endLine; i++) {
8313             edit->document()->line(i).removeCookie(QDocumentLine::PICTURE_COOKIE);
8314             edit->document()->line(i).removeCookie(QDocumentLine::PICTURE_COOKIE_DRAWING_POS);
8315             edit->document()->adjustWidth(i);
8316             for (int j = currentEditorView()->autoPreviewCursor.size() - 1; j >= 0; j--)
8317                 if (currentEditorView()->autoPreviewCursor[j].selectionStart().lineNumber() <= i &&
8318                         currentEditorView()->autoPreviewCursor[j].selectionEnd().lineNumber() >= i) {
8319                     // remove cookies from last previewed line
8320                     int el=currentEditorView()->autoPreviewCursor[j].selectionEnd().lineNumber();
8321                     edit->document()->line(el).removeCookie(QDocumentLine::PICTURE_COOKIE);
8322                     edit->document()->line(el).removeCookie(QDocumentLine::PICTURE_COOKIE_DRAWING_POS);
8323                     // remove mark
8324                     int sid = edit->document()->getFormatId("previewSelection");
8325                     if (!sid) return;
8326                     updateEmphasizedRegion(currentEditorView()->autoPreviewCursor[j], -sid);
8327                     currentEditorView()->autoPreviewCursor.removeAt(j);
8328                     if(el>endLine){
8329                         edit->document()->adjustWidth(el); // text line with preview picture needs to be resized
8330                     }
8331                 }
8332 
8333         }
8334 }
8335 
showImgPreview(const QString & fname)8336 void Texstudio::showImgPreview(const QString &fname)
8337 {
8338 	completerPreview = (sender() == completer); // completer needs signal as answer
8339 	QString imageName = fname;
8340 	QFileInfo fi(fname);
8341 	QString suffix;
8342 	QStringList suffixList;
8343 	suffixList << "jpg" << "jpeg" << "png" << "pdf";
8344 	if (fi.exists()) {
8345 		if (!suffixList.contains(fi.suffix()))
8346 			return;
8347 		suffix = fi.suffix();
8348 	}
8349 
8350 	if (suffix.isEmpty()) {
8351 		foreach (QString elem, suffixList) {
8352 			imageName = fname + elem;
8353 			fi.setFile(imageName);
8354 			if (fi.exists()) {
8355 				suffix = elem;
8356 				break;
8357 			}
8358 		}
8359 	}
8360 
8361 	suffixList.clear();
8362 	suffixList << "jpg" << "jpeg" << "png";
8363 	if (suffixList.contains(suffix)) {
8364 		QPoint p;
8365 		//if(previewEquation)
8366 		p = currentEditorView()->getHoverPosistion();
8367 		//else
8368 		//    p=currentEditorView()->editor->mapToGlobal(currentEditorView()->editor->mapFromContents(currentEditorView()->editor->cursor().documentPosition()));
8369 		QRect screen = QGuiApplication::primaryScreen()->geometry();
8370 		QPixmap img(imageName);
8371 		int w = qMin(img.width(), configManager.editorConfig->maxImageTooltipWidth);
8372 		w = qMin(w, screen.width() - 8);
8373 		QString text = QString("<img src=\"" + imageName + "\" width=%1 />").arg(w);
8374 		if (completerPreview) {
8375 			completerPreview = false;
8376 			emit imgPreview(text);
8377 		} else {
8378 		        QToolTip::showText(p, text, nullptr);
8379 			LatexEditorView::hideTooltipWhenLeavingLine = currentEditorView()->editor->cursor().lineNumber();
8380 		}
8381 	}
8382 #ifndef NO_POPPLER_PREVIEW
8383 	if (suffix == "pdf") {
8384 		//render pdf preview
8385 		PDFRenderManager *renderManager = new PDFRenderManager(this, 1);
8386 		PDFRenderManager::Error error = PDFRenderManager::NoError;
8387 		QSharedPointer<Poppler::Document> document = renderManager->loadDocument(imageName, error, "");
8388 		if (error == PDFRenderManager::NoError) {
8389 			renderManager->renderToImage(0, this, "showImgPreviewFinished", 20, 20, -1, -1, -1, -1, false, true);
8390 		} else {
8391 			delete renderManager;
8392 		}
8393 	}
8394 #endif
8395 }
8396 
showImgPreviewFinished(const QPixmap & pm,int page)8397 void Texstudio::showImgPreviewFinished(const QPixmap &pm, int page)
8398 {
8399 	if (!currentEditorView()) return;
8400 	Q_UNUSED(page)
8401 	QPoint p;
8402 	//if(previewEquation)
8403 	p = currentEditorView()->getHoverPosistion();
8404 	//else
8405 	//    p=currentEditorView()->editor->mapToGlobal(currentEditorView()->editor->mapFromContents(currentEditorView()->editor->cursor().documentPosition()));
8406 	QRect screen = QGuiApplication::primaryScreen()->geometry();
8407 	int w = pm.width();
8408 	if (w > screen.width()) w = screen.width() - 2;
8409 	QString text;
8410 	text = getImageAsText(pm, w);
8411 
8412 	if (completerPreview) {
8413 		emit imgPreview(text);
8414 	} else {
8415 	        QToolTip::showText(p, text, nullptr);
8416 		LatexEditorView::hideTooltipWhenLeavingLine = currentEditorView()->editor->cursor().lineNumber();
8417 	}
8418 #ifndef NO_POPPLER_PREVIEW
8419 	PDFRenderManager *renderManager = qobject_cast<PDFRenderManager *>(sender());
8420 	delete renderManager;
8421 #endif
8422 }
8423 
showPreview(const QString & text)8424 void Texstudio::showPreview(const QString &text)
8425 {
8426 	completerPreview = (sender() == completer); // completer needs signal as answer
8427 	LatexEditorView *edView = getEditorViewFromFileName(documents.getCompileFileName()); //todo: temporary compi
8428 	if (!edView)
8429 		edView = currentEditorView();
8430 	if (!edView) return;
8431 	int m_endingLine = edView->editor->document()->findLineContaining("\\begin{document}", 0, Qt::CaseSensitive);
8432 	if (m_endingLine < 0) return; // can't create header
8433     // use cache if available
8434     QPixmap pm;
8435     if(previewCache.find(text,&pm)){
8436         previewAvailable("",PreviewSource(text, -1, -1, true));
8437         return;
8438     }
8439 	QStringList header;
8440 	for (int l = 0; l < m_endingLine; l++)
8441 		header << edView->editor->document()->line(l).text();
8442 	if (buildManager.dvi2pngMode == BuildManager::DPM_EMBEDDED_PDF) {
8443 		header << "\\usepackage[active,tightpage]{preview}"
8444 		       << "\\usepackage{varwidth}"
8445 		       << "\\AtBeginDocument{\\begin{preview}\\begin{varwidth}{\\linewidth}}"
8446 		       << "\\AtEndDocument{\\end{varwidth}\\end{preview}}";
8447 	}
8448 	header << "\\pagestyle{empty}";// << "\\begin{document}";
8449 	buildManager.preview(header.join("\n"), PreviewSource(text, -1, -1, true), documents.getCompileFileName(), edView->editor->document()->codec());
8450 }
8451 
showPreview(const QDocumentCursor & previewc)8452 void Texstudio::showPreview(const QDocumentCursor &previewc)
8453 {
8454 	if (previewQueueOwner != currentEditorView())
8455 		previewQueue.clear();
8456 	previewQueueOwner = currentEditorView();
8457 	previewQueue.insert(previewc.lineNumber());
8458 
8459 	// mark region which is previewed, or update
8460 	int sid = previewc.document()->getFormatId("previewSelection");
8461 	if (sid)
8462 		updateEmphasizedRegion(previewc, sid);
8463 
8464 	  previewDelayTimer.start(qMax(40, configManager.autoPreviewDelay));
8465     //QTimer::singleShot(qMax(40, configManager.autoPreviewDelay), this, SLOT(showPreviewQueue())); //slow down or it could create thousands of images
8466 }
8467 
showPreview(const QDocumentCursor & previewc,bool addToList)8468 void Texstudio::showPreview(const QDocumentCursor &previewc, bool addToList)
8469 {
8470 	REQUIRE(currentEditor());
8471 	REQUIRE(previewc.document() == currentEditor()->document());
8472 
8473 	QString originalText = previewc.selectedText();
8474 	if (originalText == "") return;
8475 	// get document definitions
8476 	//preliminary code ...
8477 	const LatexDocument *rootDoc = documents.getRootDocumentForDoc();
8478 	if (!rootDoc) return;
8479 	QStringList header = makePreviewHeader(rootDoc);
8480 	if (header.isEmpty()) return;
8481 	PreviewSource ps(originalText, previewc.selectionStart().lineNumber(), previewc.selectionEnd().lineNumber(), false);
8482 	buildManager.preview(header.join("\n"), ps,  documents.getCompileFileName(), rootDoc->codec());
8483 
8484 	if (!addToList)
8485 		return;
8486 
8487 	if (configManager.autoPreview == ConfigManager::AP_PREVIOUSLY) {
8488 		QList<QDocumentCursor> &clist = currentEditorView()->autoPreviewCursor;
8489 		int sid = previewc.document()->getFormatId("previewSelection");
8490 		for (int i = clist.size() - 1; i >= 0; i--)
8491 			if (clist[i].anchorLineNumber() <= ps.toLine &&
8492 			        clist[i].lineNumber()   >= ps.fromLine) {
8493 				if (sid > 0)
8494 					updateEmphasizedRegion(clist[i], -sid);
8495 				clist.removeAt(i);
8496 			}
8497 
8498 		QDocumentCursor ss = previewc.selectionStart();
8499 		QDocumentCursor se = previewc.selectionEnd();
8500 		QDocumentCursor c(ss, se);
8501 		c.setAutoUpdated(true);
8502 		currentEditorView()->autoPreviewCursor.insert(0, c);
8503 		// mark region
8504 		if (sid)
8505 			updateEmphasizedRegion(c, sid);
8506 	}
8507 }
8508 
makePreviewHeader(const LatexDocument * rootDoc)8509 QStringList Texstudio::makePreviewHeader(const LatexDocument *rootDoc)
8510 {
8511 	LatexEditorView *edView = rootDoc->getEditorView();
8512     if (!edView){
8513         return QStringList();
8514     }
8515 	int m_endingLine = edView->editor->document()->findLineContaining("\\begin{document}", 0, Qt::CaseSensitive);
8516 	if (m_endingLine < 0) return QStringList(); // can't create header
8517 	QStringList header;
8518 	for (int l = 0; l < m_endingLine; l++) {
8519 		const QString &line = edView->editor->document()->line(l).text();
8520 		int start = line.indexOf("\\input{");
8521 		if (start < 0) {
8522 			header << line;
8523 		} else {
8524 			// rewrite input to absolute paths
8525 			QString newLine(line);
8526 			start += 7;  // behind curly brace of \\input{
8527 			int end = newLine.indexOf('}', start);
8528 			if (end >= 0) {
8529 				QString filename(newLine.mid(start, end - start));
8530 				QString absPath = documents.getAbsoluteFilePath(filename);
8531 #ifdef Q_OS_WIN
8532 				absPath.replace('\\', '/');  // make sure the path argumment to \input uses '/' as dir separator
8533 #endif
8534 				if (absPath.contains(' ')) {
8535 					absPath = '"' + absPath + '"';
8536 				}
8537 				newLine.replace(start, end - start, absPath);
8538 			}
8539 			header << newLine;
8540 		}
8541 	}
8542 	if ((buildManager.dvi2pngMode == BuildManager::DPM_EMBEDDED_PDF) && configManager.previewMode != ConfigManager::PM_EMBEDDED) {
8543 		header << "\\usepackage[active,tightpage]{preview}"
8544 		       << "\\usepackage{varwidth}"
8545 		       << "\\AtBeginDocument{\\begin{preview}\\begin{varwidth}{\\linewidth}}"
8546 		       << "\\AtEndDocument{\\end{varwidth}\\end{preview}}";
8547 	}
8548 	header << "\\pagestyle{empty}";// << "\\begin{document}";
8549 	return header;
8550 }
8551 
8552 /*!
8553  * Add a format overlay to the provided selection. Existing overlays of the format will be deleted
8554  * from all lines that are touched by the selection.
8555  *
8556  * \param c: a QDocumentCursor with a selection
8557  * \param sid: formatScheme id
8558  */
updateEmphasizedRegion(QDocumentCursor c,int sid)8559 void Texstudio::updateEmphasizedRegion(QDocumentCursor c, int sid)
8560 {
8561 	QDocument *doc = c.document();
8562 	QDocumentCursor ss = c.selectionStart();
8563 	QDocumentCursor se = c.selectionEnd();
8564 	for (int i = ss.anchorLineNumber(); i <= se.anchorLineNumber(); i++) {
8565 		int begin = i == ss.anchorLineNumber() ? ss.anchorColumnNumber() : 0;
8566 		int end = i == se.anchorLineNumber() ? se.anchorColumnNumber() : doc->line(i).length();
8567 		if (sid > 0) {
8568 			doc->line(i).clearOverlays(sid);
8569 			doc->line(i).addOverlay(QFormatRange(begin, end - begin, sid));
8570 		} else {
8571 			// remove overlay if sid <0 (removes -sid)
8572 			doc->line(i).clearOverlays(-sid);
8573 		}
8574     }
8575 }
8576 /*!
8577  * \brief Texstudio::completerIsVisible
8578  * \return true if completer is visible
8579  */
completerIsVisible()8580 bool Texstudio::completerIsVisible()
8581 {
8582     if(completer && completer->isVisible()){
8583         return true;
8584     }
8585     return false;
8586 }
8587 
showPreviewQueue()8588 void Texstudio::showPreviewQueue()
8589 {
8590 	if (previewQueueOwner != currentEditorView()) {
8591 		previewQueue.clear();
8592 		return;
8593 	}
8594 	if (configManager.autoPreview == ConfigManager::AP_NEVER) {
8595 		// remove marks
8596 		int sid = previewQueueOwner->document->getFormatId("previewSelection");
8597 		if (sid > 0) {
8598 			foreach (const QDocumentCursor &c, previewQueueOwner->autoPreviewCursor) {
8599 				updateEmphasizedRegion(c, -sid);
8600 			}
8601 		}
8602 		previewQueueOwner->autoPreviewCursor.clear();
8603 		previewQueue.clear();
8604 		return;
8605 	}
8606 	foreach (const int line, previewQueue)
8607 		foreach (const QDocumentCursor &c, previewQueueOwner->autoPreviewCursor)
8608 			if (c.lineNumber() == line)
8609 				showPreview(c, false);
8610 	previewQueue.clear();
8611 }
8612 
8613 
8614 
recompileForPreview()8615 void Texstudio::recompileForPreview(){
8616 	if (documents.getCompileFileName().isEmpty()) return;
8617 #ifndef NO_POPPLER_PREVIEW
8618 	if (PDFDocument::documentList().isEmpty()) return;
8619 #endif
8620 	if (!documents.currentDocument || documents.currentDocument->mayHaveDiffMarkers) return;
8621 	previewEditorPending = currentEditor();
8622 	if (!previewEditorPending || previewEditorPending->fileName().isEmpty()) return;
8623 	previewFullCompileDelayTimer.start(qMax(40, configManager.autoPreviewDelay));
8624 }
recompileForPreviewNow()8625 void Texstudio::recompileForPreviewNow(){
8626 	if (!previewEditorPending || previewEditorPending != currentEditor()) return;
8627 	if (buildManager.waitingForProcess()) {
8628 		if (previewEditorPending->isContentModified()) {
8629 			previewFullCompileDelayTimer.start(qMax(50, configManager.autoPreviewDelay));
8630 		}
8631 		return;
8632 	}
8633 	previewEditorPending->save();
8634 	previewIsAutoCompiling = true;
8635 	runCommand(BuildManager::CMD_COMPILE, nullptr, nullptr, false);
8636 }
8637 
editInsertRefToNextLabel(const QString & refCmd,bool backward)8638 void Texstudio::editInsertRefToNextLabel(const QString &refCmd, bool backward)
8639 {
8640 	if (!currentEditorView()) return;
8641 	QDocumentCursor c = currentEditor()->cursor();
8642 	int l = c.lineNumber();
8643 	int m = currentEditorView()->editor->document()->findLineContaining("\\label", l, Qt::CaseSensitive, backward);
8644 	if (!backward && m < l) return;
8645 	if (m < 0) return;
8646 	// TODO: The search of the line should also be switched to the token system
8647 
8648 	QDocumentLineHandle *dlh = currentEditor()->document()->line(m).handle();
8649     TokenList tl = dlh->getCookieLocked(QDocumentLine::LEXER_COOKIE).value<TokenList>();
8650 	QString label = Parsing::getArg(tl, Token::label);
8651 	if (!label.isEmpty()) {
8652 		currentEditor()->write(refCmd + "{" + label + "}");
8653 	}
8654 }
8655 
editInsertRefToPrevLabel(const QString & refCmd)8656 void Texstudio::editInsertRefToPrevLabel(const QString &refCmd)
8657 {
8658 	editInsertRefToNextLabel(refCmd, true);
8659 }
8660 
runSearch(SearchQuery * query)8661 void Texstudio::runSearch(SearchQuery *query)
8662 {
8663 	if (!currentEditorView() || !query) return;
8664 	query->run(currentEditorView()->document);
8665 }
8666 
findLabelUsages(LatexDocument * contextDoc,const QString & labelText)8667 void Texstudio::findLabelUsages(LatexDocument *contextDoc, const QString &labelText)
8668 {
8669 	if (!contextDoc) return;
8670 	LabelSearchQuery *query = new LabelSearchQuery(labelText);
8671 	searchResultWidget()->setQuery(query);
8672 	query->run(contextDoc);
8673     outputView->showPage(outputView->SEARCH_RESULT_PAGE);
8674 }
8675 
findLabelUsagesFromAction()8676 void Texstudio::findLabelUsagesFromAction()
8677 {
8678     QAction *action = qobject_cast<QAction *>(sender());
8679     if (!action) return;
8680     QString labelText = action->data().toString();
8681     LatexDocument *doc = action->property("doc").value<LatexDocument *>();
8682     findLabelUsages(doc, labelText);
8683 }
8684 
searchResultWidget()8685 SearchResultWidget *Texstudio::searchResultWidget()
8686 {
8687 	return outputView->getSearchResultWidget();
8688 }
8689 
8690 // show current cursor position in structure view
cursorPositionChanged()8691 void Texstudio::cursorPositionChanged()
8692 {
8693 	LatexEditorView *view = currentEditorView();
8694 	if (!view) return;
8695 	int i = view->editor->cursor().lineNumber();
8696 
8697 	view->checkRTLLTRLanguageSwitching();
8698 
8699 	// search line in structure
8700 	if (currentLine == i) return;
8701 	currentLine = i;
8702 
8703 	StructureEntry *newSection = currentEditorView()->document->findSectionForLine(currentLine);
8704 
8705     if(newSection!=currentSection){
8706         StructureEntry *old=currentSection;
8707         currentSection=newSection;
8708         updateCurrentPosInTOC(nullptr,old);
8709     }
8710 
8711 	syncPDFViewer(currentEditor()->cursor(), false);
8712 }
8713 
syncPDFViewer(QDocumentCursor cur,bool inForeground)8714 void Texstudio::syncPDFViewer(QDocumentCursor cur, bool inForeground)
8715 {
8716 #ifndef NO_POPPLER_PREVIEW
8717         if (inForeground) {
8718 		// open new viewer, if none exists
8719 		QAction *viewAct = getManagedAction("main/tools/view");
8720 		if (viewAct) viewAct->trigger();
8721 		return;
8722 	}
8723 
8724 	LatexDocument *doc = qobject_cast<LatexDocument *>(cur.document());
8725 	if (!doc) doc = documents.currentDocument;
8726 	if (doc) {
8727 		QString filename = doc->getFileNameOrTemporaryFileName();
8728 		if (!filename.isEmpty()) {
8729 			int lineNumber = cur.isValid() ? cur.lineNumber() : currentLine;
8730 			int originalLineNumber = doc->lineToLineSnapshotLineNumber(cur.line());
8731 			if (originalLineNumber >= 0) lineNumber = originalLineNumber;
8732 			int col = cur.columnNumber();
8733 			PDFDocument::DisplayFlags displayPolicy = PDFDocument::NoDisplayFlags;
8734 			if (inForeground) displayPolicy = PDFDocument::Raise | PDFDocument::Focus;
8735 			foreach (PDFDocument *viewer, PDFDocument::documentList()) {
8736 				if (inForeground || viewer->followCursor()) {
8737 					viewer->syncFromSource(filename, lineNumber, col, displayPolicy);
8738 				}
8739 			}
8740 		}
8741 	}
8742 #else
8743 	Q_UNUSED(cur)
8744 	Q_UNUSED(inForeground)
8745 #endif
8746 }
8747 
fileCheckin(QString filename)8748 void Texstudio::fileCheckin(QString filename)
8749 {
8750 	if (!currentEditorView()) return;
8751 	QString fn = filename.isEmpty() ? currentEditor()->fileName() : filename;
8752 	UniversalInputDialog dialog;
8753 	QString text;
8754 	dialog.addTextEdit(&text, tr("commit comment:"));
8755     bool wholeDirectory=false;
8756     if(configManager.useVCS==0){ // SVN only
8757         dialog.addVariable(&wholeDirectory, tr("check in whole directory ?"));
8758     }
8759 	if (dialog.exec() == QDialog::Accepted) {
8760 		fileSave(true);
8761 		if (wholeDirectory) {
8762 			fn = QFileInfo(fn).absolutePath();
8763 		}
8764 		//checkin(fn,text);
8765 		if (svnadd(fn)) {
8766 			checkin(fn, text, configManager.svnKeywordSubstitution);
8767 		} else {
8768             if(configManager.useVCS==0){
8769                 svn.createRepository(fn);
8770             }else{
8771                 git.createRepository(fn);
8772             }
8773 			svnadd(fn);
8774 			checkin(fn, text, configManager.svnKeywordSubstitution);
8775 		}
8776 	}
8777 }
8778 /*!
8779  * \brief lock pdf file
8780  *
8781  * Determines pdf filename by using the current text file name and substitutes its extension to 'pdf'
8782  * \param filename
8783  */
fileLockPdf(QString filename)8784 void Texstudio::fileLockPdf(QString filename)
8785 {
8786     if(configManager.useVCS>0){ // GIT
8787         return;
8788     }
8789 	if (!currentEditorView()) return;
8790 	QString finame = filename;
8791 	if (finame.isEmpty())
8792 		finame = documents.getTemporaryCompileFileName();
8793 	QFileInfo fi(finame);
8794 	QString basename = fi.baseName();
8795 	QString path = fi.path();
8796 	fi.setFile(path + "/" + basename + ".pdf");
8797 	if (!fi.isWritable()) {
8798 		svn.lock(fi.filePath());
8799 	}
8800 }
8801 /*!
8802  * \brief check-in pdf file
8803  *
8804  * Determines pdf filename by using the current text file name and substitutes its extension to 'pdf'
8805  * If the file is not under version management, it tries to add the file.
8806  * \param filename
8807  */
fileCheckinPdf(QString filename)8808 void Texstudio::fileCheckinPdf(QString filename)
8809 {
8810 	if (!currentEditorView()) return;
8811 	QString finame = filename;
8812 	if (finame.isEmpty())
8813 		finame = documents.getTemporaryCompileFileName();
8814 	QFileInfo fi(finame);
8815 	QString basename = fi.baseName();
8816 	QString path = fi.path();
8817 	QString fn = path + "/" + basename + ".pdf";
8818     if(configManager.useVCS==0){
8819         SVN::Status status = svn.status(fn);
8820         if (status == SVN::CheckedIn) return;
8821         if (status == SVN::Unmanaged)
8822             svnadd(fn);
8823         fileCheckin(fn);
8824     }else{
8825         GIT::Status status = git.status(fn);
8826         if (status == GIT::CheckedIn) return;
8827         if (status == GIT::Unmanaged)
8828             svnadd(fn);
8829         fileCheckin(fn);
8830     }
8831 }
8832 /*!
8833  * \brief svn update file
8834  * \param filename
8835  */
fileUpdate(QString filename)8836 void Texstudio::fileUpdate(QString filename)
8837 {
8838 	if (!currentEditorView()) return;
8839 	QString fn = filename.isEmpty() ? currentEditor()->fileName() : filename;
8840 	if (fn.isEmpty()) return;
8841 	QString output = svn.runSvn("update", "--non-interactive " + SVN::quote(fn));
8842 	outputView->insertMessageLine(output);
8843 }
8844 /*!
8845  * \brief svn update work directory
8846  *
8847  * Uses the directory of the current file as cwd.
8848  * \param filename
8849  */
fileUpdateCWD(QString filename)8850 void Texstudio::fileUpdateCWD(QString filename)
8851 {
8852 	if (!currentEditorView()) return;
8853 	QString fn = filename.isEmpty() ? currentEditor()->fileName() : filename;
8854 	if (fn.isEmpty()) return;
8855 	fn = QFileInfo(fn).path();
8856     QString output;
8857     if(configManager.useVCS==0){
8858         output = svn.runSvn("update", "--non-interactive " + SVN::quote(fn));
8859     }else{
8860         output = git.runGit("pull", SVN::quote(fn));
8861     }
8862 	outputView->insertMessageLine(output);
8863 }
8864 
checkinAfterSave(QString filename,int checkIn)8865 void Texstudio::checkinAfterSave(QString filename, int checkIn)
8866 {
8867 	if (checkIn > 1) { // special treatment for save
8868 		// 2: checkin
8869 		// 1: don't check in
8870 		checkin(filename);
8871 		if (configManager.svnUndo) currentEditor()->document()->clearUndo();
8872 	}
8873 	if (checkIn == 0) { // from fileSaveAs
8874 		if (configManager.autoCheckinAfterSaveLevel > 1) {
8875 			if (svnadd(filename)) {
8876 				checkin(filename, "txs auto checkin", configManager.svnKeywordSubstitution);
8877 			} else {
8878 				//create simple repository
8879                 if(configManager.useVCS==0){
8880                     svn.createRepository(filename);
8881                 }else{
8882                     git.createRepository(filename);
8883                 }
8884 				svnadd(filename);
8885 				checkin(filename, "txs auto checkin", configManager.svnKeywordSubstitution);
8886 			}
8887 			// set SVN Properties if desired
8888 			if (configManager.svnKeywordSubstitution) {
8889 				svn.runSvn("propset svn:keywords", "\"Date Author HeadURL Revision\" " + SVN::quote(filename));
8890 			}
8891 		}
8892 	}
8893 }
8894 
checkin(QString fn,QString text,bool blocking)8895 void Texstudio::checkin(QString fn, QString text, bool blocking)
8896 {
8897 	Q_UNUSED(blocking)
8898     if(configManager.useVCS==0){
8899         svn.commit(fn, text);
8900     }else{
8901         git.commit(fn, text);
8902     }
8903 	LatexEditorView *edView = getEditorViewFromFileName(fn);
8904 	if (edView)
8905 		edView->editor->setProperty("undoRevision", 0);
8906 }
8907 
svnadd(QString fn,int stage)8908 bool Texstudio::svnadd(QString fn, int stage)
8909 {
8910 	QString path = QFileInfo(fn).absolutePath();
8911     if(configManager.useVCS==0){
8912         if (!QFile::exists(path + "/.svn")) {
8913             if (stage < configManager.svnSearchPathDepth) {
8914                 if (stage > 0) {
8915                     QDir dr(path);
8916                     dr.cdUp();
8917                     path = dr.absolutePath();
8918                 }
8919                 if (svnadd(path, stage + 1)) {
8920                     checkin(path);
8921                 } else
8922                     return false;
8923             } else {
8924                 return false;
8925             }
8926         }
8927         svn.runSvn("add", SVN::quote(fn));
8928         return true;
8929     }else{
8930         GIT::Status st=git.status(fn);
8931         if(st==GIT::NoRepository){
8932             return false;
8933         }
8934         if(st==GIT::Unmanaged){
8935             git.runGit("add", GIT::quote(fn));
8936             return true;
8937         }
8938         if(st==GIT::Modified){
8939             return true;
8940         }
8941         return false;
8942     }
8943 }
8944 
svnUndo(bool redo)8945 void Texstudio::svnUndo(bool redo)
8946 {
8947 	QString fn = currentEditor()->fileName();
8948 	// get revisions of current file
8949 	QStringList revisions = svn.log(fn);
8950 
8951 	QVariant zw = currentEditor()->property("undoRevision");
8952 	int undoRevision = zw.canConvert<int>() ? zw.toInt() : 0;
8953 
8954 	int l = revisions.size();
8955 	if (undoRevision >= l - 1) return;
8956 	if (!redo) undoRevision++;
8957 
8958 	if (redo) changeToRevision(revisions.at(undoRevision - 1), revisions.at(undoRevision));
8959 	else changeToRevision(revisions.at(undoRevision), revisions.at(undoRevision - 1));
8960 
8961 	currentEditor()->document()->clearUndo();
8962 	if (redo) undoRevision--;
8963 	currentEditor()->setProperty("undoRevision", undoRevision);
8964 }
8965 
svnPatch(QEditor * ed,QString diff)8966 void Texstudio::svnPatch(QEditor *ed, QString diff)
8967 {
8968     if(diff.isEmpty()){
8969         return;
8970     }
8971 	QStringList lines;
8972 	//for(int i=0;i<diff.length();i++)
8973 	//   qDebug()<<diff[i];
8974 	if (diff.contains("\r\n")) {
8975 		lines = diff.split("\r\n");
8976 	} else {
8977 		if (diff.contains("\n")) {
8978 			lines = diff.split("\n");
8979 		} else {
8980 			lines = diff.split("\r");
8981 		}
8982 	}
8983 	for (int i = 0; i < lines.length(); i++) {
8984 		//workaround for svn bug
8985 		// at times it shows text@@ pos insted of text \n @@ ...
8986 		if (lines[i].contains("@@")) {
8987 			int p = lines[i].indexOf("@@");
8988 			lines[i] = lines[i].mid(p);
8989 		}
8990 	}
8991     if(lines.size()<4){
8992         return;
8993     }
8994     for (int i = 0; i < 3 ; i++) lines.removeFirst();
8995 	if (!lines.first().contains("@@")) {
8996 		lines.removeFirst();
8997 	}
8998 
8999     QRegExp rx("@@ -(\\d+),?(\\d*)\\s*\\+(\\d+),(\\d+)");
9000 	int cur_line;
9001 	bool atDocEnd = false;
9002     int realTextLines=ed->document()->lines();
9003 	QDocumentCursor c = ed->cursor();
9004 	foreach (const QString &elem, lines) {
9005 		QChar ch = ' ';
9006 		if (!elem.isEmpty()) {
9007 			ch = elem.at(0);
9008 		}
9009 		if (ch == '@') {
9010 			if (rx.indexIn(elem) > -1) {
9011 				cur_line = rx.cap(3).toInt();
9012 				c.moveTo(cur_line - 1, 0);
9013 			} else {
9014 				qDebug() << "Bug";
9015 			}
9016 		} else {
9017 			if (ch == '-') {
9018 				atDocEnd = (c.lineNumber() == ed->document()->lineCount() - 1);
9019 				if (c.line().text() != elem.mid(1))
9020 					qDebug() << "del:" << c.line().text() << elem;
9021 				c.eraseLine();
9022                 --realTextLines;
9023                 //if (atDocEnd) c.deletePreviousChar();
9024 			} else {
9025 				if (ch == '+') {
9026                     //atDocEnd = (c.lineNumber() == ed->document()->lineCount() - 1);
9027                     if (atDocEnd) {
9028 						c.movePosition(1, QDocumentCursor::EndOfLine, QDocumentCursor::MoveAnchor);
9029                         if(realTextLines>0)
9030                             c.insertLine();
9031                     }
9032 					c.insertText(elem.mid(1));
9033                     ++realTextLines;
9034 					// if line contains \r, no further line break needed
9035 					if (!atDocEnd) {
9036 						c.insertText("\n");
9037 					}
9038 				} else {
9039 					atDocEnd = (c.lineNumber() == ed->document()->lineCount() - 1);
9040 					int limit = 5;
9041 					if (!atDocEnd) {
9042 						while ((c.line().text() != elem.mid(1)) && (limit > 0) && (c.lineNumber() < ed->document()->lineCount() - 1)) {
9043 							qDebug() << c.line().text() << c.lineNumber() << "<>" << elem.mid(1);
9044 							c.movePosition(1, QDocumentCursor::NextLine, QDocumentCursor::MoveAnchor);
9045 							c.movePosition(1, QDocumentCursor::StartOfLine, QDocumentCursor::MoveAnchor);
9046 							limit--;
9047 							if (limit == 0) {
9048 								qDebug() << "failed";
9049 							}
9050 						}
9051 						atDocEnd = (c.lineNumber() == ed->document()->lineCount() - 1);
9052 						if (!atDocEnd) {
9053 							c.movePosition(1, QDocumentCursor::NextLine, QDocumentCursor::MoveAnchor);
9054 							c.movePosition(1, QDocumentCursor::StartOfLine, QDocumentCursor::MoveAnchor);
9055 						}
9056 					}
9057 				}
9058 			}
9059 		}
9060 	}
9061 }
9062 /*!
9063  * \brief show old revisions from svn/git repository
9064  * List all stored revision names/numbers in a dialog which allows to switch back to old revisions of the text
9065  * The text is directly updated when an old revision is selected via a combobox.
9066  * The user can either select and copy content to bring to the most recent version or he can edit the old revision thereby making it the current one.
9067  * To enable changing to the most recent version again, text is automatically saved *and* checked in.
9068  */
showOldRevisions()9069 void Texstudio::showOldRevisions()
9070 {
9071 	// check if a dialog is already open
9072 	if (svndlg) return;
9073 	//needs to save first if modified
9074 	if (!currentEditor())
9075 		return;
9076 
9077 	if (currentEditor()->fileName() == "" || !currentEditor()->fileInfo().exists())
9078 		return;
9079 
9080 	if (currentEditor()->isContentModified()) {
9081 		removeDiffMarkers();// clean document from diff markers first
9082 		currentEditor()->save();
9083 		//currentEditorView()->editor->setModified(false);
9084 		MarkCurrentFileAsRecent();
9085 		checkin(currentEditor()->fileName(), "txs auto checkin", true);
9086     }else{
9087         bool modifiedOnDisk=false;
9088         if(configManager.useVCS==0){
9089             SVN::Status st = svn.status(currentEditor()->fileName());
9090             modifiedOnDisk=(st==SVN::Modified);
9091         }else{
9092             GIT::Status st = git.status(currentEditor()->fileName());
9093             modifiedOnDisk=(st==GIT::Modified);
9094         }
9095         if(modifiedOnDisk){
9096             checkin(currentEditor()->fileName(), "txs auto checkin", true);
9097         }
9098     }
9099 	updateCaption();
9100 
9101     QStringList log;
9102     if(configManager.useVCS==0){
9103         log = svn.log(currentEditor()->fileName());
9104     }else{
9105         log = git.log(currentEditor()->fileName());
9106     }
9107 	if (log.size() < 1) return;
9108 
9109 	svndlg = new QDialog(this);
9110 	QVBoxLayout *lay = new QVBoxLayout(svndlg);
9111 	QLabel *label = new QLabel(tr("Attention: dialog is automatically closed if the text is manually edited!"), svndlg);
9112 	lay->addWidget(label);
9113 	cmbLog = new QComboBox(svndlg);
9114 	cmbLog->insertItems(0, log);
9115 	lay->addWidget(cmbLog);
9116     connect(svndlg, &QDialog::finished, this, &Texstudio::svnDialogClosed);
9117 	connect(cmbLog, SIGNAL(currentIndexChanged(QString)), this, SLOT(changeToRevision(QString)));
9118     connect(currentEditor(), SIGNAL(textEdited(QKeyEvent*)), svndlg, SLOT(close()));
9119 	currentEditor()->setProperty("Revision", log.first());
9120 	svndlg->setAttribute(Qt::WA_DeleteOnClose, true);
9121 	svndlg->show();
9122 }
9123 /*!
9124  * \brief reset when closing svn old revision dialog
9125  * the dialog itself is deleted
9126  * if the revision is the most recent, test is declared unmodified
9127  */
svnDialogClosed(int)9128 void Texstudio::svnDialogClosed(int)
9129 {
9130 	if (cmbLog->currentIndex() == 0) currentEditor()->document()->setClean();
9131 	svndlg = nullptr;
9132 }
9133 /*!
9134  * \brief change editor content from one revision to another
9135  * diff is generated via git/svn
9136  * and that diff is applied to the current editor
9137  * \param rev
9138  * \param old_rev
9139  */
changeToRevision(QString rev,QString old_rev)9140 void Texstudio::changeToRevision(QString rev, QString old_rev)
9141 {
9142 	QString filename = currentEditor()->fileName();
9143 	// get diff
9144 	QRegExp rx("^[r](\\d+) \\|");
9145     if(configManager.useVCS==1){
9146         //GIT
9147         rx.setPattern("^([a-f0-9]+) ");
9148     }
9149 	QString old_revision;
9150 	if (old_rev.isEmpty()) {
9151 		QVariant zw = currentEditor()->property("Revision");
9152 		Q_ASSERT(zw.isValid());
9153 		old_revision = zw.toString();
9154 	} else {
9155 		old_revision = old_rev;
9156 	}
9157 	if (rx.indexIn(old_revision) > -1) {
9158 		old_revision = rx.cap(1);
9159 	} else return;
9160 	QString new_revision = rev;
9161 	if (rx.indexIn(new_revision) > -1) {
9162 		new_revision = rx.cap(1);
9163 	} else return;
9164     QString cmd;
9165     if(configManager.useVCS==0){
9166         //SVN
9167         cmd = SVN::makeCmd("diff", "-r " + old_revision + ":" + new_revision + " " + SVN::quote(filename));
9168     }else{
9169         //GIT
9170         cmd = GIT::makeCmd("diff", old_revision + " " + new_revision + " " + SVN::quote(filename));
9171     }
9172 	QString buffer;
9173     runCommandNoSpecialChars(cmd, &buffer, currentEditor()->getFileCodec());
9174 	// patch
9175 	svnPatch(currentEditor(), buffer);
9176     currentEditor()->setProperty("Revision", rev);
9177 }
9178 
generateMirror(bool setCur)9179 bool Texstudio::generateMirror(bool setCur)
9180 {
9181 	if (!currentEditorView()) return false;
9182 	QDocumentCursor cursor = currentEditorView()->editor->cursor();
9183 	QDocumentCursor oldCursor = cursor;
9184 	QString line = cursor.line().text();
9185 	QString command, value;
9186 	Token tk = Parsing::getTokenAtCol(cursor.line().handle(), cursor.columnNumber());
9187 
9188 	if (tk.type == Token::env || tk.type == Token::beginEnv) {
9189 		if (tk.length > 0) {
9190 			value = tk.getText();
9191 			command = Parsing::getCommandFromToken(tk);
9192 			//int l=cursor.lineNumber();
9193 			if (currentEditor()->currentPlaceHolder() != -1 &&
9194 			        currentEditor()->getPlaceHolder(currentEditor()->currentPlaceHolder()).cursor.isWithinSelection(cursor))
9195 				currentEditor()->removePlaceHolder(currentEditor()->currentPlaceHolder()); //remove currentplaceholder to prevent nesting
9196 			//move cursor to env name
9197 			int pos = tk.start;
9198 			cursor.selectColumns(pos, pos + tk.length);
9199 
9200 			LatexDocument *doc = currentEditorView()->document;
9201 
9202 			PlaceHolder ph;
9203 			ph.cursor = cursor;
9204 			ph.autoRemove = true;
9205 			ph.autoRemoveIfLeft = true;
9206 			// remove curly brakets as well
9207 			QString searchWord = "\\end{" + value + "}";
9208 			QString inhibitor = "\\begin{" + value + "}";
9209 			bool backward = (command == "\\end");
9210 			int step = 1;
9211 			if (backward) {
9212 				qSwap(searchWord, inhibitor);
9213 				step = -1;
9214 			}
9215 			int ln = cursor.lineNumber();
9216 			int col = cursor.columnNumber();
9217 			bool finished = false;
9218 			int nested = 0;
9219 			int colSearch = 0;
9220 			int colInhibit = 0;
9221 			if (step < 0)
9222 				line = line.left(col);
9223 			while (!finished) {
9224 				if (step > 0) {
9225 					//forward search
9226 					colSearch = line.indexOf(searchWord, col);
9227 					colInhibit = line.indexOf(inhibitor, col);
9228 				} else {
9229 					//backward search
9230 					colSearch = line.lastIndexOf(searchWord);
9231 					colInhibit = line.lastIndexOf(inhibitor);
9232 				}
9233 				if (colSearch < 0 && colInhibit < 0) {
9234 					ln += step;
9235 					if (doc->lines() <= ln || ln < 0)
9236 						break;
9237 					line = doc->line(ln).text();
9238 					col = 0;
9239 				}
9240 				if (colSearch >= 0 && colInhibit >= 0) {
9241 					if (colSearch * step < colInhibit * step) {
9242 						if (nested == 0) {
9243 							finished = true;
9244 						}
9245 						nested--;
9246 						col = colSearch + 1;
9247 						if (step < 0)
9248 							line = line.left(colSearch);
9249 					} else {
9250 						if (step > 0) {
9251 							col = colInhibit + 1;
9252 						} else {
9253 							line = line.left(colInhibit);
9254 						}
9255 						nested++;
9256 					}
9257 				} else {
9258 					if (colSearch >= 0) {
9259 						nested--;
9260 						if (nested < 0)
9261 							finished = true;
9262 						if (step > 0) {
9263 							col = colSearch + 1;
9264 						} else {
9265 							line = line.left(colSearch);
9266 						}
9267 					}
9268 					if (colInhibit >= 0) {
9269 						nested++;
9270 						if (step > 0) {
9271 							col = colInhibit + 1;
9272 						} else {
9273 							line = line.left(colInhibit);
9274 						}
9275 					}
9276 				}
9277 			}
9278 			if (finished) {
9279 				line = doc->line(ln).text();
9280 				int start = colSearch;
9281 				int offset = searchWord.indexOf("{");
9282 				ph.mirrors << currentEditor()->document()->cursor(ln, start + offset + 1, ln, start + searchWord.length() - 1);
9283 			}
9284 
9285 			currentEditor()->addPlaceHolder(ph);
9286 			currentEditor()->setPlaceHolder(currentEditor()->placeHolderCount() - 1);
9287 			if (setCur)
9288 				currentEditorView()->editor->setCursor(oldCursor);
9289 			return true;
9290 		}
9291 	}
9292 	return false;
9293 }
9294 
generateBracketInverterMirror()9295 void Texstudio::generateBracketInverterMirror()
9296 {
9297 	if (!currentEditor()) return;
9298 	REQUIRE(currentEditor()->document() && currentEditor()->document()->languageDefinition());
9299 	QDocumentCursor orig, to;
9300 	currentEditor()->cursor().getMatchingPair(orig, to, false);
9301 	if (!orig.isValid() && !to.isValid()) return;  // no matching pair found
9302 
9303 	PlaceHolder ph;
9304 	ph.cursor = orig.selectionStart();
9305 	ph.mirrors << to.selectionStart();
9306 	ph.length = orig.selectedText().length();
9307 	ph.affector = BracketInvertAffector::instance();
9308 	currentEditor()->addPlaceHolder(ph);
9309 	currentEditor()->setPlaceHolder(currentEditor()->placeHolderCount() - 1);
9310 }
9311 
jumpToBracket()9312 void Texstudio::jumpToBracket()
9313 {
9314 	if (!currentEditor()) return;
9315 	REQUIRE(sender() && currentEditor()->document() && currentEditor()->document()->languageDefinition());
9316 	QDocumentCursor orig, to;
9317 	const QDocumentCursor se = currentEditor()->cursor().selectionEnd();
9318 	se.getMatchingPair(orig, to, false);
9319 	if (!orig.isValid() && !to.isValid()) return;  // no matching pair found
9320 	if (orig.selectionEnd() == se) currentEditor()->setCursor(to.selectionStart());
9321 	else currentEditor()->setCursor(to.selectionEnd());
9322 }
9323 
selectBracket()9324 void Texstudio::selectBracket()
9325 {
9326 	if (!currentEditor()) return;
9327 	REQUIRE(sender() && currentEditor()->document());
9328 	if (!currentEditor()->document()->languageDefinition()) return;
9329 
9330 	QDocumentCursor cursor = currentEditor()->cursor();
9331 	QString type = sender()->property("type").toString();
9332 	if (type == "inner") {
9333 		cursor.select(QDocumentCursor::ParenthesesInner);
9334 	} else if (type == "outer") {
9335 		cursor.select(QDocumentCursor::ParenthesesOuter);
9336 	} else if (type == "command") {
9337 		Token tk = Parsing::getTokenAtCol(cursor.line().handle(), cursor.columnNumber());
9338 		if (tk.type == Token::command) {
9339 			cursor.setColumnNumber(tk.start + tk.length);
9340 			cursor.select(QDocumentCursor::ParenthesesOuter);
9341 			cursor.setAnchorColumnNumber(tk.start);
9342 		} else {
9343 			cursor.select(QDocumentCursor::ParenthesesOuter);
9344 			if (cursor.anchorColumnNumber() > 0) {
9345 				tk = Parsing::getTokenAtCol(cursor.line().handle(), cursor.anchorColumnNumber() - 1);
9346 				if (tk.type == Token::command) {
9347 					cursor.setAnchorColumnNumber(tk.start);
9348 				}
9349 			}
9350 		}
9351 	} else if (type == "line") {
9352 		QDocumentCursor orig, to;
9353 		cursor.getMatchingPair(orig, to, true);
9354 		if (!orig.isValid() && !to.isValid()) return;  // no matching pair found
9355 
9356 		if (to < orig) to.setColumnNumber(0);
9357 		else to.setColumnNumber(to.line().length());
9358 
9359 		QDocumentCursor::sort(orig, to);
9360 		if (orig.hasSelection()) orig = orig.selectionStart();
9361 		if (to.hasSelection()) to = to.selectionEnd();
9362 
9363 		cursor.select(orig.lineNumber(), orig.columnNumber(), to.lineNumber(), to.columnNumber());
9364 	} else {
9365 		qWarning("Unhandled selectBracket() type");
9366 	}
9367 	currentEditor()->setCursor(cursor);
9368 }
9369 
findMissingBracket()9370 void Texstudio::findMissingBracket()
9371 {
9372 	if (!currentEditor()) return;
9373 	REQUIRE(currentEditor()->document() && currentEditor()->document()->languageDefinition());
9374 	QDocumentCursor c = currentEditor()->languageDefinition()->getNextMismatch(currentEditor()->cursor());
9375 	if (c.isValid()) currentEditor()->setCursor(c);
9376 }
9377 
openExternalFile(QString name,const QString & defaultExt,LatexDocument * doc)9378 void Texstudio::openExternalFile(QString name, const QString &defaultExt, LatexDocument *doc)
9379 {
9380 	if (!doc) {
9381 		if (!currentEditor()) return;
9382 		doc = qobject_cast<LatexDocument *>(currentEditor()->document());
9383 	}
9384 	if (!doc) return;
9385 	name.remove('"');  // ignore quotes (http://sourceforge.net/p/texstudio/bugs/1366/)
9386     QStringList curPaths;
9387     if (documents.masterDocument){
9388         curPaths << ensureTrailingDirSeparator(documents.masterDocument->getFileInfo().absolutePath());
9389     }
9390     if (doc->getRootDocument()){
9391         curPaths << ensureTrailingDirSeparator(doc->getRootDocument()->getFileInfo().absolutePath());
9392     }
9393     curPaths << ensureTrailingDirSeparator(doc->getFileInfo().absolutePath());
9394     if (defaultExt == "bib") {
9395         curPaths << configManager.additionalBibPaths.split(getPathListSeparator());
9396     }
9397     bool loaded = false;
9398     for (int i = 0; i < curPaths.count(); i++) {
9399         const QString &curPath = ensureTrailingDirSeparator(curPaths.value(i));
9400         if ((loaded = load(getAbsoluteFilePath(curPath + name, defaultExt))))
9401             break;
9402         if ((loaded = load(getAbsoluteFilePath(curPath + name, ""))))
9403             break;
9404         if ((loaded = load(getAbsoluteFilePath(name, defaultExt))))
9405             break;
9406     }
9407 
9408 	if (!loaded) {
9409 		Q_ASSERT(curPaths.count() > 0);
9410 		QFileInfo fi(getAbsoluteFilePath(curPaths[0] + name, defaultExt));
9411 		if (fi.exists()) {
9412 			UtilsUi::txsCritical(tr("Unable to open file \"%1\".").arg(fi.fileName()));
9413 		} else {
9414 			if (UtilsUi::txsConfirmWarning(tr("The file \"%1\" does not exist.\nDo you want to create it?").arg(fi.fileName()))) {
9415 				int lineNr = -1;
9416 				if (currentEditor()) {
9417 					lineNr = currentEditor()->cursor().lineNumber();
9418 				}
9419 				if (!fi.absoluteDir().exists())
9420 					fi.absoluteDir().mkpath(".");
9421 				fileNew(fi.absoluteFilePath());
9422 				qDebug() << doc->getFileName() << lineNr;
9423 				doc->patchStructure(lineNr, 1);
9424 			}
9425 		}
9426     }
9427 }
9428 
9429 
openExternalFileFromAction()9430 void Texstudio::openExternalFileFromAction()
9431 {
9432     QAction *act = qobject_cast<QAction *>(sender());
9433     QString name = act->data().toString();
9434     name.replace("\\string~",QDir::homePath());
9435     if (!name.isEmpty())
9436         openExternalFile(name);
9437 }
9438 
cursorHovered()9439 void Texstudio::cursorHovered()
9440 {
9441 	if (completer->isVisible()) return;
9442 	generateMirror(true);
9443 }
9444 
saveProfile()9445 void Texstudio::saveProfile()
9446 {
9447 	QString currentDir = configManager.configBaseDir;
9448 	QString fname = FileDialog::getSaveFileName(this, tr("Save Profile"), currentDir, tr("TXS Profile", "filter") + "(*.txsprofile);;" + tr("All files") + " (*)");
9449 	saveSettings(fname);
9450 }
9451 
loadProfile()9452 void Texstudio::loadProfile()
9453 {
9454 	QString currentDir = configManager.configBaseDir;
9455 	QString fname = FileDialog::getOpenFileName(this, tr("Load Profile"), currentDir, tr("TXS Profile", "filter") + "(*.txsprofile);;" + tr("All files") + " (*)");
9456 	if (fname.isNull())
9457 		return;
9458 	if (QFileInfo(fname).isReadable()) {
9459 		bool macro = false;
9460 		bool userCommand = false;
9461 		saveSettings();
9462 		QSettings *profile = new QSettings(fname, QSettings::IniFormat);
9463 		QSettings *config = configManager.newQSettings();
9464 		if (profile && config) {
9465 			QStringList keys = profile->allKeys();
9466 			foreach (const QString &key, keys) {
9467 				//special treatment for macros/usercommands (list maybe shorter than before)
9468 				if (key.startsWith("texmaker/Macros")) {
9469 					continue;
9470 				}
9471 				if (key == "texmaker/Tools/User Order") {
9472 					// logic assumes that the user command name is exclusive
9473 					QStringList order = config->value(key).toStringList() << profile->value(key).toStringList();
9474 					config->setValue(key, order);
9475 					userCommand = true;
9476 					QString nameKey("texmaker/Tools/Display Names");
9477 					QStringList displayNames = config->value(nameKey).toStringList() << profile->value(nameKey).toStringList();
9478 					config->setValue(nameKey, displayNames);
9479 					continue;
9480 				}
9481 				if (key == "texmaker/Tools/Display Names") {
9482 					continue;  // handled above
9483 				}
9484 				config->setValue(key, profile->value(key));
9485 			}
9486 			// handle macros
9487 			for (int i = 0; i < ConfigManager::MAX_NUM_MACROS; i++) {
9488 			    QStringList ls = profile->value(QString("texmaker/Macros/%1").arg(i)).toStringList();
9489 			    if (ls.isEmpty()) break;
9490 			    if (!macro) { //remove old values
9491 				config->beginGroup("texmaker");
9492 				config->remove("Macros");
9493 				config->endGroup();
9494 				configManager.completerConfig->userMacros.clear();
9495 			    }
9496 			    configManager.completerConfig->userMacros.append(Macro(ls));
9497 			    macro=true;
9498 			}
9499 		}
9500 		delete profile;
9501 		delete config;
9502 		readSettings(true);
9503         configManager.modifyManagedShortcuts();
9504 		if (macro)
9505 			updateUserMacros();
9506 		if (userCommand)
9507 			updateUserToolMenu();
9508 	} else UtilsUi::txsWarning(tr("Failed to read profile file %1.").arg(fname));
9509 }
9510 
addRowCB()9511 void Texstudio::addRowCB()
9512 {
9513 	if (!currentEditorView()) return;
9514 	QDocumentCursor cur = currentEditorView()->editor->cursor();
9515 	if (!LatexTables::inTableEnv(cur)) return;
9516 	int cols = LatexTables::getNumberOfColumns(cur);
9517 	if (cols < 1) return;
9518 	LatexTables::addRow(cur, cols);
9519 }
9520 
addColumnCB()9521 void Texstudio::addColumnCB()
9522 {
9523 	if (!currentEditorView()) return;
9524 	QDocumentCursor cur = currentEditorView()->editor->cursor();
9525 	if (!LatexTables::inTableEnv(cur)) return;
9526 	int col = LatexTables::getColumn(cur) + 1;
9527 	if (col < 1) return;
9528 	if (col == 1 && cur.atLineStart()) col = 0;
9529 	LatexTables::addColumn(currentEditorView()->document, currentEditorView()->editor->cursor().lineNumber(), col);
9530 }
9531 
removeColumnCB()9532 void Texstudio::removeColumnCB()
9533 {
9534 	if (!currentEditorView()) return;
9535 	QDocumentCursor cur = currentEditorView()->editor->cursor();
9536 	if (!LatexTables::inTableEnv(cur)) return;
9537 	// check if cursor has selection
9538 	int numberOfColumns = 1;
9539 	int col = LatexTables::getColumn(cur);
9540 	if (cur.hasSelection()) {
9541 		// if selection span within one row, romove all touched columns
9542 		QDocumentCursor c2(cur.document(), cur.anchorLineNumber(), cur.anchorColumnNumber());
9543 		if (!LatexTables::inTableEnv(c2)) return;
9544 		QString res = cur.selectedText();
9545 		if (res.contains("\\\\")) return;
9546 		int col2 = LatexTables::getColumn(c2);
9547 		numberOfColumns = abs(col - col2) + 1;
9548 		if (col2 < col) col = col2;
9549 	}
9550 	int ln = cur.lineNumber();
9551 	for (int i = 0; i < numberOfColumns; i++) {
9552 	        LatexTables::removeColumn(currentEditorView()->document, ln, col, nullptr);
9553 	}
9554 }
9555 
removeRowCB()9556 void Texstudio::removeRowCB()
9557 {
9558 	if (!currentEditorView()) return;
9559 	QDocumentCursor cur = currentEditorView()->editor->cursor();
9560 	if (!LatexTables::inTableEnv(cur)) return;
9561 	LatexTables::removeRow(cur);
9562 }
9563 
cutColumnCB()9564 void Texstudio::cutColumnCB()
9565 {
9566 	if (!currentEditorView()) return;
9567 	QDocumentCursor cur = currentEditorView()->editor->cursor();
9568 	if (!LatexTables::inTableEnv(cur)) return;
9569 	// check if cursor has selection
9570 	int numberOfColumns = 1;
9571 	int col = LatexTables::getColumn(cur);
9572 	if (cur.hasSelection()) {
9573 		// if selection span within one row, romove all touched columns
9574 		QDocumentCursor c2(cur.document(), cur.anchorLineNumber(), cur.anchorColumnNumber());
9575 		if (!LatexTables::inTableEnv(c2)) return;
9576 		QString res = cur.selectedText();
9577 		if (res.contains("\\\\")) return;
9578 		int col2 = LatexTables::getColumn(c2);
9579 		numberOfColumns = abs(col - col2) + 1;
9580 		if (col2 < col) col = col2;
9581 	}
9582 	int ln = cur.lineNumber();
9583 	m_columnCutBuffer.clear();
9584 	QStringList lst;
9585 	for (int i = 0; i < numberOfColumns; i++) {
9586 		lst.clear();
9587 		LatexTables::removeColumn(currentEditorView()->document, ln, col, &lst);
9588 		if (m_columnCutBuffer.isEmpty()) {
9589 			m_columnCutBuffer = lst;
9590 		} else {
9591 			for (int i = 0; i < m_columnCutBuffer.size(); i++) {
9592 				QString add = "&";
9593 				if (!lst.isEmpty()) add += lst.takeFirst();
9594 				m_columnCutBuffer[i] += add;
9595 			}
9596 		}
9597 	}
9598 
9599 }
9600 
pasteColumnCB()9601 void Texstudio::pasteColumnCB()
9602 {
9603 	if (!currentEditorView()) return;
9604 	QDocumentCursor cur = currentEditorView()->editor->cursor();
9605 	if (!LatexTables::inTableEnv(cur)) return;
9606 	int col = LatexTables::getColumn(cur) + 1;
9607 	if (col == 1 && cur.atLineStart()) col = 0;
9608 	LatexTables::addColumn(currentEditorView()->document, currentEditorView()->editor->cursor().lineNumber(), col, &m_columnCutBuffer);
9609 }
9610 
addHLineCB()9611 void Texstudio::addHLineCB()
9612 {
9613 	if (!currentEditorView()) return;
9614 	QDocumentCursor cur = currentEditorView()->editor->cursor();
9615 	if (!LatexTables::inTableEnv(cur)) return;
9616 	LatexTables::addHLine(cur);
9617 }
9618 
remHLineCB()9619 void Texstudio::remHLineCB()
9620 {
9621 	if (!currentEditorView()) return;
9622 	QDocumentCursor cur = currentEditorView()->editor->cursor();
9623 	if (!LatexTables::inTableEnv(cur)) return;
9624 	LatexTables::addHLine(cur, -1, true);
9625 }
9626 
findWordRepetions()9627 void Texstudio::findWordRepetions()
9628 {
9629 	if (!currentEditorView()) return;
9630 	if (configManager.editorConfig && !configManager.editorConfig->inlineSpellChecking) {
9631 		QMessageBox::information(this, tr("Problem"), tr("Finding word repetitions only works with activated online spell checking !"), QMessageBox::Ok);
9632 		return;
9633 	}
9634 	QDialog *dlg = new QDialog(this);
9635 	dlg->setAttribute(Qt::WA_DeleteOnClose, true);
9636 	dlg->setWindowTitle(tr("Find Word Repetitions"));
9637 	QGridLayout *layout = new QGridLayout;
9638 	layout->setColumnStretch(1, 1);
9639 	layout->setColumnStretch(0, 1);
9640 	QComboBox *cb = new QComboBox(dlg);
9641 	cb->addItems(QStringList() << "spellingMistake" << "wordRepetition" << "wordRepetitionLongRange" << "badWord" << "grammarMistake" << "grammarMistakeSpecial1" << "grammarMistakeSpecial2" << "grammarMistakeSpecial3" << "grammarMistakeSpecial4");
9642 	cb->setCurrentIndex(1);
9643 	cb->setObjectName("kind");
9644 	cb->setEditable(true); //so people can search for other things as well
9645 	QPushButton *btNext = new QPushButton(tr("&Find Next"), dlg);
9646 	btNext->setObjectName("next");
9647 	QPushButton *btPrev = new QPushButton(tr("&Find Previous"), dlg);
9648 	btPrev->setObjectName("prev");
9649 	QPushButton *btClose = new QPushButton(tr("&Close"), dlg);
9650 	btClose->setObjectName("close");
9651 	layout->addWidget(cb, 0, 0);
9652 	layout->addWidget(btNext, 0, 1);
9653 	layout->addWidget(btPrev, 0, 2);
9654 	layout->addWidget(btClose, 0, 3);
9655 	dlg->setLayout(layout);
9656 	connect(btNext, SIGNAL(clicked()), this, SLOT(findNextWordRepetion()));
9657 	connect(btPrev, SIGNAL(clicked()), this, SLOT(findNextWordRepetion()));
9658 	connect(btClose, SIGNAL(clicked()), dlg, SLOT(close()));
9659 	dlg->setModal(false);
9660 	dlg->show();
9661 	dlg->raise();
9662 
9663 }
9664 
findNextWordRepetion()9665 void Texstudio::findNextWordRepetion()
9666 {
9667 	QPushButton *mButton = qobject_cast<QPushButton *>(sender());
9668 	bool backward = mButton->objectName() == "prev";
9669 	if (!currentEditorView()) return;
9670 	typedef QFormatRange (QDocumentLine::*OverlaySearch) (int, int, int) const;
9671 	OverlaySearch overlaySearch = backward ? &QDocumentLine::getLastOverlay : &QDocumentLine::getFirstOverlay;
9672 	QComboBox *kind = mButton->parent()->findChild<QComboBox *>("kind");
9673 	int overlayType = currentEditorView()->document->getFormatId(kind ? kind->currentText() : "wordRepetition");
9674 	QDocumentCursor cur = currentEditor()->cursor();
9675 	if (cur.hasSelection()) {
9676 		if (backward) cur = cur.selectionStart();
9677 		else cur = cur.selectionEnd();
9678 	}
9679 	int lineNr = cur.lineNumber();
9680 	QDocumentLine line = cur.line();
9681 	int fx = backward ? 0 : (cur.endColumnNumber() + 1), tx = backward ? cur.startColumnNumber() - 1 : line.length();
9682 	while (line.isValid()) {
9683 		if (line.hasOverlay(overlayType)) {
9684 			QFormatRange range = (line.*overlaySearch)(fx, tx, overlayType);
9685 			if (range.length > 0) {
9686 				currentEditor()->setCursor(currentEditor()->document()->cursor(lineNr, range.offset, lineNr, range.offset + range.length));
9687 				return;
9688 			}
9689 		}
9690 		if (backward)
9691 			lineNr--;
9692 		else
9693 			lineNr++;
9694 		line = currentEditor()->document()->line(lineNr);
9695 		fx = 0;
9696 		tx = line.length();
9697 	}
9698 	UtilsUi::txsInformation(backward ? tr("Reached beginning of text.") : tr("Reached end of text."));
9699 }
9700 
importPackage(QString name)9701 void Texstudio::importPackage(QString name)
9702 {
9703 	if (!latexStyleParser) {
9704 		QString cmd_latex = buildManager.getCommandInfo(BuildManager::CMD_LATEX).getProgramNameUnquoted();
9705 		QString baseDir;
9706 		if (!QFileInfo(cmd_latex).isRelative())
9707 			baseDir = QFileInfo(cmd_latex).absolutePath() + "/";
9708 		latexStyleParser = new LatexStyleParser(this, configManager.configBaseDir, baseDir + "kpsewhich");
9709 		connect(latexStyleParser, SIGNAL(scanCompleted(QString)), this, SLOT(packageScanCompleted(QString)));
9710 		connect(latexStyleParser, SIGNAL(finished()), this, SLOT(packageParserFinished()));
9711 		latexStyleParser->setAlias(latexParser.packageAliases);
9712 		latexStyleParser->start();
9713 		QTimer::singleShot(30000, this, SLOT(stopPackageParser()));
9714 	}
9715 	QString dirName;
9716 	if (name.contains("/")) {
9717 		int i = name.indexOf("/");
9718 		dirName = "/" + name.mid(i + 1);
9719 		name = name.left(i);
9720 	}
9721 	name.chop(4);
9722 	name.append(".sty");
9723     // remove option# from name
9724     int i=name.indexOf("#");
9725     if(i>-1){
9726         name=name.mid(i+1);
9727     }
9728 	latexStyleParser->addFile(name + dirName);
9729 	name.chop(4);
9730 	name.append(".cls"); // try also cls
9731 	latexStyleParser->addFile(name + dirName);
9732 }
9733 
packageScanCompleted(QString name)9734 void Texstudio::packageScanCompleted(QString name)
9735 {
9736 	QStringList lst = name.split('#');
9737 	QString baseName = name;
9738 	if (lst.size() > 1) {
9739 		baseName = lst.first();
9740 		name = lst.last();
9741 	}
9742 	foreach (LatexDocument *doc, documents.documents) {
9743 		if (doc->containsPackage(baseName)) {
9744             //find proper key
9745             QStringList keys=documents.cachedPackages.keys();
9746             keys=keys.filter(name+".cwl");
9747             foreach(const QString &key,keys){
9748                 documents.cachedPackages.remove(key); // TODO: check is this still correct if keys are complex?
9749             }
9750 			doc->updateCompletionFiles(false);
9751 		}
9752 	}
9753 }
9754 
stopPackageParser()9755 void Texstudio::stopPackageParser()
9756 {
9757 	if (latexStyleParser)
9758 		latexStyleParser->stop();
9759 }
9760 
packageParserFinished()9761 void Texstudio::packageParserFinished()
9762 {
9763 	delete latexStyleParser;
9764         latexStyleParser = nullptr;
9765 }
9766 
readinAllPackageNames()9767 void Texstudio::readinAllPackageNames()
9768 {
9769 	if (!packageListReader) {
9770 		// preliminarily use cached packages
9771 		QFileInfo cacheFileInfo = QFileInfo(QDir(configManager.configBaseDir), "packageCache.dat");
9772 		if (cacheFileInfo.exists()) {
9773             std::set<QString> cachedPackages = PackageScanner::readPackageList(cacheFileInfo.absoluteFilePath());
9774 			packageListReadCompleted(cachedPackages);
9775 		}
9776 		if (configManager.scanInstalledLatexPackages) {
9777 			// start reading actually installed packages
9778 			QString cmd_latex = buildManager.getCommandInfo(BuildManager::CMD_LATEX).getProgramNameUnquoted();
9779 			QString baseDir;
9780 			if (!QFileInfo(cmd_latex).isRelative())
9781 				baseDir = QFileInfo(cmd_latex).absolutePath() + "/";
9782 #ifdef Q_OS_WIN
9783 			bool isMiktex = false;
9784 			if (baseDir.contains("miktex", Qt::CaseInsensitive)) {
9785 				isMiktex = true;
9786 			} else if (!baseDir.contains("texlive", Qt::CaseInsensitive)) {
9787 				ExecProgram execProgram(baseDir + "latex.exe --version", "");
9788 				execProgram.execAndWait();
9789 				if (execProgram.m_normalRun && execProgram.m_standardOutput.contains("miktex", Qt::CaseInsensitive)) {
9790 					isMiktex = true;
9791 				}
9792 			}
9793 			if (isMiktex)
9794 				packageListReader = new MiktexPackageScanner(quotePath(baseDir + "mpm.exe"), configManager.configBaseDir, this);
9795 			else
9796 				packageListReader = new KpathSeaParser(quotePath(baseDir + "kpsewhich"), this); // TeXlive on windows uses kpsewhich
9797 #else
9798 			QString addPaths=BuildManager::resolvePaths(BuildManager::additionalSearchPaths);
9799 			packageListReader = new KpathSeaParser(quotePath(baseDir + "kpsewhich"), this,addPaths);
9800 #endif
9801             connect(packageListReader, SIGNAL(scanCompleted(std::set<QString>)), this, SLOT(packageListReadCompleted(std::set<QString>)));
9802 			packageListReader->start();
9803 		}
9804 	}
9805 }
9806 
packageListReadCompleted(std::set<QString> packages)9807 void Texstudio::packageListReadCompleted(std::set<QString> packages)
9808 {
9809 	latexPackageList = packages;
9810 	if (qobject_cast<PackageScanner *>(sender())) {
9811 		PackageScanner::savePackageList(packages, QFileInfo(QDir(configManager.configBaseDir), "packageCache.dat").absoluteFilePath());
9812 		packageListReader->wait();
9813 		delete packageListReader;
9814 		packageListReader = nullptr;
9815 	}
9816 	foreach (LatexDocument *doc, documents.getDocuments()) {
9817 		LatexEditorView *edView = doc->getEditorView();
9818 		if (edView)
9819 			edView->updatePackageFormats();
9820 	}
9821 }
9822 
clipboardText(const QClipboard::Mode & mode) const9823 QString Texstudio::clipboardText(const QClipboard::Mode &mode) const
9824 {
9825 	return QApplication::clipboard()->text(mode);
9826 }
9827 
setClipboardText(const QString & text,const QClipboard::Mode & mode)9828 void Texstudio::setClipboardText(const QString &text, const QClipboard::Mode &mode)
9829 {
9830 	QApplication::clipboard()->setText(text, mode);
9831 }
9832 
getVersion() const9833 int Texstudio::getVersion() const
9834 {
9835 	return TXSVERSION_NUMERIC;
9836 }
9837 
9838 /*!
9839  * This function is mainly intended for use in scripting
9840  * \a shortcut: textual representation of the keysequence, e.g. simulateKeyPress("Shift+Up")
9841  */
simulateKeyPress(const QString & shortcut)9842 void Texstudio::simulateKeyPress(const QString &shortcut)
9843 {
9844 	QKeySequence seq = QKeySequence::fromString(shortcut, QKeySequence::PortableText);
9845 #if (QT_VERSION>=QT_VERSION_CHECK(6,0,0))
9846     //TODO Qt6 ?
9847     if (seq.count() > 0) {
9848         QKeyCombination keyCombination = seq[0];
9849         int key = keyCombination.key();
9850         Qt::KeyboardModifiers modifiers = keyCombination.keyboardModifiers();
9851         // TODO: we could additionally provide the text for the KeyEvent (necessary for actually typing characters
9852         QKeyEvent *event = new QKeyEvent(QEvent::KeyPress, key, modifiers);
9853         QApplication::postEvent(QApplication::focusWidget(), event);
9854         event = new QKeyEvent(QEvent::KeyRelease, key, modifiers);
9855         QApplication::postEvent(QApplication::focusWidget(), event);
9856     }
9857 #else
9858         if (seq.count() > 0) {
9859         int key = seq[0] & ~Qt::KeyboardModifierMask;
9860 		Qt::KeyboardModifiers modifiers = static_cast<Qt::KeyboardModifiers>(seq[0]) & Qt::KeyboardModifierMask;
9861 		// TODO: we could additionally provide the text for the KeyEvent (necessary for actually typing characters
9862 		QKeyEvent *event = new QKeyEvent(QEvent::KeyPress, key, modifiers);
9863 		QApplication::postEvent(QApplication::focusWidget(), event);
9864 		event = new QKeyEvent(QEvent::KeyRelease, key, modifiers);
9865 		QApplication::postEvent(QApplication::focusWidget(), event);
9866         }
9867 #endif
9868 }
9869 
updateTexQNFA()9870 void Texstudio::updateTexQNFA()
9871 {
9872     updateTexLikeQNFA("(La)TeX", "tex.qnfa");
9873     updateTexLikeQNFA("Sweave", "sweave.qnfa");
9874     updateTexLikeQNFA("Pweave", "pweave.qnfa");
9875     updateUserMacros(false); //update macro triggers for languages
9876 }
9877 
9878 /*!
9879  * \brief Extends the given Language with dynamic information
9880  * \param languageName - the language name as specified in the language attribute of the <QNFA> tag.
9881  * \param filename - the filename for the language. This is just the filename without a path.
9882  * the file is searched for in the user language directory and as a fallback in the builtin language files.
9883  */
updateTexLikeQNFA(QString languageName,QString filename)9884 void Texstudio::updateTexLikeQNFA(QString languageName, QString filename)
9885 {
9886 	QLanguageFactory::LangData m_lang = m_languages->languageData(languageName);
9887 
9888 	QFile f(configManager.configBaseDir + "languages/" + filename);
9889 	if (!f.exists()) {
9890 		f.setFileName(findResourceFile("qxs/" + filename));
9891 	}
9892 	QDomDocument doc;
9893 
9894     if(!f.open(QFile::ReadOnly | QFile::Text))
9895         return;
9896 
9897     doc.setContent(&f);
9898 
9899 	// structure commands
9900     addStructureCommandsToDom(doc, latexParser.possibleCommands);
9901 
9902 	QLanguageDefinition *oldLangDef = nullptr, *newLangDef = nullptr;
9903 	oldLangDef = m_lang.d;
9904 	Q_ASSERT(oldLangDef);
9905 
9906 	// update editors using the language
9907 	QNFADefinition::load(doc, &m_lang, m_formats);
9908     // add single line comments
9909     QNFADefinition *nd=dynamic_cast<QNFADefinition *>(m_lang.d);
9910     nd->setSingleLineComment("%");
9911 
9912 	m_languages->addLanguage(m_lang);
9913 
9914 	newLangDef = m_lang.d;
9915 	Q_ASSERT(oldLangDef != newLangDef);
9916 
9917 	if (editors) {
9918 		documents.enablePatch(false);
9919         foreach (LatexDocument *doc, documents.getDocuments()) {
9920             LatexEditorView *edView=doc->getEditorView();
9921             if(edView) {
9922                 QEditor *ed = edView->editor;
9923                 if (ed->languageDefinition() == oldLangDef) {
9924                     ed->setLanguageDefinition(newLangDef);
9925                     // ed->highlight(); is executed by caller !
9926                 }
9927             }
9928         }
9929 		documents.enablePatch(true);
9930 	}
9931 }
9932 
toggleGrammar(int type)9933 void Texstudio::toggleGrammar(int type)
9934 {
9935 	QAction *a = qobject_cast<QAction *>(sender());
9936 	REQUIRE(a);
9937 	LatexEditorView::setGrammarOverlayDisabled(type, !a->isChecked());
9938 	//a->setChecked(!a->isChecked());
9939 	for (int i = 0; i < documents.documents.size(); i++)
9940 		if (documents.documents[i]->getEditorView())
9941 			documents.documents[i]->getEditorView()->updateGrammarOverlays();
9942 }
9943 
fileDiff()9944 void Texstudio::fileDiff()
9945 {
9946 	LatexDocument *doc = documents.currentDocument;
9947 	if (!doc)
9948 		return;
9949 
9950 	//remove old markers
9951 	removeDiffMarkers();
9952 
9953 	QString currentDir = QDir::homePath();
9954 	if (!configManager.lastDocument.isEmpty()) {
9955 		QFileInfo fi(configManager.lastDocument);
9956 		if (fi.exists() && fi.isReadable()) {
9957 			currentDir = fi.absolutePath();
9958 		}
9959 	}
9960 	QStringList files = FileDialog::getOpenFileNames(this, tr("Open Files"), currentDir, tr("LaTeX Files (*.tex);;All Files (*)"),  &selectedFileFilter);
9961 	if (files.isEmpty())
9962 		return;
9963 	//
9964 	LatexDocument *doc2 = diffLoadDocHidden(files.first());
9965 	doc2->setObjectName("diffObejct");
9966 	doc2->setParent(doc);
9967 	diffDocs(doc, doc2);
9968 	//delete doc2;
9969 
9970 	// show changes (by calling LatexEditorView::documentContentChanged)
9971 	LatexEditorView *edView = currentEditorView();
9972 	edView->documentContentChanged(0, edView->document->lines());
9973 }
9974 
jumpNextDiff()9975 void Texstudio::jumpNextDiff()
9976 {
9977 	QEditor *m_edit = currentEditor();
9978 	if (!m_edit)
9979 		return;
9980 	QDocumentCursor c = m_edit->cursor();
9981 	if (c.hasSelection()) {
9982 		int l, col;
9983 		c.endBoundary(l, col);
9984 		c.moveTo(l, col);
9985 	}
9986 	LatexDocument *doc = documents.currentDocument;
9987 
9988 	//search for next diff
9989 	int lineNr = c.lineNumber();
9990 	QVariant var = doc->line(lineNr).getCookie(QDocumentLine::DIFF_LIST_COOCKIE);
9991 	if (var.isValid()) {
9992 		DiffList lineData = var.value<DiffList>();
9993 		for (int j = 0; j < lineData.size(); j++) {
9994 			DiffOp op = lineData.at(j);
9995 			if (op.start + op.length > c.columnNumber()) {
9996 				c.moveTo(lineNr, op.start);
9997 				c.moveTo(lineNr, op.start + op.length, QDocumentCursor::KeepAnchor);
9998 				m_edit->setCursor(c);
9999 				return;
10000 			}
10001 		}
10002 	}
10003 	lineNr++;
10004 
10005 
10006 	for (; lineNr < doc->lineCount(); lineNr++) {
10007 		var = doc->line(lineNr).getCookie(QDocumentLine::DIFF_LIST_COOCKIE);
10008 		if (var.isValid()) {
10009 			break;
10010 		}
10011 	}
10012 	if (var.isValid()) {
10013 		DiffList lineData = var.value<DiffList>();
10014 		DiffOp op = lineData.first();
10015 		c.moveTo(lineNr, op.start);
10016 		c.moveTo(lineNr, op.start + op.length, QDocumentCursor::KeepAnchor);
10017 		m_edit->setCursor(c);
10018 	}
10019 }
10020 
jumpPrevDiff()10021 void Texstudio::jumpPrevDiff()
10022 {
10023 	QEditor *m_edit = currentEditor();
10024 	if (!m_edit)
10025 		return;
10026 	QDocumentCursor c = m_edit->cursor();
10027 	if (c.hasSelection()) {
10028 		int l, col;
10029 		c.beginBoundary(l, col);
10030 		c.moveTo(l, col);
10031 	}
10032 	LatexDocument *doc = documents.currentDocument;
10033 
10034 	//search for next diff
10035 	int lineNr = c.lineNumber();
10036 	QVariant var = doc->line(lineNr).getCookie(QDocumentLine::DIFF_LIST_COOCKIE);
10037 	if (var.isValid()) {
10038 		DiffList lineData = var.value<DiffList>();
10039 		for (int j = lineData.size() - 1; j >= 0; j--) {
10040 			DiffOp op = lineData.at(j);
10041 			if (op.start < c.columnNumber()) {
10042 				c.moveTo(lineNr, op.start);
10043 				c.moveTo(lineNr, op.start + op.length, QDocumentCursor::KeepAnchor);
10044 				m_edit->setCursor(c);
10045 				return;
10046 			}
10047 		}
10048 	}
10049 	lineNr--;
10050 
10051 
10052 	for (; lineNr >= 0; lineNr--) {
10053 		var = doc->line(lineNr).getCookie(QDocumentLine::DIFF_LIST_COOCKIE);
10054 		if (var.isValid()) {
10055 			break;
10056 		}
10057 	}
10058 	if (var.isValid()) {
10059 		DiffList lineData = var.value<DiffList>();
10060 		DiffOp op = lineData.last();
10061 		c.moveTo(lineNr, op.start);
10062 		c.moveTo(lineNr, op.start + op.length, QDocumentCursor::KeepAnchor);
10063 		m_edit->setCursor(c);
10064 	}
10065 }
10066 
removeDiffMarkers(bool theirs)10067 void Texstudio::removeDiffMarkers(bool theirs)
10068 {
10069 	LatexDocument *doc = documents.currentDocument;
10070     if (!doc || !doc->mayHaveDiffMarkers)
10071 		return;
10072 
10073 	diffRemoveMarkers(doc, theirs);
10074 	QList<QObject *>lst = doc->children();
10075 	foreach (QObject *o, lst)
10076 		delete o;
10077 
10078 	LatexEditorView *edView = currentEditorView();
10079 	edView->documentContentChanged(0, edView->document->lines());
10080 
10081 }
10082 
editChangeDiff(QPoint pt)10083 void Texstudio::editChangeDiff(QPoint pt)
10084 {
10085 	LatexDocument *doc = documents.currentDocument;
10086 	if (!doc)
10087 		return;
10088 
10089 	bool theirs = (pt.x() < 0);
10090 	int ln = pt.x();
10091 	if (ln < 0)
10092 		ln = -ln - 1;
10093 	int col = pt.y();
10094 
10095 	diffChange(doc, ln, col, theirs);
10096 	LatexEditorView *edView = currentEditorView();
10097 	edView->documentContentChanged(0, edView->document->lines());
10098 }
10099 
fileDiffMerge()10100 void Texstudio::fileDiffMerge()
10101 {
10102 	LatexDocument *doc = documents.currentDocument;
10103 	if (!doc)
10104 		return;
10105 
10106 	diffMerge(doc);
10107 
10108 	LatexEditorView *edView = currentEditorView();
10109 	edView->documentContentChanged(0, edView->document->lines());
10110 }
10111 
diffLoadDocHidden(QString f)10112 LatexDocument *Texstudio::diffLoadDocHidden(QString f)
10113 {
10114 	QString f_real = f;
10115 #ifdef Q_OS_WIN32
10116 	QRegExp regcheck("/([a-zA-Z]:[/\\\\].*)");
10117 	if (regcheck.exactMatch(f)) f_real = regcheck.cap(1);
10118 #endif
10119 
10120 	if (!QFile::exists(f_real)) return nullptr;
10121 
10122 	LatexDocument *doc = new LatexDocument(this);
10123 	//LatexEditorView *edit = new LatexEditorView(0,configManager.editorConfig,doc);
10124 
10125 	//edit->document=doc;
10126 	//edit->document->setEditorView(edit);
10127 
10128 	QFile file(f_real);
10129 	if (!file.open(QIODevice::ReadOnly)) {
10130 		QMessageBox::warning(this, tr("Error"), tr("You do not have read permission to this file."));
10131         delete doc;
10132 		return nullptr;
10133 	}
10134 	file.close();
10135 
10136 	//if (edit->editor->fileInfo().suffix().toLower() != "tex")
10137 	//	m_languages->setLanguage(edit->editor, f_real);
10138 
10139 	//QTime time;
10140 	//time.start();
10141 
10142 	//edit->editor->load(f_real,QDocument::defaultCodec());
10143 	doc->load(f_real, QDocument::defaultCodec());
10144 
10145 	//qDebug() << "Load time: " << time.elapsed();
10146 	//edit->editor->document()->setLineEndingDirect(edit->editor->document()->originalLineEnding());
10147 
10148 	//edit->document->setEditorView(edit); //update file name (if document didn't exist)
10149 
10150 
10151 	return doc;
10152 }
10153 
fileDiff3()10154 void Texstudio::fileDiff3()
10155 {
10156 	LatexDocument *doc = documents.currentDocument;
10157 	if (!doc)
10158 		return;
10159 
10160 	//remove old markers
10161 	removeDiffMarkers();
10162 
10163 	QString currentDir = QDir::homePath();
10164 	if (!configManager.lastDocument.isEmpty()) {
10165 		QFileInfo fi(configManager.lastDocument);
10166 		if (fi.exists() && fi.isReadable()) {
10167 			currentDir = fi.absolutePath();
10168 		}
10169 	}
10170 	QStringList files = FileDialog::getOpenFileNames(this, tr("Open Compare File"), currentDir, tr("LaTeX Files (*.tex);;All Files (*)"),  &selectedFileFilter);
10171 	if (files.isEmpty())
10172 		return;
10173 	QStringList basefiles = FileDialog::getOpenFileNames(this, tr("Open Base File"), currentDir, tr("LaTeX Files (*.tex);;All Files (*)"),  &selectedFileFilter);
10174 	if (basefiles.isEmpty())
10175 		return;
10176 	showDiff3(files.first(), basefiles.first());
10177 }
10178 
showDiff3(const QString file1,const QString file2)10179 void Texstudio::showDiff3(const QString file1, const QString file2)
10180 {
10181 	LatexDocument *doc = documents.currentDocument;
10182 	if (!doc)
10183 		return;
10184 
10185 	LatexDocument *doc2 = diffLoadDocHidden(file1);
10186 	doc2->setObjectName("diffObejct");
10187 	doc2->setParent(doc);
10188 	LatexDocument *docBase = diffLoadDocHidden(file2);
10189 	docBase->setObjectName("baseObejct");
10190 	docBase->setParent(doc);
10191 	diffDocs(doc2, docBase, true);
10192 	diffDocs(doc, doc2);
10193 
10194 	// show changes (by calling LatexEditorView::documentContentChanged)
10195 	LatexEditorView *edView = currentEditorView();
10196 	edView->documentContentChanged(0, edView->document->lines());
10197 }
10198 
declareConflictResolved()10199 void Texstudio::declareConflictResolved()
10200 {
10201 	LatexDocument *doc = documents.currentDocument;
10202 	if (!doc)
10203 		return;
10204 
10205 	QString fn = doc->getFileName();
10206 	QString cmd = BuildManager::CMD_SVN;
10207 	cmd += " resolve --accept working \"" + fn + ("\"");
10208 	setStatusMessageProcess(QString(" svn resolve conflict "));
10209 	QString buffer;
10210 	runCommandNoSpecialChars(cmd, &buffer);
10211 	checkin(fn, "txs: commit after resolve");
10212 }
10213 /*!
10214  * \brief mark svn conflict of current file resolved
10215  */
fileInConflictShowDiff()10216 void Texstudio::fileInConflictShowDiff()
10217 {
10218 	QEditor *mEditor = qobject_cast<QEditor *>(sender());
10219 	REQUIRE(mEditor);
10220 	if (!QFileInfo(mEditor->fileName()).exists())  //create new qfileinfo to avoid caching
10221 		return;
10222 	removeDiffMarkers();
10223 
10224 	if (!checkSVNConflicted(false)) {
10225 
10226 		LatexDocument *doc2 = diffLoadDocHidden(mEditor->fileName());
10227 		if (!doc2)
10228 			return;
10229 		LatexDocument *doc = qobject_cast<LatexDocument *>(mEditor->document());
10230 		doc2->setObjectName("diffObejct");
10231 		doc2->setParent(doc);
10232 		diffDocs(doc, doc2);
10233 		//delete doc2;
10234 
10235 		// show changes (by calling LatexEditorView::documentContentChanged)
10236 		LatexEditorView *edView = doc->getEditorView();
10237 		if (edView)
10238 			edView->documentContentChanged(0, edView->document->lines());
10239 	}
10240 }
10241 
checkSVNConflicted(bool substituteContents)10242 bool Texstudio::checkSVNConflicted(bool substituteContents)
10243 {
10244 	LatexDocument *doc = documents.currentDocument;
10245 	if (!doc)
10246 		return false;
10247 
10248 	QString fn = doc->getFileName();
10249 	QFileInfo qf(fn + ".mine");
10250 	if (qf.exists()) {
10251 		SVN::Status status = svn.status(fn);
10252 		if (status == SVN::InConflict) {
10253 			int ret = QMessageBox::warning(this,
10254 				tr("SVN Conflict!"),
10255 				tr(
10256 					"%1is conflicted with repository.\n"
10257 					"Press \"OK\" to show differences instead of the generated source by subversion\n"
10258 					"Press \"Cancel\"to do nothing.\n"
10259 				).arg(doc->getFileName()),
10260 				QMessageBox::Ok
10261 				|
10262 				QMessageBox::Cancel
10263 			);
10264 			if (ret == QMessageBox::Ok) {
10265 				QString path = qf.absolutePath();
10266 				QDir dir(path);
10267 				dir.setSorting(QDir::Name);
10268 				qf.setFile(fn);
10269 				QString filter = qf.fileName() + ".r*";
10270 				QFileInfoList list = dir.entryInfoList(QStringList(filter));
10271 				QStringList lst;
10272 				foreach (QFileInfo elem, list) {
10273 					lst << elem.absoluteFilePath();
10274 				}
10275 				if (lst.count() != 2)
10276 					return false;
10277 				if (substituteContents) {
10278 					QTextCodec *codec = doc->codec();
10279 					doc->load(fn + ".mine", codec);
10280 				}
10281 				QString baseFile = lst.takeFirst();
10282 				QString compFile = lst.takeFirst();
10283 				showDiff3(compFile, baseFile);
10284 				return true;
10285 			}
10286 		}
10287 	}
10288 	return false;
10289 }
10290 
10291 
10292 QThread *killAtCrashedThread = nullptr;
10293 QThread *lastCrashedThread = nullptr;
10294 
recover()10295 void recover()
10296 {
10297 	Texstudio::recoverFromCrash();
10298 }
10299 
recoverFromCrash()10300 void Texstudio::recoverFromCrash()
10301 {
10302 	bool wasLoop;
10303 	QString backtraceFilename;
10304 	QString name = getLastCrashInformation(wasLoop);
10305 	if (QThread::currentThread() != QCoreApplication::instance()->thread()) {
10306 		QThread *t = QThread::currentThread();
10307 		lastCrashedThread = t;
10308 		if (qobject_cast<SafeThread *>(t)) qobject_cast<SafeThread *>(t)->crashed = true;
10309 		QTimer::singleShot(0, txsInstance, SLOT(threadCrashed()));
10310 		while (!programStopped) {
10311 			ThreadBreaker::sleep(1);
10312 			if (t &&  t == killAtCrashedThread) {
10313                 name += QString(" forced kill in %1").arg(reinterpret_cast<long int>(t), sizeof(long int) * 2, 16, QChar('0'));
10314                 name += QString(" (TXS-Version %1 %2 )").arg(TEXSTUDIO_GIT_REVISION,COMPILED_DEBUG_OR_RELEASE);
10315 				backtraceFilename = print_backtrace(name);
10316 				exit(1);
10317 			}
10318         }
10319 		ThreadBreaker::forceTerminate();
10320 		return;
10321 	}
10322 
10323 	static int nestedCrashes = 0;
10324 
10325 	nestedCrashes++;
10326 
10327 	if (nestedCrashes > 5) {
10328 		qFatal("Forced kill after recovering failed after: %s\n", qPrintable(name));
10329         exit(1);
10330     }
10331 
10332 	fprintf(stderr, "crashed with signal %s\n", qPrintable(name));
10333 
10334 	if (nestedCrashes <= 2) {
10335         backtraceFilename = print_backtrace(name + QString(" (TXS-Version %1 %2 )").arg(TEXSTUDIO_GIT_REVISION,COMPILED_DEBUG_OR_RELEASE));
10336 	}
10337 
10338 	//hide editor views in case the error occured during drawing
10339 	foreach (LatexEditorView *edView, txsInstance->editors->editors())
10340 		edView->hide();
10341 
10342 	//save recover information
10343 	foreach (LatexEditorView *edView, txsInstance->editors->editors()) {
10344 	        QEditor *ed = edView ? edView->editor : nullptr;
10345 		if (ed && ed->isContentModified() && !ed->fileName().isEmpty())
10346 			ed->saveEmergencyBackup(ed->fileName() + ".recover.bak~");
10347 	}
10348 
10349 
10350 
10351 	QMessageBox *mb = new QMessageBox();  //Don't use the standard methods like ::critical, because they load an icon, which will cause a crash again with gtk. ; mb must be on the heap, or continuing a paused loop can crash
10352 	mb->setWindowTitle(tr("TeXstudio Emergency"));
10353 	QString backtraceMsg;
10354 	if (QFileInfo(backtraceFilename).exists()) {
10355 		qDebug() << backtraceFilename;
10356 		backtraceMsg = tr("A backtrace was written to\n%1\nPlease provide this file if you send a bug report.\n\n").arg(QDir::toNativeSeparators(backtraceFilename));
10357 	}
10358 	if (!wasLoop) {
10359         mb->setText(tr( "TeXstudio has CRASHED due to a %1.\n\n%2Do you want to keep TeXstudio running? This may cause data corruption.").arg(name,backtraceMsg));
10360 		mb->setDefaultButton(mb->addButton(tr("Yes, try to recover"), QMessageBox::AcceptRole));
10361 		mb->addButton(tr("No, kill the program"), QMessageBox::RejectRole); //can't use destructiverole, it always becomes rejectrole
10362 	} else {
10363 		mb->setText(tr( "TeXstudio has been paused due to a possible endless loop.\n\n%1Do you want to keep the program running? This may cause data corruption.").arg(backtraceMsg));
10364 		mb->setDefaultButton(mb->addButton(tr("Yes, stop the loop and try to recover"), QMessageBox::AcceptRole));
10365 		mb->addButton(tr("Yes, continue the loop"), QMessageBox::RejectRole);
10366 		mb->addButton(tr("No, kill the program"), QMessageBox::DestructiveRole);
10367 	}
10368 
10369 	//show the dialog (non modal, because on Windows showing the dialog modal here, permanently disables all other windows)
10370 	mb->setWindowFlags(Qt::WindowStaysOnTopHint);
10371 	mb->setWindowModality(Qt::NonModal);
10372 	mb->setModal(false);
10373 	mb->show();
10374 	QApplication::processEvents(QEventLoop::AllEvents);
10375 	mb->setFocus(); //without it, raise doesn't work. If it is in the loop (outside time checking if), the buttons can't be clicked on (windows)
10376     QElapsedTimer t;
10377 	t.start();
10378 	while (mb->isVisible()) {
10379 		QApplication::processEvents(QEventLoop::AllEvents);
10380 		if (t.elapsed() > 1000 ) {
10381 			mb->raise(); //sometimes the box is not visible behind the main window (windows)
10382 			t.restart();
10383 		}
10384 		ThreadBreaker::msleep(1);
10385 	}
10386 
10387 	//print edit history
10388 	int i = 0;
10389 	foreach (LatexEditorView *edView, txsInstance->editors->editors()) {
10390 		Q_ASSERT(edView);
10391 		if (!edView) continue;
10392 
10393 		QFile tf(QDir::tempPath() + QString("/texstudio_undostack%1%2.txt").arg(i++).arg(edView->editor->fileInfo().completeBaseName()));
10394 		if (tf.open(QFile::WriteOnly)) {
10395 			tf.write(edView->editor->document()->debugUndoStack(100).toLocal8Bit());
10396 			tf.close();
10397 		};
10398 	}
10399 
10400 
10401 	//fprintf(stderr, "result: %i, accept: %i, yes: %i, reject: %i, dest: %i\n",mb->result(),QMessageBox::AcceptRole,QMessageBox::YesRole,QMessageBox::RejectRole,QMessageBox::DestructiveRole);
10402 	if (mb->result() == QMessageBox::DestructiveRole || (!wasLoop && mb->result() == QMessageBox::RejectRole)) {
10403 		qFatal("Killed on user request after error: %s\n", qPrintable(name));
10404         exit(1);
10405     }
10406 	if (wasLoop && mb->result() == QMessageBox::RejectRole) {
10407 		delete mb;
10408 		Guardian::continueEndlessLoop();
10409 		while (true) ;
10410 	}
10411 
10412 	//restore editor views
10413 	foreach (LatexEditorView *edView, txsInstance->editors->editors())
10414 		edView->show();
10415 
10416 	if (!programStopped)
10417 		QApplication::processEvents(QEventLoop::AllEvents);
10418 
10419 	nestedCrashes = 0; //assume it was successfully recovered if it gets this far
10420 
10421 	while (!programStopped) {
10422 		QApplication::processEvents(QEventLoop::AllEvents);
10423 		ThreadBreaker::msleep(1);
10424 	}
10425 	name = "Normal close after " + name;
10426 	print_backtrace(name);
10427 	exit(0);
10428 }
10429 
threadCrashed()10430 void Texstudio::threadCrashed()
10431 {
10432 	bool wasLoop;
10433 	QString signal = getLastCrashInformation(wasLoop);
10434 	QThread *thread = lastCrashedThread;
10435 
10436 	QString threadName = "<unknown>";
10437     QString threadId = QString("%1").arg(reinterpret_cast<long>(thread), sizeof(long int) * 2, 16, QChar('0'));
10438 	if (qobject_cast<QThread *>(static_cast<QObject *>(thread)))
10439 		threadName = QString("%1 %2").arg(threadId).arg(qobject_cast<QThread *>(thread)->objectName());
10440 
10441 	fprintf(stderr, "crashed with signal %s in thread %s\n", qPrintable(signal), qPrintable(threadName));
10442 
10443 	int btn = QMessageBox::warning(this, tr("TeXstudio Emergency"),
10444                                    tr("TeXstudio has CRASHED due to a %1 in thread %2.\nThe thread has been stopped.\nDo you want to keep TeXstudio running? This may cause data corruption.").arg(signal,threadId),
10445 	                               tr("Yes"), tr("No, kill the program"));
10446 	if (btn) {
10447 		killAtCrashedThread = thread;
10448 		ThreadBreaker::sleep(10);
10449 		QMessageBox::warning(this, tr("TeXstudio Emergency"), tr("I tried to die, but nothing happened."));
10450 	}
10451 }
10452 
iamalive()10453 void Texstudio::iamalive()
10454 {
10455 	Guardian::calm();
10456 }
10457 
slowOperationStarted()10458 void Texstudio::slowOperationStarted()
10459 {
10460 	Guardian::instance()->slowOperationStarted();
10461 }
10462 
slowOperationEnded()10463 void Texstudio::slowOperationEnded()
10464 {
10465 	Guardian::instance()->slowOperationEnded();
10466 }
10467 /*!
10468  * \brief check current latex install as it is visible from txs
10469  */
checkLatexInstall()10470 void Texstudio::checkLatexInstall()
10471 {
10472 
10473 	QString result;
10474 	// check dpi
10475 	double dpi=QGuiApplication::primaryScreen()->logicalDotsPerInch();
10476 	result+=QString("dpi: %1\n").arg(dpi);
10477 	// run pdflatex
10478 	setStatusMessageProcess(QString("check pdflatex"));
10479 	QString buffer;
10480 	// create result editor here in order to avoid empty editor
10481 	fileNew(QFileInfo(QDir::temp(), tr("System Report") + ".txt").absoluteFilePath());
10482 	m_languages->setLanguageFromName(currentEditor(), "Plain text");
10483 
10484 	CommandInfo cmdInfo = buildManager.getCommandInfo(BuildManager::CMD_PDFLATEX);
10485 	QString cmd = cmdInfo.getProgramName();
10486 	// where is pdflatex located
10487 #ifdef Q_OS_WIN
10488 	runCommand("where " + cmd, &buffer);
10489 	result += "where pdflatex: " + buffer + "\n\n";
10490 #else
10491 	runCommand("which " + cmd, &buffer);
10492 	result += "which pdflatex: " + buffer + "\n\n";
10493 #endif
10494 	buffer.clear();
10495 	cmd += " -version";
10496 	// run pdflatex
10497 	runCommand(cmd, &buffer);
10498 	result += "PDFLATEX: " + cmd + "\n";
10499 	result += buffer;
10500 	result += "\n\n";
10501 	// check env
10502 	result += "Environment variables:\n";
10503 	buffer.clear();
10504 #ifdef Q_OS_WIN
10505 	// TODO: it's not guaranteed that a process started with runCommand has the same environment
10506 	// currently that should be the case, as long as we don't start to change the environment in
10507 	// runCommand before execution.
10508 	// The equivalent to printenv on windows would be set. However this is not a program, but a
10509 	// command directly built into cmd.com, so we cannot directly use runCommand("set");
10510 	buffer = QProcessEnvironment::systemEnvironment().toStringList().join("\n");
10511 #else
10512 	runCommand("printenv", &buffer);
10513 #endif
10514 	result += buffer + "\n";
10515 
10516 	result += "\nTeXstudio:\n";
10517 	result += "Path        : " + QDir::toNativeSeparators(QCoreApplication::applicationFilePath()) + "\n";
10518 	result += "Program call: " + QCoreApplication::arguments().join(" ") + "\n";
10519 	result += "Setting file: " + QDir::toNativeSeparators(configManager.configFileName) + "\n";
10520 
10521 	result += "\nCommand configuration in TeXstudio:\n";
10522 	const CommandMapping &cmds = buildManager.getAllCommands();
10523 	foreach (const CommandInfo &ci, cmds)
10524 		result += QString("    %1 (%2)%3: %4\n").arg(ci.displayName).arg(ci.id).arg(ci.rerunCompiler ? " (r)" : "").arg(ci.commandLine);
10525 
10526 	result += "\nAdditional Search Paths:\n";
10527 	result += "    Command: " + buildManager.additionalSearchPaths + "\n";
10528 	result += "    Log: " + buildManager.additionalLogPaths + "\n";
10529 	result += "    Pdf: " + buildManager.additionalPdfPaths + "\n";
10530 
10531 	//fileNew(QFileInfo(QDir::temp(), tr("System Report") + ".txt").absoluteFilePath());
10532 	//m_languages->setLanguageFromName(currentEditor(), "Plain text");
10533 	currentEditorView()->editor->setText(result, false);
10534 }
10535 /*!
10536  * \brief display which cwls are loaded.
10537  *
10538  * This function is for debugging.
10539  */
checkCWLs()10540 void Texstudio::checkCWLs()
10541 {
10542 	bool newFile = currentEditor();
10543 	if (!newFile) fileNew();
10544 
10545 	QList<LatexDocument *> docs = currentEditorView()->document->getListOfDocs();
10546 	QStringList res;
10547 	QSet<QString> cwls;
10548 	// collect user commands and references
10549 	foreach (LatexDocument *doc, docs) {
10550 		const QSet<QString> &cwl = doc->getCWLFiles();
10551 		cwls.unite(cwl);
10552         res << doc->getFileName() + ": " + QStringList(cwl.values()).join(", ");
10553 		QList<CodeSnippet> users = doc->userCommandList();
10554 		if (!users.isEmpty()) {
10555 			QString line = QString("\t%1 user commands: ").arg(users.size());
10556 			foreach (const CodeSnippet & cs, users) line += (line.isEmpty() ? "" : "; ") + cs.word;
10557 			res << line;
10558 		}
10559 	}
10560     cwls.unite(convertStringListtoSet(configManager.completerConfig->getLoadedFiles()));
10561 	res << "global: " << configManager.completerConfig->getLoadedFiles().join(", ");
10562 
10563 	res << "" << "";
10564 
10565 	foreach (QString s, cwls) {
10566 		res << QString("------------------- Package %1: ---------------------").arg(s);
10567 		LatexPackage package;
10568 		if (!documents.cachedPackages.contains(s)) {
10569 			res << "Package not cached (normal for global packages)";
10570 			package = loadCwlFile(s);
10571 		} else package = documents.cachedPackages.value(s);
10572 
10573 		res << "\tpossible commands";
10574 		foreach (const QString &key, package.possibleCommands.keys())
10575             res << QString("\t\t%1: %2").arg(key).arg(QStringList(package.possibleCommands.value(key).values()).join(", "));
10576 		res << "\tspecial def commands";
10577 		foreach (const QString &key, package.specialDefCommands.keys())
10578 			res << QString("\t\t%1: %2").arg(key).arg(package.specialDefCommands.value(key));
10579 		res << "\tspecial treatment commands";
10580 		foreach (const QString &key, package.specialDefCommands.keys()) {
10581 			QString line = QString("\t\t%1: ").arg(key);
10582 			foreach (const QPairQStringInt &pair, package.specialTreatmentCommands.value(key))
10583 				line += QString("%1 (%2)").arg(pair.first).arg(pair.second) + ", ";
10584 			line.chop(2);
10585 			res << line;
10586 		}
10587         res << QString("\toption Commands: %1").arg(QStringList(package.optionCommands.values()).join(", "));
10588 		QString line = QString("\tkinds: ");
10589 		foreach (const QString &key, package.commandDescriptions.keys()) {
10590 			const CommandDescription &cmd = package.commandDescriptions.value(key);
10591 			line += key + "(" + cmd.toDebugString() + "), ";
10592 		}
10593 		line.chop(2);
10594 		res << line;
10595 		line = QString("\tall commands: ");
10596 		foreach (const CodeSnippet & cs, package.completionWords) line += (line.isEmpty() ? "" : "; ") + cs.word;
10597 		res << line;
10598 		res << "" << "";
10599 	}
10600 
10601 	if (newFile) fileNew();
10602 	m_languages->setLanguageFromName(currentEditor(), "Plain text");
10603 	currentEditorView()->editor->setText(res.join("\n"), false);
10604 
10605 }
10606 /*!
10607  * \brief check if Language tool is set-up correctly and running
10608  */
checkLanguageTool()10609 void Texstudio::checkLanguageTool()
10610 {
10611 
10612     QString result;
10613     // run java
10614     setStatusMessageProcess(QString("check java"));
10615     QString buffer;
10616     // create result editor here in order to avoid empty editor
10617     fileNew(QFileInfo(QDir::temp(), tr("LT Report") + ".txt").absoluteFilePath());
10618     m_languages->setLanguageFromName(currentEditor(), "Plain text");
10619 
10620     QString cmd=configManager.grammarCheckerConfig->languageToolJavaPath;
10621 
10622     // where is pdflatex located
10623 #ifdef Q_OS_WIN
10624     runCommand("where " + quoteSpaces(cmd), &buffer);
10625     result = "where java: " + buffer + "\n\n";
10626 #else
10627     runCommand("which " + quoteSpaces(cmd), &buffer);
10628     result = "which java: " + buffer + "\n\n";
10629 #endif
10630     buffer.clear();
10631     // run pdflatex
10632     QProcess *javaProcess = new QProcess();
10633 
10634     result += "JAVA: " + cmd + "\n";
10635     javaProcess->start(cmd,QStringList("-version"));
10636     javaProcess->waitForFinished(500);
10637     int code=javaProcess->exitCode();
10638     switch (code) {
10639     case 0:
10640         buffer=javaProcess->readAllStandardError();
10641         break;
10642     case -2:
10643         buffer+=tr("process failed to start\n");
10644         break;
10645     default:
10646         buffer+=tr("process crashed\n");
10647         break;
10648     }
10649 
10650     delete javaProcess;
10651 
10652     result += buffer;
10653     result += "\n";
10654 
10655     if (configManager.editorConfig->realtimeChecking){
10656         result +=tr("Real-time checking is enabled.\n");
10657     }else{
10658         result +=tr("Real-time checking is disabled!!!\n");
10659     }
10660     if(configManager.editorConfig->inlineGrammarChecking) {
10661         result +=tr("Grammar checking is enabled.\n\n");
10662     }else{
10663         result +=tr("Grammar checking is disabled!!!\n\n");
10664     }
10665     if(configManager.grammarCheckerConfig->languageToolAutorun){
10666         result +=tr("Tries to start automatically.\n\n");
10667     }else{
10668         result +=tr("Autostart disabled.\n\n");
10669     }
10670 
10671     GrammarCheck::LTStatus st=grammarCheck->languageToolStatus();
10672 
10673     result +=tr("LT current status: ");
10674     switch (st) {
10675     case GrammarCheck::LTS_Working:
10676         result +=tr("working");
10677         break;
10678     case GrammarCheck::LTS_Error:
10679         result +=tr("error");
10680         result +="\n"+grammarCheck->getLastErrorMessage();
10681         break;
10682     case GrammarCheck::LTS_Unknown:
10683         result +=tr("unknown");
10684     }
10685     result += "\n\n";
10686     result +=tr("LT-URL: %1\n").arg(grammarCheck->serverUrl());
10687 
10688     currentEditorView()->editor->setText(result, false);
10689 }
10690 /*!
10691  * \brief load document hidden
10692  *
10693  * when parsing a document, child-documents can be loaded automatically.
10694  * They are loaded here into the hidden state.
10695  * \param filename
10696  */
addDocToLoad(QString filename)10697 void Texstudio::addDocToLoad(QString filename)
10698 {
10699 	//qDebug()<<"fname:"<<filename;
10700 	if (filename.isEmpty())
10701 		return;
10702 	load(filename, false, true, recheckLabels);
10703 }
10704 
10705 /*!
10706  * \brief open pdf documentation of latex packages in the internal viewer
10707  * \param package package name
10708  * \param command latex command to search within that documentation
10709  */
openInternalDocViewer(QString package,const QString command)10710 void Texstudio::openInternalDocViewer(QString package, const QString command)
10711 {
10712 #ifndef NO_POPPLER_PREVIEW
10713 	runInternalCommand("txs:///view-pdf-internal", QFileInfo(package), "--embedded");
10714 	QList<PDFDocument *> pdfs = PDFDocument::documentList();
10715 	if (pdfs.count() > 0) {
10716 		pdfs[0]->raise();
10717 		PDFDocument *pdf = pdfs.first();
10718 		pdf->goToPage(0);
10719 		pdf->doFindDialog(command);
10720 		if (!command.isEmpty())
10721 			pdf->search(command, false, false, false, false, false);
10722 	}
10723 #else
10724 	Q_UNUSED(package)
10725 	Q_UNUSED(command)
10726 #endif
10727 }
10728 /*!
10729  * \brief close a latex environment
10730  *
10731  * If the cursor is after a \begin{env} which is not closed by the end of the document, \end{env} is inserted.
10732  *
10733  * This function uses information which is generated by the syntax checker.
10734  * As the syntaxchecker works asynchronously, a small delay between typing and functioning of this action is mandatory though that delay is probably too small for any user to notice.
10735  */
closeEnvironment()10736 void Texstudio::closeEnvironment()
10737 {
10738 	LatexEditorView *edView = currentEditorView();
10739 	if (!edView)
10740 		return;
10741 	QEditor *m_edit = currentEditor();
10742 	if (!m_edit)
10743 		return;
10744 	QDocumentCursor cursor = m_edit->cursor();
10745 
10746 	// handle current line -- based on text parsing of current line
10747 	// the below method is not exact and will fail on certain edge cases
10748 	// for the time being this is good enough. An alternative approach may use the token system:
10749 	//   QDocumentLineHandle *dlh = edView->document->line(cursor.lineNumber()).handle();
10750     //   TokenList tl = dlh->getCookieLocked(QDocumentLine::LEXER_COOKIE).value<TokenList>();
10751 	if (cursor.columnNumber() > 0) {
10752 		QString text = cursor.line().text();
10753         QRegularExpression rxBegin = QRegularExpression("\\\\begin\\{([^}]+)\\}");
10754         QRegularExpressionMatch match;
10755         int beginCol = text.lastIndexOf(rxBegin, cursor.columnNumber()-1,&match);
10756 		while (beginCol >= 0) {
10757 			QDocumentCursor from, to;
10758 			QDocumentCursor c = cursor.clone(false);
10759 			c.setColumnNumber(beginCol);
10760 			c.getMatchingPair(from, to);
10761             QString endText = "\\end{" + match.captured(1) + "}";
10762 			if (!to.isValid() || to.selectedText() != endText) {
10763 				cursor.insertText(endText);
10764 				return;
10765 			}
10766 			beginCol -= 6;
10767 			if (beginCol < 0) break;
10768 			beginCol = text.lastIndexOf(rxBegin, beginCol);
10769 		}
10770 	}
10771 
10772 	// handle previous lines -- based on StackEnvironment / syntax checker
10773 	// This only works on a succeding line of the \begin{env} statement, not in the same line.
10774 	// Therefore the above separate handling of the current line.
10775 	StackEnvironment env;
10776 	LatexDocument *doc = edView->document;
10777 	doc->getEnv(cursor.lineNumber(), env);
10778 	int lineCount = doc->lineCount();
10779 	if (lineCount < 1)
10780 		return;
10781 	StackEnvironment env_end;
10782 	QDocumentLineHandle *dlh = edView->document->line(lineCount - 1).handle();
10783     QVariant envVar = dlh->getCookieLocked(QDocumentLine::STACK_ENVIRONMENT_COOKIE);
10784 	if (envVar.isValid())
10785 		env_end = envVar.value<StackEnvironment>();
10786 	else
10787 		return;
10788 
10789 	if (env.count() > 0 && env_end.count() > 0) {
10790 		Environment mostRecentEnv = env.pop();
10791 		while (!env_end.isEmpty()) {
10792 			Environment e = env_end.pop();
10793 			if (env_end.isEmpty()) // last env is for internal use only
10794 				break;
10795 			if (e == mostRecentEnv) { // found, now close it
10796 				QString txt;
10797 				if (e.origName.isEmpty()) {
10798 					txt = "\\end{" + e.name + "}";
10799 				} else {
10800 					txt = e.origName;
10801 					int i = latexParser.mathStartCommands.indexOf(txt);
10802 					txt = latexParser.mathStopCommands.value(i);
10803 				}
10804 				m_edit->insertText(txt);
10805 				break;
10806 			}
10807 		}
10808 	}
10809 }
10810 
10811 
10812 
10813 
10814 /*!
10815  * \brief make embedded viewer larger so that it covers the text edit
10816  * If the viewer is not embedded, no action is performed.
10817  */
enlargeEmbeddedPDFViewer()10818 void Texstudio::enlargeEmbeddedPDFViewer()
10819 {
10820 #ifndef NO_POPPLER_PREVIEW
10821 	QList<PDFDocument *> oldPDFs = PDFDocument::documentList();
10822 	if (oldPDFs.isEmpty())
10823 		return;
10824 	PDFDocument *viewer = oldPDFs.first();
10825 	if (!viewer->embeddedMode)
10826 		return;
10827 	sidePanelSplitter->hide();
10828 	configManager.viewerEnlarged = true;
10829 	PDFDocumentConfig *pdfConfig=configManager.pdfDocumentConfig;
10830 	if(!enlargedViewer){
10831 		rememberFollowFromScroll=pdfConfig->followFromScroll;
10832 	}
10833 	enlargedViewer=true;
10834 	pdfConfig->followFromScroll=false;
10835 	viewer->setStateEnlarged(true);
10836 #endif
10837 }
10838 /*!
10839  * \brief set size of embedded viewer back to previous value
10840  * \param preserveConfig note change in config
10841  */
shrinkEmbeddedPDFViewer(bool preserveConfig)10842 void Texstudio::shrinkEmbeddedPDFViewer(bool preserveConfig)
10843 {
10844 #ifndef NO_POPPLER_PREVIEW
10845 	sidePanelSplitter->show();
10846 	if (!preserveConfig)
10847 		configManager.viewerEnlarged = false;
10848 	QList<PDFDocument *> oldPDFs = PDFDocument::documentList();
10849 	if (oldPDFs.isEmpty())
10850 		return;
10851 	PDFDocument *viewer = oldPDFs.first();
10852 	if (!viewer->embeddedMode)
10853 		return;
10854 	if(enlargedViewer){
10855 		PDFDocumentConfig *pdfConfig=configManager.pdfDocumentConfig;
10856 		pdfConfig->followFromScroll=rememberFollowFromScroll;
10857 		enlargedViewer=false;
10858 	}
10859 	viewer->setStateEnlarged(false);
10860 #else
10861 	Q_UNUSED(preserveConfig)
10862 #endif
10863 }
10864 
showStatusbar()10865 void Texstudio::showStatusbar()
10866 {
10867 	QAction *act = qobject_cast<QAction *>(sender());
10868 	if (act) {
10869 		statusBar()->setVisible(act->isChecked());
10870 		configManager.setOption("View/ShowStatusbar", act->isChecked());
10871 	}
10872 }
10873 /*!
10874  * \brief open extended search in bottom panel
10875  *
10876  * This is called from the editor search panel by pressing '+'.
10877  */
showExtendedSearch()10878 void Texstudio::showExtendedSearch()
10879 {
10880 	LatexEditorView *edView = currentEditorView();
10881 	if (!edView) return;
10882 
10883 	bool isWord = edView->getSearchIsWords();
10884 	bool isCase = edView->getSearchIsCase();
10885 	bool isReg = edView->getSearchIsRegExp();
10886 	SearchQuery *query = new SearchQuery(edView->getSearchText(), edView->getReplaceText(), isCase, isWord, isReg);
10887 	query->setScope(searchResultWidget()->searchScope());
10888 	searchResultWidget()->setQuery(query);
10889 	outputView->showPage(outputView->SEARCH_RESULT_PAGE);
10890 	runSearch(query);
10891 }
10892 /*!
10893  * \brief change icon size of toolbars
10894  *
10895  * This is intended to have larger symbols on high-resolution screens.
10896  * The change is instantly performed from the config dialog as visual feed-back.
10897  * \param value size in points
10898  */
changeIconSize(int value)10899 void Texstudio::changeIconSize(int value)
10900 {
10901 	// adapt icon size to dpi
10902 	double dpi=QGuiApplication::primaryScreen()->logicalDotsPerInch();
10903 	double scale=dpi/96;
10904 
10905 	int iconWidth=qRound(value*scale);
10906 
10907 	setIconSize(QSize(iconWidth, iconWidth));
10908 #ifndef NO_POPPLER_PREVIEW
10909 	foreach (PDFDocument *pdfviewer, PDFDocument::documentList()) {
10910 		if (!pdfviewer->embeddedMode) pdfviewer->setToolbarIconSize(iconWidth);
10911 	}
10912 #endif
10913 }
10914 /*!
10915  * \brief change icon size for central tool-bar
10916  *
10917  * This is intended to have larger symbols on high-resolution screens.
10918  * The change is instantly performed from the config dialog as visual feed-back.
10919  * \param value size in points
10920  */
changeSecondaryIconSize(int value)10921 void Texstudio::changeSecondaryIconSize(int value)
10922 {
10923 	// adapt icon size to dpi
10924 	double dpi=QGuiApplication::primaryScreen()->logicalDotsPerInch();
10925 	double scale=dpi/96;
10926 
10927 	int iconWidth=qRound(value*scale);
10928 
10929 	centralToolBar->setIconSize(QSize(iconWidth, iconWidth));
10930 	leftPanel->setToolbarIconSize(iconWidth);
10931 
10932 	foreach (QObject *c, statusBar()->children()) {
10933 		QAbstractButton *bt = qobject_cast<QAbstractButton *>(c);
10934 		if (bt) {
10935 			bt->setIconSize(QSize(iconWidth, iconWidth));
10936 		}
10937 	}
10938 }
10939 /*!
10940  * \brief change icon size of embbedded pdf viewer toolbar
10941  * \param value
10942  */
changePDFIconSize(int value)10943 void Texstudio::changePDFIconSize(int value){
10944     // adapt icon size to dpi
10945     double dpi=QGuiApplication::primaryScreen()->logicalDotsPerInch();
10946     double scale=dpi/96;
10947 
10948     int iconWidth=qRound(value*scale);
10949 
10950 #ifndef NO_POPPLER_PREVIEW
10951         foreach (PDFDocument *pdfviewer, PDFDocument::documentList()) {
10952                 if (pdfviewer->embeddedMode) pdfviewer->setToolbarIconSize(iconWidth);
10953         }
10954 #endif
10955 }
10956 /*!
10957  * \brief change symbol grid icon size
10958  *
10959  * This is intended to have larger symbols on high-resolution screens.
10960  * The change is instantly performed from the config dialog as visual feed-back.
10961  * \param value size in points
10962  * \param changePanel change to a symbolgrid in sidepanel in order to make the change directly visible
10963  */
changeSymbolGridIconSize(int value,bool changePanel)10964 void Texstudio::changeSymbolGridIconSize(int value, bool changePanel)
10965 {
10966 	// adapt icon size to dpi
10967 	double dpi=QGuiApplication::primaryScreen()->logicalDotsPerInch();
10968 	double scale=dpi/96;
10969 
10970 	int iconWidth=qRound(value*scale);
10971 
10972 	if (changePanel) {
10973 		leftPanel->setCurrentWidget(leftPanel->widget("symbols"));
10974 	}
10975 	symbolWidget->setSymbolSize(iconWidth);
10976 }
10977 /*!
10978  * \brief displays error messages from network replies which are used to communicate with LT
10979  * \param message
10980  */
10981 
LTErrorMessage(QString message)10982 void Texstudio::LTErrorMessage(QString message){
10983 	// adapt icon size to dpi
10984 	double dpi=QGuiApplication::primaryScreen()->logicalDotsPerInch();
10985 	double scale=dpi/96;
10986 
10987 	int iconWidth=qRound(configManager.guiSecondaryToolbarIconSize*scale);
10988 
10989 	QIcon icon = getRealIconCached("languagetool");
10990 	QSize iconSize = QSize(iconWidth, iconWidth);
10991 	statusLabelLanguageTool->setPixmap(icon.pixmap(iconSize, QIcon::Disabled));
10992 	statusLabelLanguageTool->setToolTip(QString(tr("Error when communicating with LT: %1")).arg(message));
10993 }
10994 
10995 /*!
10996  * \brief react to changed palette
10997  * i.e. change form light- to dark-mode and vice-versa
10998  * \param palette new palette
10999  */
paletteChanged(const QPalette & palette)11000 void Texstudio::paletteChanged(const QPalette &palette){
11001     bool oldDarkMode=darkMode;
11002     bool newDarkMode=systemUsesDarkMode(palette);
11003     if(newDarkMode != oldDarkMode && !configManager.useTexmakerPalette){
11004         darkMode=newDarkMode;
11005         // load appropriate syntax highlighting scheme
11006         QSettings *config=configManager.getSettings();
11007         config->beginGroup(darkMode ? "formatsDark" : "formats");
11008         m_formats = new QFormatFactory(darkMode ? ":/qxs/defaultFormatsDark.qxf" : ":/qxs/defaultFormats.qxf", this); //load default formats from resource file
11009         m_formats->load(*config, true); //load customized formats
11010         QDocument::setDefaultFormatScheme(m_formats);
11011         //m_formats->modified=true;
11012         config->endGroup();
11013     }
11014     foreach (LatexEditorView *edView, editors->editors()) {
11015         QEditor *ed = edView->editor;
11016         edView->updatePalette(palette);
11017         ed->document()->markFormatCacheDirty();
11018         ed->update();
11019         QSearchReplacePanel *searchpanel = qobject_cast<QSearchReplacePanel *>(edView->codeeditor->panels("Search")[0]);
11020         searchpanel->updateIcon();
11021     }
11022 }
11023 /*!
11024  * \brief open webpage with txs issue submit
11025  */
openBugsAndFeatures()11026 void Texstudio::openBugsAndFeatures() {
11027 	QDesktopServices::openUrl(QUrl("https://github.com/texstudio-org/texstudio/issues/"));
11028 }
11029 /*!
11030     \brief call updateTOC & updateStructureLocally as only one call works with a signal
11031  */
updateTOCs()11032 void Texstudio::updateTOCs(){
11033     updateTOC();
11034     updateStructureLocally();
11035 }
11036 
11037 /*!
11038  * \brief Collect structure info from all subfiles and create a toplevel TOC
11039  *
11040  */
updateTOC()11041 void Texstudio::updateTOC(){
11042     if(!topTOCTreeWidget->isVisible()) return; // don't update if TOC is not shown, save unnecessary effort
11043     QTreeWidgetItem *root=topTOCTreeWidget->topLevelItem(0);
11044     StructureEntry *selectedEntry=nullptr;
11045     bool itemExpanded=false;
11046     if(!root){
11047         root=new QTreeWidgetItem();
11048     }else{
11049         // get current selected item, check only first and deduce structureEntry
11050         QList<QTreeWidgetItem*> selected=topTOCTreeWidget->selectedItems();
11051         if(!selected.isEmpty()){
11052             QTreeWidgetItem *item=selected.first();
11053             if(item){
11054                 selectedEntry = item->data(0,Qt::UserRole).value<StructureEntry *>();
11055             }
11056         }
11057         // remove all item in topTOC but keep itemTODO
11058         QTreeWidgetItem *itemTODO=root->child(0);
11059         if(itemTODO && itemTODO->data(0,Qt::UserRole+1).toString()=="TODO"){
11060             itemExpanded=itemTODO->isExpanded();
11061         }
11062         QList<QTreeWidgetItem*> items=root->takeChildren();
11063         qDeleteAll(items);
11064     }
11065     QVector<QTreeWidgetItem *>rootVector(latexParser.MAX_STRUCTURE_LEVEL,root);
11066     // fill TOC, starting by current master/top
11067     LatexDocument *doc=documents.getRootDocumentForDoc();
11068     if(!doc){
11069         // no root document
11070         // clear TOC completely
11071         topTOCTreeWidget->clear();
11072         return;
11073     }
11074     root->setText(0,doc->getFileInfo().fileName());
11075 
11076     StructureEntry *base=doc->baseStructure;
11077     QList<QTreeWidgetItem*> todoList;
11078     parseStruct(base,rootVector,nullptr,&todoList);
11079     topTOCTreeWidget->insertTopLevelItem(0,root);
11080     if(!todoList.isEmpty()){
11081         QTreeWidgetItem *itemTODO=new QTreeWidgetItem();
11082         itemTODO->setText(0,tr("TODO"));
11083         itemTODO->setData(0,Qt::UserRole+1,"TODO");
11084         itemTODO->insertChildren(0,todoList);
11085         root->insertChild(0,itemTODO);
11086         itemTODO->setExpanded(itemExpanded);
11087     }
11088     root->setExpanded(true);
11089     root->setSelected(false);
11090     updateCurrentPosInTOC(nullptr,nullptr,selectedEntry);
11091 }
11092 /*!
11093  * \brief update marking of current position in global TOC
11094  *
11095  * Works recursively.
11096  * \param root nullptr at the start, treewidgetitem of which the children need to be checked later.
11097  * \param old  previously marked section of which the mark needs to be removed
11098  * \param selected  selected section
11099  */
updateCurrentPosInTOC(QTreeWidgetItem * root,StructureEntry * old,StructureEntry * selected)11100 void Texstudio::updateCurrentPosInTOC(QTreeWidgetItem* root, StructureEntry *old, StructureEntry *selected)
11101 {
11102     if(!topTOCTreeWidget->isVisible() && !structureTreeWidget->isVisible()) return; // don't update if TOC is not shown, save unnecessary effort
11103     const QColor activeItemColor(UtilsUi::mediumLightColor(QPalette().color(QPalette::Highlight), 75));
11104     bool tocMode=topTOCTreeWidget->isVisible();
11105     if(!root){
11106         if(topTOCTreeWidget->isVisible()){
11107             root=topTOCTreeWidget->topLevelItem(0);
11108         }else{
11109             root=nullptr;
11110             for(int i=0;i<structureTreeWidget->topLevelItemCount();++i){
11111                 QTreeWidgetItem* item=structureTreeWidget->topLevelItem(i);
11112                 StructureEntry *se = item->data(0,Qt::UserRole).value<StructureEntry *>();
11113                 if(old && old->document!=documents.getCurrentDocument() && se->document==old->document){
11114                     // remove cursor mark from structureView of not current document (after document switch)
11115                     updateCurrentPosInTOC(item,old);
11116                     if(root)
11117                         break; // no need to search further
11118                 }
11119                 if(se->document == documents.getCurrentDocument()){
11120                     root=item;
11121                 }
11122             }
11123         }
11124     }
11125     if(!root) return;
11126     for(int i=0;i<root->childCount();++i){
11127         QTreeWidgetItem *item=root->child(i);
11128         StructureEntry *se = item->data(0,Qt::UserRole).value<StructureEntry *>();
11129         if(selected && selected==se){
11130             item->setSelected(true);
11131         }
11132         if(old && se==old){
11133             bool hasColor=item->data(0,Qt::UserRole+1).isValid();
11134             QBrush bck=item->data(0,Qt::UserRole+1).value<QBrush>();
11135             if(tocMode || hasColor){
11136                 item->setBackground(0,bck);
11137             }else{
11138                 item->setBackground(0,palette().brush(QPalette::Base));
11139             }
11140         }
11141         if(currentSection && (se==currentSection)){
11142             item->setData(0,Qt::UserRole+1,item->background(0));
11143             item->setBackground(0,activeItemColor);
11144             if (!mDontScrollToItem && configManager.structureScrollToCurrentPosition){
11145                 if(tocMode){
11146                     topTOCTreeWidget->scrollToItem(item);
11147                 }else{
11148                     structureTreeWidget->scrollToItem(item);
11149                 }
11150             }
11151         }
11152         updateCurrentPosInTOC(item,old);
11153     }
11154 }
11155 /*!
11156  * \brief parse children of a structure entry se to collect TOC data
11157  * \param se
11158  * \param rootVector
11159  * \return section elements found (true/false)
11160  */
parseStruct(StructureEntry * se,QVector<QTreeWidgetItem * > & rootVector,QSet<LatexDocument * > * visited,QList<QTreeWidgetItem * > * todoList,int currentColor)11161 bool Texstudio::parseStruct(StructureEntry* se, QVector<QTreeWidgetItem *> &rootVector, QSet<LatexDocument*> *visited,QList<QTreeWidgetItem*> *todoList,int currentColor) {
11162     bool elementsAdded=false;
11163     bool deleteVisitedDocs=false;
11164     if (!visited) {
11165         visited = new QSet<LatexDocument *>();
11166         deleteVisitedDocs = true;
11167     }
11168     QColor colors[6];
11169     const char nrColors=6;
11170     if(darkMode){
11171         for(int i=0;i<nrColors;++i){
11172             if(configManager.globalTOCbackgroundOptions==1){
11173                 int hue=140;
11174                 colors[i]=QColor::fromHsv(i%2==0 ? hue:hue+30,240-60*(i/2),180);
11175             }else{
11176                 int hue=240;
11177                 colors[i]=QColor::fromHsv(i%2==0 ? hue:hue-30,240-30*(i/2),120);
11178             }
11179         }
11180     }else{
11181         for(int i=0;i<nrColors;++i){
11182             int hue=configManager.globalTOCbackgroundOptions==1 ? 140 : 240;
11183             colors[i]=QColor::fromHsv(i%2==0 ? hue:hue-30,70-35*(i/2),240);
11184         }
11185     }
11186 
11187     char offset=0;
11188     QString docName=se->document->getName();
11189     foreach(StructureEntry* elem,se->children){
11190         if(todoList && (elem->type == StructureEntry::SE_OVERVIEW)&&(elem->title=="TODO")){
11191             parseStruct(elem,rootVector,visited,todoList);
11192         }
11193         if(todoList && (elem->type == StructureEntry::SE_TODO)){
11194             QTreeWidgetItem * item=new QTreeWidgetItem();
11195             item->setData(0,Qt::UserRole,QVariant::fromValue<StructureEntry *>(elem));
11196             item->setText(0,elem->title);
11197             item->setToolTip(0,tr("Document: ")+docName);
11198             todoList->append(item);
11199         }
11200         if(elem->type == StructureEntry::SE_SECTION){
11201             QTreeWidgetItem * item=new QTreeWidgetItem();
11202             item->setData(0,Qt::UserRole,QVariant::fromValue<StructureEntry *>(elem));
11203             elementsAdded=true;
11204             item->setText(0,elem->title);
11205             item->setToolTip(0,tr("Document: ")+docName);
11206             item->setIcon(0,iconSection.value(elem->level));
11207             if(configManager.globalTOCbackgroundOptions>0){
11208                 item->setBackground(0,colors[currentColor]);
11209             }
11210             rootVector[elem->level]->addChild(item);
11211             item->setExpanded(elem->expanded);
11212             // fill rootVector with item for subsequent lower level elements (which are children of item then)
11213             for(int i=elem->level+1;i<latexParser.MAX_STRUCTURE_LEVEL;i++){
11214                 rootVector[i]=item;
11215             }
11216             parseStruct(elem,rootVector,visited,todoList,currentColor);
11217         }
11218         if(elem->type == StructureEntry::SE_INCLUDE){
11219             LatexDocument *doc=elem->document;
11220             //QString fn=ensureTrailingDirSeparator(doc->getRootDocument()->getFileInfo().absolutePath())+elem->title;
11221             QFileInfo fi(doc->getRootDocument()->getFileInfo().absolutePath(),elem->title);
11222             doc=documents.findDocumentFromName(fi.absoluteFilePath());
11223             if(!doc){
11224                 doc=documents.findDocumentFromName(fi.absoluteFilePath()+".tex");
11225             }
11226             bool ea=false;
11227             if(doc &&!visited->contains(doc)){
11228                 visited->insert(doc);
11229                 ea=parseStruct(doc->baseStructure,rootVector,visited,todoList,(currentColor+1+offset)%nrColors);
11230             }
11231             if(!ea){
11232                 QTreeWidgetItem * item=new QTreeWidgetItem();
11233                 item->setData(0,Qt::UserRole,QVariant::fromValue<StructureEntry *>(elem));
11234                 item->setText(0,elem->title);
11235                 item->setToolTip(0,tr("Document: ")+docName);
11236                 item->setIcon(0,QIcon(":/images/include.png"));
11237                 if(configManager.globalTOCbackgroundOptions>0){
11238                     item->setBackground(0,colors[currentColor]);
11239                 }
11240                 rootVector[latexParser.MAX_STRUCTURE_LEVEL-1]->addChild(item);
11241             }else{
11242                 offset=(offset+1)&1; //toggle between 0 & 1
11243             }
11244             elementsAdded=true;
11245         }
11246     }
11247     if(deleteVisitedDocs){
11248         delete visited;
11249         visited=nullptr;
11250     }
11251     return elementsAdded;
11252 }
11253 /*!
11254  * \brief sync expanded state to structure entry
11255  * \param item
11256  */
syncExpanded(QTreeWidgetItem * item)11257 void Texstudio::syncExpanded(QTreeWidgetItem *item){
11258     StructureEntry *se=item->data(0,Qt::UserRole).value<StructureEntry *>();
11259     if(!se) return;
11260     se->expanded=true;
11261 }
11262 
11263 /*!
11264  * \brief sync collapsed state to structure entry
11265  * \param item
11266  */
syncCollapsed(QTreeWidgetItem * item)11267 void Texstudio::syncCollapsed(QTreeWidgetItem *item){
11268     StructureEntry *se=item->data(0,Qt::UserRole).value<StructureEntry *>();
11269     if(!se) return;
11270     se->expanded=false;
11271 }
11272 
11273 
11274 
11275 /*!
11276  * \brief custom context menu for structureWidget
11277  * \param pos mouse position when clicked
11278  */
customMenuStructure(const QPoint & pos)11279 void Texstudio::customMenuStructure(const QPoint &pos){
11280     QTreeWidget* w = structureTreeWidget->isVisible() ? structureTreeWidget : topTOCTreeWidget ;
11281     QTreeWidgetItem *item = w->itemAt(pos);
11282     if(!item) return;
11283     StructureEntry *contextEntry = item->data(0,Qt::UserRole).value<StructureEntry *>();
11284     if (!contextEntry) return;
11285     if (contextEntry->type == StructureEntry::SE_DOCUMENT_ROOT) {
11286         QMenu menu;
11287         if (contextEntry->document != documents.masterDocument) {
11288             menu.addAction(tr("Close document"), this, SLOT(closeDocument()))->setData(QVariant::fromValue<LatexDocument *>(contextEntry->document));
11289             menu.addAction(tr("Set as explicit root document"), this, SLOT(toggleMasterDocument()))->setData(QVariant::fromValue<LatexDocument *>(contextEntry->document));
11290             menu.addAction(tr("Open all related documents"), this, SLOT(openAllRelatedDocuments()))->setData(QVariant::fromValue<LatexDocument *>(contextEntry->document));
11291             menu.addAction(tr("Close all related documents"), this, SLOT(closeAllRelatedDocuments()))->setData(QVariant::fromValue<LatexDocument *>(contextEntry->document));
11292         } else
11293             menu.addAction(tr("Remove explicit root document role"), this, SLOT(toggleMasterDocument()))->setData(QVariant::fromValue<LatexDocument *>(contextEntry->document));
11294         if (configManager.structureShowSingleDoc) {
11295             menu.addAction(tr("Show all open documents in this tree"), this, SLOT(toggleSingleDocMode()));
11296         } else {
11297             menu.addAction(tr("Show only current document in this tree"), this, SLOT(toggleSingleDocMode()));
11298         }
11299         /*menu.addSeparator();
11300         menu.addAction(tr("Move document to &front"), this, SLOT(moveDocumentToFront()))->setData(QVariant::fromValue<LatexDocument *>(contextEntry->document));
11301         menu.addAction(tr("Move document to &end"), this, SLOT(moveDocumentToEnd()))->setData(QVariant::fromValue<LatexDocument *>(contextEntry->document));
11302         menu.addSeparator();
11303         menu.addAction(tr("Expand Subitems"), this, SLOT(expandSubitems()));
11304         menu.addAction(tr("Collapse Subitems"), this, SLOT(collapseSubitems()));
11305         menu.addAction(tr("Expand all documents"), this, SLOT(expandAllDocuments()));
11306         menu.addAction(tr("Collapse all documents"), this, SLOT(collapseAllDocuments()));*/
11307         menu.addSeparator();
11308         menu.addAction(tr("Copy filename"), this, SLOT(copyFileName()))->setData(QVariant::fromValue<LatexDocument *>(contextEntry->document));
11309         menu.addAction(tr("Copy file path"), this, SLOT(copyFilePath()))->setData(QVariant::fromValue<LatexDocument *>(contextEntry->document));
11310         //menu.addAction(msgGraphicalShellAction(), this, SLOT(showInGraphicalShell_()));
11311         menu.exec(w->mapToGlobal(pos));
11312         return;
11313     }
11314     if (contextEntry->type == StructureEntry::SE_LABEL) {
11315         QMenu menu;
11316         menu.addAction(tr("Insert"), this, SLOT(insertTextFromAction()))->setData(contextEntry->title);
11317         menu.addAction(tr("Insert as %1").arg("\\ref{...}"), this, SLOT(insertTextFromAction()))->setData(QString("\\ref{%1}").arg(contextEntry->title));
11318         menu.addAction(tr("Insert as %1").arg("\\pageref{...}"), this, SLOT(insertTextFromAction()))->setData(QString("\\pageref{%1}").arg(contextEntry->title));
11319         menu.addSeparator();
11320         QAction *act = menu.addAction(tr("Find Usages"), this, SLOT(findLabelUsagesFromAction()));
11321         act->setData(contextEntry->title);
11322         act->setProperty("doc", QVariant::fromValue<LatexDocument *>(contextEntry->document));
11323         menu.exec(w->mapToGlobal(pos));
11324         return;
11325     }
11326     if (contextEntry->type == StructureEntry::SE_SECTION) {
11327         QMenu menu(this);
11328 
11329         StructureEntry *labelEntry = labelForStructureEntry(contextEntry);
11330         if (labelEntry) {
11331             menu.addAction(tr("Insert Label"), this, SLOT(insertTextFromAction()))->setData(labelEntry->title); // a bit indirect approach, the code should be refactored ...
11332             foreach (QString refCmd, configManager.referenceCommandsInContextMenu.split(",")) {
11333                 refCmd = refCmd.trimmed();
11334                 if (!refCmd.startsWith('\\')) continue;
11335                 menu.addAction(QString(tr("Insert %1 to Label", "autoreplaced, e.g.: Insert \\ref to Label").arg(refCmd)), this, SLOT(insertTextFromAction()))->setData(QString("%1{%2}").arg(refCmd).arg(labelEntry->title));
11336             }
11337             menu.addSeparator();
11338         } else {
11339             menu.addAction(tr("Create Label"), this, SLOT(createLabelFromAction()))->setData(QVariant::fromValue(contextEntry));
11340             menu.addSeparator();
11341         }
11342 
11343         menu.addAction(tr("Cut"), this, SLOT(editSectionCut()));
11344         menu.addAction(tr("Copy"), this, SLOT(editSectionCopy()));
11345         menu.addAction(tr("Paste Before"), this, SLOT(editSectionPasteBefore()));
11346         menu.addAction(tr("Paste After"), this, SLOT(editSectionPasteAfter()));
11347         menu.addSeparator();
11348         menu.addAction(tr("Indent Section"), this, SLOT(editIndentSection()));
11349         menu.addAction(tr("Unindent Section"), this, SLOT(editUnIndentSection()));
11350         if (!contextEntry->children.isEmpty()) {
11351             menu.addSeparator();
11352             menu.addAction(tr("Expand Subitems"), this, SLOT(expandSubitems()));
11353             menu.addAction(tr("Collapse Subitems"), this, SLOT(collapseSubitems()));
11354         }
11355 
11356         menu.exec(w->mapToGlobal(pos));
11357         return;
11358     }
11359     if (contextEntry->type == StructureEntry::SE_INCLUDE) {
11360         QMenu menu;
11361         menu.addAction(tr("Open Document"), this, SLOT(openExternalFileFromAction()))->setData(QVariant::fromValue(contextEntry));
11362         menu.addAction(tr("Go to Definition"), this, SLOT(gotoLineFromAction()))->setData(QVariant::fromValue(contextEntry));
11363 
11364         menu.exec(w->mapToGlobal(pos));
11365         return;
11366     }
11367     if (contextEntry->type == StructureEntry::SE_MAGICCOMMENT) {
11368         QMenu menu;
11369         menu.addAction(tr("Go to Definition"), this, SLOT(gotoLineFromAction()))->setData(QVariant::fromValue(contextEntry));
11370         menu.exec(w->mapToGlobal(pos));
11371         return;
11372     }
11373 
11374 }
11375 /*!
11376  * \brief create label from structure/toc context menu
11377  */
createLabelFromAction()11378 void Texstudio::createLabelFromAction()
11379 {
11380     QAction *action = qobject_cast<QAction *>(sender());
11381     if (!action) return;
11382     StructureEntry *entry = qvariant_cast<StructureEntry *>(action->data());
11383     if (!entry || !entry->document) return;
11384 
11385     // find editor and line nr
11386     int lineNr = entry->getRealLineNumber();
11387 
11388     mDontScrollToItem = entry->type != StructureEntry::SE_SECTION;
11389     LatexEditorView *edView = entry->document->getEditorView();
11390     QEditor::MoveFlags mflags = QEditor::NavigationToHeader;
11391     if (!edView) {
11392         edView = load(entry->document->getFileName());
11393         if (!edView) return;
11394         mflags &= ~QEditor::Animated;
11395         //entry is now invalid
11396     }
11397     REQUIRE(edView->getDocument());
11398 
11399     if (lineNr < 0) return; //not found. (document was closed)
11400 
11401     // find column position after structure command
11402     QString lineText = edView->getDocument()->line(lineNr).text();
11403     int pos = -1;
11404     for (int i = 0; i < latexParser.structureDepth(); i++) {
11405         foreach (const QString &cmd, latexParser.possibleCommands[QString("%structure%1").arg(i)]) {
11406             pos = lineText.indexOf(cmd);
11407             if (pos >= 0) {
11408                 pos += cmd.length();
11409                 // workaround for starred commands: \section*{Cap}
11410                 // (may have been matched by unstarred version because there is no order in possibleCommands)
11411                 if ((lineText.length() > pos + 1) && lineText.at(pos) == '*') pos++;
11412                 break;
11413             }
11414         }
11415         if (pos >= 0) break;
11416     }
11417     if (pos < 0) return; // could not find associated command
11418 
11419     // advance pos behind options, and use title to guess a label
11420     QList<CommandArgument> args = getCommandOptions(lineText, pos, &pos);
11421     QString label = "sec:";
11422     if (args.length() > 0) {
11423         QString title(args.at(0).value);
11424         if (!label.contains('\\') && !label.contains('$')) {  // title with these chars are too complicated to extract label
11425             label += makeLatexLabel(title);
11426         }
11427     }
11428 
11429     gotoLine(lineNr, pos, edView, mflags);
11430 
11431     insertTag(QString("\\label{%1}").arg(label), 7);
11432     QDocumentCursor cur(edView->editor->cursor());
11433     cur.movePosition(label.length(), QDocumentCursor::NextCharacter, QDocumentCursor::KeepAnchor);
11434     edView->editor->setCursor(cur);
11435 }
11436 
closeDocument()11437 void Texstudio::closeDocument()
11438 {
11439     QAction *action = qobject_cast<QAction *>(sender());
11440     if (!action) return;
11441     LatexDocument *document = qvariant_cast<LatexDocument *>(action->data());
11442     if (!document) return;
11443     if (document->getEditorView()) editors->requestCloseEditor(document->getEditorView());
11444     else if (document == documents.masterDocument) structureContextMenuToggleMasterDocument(document);
11445 }
11446 
toggleMasterDocument()11447 void Texstudio::toggleMasterDocument()
11448 {
11449     QAction *action = qobject_cast<QAction *>(sender());
11450     if (!action) return;
11451     LatexDocument *document = qvariant_cast<LatexDocument *>(action->data());
11452     if (!document) return;
11453     structureContextMenuToggleMasterDocument(document);
11454 }
11455 
11456 /*!
11457  * context menu action: Select the selected section and copy it to the clipboard.
11458  * TODO: the logic should probably be moved to LatexDocument or LatexEditorView
11459  */
editSectionCopy()11460 void Texstudio::editSectionCopy()
11461 {
11462     // called by action
11463     QTreeWidgetItem *item = nullptr;
11464     if(topTOCTreeWidget->isVisible()){
11465         item = topTOCTreeWidget->currentItem();
11466     }else{
11467         item = structureTreeWidget->currentItem();
11468     }
11469     if(!item) return;
11470     StructureEntry *entry = item->data(0,Qt::UserRole).value<StructureEntry *>();
11471     if(!entry) return;
11472     LatexEditorView *edView = entry->document->getEditorView();
11473     if(!edView) return;
11474     editors->setCurrentEditor(edView);
11475     QDocumentSelection sel = entry->document->sectionSelection(entry);
11476 
11477     edView->editor->setCursorPosition(sel.startLine, 0);
11478     QDocumentCursor cur = edView->editor->cursor();
11479     //m_cursor.movePosition(1, QDocumentCursor::NextLine, QDocumentCursor::KeepAnchor);
11480     cur.setSilent(true);
11481     cur.movePosition(sel.endLine - sel.startLine - 1, QDocumentCursor::NextLine, QDocumentCursor::KeepAnchor);
11482     cur.movePosition(0, QDocumentCursor::EndOfLine, QDocumentCursor::KeepAnchor);
11483     edView->editor->setCursor(cur);
11484     edView->editor->copy();
11485 }
11486 
11487 /*!
11488  * context menu action: Cut the selected section to the clipboard.
11489  * TODO: the logic should probably be moved to LatexDocument or LatexEditorView
11490  */
editSectionCut()11491 void Texstudio::editSectionCut()
11492 {
11493     // called by action
11494     QTreeWidgetItem *item = nullptr;
11495     if(topTOCTreeWidget->isVisible()){
11496         item = topTOCTreeWidget->currentItem();
11497     }else{
11498         item = structureTreeWidget->currentItem();
11499     }
11500     if(!item) return;
11501     StructureEntry *entry = item->data(0,Qt::UserRole).value<StructureEntry *>();
11502     if (!entry) return;
11503     LatexEditorView *edView = entry->document->getEditorView();
11504     if (!edView) return;
11505     editors->setCurrentEditor(edView);
11506     QDocumentSelection sel = entry->document->sectionSelection(entry);
11507 
11508     edView->editor->setCursorPosition(sel.startLine, 0);
11509     QDocumentCursor cur = edView->editor->cursor();
11510     //m_cursor.movePosition(1, QDocumentCursor::NextLine, QDocumentCursor::KeepAnchor);
11511     cur.setSilent(true);
11512     cur.movePosition(sel.endLine - sel.startLine - 1, QDocumentCursor::NextLine, QDocumentCursor::KeepAnchor);
11513     cur.movePosition(0, QDocumentCursor::EndOfLine, QDocumentCursor::KeepAnchor);
11514     edView->editor->setCursor(cur);
11515     edView->editor->cut();
11516 }
11517 
11518 /*!
11519  * context menu action: Paste the clipboard contents before the selected section.
11520  * TODO: the logic should probably be moved to LatexDocument or LatexEditorView
11521  */
editSectionPasteBefore()11522 void Texstudio::editSectionPasteBefore()
11523 {
11524     QTreeWidgetItem *item = nullptr;
11525     if(topTOCTreeWidget->isVisible()){
11526         item = topTOCTreeWidget->currentItem();
11527     }else{
11528         item = structureTreeWidget->currentItem();
11529     }
11530     if(!item) return;
11531     StructureEntry *entry = item->data(0,Qt::UserRole).value<StructureEntry *>();
11532     if (!entry) return;
11533     LatexEditorView *edView = entry->document->getEditorView();
11534     if (!edView) return;
11535     editors->setCurrentEditor(edView);
11536 
11537     int line = entry->getRealLineNumber();
11538     edView->editor->setCursorPosition(line, 0);
11539     edView->editor->insertText("\n");
11540     edView->editor->setCursorPosition(line, 0);
11541     edView->paste();
11542 }
11543 
11544 /*!
11545  * context menu action: Paste the clipboard contents after the selected section.
11546  * TODO: the logic should probably be moved to LatexDocument or LatexEditorView
11547  */
editSectionPasteAfter()11548 void Texstudio::editSectionPasteAfter()
11549 {
11550     QTreeWidgetItem *item = nullptr;
11551     if(topTOCTreeWidget->isVisible()){
11552         item = topTOCTreeWidget->currentItem();
11553     }else{
11554         item = structureTreeWidget->currentItem();
11555     }
11556     if(!item) return;
11557     StructureEntry *entry = item->data(0,Qt::UserRole).value<StructureEntry *>();
11558     if (!entry) return;
11559     LatexEditorView *edView = entry->document->getEditorView();
11560     if (!edView) return;
11561     editors->setCurrentEditor(edView);
11562     QDocumentSelection sel = entry->document->sectionSelection(entry);
11563 
11564     int line = sel.endLine;
11565     if (line >= edView->editor->document()->lines()) {
11566         edView->editor->setCursorPosition(line - 1, 0);
11567         QDocumentCursor c = edView->editor->cursor();
11568         c.movePosition(1, QDocumentCursor::End, QDocumentCursor::MoveAnchor);
11569         edView->editor->setCursor(c);
11570         edView->editor->insertText("\n");
11571     } else {
11572         edView->editor->setCursorPosition(line, 0);
11573         edView->editor->insertText("\n");
11574         edView->editor->setCursorPosition(line, 0);
11575     }
11576     edView->paste();
11577 }
11578 
11579 /*!
11580  * context menu action: Indent the selected section.
11581  * This replaces the sections and all its sub-sections with a lower heading, e.g.
11582  *     \section -> \subsection
11583  *     \chapter -> \section
11584  * TODO: the logic should probably be moved to LatexDocument or LatexEditorView
11585  */
editIndentSection()11586 void Texstudio::editIndentSection()
11587 {
11588     QTreeWidgetItem *item = nullptr;
11589     if(topTOCTreeWidget->isVisible()){
11590         item = topTOCTreeWidget->currentItem();
11591     }else{
11592         item = structureTreeWidget->currentItem();
11593     }
11594     if(!item) return;
11595     StructureEntry *entry = item->data(0,Qt::UserRole).value<StructureEntry *>();
11596     if (!entry) return;
11597     LatexEditorView *edView = entry->document->getEditorView();
11598     if (!edView) return;
11599     editors->setCurrentEditor(edView);
11600     QDocumentSelection sel = entry->document->sectionSelection(entry);
11601 
11602     QStringList sectionOrder;
11603     sectionOrder << "\\subparagraph" << "\\paragraph" << "\\subsubsection" << "\\subsection" << "\\section" << "\\chapter";
11604 
11605     // replace sections
11606     QString line;
11607     QDocumentCursor cursor = edView->editor->cursor();
11608     for (int l = sel.startLine; l < sel.endLine; l++) {
11609         edView->editor->setCursorPosition(l, 0);
11610         cursor = edView->editor->cursor();
11611         line = edView->editor->cursor().line().text();
11612         QString m_old = "";
11613         foreach (const QString &elem, sectionOrder) {
11614             if (m_old != "") line.replace(elem, m_old);
11615             m_old = elem;
11616         }
11617 
11618         cursor.movePosition(1, QDocumentCursor::EndOfLine, QDocumentCursor::KeepAnchor);
11619         edView->editor->setCursor(cursor);
11620         edView->editor->insertText(line);
11621     }
11622 }
11623 
11624 /*!
11625  * context menu action: Unindent the selected section.
11626  * This replaces the sections and all its sub-sections with a higher heading, e.g.
11627  *     \subsection -> \section
11628  *     \section -> \chapter
11629  * TODO: the logic should probably be moved to LatexDocument or LatexEditorView
11630  */
editUnIndentSection()11631 void Texstudio::editUnIndentSection()
11632 {
11633     QTreeWidgetItem *item = nullptr;
11634     if(topTOCTreeWidget->isVisible()){
11635         item = topTOCTreeWidget->currentItem();
11636     }else{
11637         item = structureTreeWidget->currentItem();
11638     }
11639     if(!item) return;
11640     StructureEntry *entry = item->data(0,Qt::UserRole).value<StructureEntry *>();
11641     if (!entry) return;
11642     LatexEditorView *edView = entry->document->getEditorView();
11643     if (!edView) return;
11644     editors->setCurrentEditor(edView);
11645     QDocumentSelection sel = entry->document->sectionSelection(entry);
11646 
11647     QStringList sectionOrder;
11648     sectionOrder << "\\chapter" << "\\section" << "\\subsection" << "\\subsubsection" << "\\paragraph" << "\\subparagraph" ;
11649 
11650     // replace sections
11651     QString line;
11652     QDocumentCursor cursor = edView->editor->cursor();
11653     for (int l = sel.startLine; l < sel.endLine; l++) {
11654         edView->editor->setCursorPosition(l, 0);
11655         cursor = edView->editor->cursor();
11656         line = edView->editor->cursor().line().text();
11657         QString m_old = "";
11658         foreach (const QString &elem, sectionOrder) {
11659             if (m_old != "") line.replace(elem, m_old);
11660             m_old = elem;
11661         }
11662 
11663         cursor.movePosition(1, QDocumentCursor::EndOfLine, QDocumentCursor::KeepAnchor);
11664         edView->editor->setCursor(cursor);
11665         edView->editor->insertText(line);
11666     }
11667 }
11668 
11669 /*! \brief move cursor to position given in calling action (TOC/structure context menu)
11670  *
11671  */
gotoLineFromAction()11672 void Texstudio::gotoLineFromAction()
11673 {
11674     QAction *action = qobject_cast<QAction *>(sender());
11675     if (!action) return;
11676     StructureEntry *entry = qvariant_cast<StructureEntry *>(action->data());
11677 
11678     if (!entry || !entry->document) return;
11679     LatexDocument *doc = entry->document;
11680     QDocumentLineHandle *dlh = entry->getLineHandle();
11681     int lineNr = -1;
11682     if ((lineNr = doc->indexOf(dlh)) >= 0) {
11683         gotoLine(entry->document, lineNr, 0);
11684     }
11685 }
11686 
11687 /*!
11688  * \brief Collect structure info from file and create a TOC
11689  * This approach avoid the model/view which repeatedly led to crashes because the view component caches info from the actual model and is not kept up-to-date properly
11690  *
11691  */
updateStructureLocally()11692 void Texstudio::updateStructureLocally(){
11693     if(!structureTreeWidget->isVisible()) return; // don't update if TOC is not shown, save unnecessary effort
11694     QTreeWidgetItem *root= nullptr;
11695 
11696     LatexDocument *doc=documents.getCurrentDocument();
11697     if(!doc){
11698         // no root document
11699         // clear TOC completely
11700         structureTreeWidget->clear();
11701         return;
11702     }
11703 
11704     LatexDocument *master = documents.getMasterDocument();
11705     bool showHiddenMasterFirst=false;
11706     bool hiddenMasterStructureIsVisible=false;
11707     if(configManager.parseMaster && master && master->isHidden()){
11708         showHiddenMasterFirst=true;
11709     }
11710     if(configManager.structureShowSingleDoc){
11711         root= structureTreeWidget->topLevelItem(0);
11712         if(structureTreeWidget->topLevelItemCount()>1){
11713             for(int i=1;structureTreeWidget->topLevelItemCount()>1;){
11714                 QTreeWidgetItem *item=structureTreeWidget->takeTopLevelItem(i);
11715                 delete item;
11716             }
11717         }
11718     }else{
11719         for(int i=0;i<structureTreeWidget->topLevelItemCount();++i){
11720             QTreeWidgetItem *item = structureTreeWidget->topLevelItem(i);
11721             StructureEntry *contextEntry = item->data(0,Qt::UserRole).value<StructureEntry *>();
11722             if (!contextEntry){
11723                 structureTreeWidget->takeTopLevelItem(i);
11724                 --i;
11725                 continue;
11726             }
11727             if (contextEntry->type == StructureEntry::SE_DOCUMENT_ROOT) {
11728                 if(contextEntry->document == doc){
11729                     root=item;
11730                 }else{
11731                     QFont font=item->font(0);
11732                     font.setBold(false);
11733                     item->setFont(0,font);
11734                     if(!documents.documents.contains(contextEntry->document) || documents.hiddenDocuments.contains(contextEntry->document)){
11735                         if(showHiddenMasterFirst && contextEntry->document == master && !hiddenMasterStructureIsVisible){
11736                             // run only once
11737                             // reload may add more structure views
11738                             hiddenMasterStructureIsVisible=true;
11739                             continue; // keep showing master document regardless
11740                         }
11741                         structureTreeWidget->takeTopLevelItem(i);
11742                         --i;
11743                     }
11744                 }
11745             }else{
11746                 // remove invalid
11747                 structureTreeWidget->takeTopLevelItem(i);
11748                 --i;
11749             }
11750         }
11751         // reorder documents
11752         for(int i=0;i<documents.documents.length();++i){
11753             bool found=false;
11754             int j=i;
11755             StructureEntry *contextEntry;
11756             for(;j<structureTreeWidget->topLevelItemCount();++j){
11757                 QTreeWidgetItem *item = structureTreeWidget->topLevelItem(j);
11758                 contextEntry = item->data(0,Qt::UserRole).value<StructureEntry *>();
11759                 if(contextEntry->document == documents.documents.value(i)){
11760                     found=true;
11761                     break;
11762                 }
11763             }
11764             if(found && i<j){
11765                 QTreeWidgetItem *item = structureTreeWidget->takeTopLevelItem(j);
11766                 if(contextEntry->document==master){
11767                     item->setIcon(0,QIcon(":/images/masterdoc.png"));
11768                 }else{
11769                     item->setIcon(0,QIcon(":/images/doc.png"));
11770                 }
11771                 structureTreeWidget->insertTopLevelItem(i,item);
11772             }
11773             if(!found){
11774                 QTreeWidgetItem *item=new QTreeWidgetItem();
11775                 LatexDocument *doc=documents.documents.value(i);
11776                 StructureEntry *base=doc->baseStructure;
11777 
11778                 item->setText(0,doc->getFileInfo().fileName());
11779                 item->setData(0,Qt::UserRole,QVariant::fromValue<StructureEntry *>(base));
11780                 if(doc==master){
11781                     item->setIcon(0,QIcon(":/images/masterdoc.png"));
11782                 }else{
11783                     item->setIcon(0,QIcon(":/images/doc.png"));
11784                 }
11785                 structureTreeWidget->insertTopLevelItem(i,item);
11786                 if(doc==documents.getCurrentDocument()){
11787                     root=item;
11788                 }
11789             }
11790         }
11791     }
11792     StructureEntry *selectedEntry=nullptr;
11793     bool itemExpandedLABEL=false;
11794     bool itemExpandedTODO=false;
11795     bool itemExpandedMAGIC=false;
11796     bool itemExpandedBIBLIO=false;
11797     bool addToTopLevel=false;
11798     if(!root){
11799         root=new QTreeWidgetItem();
11800         addToTopLevel=true;
11801     }else{
11802         // get current selected item, check only first and deduce structureEntry
11803         QList<QTreeWidgetItem*> selected=structureTreeWidget->selectedItems();
11804         if(!selected.isEmpty()){
11805             QTreeWidgetItem *item=selected.first();
11806             if(item){
11807                 selectedEntry = item->data(0,Qt::UserRole).value<StructureEntry *>();
11808             }
11809         }
11810         // remove all item in topTOC but keep itemTODO
11811         for(int i=0;i<root->childCount();++i){
11812             QTreeWidgetItem *item=root->child(i);
11813             if(item->data(0,Qt::UserRole+1).toString()=="TODO"){
11814                 itemExpandedTODO=item->isExpanded();
11815             }
11816             if(item->data(0,Qt::UserRole+1).toString()=="LABEL"){
11817                 itemExpandedLABEL=item->isExpanded();
11818             }
11819             if(item->data(0,Qt::UserRole+1).toString()=="MAGIC"){
11820                 itemExpandedMAGIC=item->isExpanded();
11821             }
11822             if(item->data(0,Qt::UserRole+1).toString()=="BIBLIO"){
11823                 itemExpandedBIBLIO=item->isExpanded();
11824             }
11825         }
11826         QList<QTreeWidgetItem*> items=root->takeChildren();
11827         qDeleteAll(items);
11828     }
11829     QVector<QTreeWidgetItem *>rootVector(latexParser.MAX_STRUCTURE_LEVEL,root);
11830     // fill TOC, starting by current master/top
11831 
11832 
11833     StructureEntry *base=doc->baseStructure;
11834 
11835     root->setText(0,doc->getFileInfo().fileName());
11836     root->setData(0,Qt::UserRole,QVariant::fromValue<StructureEntry *>(base));
11837     if(doc==master){
11838         root->setIcon(0,QIcon(":/images/masterdoc.png"));
11839     }else{
11840         root->setIcon(0,QIcon(":/images/doc.png"));
11841     }
11842     QFont font=root->font(0);
11843     font.setBold(true);
11844     root->setFont(0,font);
11845 
11846     QList<QTreeWidgetItem*> todoList;
11847     QList<QTreeWidgetItem*> labelList;
11848     QList<QTreeWidgetItem*> magicList;
11849     QList<QTreeWidgetItem*> biblioList;
11850     parseStructLocally(base,rootVector,&todoList,&labelList,&magicList,&biblioList);
11851     if(addToTopLevel)
11852         structureTreeWidget->addTopLevelItem(root);
11853 
11854     if(!biblioList.isEmpty()){
11855         QTreeWidgetItem *itemBIBLIO=new QTreeWidgetItem();
11856         itemBIBLIO->setText(0,tr("BIBLIOGRAPHY"));
11857         itemBIBLIO->setData(0,Qt::UserRole+1,"BIBLIO");
11858         itemBIBLIO->insertChildren(0,biblioList);
11859         root->insertChild(0,itemBIBLIO);
11860         itemBIBLIO->setExpanded(itemExpandedBIBLIO);
11861     }
11862     if(!magicList.isEmpty()){
11863         QTreeWidgetItem *itemTODO=new QTreeWidgetItem();
11864         itemTODO->setText(0,tr("MAGIC_COMMENTS"));
11865         itemTODO->setData(0,Qt::UserRole+1,"MAGIC");
11866         itemTODO->insertChildren(0,magicList);
11867         root->insertChild(0,itemTODO);
11868         itemTODO->setExpanded(itemExpandedMAGIC);
11869     }
11870     if(!todoList.isEmpty()){
11871         QTreeWidgetItem *itemTODO=new QTreeWidgetItem();
11872         itemTODO->setText(0,tr("TODO"));
11873         itemTODO->setData(0,Qt::UserRole+1,"TODO");
11874         itemTODO->insertChildren(0,todoList);
11875         root->insertChild(0,itemTODO);
11876         itemTODO->setExpanded(itemExpandedTODO);
11877     }
11878     if(!labelList.isEmpty()){
11879         QTreeWidgetItem *itemLABEL=new QTreeWidgetItem();
11880         itemLABEL->setText(0,tr("LABELS"));
11881         itemLABEL->setData(0,Qt::UserRole+1,"LABEL");
11882         itemLABEL->insertChildren(0,labelList);
11883         root->insertChild(0,itemLABEL);
11884         itemLABEL->setExpanded(itemExpandedLABEL);
11885     }
11886 
11887     root->setExpanded(true);
11888     root->setSelected(false);
11889     updateCurrentPosInTOC(nullptr,nullptr,selectedEntry);
11890 }
11891 
11892 /*!
11893  * \brief parse children of a structure entry se to collect structure data
11894  * This function parses only the specific document, not any sub-files (unlike parseStruct).
11895  * For this also labels and magic comments are presented.
11896  * \param se root structureentry
11897  * \param rootVector
11898  */
parseStructLocally(StructureEntry * se,QVector<QTreeWidgetItem * > & rootVector,QList<QTreeWidgetItem * > * todoList,QList<QTreeWidgetItem * > * labelList,QList<QTreeWidgetItem * > * magicList,QList<QTreeWidgetItem * > * biblioList)11899 void Texstudio::parseStructLocally(StructureEntry* se, QVector<QTreeWidgetItem *> &rootVector, QList<QTreeWidgetItem *> *todoList, QList<QTreeWidgetItem *> *labelList, QList<QTreeWidgetItem *> *magicList, QList<QTreeWidgetItem *> *biblioList) {
11900     static const QColor beyondEndColor(255, 170, 0);
11901     static const QColor inAppendixColor(200, 230, 200);
11902 
11903     foreach(StructureEntry* elem,se->children){
11904         if(todoList && (elem->type == StructureEntry::SE_OVERVIEW)){
11905             parseStructLocally(elem,rootVector,todoList,labelList,magicList,biblioList);
11906         }
11907         if(todoList && (elem->type == StructureEntry::SE_TODO)){
11908             QTreeWidgetItem * item=new QTreeWidgetItem();
11909             item->setData(0,Qt::UserRole,QVariant::fromValue<StructureEntry *>(elem));
11910             item->setText(0,elem->title);
11911             todoList->append(item);
11912         }
11913         if(labelList && (elem->type == StructureEntry::SE_LABEL)){
11914             QTreeWidgetItem * item=new QTreeWidgetItem();
11915             item->setData(0,Qt::UserRole,QVariant::fromValue<StructureEntry *>(elem));
11916             item->setText(0,elem->title);
11917             labelList->append(item);
11918         }
11919         if(magicList && (elem->type == StructureEntry::SE_MAGICCOMMENT)){
11920             QTreeWidgetItem * item=new QTreeWidgetItem();
11921             item->setData(0,Qt::UserRole,QVariant::fromValue<StructureEntry *>(elem));
11922             item->setText(0,elem->title);
11923             magicList->append(item);
11924         }
11925         if(biblioList && (elem->type == StructureEntry::SE_BIBTEX)){
11926             QTreeWidgetItem * item=new QTreeWidgetItem();
11927             item->setData(0,Qt::UserRole,QVariant::fromValue<StructureEntry *>(elem));
11928             item->setText(0,elem->title);
11929             biblioList->append(item);
11930         }
11931         if(elem->type == StructureEntry::SE_SECTION){
11932             QTreeWidgetItem * item=new QTreeWidgetItem();
11933             item->setData(0,Qt::UserRole,QVariant::fromValue<StructureEntry *>(elem));
11934             item->setText(0,elem->title);
11935             item->setIcon(0,iconSection.value(elem->level));
11936             rootVector[elem->level]->addChild(item);
11937             item->setExpanded(elem->expanded);
11938             if (documents.markStructureElementsInAppendix && elem->hasContext(StructureEntry::InAppendix)) item->setBackground(0,inAppendixColor);
11939             if (documents.markStructureElementsBeyondEnd && elem->hasContext(StructureEntry::BeyondEnd)) item->setBackground(0,beyondEndColor);
11940             // fill rootVector with item for subsequent lower level elements (which are children of item then)
11941             for(int i=elem->level+1;i<latexParser.MAX_STRUCTURE_LEVEL;i++){
11942                 rootVector[i]=item;
11943             }
11944             parseStructLocally(elem,rootVector,todoList,labelList,magicList);
11945         }
11946         if(elem->type == StructureEntry::SE_INCLUDE){
11947             LatexDocument *doc=elem->document;
11948             LatexDocument *rootDoc=doc->getRootDocument();
11949             //QString fn=ensureTrailingDirSeparator(rootDoc->getFileInfo().absolutePath())+elem->title;
11950             QFileInfo fi(rootDoc->getFileInfo().absolutePath(),elem->title);
11951             doc=documents.findDocumentFromName(fi.absoluteFilePath());
11952             if(!doc){
11953                 doc=documents.findDocumentFromName(fi.absoluteFilePath()+".tex");
11954             }
11955             QTreeWidgetItem * item=new QTreeWidgetItem();
11956             item->setData(0,Qt::UserRole,QVariant::fromValue<StructureEntry *>(elem));
11957             item->setText(0,elem->title);
11958             if(!doc){
11959                 item->setForeground(0,Qt::red);
11960             }
11961             item->setIcon(0,QIcon(":/images/include.png"));
11962             if(configManager.indentIncludesInStructure){
11963                 rootVector[latexParser.MAX_STRUCTURE_LEVEL-1]->addChild(item);
11964             }else{
11965                 rootVector[0]->addChild(item);
11966                 for(int i=1;i<latexParser.MAX_STRUCTURE_LEVEL;i++){
11967                     rootVector[i]=rootVector[0];
11968                 }
11969             }
11970         }
11971     }
11972 }
11973 
openAllRelatedDocuments()11974 void Texstudio::openAllRelatedDocuments()
11975 {
11976     QAction *action = qobject_cast<QAction *>(sender());
11977     if (!action) return;
11978     LatexDocument *document = qvariant_cast<LatexDocument *>(action->data());
11979     if (!document) return;
11980     QSet<QString> checkedFiles, filesToCheck;
11981     filesToCheck.insert(document->getFileName());
11982 
11983     while (!filesToCheck.isEmpty()) {
11984         QString f = *filesToCheck.begin();
11985         filesToCheck.erase(filesToCheck.begin());
11986         if (checkedFiles.contains(f)) continue;
11987         checkedFiles.insert(f);
11988         document = documents.findDocument(f);
11989         if (!document) {
11990             LatexEditorView *lev = load(f);
11991             document = lev ? lev->document : nullptr;
11992         }
11993         if (!document) continue;
11994         foreach (const QString &fn, document->includedFilesAndParent()) {
11995             QString t = document->findFileName(fn);
11996             if (!t.isEmpty()) filesToCheck.insert(t);
11997         }
11998     }
11999 }
12000 
closeAllRelatedDocuments()12001 void Texstudio::closeAllRelatedDocuments()
12002 {
12003     QAction *action = qobject_cast<QAction *>(sender());
12004     if (!action) return;
12005     LatexDocument *document = qvariant_cast<LatexDocument *>(action->data());
12006     if (!document) return;
12007     QList<LatexDocument *> l = document->getListOfDocs();
12008 
12009     if (!saveFilesForClosing(l)) return;
12010     foreach (LatexDocument *d, l) {
12011         if (documents.documents.contains(d))
12012             documents.deleteDocument(d); //this might hide the document
12013         if (documents.hiddenDocuments.contains(d))
12014             documents.deleteDocument(d, d->isHidden(), d->isHidden());
12015     }
12016 }
12017 
12018 /*!
12019  * \brief copy file name of document to clipboard
12020  *
12021  * Called from structure view
12022  */
copyFileName()12023 void Texstudio::copyFileName()
12024 {
12025     QAction *action = qobject_cast<QAction *>(sender());
12026     if (!action) return;
12027     LatexDocument *document = qvariant_cast<LatexDocument *>(action->data());
12028     if (!document) return;
12029     QClipboard* clipboard = QGuiApplication::clipboard();
12030     if (!clipboard) return;
12031     clipboard->setText(document->getFileInfo().fileName());
12032 }
12033 
12034 /*!
12035  * \brief copy file path of document to clipboard
12036  *
12037  * Called from structure view
12038  */
copyFilePath()12039 void Texstudio::copyFilePath()
12040 {
12041     QAction *action = qobject_cast<QAction *>(sender());
12042     if (!action) return;
12043     LatexDocument *document = qvariant_cast<LatexDocument *>(action->data());
12044     if (!document) return;
12045     QClipboard* clipboard = QGuiApplication::clipboard();
12046     if (!clipboard) return;
12047     clipboard->setText(document->getFileInfo().absoluteFilePath());
12048 }
12049 
12050 /*!
12051  * \brief toggle single/multiple documents view in structureWidget
12052  */
12053 
toggleSingleDocMode()12054 void Texstudio::toggleSingleDocMode()
12055 {
12056     bool mode = configManager.structureShowSingleDoc;
12057     configManager.structureShowSingleDoc= !mode;
12058     updateStructureLocally();
12059 }
12060 
12061 /*!
12062  Returns an associated SE_LABEL entry for a structure element if one exists, otherwise 0.
12063  TODO: currently association is checked, by checking, if the label is on the same line or on the next.
12064  This is not necessarily correct. It fails if:
12065   - there are multiple labels on one line (always the first label is chosen)
12066   - the label is more than one line after the entry (label not detected)
12067 */
labelForStructureEntry(const StructureEntry * entry)12068 StructureEntry* Texstudio::labelForStructureEntry(const StructureEntry *entry)
12069 {
12070     REQUIRE_RET(entry && entry->document, nullptr );
12071     QDocumentLineHandle *dlh = entry->getLineHandle();
12072     if (!dlh) return nullptr;
12073     QDocumentLineHandle *nextDlh = entry->document->line(entry->getRealLineNumber() + 1).handle();
12074     StructureEntryIterator iter(entry->document->baseStructure);
12075 
12076     while (iter.hasNext()) {
12077         StructureEntry *se = iter.next();
12078         if (se->type == StructureEntry::SE_LABEL) {
12079             QDocumentLineHandle *labelDlh = se->getLineHandle();
12080             if (labelDlh == dlh || labelDlh == nextDlh) {
12081                 return se;
12082             }
12083         }
12084     }
12085     return nullptr;
12086 }
12087 /*!
12088  * \brief expand item and subitems in structureWidget
12089  */
expandSubitems()12090 void Texstudio::expandSubitems()
12091 {
12092     QTreeWidgetItem *item = nullptr;
12093     if(topTOCTreeWidget->isVisible()){
12094         item = topTOCTreeWidget->currentItem();
12095     }else{
12096         item = structureTreeWidget->currentItem();
12097     }
12098     if(!item) return;
12099     UtilsUi::setSubtreeExpanded(item, true);
12100 }
12101 /*!
12102  * \brief collapse item and subitems in structureWidget
12103  */
collapseSubitems()12104 void Texstudio::collapseSubitems()
12105 {
12106     QTreeWidgetItem *item = nullptr;
12107     if(topTOCTreeWidget->isVisible()){
12108         item = topTOCTreeWidget->currentItem();
12109     }else{
12110         item = structureTreeWidget->currentItem();
12111     }
12112     if(!item) return;
12113     UtilsUi::setSubtreeExpanded(item, false);
12114 }
12115 
12116 /*! @} */
12117 
12118 
12119 #undef MAC_OR_DEFAULT
12120