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 "debuggermainwindow.h"
27 #include "debuggerconstants.h"
28 #include "debuggerinternalconstants.h"
29 #include "enginemanager.h"
30 
31 #include <coreplugin/actionmanager/actioncontainer.h>
32 #include <coreplugin/actionmanager/actionmanager.h>
33 #include <coreplugin/actionmanager/command.h>
34 #include <coreplugin/coreconstants.h>
35 #include <coreplugin/editormanager/editormanager.h>
36 #include <coreplugin/icore.h>
37 #include <coreplugin/modemanager.h>
38 #include <coreplugin/outputpane.h>
39 #include <coreplugin/rightpane.h>
40 
41 #include <projectexplorer/projectexplorericons.h>
42 
43 #include <utils/algorithm.h>
44 #include <utils/styledbar.h>
45 #include <utils/qtcassert.h>
46 #include <utils/proxyaction.h>
47 #include <utils/utilsicons.h>
48 #include <utils/stylehelper.h>
49 
50 #include <QAction>
51 #include <QComboBox>
52 #include <QContextMenuEvent>
53 #include <QDockWidget>
54 #include <QHBoxLayout>
55 #include <QHeaderView>
56 #include <QLoggingCategory>
57 #include <QMenu>
58 #include <QScrollArea>
59 #include <QStackedWidget>
60 #include <QStandardItemModel>
61 #include <QTimer>
62 #include <QToolButton>
63 #include <QTreeView>
64 
65 using namespace Debugger;
66 using namespace Core;
67 
68 static Q_LOGGING_CATEGORY(perspectivesLog, "qtc.utils.perspectives", QtWarningMsg)
69 
70 namespace Utils {
71 
72 const char LAST_PERSPECTIVE_KEY[]   = "LastPerspective";
73 const char MAINWINDOW_KEY[]         = "Debugger.MainWindow";
74 const char AUTOHIDE_TITLEBARS_KEY[] = "AutoHideTitleBars";
75 const char SHOW_CENTRALWIDGET_KEY[] = "ShowCentralWidget";
76 const char STATE_KEY[]              = "State";  // Up to 4.10
77 const char STATE_KEY2[]             = "State2"; // From 4.11 on
78 const char CHANGED_DOCK_KEY[]       = "ChangedDocks";
79 
80 static DebuggerMainWindow *theMainWindow = nullptr;
81 
82 class DockOperation
83 {
84 public:
85     void setupLayout();
86     void ensureDockExists();
87 
name() const88     QString name() const { QTC_ASSERT(widget, return QString()); return widget->objectName(); }
89     bool changedByUser() const;
90     void recordVisibility();
91 
92     Utils::Id commandId;
93     QPointer<QWidget> widget;
94     QPointer<QDockWidget> dock;
95     QPointer<QWidget> anchorWidget;
96     QPointer<Utils::ProxyAction> toggleViewAction;
97     Perspective::OperationType operationType = Perspective::Raise;
98     bool visibleByDefault = true;
99     Qt::DockWidgetArea area = Qt::BottomDockWidgetArea;
100 };
101 
102 class PerspectivePrivate
103 {
104 public:
105     ~PerspectivePrivate();
106 
107     void showInnerToolBar();
108     void hideInnerToolBar();
109     void restoreLayout();
110     void saveLayout();
111     void resetPerspective();
112     void populatePerspective();
113     void depopulatePerspective();
114     void saveAsLastUsedPerspective();
115     Context context() const;
116 
117     QString settingsId() const;
118     QToolButton *setupToolButton(QAction *action);
119 
120     QString m_id;
121     QString m_name;
122     QString m_parentPerspectiveId;
123     QString m_settingsId;
124     QVector<DockOperation> m_dockOperations;
125     QPointer<QWidget> m_centralWidget;
126     Perspective::Callback m_aboutToActivateCallback;
127     QPointer<QWidget> m_innerToolBar;
128     QHBoxLayout *m_innerToolBarLayout = nullptr;
129     QPointer<QWidget> m_switcher;
130     QString m_lastActiveSubPerspectiveId;
131 };
132 
133 class DebuggerMainWindowPrivate : public QObject
134 {
135 public:
136     DebuggerMainWindowPrivate(DebuggerMainWindow *parent);
137     ~DebuggerMainWindowPrivate();
138 
139     void selectPerspective(Perspective *perspective);
140     void depopulateCurrentPerspective();
141     void populateCurrentPerspective();
142     void destroyPerspective(Perspective *perspective);
143     void registerPerspective(Perspective *perspective);
144     void resetCurrentPerspective();
145     int indexInChooser(Perspective *perspective) const;
146     void updatePerspectiveChooserWidth();
147 
148     void cleanDocks();
149     void setCentralWidget(QWidget *widget);
150 
151     QDockWidget *dockForWidget(QWidget *widget) const;
152 
setCurrentPerspective(Perspective * perspective)153     void setCurrentPerspective(Perspective *perspective)
154     {
155         const Core::Context oldContext = m_currentPerspective
156                 ? Context(Id::fromString(m_currentPerspective->id())) : Context();
157         m_currentPerspective = perspective;
158         const Core::Context newContext = m_currentPerspective
159                 ? Context(Id::fromString(m_currentPerspective->id())) : Context();
160         ICore::updateAdditionalContexts(oldContext, newContext);
161     }
162 
163     DebuggerMainWindow *q = nullptr;
164     QPointer<Perspective> m_currentPerspective = nullptr;
165     QComboBox *m_perspectiveChooser = nullptr;
166     QMenu *m_perspectiveMenu;
167     QStackedWidget *m_centralWidgetStack = nullptr;
168     QHBoxLayout *m_subPerspectiveSwitcherLayout = nullptr;
169     QHBoxLayout *m_innerToolsLayout = nullptr;
170     QPointer<QWidget> m_editorPlaceHolder;
171     Utils::StatusLabel *m_statusLabel = nullptr;
172     QDockWidget *m_toolBarDock = nullptr;
173     bool needRestoreOnModeEnter = false;
174 
175     QList<QPointer<Perspective>> m_perspectives;
176     QSet<QString> m_persistentChangedDocks; // Dock Ids of docks with non-default visibility.
177 
178     QHash<QString, PerspectiveState> m_lastPerspectiveStates;      // Perspective::id() -> MainWindow::state()
179     QHash<QString, PerspectiveState> m_lastTypePerspectiveStates;  // Perspective::settingsId() -> MainWindow::state()
180 };
181 
DebuggerMainWindowPrivate(DebuggerMainWindow * parent)182 DebuggerMainWindowPrivate::DebuggerMainWindowPrivate(DebuggerMainWindow *parent)
183     : q(parent)
184 {
185     m_centralWidgetStack = new QStackedWidget;
186     m_statusLabel = new Utils::StatusLabel;
187     m_statusLabel->setProperty("panelwidget", true);
188     m_statusLabel->setIndent(2 * QFontMetrics(q->font()).horizontalAdvance(QChar('x')));
189     m_editorPlaceHolder = new EditorManagerPlaceHolder;
190 
191     m_perspectiveChooser = new QComboBox;
192     m_perspectiveChooser->setObjectName("PerspectiveChooser");
193     m_perspectiveChooser->setProperty("panelwidget", true);
194     m_perspectiveChooser->setSizeAdjustPolicy(QComboBox::AdjustToContents);
195     connect(m_perspectiveChooser, QOverload<int>::of(&QComboBox::activated), this, [this](int item) {
196         Perspective *perspective = Perspective::findPerspective(m_perspectiveChooser->itemData(item).toString());
197         QTC_ASSERT(perspective, return);
198         if (auto subPerspective = Perspective::findPerspective(perspective->d->m_lastActiveSubPerspectiveId))
199             subPerspective->select();
200         else
201             perspective->select();
202     });
203 
204     m_perspectiveMenu = new QMenu;
205     connect(m_perspectiveMenu, &QMenu::aboutToShow, this, [this] {
206         m_perspectiveMenu->clear();
207         for (Perspective *perspective : qAsConst(m_perspectives)) {
208             m_perspectiveMenu->addAction(perspective->d->m_name, perspective, [perspective] {
209                 if (auto subPerspective = Perspective::findPerspective(
210                         perspective->d->m_lastActiveSubPerspectiveId))
211                     subPerspective->select();
212                 else
213                     perspective->select();
214             });
215         }
216     });
217 
218     auto viewButton = new QToolButton;
219     viewButton->setText(DebuggerMainWindow::tr("&Views"));
220 
221     auto closeButton = new QToolButton();
222     closeButton->setIcon(Utils::Icons::CLOSE_SPLIT_BOTTOM.icon());
223     closeButton->setToolTip(DebuggerMainWindow::tr("Leave Debug Mode"));
224 
225     auto toolbar = new Utils::StyledBar;
226     toolbar->setProperty("topBorder", true);
227 
228     // "Engine switcher" style comboboxes
229     auto subPerspectiveSwitcher = new QWidget;
230     m_subPerspectiveSwitcherLayout = new QHBoxLayout(subPerspectiveSwitcher);
231     m_subPerspectiveSwitcherLayout->setContentsMargins(0, 0, 0, 0);
232     m_subPerspectiveSwitcherLayout->setSpacing(0);
233 
234     // All perspective toolbars will get inserted here, but only
235     // the current perspective's toolbar is set visible.
236     auto innerTools = new QWidget;
237     m_innerToolsLayout = new QHBoxLayout(innerTools);
238     m_innerToolsLayout->setContentsMargins(0, 0, 0, 0);
239     m_innerToolsLayout->setSpacing(0);
240 
241     auto hbox = new QHBoxLayout(toolbar);
242     hbox->setContentsMargins(0, 0, 0, 0);
243     hbox->setSpacing(0);
244     hbox->addWidget(m_perspectiveChooser);
245     hbox->addWidget(subPerspectiveSwitcher);
246     hbox->addWidget(innerTools);
247     hbox->addWidget(m_statusLabel);
248     hbox->addStretch(1);
249     hbox->addWidget(new Utils::StyledSeparator);
250     hbox->addWidget(viewButton);
251     hbox->addWidget(closeButton);
252 
253     auto scrolledToolbar = new QScrollArea;
254     scrolledToolbar->setWidget(toolbar);
255     scrolledToolbar->setFrameStyle(QFrame::NoFrame);
256     scrolledToolbar->setWidgetResizable(true);
257     scrolledToolbar->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
258     scrolledToolbar->setFixedHeight(StyleHelper::navigationWidgetHeight());
259     scrolledToolbar->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
260 
261     auto dock = new QDockWidget(DebuggerMainWindow::tr("Toolbar"), q);
262     dock->setObjectName("Toolbar");
263     dock->setFeatures(QDockWidget::NoDockWidgetFeatures);
264     dock->setAllowedAreas(Qt::BottomDockWidgetArea);
265     dock->setTitleBarWidget(new QWidget(dock)); // hide title bar
266     dock->setProperty("managed_dockwidget", "true");
267     dock->setWidget(scrolledToolbar);
268 
269     m_toolBarDock = dock;
270     q->addDockWidget(Qt::BottomDockWidgetArea, m_toolBarDock);
271 
272     connect(viewButton, &QAbstractButton::clicked, this, [viewButton] {
273         ActionContainer *viewsMenu = ActionManager::actionContainer(Core::Constants::M_VIEW_VIEWS);
274         viewsMenu->menu()->exec(viewButton->mapToGlobal(QPoint()));
275     });
276 
277     connect(closeButton, &QAbstractButton::clicked, [] {
278         ModeManager::activateMode(Core::Constants::MODE_EDIT);
279     });
280 }
281 
~DebuggerMainWindowPrivate()282 DebuggerMainWindowPrivate::~DebuggerMainWindowPrivate()
283 {
284     delete m_editorPlaceHolder;
285     delete m_perspectiveMenu;
286 }
287 
DebuggerMainWindow()288 DebuggerMainWindow::DebuggerMainWindow()
289     : d(new DebuggerMainWindowPrivate(this))
290 {
291     setDockNestingEnabled(true);
292     setDockActionsVisible(false);
293     setDocumentMode(true);
294 
295     connect(this, &FancyMainWindow::resetLayout,
296             d, &DebuggerMainWindowPrivate::resetCurrentPerspective);
297 
298     Context debugcontext(Debugger::Constants::C_DEBUGMODE);
299 
300     ActionContainer *viewsMenu = ActionManager::actionContainer(Core::Constants::M_VIEW_VIEWS);
301     Command *cmd = ActionManager::registerAction(showCentralWidgetAction(),
302         "Debugger.Views.ShowCentralWidget", debugcontext);
303     cmd->setAttribute(Command::CA_Hide);
304     cmd->setAttribute(Command::CA_UpdateText);
305     viewsMenu->addAction(cmd, Core::Constants::G_DEFAULT_THREE);
306     cmd = ActionManager::registerAction(menuSeparator1(),
307         "Debugger.Views.Separator1", debugcontext);
308     cmd->setAttribute(Command::CA_Hide);
309     viewsMenu->addAction(cmd, Core::Constants::G_DEFAULT_THREE);
310     cmd = ActionManager::registerAction(autoHideTitleBarsAction(),
311         "Debugger.Views.AutoHideTitleBars", debugcontext);
312     cmd->setAttribute(Command::CA_Hide);
313     viewsMenu->addAction(cmd, Core::Constants::G_DEFAULT_THREE);
314     cmd = ActionManager::registerAction(menuSeparator2(),
315         "Debugger.Views.Separator2", debugcontext);
316     cmd->setAttribute(Command::CA_Hide);
317     viewsMenu->addAction(cmd, Core::Constants::G_DEFAULT_THREE);
318     cmd = ActionManager::registerAction(resetLayoutAction(),
319         "Debugger.Views.ResetSimple", debugcontext);
320     cmd->setAttribute(Command::CA_Hide);
321     viewsMenu->addAction(cmd, Core::Constants::G_DEFAULT_THREE);
322 
323     // HACK: See QTCREATORBUG-23755. This ensures the showCentralWidget()
324     // call in restorePersistentSettings() below has something to operate on,
325     // and a plain QWidget is what we'll use anyway as central widget.
326     setCentralWidget(new QWidget);
327 
328     restorePersistentSettings();
329 }
330 
~DebuggerMainWindow()331 DebuggerMainWindow::~DebuggerMainWindow()
332 {
333     delete d;
334 }
335 
contextMenuEvent(QContextMenuEvent * ev)336 void DebuggerMainWindow::contextMenuEvent(QContextMenuEvent *ev)
337 {
338     ActionContainer *viewsMenu = ActionManager::actionContainer(Core::Constants::M_VIEW_VIEWS);
339     viewsMenu->menu()->exec(ev->globalPos());
340 }
341 
ensureMainWindowExists()342 void DebuggerMainWindow::ensureMainWindowExists()
343 {
344     if (!theMainWindow)
345         theMainWindow = new DebuggerMainWindow;
346 }
347 
doShutdown()348 void DebuggerMainWindow::doShutdown()
349 {
350     QTC_ASSERT(theMainWindow, return);
351 
352     theMainWindow->savePersistentSettings();
353 
354     delete theMainWindow;
355     theMainWindow = nullptr;
356 }
357 
registerPerspective(Perspective * perspective)358 void DebuggerMainWindowPrivate::registerPerspective(Perspective *perspective)
359 {
360     QString parentPerspective = perspective->d->m_parentPerspectiveId;
361     // Add only "main" perspectives to the chooser.
362     if (parentPerspective.isEmpty())
363         m_perspectiveChooser->addItem(perspective->d->m_name, perspective->d->m_id);
364     m_perspectives.append(perspective);
365 }
366 
destroyPerspective(Perspective * perspective)367 void DebuggerMainWindowPrivate::destroyPerspective(Perspective *perspective)
368 {
369     qCDebug(perspectivesLog) << "ABOUT TO DESTROY PERSPECTIVE" << perspective->id();
370 
371     const bool wasCurrent = perspective == m_currentPerspective;
372     if (wasCurrent) {
373         qCDebug(perspectivesLog) << "RAMPING IT DOWN FIRST AS IT WAS CURRENT" << perspective->id();
374         perspective->rampDownAsCurrent();
375     }
376 
377     m_perspectives.removeAll(perspective);
378 
379     // Dynamic perspectives are currently not visible in the chooser.
380     // This might change in the future, make sure we notice.
381     const int idx = indexInChooser(perspective);
382     if (idx != -1)
383         m_perspectiveChooser->removeItem(idx);
384 
385     for (DockOperation &op : perspective->d->m_dockOperations) {
386         if (op.commandId.isValid())
387             ActionManager::unregisterAction(op.toggleViewAction, op.commandId);
388         if (op.dock) {
389             theMainWindow->removeDockWidget(op.dock);
390             op.widget->setParent(nullptr); // Prevent deletion
391             op.dock->setParent(nullptr);
392             delete op.dock;
393             op.dock = nullptr;
394         }
395     }
396 
397     if (wasCurrent) {
398         if (Perspective *parent = Perspective::findPerspective(perspective->d->m_parentPerspectiveId)) {
399             qCDebug(perspectivesLog) << "RAMPING UP PARENT PERSPECTIVE" << parent->id();
400             parent->rampUpAsCurrent();
401         } else {
402             qCDebug(perspectivesLog) << "RAMPED DOWN WAS ACTION, BUT NO PARENT AVAILABLE. TAKE FIRST BEST";
403             if (QTC_GUARD(!m_perspectives.isEmpty()))
404                 m_perspectives.first()->rampUpAsCurrent();
405         }
406     }
407 
408     qCDebug(perspectivesLog) << "DESTROYED PERSPECTIVE" << perspective->id();
409 }
410 
showStatusMessage(const QString & message,int timeoutMS)411 void DebuggerMainWindow::showStatusMessage(const QString &message, int timeoutMS)
412 {
413     if (theMainWindow)
414         theMainWindow->d->m_statusLabel->showStatusMessage(message, timeoutMS);
415 }
416 
enterDebugMode()417 void DebuggerMainWindow::enterDebugMode()
418 {
419     theMainWindow->setDockActionsVisible(true);
420     QTC_CHECK(theMainWindow->d->m_currentPerspective == nullptr);
421     if (theMainWindow->d->needRestoreOnModeEnter)
422         theMainWindow->restorePersistentSettings();
423 
424     QSettings *settings = ICore::settings();
425     const QString lastPerspectiveId = settings->value(LAST_PERSPECTIVE_KEY).toString();
426     Perspective *perspective = Perspective::findPerspective(lastPerspectiveId);
427     // If we don't find a perspective with the stored name, pick any.
428     // This can happen e.g. when a plugin was disabled that provided
429     // the stored perspective, or when the save file was modified externally.
430     if (!perspective && !theMainWindow->d->m_perspectives.isEmpty())
431         perspective = theMainWindow->d->m_perspectives.first();
432 
433     // There's at least the debugger preset perspective that should be found above.
434     QTC_ASSERT(perspective, return);
435 
436     if (auto sub = Perspective::findPerspective(perspective->d->m_lastActiveSubPerspectiveId)) {
437         qCDebug(perspectivesLog) << "SWITCHING TO SUBPERSPECTIVE" << sub->d->m_id;
438         perspective = sub;
439     }
440 
441     perspective->rampUpAsCurrent();
442 }
443 
leaveDebugMode()444 void DebuggerMainWindow::leaveDebugMode()
445 {
446     theMainWindow->d->needRestoreOnModeEnter = true;
447     theMainWindow->savePersistentSettings();
448 
449     if (theMainWindow->d->m_currentPerspective)
450         theMainWindow->d->m_currentPerspective->rampDownAsCurrent();
451     QTC_CHECK(theMainWindow->d->m_currentPerspective == nullptr);
452 
453     theMainWindow->setDockActionsVisible(false);
454 
455     // Hide dock widgets manually in case they are floating.
456     for (QDockWidget *dockWidget : theMainWindow->dockWidgets()) {
457         if (dockWidget->isFloating())
458             dockWidget->setVisible(false);
459     }
460 }
461 
restorePersistentSettings()462 void DebuggerMainWindow::restorePersistentSettings()
463 {
464     qCDebug(perspectivesLog) << "RESTORE ALL PERSPECTIVES";
465     QSettings *settings = ICore::settings();
466     settings->beginGroup(MAINWINDOW_KEY);
467 
468     // state2 is current, state is kept for upgradeing from <=4.10
469     const QHash<QString, QVariant> states2 = settings->value(STATE_KEY2).toHash();
470     const QHash<QString, QVariant> states = settings->value(STATE_KEY).toHash();
471     d->m_lastTypePerspectiveStates.clear();
472     QSet<QString> keys = Utils::toSet(states2.keys());
473     keys.unite(Utils::toSet(states.keys()));
474     for (const QString &type : keys) {
475         PerspectiveState state = states2.value(type).value<PerspectiveState>();
476         if (state.mainWindowState.isEmpty())
477             state.mainWindowState = states.value(type).toByteArray();
478         QTC_ASSERT(!state.mainWindowState.isEmpty(), continue);
479         d->m_lastTypePerspectiveStates.insert(type, state);
480     }
481 
482     setAutoHideTitleBars(settings->value(AUTOHIDE_TITLEBARS_KEY, true).toBool());
483     showCentralWidget(settings->value(SHOW_CENTRALWIDGET_KEY, true).toBool());
484     d->m_persistentChangedDocks = Utils::toSet(settings->value(CHANGED_DOCK_KEY).toStringList());
485     settings->endGroup();
486 
487     qCDebug(perspectivesLog) << "LOADED CHANGED DOCKS:" << d->m_persistentChangedDocks;
488 }
489 
currentPerspective()490 Perspective *DebuggerMainWindow::currentPerspective()
491 {
492     return theMainWindow->d->m_currentPerspective;
493 }
494 
savePersistentSettings() const495 void DebuggerMainWindow::savePersistentSettings() const
496 {
497     // The current one might have active, non saved changes.
498     if (Perspective *perspective = d->m_currentPerspective)
499         perspective->d->saveLayout();
500 
501     QVariantHash states;
502     qCDebug(perspectivesLog) << "PERSPECTIVE TYPES: " << d->m_lastTypePerspectiveStates.keys();
503     for (auto it = d->m_lastTypePerspectiveStates.cbegin();
504          it != d->m_lastTypePerspectiveStates.cend(); ++it) {
505         const QString &type = it.key();
506         const PerspectiveState &state = it.value();
507         qCDebug(perspectivesLog) << "PERSPECTIVE TYPE " << type
508                                  << " HAS STATE: " << !state.mainWindowState.isEmpty();
509         QTC_ASSERT(!state.mainWindowState.isEmpty(), continue);
510         states.insert(type, QVariant::fromValue(state));
511     }
512 
513     QSettings *settings = ICore::settings();
514     settings->beginGroup(MAINWINDOW_KEY);
515     settings->setValue(CHANGED_DOCK_KEY, QStringList(Utils::toList(d->m_persistentChangedDocks)));
516     settings->setValue(STATE_KEY2, states);
517     settings->setValue(AUTOHIDE_TITLEBARS_KEY, autoHideTitleBars());
518     settings->setValue(SHOW_CENTRALWIDGET_KEY, isCentralWidgetShown());
519     settings->endGroup();
520 
521     qCDebug(perspectivesLog) << "SAVED CHANGED DOCKS:" << d->m_persistentChangedDocks;
522 }
523 
centralWidgetStack()524 QWidget *DebuggerMainWindow::centralWidgetStack()
525 {
526     return theMainWindow ? theMainWindow->d->m_centralWidgetStack : nullptr;
527 }
528 
addSubPerspectiveSwitcher(QWidget * widget)529 void DebuggerMainWindow::addSubPerspectiveSwitcher(QWidget *widget)
530 {
531     widget->setVisible(false);
532     widget->setProperty("panelwidget", true);
533     d->m_subPerspectiveSwitcherLayout->addWidget(widget);
534 }
535 
perspectiveMenu()536 QMenu *DebuggerMainWindow::perspectiveMenu()
537 {
538     return theMainWindow ? theMainWindow->d->m_perspectiveMenu : nullptr;
539 }
540 
instance()541 DebuggerMainWindow *DebuggerMainWindow::instance()
542 {
543     return theMainWindow;
544 }
545 
findPerspective(const QString & perspectiveId)546 Perspective *Perspective::findPerspective(const QString &perspectiveId)
547 {
548     QTC_ASSERT(theMainWindow, return nullptr);
549     return Utils::findOr(theMainWindow->d->m_perspectives, nullptr,
550                          [perspectiveId](Perspective *perspective) {
551         return perspective && perspective->d->m_id == perspectiveId;
552     });
553 }
554 
isCurrent() const555 bool Perspective::isCurrent() const
556 {
557     return theMainWindow->d->m_currentPerspective == this;
558 }
559 
dockForWidget(QWidget * widget) const560 QDockWidget *DebuggerMainWindowPrivate::dockForWidget(QWidget *widget) const
561 {
562     QTC_ASSERT(widget, return nullptr);
563 
564     for (QDockWidget *dock : q->dockWidgets()) {
565         if (dock->widget() == widget)
566             return dock;
567     }
568 
569     return nullptr;
570 }
571 
resetCurrentPerspective()572 void DebuggerMainWindowPrivate::resetCurrentPerspective()
573 {
574     QTC_ASSERT(m_currentPerspective, return);
575     cleanDocks();
576     setCentralWidget(m_currentPerspective->d->m_centralWidget);
577     q->showCentralWidget(true);
578     m_currentPerspective->d->resetPerspective();
579 }
580 
setCentralWidget(QWidget * widget)581 void DebuggerMainWindowPrivate::setCentralWidget(QWidget *widget)
582 {
583     if (widget) {
584         m_centralWidgetStack->addWidget(widget);
585         q->showCentralWidgetAction()->setText(widget->windowTitle());
586     } else {
587         m_centralWidgetStack->addWidget(m_editorPlaceHolder);
588         q->showCentralWidgetAction()->setText(DebuggerMainWindow::tr("Editor"));
589     }
590 }
591 
resetPerspective()592 void PerspectivePrivate::resetPerspective()
593 {
594     showInnerToolBar();
595 
596     for (DockOperation &op : m_dockOperations) {
597         if (!op.dock) {
598             qCDebug(perspectivesLog) << "RESET UNUSED " << op.name();
599         } else if (op.operationType == Perspective::Raise) {
600             QTC_ASSERT(op.dock, qCDebug(perspectivesLog) << op.name(); continue);
601             op.dock->raise();
602         } else {
603             op.setupLayout();
604             op.dock->setVisible(op.visibleByDefault);
605             theMainWindow->d->m_persistentChangedDocks.remove(op.name());
606             qCDebug(perspectivesLog) << "SETTING " << op.name()
607                                      << " TO ACTIVE: " << op.visibleByDefault;
608         }
609     }
610 }
611 
setupLayout()612 void DockOperation::setupLayout()
613 {
614     QTC_ASSERT(widget, return);
615     QTC_ASSERT(operationType != Perspective::Raise, return);
616     QTC_ASSERT(dock, return);
617 
618     QDockWidget *anchor = nullptr;
619     if (anchorWidget)
620         anchor = theMainWindow->d->dockForWidget(anchorWidget);
621     else if (area == Qt::BottomDockWidgetArea)
622         anchor = theMainWindow->d->m_toolBarDock;
623 
624     if (anchor) {
625         switch (operationType) {
626         case Perspective::AddToTab:
627             theMainWindow->tabifyDockWidget(anchor, dock);
628             break;
629         case Perspective::SplitHorizontal:
630             theMainWindow->splitDockWidget(anchor, dock, Qt::Horizontal);
631             break;
632         case Perspective::SplitVertical:
633             theMainWindow->splitDockWidget(anchor, dock, Qt::Vertical);
634             break;
635         default:
636             break;
637         }
638     } else {
639         theMainWindow->addDockWidget(area, dock);
640     }
641 }
642 
ensureDockExists()643 void DockOperation::ensureDockExists()
644 {
645     if (dock)
646         return;
647 
648     dock = theMainWindow->addDockForWidget(widget);
649 
650     if (theMainWindow->restoreDockWidget(dock)) {
651         qCDebug(perspectivesLog) << "RESTORED SUCCESSFULLY" << commandId;
652     } else {
653         qCDebug(perspectivesLog) << "COULD NOT RESTORE" << commandId;
654         setupLayout();
655     }
656 
657     toggleViewAction->setAction(dock->toggleViewAction());
658 
659     QObject::connect(dock->toggleViewAction(), &QAction::triggered,
660                      dock->toggleViewAction(), [this] { recordVisibility(); });
661 }
662 
changedByUser() const663 bool DockOperation::changedByUser() const
664 {
665     return theMainWindow->d->m_persistentChangedDocks.contains(name());
666 }
667 
recordVisibility()668 void DockOperation::recordVisibility()
669 {
670     if (operationType != Perspective::Raise) {
671         if (toggleViewAction->isChecked() == visibleByDefault)
672             theMainWindow->d->m_persistentChangedDocks.remove(name());
673         else
674             theMainWindow->d->m_persistentChangedDocks.insert(name());
675     }
676     qCDebug(perspectivesLog) << "RECORDING DOCK VISIBILITY " << name()
677                              << toggleViewAction->isChecked()
678                              << theMainWindow->d->m_persistentChangedDocks;
679 }
680 
indexInChooser(Perspective * perspective) const681 int DebuggerMainWindowPrivate::indexInChooser(Perspective *perspective) const
682 {
683     return perspective ? m_perspectiveChooser->findData(perspective->d->m_id) : -1;
684 }
685 
updatePerspectiveChooserWidth()686 void DebuggerMainWindowPrivate::updatePerspectiveChooserWidth()
687 {
688     Perspective *perspective = m_currentPerspective;
689     int index = indexInChooser(m_currentPerspective);
690     if (index == -1) {
691         perspective = Perspective::findPerspective(m_currentPerspective->d->m_parentPerspectiveId);
692         if (perspective)
693             index = indexInChooser(perspective);
694     }
695 
696     if (index != -1) {
697         m_perspectiveChooser->setCurrentIndex(index);
698 
699         const int contentWidth =
700             m_perspectiveChooser->fontMetrics().horizontalAdvance(perspective->d->m_name);
701         QStyleOptionComboBox option;
702         option.initFrom(m_perspectiveChooser);
703         const QSize sz(contentWidth, 1);
704         const int width = m_perspectiveChooser->style()->sizeFromContents(
705                     QStyle::CT_ComboBox, &option, sz).width();
706         m_perspectiveChooser->setFixedWidth(width);
707     }
708 }
709 
cleanDocks()710 void DebuggerMainWindowPrivate::cleanDocks()
711 {
712     m_statusLabel->clear();
713 
714     for (QDockWidget *dock : q->dockWidgets()) {
715         if (dock != m_toolBarDock)
716             dock->setVisible(false);
717     }
718 }
719 
depopulatePerspective()720 void PerspectivePrivate::depopulatePerspective()
721 {
722     ICore::removeAdditionalContext(context());
723     theMainWindow->d->m_centralWidgetStack
724             ->removeWidget(m_centralWidget ? m_centralWidget : theMainWindow->d->m_editorPlaceHolder);
725 
726     theMainWindow->d->m_statusLabel->clear();
727 
728     qCDebug(perspectivesLog) << "DEPOPULATE PERSPECTIVE" << m_id;
729     for (QDockWidget *dock : theMainWindow->dockWidgets()) {
730         if (dock != theMainWindow->d->m_toolBarDock)
731             dock->setVisible(false);
732     }
733 
734     hideInnerToolBar();
735 }
736 
saveAsLastUsedPerspective()737 void PerspectivePrivate::saveAsLastUsedPerspective()
738 {
739     if (Perspective *parent = Perspective::findPerspective(m_parentPerspectiveId))
740         parent->d->m_lastActiveSubPerspectiveId = m_id;
741     else
742         m_lastActiveSubPerspectiveId.clear();
743 
744     const QString &lastKey = m_parentPerspectiveId.isEmpty() ? m_id : m_parentPerspectiveId;
745     qCDebug(perspectivesLog) << "SAVE AS LAST USED PERSPECTIVE" << lastKey;
746     ICore::settings()->setValue(LAST_PERSPECTIVE_KEY, lastKey);
747 }
748 
populatePerspective()749 void PerspectivePrivate::populatePerspective()
750 {
751     showInnerToolBar();
752 
753     if (m_centralWidget) {
754         theMainWindow->d->m_centralWidgetStack->addWidget(m_centralWidget);
755         theMainWindow->showCentralWidgetAction()->setText(m_centralWidget->windowTitle());
756     } else {
757         theMainWindow->d->m_centralWidgetStack->addWidget(theMainWindow->d->m_editorPlaceHolder);
758         theMainWindow->showCentralWidgetAction()->setText(DebuggerMainWindow::tr("Editor"));
759     }
760 
761     ICore::addAdditionalContext(context());
762 
763     restoreLayout();
764 }
765 
766 // Perspective
767 
Perspective(const QString & id,const QString & name,const QString & parentPerspectiveId,const QString & settingsId)768 Perspective::Perspective(const QString &id, const QString &name,
769                          const QString &parentPerspectiveId,
770                          const QString &settingsId)
771     : d(new PerspectivePrivate)
772 {
773     d->m_id = id;
774     d->m_name = name;
775     d->m_parentPerspectiveId = parentPerspectiveId;
776     d->m_settingsId = settingsId;
777 
778     DebuggerMainWindow::ensureMainWindowExists();
779     theMainWindow->d->registerPerspective(this);
780 
781     d->m_innerToolBar = new QWidget;
782     d->m_innerToolBar->setVisible(false);
783     theMainWindow->d->m_innerToolsLayout->addWidget(d->m_innerToolBar);
784 
785     d->m_innerToolBarLayout = new QHBoxLayout(d->m_innerToolBar);
786     d->m_innerToolBarLayout->setContentsMargins(0, 0, 0, 0);
787     d->m_innerToolBarLayout->setSpacing(0);
788 }
789 
~Perspective()790 Perspective::~Perspective()
791 {
792     if (theMainWindow) {
793         delete d->m_innerToolBar;
794         d->m_innerToolBar = nullptr;
795     }
796     delete d;
797 }
798 
setCentralWidget(QWidget * centralWidget)799 void Perspective::setCentralWidget(QWidget *centralWidget)
800 {
801     QTC_ASSERT(d->m_centralWidget == nullptr, return);
802     d->m_centralWidget = centralWidget;
803 }
804 
id() const805 QString Perspective::id() const
806 {
807     return d->m_id;
808 }
809 
name() const810 QString Perspective::name() const
811 {
812     return d->m_name;
813 }
814 
setAboutToActivateCallback(const Perspective::Callback & cb)815 void Perspective::setAboutToActivateCallback(const Perspective::Callback &cb)
816 {
817     d->m_aboutToActivateCallback = cb;
818 }
819 
setEnabled(bool enabled)820 void Perspective::setEnabled(bool enabled)
821 {
822     QTC_ASSERT(theMainWindow, return);
823     const int index = theMainWindow->d->indexInChooser(this);
824     QTC_ASSERT(index != -1, return);
825     auto model = qobject_cast<QStandardItemModel*>(theMainWindow->d->m_perspectiveChooser->model());
826     QTC_ASSERT(model, return);
827     QStandardItem *item = model->item(index, 0);
828     item->setFlags(enabled ? item->flags() | Qt::ItemIsEnabled : item->flags() & ~Qt::ItemIsEnabled );
829 }
830 
setupToolButton(QAction * action)831 QToolButton *PerspectivePrivate::setupToolButton(QAction *action)
832 {
833     QTC_ASSERT(action, return nullptr);
834     auto toolButton = new QToolButton(m_innerToolBar);
835     toolButton->setProperty("panelwidget", true);
836     toolButton->setDefaultAction(action);
837     m_innerToolBarLayout->addWidget(toolButton);
838     return toolButton;
839 }
840 
addToolBarAction(QAction * action)841 void Perspective::addToolBarAction(QAction *action)
842 {
843     QTC_ASSERT(action, return);
844     d->setupToolButton(action);
845 }
846 
addToolBarAction(OptionalAction * action)847 void Perspective::addToolBarAction(OptionalAction *action)
848 {
849     QTC_ASSERT(action, return);
850     action->m_toolButton = d->setupToolButton(action);
851 }
852 
addToolBarWidget(QWidget * widget)853 void Perspective::addToolBarWidget(QWidget *widget)
854 {
855     QTC_ASSERT(widget, return);
856     // QStyle::polish is called before it is added to the toolbar, explicitly make it a panel widget
857     widget->setProperty("panelwidget", true);
858     widget->setParent(d->m_innerToolBar);
859     d->m_innerToolBarLayout->addWidget(widget);
860 }
861 
useSubPerspectiveSwitcher(QWidget * widget)862 void Perspective::useSubPerspectiveSwitcher(QWidget *widget)
863 {
864     d->m_switcher = widget;
865 }
866 
addToolbarSeparator()867 void Perspective::addToolbarSeparator()
868 {
869     d->m_innerToolBarLayout->addWidget(new StyledSeparator(d->m_innerToolBar));
870 }
871 
registerNextPrevShortcuts(QAction * next,QAction * prev)872 void Perspective::registerNextPrevShortcuts(QAction *next, QAction *prev)
873 {
874     static const char nextId[] = "Analyzer.nextitem";
875     static const char prevId[] = "Analyzer.previtem";
876 
877     next->setText(DebuggerMainWindow::tr("Next Item"));
878     Command * const nextCmd = ActionManager::registerAction(next, nextId,
879                                                             Context(Id::fromString(id())));
880     nextCmd->augmentActionWithShortcutToolTip(next);
881     prev->setText(DebuggerMainWindow::tr("Previous Item"));
882     Command * const prevCmd = ActionManager::registerAction(prev, prevId,
883                                                             Context(Id::fromString(id())));
884     prevCmd->augmentActionWithShortcutToolTip(prev);
885 }
886 
centralWidget() const887 QWidget *Perspective::centralWidget() const
888 {
889     return d->m_centralWidget;
890 }
891 
context() const892 Context PerspectivePrivate::context() const
893 {
894     return Context(Id::fromName(m_id.toUtf8()));
895 }
896 
~PerspectivePrivate()897 PerspectivePrivate::~PerspectivePrivate()
898 {
899     for (const DockOperation &op : qAsConst(m_dockOperations))
900         delete op.widget;
901 }
902 
showInnerToolBar()903 void PerspectivePrivate::showInnerToolBar()
904 {
905     m_innerToolBar->setVisible(true);
906     if (m_switcher)
907         m_switcher->setVisible(true);
908 }
909 
hideInnerToolBar()910 void PerspectivePrivate::hideInnerToolBar()
911 {
912     QTC_ASSERT(m_innerToolBar, return);
913     m_innerToolBar->setVisible(false);
914     if (m_switcher)
915         m_switcher->setVisible(false);
916 }
917 
addWindow(QWidget * widget,Perspective::OperationType type,QWidget * anchorWidget,bool visibleByDefault,Qt::DockWidgetArea area)918 void Perspective::addWindow(QWidget *widget,
919                             Perspective::OperationType type,
920                             QWidget *anchorWidget,
921                             bool visibleByDefault,
922                             Qt::DockWidgetArea area)
923 {
924     QTC_ASSERT(widget, return);
925     DockOperation op;
926     op.widget = widget;
927     op.operationType = type;
928     op.anchorWidget = anchorWidget;
929     op.visibleByDefault = visibleByDefault;
930     op.area = area;
931 
932     if (op.operationType != Perspective::Raise) {
933         qCDebug(perspectivesLog) << "CREATING DOCK " << op.name()
934                                  << "DEFAULT: " << op.visibleByDefault;
935         op.commandId = Id("Dock.").withSuffix(op.name());
936 
937         op.toggleViewAction = new ProxyAction(this);
938         op.toggleViewAction->setText(widget->windowTitle());
939 
940         Command *cmd = ActionManager::registerAction(op.toggleViewAction, op.commandId, d->context());
941         cmd->setAttribute(Command::CA_Hide);
942         ActionManager::actionContainer(Core::Constants::M_VIEW_VIEWS)->addAction(cmd);
943     }
944 
945     d->m_dockOperations.append(op);
946 }
947 
destroy()948 void Perspective::destroy()
949 {
950     theMainWindow->d->destroyPerspective(this);
951 }
952 
rampDownAsCurrent()953 void Perspective::rampDownAsCurrent()
954 {
955     QTC_ASSERT(this == theMainWindow->d->m_currentPerspective, return);
956     d->saveLayout();
957     d->depopulatePerspective();
958     theMainWindow->d->setCurrentPerspective(nullptr);
959 
960     Debugger::Internal::EngineManager::updatePerspectives();
961 }
962 
rampUpAsCurrent()963 void Perspective::rampUpAsCurrent()
964 {
965     if (d->m_aboutToActivateCallback)
966         d->m_aboutToActivateCallback();
967 
968     QTC_ASSERT(theMainWindow->d->m_currentPerspective == nullptr, return);
969     theMainWindow->d->setCurrentPerspective(this);
970     QTC_ASSERT(theMainWindow->d->m_currentPerspective == this, return);
971 
972     theMainWindow->showCentralWidget(true);
973 
974     d->populatePerspective();
975 
976     theMainWindow->d->updatePerspectiveChooserWidth();
977 
978     d->saveAsLastUsedPerspective();
979 
980     Debugger::Internal::EngineManager::updatePerspectives();
981 }
982 
select()983 void Perspective::select()
984 {
985     Debugger::Internal::EngineManager::activateDebugMode();
986 
987     if (theMainWindow->d->m_currentPerspective == this)
988         return;
989 
990     if (theMainWindow->d->m_currentPerspective)
991         theMainWindow->d->m_currentPerspective->rampDownAsCurrent();
992     QTC_CHECK(theMainWindow->d->m_currentPerspective == nullptr);
993 
994     rampUpAsCurrent();
995 }
996 
restoreLayout()997 void PerspectivePrivate::restoreLayout()
998 {
999     qCDebug(perspectivesLog) << "RESTORE LAYOUT FOR " << m_id << settingsId();
1000     PerspectiveState state = theMainWindow->d->m_lastPerspectiveStates.value(m_id);
1001     if (state.mainWindowState.isEmpty()) {
1002         qCDebug(perspectivesLog) << "PERSPECTIVE STATE NOT AVAILABLE BY FULL ID.";
1003         state = theMainWindow->d->m_lastTypePerspectiveStates.value(settingsId());
1004         if (state.mainWindowState.isEmpty()) {
1005             qCDebug(perspectivesLog) << "PERSPECTIVE STATE NOT AVAILABLE BY PERSPECTIVE TYPE";
1006         } else {
1007             qCDebug(perspectivesLog) << "PERSPECTIVE STATE AVAILABLE BY PERSPECTIVE TYPE.";
1008         }
1009     } else {
1010         qCDebug(perspectivesLog) << "PERSPECTIVE STATE AVAILABLE BY FULL ID.";
1011     }
1012 
1013     // The order is important here: While QMainWindow can restore layouts with
1014     // not-existing docks (some placeholders are used internally), later
1015     // replacements with restoreDockWidget(dock) trigger a re-layout, resulting
1016     // in different sizes. So make sure all docks exist first before restoring state.
1017 
1018     qCDebug(perspectivesLog) << "PERSPECTIVE" << m_id << "RESTORING LAYOUT FROM " << settingsId();
1019     for (DockOperation &op : m_dockOperations) {
1020         if (op.operationType != Perspective::Raise) {
1021             op.ensureDockExists();
1022             QTC_ASSERT(op.dock, continue);
1023             const bool active = op.visibleByDefault ^ op.changedByUser();
1024             op.dock->setVisible(active);
1025             qCDebug(perspectivesLog) << "RESTORE DOCK " << op.name() << "ACTIVE: " << active
1026                                      << (active == op.visibleByDefault ? "DEFAULT USER" : "*** NON-DEFAULT USER");
1027         }
1028     }
1029 
1030     if (state.mainWindowState.isEmpty()) {
1031         qCDebug(perspectivesLog) << "PERSPECTIVE " << m_id << "RESTORE NOT POSSIBLE, NO STORED STATE";
1032     } else {
1033         bool result = theMainWindow->restoreState(state.mainWindowState);
1034         qCDebug(perspectivesLog) << "PERSPECTIVE " << m_id << "RESTORED, SUCCESS: " << result;
1035     }
1036 
1037     for (DockOperation &op : m_dockOperations) {
1038         if (op.operationType != Perspective::Raise) {
1039             QTC_ASSERT(op.dock, continue);
1040             for (QTreeView *tv : op.dock->findChildren<QTreeView *>()) {
1041                 if (tv->property(PerspectiveState::savesHeaderKey()).toBool()) {
1042                     const QByteArray s = state.headerViewStates.value(op.name()).toByteArray();
1043                     if (!s.isEmpty())
1044                         tv->header()->restoreState(s);
1045                 }
1046             }
1047         }
1048     }
1049 }
1050 
saveLayout()1051 void PerspectivePrivate::saveLayout()
1052 {
1053     qCDebug(perspectivesLog) << "PERSPECTIVE" << m_id << "SAVE LAYOUT TO " << settingsId();
1054     PerspectiveState state;
1055     state.mainWindowState = theMainWindow->saveState();
1056     for (DockOperation &op : m_dockOperations) {
1057         if (op.operationType != Perspective::Raise) {
1058             QTC_ASSERT(op.dock, continue);
1059             for (QTreeView *tv : op.dock->findChildren<QTreeView *>()) {
1060                 if (tv->property(PerspectiveState::savesHeaderKey()).toBool()) {
1061                     if (QHeaderView *hv = tv->header())
1062                         state.headerViewStates.insert(op.name(), hv->saveState());
1063                 }
1064             }
1065         }
1066     }
1067     theMainWindow->d->m_lastPerspectiveStates.insert(m_id, state);
1068     theMainWindow->d->m_lastTypePerspectiveStates.insert(settingsId(), state);
1069 }
1070 
settingsId() const1071 QString PerspectivePrivate::settingsId() const
1072 {
1073     return m_settingsId.isEmpty() ? m_id : m_settingsId;
1074 }
1075 
1076 // ToolbarAction
1077 
OptionalAction(const QString & text)1078 OptionalAction::OptionalAction(const QString &text)
1079     : QAction(text)
1080 {
1081 }
1082 
~OptionalAction()1083 OptionalAction::~OptionalAction()
1084 {
1085     delete m_toolButton;
1086 }
1087 
setVisible(bool on)1088 void OptionalAction::setVisible(bool on)
1089 {
1090     QAction::setVisible(on);
1091     if (m_toolButton)
1092         m_toolButton->setVisible(on);
1093 }
1094 
setToolButtonStyle(Qt::ToolButtonStyle style)1095 void OptionalAction::setToolButtonStyle(Qt::ToolButtonStyle style)
1096 {
1097     QTC_ASSERT(m_toolButton, return);
1098     m_toolButton->setToolButtonStyle(style);
1099 }
1100 
savesHeaderKey()1101 const char *PerspectiveState::savesHeaderKey()
1102 {
1103     return "SavesHeader";
1104 }
1105 
1106 } // Utils
1107