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