1 /*
2     This file is part of KCachegrind.
3 
4     SPDX-FileCopyrightText: 2002-2016 Josef Weidendorfer <Josef.Weidendorfer@gmx.de>
5 
6     SPDX-License-Identifier: GPL-2.0-only
7 */
8 
9 /*
10  * QCachegrind top level window
11  */
12 
13 #define TRACE_UPDATES 0
14 
15 #include "qcgtoplevel.h"
16 
17 #include <stdlib.h> // for system()
18 
19 #include <QApplication>
20 #include <QDebug>
21 #include <QDockWidget>
22 #include <QTimer>
23 #include <QByteArray>
24 #include <QLabel>
25 #include <QMenuBar>
26 #include <QProgressBar>
27 #include <QFile>
28 #include <QFileDialog>
29 #include <QEventLoop>
30 #include <QToolBar>
31 #include <QComboBox>
32 #include <QMessageBox>
33 #include <QStatusBar>
34 #include <QWhatsThis>
35 #include <QWindow>
36 
37 #ifdef QT_DBUS_SUPPORT
38 #include <QDBusConnection>
39 #endif
40 
41 #include "partselection.h"
42 #include "functionselection.h"
43 #include "stackselection.h"
44 #include "stackbrowser.h"
45 #include "tracedata.h"
46 #include "config.h"
47 #include "globalguiconfig.h"
48 #include "multiview.h"
49 #include "callgraphview.h"
50 #include "configdialog.h"
51 
QCGTopLevel()52 QCGTopLevel::QCGTopLevel()
53 {
54 #ifdef QT_DBUS_SUPPORT
55     QDBusConnection con = QDBusConnection::sessionBus();
56     con.registerObject("/QCachegrind", this,
57                        QDBusConnection::ExportScriptableSlots);
58 #endif
59 
60     _progressBar = nullptr;
61     _statusbar = statusBar();
62     _statusLabel = new QLabel(_statusbar);
63     _statusbar->addWidget(_statusLabel, 1);
64 
65     _layoutCount = 1;
66     _layoutCurrent = 0;
67 
68     resetState();
69 
70     GlobalGUIConfig::config()->readOptions();
71 
72     createActions();
73     createDocks();
74     createToolbar();
75     createMenu();
76 
77     _multiView = new MultiView(this, this);
78     _multiView->setObjectName(QStringLiteral("MultiView"));
79     setCentralWidget(_multiView);
80 
81     // restore current state settings (not configuration options)
82     restoreCurrentState(QString());
83 
84     // restore docks & toolbars from config
85     QByteArray state, geometry;
86     ConfigGroup* topConfig = ConfigStorage::group(QStringLiteral("TopWindow"));
87     _forcePartDock = topConfig->value(QStringLiteral("ForcePartDockVisible"), false).toBool();
88     state = topConfig->value(QStringLiteral("State"), QByteArray()).toByteArray();
89     geometry = topConfig->value(QStringLiteral("Geometry"), QByteArray()).toByteArray();
90     delete topConfig;
91 
92     if (!geometry.isEmpty())
93         restoreGeometry(geometry);
94     if (!state.isEmpty())
95         restoreState(state);
96 
97     setWindowIcon(QIcon(QStringLiteral(":/app.png")));
98     setAttribute(Qt::WA_DeleteOnClose);
99 }
100 
~QCGTopLevel()101 QCGTopLevel::~QCGTopLevel()
102 {
103 #ifdef Q_OS_MAC
104     // hacky way to reinstall the dock, since each toplevel has a dock menu,
105     // we don't do any global menu stuff on mac beyond what qt gives us, and
106     // when the last window that installed the dock menu dies, it goes away.
107     // reinstall it into another window if we can.
108     auto windowList = QApplication::topLevelWidgets();
109     for (int i = 0; i < windowList.size(); i++) {
110         QWidget *topLevelRaw = windowList[i];
111         if (QCGTopLevel *topLevel = qobject_cast<QCGTopLevel*>(topLevelRaw)) {
112             if (topLevel != this) {
113                 topLevel->reinstallMacDock();
114                 break;
115             }
116         }
117     }
118 #endif
119     delete _data;
120 }
121 
reinstallMacDock()122 void QCGTopLevel::reinstallMacDock()
123 {
124 #ifdef Q_OS_MAC
125     if (macDockMenu != nullptr) {
126         this->macDockMenu->setAsDockMenu();
127     }
128 #endif
129 }
130 
131 // reset the visualization state, e.g. before loading new data
resetState()132 void QCGTopLevel::resetState()
133 {
134     _activeParts.clear();
135     _hiddenParts.clear();
136 
137     _data = nullptr;
138     _function = nullptr;
139     _eventType = nullptr;
140     _eventType2 = nullptr;
141     _groupType = ProfileContext::InvalidType;
142     _group = nullptr;
143 
144     // for delayed slots
145     _traceItemDelayed = nullptr;
146     _eventTypeDelayed = nullptr;
147     _eventType2Delayed = nullptr;
148     _groupTypeDelayed = ProfileContext::InvalidType;
149     _groupDelayed = nullptr;
150     _directionDelayed = TraceItemView::None;
151     _lastSender = nullptr;
152 }
153 
154 
155 /**
156  * This saves the current state of the main window and
157  * sub widgets.
158  *
159  * No positions are saved. These is done automatically for
160  * KToolbar, and manually in queryExit() for QT docks.
161  */
saveCurrentState(const QString & postfix)162 void QCGTopLevel::saveCurrentState(const QString& postfix)
163 {
164     QString eventType, eventType2;
165     if (_eventType) eventType = _eventType->name();
166     if (_eventType2) eventType2 = _eventType2->name();
167 
168     ConfigGroup* stateConfig = ConfigStorage::group(QLatin1String("CurrentState") + postfix);
169     stateConfig->setValue(QStringLiteral("EventType"), eventType);
170     stateConfig->setValue(QStringLiteral("EventType2"), eventType2);
171     stateConfig->setValue(QStringLiteral("GroupType"), ProfileContext::typeName(_groupType));
172     delete stateConfig;
173 
174     _partSelection->saveOptions(QStringLiteral("PartOverview"), postfix);
175     _multiView->saveLayout(QStringLiteral("MainView"), postfix);
176     _multiView->saveOptions(QStringLiteral("MainView"), postfix);
177 }
178 
179 /**
180  * This function is called when a trace is closed.
181  * Save browsing position for later restoring
182  */
saveTraceSettings()183 void QCGTopLevel::saveTraceSettings()
184 {
185     QString key = traceKey();
186 
187     ConfigGroup* lConfig = ConfigStorage::group(QStringLiteral("Layouts"));
188     lConfig->setValue(QStringLiteral("Count%1").arg(key), _layoutCount);
189     lConfig->setValue(QStringLiteral("Current%1").arg(key), _layoutCurrent);
190     delete lConfig;
191 
192     ConfigGroup* pConfig = ConfigStorage::group(QStringLiteral("TracePositions"));
193     QString eventType, eventType2;
194     if (_eventType) eventType = _eventType->name();
195     if (_eventType2) eventType2 = _eventType2->name();
196     pConfig->setValue(QStringLiteral("EventType%1").arg(key), eventType);
197     pConfig->setValue(QStringLiteral("EventType2%1").arg(key), eventType2);
198     if (_groupType != ProfileContext::InvalidType)
199         pConfig->setValue(QStringLiteral("GroupType%1").arg(key),
200                           ProfileContext::typeName(_groupType));
201 
202     if (_data) {
203         if (_group)
204             pConfig->setValue(QStringLiteral("Group%1").arg(key), _group->name());
205         saveCurrentState(key);
206     }
207     delete pConfig;
208 }
209 
210 /**
211  * This restores the current visualization state of the main window and
212  * of the profile views.
213  */
restoreCurrentState(const QString & postfix)214 void QCGTopLevel::restoreCurrentState(const QString& postfix)
215 {
216     _partSelection->restoreOptions(QStringLiteral("PartOverview"), postfix);
217     _multiView->restoreLayout(QStringLiteral("MainView"), postfix);
218     _multiView->restoreOptions(QStringLiteral("MainView"), postfix);
219 
220     _splittedToggleAction->setChecked(_multiView->childCount()>1);
221     _splitDirectionToggleAction->setEnabled(_multiView->childCount()>1);
222     _splitDirectionToggleAction->setChecked(_multiView->orientation() ==
223                                             Qt::Horizontal);
224 }
225 
sidebarMenuAboutToShow()226 void QCGTopLevel::sidebarMenuAboutToShow()
227 {
228     QAction* action;
229     QMenu *popup = _sidebarMenuAction->menu();
230 
231     popup->clear();
232 
233     action = popup->addAction(tr("Parts Overview"));
234     action->setCheckable(true);
235     action->setChecked(_partDock->isVisible());
236     connect(action, &QAction::triggered, this, &QCGTopLevel::togglePartDock);
237 
238     action = popup->addAction(tr("Top Cost Call Stack"));
239     action->setCheckable(true);
240     action->setChecked(_stackDock->isVisible());
241     connect(action, &QAction::triggered, this, &QCGTopLevel::toggleStackDock);
242 
243     action = popup->addAction(tr("Flat Profile"));
244     action->setCheckable(true);
245     action->setChecked(_functionDock->isVisible());
246     connect(action, &QAction::triggered, this, &QCGTopLevel::toggleFunctionDock);
247 }
248 
recentFilesMenuAboutToShow()249 void QCGTopLevel::recentFilesMenuAboutToShow()
250 {
251     QStringList recentFiles;
252     QMenu *popup = _recentFilesMenuAction->menu();
253 
254     popup->clear();
255 
256     ConfigGroup* generalConfig = ConfigStorage::group(QStringLiteral("GeneralSettings"));
257     recentFiles = generalConfig->value(QStringLiteral("RecentFiles"),
258                                        QStringList()).toStringList();
259     delete generalConfig;
260 
261     if (recentFiles.isEmpty())
262         popup->addAction(tr("(No recent files)"));
263     else {
264         foreach(const QString& file, recentFiles) {
265             // paths shown to user should use OS-native separators
266             popup->addAction(QDir::toNativeSeparators(file));
267         }
268     }
269 }
270 
recentFilesTriggered(QAction * action)271 void QCGTopLevel::recentFilesTriggered(QAction* action)
272 {
273     if (action)
274         load(QStringList(QDir::fromNativeSeparators(action->text())));
275 }
276 
primaryAboutToShow()277 void QCGTopLevel::primaryAboutToShow()
278 {
279     updateEventTypeMenu(_primaryMenuAction->menu(), false);
280 }
281 
secondaryAboutToShow()282 void QCGTopLevel::secondaryAboutToShow()
283 {
284     updateEventTypeMenu(_secondaryMenuAction->menu(), true);
285 }
286 
groupingAboutToShow()287 void QCGTopLevel::groupingAboutToShow()
288 {
289     if (!_functionSelection) return;
290     _functionSelection->updateGroupingMenu(_groupingMenuAction->menu());
291 }
292 
293 
294 
createDocks()295 void QCGTopLevel::createDocks()
296 {
297     // part visualization/selection side bar
298     _partDock = new QDockWidget(this);
299     _partDock->setObjectName(QStringLiteral("part-dock"));
300     _partDock->setWindowTitle(tr("Parts Overview"));
301     _partSelection = new PartSelection(this, _partDock);
302     _partDock->setWidget(_partSelection);
303 
304     connect(_partSelection, &PartSelection::partsHideSelected,
305             this, &QCGTopLevel::partsHideSelectedSlotDelayed);
306     connect(_partSelection, &PartSelection::partsUnhideAll,
307             this, &QCGTopLevel::partsUnhideAllSlotDelayed);
308 
309     // stack selection side bar
310     _stackDock = new QDockWidget(this);
311     _stackDock->setObjectName(QStringLiteral("stack-dock"));
312     _stackSelection = new StackSelection(_stackDock);
313     _stackDock->setWidget(_stackSelection);
314     _stackDock->setWindowTitle(tr("Top Cost Call Stack"));
315     _stackSelection->setWhatsThis( tr(
316                                        "<b>The Top Cost Call Stack</b>"
317                                        "<p>This is a purely fictional 'most probable' call stack. "
318                                        "It is built up by starting with the current selected "
319                                        "function and adds the callers/callees with highest cost "
320                                        "at the top and to bottom.</p>"
321                                        "<p>The <b>Cost</b> and <b>Calls</b> columns show the "
322                                        "cost used for all calls from the function in the line "
323                                        "above.</p>"));
324     connect(_stackSelection, SIGNAL(functionSelected(CostItem*)),
325             this, SLOT(setTraceItemDelayed(CostItem*)));
326     // actions are already created
327     connect(_upAction, &QAction::triggered,
328             _stackSelection, &StackSelection::browserUp );
329     connect(_backAction, &QAction::triggered,
330             _stackSelection, &StackSelection::browserBack );
331     connect(_forwardAction, &QAction::triggered,
332             _stackSelection, &StackSelection::browserForward);
333 
334     // flat function profile side bar
335     _functionDock = new QDockWidget(this);
336     _functionDock->setObjectName(QStringLiteral("function-dock"));
337     _functionDock->setWindowTitle(tr("Flat Profile"));
338     _functionSelection = new FunctionSelection(this, _functionDock);
339     _functionDock->setWidget(_functionSelection);
340     // functionDock needs call to updateView() when getting visible
341     connect(_functionDock, &QDockWidget::visibilityChanged,
342             this, &QCGTopLevel::functionVisibilityChanged);
343 
344     // defaults (later to be adjusted from stored state in config)
345     addDockWidget(Qt::LeftDockWidgetArea, _partDock );
346     addDockWidget(Qt::LeftDockWidgetArea, _stackDock );
347     addDockWidget(Qt::LeftDockWidgetArea, _functionDock );
348     _stackDock->hide();
349     _partDock->hide();
350 }
351 
352 
353 
354 
createActions()355 void QCGTopLevel::createActions()
356 {
357     QString hint;
358     QIcon icon;
359 
360     // file menu actions
361     _newAction = new QAction(tr("&New"), this);
362     _newAction->setShortcuts(QKeySequence::New);
363     _newAction->setStatusTip(tr("Open new empty window"));
364     connect(_newAction, &QAction::triggered, this, &QCGTopLevel::newWindow);
365 
366     icon = QApplication::style()->standardIcon(QStyle::SP_DialogOpenButton);
367     _openAction = new QAction(icon, tr("&Open..."), this);
368     _openAction->setShortcuts(QKeySequence::Open);
369     _openAction->setStatusTip(tr("Open profile data file"));
370     connect(_openAction, SIGNAL(triggered()), this, SLOT(load()));
371 
372     _closeAction = new QAction(tr("&Close"), this);
373     _closeAction->setShortcuts(QKeySequence::Close);
374     _closeAction->setStatusTip(tr("Close the current window"));
375     connect(_closeAction, SIGNAL(triggered()), this, SLOT(close()));
376 
377     _addAction = new QAction(tr( "&Add..." ), this);
378     _addAction->setStatusTip(tr("Add profile data to current window"));
379     connect(_addAction, SIGNAL(triggered(bool)), SLOT(add()));
380 
381     _exportAction = new QAction(tr("Export Graph"), this);
382     _exportAction->setStatusTip(tr("Generate GraphViz file 'callgraph.dot'"));
383     connect(_exportAction, &QAction::triggered, this, &QCGTopLevel::exportGraph);
384 
385     _recentFilesMenuAction = new QAction(tr("Open &Recent"), this);
386     _recentFilesMenuAction->setMenu(new QMenu(this));
387     connect(_recentFilesMenuAction->menu(), &QMenu::aboutToShow,
388             this, &QCGTopLevel::recentFilesMenuAboutToShow);
389     connect(_recentFilesMenuAction->menu(), &QMenu::triggered,
390             this, &QCGTopLevel::recentFilesTriggered);
391 
392     _exitAction = new QAction(tr("E&xit"), this);
393     _exitAction->setMenuRole(QAction::QuitRole);
394     _exitAction->setShortcut(tr("Ctrl+Q"));
395     _exitAction->setStatusTip(tr("Exit the application"));
396     connect(_exitAction, &QAction::triggered, this, &QApplication::closeAllWindows);
397 
398     // view menu actions
399 
400     _primaryMenuAction = new QAction(tr( "Primary Event Type" ), this );
401     _primaryMenuAction->setMenu(new QMenu(this));
402     connect(_primaryMenuAction->menu(), &QMenu::aboutToShow,
403             this, &QCGTopLevel::primaryAboutToShow );
404     _secondaryMenuAction = new QAction(tr( "Secondary Event Type" ), this );
405     _secondaryMenuAction->setMenu(new QMenu(this));
406     connect(_secondaryMenuAction->menu(), &QMenu::aboutToShow,
407             this, &QCGTopLevel::secondaryAboutToShow );
408     _groupingMenuAction = new QAction(tr( "Grouping" ), this );
409     _groupingMenuAction->setMenu(new QMenu(this));
410     connect(_groupingMenuAction->menu(), &QMenu::aboutToShow,
411             this, &QCGTopLevel::groupingAboutToShow );
412 
413     icon = QApplication::style()->standardIcon(QStyle::SP_BrowserReload);
414     _cyclesToggleAction = new QAction(icon, tr("Detect Cycles"), this);
415     _cyclesToggleAction->setCheckable(true);
416     _cyclesToggleAction->setStatusTip(tr("Do Cycle Detection"));
417     hint = tr("<b>Detect recursive cycles</b>"
418               "<p>If this is switched off, the treemap drawing will show "
419               "black areas when a recursive call is made instead of drawing "
420               "the recursion ad infinitum. Note that "
421               "the size of black areas often will be wrong, as inside "
422               "recursive cycles the cost of calls cannot be determined; "
423               "the error is small, "
424               "however, for false cycles (see documentation).</p>"
425               "<p>The correct handling for cycles is to detect them and "
426               "collapse all functions of a cycle into an artificial "
427               "function, which is done when this option is selected. "
428               "Unfortunately, with GUI applications, this often will "
429               "lead to huge false cycles, making the analysis impossible; "
430               "therefore, there is the option to switch this off.</p>");
431     _cyclesToggleAction->setWhatsThis(hint);
432     connect(_cyclesToggleAction, &QAction::triggered,
433             this, &QCGTopLevel::toggleCycles);
434     _cyclesToggleAction->setChecked(GlobalConfig::showCycles());
435 
436     _percentageToggleAction = new QAction(QIcon(QStringLiteral(":/percent.png")),
437                                           tr("Relative Cost"), this);
438     _percentageToggleAction->setCheckable(true);
439     _percentageToggleAction->setStatusTip(tr("Show Relative Costs"));
440     connect(_percentageToggleAction, &QAction::triggered,
441             this, &QCGTopLevel::togglePercentage);
442     _percentageToggleAction->setChecked(GlobalConfig::showPercentage());
443 
444     _hideTemplatesToggleAction = new QAction(QIcon(QStringLiteral(":/hidetemplates.png")),
445                                              tr("Shorten Templates"), this);
446     _hideTemplatesToggleAction->setCheckable(true);
447     _hideTemplatesToggleAction->setStatusTip(tr("Hide Template Parameters "
448                                                 "in C++ Symbols"));
449     connect(_hideTemplatesToggleAction, &QAction::triggered,
450             this, &QCGTopLevel::toggleHideTemplates);
451     _hideTemplatesToggleAction->setChecked(GlobalConfig::hideTemplates());
452     hint = tr("<b>Hide Template Parameters in C++ Symbols</b>"
453               "<p>If this is switched on, every symbol displayed will have "
454               "any C++ template parameters hidden, just showing &lt;&gt; "
455               "instead of a potentially nested template parameter.</p>"
456               "<p>In this mode, you can hover the mouse pointer over the "
457               "activated symbol label to show a tooltip with the "
458               "unabbreviated symbol.</p>");
459     _hideTemplatesToggleAction->setWhatsThis(hint);
460 
461     _expandedToggleAction = new QAction(QIcon(QStringLiteral(":/move.png")),
462                                         tr("Relative to Parent"), this);
463     _expandedToggleAction->setCheckable(true);
464     _expandedToggleAction->setStatusTip(
465                 tr("Show Percentage relative to Parent"));
466     hint = tr("<b>Show percentage costs relative to parent</b>"
467               "<p>If this is switched off, percentage costs are always "
468               "shown relative to the total cost of the profile part(s) "
469               "that are currently browsed. By turning on this option, "
470               "percentage cost of shown cost items will be relative "
471               "to the parent cost item.</p>"
472               "<ul><table>"
473               "<tr><td><b>Cost Type</b></td><td><b>Parent Cost</b></td></tr>"
474               "<tr><td>Function Inclusive</td><td>Total</td></tr>"
475               "<tr><td>Function Self</td><td>Function Group (*)/Total</td></tr>"
476               "<tr><td>Call</td><td>Function Inclusive</td></tr>"
477               "<tr><td>Source Line</td><td>Function Inclusive</td></tr>"
478               "</table></ul>"
479               "<p>(*) Only if function grouping is switched on "
480               "(e.g. ELF object grouping).</p>");
481     _expandedToggleAction->setWhatsThis( hint );
482     connect(_expandedToggleAction, &QAction::triggered,
483             this, &QCGTopLevel::toggleExpanded);
484     _expandedToggleAction->setChecked(GlobalConfig::showExpanded());
485 
486     _splittedToggleAction = new QAction(tr("Split Visualization"), this);
487     _splittedToggleAction->setCheckable(true);
488     _splittedToggleAction->setStatusTip(
489                 tr("Show visualization of two cost items"));
490     connect(_splittedToggleAction, &QAction::triggered,
491             this, &QCGTopLevel::toggleSplitted);
492 
493     _splitDirectionToggleAction = new QAction(tr("Split Horizontal"), this);
494     _splitDirectionToggleAction->setCheckable(true);
495     _splitDirectionToggleAction->setStatusTip(
496                 tr("Split visualization area horizontally"));
497     connect(_splitDirectionToggleAction, &QAction::triggered,
498             this, &QCGTopLevel::toggleSplitDirection);
499 
500     _sidebarMenuAction = new QAction(tr("Sidebars"), this);
501     _sidebarMenuAction->setMenu(new QMenu(this));
502     connect( _sidebarMenuAction->menu(), &QMenu::aboutToShow,
503              this, &QCGTopLevel::sidebarMenuAboutToShow);
504 
505     _layoutDup = new QAction(tr("&Duplicate"), this);
506     connect(_layoutDup, &QAction::triggered, this, &QCGTopLevel::layoutDuplicate);
507     _layoutDup->setShortcut(Qt::CTRL + Qt::Key_Plus);
508     _layoutDup->setStatusTip(tr("Duplicate current layout"));
509 
510     _layoutRemove = new QAction(tr("&Remove"), this);
511     connect(_layoutRemove, &QAction::triggered, this, &QCGTopLevel::layoutRemove);
512     _layoutRemove->setStatusTip(tr("Remove current layout"));
513 
514     _layoutNext = new QAction(tr("Go to &Next"), this);
515     connect(_layoutNext, &QAction::triggered, this, &QCGTopLevel::layoutNext);
516     _layoutNext->setShortcut(Qt::CTRL + Qt::Key_Right);
517     _layoutNext->setStatusTip(tr("Switch to next layout"));
518 
519     _layoutPrev = new QAction(tr("Go to &Previous"), this);
520     connect(_layoutPrev, &QAction::triggered, this, &QCGTopLevel::layoutPrevious);
521     _layoutPrev->setShortcut(Qt::CTRL + Qt::Key_Left);
522     _layoutPrev->setStatusTip(tr("Switch to previous layout"));
523 
524     _layoutRestore = new QAction(tr("&Restore to Default"), this);
525     connect(_layoutRestore, &QAction::triggered, this, &QCGTopLevel::layoutRestore);
526     _layoutRestore->setStatusTip(tr("Restore layouts to default"));
527 
528     _layoutSave = new QAction(tr("&Save as Default"), this);
529     connect(_layoutSave, &QAction::triggered, this, &QCGTopLevel::layoutSave);
530     _layoutSave->setStatusTip(tr("Save layouts as default"));
531 
532     // go menu actions
533     icon = QApplication::style()->standardIcon(QStyle::SP_ArrowUp);
534     _upAction = new QAction(icon, tr( "Up" ), this );
535     _upAction->setShortcut( QKeySequence(Qt::ALT+Qt::Key_Up) );
536     _upAction->setStatusTip(tr("Go Up in Call Stack"));
537     _upAction->setMenu(new QMenu(this));
538     connect(_upAction->menu(), &QMenu::aboutToShow,
539             this, &QCGTopLevel::upAboutToShow );
540     connect(_upAction->menu(), &QMenu::triggered,
541             this, &QCGTopLevel::upTriggered );
542     hint = tr("Go to last selected caller of current function");
543     _upAction->setToolTip(hint);
544 
545     icon = QApplication::style()->standardIcon(QStyle::SP_ArrowBack);
546     _backAction = new QAction(icon, tr("Back"), this);
547     _backAction->setShortcut( QKeySequence(Qt::ALT+Qt::Key_Left) );
548     _backAction->setStatusTip(tr("Go Back"));
549     _backAction->setMenu(new QMenu(this));
550     connect(_backAction->menu(), &QMenu::aboutToShow,
551             this, &QCGTopLevel::backAboutToShow );
552     connect(_backAction->menu(), &QMenu::triggered,
553             this, &QCGTopLevel::backTriggered );
554     hint = tr("Go back in function selection history");
555     _backAction->setToolTip(hint);
556 
557     icon = QApplication::style()->standardIcon(QStyle::SP_ArrowForward);
558     _forwardAction = new QAction(icon, tr("Forward"), this);
559     _forwardAction->setShortcut( QKeySequence(Qt::ALT+Qt::Key_Right) );
560     _forwardAction->setStatusTip(tr("Go Forward"));
561     _forwardAction->setMenu(new QMenu(this));
562     connect(_forwardAction->menu(), &QMenu::aboutToShow,
563             this, &QCGTopLevel::forwardAboutToShow );
564     connect(_forwardAction->menu(), &QMenu::triggered,
565             this, &QCGTopLevel::forwardTriggered );
566     hint = tr("Go forward in function selection history");
567     _forwardAction->setToolTip( hint );
568 
569     // settings menu actions
570     _configureAction = new QAction(tr("&Configure..."), this);
571     _configureAction->setMenuRole(QAction::PreferencesRole);
572     _configureAction->setStatusTip(tr("Configure QCachegrind"));
573     connect(_configureAction, SIGNAL(triggered()), this, SLOT(configure()));
574 
575     // window menu actions
576     _minimizeAction = new QAction(tr("&Minimize"), this);
577     _minimizeAction->setShortcut(Qt::CTRL + Qt::Key_M);
578     connect(_minimizeAction, &QAction::triggered, this, &QWidget::showMinimized);
579 
580     _zoomAction = new QAction(tr("&Zoom"), this);
581     // on macOS, zoom doesn't have exactly the same semantics as maximize
582     // (it creates the best fit), but maximize is the usual copout answer
583     connect(_zoomAction, &QAction::triggered, this, &QWidget::showMaximized);
584 
585     // help menu actions
586     _aboutAction = new QAction(tr("&About QCachegrind..."), this);
587     _aboutAction->setMenuRole(QAction::AboutRole);
588     _aboutAction->setStatusTip(tr("Show the application's About box"));
589     connect(_aboutAction, &QAction::triggered, this, &QCGTopLevel::about);
590 
591     _aboutQtAction = new QAction(tr("About Qt..."), this);
592     _aboutQtAction->setMenuRole(QAction::AboutQtRole);
593     connect(_aboutQtAction, &QAction::triggered, qApp, &QApplication::aboutQt);
594 
595     // toolbar actions
596     _eventTypeBox = new QComboBox(this);
597     _eventTypeBox->setMinimumContentsLength(25);
598     hint = tr("Select primary event type of costs");
599     _eventTypeBox->setToolTip( hint );
600     connect( _eventTypeBox, SIGNAL(activated(QString)),
601              this, SLOT(eventTypeSelected(QString)));
602 }
603 
createMenu()604 void QCGTopLevel::createMenu()
605 {
606     QMenuBar* mBar = menuBar();
607 
608     QMenu* fileMenu = mBar->addMenu(tr("&File"));
609     fileMenu->addAction(_newAction);
610     fileMenu->addAction(_openAction);
611     fileMenu->addAction(_recentFilesMenuAction);
612     fileMenu->addAction(_addAction);
613     fileMenu->addSeparator();
614     fileMenu->addAction(_exportAction);
615     fileMenu->addSeparator();
616     fileMenu->addAction(_closeAction);
617     fileMenu->addSeparator();
618     fileMenu->addAction(_exitAction);
619 
620     QMenu* layoutMenu = new QMenu(tr("&Layout"), this);
621     layoutMenu->addAction(_layoutDup);
622     layoutMenu->addAction(_layoutRemove);
623     layoutMenu->addSeparator();
624     layoutMenu->addAction(_layoutPrev);
625     layoutMenu->addAction(_layoutNext);
626     layoutMenu->addSeparator();
627     layoutMenu->addAction(_layoutSave);
628     layoutMenu->addAction(_layoutRestore);
629 
630     QMenu* viewMenu = mBar->addMenu(tr("&View"));
631     viewMenu->addAction(_primaryMenuAction);
632     viewMenu->addAction(_secondaryMenuAction);
633     viewMenu->addAction(_groupingMenuAction);
634     viewMenu->addSeparator();
635     viewMenu->addAction(tb->toggleViewAction());
636     viewMenu->addMenu(layoutMenu);
637     viewMenu->addAction(_sidebarMenuAction);
638     viewMenu->addAction(_splittedToggleAction);
639     viewMenu->addAction(_splitDirectionToggleAction);
640     viewMenu->addSeparator();
641     viewMenu->addAction(_cyclesToggleAction);
642     viewMenu->addAction(_percentageToggleAction);
643     viewMenu->addAction(_expandedToggleAction);
644     viewMenu->addAction(_hideTemplatesToggleAction);
645     viewMenu->addSeparator();
646     viewMenu->addAction(_configureAction);
647 
648     QMenu* goMenu = mBar->addMenu(tr("&Go"));
649     goMenu->addAction(_backAction);
650     goMenu->addAction(_forwardAction);
651     goMenu->addAction(_upAction);
652     fileMenu->addAction(_exitAction);
653 
654 #ifdef Q_OS_MAC
655     // class level for ease of manipulation
656     this->windowMenu = mBar->addMenu(tr("&Window"));
657     this->windowMenu->addAction(_minimizeAction);
658     this->windowMenu->addAction(_zoomAction);
659     connect(this->windowMenu, &QMenu::aboutToShow, this, &QCGTopLevel::windowListAboutToShow);
660     connect(this->windowMenu, &QMenu::triggered, this, &QCGTopLevel::windowListTriggered);
661 
662     // right-clicking the dock icon should be a window list
663     this->macDockMenu = new QMenu(this);
664     connect(this->macDockMenu, &QMenu::aboutToShow, this, &QCGTopLevel::macDockMenuAboutToShow);
665     // it can reuse the same events, it just needs a diff menu structure
666     connect(this->macDockMenu, &QMenu::triggered, this, &QCGTopLevel::windowListTriggered);
667     reinstallMacDock();
668 #endif
669 
670     QMenu* helpMenu = mBar->addMenu(tr("&Help"));
671     helpMenu->addAction(QWhatsThis::createAction(this));
672     helpMenu->addSeparator();
673     helpMenu->addAction(_aboutAction);
674     helpMenu->addAction(_aboutQtAction);
675 }
676 
createToolbar()677 void QCGTopLevel::createToolbar()
678 {
679     tb = new QToolBar(tr("Main Toolbar"), this);
680     tb->setObjectName(QStringLiteral("main-toolbar"));
681     addToolBar(Qt::TopToolBarArea, tb);
682 
683     tb->addAction(_openAction);
684     tb->addSeparator();
685 
686     tb->addAction(_cyclesToggleAction);
687     tb->addAction(_percentageToggleAction);
688     tb->addAction(_expandedToggleAction);
689     tb->addAction(_hideTemplatesToggleAction);
690     tb->addSeparator();
691 
692     tb->addAction(_backAction);
693     tb->addAction(_forwardAction);
694     tb->addAction(_upAction);
695     tb->addSeparator();
696 
697     tb->addWidget(_eventTypeBox);
698 }
699 
700 
about()701 void QCGTopLevel::about()
702 {
703     QString text, version;
704     version = QStringLiteral("0.8.0kde");
705     text = QStringLiteral("<h3>QCachegrind %1</h3>").arg(version);
706     text += tr("<p>QCachegrind is a graphical user interface for analysing "
707                "profiling data, which helps in the performance optimization "
708                "phase of developing a computer program. "
709                "QCachegrind is open-source, and it is distributed under the "
710                "terms of the GPL v2. For details and source code, see the "
711                "<a href=\"https://kcachegrind.github.io\">homepage</a> of the "
712                "KCachegrind project.</p>"
713                "Main author and maintainer: "
714                "<a href=\"mailto:Josef.Weidendorfer@gmx.de\">"
715                "Josef Weidendorfer</a><br>"
716                "(with lots of bug fixes/porting help by the KDE community)");
717     QMessageBox::about(this, tr("About QCachegrind"), text);
718 }
719 
configure(QString s)720 void QCGTopLevel::configure(QString s)
721 {
722     static QString lastPage;
723 
724     // if no specific config item should be focused, use last page
725     if (s.isEmpty()) s = lastPage;
726     ConfigDialog d(_data, this, s);
727 
728     if (d.exec() == QDialog::Accepted) {
729         GlobalConfig::config()->saveOptions();
730         configChanged();
731     }
732     lastPage = d.currentPage();
733 }
734 
togglePartDock()735 void QCGTopLevel::togglePartDock()
736 {
737     if (!_partDock->isVisible())
738         _partDock->show();
739     else
740         _partDock->hide();
741 }
742 
toggleStackDock()743 void QCGTopLevel::toggleStackDock()
744 {
745     if (!_stackDock->isVisible())
746         _stackDock->show();
747     else
748         _stackDock->hide();
749 }
750 
toggleFunctionDock()751 void QCGTopLevel::toggleFunctionDock()
752 {
753     if (!_functionDock->isVisible())
754         _functionDock->show();
755     else
756         _functionDock->hide();
757 }
758 
togglePercentage()759 void QCGTopLevel::togglePercentage()
760 {
761     setPercentage(_percentageToggleAction->isChecked());
762 }
763 
764 
setAbsoluteCost()765 void QCGTopLevel::setAbsoluteCost()
766 {
767     setPercentage(false);
768 }
769 
setRelativeCost()770 void QCGTopLevel::setRelativeCost()
771 {
772     setPercentage(true);
773 }
774 
setPercentage(bool show)775 void QCGTopLevel::setPercentage(bool show)
776 {
777     if (GlobalConfig::showPercentage() == show) return;
778     if (_percentageToggleAction->isChecked() != show)
779         _percentageToggleAction->setChecked(show);
780     _expandedToggleAction->setEnabled(show);
781     GlobalConfig::setShowPercentage(show);
782 
783     _partSelection->notifyChange(TraceItemView::configChanged);
784     _stackSelection->refresh();
785     _functionSelection->notifyChange(TraceItemView::configChanged);
786     _multiView->notifyChange(TraceItemView::configChanged);
787 }
788 
toggleHideTemplates()789 void QCGTopLevel::toggleHideTemplates()
790 {
791     bool show = _hideTemplatesToggleAction->isChecked();
792     if (GlobalConfig::hideTemplates() == show) return;
793     GlobalConfig::setHideTemplates(show);
794 
795     _partSelection->notifyChange(TraceItemView::configChanged);
796     _stackSelection->refresh();
797     _functionSelection->notifyChange(TraceItemView::configChanged);
798     _multiView->notifyChange(TraceItemView::configChanged);
799 }
800 
toggleExpanded()801 void QCGTopLevel::toggleExpanded()
802 {
803     bool show = _expandedToggleAction->isChecked();
804     if (GlobalConfig::showExpanded() == show) return;
805     GlobalConfig::setShowExpanded(show);
806 
807     _partSelection->notifyChange(TraceItemView::configChanged);
808     _stackSelection->refresh();
809     _functionSelection->notifyChange(TraceItemView::configChanged);
810     _multiView->notifyChange(TraceItemView::configChanged);
811 }
812 
toggleCycles()813 void QCGTopLevel::toggleCycles()
814 {
815     bool show = _cyclesToggleAction->isChecked();
816     if (GlobalConfig::showCycles() == show) return;
817     GlobalConfig::setShowCycles(show);
818 
819     if (!_data) return;
820 
821     _data->invalidateDynamicCost();
822     _data->updateFunctionCycles();
823 
824     _partSelection->notifyChange(TraceItemView::configChanged);
825     _stackSelection->rebuildStackList();
826     _functionSelection->notifyChange(TraceItemView::configChanged);
827     _multiView->notifyChange(TraceItemView::configChanged);
828 }
829 
830 
functionVisibilityChanged(bool v)831 void QCGTopLevel::functionVisibilityChanged(bool v)
832 {
833     if (v)
834         _functionSelection->updateView();
835 }
836 
837 
newWindow()838 void QCGTopLevel::newWindow()
839 {
840     QCGTopLevel* t = new QCGTopLevel();
841     t->show();
842 }
843 
844 
load()845 void QCGTopLevel::load()
846 {
847     QStringList files;
848     files = QFileDialog::getOpenFileNames(this,
849                                           tr("Open Callgrind Data"),
850                                           _lastFile,
851                                           tr("Callgrind Files (callgrind.* cachegrind.*);;All Files (*)"));
852     load(files);
853 }
854 
load(QStringList files,bool addToRecentFiles)855 void QCGTopLevel::load(QStringList files, bool addToRecentFiles)
856 {
857     if (files.isEmpty()) return;
858     _lastFile = files[0];
859 
860     if (_data && _data->parts().count()>0) {
861 
862         // In new window
863         QCGTopLevel* t = new QCGTopLevel();
864         t->show();
865         t->loadDelayed(files, addToRecentFiles);
866         return;
867     }
868 
869     // this constructor enables progress bar callbacks
870     TraceData* d = new TraceData(this);
871     int filesLoaded = d->load(files);
872     if (filesLoaded >0)
873         setData(d);
874 
875     if (!addToRecentFiles) return;
876 
877     // add to recent file list in config
878     QStringList recentFiles;
879     ConfigGroup* generalConfig = ConfigStorage::group(QStringLiteral("GeneralSettings"));
880     recentFiles = generalConfig->value(QStringLiteral("RecentFiles"),
881                                        QStringList()).toStringList();
882     foreach(const QString& file, files) {
883         recentFiles.removeAll(file);
884         if (filesLoaded >0)
885             recentFiles.prepend(file);
886         if (recentFiles.count() >5)
887             recentFiles.removeLast();
888     }
889     generalConfig->setValue(QStringLiteral("RecentFiles"), recentFiles);
890     delete generalConfig;
891 }
892 
893 
add()894 void QCGTopLevel::add()
895 {
896     QStringList files;
897     files = QFileDialog::getOpenFileNames(this,
898                                           tr("Add Callgrind Data"),
899                                           _lastFile,
900                                           tr("Callgrind Files (callgrind.*);;All Files (*)"));
901     add(files);
902 }
903 
904 
add(QStringList files)905 void QCGTopLevel::add(QStringList files)
906 {
907     if (files.isEmpty()) return;
908     _lastFile = files[0];
909 
910     if (_data) {
911         _data->load(files);
912 
913         // GUI update for added data
914         configChanged();
915         return;
916     }
917 
918     // this constructor enables progress bar callbacks
919     TraceData* d = new TraceData(this);
920     int filesLoaded = d->load(files);
921     if (filesLoaded >0)
922         setData(d);
923 }
924 
loadDelayed(QString file,bool addToRecentFiles)925 void QCGTopLevel::loadDelayed(QString file, bool addToRecentFiles)
926 {
927     _loadFilesDelayed << file;
928 
929     _addToRecentFiles = addToRecentFiles;
930     QTimer::singleShot(0, this, &QCGTopLevel::loadFilesDelayed);
931 }
932 
loadDelayed(QStringList files,bool addToRecentFiles)933 void QCGTopLevel::loadDelayed(QStringList files, bool addToRecentFiles)
934 {
935     _loadFilesDelayed << files;
936 
937     _addToRecentFiles = addToRecentFiles;
938     QTimer::singleShot(0, this, &QCGTopLevel::loadFilesDelayed);
939 }
940 
loadFilesDelayed()941 void QCGTopLevel::loadFilesDelayed()
942 {
943     if (_loadFilesDelayed.isEmpty()) return;
944 
945     load(_loadFilesDelayed, _addToRecentFiles);
946     _loadFilesDelayed.clear();
947 }
948 
949 
exportGraph()950 void QCGTopLevel::exportGraph()
951 {
952     if (!_data || !_function) return;
953 
954     GraphExporter::savePrompt(this, _data, _function, _eventType, _groupType, nullptr);
955 }
956 
957 
setEventType(QString s)958 bool QCGTopLevel::setEventType(QString s)
959 {
960     EventType* ct;
961 
962     ct = (_data) ? _data->eventTypes()->type(s) : nullptr;
963 
964     // if costtype with given name not found, use first available
965     if (!ct && _data) ct = _data->eventTypes()->type(0);
966 
967     return setEventType(ct);
968 }
969 
setEventType2(QString s)970 bool QCGTopLevel::setEventType2(QString s)
971 {
972     EventType* ct;
973 
974     // Special type tr("(Hidden)") gives 0
975     ct = (_data) ? _data->eventTypes()->type(s) : nullptr;
976 
977     return setEventType2(ct);
978 }
979 
eventTypeSelected(const QString & s)980 void QCGTopLevel::eventTypeSelected(const QString& s)
981 {
982     EventType* ct;
983 
984     ct = (_data) ? _data->eventTypes()->typeForLong(s) : nullptr;
985     setEventType(ct);
986 }
987 
eventType2Selected(const QString & s)988 void QCGTopLevel::eventType2Selected(const QString& s)
989 {
990     EventType* ct;
991 
992     ct = (_data) ? _data->eventTypes()->typeForLong(s) : nullptr;
993     setEventType2(ct);
994 }
995 
setEventType(EventType * ct)996 bool QCGTopLevel::setEventType(EventType* ct)
997 {
998     if (_eventType == ct) return false;
999     _eventType = ct;
1000 
1001     if (ct) {
1002         int idx = _eventTypeBox->findText(ct->longName());
1003         if (idx >=0) _eventTypeBox->setCurrentIndex(idx);
1004     }
1005 
1006     _partSelection->setEventType(_eventType);
1007     _stackSelection->setEventType(_eventType);
1008     _functionSelection->setEventType(_eventType);
1009     _multiView->setEventType(_eventType);
1010 
1011     updateStatusBar();
1012 
1013     return true;
1014 }
1015 
setEventType2(EventType * ct)1016 bool QCGTopLevel::setEventType2(EventType* ct)
1017 {
1018     if (_eventType2 == ct) return false;
1019     _eventType2 = ct;
1020 
1021     _partSelection->setEventType2(_eventType2);
1022     _stackSelection->setEventType2(_eventType2);
1023     _functionSelection->setEventType2(_eventType2);
1024     _multiView->setEventType2(_eventType2);
1025 
1026     updateStatusBar();
1027 
1028     return true;
1029 }
1030 
1031 
groupTypeSelected(int cg)1032 void QCGTopLevel::groupTypeSelected(int cg)
1033 {
1034     switch(cg) {
1035     case 0: setGroupType( ProfileContext::Function ); break;
1036     case 1: setGroupType( ProfileContext::Object ); break;
1037     case 2: setGroupType( ProfileContext::File ); break;
1038     case 3: setGroupType( ProfileContext::Class ); break;
1039     case 4: setGroupType( ProfileContext::FunctionCycle ); break;
1040     default: break;
1041     }
1042 }
1043 
setGroupType(QString s)1044 bool QCGTopLevel::setGroupType(QString s)
1045 {
1046     ProfileContext::Type gt;
1047 
1048     gt = ProfileContext::type(s);
1049     // only allow Function/Object/File/Class as grouptype
1050     switch(gt) {
1051     case ProfileContext::Object:
1052     case ProfileContext::File:
1053     case ProfileContext::Class:
1054     case ProfileContext::FunctionCycle:
1055         break;
1056     default:
1057         gt = ProfileContext::Function;
1058     }
1059 
1060     return setGroupType(gt);
1061 }
1062 
setGroupType(ProfileContext::Type gt)1063 bool QCGTopLevel::setGroupType(ProfileContext::Type gt)
1064 {
1065     if (_groupType == gt) return false;
1066     _groupType = gt;
1067 
1068     int idx = -1;
1069     switch(gt) {
1070     case ProfileContext::Function: idx = 0; break;
1071     case ProfileContext::Object: idx = 1; break;
1072     case ProfileContext::File: idx = 2; break;
1073     case ProfileContext::Class: idx = 3; break;
1074     case ProfileContext::FunctionCycle: idx = 4; break;
1075     default:
1076         break;
1077     }
1078 
1079     if (idx==-1) return false;
1080 
1081 #if 0
1082     if (saGroup->currentItem() != idx)
1083         saGroup->setCurrentItem(idx);
1084 #endif
1085 
1086     _stackSelection->setGroupType(_groupType);
1087 
1088     _partSelection->set(_groupType);
1089     _functionSelection->set(_groupType);
1090     _multiView->set(_groupType);
1091 
1092     updateStatusBar();
1093 
1094     return true;
1095 }
1096 
setGroup(QString s)1097 bool QCGTopLevel::setGroup(QString s)
1098 {
1099     TraceCostItem* ci = _functionSelection->group(s);
1100     if (!ci)
1101         return false;
1102 
1103     return setGroup(ci);
1104 }
1105 
1106 
setGroup(TraceCostItem * g)1107 bool QCGTopLevel::setGroup(TraceCostItem* g)
1108 {
1109     if (_group == g) return false;
1110     _group = g;
1111 
1112     _functionSelection->setGroup(g);
1113     updateStatusBar();
1114 
1115     return true;
1116 }
1117 
setFunction(QString s)1118 bool QCGTopLevel::setFunction(QString s)
1119 {
1120     if (!_data) return false;
1121 
1122     ProfileCostArray* f = _data->search(ProfileContext::Function, s, _eventType);
1123     if (!f) return false;
1124 
1125     return setFunction((TraceFunction*)f);
1126 }
1127 
setFunction(TraceFunction * f)1128 bool QCGTopLevel::setFunction(TraceFunction* f)
1129 {
1130     if (_function == f) return false;
1131     _function = f;
1132 
1133     _multiView->activate(f);
1134     _functionSelection->activate(f);
1135     _partSelection->activate(f);
1136     _stackSelection->setFunction(_function);
1137 
1138     StackBrowser* b = _stackSelection->browser();
1139     if (b) {
1140         // do not disable up: a press forces stack-up extending...
1141         _forwardAction->setEnabled(b->canGoForward());
1142         _backAction->setEnabled(b->canGoBack());
1143     }
1144 
1145 #if TRACE_UPDATES
1146     qDebug("QCGTopLevel::setFunction(%s), lastSender %s",
1147            f ? f->prettyName().toAscii() : "0",
1148            _lastSender ? _lastSender->name() :"0" );
1149 #endif
1150 
1151     return true;
1152 }
1153 
1154 
1155 /**
1156  * Delayed versions.
1157  * We always have a pair of slots: One receiver to start the
1158  * delay with a singleShot Timer. It stores the parameter into a
1159  * temporary variable. And one parameterless slot for
1160  * forwarding, using this temporary.
1161  */
setEventTypeDelayed(EventType * ct)1162 void QCGTopLevel::setEventTypeDelayed(EventType* ct)
1163 {
1164     _eventTypeDelayed = ct;
1165     QTimer::singleShot (0, this, SLOT(setEventTypeDelayed()));
1166 }
1167 
setEventType2Delayed(EventType * ct)1168 void QCGTopLevel::setEventType2Delayed(EventType* ct)
1169 {
1170     _eventType2Delayed = ct;
1171     QTimer::singleShot (0, this, SLOT(setEventType2Delayed()));
1172 }
1173 
setEventTypeDelayed()1174 void QCGTopLevel::setEventTypeDelayed()
1175 {
1176     setEventType(_eventTypeDelayed);
1177 }
1178 
setEventType2Delayed()1179 void QCGTopLevel::setEventType2Delayed()
1180 {
1181     setEventType2(_eventType2Delayed);
1182 }
1183 
setGroupTypeDelayed(ProfileContext::Type gt)1184 void QCGTopLevel::setGroupTypeDelayed(ProfileContext::Type gt)
1185 {
1186     _groupTypeDelayed = gt;
1187     QTimer::singleShot (0, this, SLOT(setGroupTypeDelayed()));
1188 }
1189 
setGroupTypeDelayed()1190 void QCGTopLevel::setGroupTypeDelayed()
1191 {
1192     setGroupType(_groupTypeDelayed);
1193 }
1194 
setGroupDelayed(TraceCostItem * g)1195 void QCGTopLevel::setGroupDelayed(TraceCostItem* g)
1196 {
1197 #if TRACE_UPDATES
1198     qDebug("QCGTopLevel::setGroupDelayed(%s), sender %s",
1199            g ? g->prettyName().toAscii() : "0",
1200            _lastSender ? _lastSender->name() :"0" );
1201 #endif
1202 
1203     _groupDelayed = g;
1204     QTimer::singleShot (0, this, SLOT(setGroupDelayed()));
1205 }
1206 
setGroupDelayed()1207 void QCGTopLevel::setGroupDelayed()
1208 {
1209     setGroup(_groupDelayed);
1210 }
1211 
setDirectionDelayed(TraceItemView::Direction d)1212 void QCGTopLevel::setDirectionDelayed(TraceItemView::Direction d)
1213 {
1214     _directionDelayed = d;
1215     QTimer::singleShot (0, this, SLOT(setDirectionDelayed()));
1216 }
1217 
setDirectionDelayed()1218 void QCGTopLevel::setDirectionDelayed()
1219 {
1220     switch(_directionDelayed) {
1221     case TraceItemView::Back:
1222         _stackSelection->browserBack();
1223         break;
1224 
1225     case TraceItemView::Forward:
1226         _stackSelection->browserForward();
1227         break;
1228 
1229     case TraceItemView::Up:
1230     {
1231         StackBrowser* b = _stackSelection ? _stackSelection->browser() : nullptr;
1232         HistoryItem* hi = b ? b->current() : nullptr;
1233         TraceFunction* f = hi ? hi->function() : nullptr;
1234 
1235         if (!f) break;
1236         f = hi->stack()->caller(f, false);
1237         if (f) setFunction(f);
1238     }
1239         break;
1240 
1241     default: break;
1242     }
1243 
1244     _directionDelayed = TraceItemView::None;
1245 }
1246 
1247 
setTraceItemDelayed(CostItem * i)1248 void QCGTopLevel::setTraceItemDelayed(CostItem* i)
1249 {
1250     // no need to select same item a 2nd time...
1251     if (_traceItemDelayed == i) return;
1252     _traceItemDelayed = i;
1253     _lastSender = sender();
1254 
1255     qDebug() << "Selected " << (i ? i->fullName() : QStringLiteral("(none)"));
1256 
1257 #if TRACE_UPDATES
1258     qDebug("QCGTopLevel::setTraceItemDelayed(%s), sender %s",
1259            i ? i->prettyName().toAscii() : "0",
1260            _lastSender ? _lastSender->name() :"0" );
1261 #endif
1262 
1263     QTimer::singleShot (0, this, SLOT(setTraceItemDelayed()));
1264 }
1265 
setTraceItemDelayed()1266 void QCGTopLevel::setTraceItemDelayed()
1267 {
1268     if (!_traceItemDelayed) return;
1269 
1270     switch(_traceItemDelayed->type()) {
1271     case ProfileContext::Function:
1272     case ProfileContext::FunctionCycle:
1273         setFunction((TraceFunction*)_traceItemDelayed);
1274         break;
1275 
1276     case ProfileContext::Object:
1277     case ProfileContext::File:
1278     case ProfileContext::Class:
1279         _multiView->activate(_traceItemDelayed);
1280         break;
1281 
1282     case ProfileContext::Instr:
1283     case ProfileContext::Line:
1284         // only for multiview
1285         _multiView->activate(_traceItemDelayed);
1286         break;
1287 
1288     default: break;
1289     }
1290 
1291     _traceItemDelayed = nullptr;
1292     _lastSender = nullptr;
1293 }
1294 
1295 /**
1296  * A TraceData object cannot be viewed many times in different
1297  * toplevel windows. Thus, this toplevel window takes ownership
1298  * of the TraceData object: on closing the window or opening
1299  * another trace, the object is destroyed.
1300  */
setData(TraceData * data)1301 void QCGTopLevel::setData(TraceData* data)
1302 {
1303     if (data == _data) return;
1304 
1305     _lastSender = nullptr;
1306 
1307     saveTraceSettings();
1308 
1309     if (_data) {
1310         _partSelection->setData(nullptr);
1311         _stackSelection->setData(nullptr);
1312         _functionSelection->setData(nullptr);
1313         _multiView->setData(nullptr);
1314         _multiView->updateView(true);
1315 
1316         // we are the owner...
1317         delete _data;
1318     }
1319 
1320     // reset members
1321     resetState();
1322 
1323     _data = data;
1324 
1325     // fill cost type list
1326     QStringList types;
1327 
1328     if (_data) {
1329         /* add all supported virtual types */
1330         EventTypeSet* m = _data->eventTypes();
1331         m->addKnownDerivedTypes();
1332 
1333         /* first, fill selection list with available cost types */
1334         for (int i=0;i<m->realCount();i++)
1335             types << m->realType(i)->longName();
1336         for (int i=0;i<m->derivedCount();i++)
1337             types << m->derivedType(i)->longName();
1338     }
1339     _eventTypes = types;
1340     _eventTypeBox->addItems(types);
1341 
1342     _stackSelection->setData(_data);
1343     _partSelection->setData(_data);
1344     _functionSelection->setData(_data);
1345     _multiView->setData(_data);
1346     // Force update of _data in all children of _multiView
1347     // This is needed to make restoring of activeItem work!
1348     _multiView->updateView(true);
1349 
1350     /* this is needed to let the other widgets know the types */
1351     restoreTraceTypes();
1352 
1353     restoreTraceSettings();
1354 
1355     QString caption;
1356     if (_data) {
1357         caption = QDir::toNativeSeparators(_data->traceName());
1358         if (!_data->command().isEmpty())
1359             caption += " [" + _data->command() + ']';
1360     }
1361     setWindowFilePath(QDir::toNativeSeparators(_data->traceName()));
1362     setWindowTitle(caption);
1363 
1364     if (!_data || (!_forcePartDock && _data->parts().count()<2))
1365         _partDock->hide();
1366     else
1367         _partDock->show();
1368 
1369     updateStatusBar();
1370 }
1371 
1372 // Clears and repopulates the given menu with dynamic items for event types.
1373 // Menu item handlers for setting the types are installed.
updateEventTypeMenu(QMenu * m,bool secondary)1374 void QCGTopLevel::updateEventTypeMenu(QMenu* m, bool secondary)
1375 {
1376     QAction* action;
1377 
1378     if (!m) return;
1379     m->clear();
1380 
1381     if (!_data) {
1382         // no data loaded yet
1383         m->addAction(tr("(None)"));
1384         return;
1385     }
1386 
1387     if (secondary) {
1388         connect(m, SIGNAL(triggered(QAction*)),
1389                 this, SLOT(setEventType2(QAction*)), Qt::UniqueConnection);
1390 
1391         if (_eventType2 != nullptr) {
1392             action = m->addAction(tr("Hide"));
1393             action->setData(199);
1394             m->addSeparator();
1395         }
1396     }
1397     else {
1398         connect(m, SIGNAL(triggered(QAction*)),
1399                 this, SLOT(setEventType(QAction*)), Qt::UniqueConnection);
1400     }
1401 
1402     EventTypeSet* ets = _data->eventTypes();
1403     EventType* et;
1404     EventType* selected = secondary ? _eventType2 : _eventType;
1405     for (int i = 0; i < ets->realCount(); i++) {
1406         et = ets->realType(i);
1407 
1408         action = m->addAction(et->longName());
1409         action->setCheckable(true);
1410         action->setData(100+i);
1411         if (et == selected) action->setChecked(true);
1412     }
1413     for (int i = 0; i < ets->derivedCount(); i++) {
1414         et = ets->derivedType(i);
1415 
1416         action = m->addAction(et->longName());
1417         action->setCheckable(true);
1418         action->setData(200+i);
1419         if (et == selected) action->setChecked(true);
1420     }
1421 }
1422 
addEventTypeMenu(QMenu * popup,bool withCost2)1423 void QCGTopLevel::addEventTypeMenu(QMenu* popup, bool withCost2)
1424 {
1425     if (_data) {
1426         QMenu* menu = popup->addMenu(tr("Primary Event Type"));
1427         updateEventTypeMenu(menu, false);
1428 
1429         if (withCost2) {
1430             QMenu* menu = popup->addMenu(tr("Secondary Event Type"));
1431             updateEventTypeMenu(menu, true);
1432         }
1433     }
1434 
1435     if (GlobalConfig::showPercentage())
1436         popup->addAction(tr("Show Absolute Cost"),
1437                          this, SLOT(setAbsoluteCost()));
1438     else
1439         popup->addAction(tr("Show Relative Cost"),
1440                          this, SLOT(setRelativeCost()));
1441 }
1442 
setEventType(QAction * action)1443 bool QCGTopLevel::setEventType(QAction* action)
1444 {
1445     if (!_data) return false;
1446     int id = action->data().toInt(nullptr);
1447 
1448     EventTypeSet* m = _data->eventTypes();
1449     EventType* ct=nullptr;
1450     if (id >=100 && id<199) ct = m->realType(id-100);
1451     if (id >=200 && id<299) ct = m->derivedType(id-200);
1452 
1453     return ct ? setEventType(ct) : false;
1454 }
1455 
setEventType2(QAction * action)1456 bool QCGTopLevel::setEventType2(QAction* action)
1457 {
1458     if (!_data) return false;
1459     int id = action->data().toInt(nullptr);
1460 
1461     EventTypeSet* m = _data->eventTypes();
1462     EventType* ct=nullptr;
1463     if (id >=100 && id<199) ct = m->realType(id-100);
1464     if (id >=200 && id<299) ct = m->derivedType(id-200);
1465 
1466     return setEventType2(ct);
1467 }
1468 
addGoMenu(QMenu * popup)1469 void QCGTopLevel::addGoMenu(QMenu* popup)
1470 {
1471     StackBrowser* b = _stackSelection->browser();
1472     if (b) {
1473         if (b->canGoBack())
1474             popup->addAction(tr("Go Back"), this, SLOT(goBack()));
1475         if (b->canGoForward())
1476             popup->addAction(tr("Go Forward"), this, SLOT(goForward()));
1477     }
1478     // do not disable up: a press forces stack-up extending...
1479     popup->addAction(tr("Go Up"), this, SLOT(goUp()));
1480 }
1481 
goBack()1482 void QCGTopLevel::goBack()
1483 {
1484     setDirectionDelayed(TraceItemView::Back);
1485 }
1486 
goForward()1487 void QCGTopLevel::goForward()
1488 {
1489     setDirectionDelayed(TraceItemView::Forward);
1490 }
1491 
goUp()1492 void QCGTopLevel::goUp()
1493 {
1494     setDirectionDelayed(TraceItemView::Up);
1495 }
1496 
traceKey()1497 QString QCGTopLevel::traceKey()
1498 {
1499     if (!_data || _data->command().isEmpty()) return QString();
1500 
1501     QString name = _data->command();
1502     QString key;
1503     for (int l=0;l<name.length();l++)
1504         if (name[l].isLetterOrNumber()) key += name[l];
1505 
1506     return QStringLiteral("-") + key;
1507 }
1508 
1509 
restoreTraceTypes()1510 void QCGTopLevel::restoreTraceTypes()
1511 {
1512     QString key = traceKey();
1513     QString groupType, eventType, eventType2;
1514 
1515     ConfigGroup* pConfig = ConfigStorage::group(QStringLiteral("TracePositions"));
1516     groupType = pConfig->value(QStringLiteral("GroupType%1").arg(key),QString()).toString();
1517     eventType = pConfig->value(QStringLiteral("EventType%1").arg(key),QString()).toString();
1518     eventType2 = pConfig->value(QStringLiteral("EventType2%1").arg(key),QString()).toString();
1519     delete pConfig;
1520 
1521     ConfigGroup* cConfig = ConfigStorage::group(QStringLiteral("CurrentState"));
1522     if (groupType.isEmpty())
1523         groupType = cConfig->value(QStringLiteral("GroupType"),QString()).toString();
1524     if (eventType.isEmpty())
1525         eventType = cConfig->value(QStringLiteral("EventType"),QString()).toString();
1526     if (eventType2.isEmpty())
1527         eventType2 = cConfig->value(QStringLiteral("EventType2"),QString()).toString();
1528     delete cConfig;
1529 
1530     setGroupType(groupType);
1531     setEventType(eventType);
1532     setEventType2(eventType2);
1533 
1534     // if still no event type set, use first available
1535     if (!_eventType && !_eventTypes.isEmpty())
1536         eventTypeSelected(_eventTypes.first());
1537 
1538     ConfigGroup* aConfig = ConfigStorage::group(QStringLiteral("Layouts"));
1539     _layoutCount = aConfig->value(QStringLiteral("Count%1").arg(key), 0).toInt();
1540     _layoutCurrent = aConfig->value(QStringLiteral("Current%1").arg(key), 0).toInt();
1541     delete aConfig;
1542 
1543     if (_layoutCount == 0) layoutRestore();
1544     updateLayoutActions();
1545 }
1546 
1547 
1548 /**
1549  * This must be called after setting group/cost types in the function
1550  * selection widget, because the group/function choosing depends on
1551  * filled lists in the function selection widget
1552  */
restoreTraceSettings()1553 void QCGTopLevel::restoreTraceSettings()
1554 {
1555     if (!_data) return;
1556 
1557     QString key = traceKey();
1558 
1559     restoreCurrentState(key);
1560 
1561     ConfigGroup* pConfig = ConfigStorage::group(QStringLiteral("TracePositions"));
1562     QString group = pConfig->value(QStringLiteral("Group%1").arg(key),QString()).toString();
1563     delete pConfig;
1564     if (!group.isEmpty()) setGroup(group);
1565 
1566     // restoreCurrentState() usually leads to a call to setTraceItemDelayed()
1567     // to restore last active item...
1568     if (!_traceItemDelayed) {
1569         // function not available any more.. try with "main"
1570         if (!setFunction(QStringLiteral("main"))) {
1571 #if 1
1572             _functionSelection->selectTopFunction();
1573 #else
1574             HighestCostList hc;
1575             hc.clear(50);
1576             TraceFunctionMap::Iterator it;
1577             for ( it = _data->functionMap().begin();
1578                   it != _data->functionMap().end(); ++it )
1579                 hc.addCost(&(*it), (*it).inclusive()->subCost(_eventType));
1580 
1581             setFunction( (TraceFunction*) hc[0]);
1582 #endif
1583         }
1584     }
1585 }
1586 
1587 
1588 /* Layout */
1589 
layoutDuplicate()1590 void QCGTopLevel::layoutDuplicate()
1591 {
1592     // save current and allocate a new slot
1593     _multiView->saveLayout(QStringLiteral("Layout%1-MainView").arg(_layoutCurrent),
1594                            traceKey());
1595     _layoutCurrent = _layoutCount;
1596     _layoutCount++;
1597 
1598     updateLayoutActions();
1599 
1600     qDebug() << "QCGTopLevel::layoutDuplicate: count " << _layoutCount;
1601 }
1602 
layoutRemove()1603 void QCGTopLevel::layoutRemove()
1604 {
1605     if (_layoutCount <2) return;
1606 
1607     int from = _layoutCount-1;
1608     if (_layoutCurrent == from) { _layoutCurrent--; from--; }
1609 
1610     // restore from last and decrement count
1611     _multiView->restoreLayout(QStringLiteral("Layout%1-MainView").arg(from),
1612                               traceKey());
1613     _layoutCount--;
1614 
1615     updateLayoutActions();
1616 
1617     qDebug() << "QCGTopLevel::layoutRemove: count " << _layoutCount;
1618 }
1619 
layoutNext()1620 void QCGTopLevel::layoutNext()
1621 {
1622     if (_layoutCount <2) return;
1623 
1624     QString key = traceKey();
1625     QString layoutPrefix = QStringLiteral("Layout%1-MainView");
1626 
1627     _multiView->saveLayout(layoutPrefix.arg(_layoutCurrent), key);
1628     _layoutCurrent++;
1629     if (_layoutCurrent == _layoutCount) _layoutCurrent = 0;
1630     _multiView->restoreLayout(layoutPrefix.arg(_layoutCurrent), key);
1631 
1632     qDebug() << "QCGTopLevel::layoutNext: current " << _layoutCurrent;
1633 }
1634 
layoutPrevious()1635 void QCGTopLevel::layoutPrevious()
1636 {
1637     if (_layoutCount <2) return;
1638 
1639     QString key = traceKey();
1640     QString layoutPrefix = QStringLiteral("Layout%1-MainView");
1641 
1642     _multiView->saveLayout(layoutPrefix.arg(_layoutCurrent), key);
1643     _layoutCurrent--;
1644     if (_layoutCurrent <0) _layoutCurrent = _layoutCount-1;
1645     _multiView->restoreLayout(layoutPrefix.arg(_layoutCurrent), key);
1646 
1647     qDebug() << "QCGTopLevel::layoutPrevious: current " << _layoutCurrent;
1648 }
1649 
layoutSave()1650 void QCGTopLevel::layoutSave()
1651 {
1652     QString key = traceKey();
1653     QString layoutPrefix = QStringLiteral("Layout%1-MainView");
1654 
1655     _multiView->saveLayout(layoutPrefix.arg(_layoutCurrent), key);
1656 
1657     // save all layouts as defaults (ie. without any group name postfix)
1658     for(int i=0;i<_layoutCount;i++) {
1659         _multiView->restoreLayout(layoutPrefix.arg(i), key);
1660         _multiView->saveLayout(layoutPrefix.arg(i), QString());
1661     }
1662     // restore the previously saved current layout
1663     _multiView->restoreLayout(layoutPrefix.arg(_layoutCurrent), key);
1664 
1665     ConfigGroup* layoutConfig = ConfigStorage::group(QStringLiteral("Layouts"));
1666     layoutConfig->setValue(QStringLiteral("DefaultCount"), _layoutCount);
1667     layoutConfig->setValue(QStringLiteral("DefaultCurrent"), _layoutCurrent);
1668     delete layoutConfig;
1669 }
1670 
layoutRestore()1671 void QCGTopLevel::layoutRestore()
1672 {
1673     ConfigGroup* layoutConfig = ConfigStorage::group(QStringLiteral("Layouts"));
1674     _layoutCount = layoutConfig->value(QStringLiteral("DefaultCount"), 0).toInt();
1675     _layoutCurrent = layoutConfig->value(QStringLiteral("DefaultCurrent"), 0).toInt();
1676     delete layoutConfig;
1677 
1678     if (_layoutCount == 0) {
1679         _layoutCount++;
1680         return;
1681     }
1682 
1683     QString layoutPrefix = QStringLiteral("Layout%1-MainView");
1684     _multiView->restoreLayout( layoutPrefix.arg(_layoutCurrent), traceKey());
1685 
1686     updateLayoutActions();
1687 }
1688 
1689 
updateLayoutActions()1690 void QCGTopLevel::updateLayoutActions()
1691 {
1692     if (_layoutNext)
1693         _layoutNext->setEnabled(_layoutCount>1);
1694 
1695     if (_layoutPrev)
1696         _layoutPrev->setEnabled(_layoutCount>1);
1697 
1698     if (_layoutRemove)
1699         _layoutRemove->setEnabled(_layoutCount>1);
1700 
1701     if (_statusbar)
1702         _statusbar->showMessage(tr("Layout Count: %1").arg(_layoutCount),
1703                                 1000);
1704 }
1705 
1706 
updateStatusBar()1707 void QCGTopLevel::updateStatusBar()
1708 {
1709     if (!_data || _data->parts().isEmpty()) {
1710         _statusLabel->setText(tr("No profile data file loaded."));
1711         return;
1712     }
1713 
1714     QString status = QStringLiteral("%1 [%2] - ")
1715                      .arg(_data->shortTraceName())
1716                      .arg(_data->activePartRange());
1717 
1718     if (_eventType) {
1719         status += tr("Total %1 Cost: %2")
1720                   .arg(_eventType->longName())
1721                   .arg(_data->prettySubCost(_eventType));
1722 
1723         /* this gets too long...
1724     if (_eventType2 && (_eventType2 != _eventType))
1725       status += tr(", %1 Cost: %2")
1726     .arg(_eventType2->longName())
1727     .arg(_data->prettySubCost(_eventType2));
1728     */
1729     }
1730     else
1731         status += tr("No event type selected");
1732 
1733     /* Not working... should give group of selected function
1734 
1735   if (_groupType != ProfileContext::Function) {
1736     status += QString(" - %1 '%2'")
1737               .arg(ProfileContext::trTypeName(_groupType))
1738               .arg(_group ? _group->prettyName() : tr("(None)"));
1739   }
1740   */
1741 
1742     _statusLabel->setText(status);
1743 }
1744 
1745 
closeEvent(QCloseEvent * event)1746 void QCGTopLevel::closeEvent(QCloseEvent* event)
1747 {
1748     GlobalConfig::config()->saveOptions();
1749 
1750     saveTraceSettings();
1751     saveCurrentState(QString());
1752 
1753     // if part dock was chosen visible even for only 1 part loaded,
1754     // keep this choice...
1755     _forcePartDock = false;
1756     if (_data && (_data->parts().count()<2) && _partDock->isVisible())
1757         _forcePartDock=true;
1758 
1759     ConfigGroup* topConfig = ConfigStorage::group(QStringLiteral("TopWindow"));
1760     topConfig->setValue(QStringLiteral("ForcePartDockVisible"), _forcePartDock, false);
1761     topConfig->setValue(QStringLiteral("State"), saveState());
1762     topConfig->setValue(QStringLiteral("Geometry"), saveGeometry());
1763     delete topConfig;
1764 
1765     event->accept();
1766 }
1767 
1768 
toggleSplitted()1769 void QCGTopLevel::toggleSplitted()
1770 {
1771     int count = _multiView->childCount();
1772     if (count<1) count = 1;
1773     if (count>2) count = 2;
1774     count = 3-count;
1775     _multiView->setChildCount(count);
1776 
1777     _splittedToggleAction->setChecked(count>1);
1778     _splitDirectionToggleAction->setEnabled(count>1);
1779     _splitDirectionToggleAction->setChecked(_multiView->orientation() ==
1780                                             Qt::Horizontal);
1781 }
1782 
toggleSplitDirection()1783 void QCGTopLevel::toggleSplitDirection()
1784 {
1785     _multiView->setOrientation( _splitDirectionToggleAction->isChecked() ?
1786                                     Qt::Horizontal : Qt::Vertical );
1787 }
1788 
1789 
1790 
1791 // this is called after a config change in the dialog
configChanged()1792 void QCGTopLevel::configChanged()
1793 {
1794     // invalidate found/cached dirs of source files
1795     if (_data)
1796         _data->resetSourceDirs();
1797 
1798     _partSelection->notifyChange(TraceItemView::configChanged);
1799     _stackSelection->refresh();
1800     _functionSelection->notifyChange(TraceItemView::configChanged);
1801     _multiView->notifyChange(TraceItemView::configChanged);
1802 }
1803 
1804 
1805 
activePartsChangedSlot(const TracePartList & list)1806 void QCGTopLevel::activePartsChangedSlot(const TracePartList& list)
1807 {
1808     if (!_data) return;
1809 
1810     if (!_data->activateParts(list)) {
1811         //    qDebug("QCGTopLevel::activePartsChangedSlot: No Change!");
1812         return;
1813     }
1814     _activeParts = list;
1815 
1816     _partSelection->set(list);
1817     _stackSelection->refresh();
1818     _functionSelection->set(list);
1819     _multiView->set(list);
1820 
1821     updateStatusBar();
1822 }
1823 
partsHideSelectedSlotDelayed()1824 void QCGTopLevel::partsHideSelectedSlotDelayed()
1825 {
1826     QTimer::singleShot( 0, this, &QCGTopLevel::partsHideSelectedSlot );
1827 }
1828 
1829 // this puts selected parts into hidden list,
1830 // deselects them and makes the remaining parts selected
partsHideSelectedSlot()1831 void QCGTopLevel::partsHideSelectedSlot()
1832 {
1833     if (!_data) return;
1834 
1835     TracePartList newHidden, newActive;
1836     foreach(TracePart* part, _data->parts()) {
1837         if (_activeParts.contains(part) ||
1838             _hiddenParts.contains(part))
1839             newHidden.append(part);
1840         else
1841             newActive.append(part);
1842     }
1843 
1844     _hiddenParts = newHidden;
1845     _partSelection->hiddenPartsChangedSlot(_hiddenParts);
1846 
1847 #if 0
1848     _mainWidget1->hiddenPartsChangedSlot(_hiddenParts);
1849     _mainWidget2->hiddenPartsChangedSlot(_hiddenParts);
1850 #endif
1851 
1852     activePartsChangedSlot(newActive);
1853 }
1854 
partsUnhideAllSlotDelayed()1855 void QCGTopLevel::partsUnhideAllSlotDelayed()
1856 {
1857     QTimer::singleShot( 0, this, &QCGTopLevel::partsUnhideAllSlot );
1858 }
1859 
1860 // this unhides all hidden parts. Does NOT change selection
partsUnhideAllSlot()1861 void QCGTopLevel::partsUnhideAllSlot()
1862 {
1863     if (!_data) return;
1864 
1865     _hiddenParts.clear();
1866     _partSelection->hiddenPartsChangedSlot(_hiddenParts);
1867 
1868 #if 0
1869     _mainWidget1->hiddenPartsChangedSlot(_hiddenParts);
1870     _mainWidget2->hiddenPartsChangedSlot(_hiddenParts);
1871 #endif
1872 }
1873 
insertWindowList(QMenu * menu)1874 void QCGTopLevel::insertWindowList(QMenu* menu)
1875 {
1876     auto windowList = QApplication::topLevelWidgets();
1877     auto activeWindow = QApplication::activeWindow();
1878     // dock menu this corresponds to (IIRC) last window that installed one,
1879     // so using it unless we have to is a bad idea
1880     if (activeWindow == nullptr) {
1881         activeWindow = this;
1882     }
1883     for (int i = 0; i < windowList.size(); i++) {
1884         QWidget *topLevelRaw = windowList[i];
1885         if (QCGTopLevel *topLevel = qobject_cast<QCGTopLevel*>(topLevelRaw)) {
1886             QString windowTitle = topLevel->windowTitle();
1887             QAction *windowItem = menu->addAction(windowTitle);
1888             windowItem->setData(QVariant::fromValue(topLevel));
1889             if (topLevel == activeWindow) {
1890                 windowItem->setCheckable(true);
1891                 windowItem->setChecked(true);
1892             }
1893         }
1894     }
1895 }
1896 
windowListAboutToShow()1897 void QCGTopLevel::windowListAboutToShow()
1898 {
1899     windowMenu->clear();
1900 
1901     windowMenu->addAction(_minimizeAction);
1902     windowMenu->addAction(_zoomAction);
1903     windowMenu->addSeparator();
1904 
1905     insertWindowList(windowMenu);
1906 }
1907 
macDockMenuAboutToShow()1908 void QCGTopLevel::macDockMenuAboutToShow()
1909 {
1910     macDockMenu-> clear();
1911 
1912     insertWindowList(macDockMenu);
1913 
1914     macDockMenu->addSeparator();
1915     macDockMenu->addAction(_newAction);
1916 }
1917 
forwardAboutToShow()1918 void QCGTopLevel::forwardAboutToShow()
1919 {
1920     QMenu *popup = _forwardAction->menu();
1921 
1922     popup->clear();
1923     StackBrowser* b = _stackSelection ? _stackSelection->browser() : nullptr;
1924     HistoryItem* hi = b ? b->current() : nullptr;
1925     TraceFunction* f;
1926     QAction* action;
1927 
1928     if (!hi) {
1929         popup->addAction(tr("(No Stack)"));
1930         return;
1931     }
1932 
1933     hi = hi->next();
1934     if (!hi) {
1935         popup->addAction(tr("(No next function)"));
1936         return;
1937     }
1938 
1939     int count = 1;
1940     while (count<GlobalConfig::maxSymbolCount() && hi) {
1941         f = hi->function();
1942         if (!f) break;
1943 
1944         QString name = GlobalConfig::shortenSymbol(f->prettyName());
1945 
1946         //qDebug("forward: Adding %s", name.toAscii());
1947         action = popup->addAction(name);
1948         action->setData(count);
1949 
1950         hi = hi->next();
1951         count++;
1952     }
1953 }
1954 
backAboutToShow()1955 void QCGTopLevel::backAboutToShow()
1956 {
1957     QMenu *popup = _backAction->menu();
1958 
1959     popup->clear();
1960     StackBrowser* b = _stackSelection ? _stackSelection->browser() : nullptr;
1961     HistoryItem* hi = b ? b->current() : nullptr;
1962     TraceFunction* f;
1963     QAction* action;
1964 
1965     if (!hi) {
1966         popup->addAction(tr("(No Stack)"));
1967         return;
1968     }
1969 
1970     hi = hi->last();
1971     if (!hi) {
1972         popup->addAction(tr("(No previous function)"));
1973         return;
1974     }
1975 
1976     int count = 1;
1977     while (count<GlobalConfig::maxSymbolCount() && hi) {
1978         f = hi->function();
1979         if (!f) break;
1980 
1981         QString name = GlobalConfig::shortenSymbol(f->prettyName());
1982 
1983         //qDebug("back: Adding %s", name.toAscii());
1984         action = popup->addAction(name);
1985         action->setData(count);
1986 
1987         hi = hi->last();
1988         count++;
1989     }
1990 }
1991 
upAboutToShow()1992 void QCGTopLevel::upAboutToShow()
1993 {
1994     QMenu *popup = _upAction->menu();
1995 
1996     popup->clear();
1997     StackBrowser* b = _stackSelection ? _stackSelection->browser() : nullptr;
1998     HistoryItem* hi = b ? b->current() : nullptr;
1999     TraceFunction* f = hi ? hi->function() : nullptr;
2000     QAction* action;
2001 
2002     if (!f) {
2003         popup->addAction(tr("(No Stack)"));
2004         return;
2005     }
2006     f = hi->stack()->caller(f, false);
2007     if (!f) {
2008         popup->addAction(tr("(No Function Up)"));
2009         return;
2010     }
2011 
2012     int count = 1;
2013     while (count<GlobalConfig::maxSymbolCount() && f) {
2014         QString name = GlobalConfig::shortenSymbol(f->prettyName());
2015 
2016         action = popup->addAction(name);
2017         action->setData(count);
2018 
2019         f = hi->stack()->caller(f, false);
2020         count++;
2021     }
2022 }
2023 
windowListTriggered(QAction * action)2024 void QCGTopLevel::windowListTriggered(QAction* action)
2025 {
2026     if (action == _minimizeAction || action == _zoomAction) {
2027         // these are always in the menu for macOS
2028         return;
2029     }
2030 
2031     QVariant data = action->data();
2032     if (QCGTopLevel* tl = qvariant_cast<QCGTopLevel*>(data)) {
2033         tl->activateWindow();
2034         tl->raise();
2035     }
2036 }
2037 
forwardTriggered(QAction * action)2038 void QCGTopLevel::forwardTriggered(QAction* action)
2039 {
2040     int count = action->data().toInt(nullptr);
2041     //qDebug("forwardTriggered: %d", count);
2042     if( count <= 0)
2043         return;
2044 
2045     StackBrowser* b = _stackSelection ? _stackSelection->browser() : nullptr;
2046     if (!b) return;
2047 
2048     while (count>1) {
2049         b->goForward();
2050         count--;
2051     }
2052     _stackSelection->browserForward();
2053 }
2054 
backTriggered(QAction * action)2055 void QCGTopLevel::backTriggered(QAction* action)
2056 {
2057     int count = action->data().toInt(nullptr);
2058     //qDebug("backTriggered: %d", count);
2059     if( count <= 0)
2060         return;
2061 
2062     StackBrowser* b = _stackSelection ? _stackSelection->browser() : nullptr;
2063     if (!b) return;
2064 
2065     while (count>1) {
2066         b->goBack();
2067         count--;
2068     }
2069     _stackSelection->browserBack();
2070 }
2071 
upTriggered(QAction * action)2072 void QCGTopLevel::upTriggered(QAction* action)
2073 {
2074     int count = action->data().toInt(nullptr);
2075     //qDebug("upTriggered: %d", count);
2076     if( count <= 0)
2077         return;
2078 
2079     StackBrowser* b = _stackSelection ? _stackSelection->browser() : nullptr;
2080     HistoryItem* hi = b ? b->current() : nullptr;
2081     if (!hi) return;
2082 
2083     TraceFunction* f = hi->function();
2084 
2085     while (count>0 && f) {
2086         f = hi->stack()->caller(f, false);
2087         count--;
2088     }
2089 
2090     //qDebug("upActivated: %s", f ? f->prettyName().toAscii() : "??" );
2091     if (f)
2092         setFunction(f);
2093 }
2094 
showMessage(const QString & msg,int ms)2095 void QCGTopLevel::showMessage(const QString& msg, int ms)
2096 {
2097     if (_statusbar)
2098         _statusbar->showMessage(msg, ms);
2099 }
2100 
showStatus(const QString & msg,int progress)2101 void QCGTopLevel::showStatus(const QString& msg, int progress)
2102 {
2103     static bool msgUpdateNeeded = true;
2104 
2105     if (!_statusbar) return;
2106 
2107     if (msg.isEmpty()) {
2108         //reset status
2109         if (_progressBar) {
2110             _statusbar->removeWidget(_progressBar);
2111             delete _progressBar;
2112             _progressBar = nullptr;
2113         }
2114         _statusbar->clearMessage();
2115         _progressMsg = msg;
2116         return;
2117     }
2118 
2119     if (_progressMsg.isEmpty())
2120         _progressStart.start();
2121 
2122     if (msg != _progressMsg) {
2123         _progressMsg = msg;
2124         msgUpdateNeeded = true;
2125     }
2126 
2127     // do nothing if last change was less than 0.5 seconds ago
2128     if (_progressStart.elapsed() < 500)
2129         return;
2130 
2131     if (!_progressBar) {
2132         _progressBar = new QProgressBar(_statusbar);
2133         _progressBar->setMaximumSize(200, _statusbar->height()-4);
2134         _statusbar->addPermanentWidget(_progressBar, 1);
2135         _progressBar->show();
2136         msgUpdateNeeded = true;
2137     }
2138 
2139     _progressStart.restart();
2140 
2141     if (msgUpdateNeeded) {
2142         _statusbar->showMessage(msg);
2143         msgUpdateNeeded = false;
2144     }
2145     _progressBar->setValue(progress);
2146 
2147     // let the progress bar update itself
2148     qApp->processEvents(QEventLoop::ExcludeUserInputEvents);
2149 }
2150 
loadStart(const QString & filename)2151 void QCGTopLevel::loadStart(const QString& filename)
2152 {
2153     showStatus(QStringLiteral("Loading %1").arg(filename), 0);
2154     Logger::_filename = filename;
2155 }
2156 
loadFinished(const QString & msg)2157 void QCGTopLevel::loadFinished(const QString& msg)
2158 {
2159     showStatus(QString(), 0);
2160     if (!msg.isEmpty())
2161         showMessage(QStringLiteral("Error loading %1: %2").arg(_filename).arg(msg),
2162                     2000);
2163 }
2164 
loadProgress(int progress)2165 void QCGTopLevel::loadProgress(int progress)
2166 {
2167     showStatus(QStringLiteral("Loading %1").arg(_filename), progress);
2168 }
2169 
loadError(int line,const QString & msg)2170 void QCGTopLevel::loadError(int line, const QString& msg)
2171 {
2172     qCritical() << "Loading" << _filename
2173                 << ":" << line << ": " << msg;
2174 }
2175 
loadWarning(int line,const QString & msg)2176 void QCGTopLevel::loadWarning(int line, const QString& msg)
2177 {
2178     qWarning() << "Loading" << _filename
2179                << ":" << line << ": " << msg;
2180 }
2181 
2182