1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 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 "editorview.h"
27 
28 #include "editormanager.h"
29 #include "editormanager_p.h"
30 #include "documentmodel.h"
31 #include "documentmodel_p.h"
32 
33 #include <coreplugin/actionmanager/actionmanager.h>
34 #include <coreplugin/editormanager/ieditor.h>
35 #include <coreplugin/editortoolbar.h>
36 #include <coreplugin/findplaceholder.h>
37 #include <coreplugin/icore.h>
38 #include <coreplugin/locator/locatorconstants.h>
39 #include <coreplugin/minisplitter.h>
40 #include <utils/algorithm.h>
41 #include <utils/infobar.h>
42 #include <utils/qtcassert.h>
43 #include <utils/theme/theme.h>
44 #include <utils/link.h>
45 #include <utils/utilsicons.h>
46 
47 #include <QDebug>
48 
49 #include <QFileInfo>
50 #include <QHBoxLayout>
51 #include <QLabel>
52 #include <QMenu>
53 #include <QMouseEvent>
54 #include <QPainter>
55 #include <QStackedWidget>
56 #include <QToolButton>
57 #include <QSplitter>
58 #include <QStackedLayout>
59 
60 using namespace Core;
61 using namespace Core::Internal;
62 using namespace Utils;
63 
64 // ================EditorView====================
65 
EditorView(SplitterOrView * parentSplitterOrView,QWidget * parent)66 EditorView::EditorView(SplitterOrView *parentSplitterOrView, QWidget *parent) :
67     QWidget(parent),
68     m_parentSplitterOrView(parentSplitterOrView),
69     m_toolBar(new EditorToolBar(this)),
70     m_container(new QStackedWidget(this)),
71     m_infoBarDisplay(new InfoBarDisplay(this)),
72     m_statusHLine(new QFrame(this)),
73     m_statusWidget(new QFrame(this))
74 {
75     auto tl = new QVBoxLayout(this);
76     tl->setSpacing(0);
77     tl->setContentsMargins(0, 0, 0, 0);
78     {
79         connect(m_toolBar, &EditorToolBar::goBackClicked,
80                 this, &EditorView::goBackInNavigationHistory);
81         connect(m_toolBar, &EditorToolBar::goForwardClicked,
82                 this, &EditorView::goForwardInNavigationHistory);
83         connect(m_toolBar, &EditorToolBar::closeClicked, this, &EditorView::closeCurrentEditor);
84         connect(m_toolBar, &EditorToolBar::listSelectionActivated,
85                 this, &EditorView::listSelectionActivated);
86         connect(m_toolBar, &EditorToolBar::currentDocumentMoved,
87                 this, &EditorView::closeCurrentEditor);
88         connect(m_toolBar, &EditorToolBar::horizontalSplitClicked,
89                 this, &EditorView::splitHorizontally);
90         connect(m_toolBar, &EditorToolBar::verticalSplitClicked, this, &EditorView::splitVertically);
91         connect(m_toolBar, &EditorToolBar::splitNewWindowClicked, this, &EditorView::splitNewWindow);
92         connect(m_toolBar, &EditorToolBar::closeSplitClicked, this, &EditorView::closeSplit);
93         m_toolBar->setMenuProvider([this](QMenu *menu) { fillListContextMenu(menu); });
94         tl->addWidget(m_toolBar);
95     }
96 
97     m_infoBarDisplay->setTarget(tl, 1);
98 
99     tl->addWidget(m_container);
100 
101     tl->addWidget(new FindToolBarPlaceHolder(this));
102 
103     {
104         m_statusHLine->setFrameStyle(QFrame::HLine);
105 
106         m_statusWidget->setFrameStyle(QFrame::NoFrame);
107         m_statusWidget->setLineWidth(0);
108         m_statusWidget->setAutoFillBackground(true);
109 
110         auto hbox = new QHBoxLayout(m_statusWidget);
111         hbox->setContentsMargins(1, 0, 1, 1);
112         m_statusWidgetLabel = new QLabel;
113         m_statusWidgetLabel->setContentsMargins(3, 0, 3, 0);
114         hbox->addWidget(m_statusWidgetLabel);
115         hbox->addStretch(1);
116 
117         m_statusWidgetButton = new QToolButton;
118         m_statusWidgetButton->setContentsMargins(0, 0, 0, 0);
119         hbox->addWidget(m_statusWidgetButton);
120 
121         m_statusHLine->setVisible(false);
122         m_statusWidget->setVisible(false);
123         tl->addWidget(m_statusHLine);
124         tl->addWidget(m_statusWidget);
125     }
126 
127     // for the case of no document selected
128     auto empty = new QWidget;
129     empty->hide();
130     auto emptyLayout = new QGridLayout(empty);
131     empty->setLayout(emptyLayout);
132     m_emptyViewLabel = new QLabel;
133     connect(EditorManagerPrivate::instance(), &EditorManagerPrivate::placeholderTextChanged,
134             m_emptyViewLabel, &QLabel::setText);
135     m_emptyViewLabel->setText(EditorManagerPrivate::placeholderText());
136     emptyLayout->addWidget(m_emptyViewLabel);
137     m_container->addWidget(empty);
138     m_widgetEditorMap.insert(empty, nullptr);
139 
140     const auto dropSupport = new DropSupport(this, [this](QDropEvent *event, DropSupport *) {
141         // do not accept move events except from other editor views (i.e. their tool bars)
142         // otherwise e.g. item views that support moving items within themselves would
143         // also "move" the item into the editor view, i.e. the item would be removed from the
144         // item view
145         if (!qobject_cast<EditorToolBar*>(event->source()))
146             event->setDropAction(Qt::CopyAction);
147         if (event->type() == QDropEvent::DragEnter && !DropSupport::isFileDrop(event))
148             return false; // do not accept drops without files
149         return event->source() != m_toolBar; // do not accept drops on ourselves
150     });
151     connect(dropSupport, &DropSupport::filesDropped,
152             this, &EditorView::openDroppedFiles);
153 
154     updateNavigatorActions();
155 }
156 
157 EditorView::~EditorView() = default;
158 
parentSplitterOrView() const159 SplitterOrView *EditorView::parentSplitterOrView() const
160 {
161     return m_parentSplitterOrView;
162 }
163 
findNextView() const164 EditorView *EditorView::findNextView() const
165 {
166     SplitterOrView *current = parentSplitterOrView();
167     QTC_ASSERT(current, return nullptr);
168     SplitterOrView *parent = current->findParentSplitter();
169     while (parent) {
170         QSplitter *splitter = parent->splitter();
171         QTC_ASSERT(splitter, return nullptr);
172         QTC_ASSERT(splitter->count() == 2, return nullptr);
173         // is current the first child? then the next view is the first one in current's sibling
174         if (splitter->widget(0) == current) {
175             auto second = qobject_cast<SplitterOrView *>(splitter->widget(1));
176             QTC_ASSERT(second, return nullptr);
177             return second->findFirstView();
178         }
179         // otherwise go up the hierarchy
180         current = parent;
181         parent = current->findParentSplitter();
182     }
183     // current has no parent, so we are at the top and there is no "next" view
184     return nullptr;
185 }
186 
findPreviousView() const187 EditorView *EditorView::findPreviousView() const
188 {
189     SplitterOrView *current = parentSplitterOrView();
190     QTC_ASSERT(current, return nullptr);
191     SplitterOrView *parent = current->findParentSplitter();
192     while (parent) {
193         QSplitter *splitter = parent->splitter();
194         QTC_ASSERT(splitter, return nullptr);
195         QTC_ASSERT(splitter->count() == 2, return nullptr);
196         // is current the last child? then the previous view is the first child in current's sibling
197         if (splitter->widget(1) == current) {
198             auto first = qobject_cast<SplitterOrView *>(splitter->widget(0));
199             QTC_ASSERT(first, return nullptr);
200             return first->findFirstView();
201         }
202         // otherwise go up the hierarchy
203         current = parent;
204         parent = current->findParentSplitter();
205     }
206     // current has no parent, so we are at the top and there is no "previous" view
207     return nullptr;
208 }
209 
closeCurrentEditor()210 void EditorView::closeCurrentEditor()
211 {
212     IEditor *editor = currentEditor();
213     if (editor)
214        EditorManagerPrivate::closeEditorOrDocument(editor);
215 }
216 
showEditorStatusBar(const QString & id,const QString & infoText,const QString & buttonText,QObject * object,const std::function<void ()> & function)217 void EditorView::showEditorStatusBar(const QString &id,
218                                      const QString &infoText,
219                                      const QString &buttonText,
220                                      QObject *object, const std::function<void()> &function)
221 {
222     m_statusWidgetId = id;
223     m_statusWidgetLabel->setText(infoText);
224     m_statusWidgetButton->setText(buttonText);
225     m_statusWidgetButton->setToolTip(buttonText);
226     m_statusWidgetButton->disconnect();
227     if (object && function)
228         connect(m_statusWidgetButton, &QToolButton::clicked, object, function);
229     m_statusWidget->setVisible(true);
230     m_statusHLine->setVisible(true);
231     //m_editorForInfoWidget = currentEditor();
232 }
233 
hideEditorStatusBar(const QString & id)234 void EditorView::hideEditorStatusBar(const QString &id)
235 {
236     if (id == m_statusWidgetId) {
237         m_statusWidget->setVisible(false);
238         m_statusHLine->setVisible(false);
239     }
240 }
241 
setCloseSplitEnabled(bool enable)242 void EditorView::setCloseSplitEnabled(bool enable)
243 {
244     m_toolBar->setCloseSplitEnabled(enable);
245 }
246 
setCloseSplitIcon(const QIcon & icon)247 void EditorView::setCloseSplitIcon(const QIcon &icon)
248 {
249     m_toolBar->setCloseSplitIcon(icon);
250 }
251 
updateEditorHistory(IEditor * editor,QList<EditLocation> & history)252 void EditorView::updateEditorHistory(IEditor *editor, QList<EditLocation> &history)
253 {
254     if (!editor)
255         return;
256     IDocument *document = editor->document();
257 
258     if (!document)
259         return;
260 
261     QByteArray state = editor->saveState();
262 
263     EditLocation location;
264     location.document = document;
265     location.filePath = document->filePath();
266     location.id = document->id();
267     location.state = QVariant(state);
268 
269     for (int i = 0; i < history.size(); ++i) {
270         const EditLocation &item = history.at(i);
271         if (item.document == document
272                 || (!item.document && !DocumentModel::indexOfFilePath(item.filePath))) {
273             history.removeAt(i--);
274         }
275     }
276     history.prepend(location);
277 }
278 
paintEvent(QPaintEvent *)279 void EditorView::paintEvent(QPaintEvent *)
280 {
281     EditorView *editorView = EditorManagerPrivate::currentEditorView();
282     if (editorView != this)
283         return;
284 
285     if (m_container->currentIndex() != 0) // so a document is selected
286         return;
287 
288     // Discreet indication where an editor would be if there is none
289     QPainter painter(this);
290 
291     QRect rect = m_container->geometry();
292     if (creatorTheme()->flag(Theme::FlatToolBars)) {
293         painter.fillRect(rect, creatorTheme()->color(Theme::EditorPlaceholderColor));
294     } else {
295         painter.setRenderHint(QPainter::Antialiasing, true);
296         painter.setPen(Qt::NoPen);
297         painter.setBrush(creatorTheme()->color(Theme::EditorPlaceholderColor));
298         const int r = 3;
299         painter.drawRoundedRect(rect.adjusted(r , r, -r, -r), r * 2, r * 2);
300     }
301 }
302 
mousePressEvent(QMouseEvent * e)303 void EditorView::mousePressEvent(QMouseEvent *e)
304 {
305     if (e->button() != Qt::LeftButton)
306         return;
307     setFocus(Qt::MouseFocusReason);
308 }
309 
focusInEvent(QFocusEvent *)310 void EditorView::focusInEvent(QFocusEvent *)
311 {
312     EditorManagerPrivate::setCurrentView(this);
313 }
314 
addEditor(IEditor * editor)315 void EditorView::addEditor(IEditor *editor)
316 {
317     if (m_editors.contains(editor))
318         return;
319 
320     m_editors.append(editor);
321 
322     m_container->addWidget(editor->widget());
323     m_widgetEditorMap.insert(editor->widget(), editor);
324     m_toolBar->addEditor(editor);
325 
326     if (editor == currentEditor())
327         setCurrentEditor(editor);
328 }
329 
hasEditor(IEditor * editor) const330 bool EditorView::hasEditor(IEditor *editor) const
331 {
332     return m_editors.contains(editor);
333 }
334 
removeEditor(IEditor * editor)335 void EditorView::removeEditor(IEditor *editor)
336 {
337     QTC_ASSERT(editor, return);
338     if (!m_editors.contains(editor))
339         return;
340 
341     const int index = m_container->indexOf(editor->widget());
342     QTC_ASSERT((index != -1), return);
343     bool wasCurrent = (index == m_container->currentIndex());
344     m_editors.removeAll(editor);
345 
346     m_container->removeWidget(editor->widget());
347     m_widgetEditorMap.remove(editor->widget());
348     editor->widget()->setParent(nullptr);
349     m_toolBar->removeToolbarForEditor(editor);
350 
351     if (wasCurrent)
352         setCurrentEditor(!m_editors.isEmpty() ? m_editors.last() : nullptr);
353 }
354 
currentEditor() const355 IEditor *EditorView::currentEditor() const
356 {
357     if (!m_editors.isEmpty())
358         return m_widgetEditorMap.value(m_container->currentWidget());
359     return nullptr;
360 }
361 
listSelectionActivated(int index)362 void EditorView::listSelectionActivated(int index)
363 {
364     EditorManagerPrivate::activateEditorForEntry(this, DocumentModel::entryAtRow(index));
365 }
366 
fillListContextMenu(QMenu * menu) const367 void EditorView::fillListContextMenu(QMenu *menu) const
368 {
369     IEditor *editor = currentEditor();
370     DocumentModel::Entry *entry = editor ? DocumentModel::entryForDocument(editor->document())
371                                          : nullptr;
372     EditorManager::addSaveAndCloseEditorActions(menu, entry, editor);
373     menu->addSeparator();
374     EditorManager::addNativeDirAndOpenWithActions(menu, entry);
375 }
376 
splitHorizontally()377 void EditorView::splitHorizontally()
378 {
379     if (m_parentSplitterOrView)
380         m_parentSplitterOrView->split(Qt::Vertical);
381     EditorManagerPrivate::updateActions();
382 }
383 
splitVertically()384 void EditorView::splitVertically()
385 {
386     if (m_parentSplitterOrView)
387         m_parentSplitterOrView->split(Qt::Horizontal);
388     EditorManagerPrivate::updateActions();
389 }
390 
splitNewWindow()391 void EditorView::splitNewWindow()
392 {
393     EditorManagerPrivate::splitNewWindow(this);
394 }
395 
closeSplit()396 void EditorView::closeSplit()
397 {
398     EditorManagerPrivate::closeView(this);
399     EditorManagerPrivate::updateActions();
400 }
401 
openDroppedFiles(const QList<DropSupport::FileSpec> & files)402 void EditorView::openDroppedFiles(const QList<DropSupport::FileSpec> &files)
403 {
404     bool first = true;
405     auto specToLink = [](const DropSupport::FileSpec &spec) {
406         return Utils::Link(FilePath::fromString(spec.filePath), spec.line, spec.column);
407     };
408     auto openEntry = [&](const DropSupport::FileSpec &spec) {
409         if (first) {
410             first = false;
411             EditorManagerPrivate::openEditorAt(this, specToLink(spec));
412         } else if (spec.column != -1 || spec.line != -1) {
413             EditorManagerPrivate::openEditorAt(this,
414                                                specToLink(spec),
415                                                Id(),
416                                                EditorManager::DoNotChangeCurrentEditor
417                                                    | EditorManager::DoNotMakeVisible);
418         } else {
419             auto *factory = IEditorFactory::preferredEditorFactories(spec.filePath).value(0);
420             DocumentModelPrivate::addSuspendedDocument(spec.filePath,
421                                                        {},
422                                                        factory ? factory->id() : Id());
423         }
424     };
425     Utils::reverseForeach(files, openEntry);
426 }
427 
setParentSplitterOrView(SplitterOrView * splitterOrView)428 void EditorView::setParentSplitterOrView(SplitterOrView *splitterOrView)
429 {
430     m_parentSplitterOrView = splitterOrView;
431 }
432 
setCurrentEditor(IEditor * editor)433 void EditorView::setCurrentEditor(IEditor *editor)
434 {
435     if (!editor || m_container->indexOf(editor->widget()) == -1) {
436         QTC_CHECK(!editor);
437         m_toolBar->setCurrentEditor(nullptr);
438         m_infoBarDisplay->setInfoBar(nullptr);
439         m_container->setCurrentIndex(0);
440         emit currentEditorChanged(nullptr);
441         return;
442     }
443 
444     m_editors.removeAll(editor);
445     m_editors.append(editor);
446 
447     const int idx = m_container->indexOf(editor->widget());
448     QTC_ASSERT(idx >= 0, return);
449     m_container->setCurrentIndex(idx);
450     m_toolBar->setCurrentEditor(editor);
451 
452     updateEditorHistory(editor);
453 
454     m_infoBarDisplay->setInfoBar(editor->document()->infoBar());
455     emit currentEditorChanged(editor);
456 }
457 
editorCount() const458 int EditorView::editorCount() const
459 {
460     return m_editors.size();
461 }
462 
editors() const463 QList<IEditor *> EditorView::editors() const
464 {
465     return m_editors;
466 }
467 
editorForDocument(const IDocument * document) const468 IEditor *EditorView::editorForDocument(const IDocument *document) const
469 {
470     foreach (IEditor *editor, m_editors)
471         if (editor->document() == document)
472             return editor;
473     return nullptr;
474 }
475 
updateEditorHistory(IEditor * editor)476 void EditorView::updateEditorHistory(IEditor *editor)
477 {
478     updateEditorHistory(editor, m_editorHistory);
479 }
480 
addCurrentPositionToNavigationHistory(const QByteArray & saveState)481 void EditorView::addCurrentPositionToNavigationHistory(const QByteArray &saveState)
482 {
483     IEditor *editor = currentEditor();
484     if (!editor)
485         return;
486     IDocument *document = editor->document();
487 
488     if (!document)
489         return;
490 
491     QByteArray state;
492     if (saveState.isNull())
493         state = editor->saveState();
494     else
495         state = saveState;
496 
497     EditLocation location;
498     location.document = document;
499     location.filePath = document->filePath();
500     location.id = document->id();
501     location.state = QVariant(state);
502     m_currentNavigationHistoryPosition = qMin(m_currentNavigationHistoryPosition, m_navigationHistory.size()); // paranoia
503     m_navigationHistory.insert(m_currentNavigationHistoryPosition, location);
504     ++m_currentNavigationHistoryPosition;
505 
506     while (m_navigationHistory.size() >= 30) {
507         if (m_currentNavigationHistoryPosition > 15) {
508             m_navigationHistory.removeFirst();
509             --m_currentNavigationHistoryPosition;
510         } else {
511             m_navigationHistory.removeLast();
512         }
513     }
514     updateNavigatorActions();
515 }
516 
cutForwardNavigationHistory()517 void EditorView::cutForwardNavigationHistory()
518 {
519     while (m_currentNavigationHistoryPosition < m_navigationHistory.size() - 1)
520         m_navigationHistory.removeLast();
521 }
522 
updateNavigatorActions()523 void EditorView::updateNavigatorActions()
524 {
525     m_toolBar->setCanGoBack(canGoBack());
526     m_toolBar->setCanGoForward(canGoForward());
527 }
528 
copyNavigationHistoryFrom(EditorView * other)529 void EditorView::copyNavigationHistoryFrom(EditorView* other)
530 {
531     if (!other)
532         return;
533     m_currentNavigationHistoryPosition = other->m_currentNavigationHistoryPosition;
534     m_navigationHistory = other->m_navigationHistory;
535     m_editorHistory = other->m_editorHistory;
536     updateNavigatorActions();
537 }
538 
updateCurrentPositionInNavigationHistory()539 void EditorView::updateCurrentPositionInNavigationHistory()
540 {
541     IEditor *editor = currentEditor();
542     if (!editor || !editor->document())
543         return;
544 
545     IDocument *document = editor->document();
546     EditLocation *location;
547     if (m_currentNavigationHistoryPosition < m_navigationHistory.size()) {
548         location = &m_navigationHistory[m_currentNavigationHistoryPosition];
549     } else {
550         m_navigationHistory.append(EditLocation());
551         location = &m_navigationHistory[m_navigationHistory.size()-1];
552     }
553     location->document = document;
554     location->filePath = document->filePath();
555     location->id = document->id();
556     location->state = QVariant(editor->saveState());
557 }
558 
fileNameWasRemoved(const FilePath & filePath)559 static bool fileNameWasRemoved(const FilePath &filePath)
560 {
561     return !filePath.isEmpty() && !filePath.exists();
562 }
563 
goBackInNavigationHistory()564 void EditorView::goBackInNavigationHistory()
565 {
566     updateCurrentPositionInNavigationHistory();
567     while (m_currentNavigationHistoryPosition > 0) {
568         --m_currentNavigationHistoryPosition;
569         EditLocation location = m_navigationHistory.at(m_currentNavigationHistoryPosition);
570         IEditor *editor = nullptr;
571         if (location.document) {
572             editor = EditorManagerPrivate::activateEditorForDocument(this, location.document,
573                                         EditorManager::IgnoreNavigationHistory);
574         }
575         if (!editor) {
576             if (fileNameWasRemoved(location.filePath)) {
577                 m_navigationHistory.removeAt(m_currentNavigationHistoryPosition);
578                 continue;
579             }
580             editor = EditorManagerPrivate::openEditor(this, location.filePath, location.id,
581                                     EditorManager::IgnoreNavigationHistory);
582             if (!editor) {
583                 m_navigationHistory.removeAt(m_currentNavigationHistoryPosition);
584                 continue;
585             }
586         }
587         editor->restoreState(location.state.toByteArray());
588         break;
589     }
590     updateNavigatorActions();
591 }
592 
goForwardInNavigationHistory()593 void EditorView::goForwardInNavigationHistory()
594 {
595     updateCurrentPositionInNavigationHistory();
596     if (m_currentNavigationHistoryPosition >= m_navigationHistory.size()-1)
597         return;
598     ++m_currentNavigationHistoryPosition;
599     while (m_currentNavigationHistoryPosition < m_navigationHistory.size()) {
600         IEditor *editor = nullptr;
601         EditLocation location = m_navigationHistory.at(m_currentNavigationHistoryPosition);
602         if (location.document) {
603             editor = EditorManagerPrivate::activateEditorForDocument(this, location.document,
604                                                                      EditorManager::IgnoreNavigationHistory);
605         }
606         if (!editor) {
607             if (fileNameWasRemoved(location.filePath)) {
608                 m_navigationHistory.removeAt(m_currentNavigationHistoryPosition);
609                 continue;
610             }
611             editor = EditorManagerPrivate::openEditor(this, location.filePath, location.id,
612                                                       EditorManager::IgnoreNavigationHistory);
613             if (!editor) {
614                 m_navigationHistory.removeAt(m_currentNavigationHistoryPosition);
615                 continue;
616             }
617         }
618         editor->restoreState(location.state.toByteArray());
619         break;
620     }
621     if (m_currentNavigationHistoryPosition >= m_navigationHistory.size())
622         m_currentNavigationHistoryPosition = qMax<int>(m_navigationHistory.size() - 1, 0);
623     updateNavigatorActions();
624 }
625 
goToEditLocation(const EditLocation & location)626 void EditorView::goToEditLocation(const EditLocation &location)
627 {
628     IEditor *editor = nullptr;
629 
630     if (location.document) {
631         editor = EditorManagerPrivate::activateEditorForDocument(this, location.document,
632                                                                  EditorManager::IgnoreNavigationHistory);
633     }
634 
635     if (!editor) {
636         if (fileNameWasRemoved(location.filePath))
637             return;
638 
639         editor = EditorManagerPrivate::openEditor(this, location.filePath, location.id,
640                                                   EditorManager::IgnoreNavigationHistory);
641     }
642 
643     if (editor) {
644         editor->restoreState(location.state.toByteArray());
645     }
646 }
647 
648 
SplitterOrView(IEditor * editor)649 SplitterOrView::SplitterOrView(IEditor *editor)
650 {
651     m_layout = new QStackedLayout(this);
652     m_layout->setSizeConstraint(QLayout::SetNoConstraint);
653     m_view = new EditorView(this);
654     if (editor)
655         m_view->addEditor(editor);
656     m_splitter = nullptr;
657     m_layout->addWidget(m_view);
658 }
659 
SplitterOrView(EditorView * view)660 SplitterOrView::SplitterOrView(EditorView *view)
661 {
662     Q_ASSERT(view);
663     m_layout = new QStackedLayout(this);
664     m_layout->setSizeConstraint(QLayout::SetNoConstraint);
665     m_view = view;
666     m_view->setParentSplitterOrView(this);
667     m_splitter = nullptr;
668     m_layout->addWidget(m_view);
669 }
670 
~SplitterOrView()671 SplitterOrView::~SplitterOrView()
672 {
673     delete m_layout;
674     m_layout = nullptr;
675     if (m_view)
676         EditorManagerPrivate::deleteEditors(EditorManagerPrivate::emptyView(m_view));
677     delete m_view;
678     m_view = nullptr;
679     delete m_splitter;
680     m_splitter = nullptr;
681 }
682 
findFirstView()683 EditorView *SplitterOrView::findFirstView()
684 {
685     if (m_splitter) {
686         for (int i = 0; i < m_splitter->count(); ++i) {
687             if (auto splitterOrView = qobject_cast<SplitterOrView*>(m_splitter->widget(i)))
688                 if (EditorView *result = splitterOrView->findFirstView())
689                     return result;
690         }
691         return nullptr;
692     }
693     return m_view;
694 }
695 
findLastView()696 EditorView *SplitterOrView::findLastView()
697 {
698     if (m_splitter) {
699         for (int i = m_splitter->count() - 1; 0 < i; --i) {
700             if (auto splitterOrView = qobject_cast<SplitterOrView*>(m_splitter->widget(i)))
701                 if (EditorView *result = splitterOrView->findLastView())
702                     return result;
703         }
704         return nullptr;
705     }
706     return m_view;
707 }
708 
findParentSplitter() const709 SplitterOrView *SplitterOrView::findParentSplitter() const
710 {
711     QWidget *w = parentWidget();
712     while (w) {
713         if (auto splitter = qobject_cast<SplitterOrView *>(w)) {
714             QTC_CHECK(splitter->splitter());
715             return splitter;
716         }
717         w = w->parentWidget();
718     }
719     return nullptr;
720 }
721 
minimumSizeHint() const722 QSize SplitterOrView::minimumSizeHint() const
723 {
724     if (m_splitter)
725         return m_splitter->minimumSizeHint();
726     return QSize(64, 64);
727 }
728 
takeSplitter()729 QSplitter *SplitterOrView::takeSplitter()
730 {
731     QSplitter *oldSplitter = m_splitter;
732     if (m_splitter)
733         m_layout->removeWidget(m_splitter);
734     m_splitter = nullptr;
735     return oldSplitter;
736 }
737 
takeView()738 EditorView *SplitterOrView::takeView()
739 {
740     EditorView *oldView = m_view;
741     if (m_view) {
742         // the focus update that is triggered by removing should already have 0 parent
743         // so we do that first
744         m_view->setParentSplitterOrView(nullptr);
745         m_layout->removeWidget(m_view);
746     }
747     m_view = nullptr;
748     return oldView;
749 }
750 
split(Qt::Orientation orientation,bool activateView)751 void SplitterOrView::split(Qt::Orientation orientation, bool activateView)
752 {
753     Q_ASSERT(m_view && m_splitter == nullptr);
754     m_splitter = new MiniSplitter(this);
755     m_splitter->setOrientation(orientation);
756     m_layout->addWidget(m_splitter);
757     m_layout->removeWidget(m_view);
758     EditorView *editorView = m_view;
759     editorView->setCloseSplitEnabled(true); // might have been disabled for root view
760     m_view = nullptr;
761     IEditor *e = editorView->currentEditor();
762     const QByteArray state = e ? e->saveState() : QByteArray();
763 
764     SplitterOrView *view = nullptr;
765     SplitterOrView *otherView = nullptr;
766     IEditor *duplicate = e && e->duplicateSupported() ? EditorManagerPrivate::duplicateEditor(e) : nullptr;
767     m_splitter->addWidget((view = new SplitterOrView(duplicate)));
768     m_splitter->addWidget((otherView = new SplitterOrView(editorView)));
769 
770     m_layout->setCurrentWidget(m_splitter);
771 
772     view->view()->copyNavigationHistoryFrom(editorView);
773     view->view()->setCurrentEditor(duplicate);
774 
775     if (orientation == Qt::Horizontal) {
776         view->view()->setCloseSplitIcon(Utils::Icons::CLOSE_SPLIT_LEFT.icon());
777         otherView->view()->setCloseSplitIcon(Utils::Icons::CLOSE_SPLIT_RIGHT.icon());
778     } else {
779         view->view()->setCloseSplitIcon(Utils::Icons::CLOSE_SPLIT_TOP.icon());
780         otherView->view()->setCloseSplitIcon(Utils::Icons::CLOSE_SPLIT_BOTTOM.icon());
781     }
782 
783     // restore old state, possibly adapted to the new layout (the editors can e.g. make sure that
784     // a previously visible text cursor stays visible)
785     if (duplicate)
786         duplicate->restoreState(state);
787     if (e)
788         e->restoreState(state);
789 
790     if (activateView)
791         EditorManagerPrivate::activateView(otherView->view());
792     emit splitStateChanged();
793 }
794 
unsplitAll()795 void SplitterOrView::unsplitAll()
796 {
797     QTC_ASSERT(m_splitter, return);
798     // avoid focus changes while unsplitting is in progress
799     bool hadFocus = false;
800     if (QWidget *w = focusWidget()) {
801         if (w->hasFocus()) {
802             w->clearFocus();
803             hadFocus = true;
804         }
805     }
806 
807     EditorView *currentView = EditorManagerPrivate::currentEditorView();
808     if (currentView) {
809         currentView->parentSplitterOrView()->takeView();
810         currentView->setParentSplitterOrView(this);
811     } else {
812         currentView = new EditorView(this);
813     }
814     m_splitter->hide();
815     m_layout->removeWidget(m_splitter); // workaround Qt bug
816     const QList<IEditor *> editorsToDelete = unsplitAll_helper();
817     m_view = currentView;
818     m_layout->addWidget(m_view);
819     delete m_splitter;
820     m_splitter = nullptr;
821 
822     // restore some focus
823     if (hadFocus) {
824         if (IEditor *editor = m_view->currentEditor())
825             editor->widget()->setFocus();
826         else
827             m_view->setFocus();
828     }
829     EditorManagerPrivate::deleteEditors(editorsToDelete);
830     emit splitStateChanged();
831 }
832 
833 /*!
834     Recursively empties all views.
835     Returns the editors to delete with EditorManagerPrivate::deleteEditors.
836     \internal
837 */
unsplitAll_helper()838 const QList<IEditor *> SplitterOrView::unsplitAll_helper()
839 {
840     if (m_view)
841         return EditorManagerPrivate::emptyView(m_view);
842     QList<IEditor *> editorsToDelete;
843     if (m_splitter) {
844         for (int i = 0; i < m_splitter->count(); ++i) {
845             if (auto splitterOrView = qobject_cast<SplitterOrView*>(m_splitter->widget(i)))
846                 editorsToDelete.append(splitterOrView->unsplitAll_helper());
847         }
848     }
849     return editorsToDelete;
850 }
851 
unsplit()852 void SplitterOrView::unsplit()
853 {
854     if (!m_splitter)
855         return;
856 
857     Q_ASSERT(m_splitter->count() == 1);
858     auto childSplitterOrView = qobject_cast<SplitterOrView*>(m_splitter->widget(0));
859     QSplitter *oldSplitter = m_splitter;
860     m_splitter = nullptr;
861     QList<IEditor *> editorsToDelete;
862     if (childSplitterOrView->isSplitter()) {
863         Q_ASSERT(childSplitterOrView->view() == nullptr);
864         m_splitter = childSplitterOrView->takeSplitter();
865         m_layout->addWidget(m_splitter);
866         m_layout->setCurrentWidget(m_splitter);
867     } else {
868         EditorView *childView = childSplitterOrView->view();
869         Q_ASSERT(childView);
870         if (m_view) {
871             m_view->copyNavigationHistoryFrom(childView);
872             if (IEditor *e = childView->currentEditor()) {
873                 childView->removeEditor(e);
874                 m_view->addEditor(e);
875                 m_view->setCurrentEditor(e);
876             }
877             editorsToDelete = EditorManagerPrivate::emptyView(childView);
878         } else {
879             m_view = childSplitterOrView->takeView();
880             m_view->setParentSplitterOrView(this);
881             m_layout->addWidget(m_view);
882             auto parentSplitter = qobject_cast<QSplitter *>(parentWidget());
883             if (parentSplitter) { // not the toplevel splitterOrView
884                 if (parentSplitter->orientation() == Qt::Horizontal)
885                     m_view->setCloseSplitIcon(parentSplitter->widget(0) == this ?
886                                                   Utils::Icons::CLOSE_SPLIT_LEFT.icon()
887                                                 : Utils::Icons::CLOSE_SPLIT_RIGHT.icon());
888                 else
889                     m_view->setCloseSplitIcon(parentSplitter->widget(0) == this ?
890                                                   Utils::Icons::CLOSE_SPLIT_TOP.icon()
891                                                 : Utils::Icons::CLOSE_SPLIT_BOTTOM.icon());
892             }
893         }
894         m_layout->setCurrentWidget(m_view);
895     }
896     delete oldSplitter;
897     if (EditorView *newCurrent = findFirstView())
898         EditorManagerPrivate::activateView(newCurrent);
899     else
900         EditorManagerPrivate::setCurrentView(nullptr);
901     EditorManagerPrivate::deleteEditors(editorsToDelete);
902     emit splitStateChanged();
903 }
904 
905 
saveState() const906 QByteArray SplitterOrView::saveState() const
907 {
908     QByteArray bytes;
909     QDataStream stream(&bytes, QIODevice::WriteOnly);
910 
911     if (m_splitter) {
912         stream << QByteArray("splitter")
913                 << (qint32)m_splitter->orientation()
914                 << m_splitter->saveState()
915                 << static_cast<SplitterOrView*>(m_splitter->widget(0))->saveState()
916                 << static_cast<SplitterOrView*>(m_splitter->widget(1))->saveState();
917     } else {
918         IEditor* e = editor();
919 
920         // don't save state of temporary or ad-hoc editors
921         if (e && (e->document()->isTemporary() || e->document()->filePath().isEmpty())) {
922             // look for another editor that is more suited
923             e = nullptr;
924             foreach (IEditor *otherEditor, editors()) {
925                 if (!otherEditor->document()->isTemporary() && !otherEditor->document()->filePath().isEmpty()) {
926                     e = otherEditor;
927                     break;
928                 }
929             }
930         }
931 
932         if (!e) {
933             stream << QByteArray("empty");
934         } else if (e == EditorManager::currentEditor()) {
935             stream << QByteArray("currenteditor")
936                    << e->document()->filePath().toString()
937                    << e->document()->id().toString()
938                    << e->saveState();
939         } else {
940             stream << QByteArray("editor")
941                    << e->document()->filePath().toString()
942                    << e->document()->id().toString()
943                    << e->saveState();
944         }
945     }
946     return bytes;
947 }
948 
restoreState(const QByteArray & state)949 void SplitterOrView::restoreState(const QByteArray &state)
950 {
951     QDataStream stream(state);
952     QByteArray mode;
953     stream >> mode;
954     if (mode == "splitter") {
955         qint32 orientation;
956         QByteArray splitter, first, second;
957         stream >> orientation >> splitter >> first >> second;
958         split((Qt::Orientation) orientation, false);
959         m_splitter->restoreState(splitter);
960         static_cast<SplitterOrView*>(m_splitter->widget(0))->restoreState(first);
961         static_cast<SplitterOrView*>(m_splitter->widget(1))->restoreState(second);
962     } else if (mode == "editor" || mode == "currenteditor") {
963         QString fileName;
964         QString id;
965         QByteArray editorState;
966         stream >> fileName >> id >> editorState;
967         if (!QFile::exists(fileName))
968             return;
969         IEditor *e = EditorManagerPrivate::openEditor(view(), FilePath::fromString(fileName), Id::fromString(id),
970                                                       EditorManager::IgnoreNavigationHistory
971                                                       | EditorManager::DoNotChangeCurrentEditor);
972 
973         if (!e) {
974             DocumentModel::Entry *entry = DocumentModelPrivate::firstSuspendedEntry();
975             if (entry) {
976                 EditorManagerPrivate::activateEditorForEntry(view(), entry,
977                     EditorManager::IgnoreNavigationHistory | EditorManager::DoNotChangeCurrentEditor);
978             }
979         }
980 
981         if (e) {
982             e->restoreState(editorState);
983             if (mode == "currenteditor")
984                 EditorManagerPrivate::setCurrentEditor(e);
985         }
986     }
987 }
988