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