2 * Copyright 2015-2017 Milian Wolff <mail@milianw.de>
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.
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.
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
19 #include "mainwindow.h"
25 #include <ui_mainwindow_noklib.h>
26 #include "aboutdialog.h"
27 #include <QAbstractButton>
28 #include <QFileDialog>
30 #include <ui_mainwindow.h>
31 #include <KConfigGroup>
32 #include <KLocalizedString>
33 #include <KRecursiveFilterProxyModel>
34 #include <KStandardAction>
41 #include <QDesktopServices>
42 #include <QFileDialog>
47 #include "../accumulatedtracedata.h"
48 #include "callercalleemodel.h"
49 #include "costdelegate.h"
51 #include "stacksmodel.h"
53 #include "treemodel.h"
54 #include "objecttreemodel.h"
55 #include "treeproxy.h"
56 #include "objecttreeproxy.h"
58 #include "gui_config.h"
61 #include "chartmodel.h"
62 #include "chartproxy.h"
63 #include "chartwidget.h"
64 #include "histogrammodel.h"
65 #include "histogramwidget.h"
71 const int MAINWINDOW_VERSION = 1;
75 const char MainWindow[] = "MainWindow";
78 const char State[] = "State";
82 void addContextMenu(QTreeView* treeView, int role)
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()) {
90 const auto location = index.data(role).value<LocationData::Ptr>();
91 if (!location || !QFile::exists(location->file)) {
94 auto menu = new QMenu(treeView);
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);
103 menu->addAction(openFile);
104 menu->popup(treeView->mapToGlobal(pos));
108 void setupTopView(TreeModel* source, QTreeView* view, TopProxy::Type type)
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);
120 void addChartTab(QTabWidget* tabWidget, const QString& title, ChartModel::Type type, const Parser* parser,
121 void (Parser::*dataReady)(const ChartData&), MainWindow* window)
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);
132 QObject::connect(window, &MainWindow::clearData, model, &ChartModel::clearData);
136 void setupTreeModel(TreeModel* model, QTreeView* view, CostDelegate* costDelegate, QLineEdit* filterFunction,
137 QLineEdit* filterFile, QLineEdit* filterModule)
139 auto proxy = new TreeProxy(TreeModel::FunctionColumn, TreeModel::FileColumn, TreeModel::ModuleColumn, model);
140 proxy->setSourceModel(model);
141 proxy->setSortRole(TreeModel::SortRole);
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)
152 view->hideColumn(TreeModel::TemporaryColumn);
154 if(AllocationData::display != AllocationData::DisplayId::managed)
156 view->hideColumn(TreeModel::AllocationsColumn);
157 view->hideColumn(TreeModel::PeakInstancesColumn);
160 view->hideColumn(TreeModel::FunctionColumn);
161 view->hideColumn(TreeModel::FileColumn);
162 view->hideColumn(TreeModel::LineColumn);
163 view->hideColumn(TreeModel::ModuleColumn);
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);
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);
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);
183 void setupCallerCalle(CallerCalleeModel* model, QTreeView* view, CostDelegate* costDelegate, QLineEdit* filterFunction,
184 QLineEdit* filterFile, QLineEdit* filterModule)
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)
210 view->hideColumn(CallerCalleeModel::SelfTemporaryColumn);
211 view->hideColumn(CallerCalleeModel::InclusiveTemporaryColumn);
213 if(AllocationData::display != AllocationData::DisplayId::managed)
215 view->hideColumn(CallerCalleeModel::SelfAllocationsColumn);
216 view->hideColumn(CallerCalleeModel::InclusiveAllocationsColumn);
217 view->hideColumn(CallerCalleeModel::SelfPeakInstancesColumn);
218 view->hideColumn(CallerCalleeModel::InclusivePeakInstancesColumn);
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);
227 QString insertWordWrapMarkers(QString text)
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"));
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")))
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);
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);
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);
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);
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) {
277 m_ui->flameGraphTab->setBottomUpData(data);
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);
285 connect(m_parser, &Parser::objectTreeBottomUpDataAvailable, this, [=](const ObjectTreeData& data) {
287 for (const ObjectRowData& row: data) {
288 if (maxGC < row.gcNum)
291 for (quint32 gc = 0; gc < maxGC; gc++) {
292 m_ui->filterGC->addItem(QString::number(gc+1));
294 objectTreeModel->resetData(data);
295 m_ui->tabWidget->setTabEnabled(m_ui->tabWidget->indexOf(m_ui->heapTab), true);
298 (AccumulatedTraceData::isHideUnmanagedStackParts ?
299 &Parser::bottomUpDataAvailable : &Parser::bottomUpFilterOutLeavesDataAvailable),
300 this, [=](const TreeData& data) {
301 bottomUpModelFilterOutLeaves->resetData(data);
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);
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);
311 m_ui->flameGraphTab->setTopDownData(data);
313 m_ui->tabWidget->setTabEnabled(m_ui->tabWidget->indexOf(m_ui->flameGraphTab), !m_diffMode);
315 connect(m_parser, &Parser::summaryAvailable, this, [=](const SummaryData& data) {
316 bottomUpModelFilterOutLeaves->setSummary(data);
317 topDownModel->setSummary(data);
318 callerCalleeModel->setSummary(data);
322 const double totalTimeS = 0.001 * data.totalTime;
323 const double peakTimeS = 0.001 * data.peakTime;
325 QTextStream stream(&textLeft);
326 const auto debuggee = insertWordWrapMarkers(data.debuggee);
328 << (data.fromAttached ? i18n("<dt><b>debuggee</b>:</dt><dd "
329 "style='font-family:monospace;'>%1 <i>(attached)</i></dd>",
331 : i18n("<dt><b>debuggee</b>:</dt><dd "
332 "style='font-family:monospace;'>%1</dd>",
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))
341 if(AllocationData::display == AllocationData::DisplayId::malloc
342 || AllocationData::display == AllocationData::DisplayId::managed)
344 QTextStream stream(&textCenter);
345 stream << "<qt><dl>" << i18n("<dt><b>calls to allocation functions</b>:</dt><dd>%1 "
347 data.cost.allocations, qint64(data.cost.allocations / totalTimeS))
348 << i18n("<dt><b>temporary allocations</b>:</dt><dd>%1 (%2%, "
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))
359 if (AccumulatedTraceData::isShowCoreCLRPartOption)
361 QTextStream stream(&textRight);
363 if (AllocationData::display == AllocationData::DisplayId::malloc)
365 stream << "<qt><dl>" << i18n("<dt><b>peak heap memory consumption</b>:</dt><dd>%1 "
367 "</dt><dd>%3 (CoreCLR), %4 (non-CoreCLR), %5 (unknown)</dd>",
368 Util::formatByteSize(data.cost.peak, 1),
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))
386 stream << "<qt><dl>" << i18n("<dt><b>peak heap memory consumption</b>:</dt><dd>%1 "
388 "</dt><dd>%3 (CoreCLR), %4 (non-CoreCLR), %5 (sbrk heap), %6 (unknown)</dd>",
389 Util::formatByteSize(data.cost.peak, 1),
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))
410 QTextStream stream(&textRight);
411 stream << "<qt><dl>" << i18n("<dt><b>peak heap memory consumption</b>:</dt><dd>%1 "
413 Util::formatByteSize(data.cost.peak, 1),
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))
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);
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());
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);
439 connect(m_parser, &Parser::finished, this, removeProgress);
440 connect(m_parser, &Parser::failedToOpen, this, [this, removeProgress](const QString& failedFile) {
442 m_ui->pages->setCurrentWidget(m_ui->openPage);
443 showError(i18n("Failed to parse file %1.", failedFile));
445 m_ui->messages->hide();
448 addChartTab(m_ui->tabWidget, i18n("Consumed"), ChartModel::Consumed, m_parser, &Parser::consumedChartDataAvailable,
451 if(AllocationData::display == AllocationData::DisplayId::malloc
452 || AllocationData::display == AllocationData::DisplayId::managed)
454 addChartTab(m_ui->tabWidget, i18n("Instances"), ChartModel::Instances, m_parser,
455 &Parser::instancesChartDataAvailable, this);
457 addChartTab(m_ui->tabWidget, i18n("Allocations"), ChartModel::Allocations, m_parser,
458 &Parser::allocationsChartDataAvailable, this);
460 addChartTab(m_ui->tabWidget, i18n("Allocated"), ChartModel::Allocated, m_parser, &Parser::allocatedChartDataAvailable,
463 if (AllocationData::display == AllocationData::DisplayId::malloc)
465 addChartTab(m_ui->tabWidget, i18n("Temporary Allocations"), ChartModel::Temporary, m_parser,
466 &Parser::temporaryChartDataAvailable, this);
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);
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);
483 connect(m_ui->aboutAction, &QAction::triggered, this, &MainWindow::about);
486 auto costDelegate = new CostDelegate(this);
488 setupTreeModel(bottomUpModelFilterOutLeaves, m_ui->bottomUpResults, costDelegate, m_ui->bottomUpFilterFunction,
489 m_ui->bottomUpFilterFile, m_ui->bottomUpFilterModule);
491 setupTreeModel(topDownModel, m_ui->topDownResults, costDelegate, m_ui->topDownFilterFunction,
492 m_ui->topDownFilterFile, m_ui->topDownFilterModule);
494 setupCallerCalle(callerCalleeModel, m_ui->callerCalleeResults, costDelegate, m_ui->callerCalleeFilterFunction,
495 m_ui->callerCalleeFilterFile, m_ui->callerCalleeFilterModule);
497 setupObjectTreeModel(objectTreeModel, m_ui->objectTreeResults, m_ui->filterClass, m_ui->filterGC);
499 auto validateInputFile = [this](const QString& path, bool allowEmpty) -> bool {
500 m_ui->messages->hide();
501 if (path.isEmpty()) {
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));
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));
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);
533 m_ui->buttonBox->setEnabled(true);
535 auto validateInputAndLoadFile = [this, validateInputFile]() {
536 const auto path = m_ui->openFileEdit->text();
537 if (!validateInputFile(path, false)) {
540 Q_ASSERT(!path.isEmpty());
541 const auto base = m_ui->compareToEdit->text();
542 if (!validateInputFile(base, true)) {
545 QApplication::setOverrideCursor(Qt::WaitCursor);
546 loadFile(path, base);
547 QApplication::restoreOverrideCursor();
550 connect(m_ui->buttonBox, &QDialogButtonBox::clicked, this, validateInputAndLoadFile);
552 connect(m_ui->openFileButton1, &QPushButton::clicked, this, &MainWindow::selectOpenFile);
553 connect(m_ui->openFileButton2, &QPushButton::clicked, this, &MainWindow::selectCompareToFile);
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);
565 if(AllocationData::display == AllocationData::DisplayId::malloc
566 || AllocationData::display == AllocationData::DisplayId::managed)
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);
573 if (AllocationData::display == AllocationData::DisplayId::malloc)
575 setupTopView(bottomUpModelFilterOutLeaves, m_ui->topTemporary, TopProxy::Temporary);
576 m_ui->topTemporary->setItemDelegate(costDelegate);
580 m_ui->widget_8->hide();
585 m_ui->widget_7->hide();
586 m_ui->widget_8->hide();
587 m_ui->widget_9->hide();
588 m_ui->widget_12->hide();
591 setWindowTitle(i18n("Heaptrack"));
592 // closing the current file shows the stack page to open a new one
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);
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);
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);
618 MainWindow::~MainWindow()
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);
627 void MainWindow::loadFile(const QString& file, const QString& diffBase)
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()));
636 setWindowTitle(i18nc("%1, %2: file names that are open", "Heaptrack - %1 compared to %2",
637 QFileInfo(file).fileName(), QFileInfo(diffBase).fileName()));
640 m_ui->pages->setCurrentWidget(m_ui->loadingPage);
641 m_parser->parse(file, diffBase);
644 void MainWindow::openNewFile()
646 auto window = new MainWindow;
647 window->setAttribute(Qt::WA_DeleteOnClose, true);
651 void MainWindow::closeFile()
653 m_ui->pages->setCurrentWidget(m_ui->openPage);
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);
660 m_openAction->setEnabled(false);
664 void MainWindow::showError(const QString& message)
666 m_ui->messages->setText(message);
667 m_ui->messages->show();
670 void MainWindow::setupStacks()
672 auto stacksModel = new StacksModel(this);
673 m_ui->stacksTree->setModel(stacksModel);
674 m_ui->stacksTree->setRootIsDecorated(false);
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);
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);
686 auto fillFromIndex = [stacksModel](const QModelIndex& current) {
687 if (!current.isValid()) {
688 stacksModel->clear();
690 auto proxy = qobject_cast<const TreeProxy*>(current.model());
692 auto leaf = proxy->mapToSource(current);
693 stacksModel->fillFromIndex(leaf);
696 connect(m_ui->bottomUpResults->selectionModel(), &QItemSelectionModel::currentChanged, this, fillFromIndex);
697 connect(m_ui->topDownResults->selectionModel(), &QItemSelectionModel::currentChanged, this, fillFromIndex);
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);
704 auto tree = (widget == m_ui->topDownTab) ? m_ui->topDownResults : m_ui->bottomUpResults;
705 fillFromIndex(tree->selectionModel()->currentIndex());
708 const auto chartWidget = dynamic_cast<ChartWidget*>(widget);
710 chartWidget->updateOnSelected(this);
711 chartWidget->setFocus(); // to handle keyboard events in the widget
714 if (ChartWidget::HelpWindow != nullptr) {
715 ChartWidget::HelpWindow->hide();
717 const auto histogramWidget = dynamic_cast<HistogramWidget*>(widget);
718 if (histogramWidget) {
719 histogramWidget->updateOnSelected();
720 histogramWidget->setFocus();
725 connect(m_ui->tabWidget, &QTabWidget::currentChanged, this, tabChanged);
726 connect(m_parser, &Parser::bottomUpDataAvailable, this, [tabChanged]() { tabChanged(0); });
728 m_ui->stacksDock->setVisible(false);
732 static void selectFile(QWidget *parent, QLineEdit *fileNameEdit)
734 QString fileName = QFileDialog::getOpenFileName(parent, "Select Data File",
735 "", "GZip files (*.gz);; All files (*)");
736 if (!fileName.isEmpty())
738 fileNameEdit->setText(fileName);
742 void MainWindow::selectOpenFile()
744 selectFile(this, m_ui->openFileEdit);
747 void MainWindow::selectCompareToFile()
749 selectFile(this, m_ui->compareToEdit);
752 void MainWindow::about()
754 AboutDialog dlg(this);
760 void MainWindow::moveEvent(QMoveEvent *event)
762 if (ChartWidget::HelpWindow != nullptr)
764 ChartWidget::HelpWindow->move(ChartWidget::HelpWindow->pos() +
765 (event->pos() - event->oldPos()));