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