mirror of
https://bitbucket.org/smil3y/kde-playground.git
synced 2025-02-23 10:22:50 +00:00
528 lines
17 KiB
C++
528 lines
17 KiB
C++
/*
|
|
This file is part of Massif Visualizer
|
|
|
|
Copyright 2010 Milian Wolff <mail@milianw.de>
|
|
Copyright 2013 Arnold Dumas <contact@arnolddumas.fr>
|
|
|
|
This program is free software; you can redistribute it and/or
|
|
modify it under the terms of the GNU General Public License as
|
|
published by the Free Software Foundation; either version 2 of
|
|
the License or (at your option) version 3 or any later version
|
|
accepted by the membership of KDE e.V. (or its successor approved
|
|
by the membership of KDE e.V.), which shall act as a proxy
|
|
defined in Section 14 of version 3 of the license.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "mainwindow.h"
|
|
|
|
#include "massifdata/filedata.h"
|
|
#include "massifdata/snapshotitem.h"
|
|
#include "massifdata/treeleafitem.h"
|
|
#include "massifdata/util.h"
|
|
|
|
#include "visualizer/totalcostmodel.h"
|
|
#include "visualizer/detailedcostmodel.h"
|
|
#include "visualizer/datatreemodel.h"
|
|
#include "visualizer/filtereddatatreemodel.h"
|
|
#include "visualizer/dotgraphgenerator.h"
|
|
|
|
#include "massif-visualizer-settings.h"
|
|
#include "configdialog.h"
|
|
|
|
#include <KStandardAction>
|
|
#include <KActionCollection>
|
|
#include <KFileDialog>
|
|
#include <KRecentFilesAction>
|
|
#include <KColorScheme>
|
|
#include <KStatusBar>
|
|
#include <KToolBar>
|
|
#include <KParts/Part>
|
|
#include <KPluginFactory>
|
|
#include <KPluginLoader>
|
|
#include <KXMLGUIFactory>
|
|
#include <KLocale>
|
|
|
|
#include <QSortFilterProxyModel>
|
|
#include <QStringListModel>
|
|
#include <QLabel>
|
|
#include <QSpinBox>
|
|
#include <QInputDialog>
|
|
|
|
#include <KMessageBox>
|
|
|
|
#ifdef HAVE_KGRAPHVIEWER
|
|
#include <kgraphviewer_interface.h>
|
|
#endif
|
|
|
|
using namespace Massif;
|
|
|
|
// Helper function
|
|
static KConfigGroup allocatorConfig()
|
|
{
|
|
return KGlobal::config()->group("Allocators");
|
|
}
|
|
|
|
MainWindow::MainWindow(QWidget* parent, Qt::WindowFlags f)
|
|
: KParts::MainWindow(parent, f)
|
|
, m_recentFiles(0)
|
|
, m_close(0)
|
|
, m_allocatorModel(new QStringListModel(this))
|
|
, m_newAllocator(0)
|
|
, m_removeAllocator(0)
|
|
, m_shortenTemplates(0)
|
|
, m_selectPeak(0)
|
|
, m_currentDocument(0)
|
|
, m_dataTreeModel(new DataTreeModel(this))
|
|
, m_dataTreeFilterModel(new FilteredDataTreeModel(m_dataTreeModel))
|
|
, m_settingSelection(false)
|
|
{
|
|
ui.setupUi(this);
|
|
|
|
setWindowTitle(i18n("Massif Visualizer"));
|
|
|
|
//BEGIN KGraphViewer
|
|
bool haveGraphViewer = false;
|
|
|
|
// NOTE: just check if kgraphviewer is available at runtime.
|
|
// The former logic has been moved to DocumentWidget constructor.
|
|
#ifdef HAVE_KGRAPHVIEWER
|
|
KPluginFactory *factory = KPluginLoader("kgraphviewerpart").factory();
|
|
if (factory) {
|
|
KParts::ReadOnlyPart* readOnlyPart = factory->create<KParts::ReadOnlyPart>("kgraphviewerpart", this);
|
|
if (readOnlyPart) {
|
|
readOnlyPart->widget()->hide();
|
|
haveGraphViewer = true;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (!haveGraphViewer) {
|
|
// cleanup UI when we installed with kgraphviewer but it's not available at runtime
|
|
KToolBar* callgraphToolbar = toolBar("callgraphToolBar");
|
|
removeToolBar(callgraphToolbar);
|
|
delete callgraphToolbar;
|
|
}
|
|
//END KGraphViewer
|
|
|
|
ui.documents->setMovable(true);
|
|
ui.documents->setTabsClosable(true);
|
|
connect(ui.documents, SIGNAL(currentChanged(int)),
|
|
this, SLOT(documentChanged()));
|
|
connect(ui.documents, SIGNAL(tabCloseRequested(int)),
|
|
this, SLOT(closeFileTab(int)));
|
|
|
|
//BEGIN custom allocators
|
|
tabifyDockWidget(ui.allocatorDock, ui.dataTreeDock);
|
|
ui.allocatorView->setModel(m_allocatorModel);
|
|
|
|
int iconSize = style()->pixelMetric(QStyle::PM_SmallIconSize);
|
|
ui.dockMenuBar->setIconSize(QSize(iconSize, iconSize));
|
|
ui.dockMenuBar->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
|
|
ui.dockMenuBar->setFloatable(false);
|
|
ui.dockMenuBar->setMovable(false);
|
|
|
|
KConfigGroup cfg = allocatorConfig();
|
|
m_allocatorModel->setStringList(cfg.entryMap().values());
|
|
|
|
connect(m_allocatorModel, SIGNAL(modelReset()),
|
|
this, SLOT(allocatorsChanged()));
|
|
|
|
connect(m_allocatorModel, SIGNAL(dataChanged(QModelIndex,QModelIndex)),
|
|
this, SLOT(allocatorsChanged()));
|
|
|
|
connect(ui.dataTreeView, SIGNAL(customContextMenuRequested(QPoint)),
|
|
this, SLOT(dataTreeContextMenuRequested(QPoint)));
|
|
ui.dataTreeView->setContextMenuPolicy(Qt::CustomContextMenu);
|
|
|
|
connect(ui.allocatorView, SIGNAL(customContextMenuRequested(QPoint)),
|
|
this, SLOT(allocatorViewContextMenuRequested(QPoint)));
|
|
ui.allocatorView->setContextMenuPolicy(Qt::CustomContextMenu);
|
|
//END custom allocators
|
|
|
|
setupActions();
|
|
setupGUI(StandardWindowOptions(Default ^ StatusBar));
|
|
statusBar()->hide();
|
|
|
|
ui.dataTreeView->setModel(m_dataTreeFilterModel);
|
|
|
|
connect(ui.filterDataTree, SIGNAL(textChanged(QString)),
|
|
m_dataTreeFilterModel, SLOT(setFilter(QString)));
|
|
connect(ui.dataTreeView->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)),
|
|
this, SLOT(treeSelectionChanged(QModelIndex,QModelIndex)));
|
|
|
|
// open page
|
|
ui.stackedWidget->setCurrentWidget(ui.openPage);
|
|
}
|
|
|
|
MainWindow::~MainWindow()
|
|
{
|
|
while (ui.documents->count()) {
|
|
closeCurrentFile();
|
|
}
|
|
|
|
m_recentFiles->saveEntries(KGlobal::config()->group( QString() ));
|
|
}
|
|
|
|
void MainWindow::setupActions()
|
|
{
|
|
KAction* openFile = KStandardAction::open(this, SLOT(openFile()), actionCollection());
|
|
m_recentFiles = KStandardAction::openRecent(this, SLOT(openFile(KUrl)), actionCollection());
|
|
m_recentFiles->loadEntries(KGlobal::config()->group( QString() ));
|
|
|
|
KAction* reload = KStandardAction::redisplay(this, SLOT(reloadCurrentFile()), actionCollection());
|
|
actionCollection()->addAction("file_reload", reload);
|
|
reload->setEnabled(false);
|
|
|
|
m_close = KStandardAction::close(this, SLOT(closeCurrentFile()), actionCollection());
|
|
m_close->setEnabled(false);
|
|
|
|
KStandardAction::quit(qApp, SLOT(closeAllWindows()), actionCollection());
|
|
|
|
KStandardAction::preferences(this, SLOT(preferences()), actionCollection());
|
|
|
|
m_shortenTemplates = new KAction(KIcon("shortentemplates"), i18n("Shorten Templates"), actionCollection());
|
|
m_shortenTemplates->setCheckable(true);
|
|
m_shortenTemplates->setChecked(Settings::shortenTemplates());
|
|
connect(m_shortenTemplates, SIGNAL(toggled(bool)), SLOT(slotShortenTemplates(bool)));
|
|
actionCollection()->addAction("shorten_templates", m_shortenTemplates);
|
|
|
|
m_selectPeak = new KAction(KIcon("flag-red"), i18n("Select peak snapshot"), actionCollection());
|
|
connect(m_selectPeak, SIGNAL(triggered()), this, SLOT(selectPeakSnapshot()));
|
|
actionCollection()->addAction("selectPeak", m_selectPeak);
|
|
m_selectPeak->setEnabled(false);
|
|
|
|
//BEGIN custom allocators
|
|
m_newAllocator = new KAction(KIcon("list-add"), i18n("add"), ui.allocatorDock);
|
|
m_newAllocator->setToolTip(i18n("add custom allocator"));
|
|
connect(m_newAllocator, SIGNAL(triggered()), this, SLOT(slotNewAllocator()));
|
|
ui.dockMenuBar->addAction(m_newAllocator);
|
|
m_removeAllocator = new KAction(KIcon("list-remove"), i18n("remove"),
|
|
ui.allocatorDock);
|
|
m_newAllocator->setToolTip(i18n("remove selected allocator"));
|
|
connect(m_removeAllocator, SIGNAL(triggered()), this, SLOT(slotRemoveAllocator()));
|
|
connect(ui.allocatorView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)),
|
|
this, SLOT(allocatorSelectionChanged()));
|
|
m_removeAllocator->setEnabled(false);
|
|
ui.dockMenuBar->addAction(m_removeAllocator);
|
|
|
|
m_markCustomAllocator = new KAction(i18n("mark as custom allocator"), ui.allocatorDock);
|
|
connect(m_markCustomAllocator, SIGNAL(triggered()),
|
|
this, SLOT(slotMarkCustomAllocator()), Qt::QueuedConnection);
|
|
//END custom allocators
|
|
|
|
//dock actions
|
|
actionCollection()->addAction("toggleDataTree", ui.dataTreeDock->toggleViewAction());
|
|
actionCollection()->addAction("toggleAllocators", ui.allocatorDock->toggleViewAction());
|
|
|
|
//open page actions
|
|
ui.openFile->setDefaultAction(openFile);
|
|
ui.openFile->setText(i18n("Open Massif Data File"));
|
|
ui.openFile->setIconSize(QSize(48, 48));
|
|
}
|
|
|
|
void MainWindow::preferences()
|
|
{
|
|
if (ConfigDialog::isShown()) {
|
|
return;
|
|
}
|
|
|
|
ConfigDialog* dlg = new ConfigDialog(this);
|
|
connect(dlg, SIGNAL(settingsChanged(QString)),
|
|
this, SLOT(settingsChanged()));
|
|
dlg->show();
|
|
}
|
|
|
|
void MainWindow::settingsChanged()
|
|
{
|
|
if (Settings::self()->shortenTemplates() != m_shortenTemplates->isChecked()) {
|
|
m_shortenTemplates->setChecked(Settings::self()->shortenTemplates());
|
|
}
|
|
|
|
Settings::self()->writeConfig();
|
|
|
|
if (m_currentDocument) {
|
|
m_currentDocument->settingsChanged();
|
|
}
|
|
ui.dataTreeView->viewport()->update();
|
|
}
|
|
|
|
void MainWindow::openFile()
|
|
{
|
|
const KUrl::List files = KFileDialog::getOpenUrls(KUrl("kfiledialog:///massif-visualizer"),
|
|
QString("application/x-valgrind-massif"),
|
|
this, i18n("Open Massif Output File"));
|
|
foreach (const KUrl& file, files) {
|
|
openFile(file);
|
|
}
|
|
}
|
|
|
|
void MainWindow::reloadCurrentFile()
|
|
{
|
|
if (m_currentDocument->file().isValid()) {
|
|
openFile(KUrl(m_currentDocument->file()));
|
|
}
|
|
}
|
|
|
|
void MainWindow::openFile(const KUrl& file)
|
|
{
|
|
Q_ASSERT(file.isValid());
|
|
|
|
// Is file already opened ?
|
|
int indexToInsert = -1;
|
|
for (int i = 0; i < ui.documents->count(); ++i) {
|
|
if (qobject_cast<DocumentWidget*>(ui.documents->widget(i))->file() == file) {
|
|
indexToInsert = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
DocumentWidget* documentWidget = new DocumentWidget(file.pathOrUrl(), m_allocatorModel->stringList(),
|
|
this, this);
|
|
|
|
if (indexToInsert != -1) {
|
|
// Remove existing instance of the file.
|
|
ui.documents->setCurrentIndex(indexToInsert);
|
|
closeCurrentFile();
|
|
// Insert the new tab at the correct position.
|
|
ui.documents->insertTab(indexToInsert, documentWidget, file.fileName());
|
|
ui.documents->setCurrentIndex(indexToInsert);
|
|
} else {
|
|
const int idx = ui.documents->addTab(documentWidget, file.fileName());
|
|
ui.documents->setCurrentIndex(idx);
|
|
}
|
|
connect(documentWidget, SIGNAL(loadingFinished()),
|
|
this, SLOT(documentChanged()));
|
|
connect(documentWidget, SIGNAL(requestClose()),
|
|
this, SLOT(closeRequested()));
|
|
|
|
m_recentFiles->addUrl(file);
|
|
ui.stackedWidget->setCurrentWidget(ui.displayPage);
|
|
}
|
|
|
|
void MainWindow::treeSelectionChanged(const QModelIndex& now, const QModelIndex& before)
|
|
{
|
|
if (!m_currentDocument || m_settingSelection || now == before) {
|
|
return;
|
|
}
|
|
|
|
m_settingSelection = true;
|
|
|
|
const ModelItem& item = now.data(DataTreeModel::ModelItemRole).value<ModelItem>();
|
|
m_currentDocument->selectModelItem(item);
|
|
|
|
m_settingSelection = false;
|
|
}
|
|
|
|
void MainWindow::modelItemSelected(const ModelItem& item)
|
|
{
|
|
if (!m_currentDocument || m_settingSelection) {
|
|
return;
|
|
}
|
|
|
|
m_settingSelection = true;
|
|
|
|
const QModelIndex& newIndex = m_dataTreeFilterModel->mapFromSource(
|
|
m_dataTreeModel->indexForItem(item)
|
|
);
|
|
ui.dataTreeView->selectionModel()->clearSelection();
|
|
ui.dataTreeView->selectionModel()->setCurrentIndex(newIndex, QItemSelectionModel::Select | QItemSelectionModel::Rows);
|
|
ui.dataTreeView->scrollTo(ui.dataTreeView->selectionModel()->currentIndex());
|
|
m_currentDocument->selectModelItem(item);
|
|
|
|
m_settingSelection = false;
|
|
}
|
|
|
|
void MainWindow::selectPeakSnapshot()
|
|
{
|
|
ui.dataTreeView->selectionModel()->clearSelection();
|
|
ui.dataTreeView->selectionModel()->setCurrentIndex(
|
|
m_dataTreeFilterModel->mapFromSource(
|
|
m_dataTreeModel->indexForSnapshot(m_currentDocument->data()->peak())),
|
|
QItemSelectionModel::Select | QItemSelectionModel::Rows
|
|
);
|
|
}
|
|
|
|
void MainWindow::closeCurrentFile()
|
|
{
|
|
closeFileTab(ui.documents->currentIndex());
|
|
}
|
|
|
|
void MainWindow::closeRequested()
|
|
{
|
|
DocumentWidget* widget = qobject_cast<DocumentWidget*>(sender());
|
|
Q_ASSERT(widget);
|
|
closeFileTab(ui.documents->indexOf(widget));
|
|
}
|
|
|
|
void MainWindow::closeFileTab(int idx)
|
|
{
|
|
Q_ASSERT(idx != -1);
|
|
ui.documents->widget(idx)->deleteLater();
|
|
ui.documents->removeTab(idx);
|
|
}
|
|
|
|
void MainWindow::allocatorsChanged()
|
|
{
|
|
KConfigGroup cfg = allocatorConfig();
|
|
cfg.deleteGroup();
|
|
int i = 0;
|
|
foreach(const QString& allocator, m_allocatorModel->stringList()) {
|
|
if (allocator.isEmpty()) {
|
|
m_allocatorModel->removeRow(i);
|
|
continue;
|
|
}
|
|
cfg.writeEntry(QString::number(i++), allocator);
|
|
}
|
|
cfg.sync();
|
|
|
|
if (m_currentDocument) {
|
|
reloadCurrentFile();
|
|
}
|
|
}
|
|
|
|
void MainWindow::allocatorSelectionChanged()
|
|
{
|
|
m_removeAllocator->setEnabled(ui.allocatorView->selectionModel()->hasSelection());
|
|
}
|
|
|
|
void MainWindow::slotNewAllocator()
|
|
{
|
|
QString allocator = QInputDialog::getText(this, i18n("Add Custom Allocator"), i18n("allocator:"));
|
|
if (allocator.isEmpty()) {
|
|
return;
|
|
}
|
|
if (!m_allocatorModel->stringList().contains(allocator)) {
|
|
m_allocatorModel->setStringList(m_allocatorModel->stringList() << allocator);
|
|
}
|
|
}
|
|
|
|
void MainWindow::slotRemoveAllocator()
|
|
{
|
|
Q_ASSERT(ui.allocatorView->selectionModel()->hasSelection());
|
|
foreach(const QModelIndex& idx, ui.allocatorView->selectionModel()->selectedRows()) {
|
|
m_allocatorModel->removeRow(idx.row(), idx.parent());
|
|
}
|
|
allocatorsChanged();
|
|
}
|
|
|
|
void MainWindow::slotMarkCustomAllocator()
|
|
{
|
|
const QString allocator = m_markCustomAllocator->data().toString();
|
|
Q_ASSERT(!allocator.isEmpty());
|
|
if (!m_allocatorModel->stringList().contains(allocator)) {
|
|
m_allocatorModel->setStringList(m_allocatorModel->stringList() << allocator);
|
|
}
|
|
}
|
|
|
|
void MainWindow::allocatorViewContextMenuRequested(const QPoint& pos)
|
|
{
|
|
const QModelIndex idx = ui.allocatorView->indexAt(pos);
|
|
|
|
QMenu menu;
|
|
menu.addAction(m_newAllocator);
|
|
if (idx.isValid()) {
|
|
menu.addAction(m_removeAllocator);
|
|
}
|
|
menu.exec(ui.allocatorView->mapToGlobal(pos));
|
|
}
|
|
|
|
void MainWindow::dataTreeContextMenuRequested(const QPoint& pos)
|
|
{
|
|
const QModelIndex idx = ui.dataTreeView->indexAt(pos);
|
|
const TreeLeafItem* item = idx.data(DataTreeModel::TreeItemRole).value<const TreeLeafItem*>();
|
|
if (!item) {
|
|
return;
|
|
}
|
|
|
|
QMenu menu;
|
|
contextMenuRequested(ModelItem(item, 0), &menu);
|
|
|
|
menu.exec(ui.dataTreeView->mapToGlobal(pos));
|
|
}
|
|
|
|
void MainWindow::contextMenuRequested(const ModelItem& item, QMenu* menu)
|
|
{
|
|
if (!item.first) {
|
|
// only handle context menu on tree-leaf items for now
|
|
return;
|
|
}
|
|
|
|
QString func = functionInLabel(item.first->label());
|
|
if (func.length() > 40) {
|
|
func.resize(40);
|
|
func.append("...");
|
|
}
|
|
menu->setTitle(func);
|
|
|
|
m_markCustomAllocator->setData(item.first->label());
|
|
menu->addAction(m_markCustomAllocator);
|
|
}
|
|
|
|
void MainWindow::slotShortenTemplates(bool shorten)
|
|
{
|
|
if (shorten == Settings::self()->shortenTemplates()) {
|
|
return;
|
|
}
|
|
|
|
Settings::self()->setShortenTemplates(shorten);
|
|
settingsChanged();
|
|
}
|
|
|
|
void MainWindow::updateWindowTitle()
|
|
{
|
|
if (m_currentDocument && m_currentDocument->isLoaded()) {
|
|
setWindowTitle(i18n("Massif Visualizer - evaluation of %1 (%2)", m_currentDocument->data()->cmd(), m_currentDocument->file().fileName()));
|
|
} else {
|
|
setWindowTitle(i18n("Massif Visualizer"));
|
|
}
|
|
}
|
|
|
|
void MainWindow::documentChanged()
|
|
{
|
|
// required to prevent GUI flickering when changing documents
|
|
// the changing actions in the toolbar are really flickering bad otherwise
|
|
setUpdatesEnabled(false);
|
|
|
|
if (m_currentDocument) {
|
|
m_dataTreeModel->setSource(0);
|
|
m_dataTreeFilterModel->setFilter(QString());
|
|
m_currentDocument->clearGuiActions(guiFactory());
|
|
disconnect(m_currentDocument, SIGNAL(modelItemSelected(Massif::ModelItem)),
|
|
this, SLOT(modelItemSelected(Massif::ModelItem)));
|
|
disconnect(m_currentDocument, SIGNAL(contextMenuRequested(Massif::ModelItem,QMenu*)),
|
|
this, SLOT(contextMenuRequested(Massif::ModelItem,QMenu*)));
|
|
}
|
|
|
|
m_currentDocument = qobject_cast<DocumentWidget*>(ui.documents->currentWidget());
|
|
|
|
updateWindowTitle();
|
|
|
|
actionCollection()->action("file_reload")->setEnabled(m_currentDocument && m_currentDocument->isLoaded());
|
|
m_close->setEnabled(m_currentDocument);
|
|
m_selectPeak->setEnabled(m_currentDocument && m_currentDocument->isLoaded());
|
|
|
|
if (!m_currentDocument) {
|
|
ui.stackedWidget->setCurrentWidget(ui.openPage);
|
|
setUpdatesEnabled(true);
|
|
return;
|
|
} else {
|
|
m_dataTreeModel->setSource(m_currentDocument->data());
|
|
m_currentDocument->addGuiActions(guiFactory());
|
|
connect(m_currentDocument, SIGNAL(modelItemSelected(Massif::ModelItem)),
|
|
this, SLOT(modelItemSelected(Massif::ModelItem)));
|
|
connect(m_currentDocument, SIGNAL(contextMenuRequested(Massif::ModelItem,QMenu*)),
|
|
this, SLOT(contextMenuRequested(Massif::ModelItem,QMenu*)));
|
|
}
|
|
|
|
setUpdatesEnabled(true);
|
|
}
|
|
|
|
#include "mainwindow.moc"
|