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