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"
67 #include "aboutdata.h"
75 const int MAINWINDOW_VERSION = 1;
79 const char MainWindow[] = "MainWindow";
82 const char State[] = "State";
86 void addContextMenu(QTreeView* treeView, int role)
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()) {
94 const auto location = index.data(role).value<LocationData::Ptr>();
95 if (!location || !QFile::exists(location->file)) {
98 auto menu = new QMenu(treeView);
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);
107 menu->addAction(openFile);
108 menu->popup(treeView->mapToGlobal(pos));
112 void setupTopView(TreeModel* source, QTreeView* view, TopProxy::Type type)
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);
124 void addChartTab(QTabWidget* tabWidget, const QString& title, ChartModel::Type type, const Parser* parser,
125 void (Parser::*dataReady)(const ChartData&), MainWindow* window)
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);
136 QObject::connect(window, &MainWindow::clearData, model, &ChartModel::clearData);
140 void setupTreeModel(TreeModel* model, QTreeView* view, CostDelegate* costDelegate, QLineEdit* filterFunction,
141 QLineEdit* filterFile, QLineEdit* filterModule)
143 auto proxy = new TreeProxy(TreeModel::FunctionColumn, TreeModel::FileColumn, TreeModel::ModuleColumn, model);
144 proxy->setSourceModel(model);
145 proxy->setSortRole(TreeModel::SortRole);
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)
156 view->hideColumn(TreeModel::TemporaryColumn);
158 if(AllocationData::display != AllocationData::DisplayId::managed)
160 view->hideColumn(TreeModel::AllocationsColumn);
161 view->hideColumn(TreeModel::PeakInstancesColumn);
164 view->hideColumn(TreeModel::FunctionColumn);
165 view->hideColumn(TreeModel::FileColumn);
166 view->hideColumn(TreeModel::LineColumn);
167 view->hideColumn(TreeModel::ModuleColumn);
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);
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);
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);
187 void setupCallerCalle(CallerCalleeModel* model, QTreeView* view, CostDelegate* costDelegate, QLineEdit* filterFunction,
188 QLineEdit* filterFile, QLineEdit* filterModule)
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)
214 view->hideColumn(CallerCalleeModel::SelfTemporaryColumn);
215 view->hideColumn(CallerCalleeModel::InclusiveTemporaryColumn);
217 if(AllocationData::display != AllocationData::DisplayId::managed)
219 view->hideColumn(CallerCalleeModel::SelfAllocationsColumn);
220 view->hideColumn(CallerCalleeModel::InclusiveAllocationsColumn);
221 view->hideColumn(CallerCalleeModel::SelfPeakInstancesColumn);
222 view->hideColumn(CallerCalleeModel::InclusivePeakInstancesColumn);
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);
231 QString insertWordWrapMarkers(QString text)
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"));
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")))
248 QSettings settings(QSettings::UserScope, AboutData::Organization, AboutData::applicationName());
249 settings.beginGroup("Charts");
250 QVariant value = settings.value("Options");
252 int options = value.toInt(&ok);
255 ChartOptions::GlobalOptions = ChartOptions::Options(options);
258 #if QT_VERSION >= 0x050A00
259 // seems it doesn't help under Windows (Qt 5.10.0)
260 QCoreApplication::setAttribute(Qt::AA_DontShowShortcutsInContextMenus, false);
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);
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);
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);
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) {
293 m_ui->flameGraphTab->setBottomUpData(data);
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);
301 connect(m_parser, &Parser::objectTreeBottomUpDataAvailable, this, [=](const ObjectTreeData& data) {
303 for (const ObjectRowData& row: data) {
304 if (maxGC < row.gcNum)
307 for (quint32 gc = 0; gc < maxGC; gc++) {
308 m_ui->filterGC->addItem(QString::number(gc+1));
310 objectTreeModel->resetData(data);
311 m_ui->tabWidget->setTabEnabled(m_ui->tabWidget->indexOf(m_ui->heapTab), true);
314 (AccumulatedTraceData::isHideUnmanagedStackParts ?
315 &Parser::bottomUpDataAvailable : &Parser::bottomUpFilterOutLeavesDataAvailable),
316 this, [=](const TreeData& data) {
317 bottomUpModelFilterOutLeaves->resetData(data);
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);
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);
327 m_ui->flameGraphTab->setTopDownData(data);
329 m_ui->tabWidget->setTabEnabled(m_ui->tabWidget->indexOf(m_ui->flameGraphTab), !m_diffMode);
331 connect(m_parser, &Parser::summaryAvailable, this, [=](const SummaryData& data) {
332 bottomUpModelFilterOutLeaves->setSummary(data);
333 topDownModel->setSummary(data);
334 callerCalleeModel->setSummary(data);
338 const double totalTimeS = 0.001 * data.totalTime;
339 const double peakTimeS = 0.001 * data.peakTime;
341 QTextStream stream(&textLeft);
342 const auto debuggee = insertWordWrapMarkers(data.debuggee);
344 << (data.fromAttached ? i18n("<dt><b>debuggee</b>:</dt><dd "
345 "style='font-family:monospace;'>%1 <i>(attached)</i></dd>",
347 : i18n("<dt><b>debuggee</b>:</dt><dd "
348 "style='font-family:monospace;'>%1</dd>",
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))
357 if(AllocationData::display == AllocationData::DisplayId::malloc
358 || AllocationData::display == AllocationData::DisplayId::managed)
360 QTextStream stream(&textCenter);
361 stream << "<qt><dl>" << i18n("<dt><b>calls to allocation functions</b>:</dt><dd>%1 "
363 data.cost.allocations, qint64(data.cost.allocations / totalTimeS))
364 << i18n("<dt><b>temporary allocations</b>:</dt><dd>%1 (%2%, "
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))
375 if (AccumulatedTraceData::isShowCoreCLRPartOption)
377 QTextStream stream(&textRight);
379 if (AllocationData::display == AllocationData::DisplayId::malloc)
381 stream << "<qt><dl>" << i18n("<dt><b>peak heap memory consumption</b>:</dt><dd>%1 "
383 "</dt><dd>%3 (CoreCLR), %4 (non-CoreCLR), %5 (unknown)</dd>",
384 Util::formatByteSize(data.cost.peak, 1),
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))
403 stream << "<qt><dl>" << i18n("<dt><b>peak heap memory consumption</b>:</dt><dd>%1 "
405 "</dt><dd>%3 (CoreCLR), %4 (non-CoreCLR), %5 (sbrk heap), %6 (unknown)</dd>",
406 Util::formatByteSize(data.cost.peak, 1),
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))
428 QTextStream stream(&textRight);
429 stream << "<qt><dl>" << i18n("<dt><b>peak heap memory consumption</b>:</dt><dd>%1 "
431 Util::formatByteSize(data.cost.peak, 1),
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))
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);
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());
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);
458 connect(m_parser, &Parser::finished, this, removeProgress);
459 connect(m_parser, &Parser::failedToOpen, this, [this, removeProgress](const QString& failedFile) {
461 m_ui->pages->setCurrentWidget(m_ui->openPage);
462 showError(i18n("Failed to parse file %1.", failedFile));
464 m_ui->messages->hide();
467 addChartTab(m_ui->tabWidget, i18n("Consumed"), ChartModel::Consumed, m_parser, &Parser::consumedChartDataAvailable,
470 if(AllocationData::display == AllocationData::DisplayId::malloc
471 || AllocationData::display == AllocationData::DisplayId::managed)
473 addChartTab(m_ui->tabWidget, i18n("Instances"), ChartModel::Instances, m_parser,
474 &Parser::instancesChartDataAvailable, this);
476 addChartTab(m_ui->tabWidget, i18n("Allocations"), ChartModel::Allocations, m_parser,
477 &Parser::allocationsChartDataAvailable, this);
479 addChartTab(m_ui->tabWidget, i18n("Allocated"), ChartModel::Allocated, m_parser, &Parser::allocatedChartDataAvailable,
482 if (AllocationData::display == AllocationData::DisplayId::malloc)
484 addChartTab(m_ui->tabWidget, i18n("Temporary Allocations"), ChartModel::Temporary, m_parser,
485 &Parser::temporaryChartDataAvailable, this);
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);
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);
502 connect(m_ui->aboutAction, &QAction::triggered, this, &MainWindow::about);
505 auto costDelegate = new CostDelegate(this);
507 setupTreeModel(bottomUpModelFilterOutLeaves, m_ui->bottomUpResults, costDelegate, m_ui->bottomUpFilterFunction,
508 m_ui->bottomUpFilterFile, m_ui->bottomUpFilterModule);
510 setupTreeModel(topDownModel, m_ui->topDownResults, costDelegate, m_ui->topDownFilterFunction,
511 m_ui->topDownFilterFile, m_ui->topDownFilterModule);
513 setupCallerCalle(callerCalleeModel, m_ui->callerCalleeResults, costDelegate, m_ui->callerCalleeFilterFunction,
514 m_ui->callerCalleeFilterFile, m_ui->callerCalleeFilterModule);
516 setupObjectTreeModel(objectTreeModel, m_ui->objectTreeResults, m_ui->filterClass, m_ui->filterGC);
518 auto validateInputFile = [this](const QString& path, bool allowEmpty) -> bool {
519 m_ui->messages->hide();
520 if (path.isEmpty()) {
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));
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));
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);
552 m_ui->buttonBox->setEnabled(true);
554 auto validateInputAndLoadFile = [this, validateInputFile]() {
555 const auto path = m_ui->openFileEdit->text();
556 if (!validateInputFile(path, false)) {
559 Q_ASSERT(!path.isEmpty());
560 const auto base = m_ui->compareToEdit->text();
561 if (!validateInputFile(base, true)) {
564 QApplication::setOverrideCursor(Qt::WaitCursor);
565 loadFile(path, base);
566 QApplication::restoreOverrideCursor();
569 connect(m_ui->buttonBox, &QDialogButtonBox::clicked, this, validateInputAndLoadFile);
571 connect(m_ui->openFileButton1, &QPushButton::clicked, this, &MainWindow::selectOpenFile);
572 connect(m_ui->openFileButton2, &QPushButton::clicked, this, &MainWindow::selectCompareToFile);
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);
584 if(AllocationData::display == AllocationData::DisplayId::malloc
585 || AllocationData::display == AllocationData::DisplayId::managed)
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);
592 if (AllocationData::display == AllocationData::DisplayId::malloc)
594 setupTopView(bottomUpModelFilterOutLeaves, m_ui->topTemporary, TopProxy::Temporary);
595 m_ui->topTemporary->setItemDelegate(costDelegate);
599 m_ui->widget_8->hide();
604 m_ui->widget_7->hide();
605 m_ui->widget_8->hide();
606 m_ui->widget_9->hide();
607 m_ui->widget_12->hide();
610 setWindowTitle(AboutData::ShortName);
611 // closing the current file shows the stack page to open a new one
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);
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);
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);
637 MainWindow::~MainWindow()
641 QSettings settings(QSettings::UserScope, AboutData::Organization, AboutData::applicationName());
642 settings.beginGroup("Charts");
643 settings.setValue("Options", ChartOptions::GlobalOptions);
647 auto state = saveState(MAINWINDOW_VERSION);
648 auto group = m_config->group(Config::Groups::MainWindow);
649 group.writeEntry(Config::Entries::State, state);
653 void MainWindow::loadFile(const QString& file, const QString& diffBase)
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()));
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()));
667 m_ui->pages->setCurrentWidget(m_ui->loadingPage);
668 m_parser->parse(file, diffBase);
671 void MainWindow::openNewFile()
673 auto window = new MainWindow;
674 window->setAttribute(Qt::WA_DeleteOnClose, true);
678 void MainWindow::closeFile()
680 m_ui->pages->setCurrentWidget(m_ui->openPage);
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);
687 m_openAction->setEnabled(false);
691 void MainWindow::showError(const QString& message)
693 m_ui->messages->setText(message);
694 m_ui->messages->show();
697 void MainWindow::setupStacks()
699 auto stacksModel = new StacksModel(this);
700 m_ui->stacksTree->setModel(stacksModel);
701 m_ui->stacksTree->setRootIsDecorated(false);
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);
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);
713 auto fillFromIndex = [stacksModel](const QModelIndex& current) {
714 if (!current.isValid()) {
715 stacksModel->clear();
717 auto proxy = qobject_cast<const TreeProxy*>(current.model());
719 auto leaf = proxy->mapToSource(current);
720 stacksModel->fillFromIndex(leaf);
723 connect(m_ui->bottomUpResults->selectionModel(), &QItemSelectionModel::currentChanged, this, fillFromIndex);
724 connect(m_ui->topDownResults->selectionModel(), &QItemSelectionModel::currentChanged, this, fillFromIndex);
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);
731 auto tree = (widget == m_ui->topDownTab) ? m_ui->topDownResults : m_ui->bottomUpResults;
732 fillFromIndex(tree->selectionModel()->currentIndex());
735 const auto chartWidget = dynamic_cast<ChartWidget*>(widget);
737 chartWidget->updateOnSelected(this);
738 chartWidget->setFocus(); // to handle keyboard events in the widget
741 if (ChartWidget::HelpWindow != nullptr) {
742 ChartWidget::HelpWindow->hide();
744 const auto histogramWidget = dynamic_cast<HistogramWidget*>(widget);
745 if (histogramWidget) {
746 histogramWidget->updateOnSelected();
747 histogramWidget->setFocus();
752 connect(m_ui->tabWidget, &QTabWidget::currentChanged, this, tabChanged);
753 connect(m_parser, &Parser::bottomUpDataAvailable, this, [tabChanged]() { tabChanged(0); });
755 m_ui->stacksDock->setVisible(false);
759 static void selectFile(QWidget *parent, QLineEdit *fileNameEdit)
761 QString fileName = QFileDialog::getOpenFileName(parent, "Select Data File",
762 "", "GZip files (*.gz);; All files (*)");
763 if (!fileName.isEmpty())
765 fileNameEdit->setText(fileName);
769 void MainWindow::selectOpenFile()
771 selectFile(this, m_ui->openFileEdit);
774 void MainWindow::selectCompareToFile()
776 selectFile(this, m_ui->compareToEdit);
779 void MainWindow::about()
781 AboutDialog dlg(this);
787 void MainWindow::moveEvent(QMoveEvent *event)
789 if (ChartWidget::HelpWindow != nullptr)
791 ChartWidget::HelpWindow->move(ChartWidget::HelpWindow->pos() +
792 (event->pos() - event->oldPos()));