fff1bf0099e95f74d5ed0ae8450bf2c2e5ef1027
[sdk/tools/heaptrack.git] / src / analyze / gui / mainwindow.cpp
1 /*
2  * Copyright 2015-2017 Milian Wolff <mail@milianw.de>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
17  */
18
19 #include "mainwindow.h"
20
21 #include <cmath>
22
23 #ifdef NO_K_LIB
24 #include "noklib.h"
25 #include <ui_mainwindow_noklib.h>
26 #include "aboutdialog.h"
27 #include <QAbstractButton>
28 #include <QFileDialog>
29 #else
30 #include <ui_mainwindow.h>
31 #include <KConfigGroup>
32 #include <KLocalizedString>
33 #include <KRecursiveFilterProxyModel>
34 #include <KStandardAction>
35 #endif
36
37 #include "util.h"
38
39 #include <QAction>
40 #include <QDebug>
41 #include <QDesktopServices>
42 #include <QFileDialog>
43 #include <QMenu>
44 #include <QMoveEvent>
45 #include <QStatusBar>
46
47 #include "../accumulatedtracedata.h"
48 #include "callercalleemodel.h"
49 #include "costdelegate.h"
50 #include "parser.h"
51 #include "stacksmodel.h"
52 #include "topproxy.h"
53 #include "treemodel.h"
54 #include "objecttreemodel.h"
55 #include "treeproxy.h"
56 #include "objecttreeproxy.h"
57
58 #include "gui_config.h"
59
60 #if USE_CHART
61 #include "chartmodel.h"
62 #include "chartproxy.h"
63 #include "chartwidget.h"
64 #include "histogrammodel.h"
65 #include "histogramwidget.h"
66 #if QWT_FOUND
67 #include "aboutdata.h"
68 #include <QSettings>
69 #endif
70 #endif
71
72 using namespace std;
73
74 namespace {
75 const int MAINWINDOW_VERSION = 1;
76
77 namespace Config {
78 namespace Groups {
79 const char MainWindow[] = "MainWindow";
80 }
81 namespace Entries {
82 const char State[] = "State";
83 }
84 }
85
86 void addContextMenu(QTreeView* treeView, int role)
87 {
88     treeView->setContextMenuPolicy(Qt::CustomContextMenu);
89     QObject::connect(treeView, &QTreeView::customContextMenuRequested, treeView, [treeView, role](const QPoint& pos) {
90         auto index = treeView->indexAt(pos);
91         if (!index.isValid()) {
92             return;
93         }
94         const auto location = index.data(role).value<LocationData::Ptr>();
95         if (!location || !QFile::exists(location->file)) {
96             return;
97         }
98         auto menu = new QMenu(treeView);
99         auto openFile =
100             new QAction(QIcon::fromTheme(QStringLiteral("document-open")), i18n("Open file in editor"), menu);
101         QObject::connect(openFile, &QAction::triggered, openFile, [location] {
102             /// FIXME: add settings to let user configure this
103             auto url = QUrl::fromLocalFile(location->file);
104             url.setFragment(QString::number(location->line));
105             QDesktopServices::openUrl(url);
106         });
107         menu->addAction(openFile);
108         menu->popup(treeView->mapToGlobal(pos));
109     });
110 }
111
112 void setupTopView(TreeModel* source, QTreeView* view, TopProxy::Type type)
113 {
114     auto proxy = new TopProxy(type, source);
115     proxy->setSourceModel(source);
116     proxy->setSortRole(TreeModel::SortRole);
117     view->setModel(proxy);
118     view->sortByColumn(0);
119     view->header()->setStretchLastSection(true);
120     addContextMenu(view, TreeModel::LocationRole);
121 }
122
123 #if USE_CHART
124 void addChartTab(QTabWidget* tabWidget, const QString& title, ChartModel::Type type, const Parser* parser,
125                  void (Parser::*dataReady)(const ChartData&), MainWindow* window)
126 {
127     auto tab = new ChartWidget(tabWidget->parentWidget());
128     tabWidget->addTab(tab, title);
129     tabWidget->setTabEnabled(tabWidget->indexOf(tab), false);
130     auto model = new ChartModel(type, tab);
131     tab->setModel(model);
132     QObject::connect(parser, dataReady, tab, [=](const ChartData& data) {
133         model->resetData(data);
134         tabWidget->setTabEnabled(tabWidget->indexOf(tab), true);
135     });
136     QObject::connect(window, &MainWindow::clearData, model, &ChartModel::clearData);
137 }
138 #endif
139
140 void setupTreeModel(TreeModel* model, QTreeView* view, CostDelegate* costDelegate, QLineEdit* filterFunction,
141                     QLineEdit* filterFile, QLineEdit* filterModule)
142 {
143     auto proxy = new TreeProxy(TreeModel::FunctionColumn, TreeModel::FileColumn, TreeModel::ModuleColumn, model);
144     proxy->setSourceModel(model);
145     proxy->setSortRole(TreeModel::SortRole);
146
147     view->setModel(proxy);
148     view->setItemDelegateForColumn(TreeModel::PeakColumn, costDelegate);
149     view->setItemDelegateForColumn(TreeModel::PeakInstancesColumn, costDelegate);
150     view->setItemDelegateForColumn(TreeModel::AllocatedColumn, costDelegate);
151     view->setItemDelegateForColumn(TreeModel::LeakedColumn, costDelegate);
152     view->setItemDelegateForColumn(TreeModel::AllocationsColumn, costDelegate);
153     view->setItemDelegateForColumn(TreeModel::TemporaryColumn, costDelegate);
154     if(AllocationData::display != AllocationData::DisplayId::malloc)
155     {
156         view->hideColumn(TreeModel::TemporaryColumn);
157
158         if(AllocationData::display != AllocationData::DisplayId::managed)
159         {
160             view->hideColumn(TreeModel::AllocationsColumn);
161             view->hideColumn(TreeModel::PeakInstancesColumn);
162         }
163     }
164     view->hideColumn(TreeModel::FunctionColumn);
165     view->hideColumn(TreeModel::FileColumn);
166     view->hideColumn(TreeModel::LineColumn);
167     view->hideColumn(TreeModel::ModuleColumn);
168
169     QObject::connect(filterFunction, &QLineEdit::textChanged, proxy, &TreeProxy::setFunctionFilter);
170     QObject::connect(filterFile, &QLineEdit::textChanged, proxy, &TreeProxy::setFileFilter);
171     QObject::connect(filterModule, &QLineEdit::textChanged, proxy, &TreeProxy::setModuleFilter);
172     addContextMenu(view, TreeModel::LocationRole);
173 }
174
175 void setupObjectTreeModel(ObjectTreeModel* model, QTreeView* view, QLineEdit* filterClass, QComboBox* filterGC) {
176     auto proxy = new ObjectTreeProxy(ObjectTreeModel::ClassNameColumn, ObjectTreeModel::GCNumColumn, model);
177     proxy->setSourceModel(model);
178     proxy->setSortRole(ObjectTreeModel::SortRole);
179
180     view->setModel(proxy);
181     view->hideColumn(ObjectTreeModel::GCNumColumn);
182     QObject::connect(filterClass, &QLineEdit::textChanged, proxy, &ObjectTreeProxy::setNameFilter);
183     QObject::connect(filterGC, static_cast<void(QComboBox::*)(int)>(&QComboBox::currentIndexChanged),
184                      proxy, &ObjectTreeProxy::setGCFilter);
185 }
186
187 void setupCallerCalle(CallerCalleeModel* model, QTreeView* view, CostDelegate* costDelegate, QLineEdit* filterFunction,
188                       QLineEdit* filterFile, QLineEdit* filterModule)
189 {
190     auto callerCalleeProxy = new TreeProxy(CallerCalleeModel::FunctionColumn, CallerCalleeModel::FileColumn,
191                                            CallerCalleeModel::ModuleColumn, model);
192     callerCalleeProxy->setSourceModel(model);
193     callerCalleeProxy->setSortRole(CallerCalleeModel::SortRole);
194     view->setModel(callerCalleeProxy);
195     view->sortByColumn(CallerCalleeModel::InclusivePeakColumn);
196     view->setItemDelegateForColumn(CallerCalleeModel::SelfPeakColumn, costDelegate);
197     view->setItemDelegateForColumn(CallerCalleeModel::SelfPeakInstancesColumn, costDelegate);
198     view->setItemDelegateForColumn(CallerCalleeModel::SelfAllocatedColumn, costDelegate);
199     view->setItemDelegateForColumn(CallerCalleeModel::SelfLeakedColumn, costDelegate);
200     view->setItemDelegateForColumn(CallerCalleeModel::SelfAllocationsColumn, costDelegate);
201     view->setItemDelegateForColumn(CallerCalleeModel::SelfTemporaryColumn, costDelegate);
202     view->setItemDelegateForColumn(CallerCalleeModel::InclusivePeakColumn, costDelegate);
203     view->setItemDelegateForColumn(CallerCalleeModel::InclusivePeakInstancesColumn, costDelegate);
204     view->setItemDelegateForColumn(CallerCalleeModel::InclusiveAllocatedColumn, costDelegate);
205     view->setItemDelegateForColumn(CallerCalleeModel::InclusiveLeakedColumn, costDelegate);
206     view->setItemDelegateForColumn(CallerCalleeModel::InclusiveAllocationsColumn, costDelegate);
207     view->setItemDelegateForColumn(CallerCalleeModel::InclusiveTemporaryColumn, costDelegate);
208     view->hideColumn(CallerCalleeModel::FunctionColumn);
209     view->hideColumn(CallerCalleeModel::FileColumn);
210     view->hideColumn(CallerCalleeModel::LineColumn);
211     view->hideColumn(CallerCalleeModel::ModuleColumn);
212     if(AllocationData::display != AllocationData::DisplayId::malloc)
213     {
214         view->hideColumn(CallerCalleeModel::SelfTemporaryColumn);
215         view->hideColumn(CallerCalleeModel::InclusiveTemporaryColumn);
216
217         if(AllocationData::display != AllocationData::DisplayId::managed)
218         {
219             view->hideColumn(CallerCalleeModel::SelfAllocationsColumn);
220             view->hideColumn(CallerCalleeModel::InclusiveAllocationsColumn);
221             view->hideColumn(CallerCalleeModel::SelfPeakInstancesColumn);
222             view->hideColumn(CallerCalleeModel::InclusivePeakInstancesColumn);
223         }
224     }
225     QObject::connect(filterFunction, &QLineEdit::textChanged, callerCalleeProxy, &TreeProxy::setFunctionFilter);
226     QObject::connect(filterFile, &QLineEdit::textChanged, callerCalleeProxy, &TreeProxy::setFileFilter);
227     QObject::connect(filterModule, &QLineEdit::textChanged, callerCalleeProxy, &TreeProxy::setModuleFilter);
228     addContextMenu(view, CallerCalleeModel::LocationRole);
229 }
230
231 QString insertWordWrapMarkers(QString text)
232 {
233     // insert zero-width spaces after every 50 word characters to enable word wrap in the middle of words
234     static const QRegularExpression pattern(QStringLiteral("(\\w{50})"));
235     return text.replace(pattern, QStringLiteral("\\1\u200B"));
236 }
237 }
238
239 MainWindow::MainWindow(QWidget* parent)
240     : QMainWindow(parent)
241     , m_ui(new Ui::MainWindow)
242     , m_parser(new Parser(this))
243 #ifndef NO_K_LIB // TODO!! find a replacement for KSharedConfig
244     , m_config(KSharedConfig::openConfig(QStringLiteral("heaptrack_gui")))
245 #endif
246 {
247 #ifdef QWT_FOUND
248     QSettings settings(QSettings::UserScope, AboutData::Organization, AboutData::applicationName());
249     settings.beginGroup("Charts");
250     QVariant value = settings.value("Options");
251     bool ok;
252     int options = value.toInt(&ok);
253     if (ok)
254     {
255         ChartOptions::GlobalOptions = ChartOptions::Options(options);
256     }
257     settings.endGroup();
258 #if QT_VERSION >= 0x050A00
259     // seems it doesn't help under Windows (Qt 5.10.0)
260     QCoreApplication::setAttribute(Qt::AA_DontShowShortcutsInContextMenus, false);
261 #endif
262 #endif // QWT_FOUND
263
264     m_ui->setupUi(this);
265
266 #ifndef NO_K_LIB // TODO!! find a replacement for KSharedConfig
267     auto group = m_config->group(Config::Groups::MainWindow);
268     auto state = group.readEntry(Config::Entries::State, QByteArray());
269     restoreState(state, MAINWINDOW_VERSION);
270 #endif
271
272     m_ui->pages->setCurrentWidget(m_ui->openPage);
273     // TODO: proper progress report
274     m_ui->loadingProgress->setMinimum(0);
275     m_ui->loadingProgress->setMaximum(0);
276
277     auto bottomUpModelFilterOutLeaves = new TreeModel(this);
278     auto topDownModel = new TreeModel(this);
279     auto callerCalleeModel = new CallerCalleeModel(this);
280     auto objectTreeModel = new ObjectTreeModel(this);
281     connect(this, &MainWindow::clearData, bottomUpModelFilterOutLeaves, &TreeModel::clearData);
282     connect(this, &MainWindow::clearData, topDownModel, &TreeModel::clearData);
283     connect(this, &MainWindow::clearData, callerCalleeModel, &CallerCalleeModel::clearData);
284     connect(this, &MainWindow::clearData, m_ui->flameGraphTab, &FlameGraph::clearData);
285     connect(this, &MainWindow::clearData, objectTreeModel, &ObjectTreeModel::clearData);
286
287     m_ui->tabWidget->setTabEnabled(m_ui->tabWidget->indexOf(m_ui->callerCalleeTab), false);
288     m_ui->tabWidget->setTabEnabled(m_ui->tabWidget->indexOf(m_ui->topDownTab), false);
289     m_ui->tabWidget->setTabEnabled(m_ui->tabWidget->indexOf(m_ui->flameGraphTab), false);
290     m_ui->tabWidget->setTabEnabled(m_ui->tabWidget->indexOf(m_ui->heapTab), false);
291     connect(m_parser, &Parser::bottomUpDataAvailable, this, [=](const TreeData& data) {
292         if (!m_diffMode) {
293             m_ui->flameGraphTab->setBottomUpData(data);
294         }
295         m_ui->progressLabel->setAlignment(Qt::AlignVCenter | Qt::AlignRight);
296         statusBar()->addWidget(m_ui->progressLabel, 1);
297         statusBar()->addWidget(m_ui->loadingProgress);
298         m_ui->pages->setCurrentWidget(m_ui->resultsPage);
299         m_ui->tabWidget->setTabEnabled(m_ui->tabWidget->indexOf(m_ui->bottomUpTab), true);
300     });
301     connect(m_parser, &Parser::objectTreeBottomUpDataAvailable, this, [=](const ObjectTreeData& data) {
302         quint32 maxGC = 0;
303         for (const ObjectRowData& row: data) {
304             if (maxGC < row.gcNum)
305                 maxGC = row.gcNum;
306         }
307         for (quint32 gc = 0; gc < maxGC; gc++) {
308             m_ui->filterGC->addItem(QString::number(gc+1));
309         }
310         objectTreeModel->resetData(data);
311         m_ui->tabWidget->setTabEnabled(m_ui->tabWidget->indexOf(m_ui->heapTab), true);
312     });
313     connect(m_parser,
314             (AccumulatedTraceData::isHideUnmanagedStackParts ?
315              &Parser::bottomUpDataAvailable : &Parser::bottomUpFilterOutLeavesDataAvailable),
316             this, [=](const TreeData& data) {
317         bottomUpModelFilterOutLeaves->resetData(data);
318     });
319     connect(m_parser, &Parser::callerCalleeDataAvailable, this, [=](const CallerCalleeRows& data) {
320         callerCalleeModel->resetData(data);
321         m_ui->tabWidget->setTabEnabled(m_ui->tabWidget->indexOf(m_ui->callerCalleeTab), true);
322     });
323     connect(m_parser, &Parser::topDownDataAvailable, this, [=](const TreeData& data) {
324         topDownModel->resetData(data);
325         m_ui->tabWidget->setTabEnabled(m_ui->tabWidget->indexOf(m_ui->topDownTab), true);
326         if (!m_diffMode) {
327             m_ui->flameGraphTab->setTopDownData(data);
328         }
329         m_ui->tabWidget->setTabEnabled(m_ui->tabWidget->indexOf(m_ui->flameGraphTab), !m_diffMode);
330     });
331     connect(m_parser, &Parser::summaryAvailable, this, [=](const SummaryData& data) {
332         bottomUpModelFilterOutLeaves->setSummary(data);
333         topDownModel->setSummary(data);
334         callerCalleeModel->setSummary(data);
335         QString textLeft;
336         QString textCenter;
337         QString textRight;
338         const double totalTimeS = 0.001 * data.totalTime;
339         const double peakTimeS = 0.001 * data.peakTime;
340         {
341             QTextStream stream(&textLeft);
342             const auto debuggee = insertWordWrapMarkers(data.debuggee);
343             stream << "<qt><dl>"
344                    << (data.fromAttached ? i18n("<dt><b>debuggee</b>:</dt><dd "
345                                                 "style='font-family:monospace;'>%1 <i>(attached)</i></dd>",
346                                                 debuggee)
347                                          : i18n("<dt><b>debuggee</b>:</dt><dd "
348                                                 "style='font-family:monospace;'>%1</dd>",
349                                                 debuggee))
350                    // xgettext:no-c-format
351                    << i18n("<dt><b>total runtime</b>:</dt><dd>%1s</dd>", totalTimeS)
352                    << i18n("<dt><b>total system memory</b>:</dt><dd>%1</dd>",
353                            Util::formatByteSize(data.totalSystemMemory, 1))
354                    << "</dl></qt>";
355         }
356
357         if(AllocationData::display == AllocationData::DisplayId::malloc
358            || AllocationData::display == AllocationData::DisplayId::managed)
359         {
360             QTextStream stream(&textCenter);
361             stream << "<qt><dl>" << i18n("<dt><b>calls to allocation functions</b>:</dt><dd>%1 "
362                                          "(%2/s)</dd>",
363                                          data.cost.allocations, qint64(data.cost.allocations / totalTimeS))
364                    << i18n("<dt><b>temporary allocations</b>:</dt><dd>%1 (%2%, "
365                            "%3/s)</dd>",
366                            data.cost.temporary,
367                            std::round(float(data.cost.temporary) * 100.f * 100.f / data.cost.allocations) / 100.f,
368                            qint64(data.cost.temporary / totalTimeS))
369                    << i18n("<dt><b>bytes allocated in total</b> (ignoring "
370                            "deallocations):</dt><dd>%1 (%2/s)</dd>",
371                            Util::formatByteSize(data.cost.allocated, 2),
372                            Util::formatByteSize(data.cost.allocated / totalTimeS, 1))
373                    << "</dl></qt>";
374         }
375         if (AccumulatedTraceData::isShowCoreCLRPartOption)
376         {
377             QTextStream stream(&textRight);
378
379             if (AllocationData::display == AllocationData::DisplayId::malloc)
380             {
381                 stream << "<qt><dl>" << i18n("<dt><b>peak heap memory consumption</b>:</dt><dd>%1 "
382                                              "after %2s</dd>"
383                                              "</dt><dd>%3 (CoreCLR), %4 (non-CoreCLR), %5 (unknown)</dd>",
384                                              Util::formatByteSize(data.cost.peak, 1),
385                                              peakTimeS,
386                                              Util::formatByteSize(data.CoreCLRPart.peak, 1),
387                                              Util::formatByteSize(data.nonCoreCLRPart.peak, 1),
388                                              Util::formatByteSize(data.unknownPart.peak, 1))
389                        << i18n("<dt><b>peak RSS</b> (including %1 "
390                                "overhead):</dt><dd>%2</dd>",
391                                AboutData::ShortName,
392                                Util::formatByteSize(data.peakRSS, 1))
393                        << i18n("<dt><b>total memory leaked</b>:</dt><dd>%1</dd>"
394                                "</dt><dd>%2 (CoreCLR), %3 (non-CoreCLR), %4 (unknown)</dd>",
395                                Util::formatByteSize(data.cost.leaked, 1),
396                                Util::formatByteSize(data.CoreCLRPart.leaked, 1),
397                                Util::formatByteSize(data.nonCoreCLRPart.leaked, 1),
398                                Util::formatByteSize(data.unknownPart.leaked, 1))
399                        << "</dl></qt>";
400             }
401             else
402             {
403                 stream << "<qt><dl>" << i18n("<dt><b>peak heap memory consumption</b>:</dt><dd>%1 "
404                                              "after %2s</dd>"
405                                              "</dt><dd>%3 (CoreCLR), %4 (non-CoreCLR), %5 (sbrk heap), %6 (unknown)</dd>",
406                                              Util::formatByteSize(data.cost.peak, 1),
407                                              peakTimeS,
408                                              Util::formatByteSize(data.CoreCLRPart.peak, 1),
409                                              Util::formatByteSize(data.nonCoreCLRPart.peak, 1),
410                                              Util::formatByteSize(data.untrackedPart.peak, 1),
411                                              Util::formatByteSize(data.unknownPart.peak, 1))
412                        << i18n("<dt><b>peak RSS</b> (including %1 "
413                                "overhead):</dt><dd>%2</dd>",
414                                AboutData::ShortName,
415                                Util::formatByteSize(data.peakRSS, 1))
416                        << i18n("<dt><b>total memory leaked</b>:</dt><dd>%1</dd>"
417                                "</dt><dd>%2 (CoreCLR), %3 (non-CoreCLR), %4 (sbrk heap), %5 (unknown)</dd>",
418                                Util::formatByteSize(data.cost.leaked, 1),
419                                Util::formatByteSize(data.CoreCLRPart.leaked, 1),
420                                Util::formatByteSize(data.nonCoreCLRPart.leaked, 1),
421                                Util::formatByteSize(data.untrackedPart.leaked, 1),
422                                Util::formatByteSize(data.unknownPart.leaked, 1))
423                        << "</dl></qt>";
424             }
425         }
426         else
427         {
428             QTextStream stream(&textRight);
429             stream << "<qt><dl>" << i18n("<dt><b>peak heap memory consumption</b>:</dt><dd>%1 "
430                                          "after %2s</dd>",
431                                          Util::formatByteSize(data.cost.peak, 1),
432                                          peakTimeS)
433                    << i18n("<dt><b>peak RSS</b> (including %1 "
434                            "overhead):</dt><dd>%2</dd>",
435                            AboutData::ShortName,
436                            Util::formatByteSize(data.peakRSS, 1))
437                    << i18n("<dt><b>total memory leaked</b>:</dt><dd>%1</dd>",
438                            Util::formatByteSize(data.cost.leaked, 1))
439                    << "</dl></qt>";
440         }
441
442         m_ui->summaryLeft->setText(textLeft);
443         m_ui->summaryCenter->setText(textCenter);
444         m_ui->summaryRight->setText(textRight);
445         m_ui->tabWidget->setTabEnabled(m_ui->tabWidget->indexOf(m_ui->summaryTab), true);
446     });
447     connect(m_parser, &Parser::progressMessageAvailable, m_ui->progressLabel, &QLabel::setText);
448     auto removeProgress = [this] {
449         auto layout = qobject_cast<QVBoxLayout*>(m_ui->loadingPage->layout());
450         Q_ASSERT(layout);
451         const auto idx = layout->indexOf(m_ui->loadingLabel) + 1;
452         layout->insertWidget(idx, m_ui->loadingProgress);
453         layout->insertWidget(idx + 1, m_ui->progressLabel);
454         m_ui->progressLabel->setAlignment(Qt::AlignVCenter | Qt::AlignHCenter);
455         m_closeAction->setEnabled(true);
456         m_openAction->setEnabled(true);
457     };
458     connect(m_parser, &Parser::finished, this, removeProgress);
459     connect(m_parser, &Parser::failedToOpen, this, [this, removeProgress](const QString& failedFile) {
460         removeProgress();
461         m_ui->pages->setCurrentWidget(m_ui->openPage);
462         showError(i18n("Failed to parse file %1.", failedFile));
463     });
464     m_ui->messages->hide();
465
466 #if USE_CHART
467     addChartTab(m_ui->tabWidget, i18n("Consumed"), ChartModel::Consumed, m_parser, &Parser::consumedChartDataAvailable,
468                 this);
469
470     if(AllocationData::display == AllocationData::DisplayId::malloc
471        || AllocationData::display == AllocationData::DisplayId::managed)
472     {
473         addChartTab(m_ui->tabWidget, i18n("Instances"), ChartModel::Instances, m_parser,
474                     &Parser::instancesChartDataAvailable, this);
475
476         addChartTab(m_ui->tabWidget, i18n("Allocations"), ChartModel::Allocations, m_parser,
477                     &Parser::allocationsChartDataAvailable, this);
478
479         addChartTab(m_ui->tabWidget, i18n("Allocated"), ChartModel::Allocated, m_parser, &Parser::allocatedChartDataAvailable,
480                     this);
481
482         if (AllocationData::display == AllocationData::DisplayId::malloc)
483         {
484             addChartTab(m_ui->tabWidget, i18n("Temporary Allocations"), ChartModel::Temporary, m_parser,
485                         &Parser::temporaryChartDataAvailable, this);
486         }
487
488         auto sizesTab = new HistogramWidget(this);
489         m_ui->tabWidget->addTab(sizesTab, i18n("Sizes"));
490         m_ui->tabWidget->setTabEnabled(m_ui->tabWidget->indexOf(sizesTab), false);
491         auto sizeHistogramModel = new HistogramModel(this);
492         sizesTab->setModel(sizeHistogramModel);
493         connect(this, &MainWindow::clearData, sizeHistogramModel, &HistogramModel::clearData);
494
495         connect(m_parser, &Parser::sizeHistogramDataAvailable, this, [=](const HistogramData& data) {
496                 sizeHistogramModel->resetData(data);
497                 m_ui->tabWidget->setTabEnabled(m_ui->tabWidget->indexOf(sizesTab), true);
498                 });
499     }
500 #endif // USE_CHART
501 #ifdef NO_K_LIB
502     connect(m_ui->aboutAction, &QAction::triggered, this, &MainWindow::about);
503 #endif
504
505     auto costDelegate = new CostDelegate(this);
506
507     setupTreeModel(bottomUpModelFilterOutLeaves, m_ui->bottomUpResults, costDelegate, m_ui->bottomUpFilterFunction,
508                    m_ui->bottomUpFilterFile, m_ui->bottomUpFilterModule);
509
510     setupTreeModel(topDownModel, m_ui->topDownResults, costDelegate, m_ui->topDownFilterFunction,
511                    m_ui->topDownFilterFile, m_ui->topDownFilterModule);
512
513     setupCallerCalle(callerCalleeModel, m_ui->callerCalleeResults, costDelegate, m_ui->callerCalleeFilterFunction,
514                      m_ui->callerCalleeFilterFile, m_ui->callerCalleeFilterModule);
515
516     setupObjectTreeModel(objectTreeModel, m_ui->objectTreeResults, m_ui->filterClass, m_ui->filterGC);
517
518     auto validateInputFile = [this](const QString& path, bool allowEmpty) -> bool {
519         m_ui->messages->hide();
520         if (path.isEmpty()) {
521             return allowEmpty;
522         }
523
524         const auto file = QFileInfo(path);
525         if (!file.exists()) {
526             showError(i18n("Input data %1 does not exist.", path));
527         } else if (!file.isFile()) {
528             showError(i18n("Input data %1 is not a file.", path));
529         } else if (!file.isReadable()) {
530             showError(i18n("Input data %1 is not readable.", path));
531         } else {
532             return true;
533         }
534         return false;
535     };
536 #ifndef NO_K_LIB
537     auto validateInput = [this, validateInputFile]() {
538         m_ui->messages->hide();
539         m_ui->buttonBox->setEnabled(validateInputFile(m_ui->openFile->url().toLocalFile(), false)
540                                     && validateInputFile(m_ui->compareTo->url().toLocalFile(), true));
541     };
542
543     connect(m_ui->openFile, &KUrlRequester::textChanged, this, validateInput);
544     connect(m_ui->compareTo, &KUrlRequester::textChanged, this, validateInput);
545     connect(m_ui->buttonBox, &QDialogButtonBox::accepted, this, [this]() {
546         const auto path = m_ui->openFile->url().toLocalFile();
547         Q_ASSERT(!path.isEmpty());
548         const auto base = m_ui->compareTo->url().toLocalFile();
549         loadFile(path, base);
550     });
551 #else
552     m_ui->buttonBox->setEnabled(true);
553
554     auto validateInputAndLoadFile = [this, validateInputFile]() {
555        const auto path = m_ui->openFileEdit->text();
556        if (!validateInputFile(path, false)) {
557            return;
558        }
559        Q_ASSERT(!path.isEmpty());
560        const auto base = m_ui->compareToEdit->text();
561        if (!validateInputFile(base, true)) {
562            return;
563        }
564        QApplication::setOverrideCursor(Qt::WaitCursor);
565        loadFile(path, base);
566        QApplication::restoreOverrideCursor();
567     };
568
569     connect(m_ui->buttonBox, &QDialogButtonBox::clicked, this, validateInputAndLoadFile);
570
571     connect(m_ui->openFileButton1, &QPushButton::clicked, this, &MainWindow::selectOpenFile);
572     connect(m_ui->openFileButton2, &QPushButton::clicked, this, &MainWindow::selectCompareToFile);
573 #endif
574
575     setupStacks();
576
577     setupTopView(bottomUpModelFilterOutLeaves, m_ui->topPeak, TopProxy::Peak);
578     m_ui->topPeak->setItemDelegate(costDelegate);
579     setupTopView(bottomUpModelFilterOutLeaves, m_ui->topPeakInstances, TopProxy::PeakInstances);
580     m_ui->topPeakInstances->setItemDelegate(costDelegate);
581     setupTopView(bottomUpModelFilterOutLeaves, m_ui->topLeaked, TopProxy::Leaked);
582     m_ui->topLeaked->setItemDelegate(costDelegate);
583
584     if(AllocationData::display == AllocationData::DisplayId::malloc
585        || AllocationData::display == AllocationData::DisplayId::managed)
586     {
587         setupTopView(bottomUpModelFilterOutLeaves, m_ui->topAllocations, TopProxy::Allocations);
588         m_ui->topAllocations->setItemDelegate(costDelegate);
589         setupTopView(bottomUpModelFilterOutLeaves, m_ui->topAllocated, TopProxy::Allocated);
590         m_ui->topAllocated->setItemDelegate(costDelegate);
591
592         if (AllocationData::display == AllocationData::DisplayId::malloc)
593         {
594             setupTopView(bottomUpModelFilterOutLeaves, m_ui->topTemporary, TopProxy::Temporary);
595             m_ui->topTemporary->setItemDelegate(costDelegate);
596         }
597         else
598         {
599             m_ui->widget_8->hide();
600         }
601     }
602     else
603     {
604         m_ui->widget_7->hide();
605         m_ui->widget_8->hide();
606         m_ui->widget_9->hide();
607         m_ui->widget_12->hide();
608     }
609
610     setWindowTitle(AboutData::ShortName);
611     // closing the current file shows the stack page to open a new one
612 #ifdef NO_K_LIB
613     m_openAction = new QAction(i18n("&Open..."), this);
614     m_openAction->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_O));
615     connect(m_openAction, &QAction::triggered, this, &MainWindow::closeFile);
616     m_openAction->setEnabled(false);
617     m_openNewAction = new QAction(i18n("&New"), this);
618     m_openNewAction->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_N));
619     connect(m_openNewAction, &QAction::triggered, this, &MainWindow::openNewFile);
620     m_closeAction = new QAction(i18n("&Close"), this);
621     connect(m_closeAction, &QAction::triggered, this, &MainWindow::close);
622     m_quitAction = new QAction(i18n("&Quit"), this);
623     connect(m_quitAction, &QAction::triggered, qApp, &QApplication::quit);
624 #else
625     m_openAction = KStandardAction::open(this, SLOT(closeFile()), this);
626     m_openAction->setEnabled(false);
627     m_openNewAction = KStandardAction::openNew(this, SLOT(openNewFile()), this);
628     m_closeAction = KStandardAction::close(this, SLOT(close()), this);
629     m_quitAction = KStandardAction::quit(qApp, SLOT(quit()), this);
630 #endif
631     m_ui->menu_File->addAction(m_openAction);
632     m_ui->menu_File->addAction(m_openNewAction);
633     m_ui->menu_File->addAction(m_closeAction);
634     m_ui->menu_File->addAction(m_quitAction);
635 }
636
637 MainWindow::~MainWindow()
638 {
639 #ifdef NO_K_LIB
640 #ifdef QWT_FOUND
641     QSettings settings(QSettings::UserScope, AboutData::Organization, AboutData::applicationName());
642     settings.beginGroup("Charts");
643     settings.setValue("Options", ChartOptions::GlobalOptions);
644     settings.endGroup();
645 #endif // QWT_FOUND
646 #else
647     auto state = saveState(MAINWINDOW_VERSION);
648     auto group = m_config->group(Config::Groups::MainWindow);
649     group.writeEntry(Config::Entries::State, state);
650 #endif // NO_K_LIB
651 }
652
653 void MainWindow::loadFile(const QString& file, const QString& diffBase)
654 {
655     // TODO: support canceling of ongoing parse jobs
656     m_closeAction->setEnabled(false);
657     m_ui->loadingLabel->setText(i18n("Loading file %1, please wait...", file));
658     if (diffBase.isEmpty()) {
659         setWindowTitle(i18nc("%1: application name; %2: file name that is open", "%1 - %2",
660                              AboutData::ShortName, QFileInfo(file).fileName()));
661         m_diffMode = false;
662     } else {
663         setWindowTitle(i18nc("%1: application name; %2, %3: file names that are open", "%1 - %2 compared to %3",
664                              AboutData::ShortName, QFileInfo(file).fileName(), QFileInfo(diffBase).fileName()));
665         m_diffMode = true;
666     }
667     m_ui->pages->setCurrentWidget(m_ui->loadingPage);
668     m_parser->parse(file, diffBase);
669 }
670
671 void MainWindow::openNewFile()
672 {
673     auto window = new MainWindow;
674     window->setAttribute(Qt::WA_DeleteOnClose, true);
675     window->show();
676 }
677
678 void MainWindow::closeFile()
679 {
680     m_ui->pages->setCurrentWidget(m_ui->openPage);
681
682     m_ui->tabWidget->setCurrentIndex(m_ui->tabWidget->indexOf(m_ui->summaryTab));
683     for (int i = 0, c = m_ui->tabWidget->count(); i < c; ++i) {
684         m_ui->tabWidget->setTabEnabled(i, false);
685     }
686
687     m_openAction->setEnabled(false);
688     emit clearData();
689 }
690
691 void MainWindow::showError(const QString& message)
692 {
693     m_ui->messages->setText(message);
694     m_ui->messages->show();
695 }
696
697 void MainWindow::setupStacks()
698 {
699     auto stacksModel = new StacksModel(this);
700     m_ui->stacksTree->setModel(stacksModel);
701     m_ui->stacksTree->setRootIsDecorated(false);
702
703     auto updateStackSpinner = [this](int stacks) {
704         m_ui->stackSpinner->setMinimum(min(stacks, 1));
705         m_ui->stackSpinner->setSuffix(i18n(" / %1", stacks));
706         m_ui->stackSpinner->setMaximum(stacks);
707     };
708     updateStackSpinner(0);
709     connect(stacksModel, &StacksModel::stacksFound, this, updateStackSpinner);
710     connect(m_ui->stackSpinner, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), stacksModel,
711             &StacksModel::setStackIndex);
712
713     auto fillFromIndex = [stacksModel](const QModelIndex& current) {
714         if (!current.isValid()) {
715             stacksModel->clear();
716         } else {
717             auto proxy = qobject_cast<const TreeProxy*>(current.model());
718             Q_ASSERT(proxy);
719             auto leaf = proxy->mapToSource(current);
720             stacksModel->fillFromIndex(leaf);
721         }
722     };
723     connect(m_ui->bottomUpResults->selectionModel(), &QItemSelectionModel::currentChanged, this, fillFromIndex);
724     connect(m_ui->topDownResults->selectionModel(), &QItemSelectionModel::currentChanged, this, fillFromIndex);
725
726     auto tabChanged = [this, fillFromIndex](int tabIndex) {
727         const auto widget = m_ui->tabWidget->widget(tabIndex);
728         const bool showDocks = (widget == m_ui->topDownTab || widget == m_ui->bottomUpTab);
729         m_ui->stacksDock->setVisible(showDocks);
730         if (showDocks) {
731             auto tree = (widget == m_ui->topDownTab) ? m_ui->topDownResults : m_ui->bottomUpResults;
732             fillFromIndex(tree->selectionModel()->currentIndex());
733         }
734 #ifdef QWT_FOUND
735         const auto chartWidget = dynamic_cast<ChartWidget*>(widget);
736         if (chartWidget) {
737             chartWidget->updateOnSelected(this);
738             chartWidget->setFocus(); // to handle keyboard events in the widget
739         }
740         else {
741             if (ChartWidget::HelpWindow != nullptr) {
742                 ChartWidget::HelpWindow->hide();
743             }
744             const auto histogramWidget = dynamic_cast<HistogramWidget*>(widget);
745             if (histogramWidget) {
746                 histogramWidget->updateOnSelected();
747                 histogramWidget->setFocus();
748             }
749         }
750 #endif
751     };
752     connect(m_ui->tabWidget, &QTabWidget::currentChanged, this, tabChanged);
753     connect(m_parser, &Parser::bottomUpDataAvailable, this, [tabChanged]() { tabChanged(0); });
754
755     m_ui->stacksDock->setVisible(false);
756 }
757
758 #ifdef NO_K_LIB
759 static void selectFile(QWidget *parent, QLineEdit *fileNameEdit)
760 {
761     QString fileName = QFileDialog::getOpenFileName(parent, "Select Data File",
762         "", "GZip files (*.gz);; All files (*)");
763     if (!fileName.isEmpty())
764     {
765         fileNameEdit->setText(fileName);
766     }
767 }
768
769 void MainWindow::selectOpenFile()
770 {
771     selectFile(this, m_ui->openFileEdit);
772 }
773
774 void MainWindow::selectCompareToFile()
775 {
776     selectFile(this, m_ui->compareToEdit);
777 }
778
779 void MainWindow::about()
780 {
781     AboutDialog dlg(this);
782     dlg.exec();
783 }
784 #endif
785
786 #ifdef QWT_FOUND
787 void MainWindow::moveEvent(QMoveEvent *event)
788 {
789     if (ChartWidget::HelpWindow != nullptr)
790     {
791         ChartWidget::HelpWindow->move(ChartWidget::HelpWindow->pos() +
792                                       (event->pos() - event->oldPos()));
793     }
794 }
795 #endif