File indexing completed on 2024-04-28 17:02:20
0001 /* 0002 This file is part of Massif Visualizer 0003 0004 Copyright 2010 Milian Wolff <mail@milianw.de> 0005 Copyright 2013 Arnold Dumas <contact@arnolddumas.fr> 0006 0007 This program is free software; you can redistribute it and/or 0008 modify it under the terms of the GNU General Public License as 0009 published by the Free Software Foundation; either version 2 of 0010 the License or (at your option) version 3 or any later version 0011 accepted by the membership of KDE e.V. (or its successor approved 0012 by the membership of KDE e.V.), which shall act as a proxy 0013 defined in Section 14 of version 3 of the license. 0014 0015 This program is distributed in the hope that it will be useful, 0016 but WITHOUT ANY WARRANTY; without even the implied warranty of 0017 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0018 GNU General Public License for more details. 0019 0020 You should have received a copy of the GNU General Public License 0021 along with this program. If not, see <http://www.gnu.org/licenses/>. 0022 */ 0023 0024 #include "mainwindow.h" 0025 0026 #include "massifdata/filedata.h" 0027 #include "massifdata/snapshotitem.h" 0028 #include "massifdata/treeleafitem.h" 0029 #include "massifdata/util.h" 0030 0031 #include "visualizer/totalcostmodel.h" 0032 #include "visualizer/detailedcostmodel.h" 0033 #include "visualizer/datatreemodel.h" 0034 #include "visualizer/filtereddatatreemodel.h" 0035 #include "visualizer/dotgraphgenerator.h" 0036 0037 #include "massif-visualizer-settings.h" 0038 #include "configdialog.h" 0039 0040 #include <KStandardAction> 0041 #include <KActionCollection> 0042 #include <KRecentFilesAction> 0043 #include <KColorScheme> 0044 #include <KToolBar> 0045 #include <KParts/Part> 0046 #include <KPluginFactory> 0047 #include <KPluginLoader> 0048 #include <KXMLGUIFactory> 0049 #include <KLocalizedString> 0050 0051 #include <QFileDialog> 0052 #include <QSortFilterProxyModel> 0053 #include <QStringListModel> 0054 #include <QLabel> 0055 #include <QSpinBox> 0056 #include <QInputDialog> 0057 #include <QIcon> 0058 0059 #include <KMessageBox> 0060 0061 #ifdef HAVE_KGRAPHVIEWER 0062 #include <kgraphviewer_interface.h> 0063 #endif 0064 0065 using namespace Massif; 0066 0067 // Helper function 0068 static KConfigGroup allocatorConfig() 0069 { 0070 return KSharedConfig::openConfig()->group("Allocators"); 0071 } 0072 0073 MainWindow::MainWindow(QWidget* parent, Qt::WindowFlags f) 0074 : KParts::MainWindow(parent, f) 0075 , m_recentFiles(0) 0076 , m_close(0) 0077 , m_allocatorModel(new QStringListModel(this)) 0078 , m_newAllocator(0) 0079 , m_removeAllocator(0) 0080 , m_shortenTemplates(0) 0081 , m_selectPeak(0) 0082 , m_currentDocument(0) 0083 , m_dataTreeModel(new DataTreeModel(this)) 0084 , m_dataTreeFilterModel(new FilteredDataTreeModel(m_dataTreeModel)) 0085 , m_settingSelection(false) 0086 { 0087 ui.setupUi(this); 0088 0089 //BEGIN KGraphViewer 0090 bool haveGraphViewer = false; 0091 0092 // NOTE: just check if kgraphviewer is available at runtime. 0093 // The former logic has been moved to DocumentWidget constructor. 0094 #ifdef HAVE_KGRAPHVIEWER 0095 KPluginFactory *factory = KPluginLoader("kgraphviewerpart").factory(); 0096 if (factory) { 0097 KParts::ReadOnlyPart* readOnlyPart = factory->create<KParts::ReadOnlyPart>("kgraphviewerpart", this); 0098 if (readOnlyPart) { 0099 readOnlyPart->widget()->hide(); 0100 haveGraphViewer = true; 0101 } 0102 } 0103 #endif 0104 0105 if (!haveGraphViewer) { 0106 // cleanup UI when we installed with kgraphviewer but it's not available at runtime 0107 KToolBar* callgraphToolbar = toolBar(QStringLiteral("callgraphToolBar")); 0108 removeToolBar(callgraphToolbar); 0109 delete callgraphToolbar; 0110 } 0111 //END KGraphViewer 0112 0113 ui.documents->setMovable(true); 0114 ui.documents->setTabsClosable(true); 0115 connect(ui.documents, &QTabWidget::currentChanged, 0116 this, &MainWindow::documentChanged); 0117 connect(ui.documents, &QTabWidget::tabCloseRequested, 0118 this, &MainWindow::closeFileTab); 0119 0120 //BEGIN custom allocators 0121 tabifyDockWidget(ui.allocatorDock, ui.dataTreeDock); 0122 ui.allocatorView->setModel(m_allocatorModel); 0123 0124 int iconSize = style()->pixelMetric(QStyle::PM_SmallIconSize); 0125 ui.dockMenuBar->setIconSize(QSize(iconSize, iconSize)); 0126 ui.dockMenuBar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); 0127 ui.dockMenuBar->setFloatable(false); 0128 ui.dockMenuBar->setMovable(false); 0129 0130 KConfigGroup cfg = allocatorConfig(); 0131 m_allocatorModel->setStringList(cfg.entryMap().values()); 0132 0133 connect(m_allocatorModel, &QStringListModel::modelReset, 0134 this, &MainWindow::allocatorsChanged); 0135 0136 connect(m_allocatorModel, &QStringListModel::dataChanged, 0137 this, &MainWindow::allocatorsChanged); 0138 0139 connect(ui.dataTreeView, &QTreeView::customContextMenuRequested, 0140 this, &MainWindow::dataTreeContextMenuRequested); 0141 ui.dataTreeView->setContextMenuPolicy(Qt::CustomContextMenu); 0142 0143 connect(ui.allocatorView, &QTreeView::customContextMenuRequested, 0144 this, &MainWindow::allocatorViewContextMenuRequested); 0145 ui.allocatorView->setContextMenuPolicy(Qt::CustomContextMenu); 0146 //END custom allocators 0147 0148 setupActions(); 0149 setupGUI(StandardWindowOptions(Default ^ StatusBar)); 0150 statusBar()->hide(); 0151 0152 ui.dataTreeView->setModel(m_dataTreeFilterModel); 0153 0154 connect(ui.filterDataTree, &KLineEdit::textChanged, 0155 m_dataTreeFilterModel, &FilteredDataTreeModel::setFilter); 0156 connect(ui.dataTreeView->selectionModel(), &QItemSelectionModel::currentChanged, 0157 this, &MainWindow::treeSelectionChanged); 0158 0159 // open page 0160 ui.stackedWidget->setCurrentWidget(ui.openPage); 0161 } 0162 0163 MainWindow::~MainWindow() 0164 { 0165 while (ui.documents->count()) { 0166 closeCurrentFile(); 0167 } 0168 0169 m_recentFiles->saveEntries(KSharedConfig::openConfig()->group( QString() )); 0170 } 0171 0172 void MainWindow::setupActions() 0173 { 0174 QAction* openFile = KStandardAction::open(this, SLOT(openFile()), actionCollection()); 0175 m_recentFiles = KStandardAction::openRecent(this, SLOT(openFile(QUrl)), actionCollection()); 0176 m_recentFiles->loadEntries(KSharedConfig::openConfig()->group( QString() )); 0177 0178 QAction* reload = KStandardAction::redisplay(this, SLOT(reloadCurrentFile()), actionCollection()); 0179 actionCollection()->addAction(QStringLiteral("file_reload"), reload); 0180 reload->setEnabled(false); 0181 0182 m_close = KStandardAction::close(this, SLOT(closeCurrentFile()), actionCollection()); 0183 m_close->setEnabled(false); 0184 0185 KStandardAction::quit(qApp, SLOT(closeAllWindows()), actionCollection()); 0186 0187 KStandardAction::preferences(this, SLOT(preferences()), actionCollection()); 0188 0189 m_shortenTemplates = new QAction(QIcon::fromTheme(QStringLiteral("shortentemplates")), i18n("Shorten Templates"), actionCollection()); 0190 m_shortenTemplates->setCheckable(true); 0191 m_shortenTemplates->setChecked(Settings::shortenTemplates()); 0192 connect(m_shortenTemplates, &QAction::toggled, this, &MainWindow::slotShortenTemplates); 0193 actionCollection()->addAction(QStringLiteral("shorten_templates"), m_shortenTemplates); 0194 0195 m_selectPeak = new QAction(QIcon::fromTheme(QStringLiteral("flag-red")), i18n("Select peak snapshot"), actionCollection()); 0196 connect(m_selectPeak, &QAction::triggered, this, &MainWindow::selectPeakSnapshot); 0197 actionCollection()->addAction(QStringLiteral("selectPeak"), m_selectPeak); 0198 m_selectPeak->setEnabled(false); 0199 0200 //BEGIN custom allocators 0201 m_newAllocator = new QAction(QIcon::fromTheme(QStringLiteral("list-add")), i18n("add"), ui.allocatorDock); 0202 m_newAllocator->setToolTip(i18n("add custom allocator")); 0203 connect(m_newAllocator, &QAction::triggered, this, &MainWindow::slotNewAllocator); 0204 ui.dockMenuBar->addAction(m_newAllocator); 0205 m_removeAllocator = new QAction(QIcon::fromTheme(QStringLiteral("list-remove")), i18n("remove"), 0206 ui.allocatorDock); 0207 m_newAllocator->setToolTip(i18n("remove selected allocator")); 0208 connect(m_removeAllocator, &QAction::triggered, this, &MainWindow::slotRemoveAllocator); 0209 connect(ui.allocatorView->selectionModel(), &QItemSelectionModel::selectionChanged, 0210 this, &MainWindow::allocatorSelectionChanged); 0211 m_removeAllocator->setEnabled(false); 0212 ui.dockMenuBar->addAction(m_removeAllocator); 0213 0214 m_markCustomAllocator = new QAction(i18n("mark as custom allocator"), ui.allocatorDock); 0215 connect(m_markCustomAllocator, &QAction::triggered, 0216 this, &MainWindow::slotMarkCustomAllocator, Qt::QueuedConnection); 0217 //END custom allocators 0218 0219 //dock actions 0220 actionCollection()->addAction(QStringLiteral("toggleDataTree"), ui.dataTreeDock->toggleViewAction()); 0221 actionCollection()->addAction(QStringLiteral("toggleAllocators"), ui.allocatorDock->toggleViewAction()); 0222 0223 //open page actions 0224 ui.openFile->setDefaultAction(openFile); 0225 ui.openFile->setText(i18n("Open Massif Data File")); 0226 ui.openFile->setIconSize(QSize(48, 48)); 0227 } 0228 0229 void MainWindow::preferences() 0230 { 0231 if (ConfigDialog::isShown()) { 0232 return; 0233 } 0234 0235 ConfigDialog* dlg = new ConfigDialog(this); 0236 connect(dlg, &ConfigDialog::settingsChanged, 0237 this, &MainWindow::settingsChanged); 0238 dlg->show(); 0239 } 0240 0241 void MainWindow::settingsChanged() 0242 { 0243 if (Settings::self()->shortenTemplates() != m_shortenTemplates->isChecked()) { 0244 m_shortenTemplates->setChecked(Settings::self()->shortenTemplates()); 0245 } 0246 0247 Settings::self()->save(); 0248 0249 if (m_currentDocument) { 0250 m_currentDocument->settingsChanged(); 0251 } 0252 ui.dataTreeView->viewport()->update(); 0253 } 0254 0255 void MainWindow::openFile() 0256 { 0257 const QList<QUrl> files = QFileDialog::getOpenFileUrls(this, i18n("Open Massif Output File"), QUrl(), 0258 i18n("Massif data files (massif.out.*)")); 0259 foreach (const QUrl& file, files) { 0260 openFile(file); 0261 } 0262 } 0263 0264 void MainWindow::reloadCurrentFile() 0265 { 0266 if (m_currentDocument->file().isValid()) { 0267 openFile(QUrl(m_currentDocument->file())); 0268 } 0269 } 0270 0271 void MainWindow::openFile(const QUrl& file) 0272 { 0273 Q_ASSERT(file.isValid()); 0274 0275 // Is file already opened ? 0276 int indexToInsert = -1; 0277 for (int i = 0; i < ui.documents->count(); ++i) { 0278 if (qobject_cast<DocumentWidget*>(ui.documents->widget(i))->file() == file) { 0279 indexToInsert = i; 0280 break; 0281 } 0282 } 0283 0284 DocumentWidget* documentWidget = new DocumentWidget(file, m_allocatorModel->stringList(), 0285 this, this); 0286 0287 if (indexToInsert != -1) { 0288 // Remove existing instance of the file. 0289 ui.documents->setCurrentIndex(indexToInsert); 0290 closeCurrentFile(); 0291 // Insert the new tab at the correct position. 0292 ui.documents->insertTab(indexToInsert, documentWidget, file.fileName()); 0293 ui.documents->setCurrentIndex(indexToInsert); 0294 } else { 0295 const int idx = ui.documents->addTab(documentWidget, file.fileName()); 0296 ui.documents->setCurrentIndex(idx); 0297 } 0298 connect(documentWidget, &DocumentWidget::loadingFinished, 0299 this, &MainWindow::documentChanged); 0300 connect(documentWidget, &DocumentWidget::requestClose, 0301 this, &MainWindow::closeRequested); 0302 0303 m_recentFiles->addUrl(file); 0304 ui.stackedWidget->setCurrentWidget(ui.displayPage); 0305 } 0306 0307 void MainWindow::treeSelectionChanged(const QModelIndex& now, const QModelIndex& before) 0308 { 0309 if (!m_currentDocument || m_settingSelection || now == before) { 0310 return; 0311 } 0312 0313 m_settingSelection = true; 0314 0315 const ModelItem& item = now.data(DataTreeModel::ModelItemRole).value<ModelItem>(); 0316 m_currentDocument->selectModelItem(item); 0317 0318 m_settingSelection = false; 0319 } 0320 0321 void MainWindow::modelItemSelected(const ModelItem& item) 0322 { 0323 if (!m_currentDocument || m_settingSelection) { 0324 return; 0325 } 0326 0327 m_settingSelection = true; 0328 0329 const QModelIndex& newIndex = m_dataTreeFilterModel->mapFromSource( 0330 m_dataTreeModel->indexForItem(item) 0331 ); 0332 ui.dataTreeView->selectionModel()->clearSelection(); 0333 ui.dataTreeView->selectionModel()->setCurrentIndex(newIndex, QItemSelectionModel::Select | QItemSelectionModel::Rows); 0334 ui.dataTreeView->scrollTo(ui.dataTreeView->selectionModel()->currentIndex()); 0335 m_currentDocument->selectModelItem(item); 0336 0337 m_settingSelection = false; 0338 } 0339 0340 void MainWindow::selectPeakSnapshot() 0341 { 0342 ui.dataTreeView->selectionModel()->clearSelection(); 0343 ui.dataTreeView->selectionModel()->setCurrentIndex( 0344 m_dataTreeFilterModel->mapFromSource( 0345 m_dataTreeModel->indexForSnapshot(m_currentDocument->data()->peak())), 0346 QItemSelectionModel::Select | QItemSelectionModel::Rows 0347 ); 0348 } 0349 0350 void MainWindow::closeCurrentFile() 0351 { 0352 closeFileTab(ui.documents->currentIndex()); 0353 } 0354 0355 void MainWindow::closeRequested() 0356 { 0357 DocumentWidget* widget = qobject_cast<DocumentWidget*>(sender()); 0358 Q_ASSERT(widget); 0359 closeFileTab(ui.documents->indexOf(widget)); 0360 } 0361 0362 void MainWindow::closeFileTab(int idx) 0363 { 0364 Q_ASSERT(idx != -1); 0365 ui.documents->widget(idx)->deleteLater(); 0366 ui.documents->removeTab(idx); 0367 } 0368 0369 void MainWindow::allocatorsChanged() 0370 { 0371 KConfigGroup cfg = allocatorConfig(); 0372 cfg.deleteGroup(); 0373 int i = 0; 0374 foreach(const QString& allocator, m_allocatorModel->stringList()) { 0375 if (allocator.isEmpty()) { 0376 m_allocatorModel->removeRow(i); 0377 continue; 0378 } 0379 cfg.writeEntry(QString::number(i++), allocator); 0380 } 0381 cfg.sync(); 0382 0383 if (m_currentDocument) { 0384 reloadCurrentFile(); 0385 } 0386 } 0387 0388 void MainWindow::allocatorSelectionChanged() 0389 { 0390 m_removeAllocator->setEnabled(ui.allocatorView->selectionModel()->hasSelection()); 0391 } 0392 0393 void MainWindow::slotNewAllocator() 0394 { 0395 QString allocator = QInputDialog::getText(this, i18n("Add Custom Allocator"), i18n("allocator:")); 0396 if (allocator.isEmpty()) { 0397 return; 0398 } 0399 if (!m_allocatorModel->stringList().contains(allocator)) { 0400 m_allocatorModel->setStringList(m_allocatorModel->stringList() << allocator); 0401 } 0402 } 0403 0404 void MainWindow::slotRemoveAllocator() 0405 { 0406 Q_ASSERT(ui.allocatorView->selectionModel()->hasSelection()); 0407 foreach(const QModelIndex& idx, ui.allocatorView->selectionModel()->selectedRows()) { 0408 m_allocatorModel->removeRow(idx.row(), idx.parent()); 0409 } 0410 allocatorsChanged(); 0411 } 0412 0413 void MainWindow::slotMarkCustomAllocator() 0414 { 0415 const QString allocator = m_markCustomAllocator->data().toString(); 0416 Q_ASSERT(!allocator.isEmpty()); 0417 if (!m_allocatorModel->stringList().contains(allocator)) { 0418 m_allocatorModel->setStringList(m_allocatorModel->stringList() << allocator); 0419 } 0420 } 0421 0422 void MainWindow::allocatorViewContextMenuRequested(const QPoint& pos) 0423 { 0424 const QModelIndex idx = ui.allocatorView->indexAt(pos); 0425 0426 QMenu menu; 0427 menu.addAction(m_newAllocator); 0428 if (idx.isValid()) { 0429 menu.addAction(m_removeAllocator); 0430 } 0431 menu.exec(ui.allocatorView->mapToGlobal(pos)); 0432 } 0433 0434 void MainWindow::dataTreeContextMenuRequested(const QPoint& pos) 0435 { 0436 const QModelIndex idx = ui.dataTreeView->indexAt(pos); 0437 const TreeLeafItem* item = idx.data(DataTreeModel::TreeItemRole).value<const TreeLeafItem*>(); 0438 if (!item) { 0439 return; 0440 } 0441 0442 QMenu menu; 0443 contextMenuRequested(ModelItem(item, 0), &menu); 0444 0445 menu.exec(ui.dataTreeView->mapToGlobal(pos)); 0446 } 0447 0448 void MainWindow::contextMenuRequested(const ModelItem& item, QMenu* menu) 0449 { 0450 if (!item.first) { 0451 // only handle context menu on tree-leaf items for now 0452 return; 0453 } 0454 0455 QByteArray func = functionInLabel(item.first->label()); 0456 if (func.length() > 40) { 0457 func.resize(40); 0458 func.append("..."); 0459 } 0460 menu->setTitle(QString::fromUtf8(func)); 0461 0462 m_markCustomAllocator->setData(item.first->label()); 0463 menu->addAction(m_markCustomAllocator); 0464 } 0465 0466 void MainWindow::slotShortenTemplates(bool shorten) 0467 { 0468 if (shorten == Settings::self()->shortenTemplates()) { 0469 return; 0470 } 0471 0472 Settings::self()->setShortenTemplates(shorten); 0473 settingsChanged(); 0474 } 0475 0476 void MainWindow::updateWindowTitle() 0477 { 0478 if (m_currentDocument && m_currentDocument->isLoaded()) { 0479 setWindowTitle(i18n("Evaluation of %1 (%2)", m_currentDocument->data()->cmd(), m_currentDocument->file().fileName())); 0480 } else { 0481 setWindowTitle(QString()); 0482 } 0483 } 0484 0485 void MainWindow::documentChanged() 0486 { 0487 // required to prevent GUI flickering when changing documents 0488 // the changing actions in the toolbar are really flickering bad otherwise 0489 setUpdatesEnabled(false); 0490 0491 if (m_currentDocument) { 0492 m_dataTreeModel->setSource(0); 0493 m_dataTreeFilterModel->setFilter(QString()); 0494 m_currentDocument->clearGuiActions(guiFactory()); 0495 disconnect(m_currentDocument, &DocumentWidget::modelItemSelected, 0496 this, &MainWindow::modelItemSelected); 0497 disconnect(m_currentDocument, &DocumentWidget::contextMenuRequested, 0498 this, &MainWindow::contextMenuRequested); 0499 } 0500 0501 m_currentDocument = qobject_cast<DocumentWidget*>(ui.documents->currentWidget()); 0502 0503 updateWindowTitle(); 0504 0505 actionCollection()->action(QStringLiteral("file_reload"))->setEnabled(m_currentDocument && m_currentDocument->isLoaded()); 0506 m_close->setEnabled(m_currentDocument); 0507 m_selectPeak->setEnabled(m_currentDocument && m_currentDocument->isLoaded()); 0508 0509 if (!m_currentDocument) { 0510 ui.stackedWidget->setCurrentWidget(ui.openPage); 0511 setUpdatesEnabled(true); 0512 return; 0513 } else { 0514 m_dataTreeModel->setSource(m_currentDocument->data()); 0515 m_currentDocument->addGuiActions(guiFactory()); 0516 connect(m_currentDocument, &DocumentWidget::modelItemSelected, 0517 this, &MainWindow::modelItemSelected); 0518 connect(m_currentDocument, &DocumentWidget::contextMenuRequested, 0519 this, &MainWindow::contextMenuRequested); 0520 } 0521 0522 setUpdatesEnabled(true); 0523 }