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 }