1 /****************************************************************************
2 **
3 ** Copyright (C) 2020 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qt Creator.
7 **
8 ** Commercial License Usage
9 ** Licensees holding valid commercial Qt licenses may use this file in
10 ** accordance with the commercial license agreement provided with the
11 ** Software or, alternatively, in accordance with the terms contained in
12 ** a written agreement between you and The Qt Company. For licensing terms
13 ** and conditions see https://www.qt.io/terms-conditions. For further
14 ** information use the contact form at https://www.qt.io/contact-us.
15 **
16 ** GNU General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU
18 ** General Public License version 3 as published by the Free Software
19 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
20 ** included in the packaging of this file. Please review the following
21 ** information to ensure the GNU General Public License requirements will
22 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
23 **
24 ****************************************************************************/
25 
26 #include "editormanager.h"
27 #include "editormanager_p.h"
28 #include "editorwindow.h"
29 
30 #include "editorview.h"
31 #include "openeditorswindow.h"
32 #include "openeditorsview.h"
33 #include "documentmodel.h"
34 #include "documentmodel_p.h"
35 #include "ieditor.h"
36 
37 #include <app/app_version.h>
38 
39 #include <coreplugin/actionmanager/actioncontainer.h>
40 #include <coreplugin/actionmanager/actionmanager.h>
41 #include <coreplugin/actionmanager/command.h>
42 #include <coreplugin/dialogs/openwithdialog.h>
43 #include <coreplugin/dialogs/readonlyfilesdialog.h>
44 #include <coreplugin/diffservice.h>
45 #include <coreplugin/documentmanager.h>
46 #include <coreplugin/editormanager/ieditorfactory.h>
47 #include <coreplugin/editormanager/ieditorfactory_p.h>
48 #include <coreplugin/editormanager/iexternaleditor.h>
49 #include <coreplugin/editortoolbar.h>
50 #include <coreplugin/fileutils.h>
51 #include <coreplugin/findplaceholder.h>
52 #include <coreplugin/find/searchresultitem.h>
53 #include <coreplugin/icore.h>
54 #include <coreplugin/imode.h>
55 #include <coreplugin/iversioncontrol.h>
56 #include <coreplugin/modemanager.h>
57 #include <coreplugin/outputpane.h>
58 #include <coreplugin/outputpanemanager.h>
59 #include <coreplugin/rightpane.h>
60 #include <coreplugin/settingsdatabase.h>
61 #include <coreplugin/vcsmanager.h>
62 
63 #include <extensionsystem/pluginmanager.h>
64 
65 #include <utils/algorithm.h>
66 #include <utils/checkablemessagebox.h>
67 #include <utils/executeondestruction.h>
68 #include <utils/fileutils.h>
69 #include <utils/hostosinfo.h>
70 #include <utils/infobar.h>
71 #include <utils/link.h>
72 #include <utils/macroexpander.h>
73 #include <utils/mimetypes/mimedatabase.h>
74 #include <utils/mimetypes/mimetype.h>
75 #include <utils/overridecursor.h>
76 #include <utils/qtcassert.h>
77 #include <utils/stringutils.h>
78 #include <utils/utilsicons.h>
79 
80 #include <QClipboard>
81 #include <QDateTime>
82 #include <QDebug>
83 #include <QFileInfo>
84 #include <QHash>
85 #include <QMap>
86 #include <QRegularExpression>
87 #include <QRegularExpressionMatch>
88 #include <QSet>
89 #include <QSettings>
90 #include <QTextCodec>
91 #include <QTimer>
92 
93 #include <QAction>
94 #include <QApplication>
95 #include <QFileDialog>
96 #include <QMenu>
97 #include <QMessageBox>
98 #include <QPushButton>
99 #include <QSplitter>
100 
101 #include <algorithm>
102 
103 #if defined(WITH_TESTS)
104 #include <coreplugin/coreplugin.h>
105 #include <QTest>
106 #endif
107 
108 enum { debugEditorManager=0 };
109 
110 static const char kCurrentDocumentPrefix[] = "CurrentDocument";
111 static const char kCurrentDocumentXPos[] = "CurrentDocument:XPos";
112 static const char kCurrentDocumentYPos[] = "CurrentDocument:YPos";
113 static const char kMakeWritableWarning[] = "Core.EditorManager.MakeWritable";
114 
115 static const char documentStatesKey[] = "EditorManager/DocumentStates";
116 static const char reloadBehaviorKey[] = "EditorManager/ReloadBehavior";
117 static const char autoSaveEnabledKey[] = "EditorManager/AutoSaveEnabled";
118 static const char autoSaveIntervalKey[] = "EditorManager/AutoSaveInterval";
119 static const char autoSuspendEnabledKey[] = "EditorManager/AutoSuspendEnabled";
120 static const char autoSuspendMinDocumentCountKey[] = "EditorManager/AutoSuspendMinDocuments";
121 static const char warnBeforeOpeningBigTextFilesKey[] = "EditorManager/WarnBeforeOpeningBigTextFiles";
122 static const char bigTextFileSizeLimitKey[] = "EditorManager/BigTextFileSizeLimitInMB";
123 static const char maxRecentFilesKey[] = "EditorManager/MaxRecentFiles";
124 static const char fileSystemCaseSensitivityKey[] = "Core/FileSystemCaseSensitivity";
125 static const char preferredEditorFactoriesKey[] = "EditorManager/PreferredEditorFactories";
126 
127 static const char scratchBufferKey[] = "_q_emScratchBuffer";
128 
129 // for lupdate
130 using namespace Core;
131 
132 using namespace Core::Internal;
133 using namespace Utils;
134 
135 //===================EditorManager=====================
136 
137 /*!
138     \class Core::EditorManagerPlaceHolder
139     \inheaderfile coreplugin/editormanager/editormanager.h
140     \inmodule QtCreator
141     \ingroup mainclasses
142 
143     \brief The EditorManagerPlaceHolder class is used to integrate an editor
144     area into a \l{Core::IMode}{mode}.
145 
146     Create an instance of EditorManagerPlaceHolder and integrate it into your
147     mode widget's layout, to add the main editor area into your mode. The best
148     place for the editor area is the central widget of a QMainWindow.
149     Examples are the Edit and Debug modes.
150 */
151 
152 /*!
153     Creates an EditorManagerPlaceHolder with the specified \a parent.
154 */
EditorManagerPlaceHolder(QWidget * parent)155 EditorManagerPlaceHolder::EditorManagerPlaceHolder(QWidget *parent)
156     : QWidget(parent)
157 {
158     setLayout(new QVBoxLayout);
159     layout()->setContentsMargins(0, 0, 0, 0);
160     setFocusProxy(EditorManagerPrivate::mainEditorArea());
161 }
162 
163 /*!
164     \internal
165 */
~EditorManagerPlaceHolder()166 EditorManagerPlaceHolder::~EditorManagerPlaceHolder()
167 {
168     // EditorManager will be deleted in ~MainWindow()
169     QWidget *em = EditorManagerPrivate::mainEditorArea();
170     if (em && em->parent() == this) {
171         em->hide();
172         em->setParent(nullptr);
173     }
174 }
175 
176 /*!
177     \internal
178 */
showEvent(QShowEvent *)179 void EditorManagerPlaceHolder::showEvent(QShowEvent *)
180 {
181     QWidget *previousFocus = nullptr;
182     QWidget *em = EditorManagerPrivate::mainEditorArea();
183     if (em->focusWidget() && em->focusWidget()->hasFocus())
184         previousFocus = em->focusWidget();
185     layout()->addWidget(em);
186     em->show();
187     if (previousFocus)
188         previousFocus->setFocus();
189 }
190 
191 // ---------------- EditorManager
192 
193 /*!
194     \class Core::EditorManager
195     \inheaderfile coreplugin/editormanager/editormanager.h
196     \inmodule QtCreator
197 
198     \brief The EditorManager class manages the editors created for files
199     according to their MIME type.
200 
201     Whenever a user wants to edit or create a file, the EditorManager scans all
202     IEditorFactory interfaces for suitable editors. The selected IEditorFactory
203     is then asked to create an editor, as determined by the MIME type of the
204     file.
205 
206     Users can split the editor view or open the editor in a new window when
207     to work on and view multiple files on the same screen or on multiple
208     screens. For more information, see
209     \l{https://doc.qt.io/qtcreator/creator-coding-navigating.html#splitting-the-editor-view}
210     {Splitting the Editor View}.
211 
212     Plugins use the EditorManager to open documents in editors or close them,
213     and to get notified when documents are opened, closed or saved.
214 */
215 
216 /*!
217     \enum Core::Internal::MakeWritableResult
218     \internal
219 
220     This enum specifies whether the document has successfully been made writable.
221 
222     \value OpenedWithVersionControl
223            The document was opened under version control.
224     \value MadeWritable
225            The document was made writable.
226     \value SavedAs
227            The document was saved under another name.
228     \value Failed
229            The document cannot be made writable.
230 */
231 
232 /*!
233     \enum EditorManager::OpenEditorFlag
234 
235     This enum specifies settings for opening a file in an editor.
236 
237     \value NoFlags
238            Does not use any settings.
239     \value DoNotChangeCurrentEditor
240            Does not switch focus to the newly opened editor.
241     \value IgnoreNavigationHistory
242            Does not add an entry to the navigation history for the
243            opened editor.
244     \value DoNotMakeVisible
245            Does not force the editor to become visible.
246     \value OpenInOtherSplit
247            Opens the document in another split of the window.
248     \value DoNotSwitchToDesignMode
249            Opens the document in the current mode.
250     \value DoNotSwitchToEditMode
251            Opens the document in the current mode.
252     \value SwitchSplitIfAlreadyVisible
253            Switches to another split if the document is already
254            visible there.
255 */
256 
257 /*!
258     \class EditorManager::FilePathInfo
259     \inheaderfile coreplugin/editormanager/editormanager.h
260     \inmodule QtCreator
261 
262     \brief The FilePathInfo class contains information about a file path's
263     special segments.
264 
265     File names can have an additional postfix, optionally specifying a line and
266     column number, when opening a file in \QC from the command line or locator.
267     The FilePathInfo class contains the file name, the complete postfix string,
268     and the parsed line and column numbers.
269 
270     \sa EditorManager::splitLineAndColumnNumber()
271 */
272 
273 /*!
274     \fn void EditorManager::currentEditorChanged(IEditor *editor)
275 
276     This signal is emitted after the current editor changed to \a editor.
277 */
278 
279 /*!
280     \fn void EditorManager::currentDocumentStateChanged()
281 
282     This signal is emitted when the meta data of the current document, for
283     example file name or modified state, changed.
284 
285     \sa IDocument::changed()
286 */
287 
288 /*!
289     \fn void EditorManager::documentStateChanged(IDocument *document)
290 
291     This signal is emitted when the meta data of the \a document, for
292     example file name or modified state, changed.
293 
294     \sa IDocument::changed()
295 */
296 
297 /*!
298     \fn void EditorManager::editorCreated(IEditor *editor, const QString &fileName)
299 
300     This signal is emitted after an \a editor was created for \a fileName, but
301     before it was opened in an editor view.
302 */
303 /*!
304     \fn void EditorManager::editorOpened(IEditor *editor)
305 
306     This signal is emitted after a new \a editor was opened in an editor view.
307 
308     Usually the more appropriate signal to listen to is documentOpened().
309 */
310 
311 /*!
312     \fn void EditorManager::documentOpened(IDocument *document)
313 
314     This signal is emitted after the first editor for \a document opened in an
315     editor view.
316 */
317 
318 /*!
319     \fn void EditorManager::editorAboutToClose(IEditor *editor)
320 
321     This signal is emitted before \a editor is closed. This can be used to free
322     resources that were allocated for the editor separately from the editor
323     itself. It cannot be used to prevent the editor from closing. See
324     addCloseEditorListener() for that.
325 
326     Usually the more appropriate signal to listen to is documentClosed().
327 
328     \sa addCloseEditorListener()
329 */
330 
331 /*!
332     \fn void EditorManager::editorsClosed(QList<IEditor *> editors)
333 
334     This signal is emitted after the \a editors closed, but before they are
335     deleted.
336 
337     Usually the more appropriate signal to listen to is documentClosed().
338 */
339 
340 /*!
341     \fn void EditorManager::documentClosed(IDocument *document)
342 
343     This signal is emitted after the \a document closed, but before it is deleted.
344 */
345 /*!
346     \fn void EditorManager::findOnFileSystemRequest(const QString &path)
347 
348     \internal
349 */
350 /*!
351     \fn void EditorManager::aboutToSave(IDocument *document)
352 
353     This signal is emitted before the \a document is saved.
354 */
355 /*!
356     \fn void EditorManager::saved(IDocument *document)
357 
358     This signal is emitted after the \a document was saved.
359 */
360 /*!
361     \fn void EditorManager::autoSaved()
362 
363     This signal is emitted after auto-save was triggered.
364 */
365 /*!
366     \fn void EditorManager::currentEditorAboutToChange(IEditor *editor)
367 
368     This signal is emitted before the current editor changes to \a editor.
369 */
370 
371 static EditorManager *m_instance = nullptr;
372 static EditorManagerPrivate *d;
373 
autoSaveName(const QString & fileName)374 static QString autoSaveName(const QString &fileName)
375 {
376     return fileName + ".autosave";
377 }
378 
setFocusToEditorViewAndUnmaximizePanes(EditorView * view)379 static void setFocusToEditorViewAndUnmaximizePanes(EditorView *view)
380 {
381     IEditor *editor = view->currentEditor();
382     QWidget *target = editor ? editor->widget() : view;
383     QWidget *focus = target->focusWidget();
384     QWidget *w = focus ? focus : target;
385 
386     w->setFocus();
387     ICore::raiseWindow(w);
388 
389     OutputPanePlaceHolder *holder = OutputPanePlaceHolder::getCurrent();
390     if (holder && holder->window() == view->window()) {
391         // unmaximize output pane if necessary
392         if (holder->isVisible() && holder->isMaximized())
393             holder->setMaximized(false);
394     }
395 }
396 
EditorManagerPrivate(QObject * parent)397 EditorManagerPrivate::EditorManagerPrivate(QObject *parent) :
398     QObject(parent),
399     m_revertToSavedAction(new QAction(EditorManager::tr("Revert to Saved"), this)),
400     m_saveAction(new QAction(this)),
401     m_saveAsAction(new QAction(this)),
402     m_closeCurrentEditorAction(new QAction(EditorManager::tr("Close"), this)),
403     m_closeAllEditorsAction(new QAction(EditorManager::tr("Close All"), this)),
404     m_closeOtherDocumentsAction(new QAction(EditorManager::tr("Close Others"), this)),
405     m_closeAllEditorsExceptVisibleAction(new QAction(EditorManager::tr("Close All Except Visible"), this)),
406     m_gotoNextDocHistoryAction(new QAction(EditorManager::tr("Next Open Document in History"), this)),
407     m_gotoPreviousDocHistoryAction(new QAction(EditorManager::tr("Previous Open Document in History"), this)),
408     m_goBackAction(new QAction(Utils::Icons::PREV.icon(), EditorManager::tr("Go Back"), this)),
409     m_goForwardAction(new QAction(Utils::Icons::NEXT.icon(), EditorManager::tr("Go Forward"), this)),
410     m_gotoLastEditAction(new QAction(EditorManager::tr("Go to Last Edit"), this)),
411     m_copyFilePathContextAction(new QAction(EditorManager::tr("Copy Full Path"), this)),
412     m_copyLocationContextAction(new QAction(EditorManager::tr("Copy Path and Line Number"), this)),
413     m_copyFileNameContextAction(new QAction(EditorManager::tr("Copy File Name"), this)),
414     m_saveCurrentEditorContextAction(new QAction(EditorManager::tr("&Save"), this)),
415     m_saveAsCurrentEditorContextAction(new QAction(EditorManager::tr("Save &As..."), this)),
416     m_revertToSavedCurrentEditorContextAction(new QAction(EditorManager::tr("Revert to Saved"), this)),
417     m_closeCurrentEditorContextAction(new QAction(EditorManager::tr("Close"), this)),
418     m_closeAllEditorsContextAction(new QAction(EditorManager::tr("Close All"), this)),
419     m_closeOtherDocumentsContextAction(new QAction(EditorManager::tr("Close Others"), this)),
420     m_closeAllEditorsExceptVisibleContextAction(new QAction(EditorManager::tr("Close All Except Visible"), this)),
421     m_openGraphicalShellAction(new QAction(FileUtils::msgGraphicalShellAction(), this)),
422     m_openGraphicalShellContextAction(new QAction(FileUtils::msgGraphicalShellAction(), this)),
423     m_openTerminalAction(new QAction(FileUtils::msgTerminalHereAction(), this)),
424     m_findInDirectoryAction(new QAction(FileUtils::msgFindInDirectory(), this)),
425     m_filePropertiesAction(new QAction(tr("Properties..."), this)),
426     m_pinAction(new QAction(tr("Pin"), this))
427 {
428     d = this;
429 }
430 
~EditorManagerPrivate()431 EditorManagerPrivate::~EditorManagerPrivate()
432 {
433     if (ICore::instance())
434         delete m_openEditorsFactory;
435 
436     // close all extra windows
437     for (int i = 0; i < m_editorAreas.size(); ++i) {
438         EditorArea *area = m_editorAreas.at(i);
439         disconnect(area, &QObject::destroyed, this, &EditorManagerPrivate::editorAreaDestroyed);
440         delete area;
441     }
442     m_editorAreas.clear();
443 
444     DocumentModel::destroy();
445     d = nullptr;
446 }
447 
init()448 void EditorManagerPrivate::init()
449 {
450     DocumentModel::init();
451     connect(ICore::instance(), &ICore::contextAboutToChange,
452             this, &EditorManagerPrivate::handleContextChange);
453     connect(qApp, &QApplication::applicationStateChanged,
454             this, [](Qt::ApplicationState state) {
455                 if (state == Qt::ApplicationActive)
456                     EditorManager::updateWindowTitles();
457             });
458 
459     const Context editManagerContext(Constants::C_EDITORMANAGER);
460     // combined context for edit & design modes
461     const Context editDesignContext(Constants::C_EDITORMANAGER, Constants::C_DESIGN_MODE);
462 
463     ActionContainer *mfile = ActionManager::actionContainer(Constants::M_FILE);
464 
465     // Revert to saved
466     m_revertToSavedAction->setIcon(QIcon::fromTheme("document-revert"));
467     Command *cmd = ActionManager::registerAction(m_revertToSavedAction,
468                                        Constants::REVERTTOSAVED, editManagerContext);
469     cmd->setAttribute(Command::CA_UpdateText);
470     cmd->setDescription(tr("Revert File to Saved"));
471     mfile->addAction(cmd, Constants::G_FILE_SAVE);
472     connect(m_revertToSavedAction, &QAction::triggered, m_instance, &EditorManager::revertToSaved);
473 
474     // Save Action
475     ActionManager::registerAction(m_saveAction, Constants::SAVE, editManagerContext);
476     connect(m_saveAction, &QAction::triggered, m_instance, []() { EditorManager::saveDocument(); });
477 
478     // Save As Action
479     ActionManager::registerAction(m_saveAsAction, Constants::SAVEAS, editManagerContext);
480     connect(m_saveAsAction, &QAction::triggered, m_instance, &EditorManager::saveDocumentAs);
481 
482     // Window Menu
483     ActionContainer *mwindow = ActionManager::actionContainer(Constants::M_WINDOW);
484 
485     // Window menu separators
486     mwindow->addSeparator(editManagerContext, Constants::G_WINDOW_SPLIT);
487     mwindow->addSeparator(editManagerContext, Constants::G_WINDOW_NAVIGATE);
488 
489     // Close Action
490     cmd = ActionManager::registerAction(m_closeCurrentEditorAction, Constants::CLOSE, editManagerContext, true);
491     cmd->setDefaultKeySequence(QKeySequence(tr("Ctrl+W")));
492     cmd->setAttribute(Command::CA_UpdateText);
493     cmd->setDescription(m_closeCurrentEditorAction->text());
494     mfile->addAction(cmd, Constants::G_FILE_CLOSE);
495     connect(m_closeCurrentEditorAction, &QAction::triggered,
496             m_instance, &EditorManager::slotCloseCurrentEditorOrDocument);
497 
498     if (HostOsInfo::isWindowsHost()) {
499         // workaround for QTCREATORBUG-72
500         QAction *action = new QAction(tr("Alternative Close"), this);
501         cmd = ActionManager::registerAction(action, Constants::CLOSE_ALTERNATIVE, editManagerContext);
502         cmd->setDefaultKeySequence(QKeySequence(tr("Ctrl+F4")));
503         cmd->setDescription(EditorManager::tr("Close"));
504         connect(action, &QAction::triggered,
505                 m_instance, &EditorManager::slotCloseCurrentEditorOrDocument);
506     }
507 
508     // Close All Action
509     cmd = ActionManager::registerAction(m_closeAllEditorsAction, Constants::CLOSEALL, editManagerContext, true);
510     cmd->setDefaultKeySequence(QKeySequence(tr("Ctrl+Shift+W")));
511     mfile->addAction(cmd, Constants::G_FILE_CLOSE);
512     connect(m_closeAllEditorsAction, &QAction::triggered, m_instance, &EditorManager::closeAllDocuments);
513 
514     // Close All Others Action
515     cmd = ActionManager::registerAction(m_closeOtherDocumentsAction, Constants::CLOSEOTHERS, editManagerContext, true);
516     mfile->addAction(cmd, Constants::G_FILE_CLOSE);
517     cmd->setAttribute(Command::CA_UpdateText);
518     connect(m_closeOtherDocumentsAction, &QAction::triggered,
519             m_instance, []() { EditorManager::closeOtherDocuments(); });
520 
521     // Close All Others Except Visible Action
522     cmd = ActionManager::registerAction(m_closeAllEditorsExceptVisibleAction, Constants::CLOSEALLEXCEPTVISIBLE, editManagerContext, true);
523     mfile->addAction(cmd, Constants::G_FILE_CLOSE);
524     connect(m_closeAllEditorsExceptVisibleAction,
525             &QAction::triggered,
526             this,
527             &EditorManagerPrivate::closeAllEditorsExceptVisible);
528 
529     cmd = ActionManager::registerAction(m_openGraphicalShellAction,
530                                         Constants::SHOWINGRAPHICALSHELL,
531                                         editManagerContext);
532     connect(m_openGraphicalShellAction, &QAction::triggered, this, [] {
533         if (!EditorManager::currentDocument())
534             return;
535         const FilePath fp = EditorManager::currentDocument()->filePath();
536         if (!fp.isEmpty())
537             FileUtils::showInGraphicalShell(ICore::dialogParent(), fp.toString());
538     });
539 
540     //Save XXX Context Actions
541     connect(m_copyFilePathContextAction, &QAction::triggered,
542             this, &EditorManagerPrivate::copyFilePathFromContextMenu);
543     connect(m_copyLocationContextAction, &QAction::triggered,
544             this, &EditorManagerPrivate::copyLocationFromContextMenu);
545     connect(m_copyFileNameContextAction, &QAction::triggered,
546             this, &EditorManagerPrivate::copyFileNameFromContextMenu);
547     connect(m_saveCurrentEditorContextAction, &QAction::triggered,
548             this, &EditorManagerPrivate::saveDocumentFromContextMenu);
549     connect(m_saveAsCurrentEditorContextAction, &QAction::triggered,
550             this, &EditorManagerPrivate::saveDocumentAsFromContextMenu);
551     connect(m_revertToSavedCurrentEditorContextAction, &QAction::triggered,
552             this, &EditorManagerPrivate::revertToSavedFromContextMenu);
553 
554     // Close XXX Context Actions
555     connect(m_closeAllEditorsContextAction, &QAction::triggered,
556             m_instance, &EditorManager::closeAllDocuments);
557     connect(m_closeCurrentEditorContextAction, &QAction::triggered,
558             this, &EditorManagerPrivate::closeEditorFromContextMenu);
559     connect(m_closeOtherDocumentsContextAction, &QAction::triggered,
560             this, &EditorManagerPrivate::closeOtherDocumentsFromContextMenu);
561     connect(m_closeAllEditorsExceptVisibleContextAction, &QAction::triggered,
562             this, &EditorManagerPrivate::closeAllEditorsExceptVisible);
563 
564     connect(m_openGraphicalShellContextAction, &QAction::triggered, this, [this] {
565         if (!m_contextMenuEntry || m_contextMenuEntry->fileName().isEmpty())
566             return;
567         FileUtils::showInGraphicalShell(ICore::dialogParent(),
568                                         m_contextMenuEntry->fileName().toString());
569     });
570     connect(m_openTerminalAction, &QAction::triggered, this, &EditorManagerPrivate::openTerminal);
571     connect(m_findInDirectoryAction, &QAction::triggered,
572             this, &EditorManagerPrivate::findInDirectory);
573     connect(m_filePropertiesAction, &QAction::triggered, this, []() {
574         if (!d->m_contextMenuEntry || d->m_contextMenuEntry->fileName().isEmpty())
575             return;
576         DocumentManager::showFilePropertiesDialog(d->m_contextMenuEntry->fileName());
577     });
578     connect(m_pinAction, &QAction::triggered, this, &EditorManagerPrivate::togglePinned);
579 
580     // Goto Previous In History Action
581     cmd = ActionManager::registerAction(m_gotoPreviousDocHistoryAction, Constants::GOTOPREVINHISTORY, editDesignContext);
582     cmd->setDefaultKeySequence(QKeySequence(useMacShortcuts ? tr("Alt+Tab") : tr("Ctrl+Tab")));
583     mwindow->addAction(cmd, Constants::G_WINDOW_NAVIGATE);
584     connect(m_gotoPreviousDocHistoryAction, &QAction::triggered,
585             this, &EditorManagerPrivate::gotoPreviousDocHistory);
586 
587     // Goto Next In History Action
588     cmd = ActionManager::registerAction(m_gotoNextDocHistoryAction, Constants::GOTONEXTINHISTORY, editDesignContext);
589     cmd->setDefaultKeySequence(QKeySequence(useMacShortcuts ? tr("Alt+Shift+Tab") : tr("Ctrl+Shift+Tab")));
590     mwindow->addAction(cmd, Constants::G_WINDOW_NAVIGATE);
591     connect(m_gotoNextDocHistoryAction, &QAction::triggered,
592             this, &EditorManagerPrivate::gotoNextDocHistory);
593 
594     // Go back in navigation history
595     cmd = ActionManager::registerAction(m_goBackAction, Constants::GO_BACK, editDesignContext);
596     cmd->setDefaultKeySequence(QKeySequence(useMacShortcuts ? tr("Ctrl+Alt+Left") : tr("Alt+Left")));
597     mwindow->addAction(cmd, Constants::G_WINDOW_NAVIGATE);
598     connect(m_goBackAction, &QAction::triggered,
599             m_instance, &EditorManager::goBackInNavigationHistory);
600 
601     // Go forward in navigation history
602     cmd = ActionManager::registerAction(m_goForwardAction, Constants::GO_FORWARD, editDesignContext);
603     cmd->setDefaultKeySequence(QKeySequence(useMacShortcuts ? tr("Ctrl+Alt+Right") : tr("Alt+Right")));
604     mwindow->addAction(cmd, Constants::G_WINDOW_NAVIGATE);
605     connect(m_goForwardAction, &QAction::triggered,
606             m_instance, &EditorManager::goForwardInNavigationHistory);
607 
608     // Go to last edit
609     cmd = ActionManager::registerAction(m_gotoLastEditAction, Constants::GOTOLASTEDIT, editDesignContext);
610     mwindow->addAction(cmd, Constants::G_WINDOW_NAVIGATE);
611     connect(m_gotoLastEditAction, &QAction::triggered,
612             this, &EditorManagerPrivate::gotoLastEditLocation);
613 
614     m_splitAction = new QAction(Utils::Icons::SPLIT_HORIZONTAL.icon(), tr("Split"), this);
615     cmd = ActionManager::registerAction(m_splitAction, Constants::SPLIT, editManagerContext);
616     cmd->setDefaultKeySequence(QKeySequence(useMacShortcuts ? tr("Meta+E,2") : tr("Ctrl+E,2")));
617     mwindow->addAction(cmd, Constants::G_WINDOW_SPLIT);
618     connect(m_splitAction, &QAction::triggered, this, []() { split(Qt::Vertical); });
619 
620     m_splitSideBySideAction = new QAction(Utils::Icons::SPLIT_VERTICAL.icon(),
621                                           tr("Split Side by Side"), this);
622     cmd = ActionManager::registerAction(m_splitSideBySideAction, Constants::SPLIT_SIDE_BY_SIDE, editManagerContext);
623     cmd->setDefaultKeySequence(QKeySequence(useMacShortcuts ? tr("Meta+E,3") : tr("Ctrl+E,3")));
624     mwindow->addAction(cmd, Constants::G_WINDOW_SPLIT);
625     connect(m_splitSideBySideAction, &QAction::triggered, m_instance, &EditorManager::splitSideBySide);
626 
627     m_splitNewWindowAction = new QAction(tr("Open in New Window"), this);
628     cmd = ActionManager::registerAction(m_splitNewWindowAction, Constants::SPLIT_NEW_WINDOW, editManagerContext);
629     cmd->setDefaultKeySequence(QKeySequence(useMacShortcuts ? tr("Meta+E,4") : tr("Ctrl+E,4")));
630     mwindow->addAction(cmd, Constants::G_WINDOW_SPLIT);
631     connect(m_splitNewWindowAction, &QAction::triggered,
632             this, []() { splitNewWindow(currentEditorView()); });
633 
634     m_removeCurrentSplitAction = new QAction(tr("Remove Current Split"), this);
635     cmd = ActionManager::registerAction(m_removeCurrentSplitAction, Constants::REMOVE_CURRENT_SPLIT, editManagerContext);
636     cmd->setDefaultKeySequence(QKeySequence(useMacShortcuts ? tr("Meta+E,0") : tr("Ctrl+E,0")));
637     mwindow->addAction(cmd, Constants::G_WINDOW_SPLIT);
638     connect(m_removeCurrentSplitAction, &QAction::triggered,
639             this, &EditorManagerPrivate::removeCurrentSplit);
640 
641     m_removeAllSplitsAction = new QAction(tr("Remove All Splits"), this);
642     cmd = ActionManager::registerAction(m_removeAllSplitsAction, Constants::REMOVE_ALL_SPLITS, editManagerContext);
643     cmd->setDefaultKeySequence(QKeySequence(useMacShortcuts ? tr("Meta+E,1") : tr("Ctrl+E,1")));
644     mwindow->addAction(cmd, Constants::G_WINDOW_SPLIT);
645     connect(m_removeAllSplitsAction, &QAction::triggered,
646             this, &EditorManagerPrivate::removeAllSplits);
647 
648     m_gotoPreviousSplitAction = new QAction(tr("Go to Previous Split or Window"), this);
649     cmd = ActionManager::registerAction(m_gotoPreviousSplitAction, Constants::GOTO_PREV_SPLIT, editManagerContext);
650     cmd->setDefaultKeySequence(QKeySequence(useMacShortcuts ? tr("Meta+E,i") : tr("Ctrl+E,i")));
651     mwindow->addAction(cmd, Constants::G_WINDOW_SPLIT);
652     connect(m_gotoPreviousSplitAction, &QAction::triggered,
653             this, &EditorManagerPrivate::gotoPreviousSplit);
654 
655     m_gotoNextSplitAction = new QAction(tr("Go to Next Split or Window"), this);
656     cmd = ActionManager::registerAction(m_gotoNextSplitAction, Constants::GOTO_NEXT_SPLIT, editManagerContext);
657     cmd->setDefaultKeySequence(QKeySequence(useMacShortcuts ? tr("Meta+E,o") : tr("Ctrl+E,o")));
658     mwindow->addAction(cmd, Constants::G_WINDOW_SPLIT);
659     connect(m_gotoNextSplitAction, &QAction::triggered, this, &EditorManagerPrivate::gotoNextSplit);
660 
661     ActionContainer *medit = ActionManager::actionContainer(Constants::M_EDIT);
662     ActionContainer *advancedMenu = ActionManager::createMenu(Constants::M_EDIT_ADVANCED);
663     medit->addMenu(advancedMenu, Constants::G_EDIT_ADVANCED);
664     advancedMenu->menu()->setTitle(tr("Ad&vanced"));
665     advancedMenu->appendGroup(Constants::G_EDIT_FORMAT);
666     advancedMenu->appendGroup(Constants::G_EDIT_TEXT);
667     advancedMenu->appendGroup(Constants::G_EDIT_COLLAPSING);
668     advancedMenu->appendGroup(Constants::G_EDIT_BLOCKS);
669     advancedMenu->appendGroup(Constants::G_EDIT_FONT);
670     advancedMenu->appendGroup(Constants::G_EDIT_EDITOR);
671 
672     // Advanced menu separators
673     advancedMenu->addSeparator(editManagerContext, Constants::G_EDIT_TEXT);
674     advancedMenu->addSeparator(editManagerContext, Constants::G_EDIT_COLLAPSING);
675     advancedMenu->addSeparator(editManagerContext, Constants::G_EDIT_BLOCKS);
676     advancedMenu->addSeparator(editManagerContext, Constants::G_EDIT_FONT);
677     advancedMenu->addSeparator(editManagerContext, Constants::G_EDIT_EDITOR);
678 
679     // other setup
680     auto mainEditorArea = new EditorArea();
681     // assign parent to avoid failing updates (e.g. windowTitle) before it is displayed first time
682     mainEditorArea->setParent(ICore::mainWindow());
683     mainEditorArea->hide();
684     connect(mainEditorArea, &EditorArea::windowTitleNeedsUpdate,
685             this, &EditorManagerPrivate::updateWindowTitle);
686     connect(mainEditorArea, &QObject::destroyed, this, &EditorManagerPrivate::editorAreaDestroyed);
687     d->m_editorAreas.append(mainEditorArea);
688     d->m_currentView = mainEditorArea->view();
689 
690     updateActions();
691 
692     // The popup needs a parent to get keyboard focus.
693     m_windowPopup = new OpenEditorsWindow(mainEditorArea);
694     m_windowPopup->hide();
695 
696     m_autoSaveTimer = new QTimer(this);
697     m_autoSaveTimer->setObjectName("EditorManager::m_autoSaveTimer");
698     connect(m_autoSaveTimer, &QTimer::timeout, this, &EditorManagerPrivate::autoSave);
699     updateAutoSave();
700 
701     d->m_openEditorsFactory = new OpenEditorsViewFactory();
702 
703     globalMacroExpander()->registerFileVariables(kCurrentDocumentPrefix, tr("Current document"),
704         [] {
705             IDocument *document = EditorManager::currentDocument();
706             return document ? document->filePath() : FilePath();
707         });
708 
709     globalMacroExpander()->registerIntVariable(kCurrentDocumentXPos,
710         tr("X-coordinate of the current editor's upper left corner, relative to screen."),
711         []() -> int {
712             IEditor *editor = EditorManager::currentEditor();
713             return editor ? editor->widget()->mapToGlobal(QPoint(0, 0)).x() : 0;
714         });
715 
716     globalMacroExpander()->registerIntVariable(kCurrentDocumentYPos,
717         tr("Y-coordinate of the current editor's upper left corner, relative to screen."),
718         []() -> int {
719             IEditor *editor = EditorManager::currentEditor();
720             return editor ? editor->widget()->mapToGlobal(QPoint(0, 0)).y() : 0;
721                                                });
722 }
723 
extensionsInitialized()724 void EditorManagerPrivate::extensionsInitialized()
725 {
726     // Do not ask for files to save.
727     // MainWindow::closeEvent has already done that.
728     ICore::addPreCloseListener([]() -> bool { return EditorManager::closeAllEditors(false); });
729 }
730 
instance()731 EditorManagerPrivate *EditorManagerPrivate::instance()
732 {
733     return d;
734 }
735 
mainEditorArea()736 EditorArea *EditorManagerPrivate::mainEditorArea()
737 {
738     return d->m_editorAreas.at(0);
739 }
740 
skipOpeningBigTextFile(const FilePath & filePath)741 bool EditorManagerPrivate::skipOpeningBigTextFile(const FilePath &filePath)
742 {
743     if (!d->m_settings.warnBeforeOpeningBigFilesEnabled)
744         return false;
745 
746     if (!filePath.exists())
747         return false;
748 
749     Utils::MimeType mimeType = Utils::mimeTypeForFile(filePath.toString());
750     if (!mimeType.inherits("text/plain"))
751         return false;
752 
753     const QFileInfo fileInfo = filePath.toFileInfo();
754     const qint64 fileSize = fileInfo.size();
755     const double fileSizeInMB = fileSize / 1000.0 / 1000.0;
756     if (fileSizeInMB > d->m_settings.bigFileSizeLimitInMB
757         && fileSize < EditorManager::maxTextFileSize()) {
758         const QString title = EditorManager::tr("Continue Opening Huge Text File?");
759         const QString text = EditorManager::tr(
760             "The text file \"%1\" has the size %2MB and might take more memory to open"
761             " and process than available.\n"
762             "\n"
763             "Continue?")
764                 .arg(fileInfo.fileName())
765                 .arg(fileSizeInMB, 0, 'f', 2);
766 
767         CheckableMessageBox messageBox(ICore::dialogParent());
768         messageBox.setWindowTitle(title);
769         messageBox.setText(text);
770         messageBox.setStandardButtons(QDialogButtonBox::Yes|QDialogButtonBox::No);
771         messageBox.setDefaultButton(QDialogButtonBox::No);
772         messageBox.setIcon(QMessageBox::Question);
773         messageBox.setCheckBoxVisible(true);
774         messageBox.setCheckBoxText(CheckableMessageBox::msgDoNotAskAgain());
775         messageBox.exec();
776         setWarnBeforeOpeningBigFilesEnabled(!messageBox.isChecked());
777         return messageBox.clickedStandardButton() != QDialogButtonBox::Yes;
778     }
779 
780     return false;
781 }
782 
openEditor(EditorView * view,const FilePath & filePath,Id editorId,EditorManager::OpenEditorFlags flags,bool * newEditor)783 IEditor *EditorManagerPrivate::openEditor(EditorView *view, const FilePath &filePath, Id editorId,
784                                           EditorManager::OpenEditorFlags flags, bool *newEditor)
785 {
786     if (debugEditorManager)
787         qDebug() << Q_FUNC_INFO << filePath << editorId.name();
788 
789     const QString fn = Utils::FileUtils::normalizePathName(filePath.toString());
790     if (fn.isEmpty())
791         return nullptr;
792     const QFileInfo fi(fn);
793 
794     if (newEditor)
795         *newEditor = false;
796 
797     const QList<IEditor *> editors = DocumentModel::editorsForFilePath(FilePath::fromString(fn));
798     if (!editors.isEmpty()) {
799         IEditor *editor = editors.first();
800         if (flags & EditorManager::SwitchSplitIfAlreadyVisible) {
801             for (IEditor *ed : editors) {
802                 EditorView *v = viewForEditor(ed);
803                 // Don't switch to a view where editor is not its current editor
804                 if (v && v->currentEditor() == ed) {
805                     editor = ed;
806                     view = v;
807                     break;
808                 }
809             }
810         }
811         return activateEditor(view, editor, flags);
812     }
813 
814     QString realFn = autoSaveName(fn);
815     QFileInfo rfi(realFn);
816     if (!fi.exists() || !rfi.exists() || fi.lastModified() >= rfi.lastModified()) {
817         QFile::remove(realFn);
818         realFn = fn;
819     }
820 
821     EditorFactoryList factories = EditorManagerPrivate::findFactories(Id(), fn);
822     if (factories.isEmpty()) {
823         Utils::MimeType mimeType = Utils::mimeTypeForFile(fn);
824         QMessageBox msgbox(QMessageBox::Critical, EditorManager::tr("File Error"),
825                            tr("Could not open \"%1\": Cannot open files of type \"%2\".")
826                            .arg(FilePath::fromString(realFn).toUserOutput(), mimeType.name()),
827                            QMessageBox::Ok, ICore::dialogParent());
828         msgbox.exec();
829         return nullptr;
830     }
831     if (editorId.isValid()) {
832         IEditorFactory *factory = Utils::findOrDefault(IEditorFactory::allEditorFactories(),
833                                                        Utils::equal(&IEditorFactory::id, editorId));
834         if (factory) {
835             factories.removeOne(factory);
836             factories.push_front(factory);
837         }
838     }
839 
840     if (skipOpeningBigTextFile(filePath))
841         return nullptr;
842 
843     IEditor *editor = nullptr;
844     auto overrideCursor = Utils::OverrideCursor(QCursor(Qt::WaitCursor));
845 
846     auto fp = Utils::FilePath::fromString(fn);
847     auto realFp = Utils::FilePath::fromString(realFn);
848 
849     IEditorFactory *factory = factories.takeFirst();
850     while (factory) {
851         editor = createEditor(factory, fn);
852         if (!editor) {
853             factory = factories.takeFirst();
854             continue;
855         }
856 
857         QString errorString;
858         IDocument::OpenResult openResult = editor->document()->open(&errorString, fp, realFp);
859         if (openResult == IDocument::OpenResult::Success)
860             break;
861 
862         overrideCursor.reset();
863         delete editor;
864         editor = nullptr;
865 
866         if (openResult == IDocument::OpenResult::ReadError) {
867             QMessageBox msgbox(QMessageBox::Critical, EditorManager::tr("File Error"),
868                                tr("Could not open \"%1\" for reading. "
869                                   "Either the file does not exist or you do not have "
870                                   "the permissions to open it.")
871                                .arg(FilePath::fromString(realFn).toUserOutput()),
872                                QMessageBox::Ok, ICore::dialogParent());
873             msgbox.exec();
874             return nullptr;
875         }
876         QTC_CHECK(openResult == IDocument::OpenResult::CannotHandle);
877 
878         if (errorString.isEmpty()) {
879             errorString = tr("Could not open \"%1\": Unknown error.")
880                     .arg(FilePath::fromString(realFn).toUserOutput());
881         }
882 
883         QMessageBox msgbox(QMessageBox::Critical,
884                            EditorManager::tr("File Error"),
885                            errorString,
886                            QMessageBox::Open | QMessageBox::Cancel,
887                            ICore::dialogParent());
888 
889         IEditorFactory *selectedFactory = nullptr;
890         if (!factories.isEmpty()) {
891             auto button = qobject_cast<QPushButton *>(msgbox.button(QMessageBox::Open));
892             QTC_ASSERT(button, return nullptr);
893             auto menu = new QMenu(button);
894             foreach (IEditorFactory *factory, factories) {
895                 QAction *action = menu->addAction(factory->displayName());
896                 connect(action, &QAction::triggered, &msgbox, [&selectedFactory, factory, &msgbox]() {
897                     selectedFactory = factory;
898                     msgbox.done(QMessageBox::Open);
899                 });
900             }
901 
902             button->setMenu(menu);
903         } else {
904             msgbox.setStandardButtons(QMessageBox::Ok);
905         }
906 
907         int ret = msgbox.exec();
908         if (ret == QMessageBox::Cancel || ret == QMessageBox::Ok)
909             return nullptr;
910 
911         overrideCursor.set();
912 
913         factories.removeOne(selectedFactory);
914         factory = selectedFactory;
915     }
916 
917     if (!editor)
918         return nullptr;
919 
920     if (realFn != fn)
921         editor->document()->setRestoredFrom(realFp);
922     addEditor(editor);
923 
924     if (newEditor)
925         *newEditor = true;
926 
927     IEditor *result = activateEditor(view, editor, flags);
928     if (editor == result)
929         restoreEditorState(editor);
930 
931     return result;
932 }
933 
openEditorAt(EditorView * view,const Link & link,Id editorId,EditorManager::OpenEditorFlags flags,bool * newEditor)934 IEditor *EditorManagerPrivate::openEditorAt(EditorView *view,
935                                             const Link &link,
936                                             Id editorId,
937                                             EditorManager::OpenEditorFlags flags,
938                                             bool *newEditor)
939 {
940     EditorManager::cutForwardNavigationHistory();
941     EditorManager::addCurrentPositionToNavigationHistory();
942     EditorManager::OpenEditorFlags tempFlags = flags | EditorManager::IgnoreNavigationHistory;
943     IEditor *editor = openEditor(view, link.targetFilePath, editorId, tempFlags, newEditor);
944     if (editor && link.targetLine != -1)
945         editor->gotoLine(link.targetLine, link.targetColumn);
946     return editor;
947 }
948 
openEditorWith(const FilePath & filePath,Id editorId)949 IEditor *EditorManagerPrivate::openEditorWith(const FilePath &filePath, Id editorId)
950 {
951     // close any open editors that have this file open
952     // remember the views to open new editors in there
953     QList<EditorView *> views;
954     QList<IEditor *> editorsOpenForFile = DocumentModel::editorsForFilePath(filePath);
955     foreach (IEditor *openEditor, editorsOpenForFile) {
956         EditorView *view = EditorManagerPrivate::viewForEditor(openEditor);
957         if (view && view->currentEditor() == openEditor) // visible
958             views.append(view);
959     }
960     if (!EditorManager::closeEditors(editorsOpenForFile)) // don't open if cancel was pressed
961         return nullptr;
962 
963     IEditor *openedEditor = nullptr;
964     if (views.isEmpty()) {
965         openedEditor = EditorManager::openEditor(filePath, editorId);
966     } else {
967         if (EditorView *currentView = EditorManagerPrivate::currentEditorView()) {
968             if (views.removeOne(currentView))
969                 views.prepend(currentView); // open editor in current view first
970         }
971         EditorManager::OpenEditorFlags flags;
972         foreach (EditorView *view, views) {
973             IEditor *editor = EditorManagerPrivate::openEditor(view, filePath, editorId, flags);
974             if (!openedEditor && editor)
975                 openedEditor = editor;
976             // Do not change the current editor after opening the first one. That
977             // * prevents multiple updates of focus etc which are not necessary
978             // * lets us control which editor is made current by putting the current editor view
979             //   to the front (if that was in the list in the first place)
980             flags |= EditorManager::DoNotChangeCurrentEditor;
981             // do not try to open more editors if this one failed, or editor type does not
982             // support duplication anyhow
983             if (!editor || !editor->duplicateSupported())
984                 break;
985         }
986     }
987     return openedEditor;
988 }
989 
activateEditorForDocument(EditorView * view,IDocument * document,EditorManager::OpenEditorFlags flags)990 IEditor *EditorManagerPrivate::activateEditorForDocument(EditorView *view, IDocument *document,
991                                                          EditorManager::OpenEditorFlags flags)
992 {
993     Q_ASSERT(view);
994     IEditor *editor = view->editorForDocument(document);
995     if (!editor) {
996         const QList<IEditor*> editors = DocumentModel::editorsForDocument(document);
997         if (editors.isEmpty())
998             return nullptr;
999         editor = editors.first();
1000     }
1001     return activateEditor(view, editor, flags);
1002 }
1003 
viewForEditor(IEditor * editor)1004 EditorView *EditorManagerPrivate::viewForEditor(IEditor *editor)
1005 {
1006     QWidget *w = editor->widget();
1007     while (w) {
1008         w = w->parentWidget();
1009         if (auto view = qobject_cast<EditorView *>(w))
1010             return view;
1011     }
1012     return nullptr;
1013 }
1014 
makeFileWritable(IDocument * document)1015 MakeWritableResult EditorManagerPrivate::makeFileWritable(IDocument *document)
1016 {
1017     if (!document)
1018         return Failed;
1019     ReadOnlyFilesDialog roDialog(document, ICore::dialogParent(), document->isSaveAsAllowed());
1020     switch (roDialog.exec()) {
1021     case ReadOnlyFilesDialog::RO_MakeWritable:
1022     case ReadOnlyFilesDialog::RO_OpenVCS:
1023         return MadeWritable;
1024     case ReadOnlyFilesDialog::RO_SaveAs:
1025         return SavedAs;
1026     default:
1027         return Failed;
1028     }
1029 }
1030 
1031 /*!
1032     Implements the logic of the escape key shortcut (ReturnToEditor).
1033     Should only be called by the shortcut handler.
1034     \internal
1035 */
doEscapeKeyFocusMoveMagic()1036 void EditorManagerPrivate::doEscapeKeyFocusMoveMagic()
1037 {
1038     // use cases to cover:
1039     // 1. if app focus is in mode or external window without editor view (e.g. Design, Projects, ext. Help)
1040     //      if there are extra views (e.g. output)
1041     //        hide them
1042     //      otherwise
1043     //        activate & raise the current editor view (can be external)
1044     //        if that is in edit mode
1045     //          activate edit mode and unmaximize output pane
1046     // 2. if app focus is in external window with editor view
1047     //      hide find if necessary
1048     // 2. if app focus is in mode with editor view
1049     //      if current editor view is in external window
1050     //        raise and activate current editor view
1051     //      otherwise if the current editor view is not app focus
1052     //        move focus to editor view in mode and unmaximize output pane
1053     //      otherwise if the current view is app focus
1054     //        if mode is not edit mode
1055     //          if there are extra views (find, help, output)
1056     //            hide them
1057     //          otherwise
1058     //            activate edit mode and unmaximize output pane
1059     //        otherwise (i.e. mode is edit mode)
1060     //          hide extra views (find, help, output)
1061 
1062     QWidget *activeWindow = QApplication::activeWindow();
1063     if (!activeWindow)
1064         return;
1065     QWidget *focus = QApplication::focusWidget();
1066     EditorView *editorView = currentEditorView();
1067     bool editorViewActive = (focus && focus == editorView->focusWidget());
1068     bool editorViewVisible = editorView->isVisible();
1069 
1070     bool stuffHidden = false;
1071     FindToolBarPlaceHolder *findPane = FindToolBarPlaceHolder::getCurrent();
1072     if (findPane && findPane->isVisible() && findPane->isUsedByWidget(focus)) {
1073         findPane->hide();
1074         stuffHidden = true;
1075     } else if (!( editorViewVisible && !editorViewActive && editorView->window() == activeWindow )) {
1076         QWidget *outputPane = OutputPanePlaceHolder::getCurrent();
1077         if (outputPane && outputPane->isVisible() && outputPane->window() == activeWindow) {
1078             OutputPaneManager::instance()->slotHide();
1079             stuffHidden = true;
1080         }
1081         QWidget *rightPane = RightPanePlaceHolder::current();
1082         if (rightPane && rightPane->isVisible() && rightPane->window() == activeWindow) {
1083             RightPaneWidget::instance()->setShown(false);
1084             stuffHidden = true;
1085         }
1086         if (findPane && findPane->isVisible() && findPane->window() == activeWindow) {
1087             findPane->hide();
1088             stuffHidden = true;
1089         }
1090     }
1091     if (stuffHidden)
1092         return;
1093 
1094     if (!editorViewActive && editorViewVisible) {
1095         setFocusToEditorViewAndUnmaximizePanes(editorView);
1096         return;
1097     }
1098 
1099     if (!editorViewActive && !editorViewVisible) {
1100         // assumption is that editorView is in main window then
1101         ModeManager::activateMode(Id(Constants::MODE_EDIT));
1102         QTC_CHECK(editorView->isVisible());
1103         setFocusToEditorViewAndUnmaximizePanes(editorView);
1104         return;
1105     }
1106 
1107     if (editorView->window() == ICore::mainWindow()) {
1108         // we are in a editor view and there's nothing to hide, switch to edit
1109         ModeManager::activateMode(Id(Constants::MODE_EDIT));
1110         QTC_CHECK(editorView->isVisible());
1111         // next call works only because editor views in main window are shared between modes
1112         setFocusToEditorViewAndUnmaximizePanes(editorView);
1113     }
1114 }
1115 
windowPopup()1116 OpenEditorsWindow *EditorManagerPrivate::windowPopup()
1117 {
1118     return d->m_windowPopup;
1119 }
1120 
showPopupOrSelectDocument()1121 void EditorManagerPrivate::showPopupOrSelectDocument()
1122 {
1123     if (QApplication::keyboardModifiers() == Qt::NoModifier) {
1124         windowPopup()->selectAndHide();
1125     } else {
1126         QWidget *activeWindow = QApplication::activeWindow();
1127         // decide where to show the popup
1128         // if the active window has editors, we want that editor area as a reference
1129         // TODO: this does not work correctly with multiple editor areas in the same window
1130         EditorArea *activeEditorArea = nullptr;
1131         foreach (EditorArea *area, d->m_editorAreas) {
1132             if (area->window() == activeWindow) {
1133                 activeEditorArea = area;
1134                 break;
1135             }
1136         }
1137         // otherwise we take the "current" editor area
1138         if (!activeEditorArea)
1139             activeEditorArea = findEditorArea(EditorManagerPrivate::currentEditorView());
1140         QTC_ASSERT(activeEditorArea, activeEditorArea = d->m_editorAreas.first());
1141 
1142         // editor area in main window is invisible when invoked from Design Mode.
1143         QWidget *referenceWidget = activeEditorArea->isVisible() ? activeEditorArea : activeEditorArea->window();
1144         QTC_CHECK(referenceWidget->isVisible());
1145         const QPoint p = referenceWidget->mapToGlobal(QPoint(0, 0));
1146         OpenEditorsWindow *popup = windowPopup();
1147         popup->setMaximumSize(qMax(popup->minimumWidth(), referenceWidget->width() / 2),
1148                               qMax(popup->minimumHeight(), referenceWidget->height() / 2));
1149         popup->adjustSize();
1150         popup->move((referenceWidget->width() - popup->width()) / 2 + p.x(),
1151                     (referenceWidget->height() - popup->height()) / 2 + p.y());
1152         popup->setVisible(true);
1153     }
1154 }
1155 
1156 // Run the OpenWithDialog and return the editor id
1157 // selected by the user.
getOpenWithEditorId(const QString & fileName,bool * isExternalEditor)1158 Id EditorManagerPrivate::getOpenWithEditorId(const QString &fileName, bool *isExternalEditor)
1159 {
1160     // Collect editors that can open the file
1161     QList<Id> allEditorIds;
1162     QStringList allEditorDisplayNames;
1163     QList<Id> externalEditorIds;
1164     // Built-in
1165     const EditorFactoryList editors = IEditorFactory::preferredEditorFactories(fileName);
1166     const int size = editors.size();
1167     allEditorDisplayNames.reserve(size);
1168     for (int i = 0; i < size; i++) {
1169         allEditorIds.push_back(editors.at(i)->id());
1170         allEditorDisplayNames.push_back(editors.at(i)->displayName());
1171     }
1172     // External editors
1173     const Utils::MimeType mt = Utils::mimeTypeForFile(fileName);
1174     const ExternalEditorList exEditors = IExternalEditor::externalEditors(mt);
1175     const int esize = exEditors.size();
1176     for (int i = 0; i < esize; i++) {
1177         externalEditorIds.push_back(exEditors.at(i)->id());
1178         allEditorIds.push_back(exEditors.at(i)->id());
1179         allEditorDisplayNames.push_back(exEditors.at(i)->displayName());
1180     }
1181     if (allEditorIds.empty())
1182         return Id();
1183     QTC_ASSERT(allEditorIds.size() == allEditorDisplayNames.size(), return Id());
1184     // Run dialog.
1185     OpenWithDialog dialog(fileName, ICore::dialogParent());
1186     dialog.setEditors(allEditorDisplayNames);
1187     dialog.setCurrentEditor(0);
1188     if (dialog.exec() != QDialog::Accepted)
1189         return Id();
1190     const Id selectedId = allEditorIds.at(dialog.editor());
1191     if (isExternalEditor)
1192         *isExternalEditor = externalEditorIds.contains(selectedId);
1193     return selectedId;
1194 }
1195 
toMap(const QHash<Utils::MimeType,IEditorFactory * > & hash)1196 static QMap<QString, QVariant> toMap(const QHash<Utils::MimeType, IEditorFactory *> &hash)
1197 {
1198     QMap<QString, QVariant> map;
1199     auto it = hash.begin();
1200     const auto end = hash.end();
1201     while (it != end) {
1202         map.insert(it.key().name(), it.value()->id().toSetting());
1203         ++it;
1204     }
1205     return map;
1206 }
1207 
fromMap(const QMap<QString,QVariant> & map)1208 static QHash<Utils::MimeType, IEditorFactory *> fromMap(const QMap<QString, QVariant> &map)
1209 {
1210     const EditorFactoryList factories = IEditorFactory::allEditorFactories();
1211     QHash<Utils::MimeType, IEditorFactory *> hash;
1212     auto it = map.begin();
1213     const auto end = map.end();
1214     while (it != end) {
1215         const Utils::MimeType mimeType = Utils::mimeTypeForName(it.key());
1216         if (mimeType.isValid()) {
1217             const Id factoryId = Id::fromSetting(it.value());
1218             IEditorFactory *factory = Utils::findOrDefault(factories,
1219                                                            Utils::equal(&IEditorFactory::id,
1220                                                                         factoryId));
1221             if (factory)
1222                 hash.insert(mimeType, factory);
1223         }
1224         ++it;
1225     }
1226     return hash;
1227 }
1228 
saveSettings()1229 void EditorManagerPrivate::saveSettings()
1230 {
1231     ICore::settingsDatabase()->setValue(documentStatesKey, d->m_editorStates);
1232 
1233     const Settings def;
1234     QtcSettings *qsettings = ICore::settings();
1235     qsettings->setValueWithDefault(reloadBehaviorKey,
1236                                    int(d->m_settings.reloadSetting),
1237                                    int(def.reloadSetting));
1238     qsettings->setValueWithDefault(autoSaveEnabledKey,
1239                                    d->m_settings.autoSaveEnabled,
1240                                    def.autoSaveEnabled);
1241     qsettings->setValueWithDefault(autoSaveIntervalKey,
1242                                    d->m_settings.autoSaveInterval,
1243                                    def.autoSaveInterval);
1244     qsettings->setValueWithDefault(autoSuspendEnabledKey,
1245                                    d->m_settings.autoSuspendEnabled,
1246                                    def.autoSuspendEnabled);
1247     qsettings->setValueWithDefault(autoSuspendMinDocumentCountKey,
1248                                    d->m_settings.autoSuspendMinDocumentCount,
1249                                    def.autoSuspendMinDocumentCount);
1250     qsettings->setValueWithDefault(warnBeforeOpeningBigTextFilesKey,
1251                                    d->m_settings.warnBeforeOpeningBigFilesEnabled,
1252                                    def.warnBeforeOpeningBigFilesEnabled);
1253     qsettings->setValueWithDefault(bigTextFileSizeLimitKey,
1254                                    d->m_settings.bigFileSizeLimitInMB,
1255                                    def.bigFileSizeLimitInMB);
1256     qsettings->setValueWithDefault(maxRecentFilesKey,
1257                                    d->m_settings.maxRecentFiles,
1258                                    def.maxRecentFiles);
1259 
1260     qsettings->setValueWithDefault(fileSystemCaseSensitivityKey,
1261                                    HostOsInfo::fileNameCaseSensitivity(),
1262                                    OsSpecificAspects::fileNameCaseSensitivity(HostOsInfo::hostOs()));
1263     qsettings->setValueWithDefault(preferredEditorFactoriesKey,
1264                                    toMap(userPreferredEditorFactories()));
1265 }
1266 
readSettings()1267 void EditorManagerPrivate::readSettings()
1268 {
1269     Settings def;
1270     QSettings *qs = ICore::settings();
1271     d->m_settings.warnBeforeOpeningBigFilesEnabled
1272         = qs->value(warnBeforeOpeningBigTextFilesKey, def.warnBeforeOpeningBigFilesEnabled).toBool();
1273     d->m_settings.bigFileSizeLimitInMB
1274         = qs->value(bigTextFileSizeLimitKey, def.bigFileSizeLimitInMB).toInt();
1275 
1276     const int maxRecentFiles = qs->value(maxRecentFilesKey, def.maxRecentFiles).toInt();
1277     if (maxRecentFiles > 0)
1278         d->m_settings.maxRecentFiles = maxRecentFiles;
1279 
1280     if (qs->contains(fileSystemCaseSensitivityKey)) {
1281         Qt::CaseSensitivity defaultSensitivity
1282                 = OsSpecificAspects::fileNameCaseSensitivity(HostOsInfo::hostOs());
1283         bool ok = false;
1284         Qt::CaseSensitivity sensitivity = defaultSensitivity;
1285         int sensitivitySetting = qs->value(fileSystemCaseSensitivityKey).toInt(&ok);
1286         if (ok) {
1287             switch (Qt::CaseSensitivity(sensitivitySetting)) {
1288             case Qt::CaseSensitive:
1289                 sensitivity = Qt::CaseSensitive;
1290                 break;
1291             case Qt::CaseInsensitive:
1292                 sensitivity = Qt::CaseInsensitive;
1293             }
1294         }
1295         if (sensitivity == defaultSensitivity)
1296             HostOsInfo::unsetOverrideFileNameCaseSensitivity();
1297         else
1298             HostOsInfo::setOverrideFileNameCaseSensitivity(sensitivity);
1299     }
1300     const QHash<Utils::MimeType, IEditorFactory *> preferredEditorFactories = fromMap(
1301         qs->value(preferredEditorFactoriesKey).toMap());
1302     setUserPreferredEditorFactories(preferredEditorFactories);
1303 
1304     SettingsDatabase *settings = ICore::settingsDatabase();
1305     if (settings->contains(documentStatesKey)) {
1306         d->m_editorStates = settings->value(documentStatesKey)
1307             .value<QMap<QString, QVariant> >();
1308     }
1309 
1310     d->m_settings.reloadSetting = IDocument::ReloadSetting(
1311         qs->value(reloadBehaviorKey, def.reloadSetting).toInt());
1312 
1313     d->m_settings.autoSaveEnabled = qs->value(autoSaveEnabledKey, def.autoSaveEnabled).toBool();
1314     d->m_settings.autoSaveInterval = qs->value(autoSaveIntervalKey, def.autoSaveInterval).toInt();
1315 
1316     d->m_settings.autoSuspendEnabled = qs->value(autoSuspendEnabledKey, def.autoSuspendEnabled)
1317                                            .toBool();
1318     d->m_settings.autoSuspendMinDocumentCount
1319         = qs->value(autoSuspendMinDocumentCountKey, def.autoSuspendMinDocumentCount).toInt();
1320 
1321     updateAutoSave();
1322 }
1323 
setAutoSaveEnabled(bool enabled)1324 void EditorManagerPrivate::setAutoSaveEnabled(bool enabled)
1325 {
1326     d->m_settings.autoSaveEnabled = enabled;
1327     updateAutoSave();
1328 }
1329 
autoSaveEnabled()1330 bool EditorManagerPrivate::autoSaveEnabled()
1331 {
1332     return d->m_settings.autoSaveEnabled;
1333 }
1334 
setAutoSaveInterval(int interval)1335 void EditorManagerPrivate::setAutoSaveInterval(int interval)
1336 {
1337     d->m_settings.autoSaveInterval = interval;
1338     updateAutoSave();
1339 }
1340 
autoSaveInterval()1341 int EditorManagerPrivate::autoSaveInterval()
1342 {
1343     return d->m_settings.autoSaveInterval;
1344 }
1345 
setAutoSuspendEnabled(bool enabled)1346 void EditorManagerPrivate::setAutoSuspendEnabled(bool enabled)
1347 {
1348     d->m_settings.autoSuspendEnabled = enabled;
1349 }
1350 
autoSuspendEnabled()1351 bool EditorManagerPrivate::autoSuspendEnabled()
1352 {
1353     return d->m_settings.autoSuspendEnabled;
1354 }
1355 
setAutoSuspendMinDocumentCount(int count)1356 void EditorManagerPrivate::setAutoSuspendMinDocumentCount(int count)
1357 {
1358     d->m_settings.autoSuspendMinDocumentCount = count;
1359 }
1360 
autoSuspendMinDocumentCount()1361 int EditorManagerPrivate::autoSuspendMinDocumentCount()
1362 {
1363     return d->m_settings.autoSuspendMinDocumentCount;
1364 }
1365 
warnBeforeOpeningBigFilesEnabled()1366 bool EditorManagerPrivate::warnBeforeOpeningBigFilesEnabled()
1367 {
1368     return d->m_settings.warnBeforeOpeningBigFilesEnabled;
1369 }
1370 
setWarnBeforeOpeningBigFilesEnabled(bool enabled)1371 void EditorManagerPrivate::setWarnBeforeOpeningBigFilesEnabled(bool enabled)
1372 {
1373     d->m_settings.warnBeforeOpeningBigFilesEnabled = enabled;
1374 }
1375 
bigFileSizeLimit()1376 int EditorManagerPrivate::bigFileSizeLimit()
1377 {
1378     return d->m_settings.bigFileSizeLimitInMB;
1379 }
1380 
setMaxRecentFiles(int count)1381 void EditorManagerPrivate::setMaxRecentFiles(int count)
1382 {
1383     d->m_settings.maxRecentFiles = count;
1384 }
1385 
maxRecentFiles()1386 int EditorManagerPrivate::maxRecentFiles()
1387 {
1388     return d->m_settings.maxRecentFiles;
1389 }
1390 
setBigFileSizeLimit(int limitInMB)1391 void EditorManagerPrivate::setBigFileSizeLimit(int limitInMB)
1392 {
1393     d->m_settings.bigFileSizeLimitInMB = limitInMB;
1394 }
1395 
findFactories(Id editorId,const QString & fileName)1396 EditorFactoryList EditorManagerPrivate::findFactories(Id editorId, const QString &fileName)
1397 {
1398     if (debugEditorManager)
1399         qDebug() << Q_FUNC_INFO << editorId.name() << fileName;
1400 
1401     EditorFactoryList factories;
1402     if (!editorId.isValid()) {
1403         factories = IEditorFactory::preferredEditorFactories(fileName);
1404     } else {
1405         // Find by editor id
1406         IEditorFactory *factory = Utils::findOrDefault(IEditorFactory::allEditorFactories(),
1407                                                        Utils::equal(&IEditorFactory::id, editorId));
1408         if (factory)
1409             factories.push_back(factory);
1410     }
1411     if (factories.empty()) {
1412         qWarning("%s: unable to find an editor factory for the file '%s', editor Id '%s'.",
1413                  Q_FUNC_INFO, fileName.toUtf8().constData(), editorId.name().constData());
1414     }
1415 
1416     return factories;
1417 }
1418 
createEditor(IEditorFactory * factory,const QString & fileName)1419 IEditor *EditorManagerPrivate::createEditor(IEditorFactory *factory, const QString &fileName)
1420 {
1421     if (!factory)
1422         return nullptr;
1423 
1424     IEditor *editor = factory->createEditor();
1425     if (editor) {
1426         QTC_CHECK(editor->document()->id().isValid()); // sanity check that the editor has an id set
1427         connect(editor->document(), &IDocument::changed, d, &EditorManagerPrivate::handleDocumentStateChange);
1428         emit m_instance->editorCreated(editor, fileName);
1429     }
1430 
1431     return editor;
1432 }
1433 
addEditor(IEditor * editor)1434 void EditorManagerPrivate::addEditor(IEditor *editor)
1435 {
1436     if (!editor)
1437         return;
1438     ICore::addContextObject(editor);
1439 
1440     bool isNewDocument = false;
1441     DocumentModelPrivate::addEditor(editor, &isNewDocument);
1442     if (isNewDocument) {
1443         IDocument *document = editor->document();
1444         const bool isTemporary = document->isTemporary();
1445         const bool addWatcher = !isTemporary;
1446         DocumentManager::addDocument(document, addWatcher);
1447         if (!isTemporary)
1448             DocumentManager::addToRecentFiles(document->filePath(), document->id());
1449         emit m_instance->documentOpened(document);
1450     }
1451     emit m_instance->editorOpened(editor);
1452     QMetaObject::invokeMethod(d, &EditorManagerPrivate::autoSuspendDocuments, Qt::QueuedConnection);
1453 }
1454 
removeEditor(IEditor * editor,bool removeSuspendedEntry)1455 void EditorManagerPrivate::removeEditor(IEditor *editor, bool removeSuspendedEntry)
1456 {
1457     DocumentModel::Entry *entry = DocumentModelPrivate::removeEditor(editor);
1458     QTC_ASSERT(entry, return);
1459     if (entry->isSuspended) {
1460         IDocument *document = editor->document();
1461         DocumentManager::removeDocument(document);
1462         if (removeSuspendedEntry)
1463             DocumentModelPrivate::removeEntry(entry);
1464         emit m_instance->documentClosed(document);
1465     }
1466     ICore::removeContextObject(editor);
1467 }
1468 
placeEditor(EditorView * view,IEditor * editor)1469 IEditor *EditorManagerPrivate::placeEditor(EditorView *view, IEditor *editor)
1470 {
1471     Q_ASSERT(view && editor);
1472 
1473     if (view->hasEditor(editor))
1474         return editor;
1475     if (IEditor *e = view->editorForDocument(editor->document()))
1476         return e;
1477 
1478     const QByteArray state = editor->saveState();
1479     if (EditorView *sourceView = viewForEditor(editor)) {
1480         // try duplication or pull editor over to new view
1481         bool duplicateSupported = editor->duplicateSupported();
1482         if (editor != sourceView->currentEditor() || !duplicateSupported) {
1483             // pull the IEditor over to the new view
1484             sourceView->removeEditor(editor);
1485             view->addEditor(editor);
1486             view->setCurrentEditor(editor);
1487             // possibly adapts old state to new layout
1488             editor->restoreState(state);
1489             if (!sourceView->currentEditor()) {
1490                 EditorView *replacementView = nullptr;
1491                 if (IEditor *replacement = pickUnusedEditor(&replacementView)) {
1492                     if (replacementView)
1493                         replacementView->removeEditor(replacement);
1494                     sourceView->addEditor(replacement);
1495                     sourceView->setCurrentEditor(replacement);
1496                 }
1497             }
1498             return editor;
1499         } else if (duplicateSupported) {
1500             editor = duplicateEditor(editor);
1501             Q_ASSERT(editor);
1502         }
1503     }
1504     view->addEditor(editor);
1505     view->setCurrentEditor(editor);
1506     // possibly adapts old state to new layout
1507     editor->restoreState(state);
1508     return editor;
1509 }
1510 
duplicateEditor(IEditor * editor)1511 IEditor *EditorManagerPrivate::duplicateEditor(IEditor *editor)
1512 {
1513     if (!editor->duplicateSupported())
1514         return nullptr;
1515 
1516     IEditor *duplicate = editor->duplicate();
1517     emit m_instance->editorCreated(duplicate, duplicate->document()->filePath().toString());
1518     addEditor(duplicate);
1519     return duplicate;
1520 }
1521 
activateEditor(EditorView * view,IEditor * editor,EditorManager::OpenEditorFlags flags)1522 IEditor *EditorManagerPrivate::activateEditor(EditorView *view, IEditor *editor,
1523                                               EditorManager::OpenEditorFlags flags)
1524 {
1525     Q_ASSERT(view);
1526 
1527     if (!editor)
1528         return nullptr;
1529 
1530     editor = placeEditor(view, editor);
1531 
1532     if (!(flags & EditorManager::DoNotChangeCurrentEditor)) {
1533         setCurrentEditor(editor, (flags & EditorManager::IgnoreNavigationHistory));
1534         if (!(flags & EditorManager::DoNotMakeVisible)) {
1535             // switch to design mode?
1536             if (!(flags & EditorManager::DoNotSwitchToDesignMode) && editor->isDesignModePreferred()) {
1537                 ModeManager::activateMode(Constants::MODE_DESIGN);
1538                 ModeManager::setFocusToCurrentMode();
1539             } else {
1540                 if (!(flags & EditorManager::DoNotSwitchToEditMode)) {
1541                     int index;
1542                     findEditorArea(view, &index);
1543                     if (index == 0) // main window --> we might need to switch mode
1544                         if (!editor->widget()->isVisible())
1545                             ModeManager::activateMode(Constants::MODE_EDIT);
1546                 }
1547                 editor->widget()->setFocus();
1548                 if (!(flags & EditorManager::DoNotRaise))
1549                     ICore::raiseWindow(editor->widget());
1550             }
1551         }
1552     } else if (!(flags & EditorManager::DoNotMakeVisible)) {
1553         view->setCurrentEditor(editor);
1554     }
1555     return editor;
1556 }
1557 
activateEditorForEntry(EditorView * view,DocumentModel::Entry * entry,EditorManager::OpenEditorFlags flags)1558 bool EditorManagerPrivate::activateEditorForEntry(EditorView *view, DocumentModel::Entry *entry,
1559                                                   EditorManager::OpenEditorFlags flags)
1560 {
1561     QTC_ASSERT(view, return false);
1562     if (!entry) { // no document
1563         view->setCurrentEditor(nullptr);
1564         setCurrentView(view);
1565         setCurrentEditor(nullptr);
1566         return false;
1567     }
1568     IDocument *document = entry->document;
1569     if (!entry->isSuspended)  {
1570         IEditor *editor = activateEditorForDocument(view, document, flags);
1571         return editor != nullptr;
1572     }
1573 
1574     if (!openEditor(view, entry->fileName(), entry->id(), flags)) {
1575         DocumentModelPrivate::removeEntry(entry);
1576         return false;
1577     }
1578     return true;
1579 }
1580 
closeEditorOrDocument(IEditor * editor)1581 void EditorManagerPrivate::closeEditorOrDocument(IEditor *editor)
1582 {
1583     QTC_ASSERT(editor, return);
1584     QList<IEditor *> visible = EditorManager::visibleEditors();
1585     if (Utils::contains(visible,
1586                         [&editor](IEditor *other) {
1587                             return editor != other && other->document() == editor->document();
1588                         })) {
1589         EditorManager::closeEditors({editor});
1590     } else {
1591         EditorManager::closeDocuments({editor->document()});
1592     }
1593 }
1594 
closeEditors(const QList<IEditor * > & editors,CloseFlag flag)1595 bool EditorManagerPrivate::closeEditors(const QList<IEditor*> &editors, CloseFlag flag)
1596 {
1597     if (editors.isEmpty())
1598         return true;
1599     bool closingFailed = false;
1600     // close Editor History list
1601     windowPopup()->setVisible(false);
1602 
1603 
1604     EditorView *currentView = currentEditorView();
1605 
1606     // go through all editors to close and
1607     // 1. ask all core listeners to check whether the editor can be closed
1608     // 2. keep track of the document and all the editors that might remain open for it
1609     QSet<IEditor*> acceptedEditors;
1610     QHash<IDocument *, QList<IEditor *> > editorsForDocuments;
1611     foreach (IEditor *editor, editors) {
1612         bool editorAccepted = true;
1613         foreach (const std::function<bool(IEditor*)> listener, d->m_closeEditorListeners) {
1614             if (!listener(editor)) {
1615                 editorAccepted = false;
1616                 closingFailed = true;
1617                 break;
1618             }
1619         }
1620         if (editorAccepted) {
1621             acceptedEditors.insert(editor);
1622             IDocument *document = editor->document();
1623             if (!editorsForDocuments.contains(document)) // insert the document to track
1624                 editorsForDocuments.insert(document, DocumentModel::editorsForDocument(document));
1625             // keep track that we'll close this editor for the document
1626             editorsForDocuments[document].removeAll(editor);
1627         }
1628     }
1629     if (acceptedEditors.isEmpty())
1630         return false;
1631 
1632     //ask whether to save modified documents that we are about to close
1633     if (flag == CloseFlag::CloseWithAsking) {
1634         // Check for which documents we will close all editors, and therefore might have to ask the user
1635         QList<IDocument *> documentsToClose;
1636         for (auto i = editorsForDocuments.constBegin(); i != editorsForDocuments.constEnd(); ++i) {
1637             if (i.value().isEmpty())
1638                 documentsToClose.append(i.key());
1639         }
1640 
1641         bool cancelled = false;
1642         QList<IDocument *> rejectedList;
1643         DocumentManager::saveModifiedDocuments(documentsToClose, QString(), &cancelled,
1644                                                QString(), nullptr, &rejectedList);
1645         if (cancelled)
1646             return false;
1647         if (!rejectedList.isEmpty()) {
1648             closingFailed = true;
1649             QSet<IEditor*> skipSet = Utils::toSet(DocumentModel::editorsForDocuments(rejectedList));
1650             acceptedEditors = acceptedEditors.subtract(skipSet);
1651         }
1652     }
1653     if (acceptedEditors.isEmpty())
1654         return false;
1655 
1656     // save editor states
1657     for (IEditor *editor : qAsConst(acceptedEditors)) {
1658         if (!editor->document()->filePath().isEmpty() && !editor->document()->isTemporary()) {
1659             QByteArray state = editor->saveState();
1660             if (!state.isEmpty())
1661                 d->m_editorStates.insert(editor->document()->filePath().toString(), QVariant(state));
1662         }
1663     }
1664 
1665     EditorView *focusView = nullptr;
1666 
1667     // Remove accepted editors from document model/manager and context list,
1668     // and sort them per view, so we can remove them from views in an orderly
1669     // manner.
1670     QMultiHash<EditorView *, IEditor *> editorsPerView;
1671     for (IEditor *editor : qAsConst(acceptedEditors)) {
1672         emit m_instance->editorAboutToClose(editor);
1673         removeEditor(editor, flag != CloseFlag::Suspend);
1674         if (EditorView *view = viewForEditor(editor)) {
1675             editorsPerView.insert(view, editor);
1676             if (QApplication::focusWidget()
1677                 && QApplication::focusWidget() == editor->widget()->focusWidget()) {
1678                 focusView = view;
1679             }
1680         }
1681     }
1682     QTC_CHECK(!focusView || focusView == currentView);
1683 
1684     // Go through views, remove the editors from them.
1685     // Sort such that views for which the current editor is closed come last,
1686     // and if the global current view is one of them, that comes very last.
1687     // When handling the last view in the list we handle the case where all
1688     // visible editors are closed, and we need to e.g. revive an invisible or
1689     // a suspended editor
1690     QList<EditorView *> views = editorsPerView.keys();
1691     Utils::sort(views, [editorsPerView, currentView](EditorView *a, EditorView *b) {
1692         if (a == b)
1693             return false;
1694         const bool aHasCurrent = editorsPerView.values(a).contains(a->currentEditor());
1695         const bool bHasCurrent = editorsPerView.values(b).contains(b->currentEditor());
1696         const bool aHasGlobalCurrent = (a == currentView && aHasCurrent);
1697         const bool bHasGlobalCurrent = (b == currentView && bHasCurrent);
1698         if (bHasGlobalCurrent && !aHasGlobalCurrent)
1699             return true;
1700         if (bHasCurrent && !aHasCurrent)
1701             return true;
1702         return false;
1703     });
1704     for (EditorView *view : qAsConst(views)) {
1705         QList<IEditor *> editors = editorsPerView.values(view);
1706         // handle current editor in view last
1707         IEditor *viewCurrentEditor = view->currentEditor();
1708         if (editors.contains(viewCurrentEditor) && editors.last() != viewCurrentEditor) {
1709             editors.removeAll(viewCurrentEditor);
1710             editors.append(viewCurrentEditor);
1711         }
1712         for (IEditor *editor : qAsConst(editors)) {
1713             if (editor == viewCurrentEditor && view == views.last()) {
1714                 // Avoid removing the globally current editor from its view,
1715                 // set a new current editor before.
1716                 EditorManager::OpenEditorFlags flags = view != currentView
1717                         ? EditorManager::DoNotChangeCurrentEditor : EditorManager::NoFlags;
1718                 const QList<IEditor *> viewEditors = view->editors();
1719                 IEditor *newCurrent = viewEditors.size() > 1 ? viewEditors.at(viewEditors.size() - 2)
1720                                                              : nullptr;
1721                 if (!newCurrent)
1722                     newCurrent = pickUnusedEditor();
1723                 if (newCurrent) {
1724                     activateEditor(view, newCurrent, flags);
1725                 } else {
1726                     DocumentModel::Entry *entry = DocumentModelPrivate::firstSuspendedEntry();
1727                     if (entry) {
1728                         activateEditorForEntry(view, entry, flags);
1729                     } else { // no "suspended" ones, so any entry left should have a document
1730                         const QList<DocumentModel::Entry *> documents = DocumentModel::entries();
1731                         if (!documents.isEmpty()) {
1732                             if (IDocument *document = documents.last()->document) {
1733                                 // Do not auto-switch to design mode if the new editor will be for
1734                                 // the same document as the one that was closed.
1735                                 if (view == currentView && document == editor->document())
1736                                     flags = EditorManager::DoNotSwitchToDesignMode;
1737                                 activateEditorForDocument(view, document, flags);
1738                             }
1739                         } else {
1740                             // no documents left - set current view since view->removeEditor can
1741                             // trigger a focus change, context change, and updateActions, which
1742                             // requests the current EditorView
1743                             setCurrentView(currentView);
1744                         }
1745                     }
1746                 }
1747             }
1748             view->removeEditor(editor);
1749         }
1750     }
1751 
1752     emit m_instance->editorsClosed(Utils::toList(acceptedEditors));
1753 
1754     if (focusView) {
1755         activateView(focusView);
1756     } else {
1757         setCurrentView(currentView);
1758         setCurrentEditor(currentView->currentEditor());
1759     }
1760 
1761     foreach (IEditor *editor, acceptedEditors)
1762         delete editor;
1763 
1764     if (!EditorManager::currentEditor()) {
1765         emit m_instance->currentEditorChanged(nullptr);
1766         updateActions();
1767     }
1768 
1769     return !closingFailed;
1770 }
1771 
activateView(EditorView * view)1772 void EditorManagerPrivate::activateView(EditorView *view)
1773 {
1774     QTC_ASSERT(view, return);
1775     QWidget *focusWidget;
1776     if (IEditor *editor = view->currentEditor()) {
1777         setCurrentEditor(editor, true);
1778         focusWidget = editor->widget();
1779     } else {
1780         setCurrentView(view);
1781         focusWidget = view;
1782     }
1783     focusWidget->setFocus();
1784     ICore::raiseWindow(focusWidget);
1785 }
1786 
restoreEditorState(IEditor * editor)1787 void EditorManagerPrivate::restoreEditorState(IEditor *editor)
1788 {
1789     QTC_ASSERT(editor, return);
1790     QString fileName = editor->document()->filePath().toString();
1791     editor->restoreState(d->m_editorStates.value(fileName).toByteArray());
1792 }
1793 
visibleDocumentsCount()1794 int EditorManagerPrivate::visibleDocumentsCount()
1795 {
1796     const QList<IEditor *> editors = EditorManager::visibleEditors();
1797     const int editorsCount = editors.count();
1798     if (editorsCount < 2)
1799         return editorsCount;
1800 
1801     QSet<const IDocument *> visibleDocuments;
1802     foreach (IEditor *editor, editors) {
1803         if (const IDocument *document = editor->document())
1804             visibleDocuments << document;
1805     }
1806     return visibleDocuments.count();
1807 }
1808 
setCurrentEditor(IEditor * editor,bool ignoreNavigationHistory)1809 void EditorManagerPrivate::setCurrentEditor(IEditor *editor, bool ignoreNavigationHistory)
1810 {
1811     if (editor)
1812         setCurrentView(nullptr);
1813 
1814     if (d->m_currentEditor == editor)
1815         return;
1816 
1817     emit m_instance->currentEditorAboutToChange(d->m_currentEditor);
1818 
1819     if (d->m_currentEditor && !ignoreNavigationHistory)
1820         EditorManager::addCurrentPositionToNavigationHistory();
1821 
1822     d->m_currentEditor = editor;
1823     if (editor) {
1824         if (EditorView *view = viewForEditor(editor))
1825             view->setCurrentEditor(editor);
1826         // update global history
1827         EditorView::updateEditorHistory(editor, d->m_globalHistory);
1828     }
1829     updateActions();
1830     emit m_instance->currentEditorChanged(editor);
1831 }
1832 
setCurrentView(EditorView * view)1833 void EditorManagerPrivate::setCurrentView(EditorView *view)
1834 {
1835     if (view == d->m_currentView)
1836         return;
1837 
1838     EditorView *old = d->m_currentView;
1839     d->m_currentView = view;
1840 
1841     if (old)
1842         old->update();
1843     if (view)
1844         view->update();
1845 }
1846 
findEditorArea(const EditorView * view,int * areaIndex)1847 EditorArea *EditorManagerPrivate::findEditorArea(const EditorView *view, int *areaIndex)
1848 {
1849     SplitterOrView *current = view->parentSplitterOrView();
1850     while (current) {
1851         if (auto area = qobject_cast<EditorArea *>(current)) {
1852             int index = d->m_editorAreas.indexOf(area);
1853             QTC_ASSERT(index >= 0, return nullptr);
1854             if (areaIndex)
1855                 *areaIndex = index;
1856             return area;
1857         }
1858         current = current->findParentSplitter();
1859     }
1860     QTC_CHECK(false); // we should never have views without a editor area
1861     return nullptr;
1862 }
1863 
closeView(EditorView * view)1864 void EditorManagerPrivate::closeView(EditorView *view)
1865 {
1866     if (!view)
1867         return;
1868 
1869     const QList<IEditor *> editorsToDelete = emptyView(view);
1870 
1871     SplitterOrView *splitterOrView = view->parentSplitterOrView();
1872     Q_ASSERT(splitterOrView);
1873     Q_ASSERT(splitterOrView->view() == view);
1874     SplitterOrView *splitter = splitterOrView->findParentSplitter();
1875     Q_ASSERT(splitterOrView->hasEditors() == false);
1876     splitterOrView->hide();
1877     delete splitterOrView;
1878 
1879     splitter->unsplit();
1880 
1881     EditorView *newCurrent = splitter->findFirstView();
1882     if (newCurrent)
1883         EditorManagerPrivate::activateView(newCurrent);
1884     deleteEditors(editorsToDelete);
1885 }
1886 
1887 /*!
1888     Removes all editors from the view and from the document model, taking care of
1889     the handling of editors that are the last ones for the document.
1890     Returns the list of editors that were actually removed from the document model and
1891     need to be deleted with \c EditorManagerPrivate::deleteEditors.
1892     \internal
1893 */
emptyView(EditorView * view)1894 const QList<IEditor *> EditorManagerPrivate::emptyView(EditorView *view)
1895 {
1896     if (!view)
1897         return {};
1898     const QList<IEditor *> editors = view->editors();
1899     QList<IEditor *> removedEditors;
1900     for (IEditor *editor : editors) {
1901         if (DocumentModel::editorsForDocument(editor->document()).size() == 1) {
1902             // it's the only editor for that file
1903             // so we need to keep it around (--> in the editor model)
1904             if (EditorManager::currentEditor() == editor) {
1905                 // we don't want a current editor that is not open in a view
1906                 setCurrentView(view);
1907                 setCurrentEditor(nullptr);
1908             }
1909             view->removeEditor(editor);
1910         } else {
1911             emit m_instance->editorAboutToClose(editor);
1912             removeEditor(editor, true /*=removeSuspendedEntry, but doesn't matter since it's not the last editor anyhow*/);
1913             view->removeEditor(editor);
1914             removedEditors.append(editor);
1915         }
1916     }
1917     return removedEditors;
1918 }
1919 
1920 /*!
1921     Signals editorsClosed() and deletes the editors.
1922     \internal
1923 */
deleteEditors(const QList<IEditor * > & editors)1924 void EditorManagerPrivate::deleteEditors(const QList<IEditor *> &editors)
1925 {
1926     if (!editors.isEmpty()) {
1927         emit m_instance->editorsClosed(editors);
1928         qDeleteAll(editors);
1929     }
1930 }
1931 
createEditorWindow()1932 EditorWindow *EditorManagerPrivate::createEditorWindow()
1933 {
1934     auto win = new EditorWindow;
1935     EditorArea *area = win->editorArea();
1936     d->m_editorAreas.append(area);
1937     connect(area, &QObject::destroyed, d, &EditorManagerPrivate::editorAreaDestroyed);
1938     return win;
1939 }
1940 
splitNewWindow(EditorView * view)1941 void EditorManagerPrivate::splitNewWindow(EditorView *view)
1942 {
1943     IEditor *editor = view->currentEditor();
1944     IEditor *newEditor = nullptr;
1945     const QByteArray state = editor ? editor->saveState() : QByteArray();
1946     if (editor && editor->duplicateSupported())
1947         newEditor = EditorManagerPrivate::duplicateEditor(editor);
1948     else
1949         newEditor = editor; // move to the new view
1950 
1951     EditorWindow *win = createEditorWindow();
1952     win->show();
1953     ICore::raiseWindow(win);
1954     if (newEditor) {
1955         activateEditor(win->editorArea()->view(), newEditor, EditorManager::IgnoreNavigationHistory);
1956         // possibly adapts old state to new layout
1957         newEditor->restoreState(state);
1958     } else {
1959         win->editorArea()->view()->setFocus();
1960     }
1961     updateActions();
1962 }
1963 
pickUnusedEditor(EditorView ** foundView)1964 IEditor *EditorManagerPrivate::pickUnusedEditor(EditorView **foundView)
1965 {
1966     foreach (IEditor *editor, DocumentModel::editorsForOpenedDocuments()) {
1967         EditorView *view = viewForEditor(editor);
1968         if (!view || view->currentEditor() != editor) {
1969             if (foundView)
1970                 *foundView = view;
1971             return editor;
1972         }
1973     }
1974     return nullptr;
1975 }
1976 
1977 /* Adds the file name to the recent files if there is at least one non-temporary editor for it */
addDocumentToRecentFiles(IDocument * document)1978 void EditorManagerPrivate::addDocumentToRecentFiles(IDocument *document)
1979 {
1980     if (document->isTemporary())
1981         return;
1982     DocumentModel::Entry *entry = DocumentModel::entryForDocument(document);
1983     if (!entry)
1984         return;
1985     DocumentManager::addToRecentFiles(document->filePath(), entry->id());
1986 }
1987 
updateAutoSave()1988 void EditorManagerPrivate::updateAutoSave()
1989 {
1990     if (d->m_settings.autoSaveEnabled)
1991         d->m_autoSaveTimer->start(d->m_settings.autoSaveInterval * (60 * 1000));
1992     else
1993         d->m_autoSaveTimer->stop();
1994 }
1995 
updateMakeWritableWarning()1996 void EditorManagerPrivate::updateMakeWritableWarning()
1997 {
1998     IDocument *document = EditorManager::currentDocument();
1999     QTC_ASSERT(document, return);
2000     bool ww = document->isModified() && document->isFileReadOnly();
2001     if (ww != document->hasWriteWarning()) {
2002         document->setWriteWarning(ww);
2003 
2004         // Do this after setWriteWarning so we don't re-evaluate this part even
2005         // if we do not really show a warning.
2006         bool promptVCS = false;
2007         const QString directory = document->filePath().toFileInfo().absolutePath();
2008         IVersionControl *versionControl = VcsManager::findVersionControlForDirectory(directory);
2009         if (versionControl && versionControl->openSupportMode(document->filePath().toString()) != IVersionControl::NoOpen) {
2010             if (versionControl->settingsFlags() & IVersionControl::AutoOpen) {
2011                 vcsOpenCurrentEditor();
2012                 ww = false;
2013             } else {
2014                 promptVCS = true;
2015             }
2016         }
2017 
2018         if (ww) {
2019             // we are about to change a read-only file, warn user
2020             if (promptVCS) {
2021                 InfoBarEntry info(Id(kMakeWritableWarning),
2022                                   tr("<b>Warning:</b> This file was not opened in %1 yet.")
2023                                   .arg(versionControl->displayName()));
2024                 info.setCustomButtonInfo(tr("Open"), &vcsOpenCurrentEditor);
2025                 document->infoBar()->addInfo(info);
2026             } else {
2027                 InfoBarEntry info(Id(kMakeWritableWarning),
2028                                   tr("<b>Warning:</b> You are changing a read-only file."));
2029                 info.setCustomButtonInfo(tr("Make Writable"), &makeCurrentEditorWritable);
2030                 document->infoBar()->addInfo(info);
2031             }
2032         } else {
2033             document->infoBar()->removeInfo(Id(kMakeWritableWarning));
2034         }
2035     }
2036 }
2037 
setupSaveActions(IDocument * document,QAction * saveAction,QAction * saveAsAction,QAction * revertToSavedAction)2038 void EditorManagerPrivate::setupSaveActions(IDocument *document, QAction *saveAction,
2039                                             QAction *saveAsAction, QAction *revertToSavedAction)
2040 {
2041     const bool hasFile = document && !document->filePath().isEmpty();
2042     saveAction->setEnabled(hasFile && document->isModified());
2043     saveAsAction->setEnabled(document && document->isSaveAsAllowed());
2044     revertToSavedAction->setEnabled(hasFile);
2045 
2046     if (document && !document->displayName().isEmpty()) {
2047         const QString quotedName = QLatin1Char('"')
2048                 + Utils::quoteAmpersands(document->displayName()) + QLatin1Char('"');
2049         saveAction->setText(tr("&Save %1").arg(quotedName));
2050         saveAsAction->setText(tr("Save %1 &As...").arg(quotedName));
2051         revertToSavedAction->setText(document->isModified()
2052                                      ? tr("Revert %1 to Saved").arg(quotedName)
2053                                      : tr("Reload %1").arg(quotedName));
2054     } else {
2055         saveAction->setText(EditorManager::tr("&Save"));
2056         saveAsAction->setText(EditorManager::tr("Save &As..."));
2057         revertToSavedAction->setText(EditorManager::tr("Revert to Saved"));
2058     }
2059 }
2060 
updateActions()2061 void EditorManagerPrivate::updateActions()
2062 {
2063     IDocument *curDocument = EditorManager::currentDocument();
2064     const int openedCount = DocumentModel::entryCount();
2065 
2066     if (curDocument)
2067         updateMakeWritableWarning();
2068 
2069     QString quotedName;
2070     if (curDocument)
2071         quotedName = QLatin1Char('"') + Utils::quoteAmpersands(curDocument->displayName())
2072                 + QLatin1Char('"');
2073     setupSaveActions(curDocument, d->m_saveAction, d->m_saveAsAction, d->m_revertToSavedAction);
2074 
2075     d->m_closeCurrentEditorAction->setEnabled(curDocument);
2076     d->m_closeCurrentEditorAction->setText(tr("Close %1").arg(quotedName));
2077     d->m_closeAllEditorsAction->setEnabled(openedCount > 0);
2078     d->m_closeOtherDocumentsAction->setEnabled(openedCount > 1);
2079     d->m_closeOtherDocumentsAction->setText((openedCount > 1 ? tr("Close All Except %1").arg(quotedName) : tr("Close Others")));
2080 
2081     d->m_closeAllEditorsExceptVisibleAction->setEnabled(visibleDocumentsCount() < openedCount);
2082 
2083     d->m_gotoNextDocHistoryAction->setEnabled(openedCount != 0);
2084     d->m_gotoPreviousDocHistoryAction->setEnabled(openedCount != 0);
2085     EditorView *view  = currentEditorView();
2086     d->m_goBackAction->setEnabled(view ? view->canGoBack() : false);
2087     d->m_goForwardAction->setEnabled(view ? view->canGoForward() : false);
2088 
2089     SplitterOrView *viewParent = (view ? view->parentSplitterOrView() : nullptr);
2090     SplitterOrView *parentSplitter = (viewParent ? viewParent->findParentSplitter() : nullptr);
2091     bool hasSplitter = parentSplitter && parentSplitter->isSplitter();
2092     d->m_removeCurrentSplitAction->setEnabled(hasSplitter);
2093     d->m_removeAllSplitsAction->setEnabled(hasSplitter);
2094     d->m_gotoNextSplitAction->setEnabled(hasSplitter || d->m_editorAreas.size() > 1);
2095 }
2096 
updateWindowTitleForDocument(IDocument * document,QWidget * window)2097 void EditorManagerPrivate::updateWindowTitleForDocument(IDocument *document, QWidget *window)
2098 {
2099     QTC_ASSERT(window, return);
2100     QString windowTitle;
2101     const QString dashSep(" - ");
2102 
2103     const QString documentName = document ? document->displayName() : QString();
2104     if (!documentName.isEmpty())
2105         windowTitle.append(documentName);
2106 
2107     const QString filePath = document ? document->filePath().toFileInfo().absoluteFilePath()
2108                               : QString();
2109     const QString windowTitleAddition = d->m_titleAdditionHandler
2110             ? d->m_titleAdditionHandler(filePath)
2111             : QString();
2112     if (!windowTitleAddition.isEmpty()) {
2113         if (!windowTitle.isEmpty())
2114             windowTitle.append(" ");
2115         windowTitle.append(windowTitleAddition);
2116     }
2117 
2118     const QString windowTitleVcsTopic = d->m_titleVcsTopicHandler
2119            ? d->m_titleVcsTopicHandler(filePath)
2120            : QString();
2121     if (!windowTitleVcsTopic.isEmpty()) {
2122         if (!windowTitle.isEmpty())
2123             windowTitle.append(" ");
2124         windowTitle.append(QStringLiteral("[") + windowTitleVcsTopic + QStringLiteral("]"));
2125     }
2126 
2127     const QString sessionTitle = d->m_sessionTitleHandler
2128            ? d->m_sessionTitleHandler(filePath)
2129            : QString();
2130     if (!sessionTitle.isEmpty()) {
2131         if (!windowTitle.isEmpty())
2132             windowTitle.append(dashSep);
2133         windowTitle.append(sessionTitle);
2134     }
2135 
2136     if (!windowTitle.isEmpty())
2137         windowTitle.append(dashSep);
2138     windowTitle.append(Core::Constants::IDE_DISPLAY_NAME);
2139     window->window()->setWindowTitle(windowTitle);
2140     window->window()->setWindowFilePath(filePath);
2141 
2142     if (HostOsInfo::isMacHost()) {
2143         if (document)
2144             window->window()->setWindowModified(document->isModified());
2145         else
2146             window->window()->setWindowModified(false);
2147     }
2148 }
2149 
updateWindowTitle()2150 void EditorManagerPrivate::updateWindowTitle()
2151 {
2152     EditorArea *mainArea = mainEditorArea();
2153     IDocument *document = mainArea->currentDocument();
2154     updateWindowTitleForDocument(document, mainArea->window());
2155 }
2156 
gotoNextDocHistory()2157 void EditorManagerPrivate::gotoNextDocHistory()
2158 {
2159     OpenEditorsWindow *dialog = windowPopup();
2160     if (dialog->isVisible()) {
2161         dialog->selectNextEditor();
2162     } else {
2163         EditorView *view = currentEditorView();
2164         dialog->setEditors(d->m_globalHistory, view);
2165         dialog->selectNextEditor();
2166         showPopupOrSelectDocument();
2167     }
2168 }
2169 
gotoPreviousDocHistory()2170 void EditorManagerPrivate::gotoPreviousDocHistory()
2171 {
2172     OpenEditorsWindow *dialog = windowPopup();
2173     if (dialog->isVisible()) {
2174         dialog->selectPreviousEditor();
2175     } else {
2176         EditorView *view = currentEditorView();
2177         dialog->setEditors(d->m_globalHistory, view);
2178         dialog->selectPreviousEditor();
2179         showPopupOrSelectDocument();
2180     }
2181 }
2182 
gotoLastEditLocation()2183 void EditorManagerPrivate::gotoLastEditLocation()
2184 {
2185     currentEditorView()->goToEditLocation(d->m_globalLastEditLocation);
2186 }
2187 
gotoNextSplit()2188 void EditorManagerPrivate::gotoNextSplit()
2189 {
2190     EditorView *view = currentEditorView();
2191     if (!view)
2192         return;
2193     EditorView *nextView = view->findNextView();
2194     if (!nextView) {
2195         // we are in the "last" view in this editor area
2196         int index = -1;
2197         EditorArea *area = findEditorArea(view, &index);
2198         QTC_ASSERT(area, return);
2199         QTC_ASSERT(index >= 0 && index < d->m_editorAreas.size(), return);
2200         // find next editor area. this might be the same editor area if there's only one.
2201         int nextIndex = index + 1;
2202         if (nextIndex >= d->m_editorAreas.size())
2203             nextIndex = 0;
2204         nextView = d->m_editorAreas.at(nextIndex)->findFirstView();
2205     }
2206 
2207     if (QTC_GUARD(nextView))
2208         activateView(nextView);
2209 }
2210 
gotoPreviousSplit()2211 void EditorManagerPrivate::gotoPreviousSplit()
2212 {
2213     EditorView *view = currentEditorView();
2214     if (!view)
2215         return;
2216     EditorView *prevView = view->findPreviousView();
2217     if (!prevView) {
2218         // we are in the "first" view in this editor area
2219         int index = -1;
2220         EditorArea *area = findEditorArea(view, &index);
2221         QTC_ASSERT(area, return);
2222         QTC_ASSERT(index >= 0 && index < d->m_editorAreas.size(), return);
2223         // find previous editor area. this might be the same editor area if there's only one.
2224         int nextIndex = index - 1;
2225         if (nextIndex < 0)
2226             nextIndex = d->m_editorAreas.count() - 1;
2227         prevView = d->m_editorAreas.at(nextIndex)->findLastView();
2228     }
2229 
2230     if (QTC_GUARD(prevView))
2231         activateView(prevView);
2232 }
2233 
makeCurrentEditorWritable()2234 void EditorManagerPrivate::makeCurrentEditorWritable()
2235 {
2236     if (IDocument* doc = EditorManager::currentDocument())
2237         makeFileWritable(doc);
2238 }
2239 
setPlaceholderText(const QString & text)2240 void EditorManagerPrivate::setPlaceholderText(const QString &text)
2241 {
2242     if (d->m_placeholderText == text)
2243         return;
2244     d->m_placeholderText = text;
2245     emit d->placeholderTextChanged(d->m_placeholderText);
2246 }
2247 
placeholderText()2248 QString EditorManagerPrivate::placeholderText()
2249 {
2250     return d->m_placeholderText;
2251 }
2252 
vcsOpenCurrentEditor()2253 void EditorManagerPrivate::vcsOpenCurrentEditor()
2254 {
2255     IDocument *document = EditorManager::currentDocument();
2256     if (!document)
2257         return;
2258 
2259     const QString directory = document->filePath().toFileInfo().absolutePath();
2260     IVersionControl *versionControl = VcsManager::findVersionControlForDirectory(directory);
2261     if (!versionControl || versionControl->openSupportMode(document->filePath().toString()) == IVersionControl::NoOpen)
2262         return;
2263 
2264     if (!versionControl->vcsOpen(document->filePath().toString())) {
2265         // TODO: wrong dialog parent
2266         QMessageBox::warning(ICore::dialogParent(), tr("Cannot Open File"),
2267                              tr("Cannot open the file for editing with VCS."));
2268     }
2269 }
2270 
handleDocumentStateChange()2271 void EditorManagerPrivate::handleDocumentStateChange()
2272 {
2273     updateActions();
2274     auto document = qobject_cast<IDocument *>(sender());
2275     if (!document->isModified())
2276         document->removeAutoSaveFile();
2277     if (EditorManager::currentDocument() == document)
2278         emit m_instance->currentDocumentStateChanged();
2279     emit m_instance->documentStateChanged(document);
2280 }
2281 
editorAreaDestroyed(QObject * area)2282 void EditorManagerPrivate::editorAreaDestroyed(QObject *area)
2283 {
2284     QWidget *activeWin = QApplication::activeWindow();
2285     EditorArea *newActiveArea = nullptr;
2286     for (int i = 0; i < d->m_editorAreas.size(); ++i) {
2287         EditorArea *r = d->m_editorAreas.at(i);
2288         if (r == area) {
2289             d->m_editorAreas.removeAt(i);
2290             --i; // we removed the current one
2291         } else if (r->window() == activeWin) {
2292             newActiveArea = r;
2293         }
2294     }
2295     // check if the destroyed editor area had the current view or current editor
2296     if (d->m_currentEditor || (d->m_currentView && d->m_currentView->parentSplitterOrView() != area))
2297         return;
2298     // we need to set a new current editor or view
2299     if (!newActiveArea) {
2300         // some window managers behave weird and don't activate another window
2301         // or there might be a Qt Creator toplevel activated that doesn't have editor windows
2302         newActiveArea = d->m_editorAreas.first();
2303     }
2304 
2305     // check if the focusWidget points to some view
2306     SplitterOrView *focusSplitterOrView = nullptr;
2307     QWidget *candidate = newActiveArea->focusWidget();
2308     while (candidate && candidate != newActiveArea) {
2309         if ((focusSplitterOrView = qobject_cast<SplitterOrView *>(candidate)))
2310             break;
2311         candidate = candidate->parentWidget();
2312     }
2313     // focusWidget might have been 0
2314     if (!focusSplitterOrView)
2315         focusSplitterOrView = newActiveArea->findFirstView()->parentSplitterOrView();
2316     QTC_ASSERT(focusSplitterOrView, focusSplitterOrView = newActiveArea);
2317     EditorView *focusView = focusSplitterOrView->findFirstView(); // can be just focusSplitterOrView
2318     QTC_ASSERT(focusView, focusView = newActiveArea->findFirstView());
2319     QTC_ASSERT(focusView, return);
2320     EditorManagerPrivate::activateView(focusView);
2321 }
2322 
autoSave()2323 void EditorManagerPrivate::autoSave()
2324 {
2325     QStringList errors;
2326     // FIXME: the saving should be staggered
2327     foreach (IDocument *document, DocumentModel::openedDocuments()) {
2328         if (!document->isModified() || !document->shouldAutoSave())
2329             continue;
2330         const QString saveName = autoSaveName(document->filePath().toString());
2331         const QString savePath = QFileInfo(saveName).absolutePath();
2332         if (document->filePath().isEmpty()
2333                 || !QFileInfo(savePath).isWritable()) // FIXME: save them to a dedicated directory
2334             continue;
2335         QString errorString;
2336         if (!document->autoSave(&errorString, Utils::FilePath::fromUserInput(saveName)))
2337             errors << errorString;
2338     }
2339     if (!errors.isEmpty())
2340         QMessageBox::critical(ICore::dialogParent(),
2341                               tr("File Error"),
2342                               errors.join(QLatin1Char('\n')));
2343     emit m_instance->autoSaved();
2344 }
2345 
handleContextChange(const QList<IContext * > & context)2346 void EditorManagerPrivate::handleContextChange(const QList<IContext *> &context)
2347 {
2348     if (debugEditorManager)
2349         qDebug() << Q_FUNC_INFO;
2350     d->m_scheduledCurrentEditor = nullptr;
2351     IEditor *editor = nullptr;
2352     foreach (IContext *c, context)
2353         if ((editor = qobject_cast<IEditor*>(c)))
2354             break;
2355     if (editor && editor != d->m_currentEditor) {
2356         // Delay actually setting the current editor to after the current event queue has been handled
2357         // Without doing this, e.g. clicking into projects tree or locator would always open editors
2358         // in the main window. That is because clicking anywhere in the main window (even over e.g.
2359         // the locator line edit) first activates the window and sets focus to its focus widget.
2360         // Only afterwards the focus is shifted to the widget that received the click.
2361         d->m_scheduledCurrentEditor = editor;
2362         QMetaObject::invokeMethod(d, &EditorManagerPrivate::setCurrentEditorFromContextChange,
2363                                   Qt::QueuedConnection);
2364     } else {
2365         updateActions();
2366     }
2367 }
2368 
copyFilePathFromContextMenu()2369 void EditorManagerPrivate::copyFilePathFromContextMenu()
2370 {
2371     if (!d->m_contextMenuEntry)
2372         return;
2373     QApplication::clipboard()->setText(d->m_contextMenuEntry->fileName().toUserOutput());
2374 }
2375 
copyLocationFromContextMenu()2376 void EditorManagerPrivate::copyLocationFromContextMenu()
2377 {
2378     const auto action = qobject_cast<const QAction *>(sender());
2379     if (!d->m_contextMenuEntry || !action)
2380         return;
2381     const QString text = d->m_contextMenuEntry->fileName().toUserOutput()
2382             + QLatin1Char(':') + action->data().toString();
2383     QApplication::clipboard()->setText(text);
2384 }
2385 
copyFileNameFromContextMenu()2386 void EditorManagerPrivate::copyFileNameFromContextMenu()
2387 {
2388     if (!d->m_contextMenuEntry)
2389         return;
2390     QApplication::clipboard()->setText(d->m_contextMenuEntry->fileName().fileName());
2391 }
2392 
saveDocumentFromContextMenu()2393 void EditorManagerPrivate::saveDocumentFromContextMenu()
2394 {
2395     IDocument *document = d->m_contextMenuEntry ? d->m_contextMenuEntry->document : nullptr;
2396     if (document)
2397         saveDocument(document);
2398 }
2399 
saveDocumentAsFromContextMenu()2400 void EditorManagerPrivate::saveDocumentAsFromContextMenu()
2401 {
2402     IDocument *document = d->m_contextMenuEntry ? d->m_contextMenuEntry->document : nullptr;
2403     if (document)
2404         saveDocumentAs(document);
2405 }
2406 
revertToSavedFromContextMenu()2407 void EditorManagerPrivate::revertToSavedFromContextMenu()
2408 {
2409     IDocument *document = d->m_contextMenuEntry ? d->m_contextMenuEntry->document : nullptr;
2410     if (document)
2411         revertToSaved(document);
2412 }
2413 
closeEditorFromContextMenu()2414 void EditorManagerPrivate::closeEditorFromContextMenu()
2415 {
2416     if (d->m_contextMenuEditor) {
2417         closeEditorOrDocument(d->m_contextMenuEditor);
2418     } else {
2419         IDocument *document = d->m_contextMenuEntry ? d->m_contextMenuEntry->document : nullptr;
2420         if (document)
2421             EditorManager::closeDocuments({document});
2422     }
2423 }
2424 
closeOtherDocumentsFromContextMenu()2425 void EditorManagerPrivate::closeOtherDocumentsFromContextMenu()
2426 {
2427     IDocument *document = d->m_contextMenuEntry ? d->m_contextMenuEntry->document : nullptr;
2428     EditorManager::closeOtherDocuments(document);
2429 }
2430 
saveDocument(IDocument * document)2431 bool EditorManagerPrivate::saveDocument(IDocument *document)
2432 {
2433     if (!document)
2434         return false;
2435 
2436     document->checkPermissions();
2437 
2438     const QString fileName = document->filePath().toString();
2439 
2440     if (fileName.isEmpty())
2441         return saveDocumentAs(document);
2442 
2443     bool success = false;
2444     bool isReadOnly;
2445 
2446     emit m_instance->aboutToSave(document);
2447     // try saving, no matter what isReadOnly tells us
2448     success = DocumentManager::saveDocument(document, FilePath(), &isReadOnly);
2449 
2450     if (!success && isReadOnly) {
2451         MakeWritableResult answer = makeFileWritable(document);
2452         if (answer == Failed)
2453             return false;
2454         if (answer == SavedAs)
2455             return true;
2456 
2457         document->checkPermissions();
2458 
2459         success = DocumentManager::saveDocument(document);
2460     }
2461 
2462     if (success) {
2463         addDocumentToRecentFiles(document);
2464         emit m_instance->saved(document);
2465     }
2466 
2467     return success;
2468 }
2469 
saveDocumentAs(IDocument * document)2470 bool EditorManagerPrivate::saveDocumentAs(IDocument *document)
2471 {
2472     if (!document)
2473         return false;
2474 
2475     const auto &absoluteFilePath = FilePath::fromString(DocumentManager::getSaveAsFileName(document));
2476 
2477     if (absoluteFilePath.isEmpty())
2478         return false;
2479 
2480     if (absoluteFilePath != document->filePath()) {
2481         // close existing editors for the new file name
2482         IDocument *otherDocument = DocumentModel::documentForFilePath(absoluteFilePath);
2483         if (otherDocument)
2484             EditorManager::closeDocuments({otherDocument}, false);
2485     }
2486 
2487     emit m_instance->aboutToSave(document);
2488     const bool success = DocumentManager::saveDocument(document, absoluteFilePath);
2489     document->checkPermissions();
2490 
2491     // TODO: There is an issue to be treated here. The new file might be of a different mime
2492     // type than the original and thus require a different editor. An alternative strategy
2493     // would be to close the current editor and open a new appropriate one, but this is not
2494     // a good way out either (also the undo stack would be lost). Perhaps the best is to
2495     // re-think part of the editors design.
2496 
2497     if (success) {
2498         addDocumentToRecentFiles(document);
2499         emit m_instance->saved(document);
2500     }
2501 
2502     updateActions();
2503     return success;
2504 }
2505 
closeAllEditorsExceptVisible()2506 void EditorManagerPrivate::closeAllEditorsExceptVisible()
2507 {
2508     DocumentModelPrivate::removeAllSuspendedEntries(DocumentModelPrivate::DoNotRemovePinnedFiles);
2509     QList<IDocument *> documentsToClose = DocumentModel::openedDocuments();
2510     // Remove all pinned files from the list of files to close.
2511     documentsToClose = Utils::filtered(documentsToClose, [](IDocument *document) {
2512         DocumentModel::Entry *entry = DocumentModel::entryForDocument(document);
2513         return !entry->pinned;
2514     });
2515     foreach (IEditor *editor, EditorManager::visibleEditors())
2516         documentsToClose.removeAll(editor->document());
2517     EditorManager::closeDocuments(documentsToClose, true);
2518 }
2519 
revertToSaved(IDocument * document)2520 void EditorManagerPrivate::revertToSaved(IDocument *document)
2521 {
2522     if (!document)
2523         return;
2524     const QString fileName =  document->filePath().toString();
2525     if (fileName.isEmpty())
2526         return;
2527     if (document->isModified()) {
2528         QMessageBox msgBox(QMessageBox::Question,
2529                            tr("Revert to Saved"),
2530                            tr("You will lose your current changes if you proceed reverting %1.")
2531                                .arg(QDir::toNativeSeparators(fileName)),
2532                            QMessageBox::Yes | QMessageBox::No,
2533                            ICore::dialogParent());
2534         msgBox.button(QMessageBox::Yes)->setText(tr("Proceed"));
2535         msgBox.button(QMessageBox::No)->setText(tr("Cancel"));
2536 
2537         QPushButton *diffButton = nullptr;
2538         auto diffService = DiffService::instance();
2539         if (diffService)
2540             diffButton = msgBox.addButton(tr("Cancel && &Diff"), QMessageBox::RejectRole);
2541 
2542         msgBox.setDefaultButton(QMessageBox::No);
2543         msgBox.setEscapeButton(QMessageBox::No);
2544         if (msgBox.exec() == QMessageBox::No)
2545             return;
2546 
2547         if (diffService && msgBox.clickedButton() == diffButton) {
2548             diffService->diffModifiedFiles(QStringList(fileName));
2549             return;
2550         }
2551     }
2552     QString errorString;
2553     if (!document->reload(&errorString, IDocument::FlagReload, IDocument::TypeContents))
2554         QMessageBox::critical(ICore::dialogParent(), tr("File Error"), errorString);
2555 }
2556 
autoSuspendDocuments()2557 void EditorManagerPrivate::autoSuspendDocuments()
2558 {
2559     if (!d->m_settings.autoSuspendEnabled)
2560         return;
2561 
2562     auto visibleDocuments = Utils::transform<QSet>(EditorManager::visibleEditors(),
2563                                                    &IEditor::document);
2564     int keptEditorCount = 0;
2565     QList<IDocument *> documentsToSuspend;
2566     foreach (const EditLocation &editLocation, d->m_globalHistory) {
2567         IDocument *document = editLocation.document;
2568         if (!document || !document->isSuspendAllowed() || document->isModified()
2569                 || document->isTemporary() || document->filePath().isEmpty()
2570                 || visibleDocuments.contains(document))
2571             continue;
2572         if (keptEditorCount >= d->m_settings.autoSuspendMinDocumentCount)
2573             documentsToSuspend.append(document);
2574         else
2575             ++keptEditorCount;
2576     }
2577     closeEditors(DocumentModel::editorsForDocuments(documentsToSuspend), CloseFlag::Suspend);
2578 }
2579 
openTerminal()2580 void EditorManagerPrivate::openTerminal()
2581 {
2582     if (!d->m_contextMenuEntry || d->m_contextMenuEntry->fileName().isEmpty())
2583         return;
2584     FileUtils::openTerminal(d->m_contextMenuEntry->fileName().parentDir().toString());
2585 }
2586 
findInDirectory()2587 void EditorManagerPrivate::findInDirectory()
2588 {
2589     if (!d->m_contextMenuEntry || d->m_contextMenuEntry->fileName().isEmpty())
2590         return;
2591     const FilePath path = d->m_contextMenuEntry->fileName();
2592     emit m_instance->findOnFileSystemRequest(
2593         (path.isDir() ? path : path.parentDir()).toString());
2594 }
2595 
togglePinned()2596 void EditorManagerPrivate::togglePinned()
2597 {
2598     if (!d->m_contextMenuEntry || d->m_contextMenuEntry->fileName().isEmpty())
2599         return;
2600 
2601     const bool currentlyPinned = d->m_contextMenuEntry->pinned;
2602     DocumentModelPrivate::setPinned(d->m_contextMenuEntry, !currentlyPinned);
2603 }
2604 
split(Qt::Orientation orientation)2605 void EditorManagerPrivate::split(Qt::Orientation orientation)
2606 {
2607     EditorView *view = currentEditorView();
2608 
2609     if (view)
2610         view->parentSplitterOrView()->split(orientation);
2611 
2612     updateActions();
2613 }
2614 
removeCurrentSplit()2615 void EditorManagerPrivate::removeCurrentSplit()
2616 {
2617     EditorView *viewToClose = currentEditorView();
2618 
2619     QTC_ASSERT(viewToClose, return);
2620     QTC_ASSERT(!qobject_cast<EditorArea *>(viewToClose->parentSplitterOrView()), return);
2621 
2622     closeView(viewToClose);
2623     updateActions();
2624 }
2625 
removeAllSplits()2626 void EditorManagerPrivate::removeAllSplits()
2627 {
2628     EditorView *view = currentEditorView();
2629     QTC_ASSERT(view, return);
2630     EditorArea *currentArea = findEditorArea(view);
2631     QTC_ASSERT(currentArea, return);
2632     currentArea->unsplitAll();
2633 }
2634 
setCurrentEditorFromContextChange()2635 void EditorManagerPrivate::setCurrentEditorFromContextChange()
2636 {
2637     if (!d->m_scheduledCurrentEditor)
2638         return;
2639     IEditor *newCurrent = d->m_scheduledCurrentEditor;
2640     d->m_scheduledCurrentEditor = nullptr;
2641     setCurrentEditor(newCurrent);
2642 }
2643 
currentEditorView()2644 EditorView *EditorManagerPrivate::currentEditorView()
2645 {
2646     EditorView *view = d->m_currentView;
2647     if (!view) {
2648         if (d->m_currentEditor) {
2649             view = EditorManagerPrivate::viewForEditor(d->m_currentEditor);
2650             QTC_ASSERT(view, view = d->m_editorAreas.first()->findFirstView());
2651         }
2652         QTC_CHECK(view);
2653         if (!view) { // should not happen, we should always have either currentview or currentdocument
2654             foreach (EditorArea *area, d->m_editorAreas) {
2655                 if (area->window()->isActiveWindow()) {
2656                     view = area->findFirstView();
2657                     break;
2658                 }
2659             }
2660             QTC_ASSERT(view, view = d->m_editorAreas.first()->findFirstView());
2661         }
2662     }
2663     return view;
2664 }
2665 
2666 /*!
2667     Returns the pointer to the instance. Only use for connecting to signals.
2668 */
instance()2669 EditorManager *EditorManager::instance()
2670 {
2671     return m_instance;
2672 }
2673 
2674 /*!
2675     \internal
2676 */
EditorManager(QObject * parent)2677 EditorManager::EditorManager(QObject *parent) :
2678     QObject(parent)
2679 {
2680     m_instance = this;
2681     d = new EditorManagerPrivate(this);
2682     d->init();
2683 }
2684 
2685 /*!
2686     \internal
2687 */
~EditorManager()2688 EditorManager::~EditorManager()
2689 {
2690     delete d;
2691     m_instance = nullptr;
2692 }
2693 
2694 /*!
2695     Returns the document of the currently active editor.
2696 
2697     \sa currentEditor()
2698 */
currentDocument()2699 IDocument *EditorManager::currentDocument()
2700 {
2701     return d->m_currentEditor ? d->m_currentEditor->document() : nullptr;
2702 }
2703 
2704 /*!
2705     Returns the currently active editor.
2706 
2707     \sa currentDocument()
2708 */
currentEditor()2709 IEditor *EditorManager::currentEditor()
2710 {
2711     return d->m_currentEditor;
2712 }
2713 
2714 /*!
2715     Closes all open editors. If \a askAboutModifiedEditors is \c true, prompts
2716     users to save their changes before closing the editors.
2717 
2718     Returns whether all editors were closed.
2719 */
closeAllEditors(bool askAboutModifiedEditors)2720 bool EditorManager::closeAllEditors(bool askAboutModifiedEditors)
2721 {
2722     DocumentModelPrivate::removeAllSuspendedEntries();
2723     return closeDocuments(DocumentModel::openedDocuments(), askAboutModifiedEditors);
2724 }
2725 
2726 /*!
2727     Closes all open documents except \a document and pinned files.
2728 */
closeOtherDocuments(IDocument * document)2729 void EditorManager::closeOtherDocuments(IDocument *document)
2730 {
2731     DocumentModelPrivate::removeAllSuspendedEntries(DocumentModelPrivate::DoNotRemovePinnedFiles);
2732     QList<IDocument *> documentsToClose = DocumentModel::openedDocuments();
2733     // Remove all pinned files from the list of files to close.
2734     documentsToClose = Utils::filtered(documentsToClose, [](IDocument *document) {
2735         DocumentModel::Entry *entry = DocumentModel::entryForDocument(document);
2736         return !entry->pinned;
2737     });
2738     documentsToClose.removeAll(document);
2739     closeDocuments(documentsToClose, true);
2740 }
2741 
2742 /*!
2743     Closes all open documents except pinned files.
2744 
2745     Returns whether all editors were closed.
2746 */
closeAllDocuments()2747 bool EditorManager::closeAllDocuments()
2748 {
2749     // Only close the files that aren't pinned.
2750     const QList<DocumentModel::Entry *> entriesToClose
2751             = Utils::filtered(DocumentModel::entries(), Utils::equal(&DocumentModel::Entry::pinned, false));
2752     return EditorManager::closeDocuments(entriesToClose);
2753 }
2754 
2755 /*!
2756     \internal
2757 */
slotCloseCurrentEditorOrDocument()2758 void EditorManager::slotCloseCurrentEditorOrDocument()
2759 {
2760     if (!d->m_currentEditor)
2761         return;
2762     addCurrentPositionToNavigationHistory();
2763     d->closeEditorOrDocument(d->m_currentEditor);
2764 }
2765 
2766 /*!
2767     Closes all open documents except the current document.
2768 */
closeOtherDocuments()2769 void EditorManager::closeOtherDocuments()
2770 {
2771     closeOtherDocuments(currentDocument());
2772 }
2773 
assignAction(QAction * self,QAction * other)2774 static void assignAction(QAction *self, QAction *other)
2775 {
2776     self->setText(other->text());
2777     self->setIcon(other->icon());
2778     self->setShortcut(other->shortcut());
2779     self->setEnabled(other->isEnabled());
2780     self->setIconVisibleInMenu(other->isIconVisibleInMenu());
2781 }
2782 
2783 /*!
2784     Adds save, close and other editor context menu items for the document
2785     \a entry and editor \a editor to the context menu \a contextMenu.
2786 */
addSaveAndCloseEditorActions(QMenu * contextMenu,DocumentModel::Entry * entry,IEditor * editor)2787 void EditorManager::addSaveAndCloseEditorActions(QMenu *contextMenu, DocumentModel::Entry *entry,
2788                                                  IEditor *editor)
2789 {
2790     QTC_ASSERT(contextMenu, return);
2791     d->m_contextMenuEntry = entry;
2792     d->m_contextMenuEditor = editor;
2793 
2794     const FilePath filePath = entry ? entry->fileName() : FilePath();
2795     const bool copyActionsEnabled = !filePath.isEmpty();
2796     d->m_copyFilePathContextAction->setEnabled(copyActionsEnabled);
2797     d->m_copyLocationContextAction->setEnabled(copyActionsEnabled);
2798     d->m_copyFileNameContextAction->setEnabled(copyActionsEnabled);
2799     contextMenu->addAction(d->m_copyFilePathContextAction);
2800     if (editor && entry) {
2801         if (const int lineNumber = editor->currentLine()) {
2802             d->m_copyLocationContextAction->setData(QVariant(lineNumber));
2803             contextMenu->addAction(d->m_copyLocationContextAction);
2804         }
2805     }
2806     contextMenu->addAction(d->m_copyFileNameContextAction);
2807     contextMenu->addSeparator();
2808 
2809     assignAction(d->m_saveCurrentEditorContextAction, ActionManager::command(Constants::SAVE)->action());
2810     assignAction(d->m_saveAsCurrentEditorContextAction, ActionManager::command(Constants::SAVEAS)->action());
2811     assignAction(d->m_revertToSavedCurrentEditorContextAction, ActionManager::command(Constants::REVERTTOSAVED)->action());
2812 
2813     IDocument *document = entry ? entry->document : nullptr;
2814 
2815     EditorManagerPrivate::setupSaveActions(document,
2816                                            d->m_saveCurrentEditorContextAction,
2817                                            d->m_saveAsCurrentEditorContextAction,
2818                                            d->m_revertToSavedCurrentEditorContextAction);
2819 
2820     contextMenu->addAction(d->m_saveCurrentEditorContextAction);
2821     contextMenu->addAction(d->m_saveAsCurrentEditorContextAction);
2822     contextMenu->addAction(ActionManager::command(Constants::SAVEALL)->action());
2823     contextMenu->addAction(d->m_revertToSavedCurrentEditorContextAction);
2824 
2825     contextMenu->addSeparator();
2826 
2827     const QString quotedDisplayName = entry ? Utils::quoteAmpersands(entry->displayName()) : QString();
2828     d->m_closeCurrentEditorContextAction->setText(entry
2829                                                     ? tr("Close \"%1\"").arg(quotedDisplayName)
2830                                                     : tr("Close Editor"));
2831     d->m_closeOtherDocumentsContextAction->setText(entry
2832                                                    ? tr("Close All Except \"%1\"").arg(quotedDisplayName)
2833                                                    : tr("Close Other Editors"));
2834     d->m_closeCurrentEditorContextAction->setEnabled(entry != nullptr);
2835     d->m_closeOtherDocumentsContextAction->setEnabled(entry != nullptr);
2836     d->m_closeAllEditorsContextAction->setEnabled(!DocumentModel::entries().isEmpty());
2837     d->m_closeAllEditorsExceptVisibleContextAction->setEnabled(
2838                 EditorManagerPrivate::visibleDocumentsCount() < DocumentModel::entries().count());
2839     contextMenu->addAction(d->m_closeCurrentEditorContextAction);
2840     contextMenu->addAction(d->m_closeAllEditorsContextAction);
2841     contextMenu->addAction(d->m_closeOtherDocumentsContextAction);
2842     contextMenu->addAction(d->m_closeAllEditorsExceptVisibleContextAction);
2843 }
2844 
2845 /*!
2846     Adds the pin editor menu items for the document \a entry to the context menu
2847     \a contextMenu.
2848 */
addPinEditorActions(QMenu * contextMenu,DocumentModel::Entry * entry)2849 void EditorManager::addPinEditorActions(QMenu *contextMenu, DocumentModel::Entry *entry)
2850 {
2851     const QString quotedDisplayName = entry ? Utils::quoteAmpersands(entry->displayName()) : QString();
2852     if (entry) {
2853         d->m_pinAction->setText(entry->pinned
2854                                 ? tr("Unpin \"%1\"").arg(quotedDisplayName)
2855                                 : tr("Pin \"%1\"").arg(quotedDisplayName));
2856     } else {
2857         d->m_pinAction->setText(tr("Pin Editor"));
2858     }
2859     d->m_pinAction->setEnabled(entry != nullptr);
2860     contextMenu->addAction(d->m_pinAction);
2861 }
2862 
2863 /*!
2864     Adds the native directory handling and open with menu items for the document
2865     \a entry to the context menu \a contextMenu.
2866 */
addNativeDirAndOpenWithActions(QMenu * contextMenu,DocumentModel::Entry * entry)2867 void EditorManager::addNativeDirAndOpenWithActions(QMenu *contextMenu, DocumentModel::Entry *entry)
2868 {
2869     QTC_ASSERT(contextMenu, return);
2870     d->m_contextMenuEntry = entry;
2871     bool enabled = entry && !entry->fileName().isEmpty();
2872     d->m_openGraphicalShellContextAction->setEnabled(enabled);
2873     d->m_openTerminalAction->setEnabled(enabled);
2874     d->m_findInDirectoryAction->setEnabled(enabled);
2875     d->m_filePropertiesAction->setEnabled(enabled);
2876     contextMenu->addAction(d->m_openGraphicalShellContextAction);
2877     contextMenu->addAction(d->m_openTerminalAction);
2878     contextMenu->addAction(d->m_findInDirectoryAction);
2879     contextMenu->addAction(d->m_filePropertiesAction);
2880     QMenu *openWith = contextMenu->addMenu(tr("Open With"));
2881     openWith->setEnabled(enabled);
2882     if (enabled)
2883         populateOpenWithMenu(openWith, entry->fileName().toString());
2884 }
2885 
2886 /*!
2887     Populates the \uicontrol {Open With} menu \a menu with editors that are
2888     suitable for opening the document \a fileName.
2889 */
populateOpenWithMenu(QMenu * menu,const QString & fileName)2890 void EditorManager::populateOpenWithMenu(QMenu *menu, const QString &fileName)
2891 {
2892     using EditorFactoryList = QList<IEditorFactory*>;
2893     using ExternalEditorList = QList<IExternalEditor*>;
2894 
2895     menu->clear();
2896 
2897     const EditorFactoryList factories = IEditorFactory::preferredEditorFactories(fileName);
2898     const Utils::MimeType mt = Utils::mimeTypeForFile(fileName);
2899     const ExternalEditorList extEditors = IExternalEditor::externalEditors(mt);
2900     const bool anyMatches = !factories.empty() || !extEditors.empty();
2901     if (anyMatches) {
2902         // Add all suitable editors
2903         foreach (IEditorFactory *editorFactory, factories) {
2904             Utils::Id editorId = editorFactory->id();
2905             // Add action to open with this very editor factory
2906             QString const actionTitle = editorFactory->displayName();
2907             QAction *action = menu->addAction(actionTitle);
2908             // Below we need QueuedConnection because otherwise, if a qrc file
2909             // is inside of a qrc file itself, and the qrc editor opens the Open with menu,
2910             // crashes happen, because the editor instance is deleted by openEditorWith
2911             // while the menu is still being processed.
2912             connect(action, &QAction::triggered, d,
2913                     [fileName, editorId]() {
2914                         EditorManagerPrivate::openEditorWith(FilePath::fromString(fileName), editorId);
2915                     }, Qt::QueuedConnection);
2916         }
2917         // Add all suitable external editors
2918         foreach (IExternalEditor *externalEditor, extEditors) {
2919             QAction *action = menu->addAction(externalEditor->displayName());
2920             Utils::Id editorId = externalEditor->id();
2921             connect(action, &QAction::triggered, [fileName, editorId]() {
2922                 EditorManager::openExternalEditor(FilePath::fromString(fileName), editorId);
2923             });
2924         }
2925     }
2926     menu->setEnabled(anyMatches);
2927 }
2928 
2929 /*!
2930     Returns reload behavior settings.
2931 */
reloadSetting()2932 IDocument::ReloadSetting EditorManager::reloadSetting()
2933 {
2934     return d->m_settings.reloadSetting;
2935 }
2936 
2937 /*!
2938     \internal
2939 
2940     Sets editor reaload behavior settings to \a behavior.
2941 */
setReloadSetting(IDocument::ReloadSetting behavior)2942 void EditorManager::setReloadSetting(IDocument::ReloadSetting behavior)
2943 {
2944     d->m_settings.reloadSetting = behavior;
2945 }
2946 
2947 /*!
2948     Saves the current document.
2949 */
saveDocument()2950 void EditorManager::saveDocument()
2951 {
2952     EditorManagerPrivate::saveDocument(currentDocument());
2953 }
2954 
2955 /*!
2956     Saves the current document under a different file name.
2957 */
saveDocumentAs()2958 void EditorManager::saveDocumentAs()
2959 {
2960     EditorManagerPrivate::saveDocumentAs(currentDocument());
2961 }
2962 
2963 /*!
2964     Reverts the current document to its last saved state.
2965 */
revertToSaved()2966 void EditorManager::revertToSaved()
2967 {
2968     EditorManagerPrivate::revertToSaved(currentDocument());
2969 }
2970 
2971 /*!
2972     Closes the documents specified by \a entries.
2973 
2974     Returns whether all documents were closed.
2975 */
closeDocuments(const QList<DocumentModel::Entry * > & entries)2976 bool EditorManager::closeDocuments(const QList<DocumentModel::Entry *> &entries)
2977 {
2978     QList<IDocument *> documentsToClose;
2979     for (DocumentModel::Entry *entry : entries) {
2980         if (!entry)
2981             continue;
2982         if (entry->isSuspended)
2983             DocumentModelPrivate::removeEntry(entry);
2984         else
2985             documentsToClose << entry->document;
2986     }
2987     return closeDocuments(documentsToClose);
2988 }
2989 
2990 /*!
2991     Closes the editors specified by \a editorsToClose. If
2992     \a askAboutModifiedEditors is \c true, prompts users
2993     to save their changes before closing the editor.
2994 
2995     Returns whether all editors were closed.
2996 
2997     Usually closeDocuments() is the better alternative.
2998 
2999     \sa closeDocuments()
3000 */
closeEditors(const QList<IEditor * > & editorsToClose,bool askAboutModifiedEditors)3001 bool EditorManager::closeEditors(const QList<IEditor*> &editorsToClose, bool askAboutModifiedEditors)
3002 {
3003     return EditorManagerPrivate::closeEditors(editorsToClose,
3004                                               askAboutModifiedEditors ? EditorManagerPrivate::CloseFlag::CloseWithAsking
3005                                                                       : EditorManagerPrivate::CloseFlag::CloseWithoutAsking);
3006 }
3007 
3008 /*!
3009     Activates an editor for the document specified by \a entry in the active
3010     split using the specified \a flags.
3011 */
activateEditorForEntry(DocumentModel::Entry * entry,OpenEditorFlags flags)3012 void EditorManager::activateEditorForEntry(DocumentModel::Entry *entry, OpenEditorFlags flags)
3013 {
3014     EditorManagerPrivate::activateEditorForEntry(EditorManagerPrivate::currentEditorView(),
3015                                                  entry, flags);
3016 }
3017 
3018 /*!
3019     Activates the \a editor in the active split using the specified \a flags.
3020 
3021     \sa currentEditor()
3022 */
activateEditor(IEditor * editor,OpenEditorFlags flags)3023 void EditorManager::activateEditor(IEditor *editor, OpenEditorFlags flags)
3024 {
3025     QTC_ASSERT(editor, return);
3026     EditorView *view = EditorManagerPrivate::viewForEditor(editor);
3027     // an IEditor doesn't have to belong to a view, it might be kept in storage by the editor model
3028     if (!view)
3029         view = EditorManagerPrivate::currentEditorView();
3030     EditorManagerPrivate::activateEditor(view, editor, flags);
3031 }
3032 
3033 /*!
3034     Activates an editor for the \a document in the active split using the
3035     specified \a flags.
3036 */
activateEditorForDocument(IDocument * document,OpenEditorFlags flags)3037 IEditor *EditorManager::activateEditorForDocument(IDocument *document, OpenEditorFlags flags)
3038 {
3039     return EditorManagerPrivate::activateEditorForDocument(EditorManagerPrivate::currentEditorView(), document, flags);
3040 }
3041 
3042 /*!
3043     Opens the document specified by \a filePath using the editor type \a
3044     editorId and the specified \a flags.
3045 
3046     If \a editorId is \c Id(), the editor type is derived from the file's MIME
3047     type.
3048 
3049     If \a newEditor is not \c nullptr, and a new editor instance was created,
3050     it is set to \c true. If an existing editor instance was used, it is set
3051     to \c false.
3052 
3053     \sa openEditorAt()
3054     \sa openEditorWithContents()
3055     \sa openExternalEditor()
3056 */
openEditor(const FilePath & filePath,Id editorId,OpenEditorFlags flags,bool * newEditor)3057 IEditor *EditorManager::openEditor(const FilePath &filePath, Id editorId,
3058                                    OpenEditorFlags flags, bool *newEditor)
3059 {
3060     if (flags & EditorManager::OpenInOtherSplit)
3061         EditorManager::gotoOtherSplit();
3062 
3063     return EditorManagerPrivate::openEditor(EditorManagerPrivate::currentEditorView(),
3064                                             filePath, editorId, flags, newEditor);
3065 }
3066 
openEditor(const QString & fileName,Id editorId,OpenEditorFlags flags,bool * newEditor)3067 IEditor *EditorManager::openEditor(const QString &fileName, Id editorId,
3068                                    OpenEditorFlags flags, bool *newEditor)
3069 {
3070     QFileInfo fi(fileName);
3071     return openEditor(FilePath::fromString(fileName), editorId, flags, newEditor);
3072 }
3073 
3074 /*!
3075     Opens the document specified by \a filePath using the editor type \a
3076     editorId and the specified \a flags.
3077 
3078     Moves the text cursor to the \a line and \a column.
3079 
3080     If \a editorId is \c Id(), the editor type is derived from the file's MIME
3081     type.
3082 
3083     If \a newEditor is not \c nullptr, and a new editor instance was created,
3084     it is set to \c true. If an existing editor instance was used, it is set
3085     to \c false.
3086 
3087     \sa openEditor()
3088     \sa openEditorAtSearchResult()
3089     \sa openEditorWithContents()
3090     \sa openExternalEditor()
3091     \sa IEditor::gotoLine()
3092 */
openEditorAt(const Link & link,Id editorId,OpenEditorFlags flags,bool * newEditor)3093 IEditor *EditorManager::openEditorAt(const Link &link,
3094                                      Id editorId,
3095                                      OpenEditorFlags flags,
3096                                      bool *newEditor)
3097 {
3098     if (flags & EditorManager::OpenInOtherSplit)
3099         EditorManager::gotoOtherSplit();
3100 
3101     return EditorManagerPrivate::openEditorAt(EditorManagerPrivate::currentEditorView(),
3102                                               link,
3103                                               editorId,
3104                                               flags,
3105                                               newEditor);
3106 }
3107 
openEditorAt(const QString & fileName,int line,int column,Id editorId,OpenEditorFlags flags,bool * newEditor)3108 IEditor *EditorManager::openEditorAt(const QString &fileName, int line, int column,
3109                                      Id editorId, OpenEditorFlags flags, bool *newEditor)
3110 {
3111     return openEditorAt(Link(FilePath::fromString(fileName), line, column), editorId, flags, newEditor);
3112 }
3113 
3114 /*!
3115     Opens the document at the position of the search result \a item using the
3116     editor type \a editorId and the specified \a flags.
3117 
3118     If \a editorId is \c Id(), the editor type is derived from the file's MIME
3119     type.
3120 
3121     If \a newEditor is not \c nullptr, and a new editor instance was created,
3122     it is set to \c true. If an existing editor instance was used, it is set to
3123     \c false.
3124 
3125     \sa openEditorAt()
3126 */
openEditorAtSearchResult(const SearchResultItem & item,Id editorId,OpenEditorFlags flags,bool * newEditor)3127 void EditorManager::openEditorAtSearchResult(const SearchResultItem &item,
3128                                              Id editorId,
3129                                              OpenEditorFlags flags,
3130                                              bool *newEditor)
3131 {
3132     if (item.path().empty()) {
3133         openEditor(QDir::fromNativeSeparators(item.lineText()), editorId, flags, newEditor);
3134         return;
3135     }
3136 
3137     openEditorAt(QDir::fromNativeSeparators(item.path().first()),
3138                  item.mainRange().begin.line,
3139                  item.mainRange().begin.column,
3140                  editorId,
3141                  flags,
3142                  newEditor);
3143 }
3144 
3145 /*!
3146     Returns whether \a fileName is an auto-save file created by \QC.
3147 */
isAutoSaveFile(const QString & filePath)3148 bool EditorManager::isAutoSaveFile(const QString &filePath)
3149 {
3150     return filePath.endsWith(".autosave");
3151 }
3152 
3153 /*!
3154     Opens the document specified by \a filePath in the external editor specified
3155     by \a editorId.
3156 
3157     Returns \c false and displays an error message if \a editorId is not the ID
3158     of an external editor or the external editor cannot be opened.
3159 
3160     \sa openEditor()
3161 */
openExternalEditor(const FilePath & filePath,Id editorId)3162 bool EditorManager::openExternalEditor(const FilePath &filePath, Id editorId)
3163 {
3164     IExternalEditor *ee = Utils::findOrDefault(IExternalEditor::allExternalEditors(),
3165                                                Utils::equal(&IExternalEditor::id, editorId));
3166     if (!ee)
3167         return false;
3168     QString errorMessage;
3169     QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
3170     const bool ok = ee->startEditor(filePath, &errorMessage);
3171     QApplication::restoreOverrideCursor();
3172     if (!ok)
3173         QMessageBox::critical(ICore::dialogParent(), tr("Opening File"), errorMessage);
3174     return ok;
3175 }
3176 
3177 /*!
3178     Adds \a listener to the hooks that are asked if editors may be closed.
3179 
3180     When an editor requests to close, all listeners are called. If one of the
3181     calls returns \c false, the process is aborted and the event is ignored. If
3182     all calls return \c true, editorAboutToClose() is emitted and the event is
3183     accepted.
3184 */
addCloseEditorListener(const std::function<bool (IEditor *)> & listener)3185 void EditorManager::addCloseEditorListener(const std::function<bool (IEditor *)> &listener)
3186 {
3187     d->m_closeEditorListeners.append(listener);
3188 }
3189 
3190 /*!
3191     Asks the user for a list of files to open and returns the choice.
3192 
3193     \sa DocumentManager::getOpenFileNames()
3194 */
getOpenFileNames()3195 QStringList EditorManager::getOpenFileNames()
3196 {
3197     QString selectedFilter;
3198     const QString &fileFilters = DocumentManager::allDocumentFactoryFiltersString(&selectedFilter);
3199     return DocumentManager::getOpenFileNames(fileFilters, QString(), &selectedFilter);
3200 }
3201 
makeTitleUnique(QString * titlePattern)3202 static QString makeTitleUnique(QString *titlePattern)
3203 {
3204     QString title;
3205     if (titlePattern) {
3206         const QChar dollar = QLatin1Char('$');
3207 
3208         QString base = *titlePattern;
3209         if (base.isEmpty())
3210             base = "unnamed$";
3211         if (base.contains(dollar)) {
3212             int i = 1;
3213             QSet<QString> docnames;
3214             foreach (DocumentModel::Entry *entry, DocumentModel::entries()) {
3215                 QString name = entry->fileName().toString();
3216                 if (name.isEmpty())
3217                     name = entry->displayName();
3218                 else
3219                     name = QFileInfo(name).completeBaseName();
3220                 docnames << name;
3221             }
3222 
3223             do {
3224                 title = base;
3225                 title.replace(QString(dollar), QString::number(i++));
3226             } while (docnames.contains(title));
3227         } else {
3228             title = *titlePattern;
3229         }
3230         *titlePattern = title;
3231     }
3232     return title;
3233 }
3234 
3235 /*!
3236     Opens \a contents in an editor of the type \a editorId using the specified
3237     \a flags.
3238 
3239     The editor is given a display name based on \a titlePattern. If a non-empty
3240     \a uniqueId is specified and an editor with that unique ID is found, it is
3241     re-used. Otherwise, a new editor with that unique ID is created.
3242 
3243     Returns the new or re-used editor.
3244 
3245     \sa clearUniqueId()
3246 */
openEditorWithContents(Id editorId,QString * titlePattern,const QByteArray & contents,const QString & uniqueId,OpenEditorFlags flags)3247 IEditor *EditorManager::openEditorWithContents(Id editorId,
3248                                         QString *titlePattern,
3249                                         const QByteArray &contents,
3250                                         const QString &uniqueId,
3251                                         OpenEditorFlags flags)
3252 {
3253     if (debugEditorManager)
3254         qDebug() << Q_FUNC_INFO << editorId.name() << titlePattern << uniqueId << contents;
3255 
3256     if (flags & EditorManager::OpenInOtherSplit)
3257             EditorManager::gotoOtherSplit();
3258 
3259     QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
3260     Utils::ExecuteOnDestruction appRestoreCursor(&QApplication::restoreOverrideCursor);
3261     Q_UNUSED(appRestoreCursor)
3262 
3263 
3264     const QString title = makeTitleUnique(titlePattern);
3265 
3266     IEditor *edt = nullptr;
3267     if (!uniqueId.isEmpty()) {
3268         foreach (IDocument *document, DocumentModel::openedDocuments())
3269             if (document->property(scratchBufferKey).toString() == uniqueId) {
3270                 edt = DocumentModel::editorsForDocument(document).constFirst();
3271 
3272                 document->setContents(contents);
3273                 if (!title.isEmpty())
3274                     edt->document()->setPreferredDisplayName(title);
3275 
3276                 activateEditor(edt, flags);
3277                 return edt;
3278             }
3279     }
3280 
3281     EditorFactoryList factories = EditorManagerPrivate::findFactories(editorId, title);
3282     if (factories.isEmpty())
3283         return nullptr;
3284 
3285     edt = EditorManagerPrivate::createEditor(factories.first(), title);
3286     if (!edt)
3287         return nullptr;
3288     if (!edt->document()->setContents(contents)) {
3289         delete edt;
3290         edt = nullptr;
3291         return nullptr;
3292     }
3293 
3294     if (!uniqueId.isEmpty())
3295         edt->document()->setProperty(scratchBufferKey, uniqueId);
3296 
3297     if (!title.isEmpty())
3298         edt->document()->setPreferredDisplayName(title);
3299 
3300     EditorManagerPrivate::addEditor(edt);
3301     activateEditor(edt, flags);
3302     return edt;
3303 }
3304 
3305 /*!
3306     Returns whether the document specified by \a filePath should be opened even
3307     though it is big. Depending on the settings this might ask the user to
3308     decide whether the file should be opened.
3309 */
skipOpeningBigTextFile(const FilePath & filePath)3310 bool EditorManager::skipOpeningBigTextFile(const FilePath &filePath)
3311 {
3312     return EditorManagerPrivate::skipOpeningBigTextFile(filePath);
3313 }
3314 
3315 /*!
3316     Clears the unique ID of \a document.
3317 
3318     \sa openEditorWithContents()
3319 */
clearUniqueId(IDocument * document)3320 void EditorManager::clearUniqueId(IDocument *document)
3321 {
3322     document->setProperty(scratchBufferKey, QVariant());
3323 }
3324 
3325 /*!
3326     Saves the changes in \a document.
3327 
3328     Returns whether the operation was successful.
3329 */
saveDocument(IDocument * document)3330 bool EditorManager::saveDocument(IDocument *document)
3331 {
3332     return EditorManagerPrivate::saveDocument(document);
3333 }
3334 
3335 /*!
3336     \internal
3337 */
hasSplitter()3338 bool EditorManager::hasSplitter()
3339 {
3340     EditorView *view = EditorManagerPrivate::currentEditorView();
3341     QTC_ASSERT(view, return false);
3342     EditorArea *area = EditorManagerPrivate::findEditorArea(view);
3343     QTC_ASSERT(area, return false);
3344     return area->isSplitter();
3345 }
3346 
3347 /*!
3348     Returns the list of visible editors.
3349 */
visibleEditors()3350 QList<IEditor*> EditorManager::visibleEditors()
3351 {
3352     QList<IEditor *> editors;
3353     foreach (EditorArea *area, d->m_editorAreas) {
3354         if (area->isSplitter()) {
3355             EditorView *firstView = area->findFirstView();
3356             EditorView *view = firstView;
3357             if (view) {
3358                 do {
3359                     if (view->currentEditor())
3360                         editors.append(view->currentEditor());
3361                     view = view->findNextView();
3362                     QTC_ASSERT(view != firstView, break); // we start with firstView and shouldn't have cycles
3363                 } while (view);
3364             }
3365         } else {
3366             if (area->editor())
3367                 editors.append(area->editor());
3368         }
3369     }
3370     return editors;
3371 }
3372 
3373 /*!
3374     Closes \a documents. If \a askAboutModifiedEditors is \c true, prompts
3375     users to save their changes before closing the documents.
3376 
3377     Returns whether the documents were closed.
3378 */
closeDocuments(const QList<IDocument * > & documents,bool askAboutModifiedEditors)3379 bool EditorManager::closeDocuments(const QList<IDocument *> &documents, bool askAboutModifiedEditors)
3380 {
3381     return m_instance->closeEditors(DocumentModel::editorsForDocuments(documents), askAboutModifiedEditors);
3382 }
3383 
3384 /*!
3385     Adds the current cursor position specified by \a saveState to the
3386     navigation history. If \a saveState is \l{QByteArray::isNull()}{null} (the
3387     default), the current state of the active editor is used. Otherwise \a
3388     saveState must be a valid state of the active editor.
3389 
3390     \sa IEditor::saveState()
3391 */
addCurrentPositionToNavigationHistory(const QByteArray & saveState)3392 void EditorManager::addCurrentPositionToNavigationHistory(const QByteArray &saveState)
3393 {
3394     EditorManagerPrivate::currentEditorView()->addCurrentPositionToNavigationHistory(saveState);
3395     EditorManagerPrivate::updateActions();
3396 }
3397 
3398 /*!
3399     Sets the location that was last modified to \a editor.
3400     Used for \uicontrol{Window} > \uicontrol{Go to Last Edit}.
3401 */
setLastEditLocation(const IEditor * editor)3402 void EditorManager::setLastEditLocation(const IEditor* editor)
3403 {
3404     IDocument *document = editor->document();
3405     if (!document)
3406         return;
3407 
3408     const QByteArray &state = editor->saveState();
3409     EditLocation location;
3410     location.document = document;
3411     location.filePath = document->filePath();
3412     location.id = document->id();
3413     location.state = QVariant(state);
3414 
3415     d->m_globalLastEditLocation = location;
3416 }
3417 
3418 /*!
3419     Cuts the forward part of the navigation history, so the user cannot
3420     \uicontrol{Go Forward} anymore (until the user goes backward again).
3421 
3422     \sa goForwardInNavigationHistory()
3423     \sa addCurrentPositionToNavigationHistory()
3424 */
cutForwardNavigationHistory()3425 void EditorManager::cutForwardNavigationHistory()
3426 {
3427     EditorManagerPrivate::currentEditorView()->cutForwardNavigationHistory();
3428     EditorManagerPrivate::updateActions();
3429 }
3430 
3431 /*!
3432     Goes back in the navigation history.
3433 
3434     \sa goForwardInNavigationHistory()
3435     \sa addCurrentPositionToNavigationHistory()
3436 */
goBackInNavigationHistory()3437 void EditorManager::goBackInNavigationHistory()
3438 {
3439     EditorManagerPrivate::currentEditorView()->goBackInNavigationHistory();
3440     EditorManagerPrivate::updateActions();
3441     return;
3442 }
3443 
3444 /*!
3445     Goes forward in the navigation history.
3446 
3447     \sa goBackInNavigationHistory()
3448     \sa addCurrentPositionToNavigationHistory()
3449 */
goForwardInNavigationHistory()3450 void EditorManager::goForwardInNavigationHistory()
3451 {
3452     EditorManagerPrivate::currentEditorView()->goForwardInNavigationHistory();
3453     EditorManagerPrivate::updateActions();
3454 }
3455 
windowForEditorArea(EditorArea * area)3456 EditorWindow *windowForEditorArea(EditorArea *area)
3457 {
3458     return qobject_cast<EditorWindow *>(area->window());
3459 }
3460 
editorWindows(const QList<EditorArea * > & areas)3461 QVector<EditorWindow *> editorWindows(const QList<EditorArea *> &areas)
3462 {
3463     QVector<EditorWindow *> result;
3464     for (EditorArea *area : areas)
3465         if (EditorWindow *window = windowForEditorArea(area))
3466             result.append(window);
3467     return result;
3468 }
3469 
3470 /*!
3471     \internal
3472 
3473     Returns the serialized state of all non-temporary editors, the split layout
3474     and external editor windows.
3475 
3476     \sa restoreState()
3477 */
saveState()3478 QByteArray EditorManager::saveState()
3479 {
3480     QByteArray bytes;
3481     QDataStream stream(&bytes, QIODevice::WriteOnly);
3482 
3483     stream << QByteArray("EditorManagerV5");
3484 
3485     // TODO: In case of split views it's not possible to restore these for all correctly with this
3486     QList<IDocument *> documents = DocumentModel::openedDocuments();
3487     foreach (IDocument *document, documents) {
3488         if (!document->filePath().isEmpty() && !document->isTemporary()) {
3489             IEditor *editor = DocumentModel::editorsForDocument(document).constFirst();
3490             QByteArray state = editor->saveState();
3491             if (!state.isEmpty())
3492                 d->m_editorStates.insert(document->filePath().toString(), QVariant(state));
3493         }
3494     }
3495 
3496     stream << d->m_editorStates;
3497 
3498     QList<DocumentModel::Entry *> entries = DocumentModel::entries();
3499     int entriesCount = 0;
3500     foreach (DocumentModel::Entry *entry, entries) {
3501         // The editor may be 0 if it was not loaded yet: In that case it is not temporary
3502         if (!entry->document->isTemporary())
3503             ++entriesCount;
3504     }
3505 
3506     stream << entriesCount;
3507 
3508     foreach (DocumentModel::Entry *entry, entries) {
3509         if (!entry->document->isTemporary()) {
3510             stream << entry->fileName().toString() << entry->plainDisplayName() << entry->id()
3511                    << entry->pinned;
3512         }
3513     }
3514 
3515     stream << d->m_editorAreas.first()->saveState(); // TODO
3516 
3517     // windows
3518     const QVector<EditorWindow *> windows = editorWindows(d->m_editorAreas);
3519     const QVector<QVariantHash> windowStates = Utils::transform(windows, &EditorWindow::saveState);
3520     stream << windowStates;
3521     return bytes;
3522 }
3523 
3524 /*!
3525     \internal
3526 
3527     Restores the \a state of the split layout, editor windows and editors.
3528 
3529     Returns \c true if the state can be restored.
3530 
3531     \sa saveState()
3532 */
restoreState(const QByteArray & state)3533 bool EditorManager::restoreState(const QByteArray &state)
3534 {
3535     closeAllEditors(true);
3536     // remove extra windows
3537     for (int i = d->m_editorAreas.count() - 1; i > 0 /* keep first alive */; --i)
3538         delete d->m_editorAreas.at(i); // automatically removes it from list
3539     if (d->m_editorAreas.first()->isSplitter())
3540         EditorManagerPrivate::removeAllSplits();
3541     QDataStream stream(state);
3542 
3543     QByteArray version;
3544     stream >> version;
3545 
3546     const bool isVersion5 = version == "EditorManagerV5";
3547     if (version != "EditorManagerV4" && !isVersion5)
3548         return false;
3549 
3550     QApplication::setOverrideCursor(Qt::WaitCursor);
3551 
3552     stream >> d->m_editorStates;
3553 
3554     int editorCount = 0;
3555     stream >> editorCount;
3556     while (--editorCount >= 0) {
3557         QString fileName;
3558         stream >> fileName;
3559         QString displayName;
3560         stream >> displayName;
3561         Id id;
3562         stream >> id;
3563         bool pinned = false;
3564         if (isVersion5)
3565             stream >> pinned;
3566 
3567         if (!fileName.isEmpty() && !displayName.isEmpty()) {
3568             QFileInfo fi(fileName);
3569             if (!fi.exists())
3570                 continue;
3571             QFileInfo rfi(autoSaveName(fileName));
3572             if (rfi.exists() && fi.lastModified() < rfi.lastModified()) {
3573                 if (IEditor *editor = openEditor(fileName, id, DoNotMakeVisible))
3574                     DocumentModelPrivate::setPinned(DocumentModel::entryForDocument(editor->document()), pinned);
3575             } else {
3576                  if (DocumentModel::Entry *entry = DocumentModelPrivate::addSuspendedDocument(fileName, displayName, id))
3577                      DocumentModelPrivate::setPinned(entry, pinned);
3578             }
3579         }
3580     }
3581 
3582     QByteArray splitterstates;
3583     stream >> splitterstates;
3584     d->m_editorAreas.first()->restoreState(splitterstates); // TODO
3585 
3586     if (!stream.atEnd()) { // safety for settings from Qt Creator 4.5 and earlier
3587         // restore windows
3588         QVector<QVariantHash> windowStates;
3589         stream >> windowStates;
3590         for (const QVariantHash &windowState : qAsConst(windowStates)) {
3591             EditorWindow *window = d->createEditorWindow();
3592             window->restoreState(windowState);
3593             window->show();
3594         }
3595     }
3596 
3597     // splitting and stuff results in focus trouble, that's why we set the focus again after restoration
3598     if (d->m_currentEditor) {
3599         d->m_currentEditor->widget()->setFocus();
3600     } else if (Internal::EditorView *view = EditorManagerPrivate::currentEditorView()) {
3601         if (IEditor *e = view->currentEditor())
3602             e->widget()->setFocus();
3603         else
3604             view->setFocus();
3605     }
3606 
3607     QApplication::restoreOverrideCursor();
3608 
3609     return true;
3610 }
3611 
3612 /*!
3613     \internal
3614 */
showEditorStatusBar(const QString & id,const QString & infoText,const QString & buttonText,QObject * object,const std::function<void ()> & function)3615 void EditorManager::showEditorStatusBar(const QString &id,
3616                                         const QString &infoText,
3617                                         const QString &buttonText,
3618                                         QObject *object,
3619                                         const std::function<void()> &function)
3620 {
3621 
3622     EditorManagerPrivate::currentEditorView()->showEditorStatusBar(
3623                 id, infoText, buttonText, object, function);
3624 }
3625 
3626 /*!
3627     \internal
3628 */
hideEditorStatusBar(const QString & id)3629 void EditorManager::hideEditorStatusBar(const QString &id)
3630 {
3631     // TODO: what if the current editor view betwenn show and hideEditorStatusBar changed?
3632     EditorManagerPrivate::currentEditorView()->hideEditorStatusBar(id);
3633 }
3634 
3635 /*!
3636     Returns the default text codec as the user specified in the settings.
3637 */
defaultTextCodec()3638 QTextCodec *EditorManager::defaultTextCodec()
3639 {
3640     QSettings *settings = ICore::settings();
3641     const QByteArray codecName =
3642             settings->value(Constants::SETTINGS_DEFAULTTEXTENCODING).toByteArray();
3643     if (QTextCodec *candidate = QTextCodec::codecForName(codecName))
3644         return candidate;
3645     // Qt5 doesn't return a valid codec when looking up the "System" codec, but will return
3646     // such a codec when asking for the codec for locale and no matching codec is available.
3647     // So check whether such a codec was saved to the settings.
3648     QTextCodec *localeCodec = QTextCodec::codecForLocale();
3649     if (codecName == localeCodec->name())
3650         return localeCodec;
3651     if (QTextCodec *defaultUTF8 = QTextCodec::codecForName("UTF-8"))
3652         return defaultUTF8;
3653     return QTextCodec::codecForLocale();
3654 }
3655 
3656 /*!
3657     Returns the default line ending as the user specified in the settings.
3658 */
defaultLineEnding()3659 TextFileFormat::LineTerminationMode EditorManager::defaultLineEnding()
3660 {
3661     QSettings *settings = ICore::settings();
3662     const int defaultLineTerminator = settings->value(Constants::SETTINGS_DEFAULT_LINE_TERMINATOR,
3663             TextFileFormat::LineTerminationMode::NativeLineTerminator).toInt();
3664 
3665     return static_cast<TextFileFormat::LineTerminationMode>(defaultLineTerminator);
3666 }
3667 
3668 /*!
3669     Splits the editor view horizontally into adjacent views.
3670 */
splitSideBySide()3671 void EditorManager::splitSideBySide()
3672 {
3673     EditorManagerPrivate::split(Qt::Horizontal);
3674 }
3675 
3676 /*!
3677  * Moves focus to another split, creating it if necessary.
3678  * If there's no split and no other window, a side-by-side split is created.
3679  * If the current window is split, focus is moved to the next split within this window, cycling.
3680  * If the current window is not split, focus is moved to the next window.
3681  */
gotoOtherSplit()3682 void EditorManager::gotoOtherSplit()
3683 {
3684     EditorView *view = EditorManagerPrivate::currentEditorView();
3685     if (!view)
3686         return;
3687     EditorView *nextView = view->findNextView();
3688     if (!nextView) {
3689         // we are in the "last" view in this editor area
3690         int index = -1;
3691         EditorArea *area = EditorManagerPrivate::findEditorArea(view, &index);
3692         QTC_ASSERT(area, return);
3693         QTC_ASSERT(index >= 0 && index < d->m_editorAreas.size(), return);
3694         // stay in same window if it is split
3695         if (area->isSplitter()) {
3696             nextView = area->findFirstView();
3697             QTC_CHECK(nextView != view);
3698         } else {
3699             // find next editor area. this might be the same editor area if there's only one.
3700             int nextIndex = index + 1;
3701             if (nextIndex >= d->m_editorAreas.size())
3702                 nextIndex = 0;
3703             nextView = d->m_editorAreas.at(nextIndex)->findFirstView();
3704             QTC_CHECK(nextView);
3705             // if we had only one editor area with only one view, we end up at the startpoint
3706             // in that case we need to split
3707             if (nextView == view) {
3708                 QTC_CHECK(!area->isSplitter());
3709                 splitSideBySide(); // that deletes 'view'
3710                 view = area->findFirstView();
3711                 nextView = view->findNextView();
3712                 QTC_CHECK(nextView != view);
3713                 QTC_CHECK(nextView);
3714             }
3715         }
3716     }
3717 
3718     if (nextView)
3719         EditorManagerPrivate::activateView(nextView);
3720 }
3721 
3722 /*!
3723     Returns the maximum file size that should be opened in a text editor.
3724 */
maxTextFileSize()3725 qint64 EditorManager::maxTextFileSize()
3726 {
3727     return qint64(3) << 24;
3728 }
3729 
3730 /*!
3731     \internal
3732 
3733     Sets the window title addition handler to \a handler.
3734 */
setWindowTitleAdditionHandler(WindowTitleHandler handler)3735 void EditorManager::setWindowTitleAdditionHandler(WindowTitleHandler handler)
3736 {
3737     d->m_titleAdditionHandler = handler;
3738 }
3739 
3740 /*!
3741     \internal
3742 
3743     Sets the session title addition handler to \a handler.
3744 */
setSessionTitleHandler(WindowTitleHandler handler)3745 void EditorManager::setSessionTitleHandler(WindowTitleHandler handler)
3746 {
3747     d->m_sessionTitleHandler = handler;
3748 }
3749 
3750 /*!
3751     \internal
3752 */
updateWindowTitles()3753 void EditorManager::updateWindowTitles()
3754 {
3755     foreach (EditorArea *area, d->m_editorAreas)
3756         emit area->windowTitleNeedsUpdate();
3757 }
3758 
3759 /*!
3760     \internal
3761 */
setWindowTitleVcsTopicHandler(WindowTitleHandler handler)3762 void EditorManager::setWindowTitleVcsTopicHandler(WindowTitleHandler handler)
3763 {
3764     d->m_titleVcsTopicHandler = handler;
3765 }
3766