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 ¯o, 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 © 20082013 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(¤tPackageList);
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(' ', " "); // 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(' ', " "); // 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(' ', " ").replace('-', "‑"); // 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