diff --git a/cgview/main.cpp b/cgview/main.cpp index 2f9d683..798ad96 100644 --- a/cgview/main.cpp +++ b/cgview/main.cpp @@ -1,193 +1,192 @@ /* This file is part of KCachegrind. Copyright (c) 2008-2016 Josef Weidendorfer KCachegrind 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, version 2. 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; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include #include "tracedata.h" #include "loader.h" #include "config.h" #include "globalconfig.h" #include "logger.h" /* * Just a simple command line tool using libcore */ void showHelp(QTextStream& out, bool fullHelp = true) { out << "Show profiles from callgrind files. (c) 2010-2016 J. Weidendorfer\n"; if (!fullHelp) out << "Type 'cgview -h' for help." << endl; else out << "Usage: cgview [options] ...\n\n" "Options:\n" " -h Show this help text\n" " -e Sort list according to exclusive cost\n" " -s Sort and show counters for event \n" " -c Sort by call count\n" " -b Show butterfly (callers and callees)\n" " -n Do not detect recursive cycles" << endl; exit(1); } int main(int argc, char** argv) { QCoreApplication app(argc, argv); QTextStream out(stdout); Loader::initLoaders(); ConfigStorage::setStorage(new ConfigStorage); GlobalConfig::config()->addDefaultTypes(); QStringList list = app.arguments(); list.pop_front(); if (list.isEmpty()) showHelp(out, false); bool sortByExcl = false; bool sortByCount = false; bool showCalls = false; QString showEvent; QStringList files; for(int arg = 0; argload(files); EventTypeSet* m = d->eventTypes(); if (m->realCount() == 0) { out << "Error: No event types found." << endl; return 1; } out << "\nTotals for event types:\n"; - QString p; EventType* et; for (int i=0;irealCount();i++) { et = m->realType(i); out.setFieldWidth(14); out.setFieldAlignment(QTextStream::AlignRight); out << d->subCost(et).pretty(); out.setFieldWidth(0); out << " " << et->longName() << " (" << et->name() << ")\n"; } for (int i=0;iderivedCount();i++) { et = m->derivedType(i); out.setFieldWidth(14); out.setFieldAlignment(QTextStream::AlignRight); out << d->subCost(et).pretty(); out.setFieldWidth(0); out << " " << et->longName() << " (" << et->name() << " = " << et->formula() << ")\n"; } out << endl; if (showEvent.isEmpty()) et = m->realType(0); else { et = m->type(showEvent); if (!et) { out << "Error: event '" << showEvent << "' not found." << endl; return 1; } } Q_ASSERT( et!=0 ); out << "Sorted by: " << (sortByExcl ? "Exclusive ":"Inclusive ") << et->longName() << " (" << et->name() << ")" << endl; QList flist; HighestCostList hc; hc.clear(50); TraceFunctionMap::Iterator it; for ( it = d->functionMap().begin(); it != d->functionMap().end(); ++it ) flist.append(&(*it)); TraceFunction *f; foreach(f, d->functionCycles()) flist.append(f); foreach(f, flist) { if (sortByCount) hc.addCost(f, f->calledCount()); else if (sortByExcl) hc.addCost(f, f->subCost(et)); else hc.addCost(f, f->inclusive()->subCost(et)); } out << "\n Inclusive Exclusive Called Function name (DSO)\n"; out << " ==================================================================\n"; out.setFieldAlignment(QTextStream::AlignRight); for(int i=0; i0) out << endl; foreach(TraceCall* c, f->callers()) { out << " "; out.setFieldWidth(14); out << c->subCost(et).pretty(); out.setFieldWidth(0); out << " "; out.setFieldWidth(13); out << c->prettyCallCount(); out.setFieldWidth(0); out << " < " << c->caller()->prettyName() << endl; } } out.setFieldWidth(14); out << f->inclusive()->subCost(et).pretty(); out << f->subCost(et).pretty(); out.setFieldWidth(13); out << f->prettyCalledCount(); out.setFieldWidth(0); out << " " << f->name() << " (" << f->object()->name() << ")" << endl; if (showCalls) { foreach(TraceCall* c, f->callings()) { out << " "; out.setFieldWidth(14); out << c->subCost(et).pretty(); out.setFieldWidth(0); out << " "; out.setFieldWidth(13); out << c->prettyCallCount(); out.setFieldWidth(0); out << " > " << c->called()->prettyName() << endl; } } } } diff --git a/kcachegrind/configdlg.h b/kcachegrind/configdlg.h index a0db398..f2ff6c1 100644 --- a/kcachegrind/configdlg.h +++ b/kcachegrind/configdlg.h @@ -1,73 +1,74 @@ /* This file is part of KCachegrind. Copyright (C) 2002-2016 Josef Weidendorfer KCachegrind 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, version 2. 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; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /* * Configuration Dialog for KCachegrind */ #ifndef CONFIGDLG_H #define CONFIGDLG_H #include "ui_configdlgbase.h" class TraceData; class GlobalGUIConfig; class ConfigColorSetting; class ConfigDlgBase : public QDialog, public Ui::ConfigDlgBase { + Q_OBJECT public: ConfigDlgBase( QWidget *parent ) : QDialog( parent ) { setupUi( this ); } }; class ConfigDlg : public ConfigDlgBase { Q_OBJECT public: ConfigDlg(GlobalGUIConfig*, TraceData*, QWidget* parent = 0); ~ConfigDlg(); static bool configure(GlobalGUIConfig*, TraceData*, QWidget*); protected slots: void objectActivated(const QString &); void objectCheckChanged(bool); void objectColorChanged(const QColor &); void classActivated(const QString &); void classCheckChanged(bool); void classColorChanged(const QColor &); void fileActivated(const QString &); void fileCheckChanged(bool); void fileColorChanged(const QColor &); void dirsItemChanged(); void dirsDeletePressed(); void dirsAddPressed(); private: QTreeWidgetItem *getSelectedDirItem(); GlobalGUIConfig* _config; TraceData* _data; ConfigColorSetting *_objectCS, *_classCS, *_fileCS; }; #endif diff --git a/kcachegrind/toplevel.cpp b/kcachegrind/toplevel.cpp index c4eaae2..74fe1b7 100644 --- a/kcachegrind/toplevel.cpp +++ b/kcachegrind/toplevel.cpp @@ -1,2392 +1,2392 @@ /* This file is part of KCachegrind. Copyright (C) 2002-2016 Josef Weidendorfer KCachegrind 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, version 2. 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; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /* * KCachegrind top level window */ #define TRACE_UPDATES 0 #define ENABLE_DUMPDOCK 0 #include "toplevel.h" #include // for system() #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if ENABLE_DUMPDOCK #include "dumpselection.h" #endif #include "partselection.h" #include "functionselection.h" #include "stackselection.h" #include "stackbrowser.h" #include "tracedata.h" #include "globalguiconfig.h" #include "config.h" #include "configdlg.h" #include "multiview.h" #include "callgraphview.h" TopLevel::TopLevel() : KXmlGuiWindow(0) { QDBusConnection::sessionBus().registerObject(QStringLiteral("/KCachegrind"), this, QDBusConnection::ExportScriptableSlots); _progressBar = 0; _statusbar = statusBar(); _statusLabel = new QLabel(_statusbar); _statusbar->addWidget(_statusLabel, 1); _ccProcess = 0; _layoutCount = 1; _layoutCurrent = 0; resetState(); KConfig *kconfig = KSharedConfig::openConfig().data(); GlobalGUIConfig::config()->readOptions(); createDocks(); _multiView = new MultiView(this, this ); _multiView->setObjectName(QStringLiteral("MultiView")); setCentralWidget(_multiView); createActions(); _partDockShown->setChecked(!_partDock->isHidden()); _stackDockShown->setChecked(!_stackDock->isHidden()); _functionDockShown->setChecked(!_functionDock->isHidden()); connect(_partDock, &QDockWidget::visibilityChanged, this, &TopLevel::partVisibilityChanged); connect(_stackDock, &QDockWidget::visibilityChanged, this, &TopLevel::stackVisibilityChanged); connect(_functionDock, &QDockWidget::visibilityChanged, this, &TopLevel::functionVisibilityChanged); #if ENABLE_DUMPDOCK _dumpDockShown->setChecked(!_dumpDock->isHidden()); connect(_dumpDock, SIGNAL(visibilityChanged(bool)), this, SLOT(dumpVisibilityChanged(bool))); #endif // set toggle after reading configuration _showPercentage = GlobalConfig::showPercentage(); _showExpanded = GlobalConfig::showExpanded(); _showCycles = GlobalConfig::showCycles(); _hideTemplates = GlobalConfig::hideTemplates(); _taPercentage->setChecked(_showPercentage); _taExpanded->setChecked(_showExpanded); _taCycles->setChecked(_showCycles); _taHideTemplates->setChecked(_hideTemplates); setupPartSelection(_partSelection); // KCachegrind for KDE 3.0.x does not allow to hide toolbars... setStandardToolBarMenuEnabled(true); _openRecent->loadEntries( KConfigGroup( kconfig, "" ) ); // QT dock windows are created before (using QT position restoring) createGUI(); setAutoSaveSettings(); // restore current state settings (not configuration options) restoreCurrentState(QString::null); //krazy:exclude=nullstrassign for old broken gcc // if this is the first toplevel, show tip of day if (memberList().count() == 1) QTimer::singleShot( 200, this, &TopLevel::slotShowTipOnStart ); } void TopLevel::resetState() { _activeParts.clear(); _hiddenParts.clear(); _data = 0; _function = 0; _eventType = 0; _eventType2 = 0; _groupType = ProfileContext::InvalidType; _group = 0; // for delayed slots _traceItemDelayed = 0; _eventTypeDelayed = 0; _eventType2Delayed = 0; _groupTypeDelayed = ProfileContext::InvalidType; _groupDelayed = 0; _directionDelayed = TraceItemView::None; _lastSender = 0; } /** * Setup the part selection widget. * Statusbar has to be created before. */ void TopLevel::setupPartSelection(PartSelection* ps) { // setup connections from the part selection widget connect(ps, &PartSelection::partsHideSelected, this, &TopLevel::partsHideSelectedSlotDelayed); connect(ps, &PartSelection::partsUnhideAll, this, &TopLevel::partsUnhideAllSlotDelayed); } /** * This saves the current state of the main window and * sub widgets. * * No positions are saved. These is done automatically for * KToolbar, and manually in queryClose() for Qt docks. */ void TopLevel::saveCurrentState(const QString& postfix) { QString eventType = _eventType ? _eventType->name() : QStringLiteral("?"); QString eventType2 = _eventType2 ? _eventType2->name() : QStringLiteral("?"); ConfigGroup* stateConfig = ConfigStorage::group(QStringLiteral("CurrentState") + postfix); stateConfig->setValue(QStringLiteral("EventType"), eventType); stateConfig->setValue(QStringLiteral("EventType2"), eventType2); stateConfig->setValue(QStringLiteral("GroupType"), ProfileContext::typeName(_groupType)); delete stateConfig; _partSelection->saveOptions(QStringLiteral("PartOverview"), postfix); _multiView->saveLayout(QStringLiteral("MainView"), postfix); _multiView->saveOptions(QStringLiteral("MainView"), postfix); } /** * This function is called when a trace is closed. * Save browsing position for later restoring */ void TopLevel::saveTraceSettings() { QString key = traceKey(); ConfigGroup* lConfig = ConfigStorage::group(QStringLiteral("Layouts")); lConfig->setValue(QStringLiteral("Count%1").arg(key), _layoutCount); lConfig->setValue(QStringLiteral("Current%1").arg(key), _layoutCurrent); delete lConfig; ConfigGroup* pConfig = ConfigStorage::group(QStringLiteral("TracePositions")); if (_eventType) pConfig->setValue(QStringLiteral("EventType%1").arg(key), _eventType->name()); if (_eventType2) pConfig->setValue(QStringLiteral("EventType2%1").arg(key), _eventType2->name()); if (_groupType != ProfileContext::InvalidType) pConfig->setValue(QStringLiteral("GroupType%1").arg(key), ProfileContext::typeName(_groupType)); if (_data) { if (_group) pConfig->setValue(QStringLiteral("Group%1").arg(key), _group->name()); saveCurrentState(key); } delete pConfig; } /** * This restores the current state of the main window and * sub widgets. * * This does NOT restore any positions. This is done automatically for * KToolbar, and manually in the createDocks() for QT docks.. */ void TopLevel::restoreCurrentState(const QString& postfix) { _partSelection->restoreOptions(QStringLiteral("PartOverview"), postfix); _multiView->restoreLayout(QStringLiteral("MainView"), postfix); _multiView->restoreOptions(QStringLiteral("MainView"), postfix); _taSplit->setChecked(_multiView->childCount()>1); _taSplitDir->setEnabled(_multiView->childCount()>1); _taSplitDir->setChecked(_multiView->orientation() == Qt::Horizontal); } void TopLevel::createDocks() { _partDock = new QDockWidget(this); _partDock->setObjectName(QStringLiteral("part dock")); _partDock->setWindowTitle(i18n("Parts Overview")); _partSelection = new PartSelection(this, _partDock); _partDock->setWidget(_partSelection); _stackDock = new QDockWidget(this); _stackDock->setObjectName(QStringLiteral("stack dock")); // Why is the caption only correct with a close button? _stackSelection = new StackSelection(_stackDock); _stackDock->setWidget(_stackSelection); _stackDock->setWindowTitle(i18n("Top Cost Call Stack")); _stackSelection->setWhatsThis( i18n( "The Top Cost Call Stack" "

This is a purely fictional 'most probable' call stack. " "It is built up by starting with the current selected " "function and adds the callers/callees with highest cost " "at the top and to bottom.

" "

The Cost and Calls columns show the " "cost used for all calls from the function in the line " "above.

")); connect(_stackSelection, SIGNAL(functionSelected(CostItem*)), this, SLOT(setTraceItemDelayed(CostItem*))); _functionDock = new QDockWidget(this); _functionDock->setObjectName(QStringLiteral("function dock")); _functionDock->setWindowTitle(i18n("Flat Profile")); _functionSelection = new FunctionSelection(this, _functionDock); _functionDock->setWidget(_functionSelection); #if ENABLE_DUMPDOCK _dumpDock = new QDockWidget(this); _dumpDock->setWindowTitle(i18n("Profile Dumps")); _dumpSelection = new DumpSelection(this, _dumpDock, "dumpSelection"); _dumpSelection->setTopLevel(this); _dumpDock->setWidget(_dumpSelection); _dumpSelection->setWhatsThis( i18n( "Profile Dumps" "

This dockable shows in the top part the list of " "loadable profile dumps in all subdirectories of: " "

  • current working directory of KCachegrind, " "i.e. where it was started from, and
  • " "
  • the default profile dump directory given in the " "configuration.
" "The list is sorted according to the target command " "profiled in the corresponding dump.

" "

On selecting a profile dump, information for it " "is shown in the bottom area of the dockable: " "

  • Options allows you to view the profiled " "command and profile options of this dump. By changing " "any item, a new (yet unexisting) profile template " "is created. Press Run Profile to start a " "profile run with these options in the background.
  • " "
  • Info gives detailed info on the selected " "dump like event cost summary and properties of the " "simulated cache.
  • " "
  • State is only available for current happening " "profiles runs. Press Update to see different " "counters of the run, and a stack trace of the current " "position in the program profiled. Check the Every " "option to let KCachegrind regularly poll these data. " "Check the Sync option to let the dockable activate " "the top function in the current loaded dump.

")); #endif // default positions, will be adjusted automatically by stored state in config addDockWidget(Qt::LeftDockWidgetArea, _partDock ); addDockWidget(Qt::LeftDockWidgetArea, _stackDock ); addDockWidget(Qt::LeftDockWidgetArea, _functionDock ); _stackDock->hide(); _partDock->hide(); #if ENABLE_DUMPDOCK addDockWidget( Qt::LeftDockWidgetArea, _dumpDock ); _dumpDock->hide(); #endif KConfigGroup dockConfig(KSharedConfig::openConfig(), "Docks"); _forcePartDock = dockConfig.readEntry("ForcePartDockVisible", false); } TopLevel::~TopLevel() { delete _data; } void TopLevel::saveProperties(KConfigGroup & c) { if ( _data ) c.writeEntry("TraceName", _data->traceName()); } void TopLevel::readProperties(const KConfigGroup &c) { QString traceName = c.readEntry("TraceName"); if (!traceName.isEmpty()) { openDataFile(traceName); } } void TopLevel::createLayoutActions() { QString hint; QAction* action; action = actionCollection()->addAction( QStringLiteral("layout_duplicate") ); action->setText( i18n( "&Duplicate" ) ); connect(action, &QAction::triggered, this, &TopLevel::layoutDuplicate); actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_Plus)); hint = i18n("Duplicate Current Layout" "

Make a copy of the current layout.

"); action->setWhatsThis( hint ); action = actionCollection()->addAction( QStringLiteral("layout_remove") ); action->setText( i18n( "&Remove" ) ); connect(action, &QAction::triggered, this, &TopLevel::layoutRemove); hint = i18n("Remove Current Layout" "

Delete current layout and make the previous active.

"); action->setWhatsThis( hint ); action = actionCollection()->addAction( QStringLiteral("layout_next") ); action->setText( i18n( "&Go to Next" ) ); connect(action, &QAction::triggered, this, &TopLevel::layoutNext); actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_Right)); hint = i18n("Go to Next Layout"); action->setWhatsThis( hint ); action = actionCollection()->addAction( QStringLiteral("layout_previous") ); action->setText( i18n( "&Go to Previous" ) ); connect(action, &QAction::triggered, this, &TopLevel::layoutPrevious); actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL + Qt::Key_Left)); hint = i18n("Go to Previous Layout"); action->setWhatsThis( hint ); action = actionCollection()->addAction( QStringLiteral("layout_restore") ); action->setText( i18n( "&Restore to Default" ) ); connect(action, &QAction::triggered, this, &TopLevel::layoutRestore); hint = i18n("Restore Layouts to Default"); action->setWhatsThis( hint ); action = actionCollection()->addAction( QStringLiteral("layout_save") ); action->setText( i18n( "&Save as Default" ) ); connect(action, &QAction::triggered, this, &TopLevel::layoutSave); hint = i18n("Save Layouts as Default"); action->setWhatsThis( hint ); } // TODO: split this up... void TopLevel::createMiscActions() { QString hint; QAction* action; action = KStandardAction::openNew(this, SLOT(newWindow()), actionCollection()); hint = i18n("New

Open new empty KCachegrind window.

"); action->setWhatsThis( hint ); action = actionCollection()->addAction( QStringLiteral("file_add") ); action->setText( i18n( "&Add..." ) ); connect(action, SIGNAL(triggered(bool) ), SLOT(add())); hint = i18n("Add Profile Data" "

This opens an additional profile data file in the current window.

"); action->setWhatsThis( hint ); action = actionCollection()->addAction( QStringLiteral("reload") ); action->setIcon( QIcon::fromTheme(QStringLiteral("view-refresh")) ); action->setText( i18nc("Reload a document", "&Reload" ) ); connect(action, &QAction::triggered, this, &TopLevel::reload); actionCollection()->setDefaultShortcut(action, KStandardShortcut::Reload); hint = i18n("Reload Profile Data" "

This loads any new created parts, too.

"); action->setWhatsThis( hint ); action = actionCollection()->addAction( QStringLiteral("export") ); action->setText( i18n( "&Export Graph" ) ); connect(action, &QAction::triggered, this, &TopLevel::exportGraph); hint = i18n("Export Call Graph" "

Generates a file with extension .dot for the tools " "of the GraphViz package.

"); action->setWhatsThis( hint ); _taDump = actionCollection()->add( QStringLiteral("dump") ); _taDump->setIcon( QIcon::fromTheme(QStringLiteral("edit-redo")) ); _taDump->setText( i18n( "&Force Dump" ) ); connect(_taDump, &QAction::triggered, this, &TopLevel::forceTrace); actionCollection()->setDefaultShortcut(_taDump, QKeySequence::Undo); hint = i18n("Force Dump" "

This forces a dump for a Callgrind profile run " "in the current directory. This action is checked while " "KCachegrind looks for the dump. If the dump is " "finished, it automatically reloads the current trace. " "If this is the one from the running Callgrind, the new " "created trace part will be loaded, too.

" "

Force dump creates a file 'callgrind.cmd', and " "checks every second for its existence. A running " "Callgrind will detect this file, dump a trace part, " "and delete 'callgrind.cmd'. " "The deletion is detected by KCachegrind, " "and it does a Reload. If there is no Callgrind " "running, press 'Force Dump' again to cancel the dump " "request. This deletes 'callgrind.cmd' itself and " "stops polling for a new dump.

" "

Note: A Callgrind run only detects " "existence of 'callgrind.cmd' when actively running " "a few milliseconds, i.e. " "not sleeping. Tip: For a profiled GUI program, " "you can awake Callgrind e.g. by resizing a window " "of the program.

"); _taDump->setWhatsThis( hint ); action = KStandardAction::open(this, SLOT(load()), actionCollection()); hint = i18n("Open Profile Data" "

This opens a profile data file, with possible multiple parts

"); action->setToolTip( hint ); action->setWhatsThis( hint ); _openRecent = KStandardAction::openRecent(this, SLOT(load(const QUrl&)), actionCollection()); KStandardAction::showStatusbar(this, SLOT(toggleStatusBar()), actionCollection()); _partDockShown = actionCollection()->add(QStringLiteral("settings_show_partdock")); _partDockShown->setText(i18n("Parts Overview")); connect(_partDockShown, &QAction::triggered, this, &TopLevel::togglePartDock); hint = i18n("Show/Hide the Parts Overview Dockable"); _partDockShown->setToolTip( hint ); _partDockShown->setWhatsThis( hint ); _stackDockShown = actionCollection()->add(QStringLiteral("settings_show_stackdock")); _stackDockShown->setText(i18n("Call Stack")); connect(_stackDockShown, &QAction::triggered, this, &TopLevel::toggleStackDock); hint = i18n("Show/Hide the Call Stack Dockable"); _stackDockShown->setToolTip( hint ); _stackDockShown->setWhatsThis( hint ); _functionDockShown = actionCollection()->add(QStringLiteral("settings_show_profiledock")); _functionDockShown->setText(i18n("Function Profile")); connect(_functionDockShown, &QAction::triggered, this, &TopLevel::toggleFunctionDock); hint = i18n("Show/Hide the Function Profile Dockable"); _functionDockShown->setToolTip( hint ); _functionDockShown->setWhatsThis( hint ); #if ENABLE_DUMPDOCK _dumpDockShown = actionCollection()->add("settings_show_dumpdock", this, SLOT(toggleDumpDock())); _dumpDockShown->setText(i18n("Profile Dumps")); hint = i18n("Show/Hide the Profile Dumps Dockable"); _dumpDockShown->setToolTip( hint ); _dumpDockShown->setWhatsThis( hint ); #endif _taPercentage = actionCollection()->add(QStringLiteral("view_percentage")); _taPercentage->setIcon(QIcon::fromTheme(QStringLiteral("percent"))); _taPercentage->setText(i18n("Relative")); connect(_taPercentage, &QAction::triggered, this, &TopLevel::togglePercentage); hint = i18n("Show relative instead of absolute costs"); _taPercentage->setToolTip( hint ); _taPercentage->setWhatsThis( hint ); _taExpanded = actionCollection()->add(QStringLiteral("view_expanded")); _taExpanded->setIcon(QIcon::fromTheme(QStringLiteral("move"))); _taExpanded->setText(i18n("Relative to Parent")); connect(_taExpanded, &QAction::triggered, this, &TopLevel::toggleExpanded); hint = i18n("Show percentage costs relative to parent"); _taExpanded->setToolTip( hint ); _taExpanded->setWhatsThis( hint ); hint = i18n("Show percentage costs relative to parent" "

If this is switched off, percentage costs are always shown " "relative to the total cost of the profile part(s) that are " "currently browsed. By turning on this option, percentage cost " "of shown cost items will be relative to the parent cost item.

" "
    " "" "" "" "" "" "
    Cost TypeParent Cost
    Function CumulativeTotal
    Function SelfFunction Group (*) / Total
    CallFunction Inclusive
    Source LineFunction Inclusive
" "

(*) Only if function grouping is switched on (e.g. ELF object grouping).

"); _taExpanded->setWhatsThis( hint ); _taCycles = actionCollection()->add(QStringLiteral("view_cycles")); _taCycles->setIcon(QIcon::fromTheme(QStringLiteral("edit-undo"))); _taCycles->setText(i18n( "Cycle Detection" )); connect(_taCycles, &QAction::triggered, this, &TopLevel::toggleCycles); hint = i18n("Detect recursive cycles" "

If this is switched off, the treemap drawing will show " "black areas when a recursive call is made instead of drawing the " "recursion ad infinitum. Note that " "the size of black areas often will be wrong, as inside recursive " "cycles the cost of calls cannot be determined; the error is small, " "however, for false cycles (see documentation).

" "

The correct handling for cycles is to detect them and collapse all " "functions of a cycle into an artificial function, which is done when this " "option is selected. Unfortunately, with GUI applications, this often will " "lead to huge false cycles, making the analysis impossible; therefore, there " "is the option to switch this off.

"); _taCycles->setWhatsThis( hint ); _taHideTemplates = actionCollection()->add(QStringLiteral("hide_templates")); _taHideTemplates->setIcon(QIcon::fromTheme(QStringLiteral("hidetemplates"))); _taHideTemplates->setText(i18n( "Shorten Templates" )); connect(_taHideTemplates, &QAction::triggered, this, &TopLevel::toggleHideTemplates); _taHideTemplates->setToolTip(i18n( "Hide Template Parameters in C++ Symbols" )); hint = i18n("Hide Template Parameters in C++ Symbols" "

If this is switched on, every symbol displayed will have " "any C++ template parameters hidden, just showing <> " "instead of a potentially nested template parameter.

" "

In this mode, you can hover the mouse pointer over the " "activated symbol label to show a tooltip with the " "unabbreviated symbol.

"); _taHideTemplates->setWhatsThis(hint); KStandardAction::quit(this, SLOT(close()), actionCollection()); KStandardAction::preferences(this, SLOT(configure()), actionCollection()); KStandardAction::keyBindings(this, SLOT(configureKeys()), actionCollection()); KStandardAction::configureToolbars(this,SLOT(configureToolbars()), actionCollection()); _paUp = new KToolBarPopupAction( QIcon::fromTheme( QStringLiteral("go-up") ), i18n( "&Up" ), this ); connect( _paUp, &QAction::triggered, _stackSelection, &StackSelection::browserUp ); actionCollection()->addAction( QStringLiteral("go_up"), _paUp ); actionCollection()->setDefaultShortcut(_paUp, QKeySequence(Qt::ALT + Qt::Key_Up)); connect( _paUp->menu(), &QMenu::aboutToShow, this, &TopLevel::upAboutToShow ); connect( _paUp->menu(), &QMenu::triggered, this, &TopLevel::upTriggered ); hint = i18n("Go Up" "

Go to last selected caller of current function. " "If no caller was visited, use that with highest cost.

"); _paUp->setToolTip( hint ); _paUp->setWhatsThis( hint ); QPair< KGuiItem, KGuiItem > backForward = KStandardGuiItem::backAndForward(); _paBack = new KToolBarPopupAction( backForward.first.icon(), backForward.first.text(), this ); connect( _paBack, &QAction::triggered, _stackSelection, &StackSelection::browserBack ); actionCollection()->addAction( QStringLiteral("go_back"), _paBack ); actionCollection()->setDefaultShortcut(_paBack, QKeySequence(Qt::ALT + Qt::Key_Left)); connect( _paBack->menu(), &QMenu::aboutToShow, this, &TopLevel::backAboutToShow ); connect( _paBack->menu(), &QMenu::triggered, this, &TopLevel::backTriggered ); hint = i18n("Go back in function selection history"); _paBack->setToolTip( hint ); _paBack->setWhatsThis( hint ); _paForward = new KToolBarPopupAction( backForward.second.icon(), backForward.second.text(), this ); connect( _paForward, &QAction::triggered, _stackSelection, &StackSelection::browserForward ); actionCollection()->addAction( QStringLiteral("go_forward"), _paForward ); actionCollection()->setDefaultShortcut(_paForward, QKeySequence(Qt::ALT + Qt::Key_Right)); connect( _paForward->menu(), &QMenu::aboutToShow, this, &TopLevel::forwardAboutToShow ); connect( _paForward->menu(), &QMenu::triggered, this, &TopLevel::forwardTriggered ); hint = i18n("Go forward in function selection history"); _paForward->setToolTip( hint ); _paForward->setWhatsThis( hint ); _saCost = actionCollection()->add(QStringLiteral("view_cost_type")); _saCost->setText(i18n("Primary Event Type")); hint = i18n("Select primary event type of costs"); _saCost->setComboWidth(300); _saCost->setToolTip( hint ); _saCost->setWhatsThis( hint ); // This is needed because for KDE4, "_saCost->setComboWidth(300);" seems to // have no effect. Instead, list box entry widths are used to determine the // combobox width. However, at KCachegrind startup, we do not have yet // a list of event types, as this depends on the profile data. // In KDE 4.2, we used a translatable string, which did not really work as // the semantic is not known to translators. Instead, we use a // nontranslatable string now... QStringList dummyItems; dummyItems << QStringLiteral("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"); _saCost->setItems(dummyItems); // cost types are dependent on loaded data, thus KSelectAction // is filled in setData() connect( _saCost, SIGNAL(triggered(const QString&)), this, SLOT(eventTypeSelected(const QString&))); _saCost2 = actionCollection()->add(QStringLiteral("view_cost_type2")); _saCost2->setText(i18n("Secondary Event Type")); hint = i18n("Select secondary event type for cost e.g. shown in annotations"); _saCost2->setComboWidth(300); _saCost2->setToolTip( hint ); _saCost2->setWhatsThis( hint ); _saCost2->setItems(dummyItems); connect( _saCost2, SIGNAL(triggered(const QString&)), this, SLOT(eventType2Selected(const QString&))); saGroup = actionCollection()->add(QStringLiteral("view_group_type")); saGroup->setText(i18n("Grouping")); hint = i18n("Select how functions are grouped into higher level cost items"); saGroup->setToolTip( hint ); saGroup->setWhatsThis( hint ); QStringList args; args << i18n("(No Grouping)") << ProfileContext::i18nTypeName(ProfileContext::Object) << ProfileContext::i18nTypeName(ProfileContext::File) << ProfileContext::i18nTypeName(ProfileContext::Class) << ProfileContext::i18nTypeName(ProfileContext::FunctionCycle); saGroup->setItems(args); connect( saGroup, SIGNAL(triggered(int)), this, SLOT(groupTypeSelected(int))); _taSplit = actionCollection()->add(QStringLiteral("view_split")); _taSplit->setIcon(QIcon::fromTheme(QStringLiteral("view-split-left-right"))); _taSplit->setText(i18n("Split")); connect(_taSplit, &QAction::triggered, this, &TopLevel::splitSlot); hint = i18n("Show two information panels"); _taSplit->setToolTip( hint ); _taSplit->setWhatsThis( hint ); _taSplitDir = actionCollection()->add(QStringLiteral("view_split_dir")); _taSplitDir->setIcon(QIcon::fromTheme(QStringLiteral("view-split-left-right"))); _taSplitDir->setText(i18n("Split Horizontal")); connect(_taSplitDir, &QAction::triggered, this, &TopLevel::splitDirSlot); hint = i18n("Change Split Orientation when main window is split."); _taSplitDir->setToolTip( hint ); _taSplitDir->setWhatsThis( hint ); // copied from KMail... KStandardAction::tipOfDay( this, SLOT( slotShowTip() ), actionCollection() ); } void TopLevel::createActions() { createMiscActions(); createLayoutActions(); } void TopLevel::toggleStatusBar() { if (statusBar()->isVisible()) statusBar()->hide(); else statusBar()->show(); } void TopLevel::togglePartDock() { if (!_partDock->isVisible()) _partDock->show(); else _partDock->hide(); } void TopLevel::toggleStackDock() { if (!_stackDock->isVisible()) _stackDock->show(); else _stackDock->hide(); } void TopLevel::toggleDumpDock() { #if ENABLE_DUMPDOCK if (!_dumpDock->isVisible()) _dumpDock->show(); else _dumpDock->hide(); #endif } void TopLevel::toggleFunctionDock() { if (!_functionDock->isVisible()) _functionDock->show(); else _functionDock->hide(); } void TopLevel::togglePercentage() { setPercentage(_taPercentage->isChecked()); } void TopLevel::setAbsoluteCost() { setPercentage(false); } void TopLevel::setRelativeCost() { setPercentage(true); } void TopLevel::updateViewsOnChange(int change) { _partSelection->notifyChange(change); _functionSelection->notifyChange(change); _multiView->notifyChange(change); } void TopLevel::setPercentage(bool show) { if (_showPercentage == show) return; _showPercentage = show; if (_taPercentage->isChecked() != show) _taPercentage->setChecked(show); GlobalConfig::setShowPercentage(_showPercentage); _stackSelection->refresh(); updateViewsOnChange(TraceItemView::configChanged); } void TopLevel::toggleExpanded() { bool show = _taExpanded->isChecked(); if (_showExpanded == show) return; _showExpanded = show; GlobalConfig::setShowExpanded(_showExpanded); _stackSelection->refresh(); updateViewsOnChange(TraceItemView::configChanged); } void TopLevel::toggleCycles() { bool show = _taCycles->isChecked(); if (_showCycles == show) return; _showCycles = show; GlobalConfig::setShowCycles(_showCycles); if (!_data) return; _data->invalidateDynamicCost(); _data->updateFunctionCycles(); _stackSelection->rebuildStackList(); updateViewsOnChange(TraceItemView::configChanged); } void TopLevel::toggleHideTemplates() { bool b = _taHideTemplates->isChecked(); if (_hideTemplates == b) return; _hideTemplates = b; GlobalConfig::setHideTemplates(_hideTemplates); _stackSelection->refresh(); updateViewsOnChange(TraceItemView::configChanged); } void TopLevel::partVisibilityChanged(bool v) { _partDockShown->setChecked(v); } void TopLevel::stackVisibilityChanged(bool v) { _stackDockShown->setChecked(v); } #if ENABLE_DUMPDOCK void TopLevel::dumpVisibilityChanged(bool v) #else void TopLevel::dumpVisibilityChanged(bool) #endif { #if ENABLE_DUMPDOCK _dumpDockShown->setChecked(v); #endif } void TopLevel::functionVisibilityChanged(bool v) { _functionDockShown->setChecked(v); if (v) _functionSelection->updateView(); } void TopLevel::querySlot() { _functionSelection->query(queryLineEdit->text()); } void TopLevel::configureKeys() { KShortcutsDialog::configure(actionCollection(), KShortcutsEditor::LetterShortcutsAllowed, this); } void TopLevel::configureToolbars() { KEditToolBar *dlg = new KEditToolBar(guiFactory(), this); if (dlg->exec()) createGUI(); delete dlg; } void TopLevel::newWindow() { TopLevel* t = new TopLevel(); t->show(); } void TopLevel::load() { QUrl url = QFileDialog::getOpenFileUrl(this, i18n("Select Callgrind Profile Data"), QUrl(), i18n("Callgrind Profile Data (cachegrind.out* callgrind.out*);;All Files (*)")); load(url); } void TopLevel::load(const QUrl& url) { if (url.isEmpty()) return; QString tmpFileName; QTemporaryFile tmpFile; if (url.isLocalFile()) { tmpFileName = url.toLocalFile(); } else if (tmpFile.open()){ // network transparency tmpFileName = tmpFile.fileName(); KIO::FileCopyJob *job = KIO::file_copy(url, QUrl::fromLocalFile(tmpFileName)); KJobWidgets::setWindow(job, this); job->exec(); } if (!tmpFileName.isEmpty()) { _openRecent->addUrl(url); _openRecent->saveEntries( KConfigGroup( KSharedConfig::openConfig(), QString() ) ); load(tmpFileName); } else { KMessageBox::error(this, i18n("Could not open the file \"%1\". " "Check it exists and you have enough " "permissions to read it.", url.toDisplayString())); } } /* if file name is ".": load first file found in current directory, but do * not show an error message if nothing could be loaded */ void TopLevel::load(QString file) { if (file.isEmpty()) return; bool showError = true; if (file == QStringLiteral(".")) showError = false; if (_data && _data->parts().count()>0) { // In new window TopLevel* t = new TopLevel(); t->show(); t->loadDelayed(file); return; } bool loaded = openDataFile(file); if (!loaded && showError) KMessageBox::error(this, i18n("Could not open the file \"%1\". " "Check it exists and you have enough " "permissions to read it.", file)); } void TopLevel::add() { QUrl url = QFileDialog::getOpenFileUrl(this, i18n("Add Callgrind Profile Data"), QUrl(), i18n("Callgrind Profile Data (cachegrind.out* callgrind.out*);;All Files (*)")); add(url); } void TopLevel::add(const QUrl &url) { if (url.isEmpty()) return; QString tmpFileName; QTemporaryFile tmpFile; if (url.isLocalFile()) { tmpFileName = url.toLocalFile(); } else if (tmpFile.open()){ // network transparency tmpFileName = tmpFile.fileName(); KIO::FileCopyJob *job = KIO::file_copy(url, QUrl::fromLocalFile(tmpFileName)); KJobWidgets::setWindow(job, this); job->exec(); } if (!tmpFileName.isEmpty()) { _openRecent->addUrl(url); _openRecent->saveEntries( KSharedConfig::openConfig()->group( QString() ) ); add(tmpFileName); } } void TopLevel::add(QString file) { if (file.isEmpty()) return; if (_data) { _data->load(file); // GUI update for added data configChanged(); return; } openDataFile(file); } void TopLevel::loadDelayed(QString file) { _loadFilesDelayed << file; QTimer::singleShot(0, this, &TopLevel::loadTraceDelayed); } void TopLevel::loadDelayed(QStringList files) { _loadFilesDelayed << files; QTimer::singleShot(0, this, &TopLevel::loadTraceDelayed); } void TopLevel::loadTraceDelayed() { if (_loadFilesDelayed.isEmpty()) return; if (_loadFilesDelayed.count()>1) { // FIXME: we expect all files to be local and existing TraceData* d = new TraceData(this); d->load(_loadFilesDelayed); setData(d); } else { QString file = _loadFilesDelayed[0]; // if URL scheme is missing (URL is relative), this is a local file QUrl u = QUrl::fromUserInput(file, QString(), QUrl::AssumeLocalFile); if (u.isLocalFile()) load(file); // special case for local file: maybe just prefix else load(u); } _loadFilesDelayed.clear(); } void TopLevel::reload() { QString trace; if (!_data || _data->parts().count()==0) trace = QStringLiteral("."); // open first trace found in dir else trace = _data->traceName(); // this also keeps sure we have the same browsing position... openDataFile(trace); } void TopLevel::exportGraph() { if (!_data || !_function) return; QString n = QStringLiteral("callgraph.dot"); GraphExporter ge(_data, _function, _eventType, _groupType, n); ge.writeDot(); #ifdef Q_OS_UNIX // shell commands only work in UNIX QString cmd = QStringLiteral("(dot %1 -Tps > %2.ps; xdg-open %3.ps)&") .arg(n).arg(n).arg(n); if (::system(QFile::encodeName( cmd ))<0) qDebug() << "TopLevel::exportGraph: can not run " << cmd; #endif } bool TopLevel::setEventType(QString s) { EventType* ct; ct = (_data) ? _data->eventTypes()->type(s) : 0; // if costtype with given name not found, use first available if (!ct && _data) ct = _data->eventTypes()->type(0); return setEventType(ct); } bool TopLevel::setEventType2(QString s) { EventType* ct; // Special type i18n("(Hidden)") gives 0 ct = (_data) ? _data->eventTypes()->type(s) : 0; return setEventType2(ct); } void TopLevel::eventTypeSelected(const QString& s) { EventType* ct; ct = (_data) ? _data->eventTypes()->typeForLong(s) : 0; setEventType(ct); } void TopLevel::eventType2Selected(const QString& s) { EventType* ct; ct = (_data) ? _data->eventTypes()->typeForLong(s) : 0; setEventType2(ct); } bool TopLevel::setEventType(EventType* ct) { if (_eventType == ct) return false; _eventType = ct; if (ct) { QStringList l = _saCost->items(); int idx = l.indexOf(ct->longName()); if (idx >= 0) _saCost->setCurrentItem(idx); } _partSelection->setEventType(_eventType); _stackSelection->setEventType(_eventType); _functionSelection->setEventType(_eventType); _multiView->setEventType(_eventType); updateStatusBar(); return true; } bool TopLevel::setEventType2(EventType* ct) { if (_eventType2 == ct) return false; _eventType2 = ct; QString longName = ct ? ct->longName() : i18n("(Hidden)"); QStringList l = _saCost2->items(); int idx = l.indexOf(longName); if (idx >= 0) _saCost2->setCurrentItem(idx); _partSelection->setEventType2(_eventType2); _stackSelection->setEventType2(_eventType2); _functionSelection->setEventType2(_eventType2); _multiView->setEventType2(_eventType2); updateStatusBar(); return true; } void TopLevel::groupTypeSelected(int cg) { switch(cg) { case 0: setGroupType( ProfileContext::Function ); break; case 1: setGroupType( ProfileContext::Object ); break; case 2: setGroupType( ProfileContext::File ); break; case 3: setGroupType( ProfileContext::Class ); break; case 4: setGroupType( ProfileContext::FunctionCycle ); break; default: break; } } bool TopLevel::setGroupType(QString s) { ProfileContext::Type gt; gt = ProfileContext::type(s); // only allow Function/Object/File/Class as grouptype switch(gt) { case ProfileContext::Object: case ProfileContext::File: case ProfileContext::Class: case ProfileContext::FunctionCycle: break; default: gt = ProfileContext::Function; } return setGroupType(gt); } bool TopLevel::setGroupType(ProfileContext::Type gt) { if (_groupType == gt) return false; _groupType = gt; int idx = -1; switch(gt) { case ProfileContext::Function: idx = 0; break; case ProfileContext::Object: idx = 1; break; case ProfileContext::File: idx = 2; break; case ProfileContext::Class: idx = 3; break; case ProfileContext::FunctionCycle: idx = 4; break; default: break; } if (idx==-1) return false; if (saGroup->currentItem() != idx) saGroup->setCurrentItem(idx); _stackSelection->setGroupType(_groupType); _partSelection->set(_groupType); _functionSelection->set(_groupType); _multiView->set(_groupType); updateStatusBar(); return true; } bool TopLevel::setGroup(QString s) { TraceCostItem* ci = _functionSelection->group(s); if (!ci) return false; return setGroup(ci); } bool TopLevel::setGroup(TraceCostItem* g) { if (_group == g) return false; _group = g; _functionSelection->setGroup(g); updateStatusBar(); return true; } bool TopLevel::setFunction(QString s) { if (!_data) return false; ProfileCostArray* f = _data->search(ProfileContext::Function, s, _eventType); if (!f) return false; return setFunction((TraceFunction*)f); } bool TopLevel::setFunction(TraceFunction* f) { if (_function == f) return false; _function = f; _multiView->activate(f); _functionSelection->activate(f); _partSelection->activate(f); _stackSelection->setFunction(_function); StackBrowser* b = _stackSelection->browser(); if (b) { // do not disable up: a press forces stack-up extending... _paForward->setEnabled(b->canGoForward()); _paBack->setEnabled(b->canGoBack()); } #if TRACE_UPDATES qDebug("TopLevel::setFunction(%s), lastSender %s", f ? f->prettyName().ascii() : "0", _lastSender ? _lastSender->name() :"0" ); #endif return true; } /** * Delayed versions. * We always have a pair of slots: One receiver to start the * delay with a singleShot Timer. It stores the parameter into a * temporary variable. And one parameterless slot for * forwarding, using this temporary. */ void TopLevel::setEventTypeDelayed(EventType* ct) { _eventTypeDelayed = ct; QTimer::singleShot (0, this, SLOT(setEventTypeDelayed())); } void TopLevel::setEventType2Delayed(EventType* ct) { _eventType2Delayed = ct; QTimer::singleShot (0, this, SLOT(setEventType2Delayed())); } void TopLevel::setEventTypeDelayed() { setEventType(_eventTypeDelayed); } void TopLevel::setEventType2Delayed() { setEventType2(_eventType2Delayed); } void TopLevel::setGroupTypeDelayed(ProfileContext::Type gt) { _groupTypeDelayed = gt; QTimer::singleShot (0, this, SLOT(setGroupTypeDelayed())); } void TopLevel::setGroupTypeDelayed() { setGroupType(_groupTypeDelayed); } void TopLevel::setGroupDelayed(TraceCostItem* g) { #if TRACE_UPDATES qDebug("TopLevel::setGroupDelayed(%s), sender %s", g ? g->prettyName().ascii() : "0", _lastSender ? _lastSender->name() :"0" ); #endif _groupDelayed = g; QTimer::singleShot (0, this, SLOT(setGroupDelayed())); } void TopLevel::setGroupDelayed() { setGroup(_groupDelayed); } void TopLevel::setDirectionDelayed(TraceItemView::Direction d) { _directionDelayed = d; QTimer::singleShot (0, this, SLOT(setDirectionDelayed())); } void TopLevel::setDirectionDelayed() { switch(_directionDelayed) { case TraceItemView::Back: _stackSelection->browserBack(); break; case TraceItemView::Forward: _stackSelection->browserForward(); break; case TraceItemView::Up: { StackBrowser* b = _stackSelection ? _stackSelection->browser() : 0; HistoryItem* hi = b ? b->current() : 0; TraceFunction* f = hi ? hi->function() : 0; if (!f) break; f = hi->stack()->caller(f, false); if (f) setFunction(f); } break; default: break; } _directionDelayed = TraceItemView::None; } void TopLevel::setTraceItemDelayed(CostItem* i) { // no need to select same item a 2nd time... if (_traceItemDelayed == i) return; _traceItemDelayed = i; _lastSender = sender(); qDebug() << "Selected " << (i ? i->prettyName() : QStringLiteral("(none)")); #if TRACE_UPDATES qDebug("TopLevel::setTraceItemDelayed(%s), sender %s", i ? i->prettyName().ascii() : "0", _lastSender ? _lastSender->name() :"0" ); #endif QTimer::singleShot (0, this, SLOT(setTraceItemDelayed())); } void TopLevel::setTraceItemDelayed() { if (!_traceItemDelayed) return; switch(_traceItemDelayed->type()) { case ProfileContext::Function: case ProfileContext::FunctionCycle: setFunction((TraceFunction*)_traceItemDelayed); break; case ProfileContext::Object: case ProfileContext::File: case ProfileContext::Class: _multiView->activate(_traceItemDelayed); break; #if 0 // this conflicts with the selection policy of InstrView ?!? case ProfileContext::Instr: case ProfileContext::Line: // only for multiview _multiView->activate(_traceItemDelayed); break; #endif default: break; } _traceItemDelayed = 0; _lastSender = 0; } /** * A TraceData object cannot be viewed many times in different * toplevel windows. Thus, this toplevel window takes ownership * of the TraceData object: on closing the window or opening * another trace, the object is destroyed. */ void TopLevel::setData(TraceData* data) { if (data == _data) return; _lastSender = 0; saveTraceSettings(); if (_data) { _partSelection->setData(0); _stackSelection->setData(0); _functionSelection->setData(0); _multiView->setData(0); _multiView->updateView(true); // we are the owner... delete _data; } // reset members resetState(); _data = data; // fill cost type list QStringList types; if (_data) { /* add all supported virtual types */ EventTypeSet* m = _data->eventTypes(); m->addKnownDerivedTypes(); /* first, fill selection list with available cost types */ for (int i=0;irealCount();i++) types << m->realType(i)->longName(); for (int i=0;iderivedCount();i++) types << m->derivedType(i)->longName(); } _saCost->setItems(types); _saCost->setComboWidth(300); if (types.count()>0) { // second type list gets an additional "(Hidden)" types.prepend(i18n("(Hidden)")); } _saCost2->setItems(types); _saCost2->setComboWidth(300); // default is hidden if (types.count()>0) _saCost2->setCurrentItem(0); _partSelection->setData(_data); _stackSelection->setData(_data); _functionSelection->setData(_data); _multiView->setData(_data); // Force update of _data in all children of _multiView // This is needed to make restoring of activeItem work! _multiView->updateView(true); /* this is needed to let the other widgets know the types */ restoreTraceTypes(); restoreTraceSettings(); QString caption; if (_data) { caption = _data->traceName(); if (!_data->command().isEmpty()) caption += " [" + _data->command() + ']'; } setWindowTitle(caption); if (!_data || (!_forcePartDock && _data->parts().count()<2)) { _partDock->hide(); _partDockShown->setChecked(false); } else { _partDock->show(); _partDockShown->setChecked(true); } updateStatusBar(); } void TopLevel::addEventTypeMenu(QMenu* popup, bool withCost2) { if (_data) { QMenu *popup1, *popup2 = 0; QAction* action; popup1 = popup->addMenu(i18n("Primary Event Type")); connect(popup1, SIGNAL(triggered(QAction*)), this, SLOT(setEventType(QAction*))); if (withCost2) { popup2 = popup->addMenu(i18n("Secondary Event Type")); connect(popup2, SIGNAL(triggered(QAction*)), this, SLOT(setEventType2(QAction*))); if (_eventType2) { action = popup2->addAction(i18n("Hide")); action->setData(199); popup2->addSeparator(); } } EventTypeSet* m = _data->eventTypes(); EventType* ct; for (int i=0;irealCount();i++) { ct = m->realType(i); action = popup1->addAction(ct->longName()); action->setCheckable(true); action->setData(100+i); if (_eventType == ct) action->setChecked(true); if (popup2) { action = popup2->addAction(ct->longName()); action->setCheckable(true); action->setData(100+i); if (_eventType2 == ct) action->setChecked(true); } } for (int i=0;iderivedCount();i++) { ct = m->derivedType(i); action = popup1->addAction(ct->longName()); action->setCheckable(true); action->setData(200+i); if (_eventType == ct) action->setChecked(true); if (popup2) { action = popup2->addAction(ct->longName()); action->setCheckable(true); action->setData(200+i); if (_eventType2 == ct) action->setChecked(true); } } } if (_showPercentage) popup->addAction(i18n("Show Absolute Cost"), this, SLOT(setAbsoluteCost())); else popup->addAction(i18n("Show Relative Cost"), this, SLOT(setRelativeCost())); } bool TopLevel::setEventType(QAction* action) { if (!_data) return false; int id = action->data().toInt(0); EventTypeSet* m = _data->eventTypes(); EventType* ct=0; if (id >=100 && id<199) ct = m->realType(id-100); if (id >=200 && id<299) ct = m->derivedType(id-200); return ct ? setEventType(ct) : false; } bool TopLevel::setEventType2(QAction* action) { if (!_data) return false; int id = action->data().toInt(0); EventTypeSet* m = _data->eventTypes(); EventType* ct=0; if (id >=100 && id<199) ct = m->realType(id-100); if (id >=200 && id<299) ct = m->derivedType(id-200); return setEventType2(ct); } void TopLevel::addGoMenu(QMenu* popup) { popup->addAction(i18n("Go Back"), this, SLOT(goBack())); popup->addAction(i18n("Go Forward"), this, SLOT(goForward())); popup->addAction(i18n("Go Up"), this, SLOT(goUp())); } void TopLevel::goBack() { setDirectionDelayed(TraceItemView::Back); } void TopLevel::goForward() { setDirectionDelayed(TraceItemView::Forward); } void TopLevel::goUp() { setDirectionDelayed(TraceItemView::Up); } QString TopLevel::traceKey() { if (!_data || _data->command().isEmpty()) return QString(); QString name = _data->command(); QString key; for (int l=0;lvalue(QStringLiteral("GroupType%1").arg(key),QString()).toString(); eventType = pConfig->value(QStringLiteral("EventType%1").arg(key),QString()).toString(); eventType2 = pConfig->value(QStringLiteral("EventType2%1").arg(key),QString()).toString(); delete pConfig; ConfigGroup* cConfig = ConfigStorage::group(QStringLiteral("CurrentState")); if (groupType.isEmpty()) groupType = cConfig->value(QStringLiteral("GroupType"),QString()).toString(); if (eventType.isEmpty()) eventType = cConfig->value(QStringLiteral("EventType"),QString()).toString(); if (eventType2.isEmpty()) eventType2 = cConfig->value(QStringLiteral("EventType2"),QString()).toString(); delete cConfig; setGroupType(groupType); setEventType(eventType); setEventType2(eventType2); // if still no cost type set, use first available if (!_eventType && !_saCost->items().isEmpty()) eventTypeSelected(_saCost->items().first()); ConfigGroup* aConfig = ConfigStorage::group(QStringLiteral("Layouts")); _layoutCount = aConfig->value(QStringLiteral("Count%1").arg(key), 0).toInt(); _layoutCurrent = aConfig->value(QStringLiteral("Current%1").arg(key), 0).toInt(); delete aConfig; if (_layoutCount == 0) layoutRestore(); updateLayoutActions(); } /** * This must be called after setting group/cost types in the function * selection widget, because the group/function choosing depends on * filled lists in the function selection widget */ void TopLevel::restoreTraceSettings() { if (!_data) return; QString key = traceKey(); restoreCurrentState(key); ConfigGroup* pConfig = ConfigStorage::group(QStringLiteral("TracePositions")); QString group = pConfig->value(QStringLiteral("Group%1").arg(key),QString()).toString(); delete pConfig; if (!group.isEmpty()) setGroup(group); // restoreCurrentState() usually leads to a call to setTraceItemDelayed() // to restore last active item... if (!_traceItemDelayed) { // function not available any more.. try with "main" if (!setFunction(QStringLiteral("main"))) _functionSelection->selectTopFunction(); } } /* Layout */ void TopLevel::layoutDuplicate() { // save current and allocate a new slot _multiView->saveLayout(QStringLiteral("Layout%1-MainView").arg(_layoutCurrent), traceKey()); _layoutCurrent = _layoutCount; _layoutCount++; updateLayoutActions(); if (0) qDebug() << "TopLevel::layoutDuplicate: count " << _layoutCount; } void TopLevel::layoutRemove() { if (_layoutCount <2) return; int from = _layoutCount-1; if (_layoutCurrent == from) { _layoutCurrent--; from--; } // restore from last and decrement count _multiView->restoreLayout(QStringLiteral("Layout%1-MainView").arg(from), traceKey()); _layoutCount--; updateLayoutActions(); } void TopLevel::layoutNext() { if (_layoutCount <2) return; QString key = traceKey(); QString layoutPrefix = QStringLiteral("Layout%1-MainView"); _multiView->saveLayout(layoutPrefix.arg(_layoutCurrent), key); _layoutCurrent++; if (_layoutCurrent == _layoutCount) _layoutCurrent = 0; _multiView->restoreLayout(layoutPrefix.arg(_layoutCurrent), key); if (0) qDebug() << "TopLevel::layoutNext: current " << _layoutCurrent << endl; } void TopLevel::layoutPrevious() { if (_layoutCount <2) return; QString key = traceKey(); QString layoutPrefix = QStringLiteral("Layout%1-MainView"); _multiView->saveLayout(layoutPrefix.arg(_layoutCurrent), key); _layoutCurrent--; if (_layoutCurrent <0) _layoutCurrent = _layoutCount-1; _multiView->restoreLayout(layoutPrefix.arg(_layoutCurrent), key); if (0) qDebug() << "TopLevel::layoutPrevious: current " << _layoutCurrent << endl; } void TopLevel::layoutSave() { QString key = traceKey(); QString layoutPrefix = QStringLiteral("Layout%1-MainView"); _multiView->saveLayout(layoutPrefix.arg(_layoutCurrent), key); // save all layouts as defaults (ie. without any group name postfix) for(int i=0;i<_layoutCount;i++) { _multiView->restoreLayout(layoutPrefix.arg(i), key); _multiView->saveLayout(layoutPrefix.arg(i), QString()); } // restore the previously saved current layout _multiView->restoreLayout(layoutPrefix.arg(_layoutCurrent), key); ConfigGroup* layoutConfig = ConfigStorage::group(QStringLiteral("Layouts")); layoutConfig->setValue(QStringLiteral("DefaultCount"), _layoutCount); layoutConfig->setValue(QStringLiteral("DefaultCurrent"), _layoutCurrent); delete layoutConfig; } void TopLevel::layoutRestore() { KConfig *config = KSharedConfig::openConfig().data(); KConfigGroup aConfig(config, "Layouts"); _layoutCount = aConfig.readEntry("DefaultCount", 0); _layoutCurrent = aConfig.readEntry("DefaultCurrent", 0); if (_layoutCount == 0) { _layoutCount++; return; } QString layoutPrefix = QStringLiteral("Layout%1-MainView"); _multiView->restoreLayout( layoutPrefix.arg(_layoutCurrent), traceKey()); updateLayoutActions(); } void TopLevel::updateLayoutActions() { QAction* ka; ka = actionCollection()->action(QStringLiteral("layout_next")); if (ka) ka->setEnabled(_layoutCount>1); ka = actionCollection()->action(QStringLiteral("layout_previous")); if (ka) ka->setEnabled(_layoutCount>1); ka = actionCollection()->action(QStringLiteral("layout_remove")); if (ka) ka->setEnabled(_layoutCount>1); _statusbar->showMessage(i18n("Layout Count: %1", _layoutCount), 1000); } void TopLevel::updateStatusBar() { if (!_data || _data->parts().count()==0) { _statusLabel->setText(i18n("No profile data file loaded.")); return; } QString status = QStringLiteral("%1 [%2] - ") .arg(_data->shortTraceName()) .arg(_data->activePartRange()); if (_eventType) { status += i18n("Total %1 Cost: %2", _eventType->longName(), _data->prettySubCost(_eventType)); /* this gets too long... if (_eventType2 && (_eventType2 != _eventType)) status += i18n(", %1 Cost: %2") .arg(_eventType2->longName()) .arg(_data->prettySubCost(_eventType2)); */ } else status += i18n("No event type selected"); /* Not working... should give group of selected function if (_groupType != ProfileContext::Function) { status += QString(" - %1 '%2'") .arg(ProfileContext::i18nTypeName(_groupType)) .arg(_group ? _group->prettyName() : i18n("(None)")); } */ _statusLabel->setText(status); } void TopLevel::configure() { if (ConfigDlg::configure( GlobalGUIConfig::config(), _data, this)) { GlobalGUIConfig::config()->saveOptions(); configChanged(); } else GlobalGUIConfig::config()->readOptions(); } bool TopLevel::queryClose() { saveTraceSettings(); // save current toplevel options as defaults... GlobalConfig::setShowPercentage(_showPercentage); GlobalConfig::setShowExpanded(_showExpanded); GlobalConfig::setShowCycles(_showCycles); GlobalConfig::setHideTemplates(_hideTemplates); GlobalGUIConfig::config()->saveOptions(); saveCurrentState(QString()); // toolbar and dock positions are automatically stored // if part dock was chosen visible even for only 1 part loaded, // keep this choice... _forcePartDock = false; if (_data && (_data->parts().count()<2) && _partDock->isVisible()) _forcePartDock=true; KConfigGroup dockConfig(KSharedConfig::openConfig(), "Docks"); dockConfig.writeEntry("ForcePartDockVisible", _forcePartDock); return true; } void TopLevel::splitSlot() { int count = _multiView->childCount(); if (count<1) count = 1; if (count>2) count = 2; count = 3-count; _multiView->setChildCount(count); _taSplit->setChecked(count>1); _taSplitDir->setEnabled(count>1); _taSplitDir->setChecked(_multiView->orientation() == Qt::Horizontal); } void TopLevel::splitDirSlot() { _multiView->setOrientation( _taSplitDir->isChecked() ? Qt::Horizontal : Qt::Vertical ); } // this is called after a config change in the dialog void TopLevel::configChanged() { // Invalidate found/cached dirs of source files if we have TraceData loaded. if (_data) { _data->resetSourceDirs(); } _stackSelection->refresh(); updateViewsOnChange(TraceItemView::configChanged); } void TopLevel::slotShowTipOnStart() { KTipDialog::showTip(this); } void TopLevel::slotShowTip() { KTipDialog::showTip( this, QString(), true ); } void TopLevel::dummySlot() { } void TopLevel::activePartsChangedSlot(const TracePartList& list) { if (!_data) return; if (!_data->activateParts(list)) { // qDebug("TopLevel::activePartsChangedSlot: No Change!"); return; } _activeParts = list; _partSelection->set(list); _multiView->set(list); _functionSelection->set(list); _stackSelection->refresh(); updateStatusBar(); } void TopLevel::partsHideSelectedSlotDelayed() { QTimer::singleShot( 0, this, &TopLevel::partsHideSelectedSlot ); } // this puts selected parts into hidden list, // deselects them and makes the remaining parts selected void TopLevel::partsHideSelectedSlot() { if (!_data) return; TracePartList newHidden, newActive; foreach(TracePart* part, _data->parts()) { if (_activeParts.contains(part) || _hiddenParts.contains(part)) newHidden.append(part); else newActive.append(part); } _hiddenParts = newHidden; _partSelection->hiddenPartsChangedSlot(_hiddenParts); #if 0 _mainWidget1->hiddenPartsChangedSlot(_hiddenParts); _mainWidget2->hiddenPartsChangedSlot(_hiddenParts); #endif activePartsChangedSlot(newActive); } void TopLevel::partsUnhideAllSlotDelayed() { QTimer::singleShot( 0, this, &TopLevel::partsUnhideAllSlot ); } // this unhides all hidden parts. Does NOT change selection void TopLevel::partsUnhideAllSlot() { if (!_data) return; _hiddenParts.clear(); _partSelection->hiddenPartsChangedSlot(_hiddenParts); #if 0 _mainWidget1->hiddenPartsChangedSlot(_hiddenParts); _mainWidget2->hiddenPartsChangedSlot(_hiddenParts); #endif } void TopLevel::forceTrace() { if (_ccProcess) { // callgrind_control still running, cancel request qDebug("TopLevel::forceTrace: killing old callgrind_control"); _ccProcess->kill(); delete _ccProcess; _ccProcess = 0; _ccOutput = QString(); } if (!_taDump->isChecked()) return; // get PID of first loaded part int pid = 0; TracePart* p = 0; TracePartList pl; if (_data) pl = _data->parts(); if (!pl.isEmpty()) p = pl.first(); if (p) pid = p->processID(); if (pid == 0) { showMessage(i18n("Cannot determine receiver PID for dump request"), 5000); _taDump->setChecked(false); return; } qDebug("TopLevel::forceTrace: run 'callgrind_control -d %d'", pid); _ccProcess = new QProcess(this); connect(_ccProcess, &QProcess::readyReadStandardOutput, this, &TopLevel::ccReadOutput); connect(_ccProcess, SIGNAL(error(QProcess::ProcessError)), SLOT(ccError(QProcess::ProcessError))); connect(_ccProcess, SIGNAL(finished(int,QProcess::ExitStatus)), SLOT(ccExit(int,QProcess::ExitStatus))); _ccProcess->start(QStringLiteral("callgrind_control -d %1").arg(pid), QIODevice::ReadOnly); } void TopLevel::ccReadOutput() { QProcess* p = qobject_cast(sender()); qDebug("TopLevel::ccReadOutput: QProcess %p", p); // signal from old/uninteresting process? if (!_ccProcess) return; if (p != _ccProcess) return; - _ccOutput.append(_ccProcess->readAllStandardOutput()); + _ccOutput.append(QString(_ccProcess->readAllStandardOutput())); } void TopLevel::ccError(QProcess::ProcessError e) { QProcess* p = qobject_cast(sender()); qDebug("TopLevel::ccError: Got %d from QProcess %p", e, p); // signal from old/uninteresting process? if (!_ccProcess) return; if (p != _ccProcess) return; showMessage(i18n("Error running callgrind_control"), 5000); _ccProcess->deleteLater(); _ccProcess = 0; } void TopLevel::ccExit(int exitCode, QProcess::ExitStatus s) { QProcess* p = qobject_cast(sender()); qDebug("TopLevel::ccExit: QProcess %p, exitCode %d", p, exitCode); // signal from old/uninteresting process? if (!_ccProcess) return; if (p != _ccProcess) return; _ccProcess->deleteLater(); _ccProcess = 0; _taDump->setChecked(false); // if not successful no need to reload if ((s == QProcess::CrashExit) || (exitCode != 0)) return; // FIXME: Are we sure that file is completely // dumped after waiting one second? QTimer::singleShot( 1000, this, &TopLevel::reload ); } void TopLevel::forwardAboutToShow() { QMenu *popup = _paForward->menu(); popup->clear(); StackBrowser* b = _stackSelection ? _stackSelection->browser() : 0; HistoryItem* hi = b ? b->current() : 0; TraceFunction* f; QAction* action; if (!hi) { popup->addAction(i18n("(No Stack)")); return; } hi = hi->next(); if (!hi) { popup->addAction(i18n("(No next function)")); return; } int count = 1; while (countfunction(); if (!f) break; QString name = GlobalConfig::shortenSymbol(f->prettyName()); //qDebug("forward: Adding %s", name.ascii()); action = popup->addAction(name); action->setData(count); hi = hi->next(); count++; } } void TopLevel::backAboutToShow() { QMenu *popup = _paBack->menu(); popup->clear(); StackBrowser* b = _stackSelection ? _stackSelection->browser() : 0; HistoryItem* hi = b ? b->current() : 0; TraceFunction* f; QAction* action; if (!hi) { popup->addAction(i18n("(No Stack)")); return; } hi = hi->last(); if (!hi) { popup->addAction(i18n("(No previous function)")); return; } int count = 1; while (countfunction(); if (!f) break; QString name = GlobalConfig::shortenSymbol(f->prettyName()); //qDebug("back: Adding %s", name.ascii()); action = popup->addAction(name); action->setData(count); hi = hi->last(); count++; } } void TopLevel::upAboutToShow() { QMenu *popup = _paUp->menu(); popup->clear(); StackBrowser* b = _stackSelection ? _stackSelection->browser() : 0; HistoryItem* hi = b ? b->current() : 0; TraceFunction* f = hi ? hi->function() : 0; QAction* action; if (!f) { popup->addAction(i18n("(No Stack)")); return; } f = hi->stack()->caller(f, false); if (!f) { popup->addAction(i18n("(No Function Up)")); return; } int count = 1; while (countprettyName()); action = popup->addAction(name); action->setData(count); f = hi->stack()->caller(f, false); count++; } } void TopLevel::forwardTriggered(QAction* action) { int count = action->data().toInt(0); //qDebug("forwardTriggered: %d", count); if( count <= 0) return; StackBrowser* b = _stackSelection ? _stackSelection->browser() : 0; if (!b) return; while (count>1) { b->goForward(); count--; } _stackSelection->browserForward(); } void TopLevel::backTriggered(QAction* action) { int count = action->data().toInt(0); //qDebug("backTriggered: %d", count); if( count <= 0) return; StackBrowser* b = _stackSelection ? _stackSelection->browser() : 0; if (!b) return; while (count>1) { b->goBack(); count--; } _stackSelection->browserBack(); } void TopLevel::upTriggered(QAction* action) { int count = action->data().toInt(0); //qDebug("upTriggered: %d", count); if( count <= 0) return; StackBrowser* b = _stackSelection ? _stackSelection->browser() : 0; HistoryItem* hi = b ? b->current() : 0; if (!hi) return; TraceFunction* f = hi->function(); while (count>0 && f) { f = hi->stack()->caller(f, false); count--; } //qDebug("upActivated: %s", f ? f->prettyName().ascii() : "??" ); if (f) setFunction(f); } void TopLevel::showMessage(const QString& msg, int ms) { if (_statusbar) _statusbar->showMessage(msg, ms); } void TopLevel::showStatus(const QString& msg, int progress) { static bool msgUpdateNeeded = true; if (!_statusbar) return; if (msg.isEmpty()) { //reset status if (_progressBar) { _statusbar->removeWidget(_progressBar); delete _progressBar; _progressBar = 0; } _statusbar->clearMessage(); _progressMsg = msg; return; } if (_progressMsg.isEmpty()) _progressStart.start(); if (msg != _progressMsg) { _progressMsg = msg; msgUpdateNeeded = true; } // do nothing if last change was less than 0.5 seconds ago if (_progressStart.elapsed() < 500) return; if (!_progressBar) { _progressBar = new QProgressBar(_statusbar); _progressBar->setMaximumSize(200, _statusbar->height()-4); _statusbar->addPermanentWidget(_progressBar, 1); _progressBar->show(); msgUpdateNeeded = true; } _progressStart.restart(); if (msgUpdateNeeded) { _statusbar->showMessage(msg); msgUpdateNeeded = false; } _progressBar->setValue(progress); // let the progress bar update itself qApp->processEvents(QEventLoop::ExcludeUserInputEvents); } void TopLevel::loadStart(const QString& filename) { showStatus(i18n("Loading %1", filename), 0); Logger::_filename = filename; } void TopLevel::loadFinished(const QString& msg) { showStatus(QString(), 0); if (!msg.isEmpty()) showMessage(i18n("Error loading %1: %2", _filename, msg), 2000); } void TopLevel::loadProgress(int progress) { showStatus(i18n("Loading %1", _filename), progress); } void TopLevel::loadError(int line, const QString& msg) { qCritical() << "Loading" << _filename << ":" << line << ": " << msg; } void TopLevel::loadWarning(int line, const QString& msg) { qWarning() << "Loading" << _filename << ":" << line << ": " << msg; } bool TopLevel::openDataFile(const QString& file) { TraceData* d = new TraceData(this); int filesLoaded; // see whether this file is compressed, than take the direct route QMimeDatabase dataBase; QString mimeType = dataBase.mimeTypeForFile(file, QMimeDatabase::MatchContent).name(); KCompressionDevice* compressed; compressed = new KCompressionDevice(file, KFilterDev::compressionTypeForMimeType(mimeType)); if (compressed && (compressed->compressionType() != KCompressionDevice::None)) { filesLoaded = d->load(compressed, file); } else { // else fallback to string based method that can also find multi-part callgrind data. filesLoaded = d->load(file); } if (filesLoaded > 0) { setData(d); return true; } else { return false; } } diff --git a/libcore/tracedata.h b/libcore/tracedata.h index b52f24f..c10a2e0 100644 --- a/libcore/tracedata.h +++ b/libcore/tracedata.h @@ -1,1519 +1,1518 @@ /* This file is part of KCachegrind. Copyright (c) 2002-2016 Josef Weidendorfer KCachegrind 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, version 2. 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; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /* * Classes holding profiling data for * multiple tracefiles for one command. * See class TraceData first. */ #ifndef TRACEDATA_H #define TRACEDATA_H #include #include #include #include "costitem.h" #include "subcost.h" #include "utils.h" #include "addr.h" #include "context.h" #include "eventtype.h" class QFile; /** * All cost items are classes prefixed with "Trace". * "ProfileCostArray" holds basic cost metrics for the simplest, smallest * trace entity: These are events counted for an instruction at * a specific memory address of the traced program. * All other cost items are derived from ProfileCostArray, and add needed * cost metrics, e.g. for a call the number of calls that happened. * * Abstract, i.e. never instantiated cost items are * - ProfileCostArray: Basic cost metrics (instr/read/write access + cache events) * - TraceCallCost: Additional call count cost metric. * - TraceInclusiveCost: Additional ProfileCostArray aggregated. * - TraceListCost: Adds dependency to a list of ProfileCostArray's * - TraceCallListCost: same for list of TraceCallCost's * - TraceInclusiveListCost: same for list of TraceInclusiveCost's * - TraceCostItem: Base for cost items for "interesting" costs: * TraceFunction, TraceClass, TraceFile, TraceObject * * The smallest Cachegrind output is trace data indexed by a source * line number, a TracePartLine. Another one is a call from one * source line of a function to another function, a TracePartLineCall. * All other cost items derive the value by summation of cost metrics * from TraceLineItem and TracePartLineCall costs; their cost is * calculated lazy on demand and cached afterwards. * * For cost items, which are sums over all trace files read in, the * summed cost metrics change when e.g. a new trace file is read. * Thus, their cached costs are invalidated, and again recalculated * only on demand. In the following list, theses cost items are called * "dynamic", the other "fixed" (but neverless calculated lazy). * * Cost Item Type Summation of ... * * TracePartLineCall fixed Read from trace file * TracePartLine fixed Read from trace file * TracePartCall fixed TracePartLineCall's * TraceLineCall dynamic TracePartLineCall's * TraceCall dynamic TraceLineCall's * TraceLine dynamic TracePartLine's and TraceLineCall's * TracePartFunction fixed TracePartLine's / TracePartCall's * TraceFunction dynamic TraceLine's / TraceCall's (called from) * TracePartClass fixed TracePartFunction's * TraceClass dynamic TraceFunction's * TracePartFile fixed TracePartFunction's * TraceFile dynamic TraceFunction's * TracePartObject fixed TracePartFunction's * TraceObject dynamic TraceFunction's * TracePart fixed TracePartLine's * TraceData dynamic TracePart's * * As there exists only one TraceData object for a traced program, it is the * owner of some "high level" cost items. The following shows the owner * relationship of the cost item classes, together with references. * * Cost Item Owner (& back ref) Other References to * * TracePartLineCall TraceLineCall * TracePartCall TraceCall TracePartLineCall's * TracePartLine TraceLine TracePartLineCall's * TracePartFunction TraceFunction * TracePartClass TraceClass TracePart * TracePartFile TraceFile TracePart * TracePartObject TraceObject TracePart * TraceLineCall TraceCall TracePartLineCall's * TraceCall TraceFunction TracePartCall's * TraceLine TraceData TraceLineCall's * TraceFunction TraceData TraceCall's (calling) * TraceClass TraceData * TraceFile TraceData * TraceObject TraceData * TracePart TraceData * TraceData Main Application * * Convention: * - The owner has a factory method for owned objects, * and calls addXXX() to install references in other objects * - The owner is first arg in a constructor. */ class FixCost; class FixCallCost; class FixJump; class FixPool; class DynPool; class Logger; class ProfileCostArray; class EventType; class EventTypeSet; class EventTypeMapping; class TraceJumpCost; class TraceCallCost; class TraceInclusiveCost; class TracePartInstr; class TracePartInstrCall; class TracePartLine; class TracePartLineCall; class TracePartCall; class TracePartLineRegion; class TracePartFunction; class TracePartClass; class TracePartObject; class TracePartFile; class TraceInstr; class TraceInstrJump; class TraceInstrCall; class TraceLine; class TraceLineJump; class TraceLineCall; class TraceCall; class TraceLineRegion; class TraceFunctionSource; class TraceFunction; class TraceFunctionCycle; class TraceClass; class TraceObject; class TraceFile; class TracePart; class TraceData; typedef QList TraceCostList; typedef QList TraceJumpCostList; typedef QList TraceCallCostList; typedef QList TraceInclusiveCostList; typedef QList TracePartCallList; typedef QList TracePartInstrList; typedef QList TracePartLineList; typedef QList TracePartLineRegionList; typedef QList TracePartFunctionList; typedef QList TracePartInstrCallList; typedef QList TracePartLineCallList; typedef QList TracePartList; typedef QList TraceInstrList; typedef QList TraceLineList; typedef QList TraceInstrJumpList; typedef QList TraceLineJumpList; typedef QList TraceInstrCallList; typedef QList TraceLineCallList; typedef QList TraceCallList; typedef QList TraceFileList; typedef QList TraceLineRegionList; typedef QList TraceFunctionSourceList; typedef QList TraceFunctionList; typedef QList TraceFunctionCycleList; typedef QMap TraceObjectMap; typedef QMap TraceClassMap; typedef QMap TraceFileMap; typedef QMap TraceFunctionMap; typedef QMap TraceLineMap; typedef QMap TraceInstrMap; /** * Cost of a (conditional) jump. */ class TraceJumpCost: public CostItem { public: explicit TraceJumpCost(ProfileContext*); virtual ~TraceJumpCost(); // reimplementations for cost addition virtual QString costString(EventTypeSet* m); virtual void clear(); void addCost(TraceJumpCost*); // additional cost metrics SubCost followedCount(); SubCost executedCount(); void addFollowedCount(SubCost c) { _followedCount += c; } void addExecutedCount(SubCost c) { _executedCount += c; } protected: SubCost _executedCount, _followedCount; }; /** * Cost item with additional call count metric. */ class TraceCallCost: public ProfileCostArray { public: explicit TraceCallCost(ProfileContext*); virtual ~TraceCallCost(); // reimplementations for cost addition virtual QString costString(EventTypeSet* m); virtual void clear(); // additional cost metric SubCost callCount(); QString prettyCallCount(); void addCallCount(SubCost c); protected: SubCost _callCount; }; /** * Cost item with additional inclusive metric */ class TraceInclusiveCost: public ProfileCostArray { public: explicit TraceInclusiveCost(ProfileContext*); virtual ~TraceInclusiveCost(); // reimplementations for cost addition virtual QString costString(EventTypeSet* m); virtual void clear(); // additional cost metric ProfileCostArray* inclusive(); void addInclusive(ProfileCostArray*); protected: ProfileCostArray _inclusive; }; /** * Cost Item * depends on a list of cost items. */ class TraceListCost: public ProfileCostArray { public: explicit TraceListCost(ProfileContext*); virtual ~TraceListCost(); // reimplementation for dependency list virtual void update(); TraceCostList& deps() { return _deps; } void addDep(ProfileCostArray*); ProfileCostArray* findDepFromPart(TracePart*); protected: // overwrite in subclass to change update behaviour virtual bool onlyActiveParts() { return false; } TraceCostList _deps; private: // very temporary: cached ProfileCostArray* _lastDep; }; /** * Jump Cost Item * depends on a list of Jump cost items. */ class TraceJumpListCost: public TraceJumpCost { public: explicit TraceJumpListCost(ProfileContext*); virtual ~TraceJumpListCost(); // reimplementation for dependency list virtual void update(); TraceJumpCostList deps() { return _deps; } void addDep(TraceJumpCost*); TraceJumpCost* findDepFromPart(TracePart*); protected: // overwrite in subclass to change update behaviour virtual bool onlyActiveParts() { return false; } TraceJumpCostList _deps; private: // very temporary: cached TraceJumpCost* _lastDep; }; /** * Call Cost Item * depends on a list of Call cost items. */ class TraceCallListCost: public TraceCallCost { public: explicit TraceCallListCost(ProfileContext*); virtual ~TraceCallListCost(); // reimplementation for dependency list virtual void update(); TraceCallCostList deps() { return _deps; } void addDep(TraceCallCost*); TraceCallCost* findDepFromPart(TracePart*); protected: // overwrite in subclass to change update behaviour virtual bool onlyActiveParts() { return false; } TraceCallCostList _deps; private: // very temporary: cached TraceCallCost* _lastDep; }; /** * Inclusive Cost Item depends on a list of inclusive cost items. */ class TraceInclusiveListCost: public TraceInclusiveCost { public: explicit TraceInclusiveListCost(ProfileContext*); virtual ~TraceInclusiveListCost(); // reimplementation for dependency virtual void update(); TraceInclusiveCostList deps() { return _deps; } void addDep(TraceInclusiveCost*); TraceInclusiveCost* findDepFromPart(TracePart*); protected: // overwrite in subclass to change update behaviour virtual bool onlyActiveParts() { return false; } TraceInclusiveCostList _deps; private: // very temporary: cached TraceInclusiveCost* _lastDep; }; /*----------------------------------------------------------------- * Classes for cost items of one trace file, i.e. a "trace part" *----------------------------------------------------------------- */ /** * Cost of jump at a instruction code address from a trace file. */ class TracePartInstrJump: public TraceJumpCost { public: TracePartInstrJump(TraceInstrJump*, TracePartInstrJump*); virtual ~TracePartInstrJump(); // fix cost item virtual void update() {} TraceInstrJump* instrJump() const { return (TraceInstrJump*) _dep; } TracePartInstrJump* next() const { return _next; } private: // chaining all parts for InstrJump TracePartInstrJump* _next; }; /** * Cost of a call at a instruction code address from a trace file. * Cost is always up to date, no lazy update needed. */ class TracePartInstrCall: public TraceCallCost { public: explicit TracePartInstrCall(TraceInstrCall*); virtual ~TracePartInstrCall(); // fix cost item virtual void update() {} TraceInstrCall* instrCall() const { return (TraceInstrCall*) _dep; } }; /** * Cost of a code instruction address from a trace file. * Cost is always up to date, no lazy update needed. */ class TracePartInstr: public ProfileCostArray { public: explicit TracePartInstr(TraceInstr*); virtual ~TracePartInstr(); // fix cost item virtual void update() {} TraceInstr* instr() const { return (TraceInstr*)_dep; } }; /** * Cost of jump at a source line from a trace file. */ class TracePartLineJump: public TraceJumpCost { public: explicit TracePartLineJump(TraceLineJump*); virtual ~TracePartLineJump(); // fix cost item virtual void update() {} TraceLineJump* lineJump() const { return (TraceLineJump*) _dep; } }; /** * Cost of a call at a line from a trace file. * Cost is always up to date, no lazy update needed. */ class TracePartLineCall: public TraceCallCost { public: explicit TracePartLineCall(TraceLineCall*); virtual ~TracePartLineCall(); // fix cost item virtual void update() {} TraceLineCall* lineCall() const { return (TraceLineCall*) _dep; } }; /** * Cost of a line from a trace file. * Cost is always up to date, no lazy update needed. */ class TracePartLine: public ProfileCostArray { public: explicit TracePartLine(TraceLine*); virtual ~TracePartLine(); // fix cost item virtual void update() {} TraceLine* line() const { return (TraceLine*)_dep; } }; /** * Cost of a source region. */ class TracePartLineRegion: public TraceInclusiveCost { public: explicit TracePartLineRegion(TraceLineRegion*); virtual ~TracePartLineRegion(); virtual void update(); TraceLineRegion* region() const { return (TraceLineRegion*)_dep; } }; /** * Cost of a call at a function to another function, * from a single trace file. */ class TracePartCall: public TraceCallListCost { public: explicit TracePartCall(TraceCall* call); virtual ~TracePartCall(); // calls a function itself? bool isRecursion(); // reimplementation for dependency list virtual void update(); TraceCall* call() const { return (TraceCall*)_dep; } FixCallCost* setFirstFixCallCost(FixCallCost* fc) { FixCallCost* t = _firstFixCallCost; _firstFixCallCost = fc; return t; } FixCallCost* firstFixCallCost() const { return _firstFixCallCost; } private: FixCallCost* _firstFixCallCost; }; /** * Cost of a function, * from a single trace file. */ class TracePartFunction: public TraceInclusiveCost { public: TracePartFunction(TraceFunction*, TracePartObject*, TracePartFile*); virtual ~TracePartFunction(); virtual void update(); virtual QString costString(EventTypeSet* m); void addPartInstr(TracePartInstr*); void addPartLine(TracePartLine*); void addPartCaller(TracePartCall*); void addPartCalling(TracePartCall*); TraceFunction* function() { return (TraceFunction*) _dep; } TracePartObject* partObject() { return _partObject; } TracePartClass* partClass() { return _partClass; } TracePartFile* partFile() { return _partFile; } const TracePartCallList& partCallers() { return _partCallers; } const TracePartCallList& partCallings() { return _partCallings; } void setPartObject(TracePartObject* o) { _partObject = o; } void setPartClass(TracePartClass* c) { _partClass = c; } void setPartFile(TracePartFile* f) { _partFile = f; } /* for linked list of FixXXX objects */ FixCost* setFirstFixCost(FixCost* fc) { FixCost* t = _firstFixCost; _firstFixCost = fc; return t; } FixCost* firstFixCost() const { return _firstFixCost; } FixJump* setFirstFixJump(FixJump* fj) { FixJump* t = _firstFixJump; _firstFixJump = fj; return t; } FixJump* firstFixJump() const { return _firstFixJump; } // additional cost metrics SubCost calledCount(); SubCost callingCount(); QString prettyCalledCount(); QString prettyCallingCount(); int calledContexts(); int callingContexts(); private: TracePartObject* _partObject; TracePartClass* _partClass; TracePartFile* _partFile; TracePartCallList _partCallings; TracePartCallList _partCallers; TracePartInstrList _partInstr; TracePartLineList _partLines; // cached SubCost _calledCount, _callingCount; int _calledContexts, _callingContexts; FixCost* _firstFixCost; FixJump* _firstFixJump; }; /** * Cost of a class, * from a single trace file. */ class TracePartClass: public TraceInclusiveListCost { public: explicit TracePartClass(TraceClass*); virtual ~TracePartClass(); QString prettyName() const; TraceClass* cls() { return (TraceClass*)_dep; } void addPartFunction(TracePartFunction* f) { addDep(f); } }; /** * Cost of a source file, * from a single trace file. */ class TracePartFile: public TraceInclusiveListCost { public: explicit TracePartFile(TraceFile*); virtual ~TracePartFile(); TraceFile* file() { return (TraceFile*)_dep; } void addPartFunction(TracePartFunction* f) { addDep(f); } }; /** * Cost of a object, * from a single trace file. */ class TracePartObject: public TraceInclusiveListCost { public: explicit TracePartObject(TraceObject*); virtual ~TracePartObject(); TraceObject* object() const { return (TraceObject*)_dep; } void addPartFunction(TracePartFunction* f) { addDep(f); } }; /** * A Trace Part: All data read from a trace file, containing all costs * that happened in a specified time interval of the executed command. */ class TracePart: public TraceListCost { public: explicit TracePart(TraceData*); virtual ~TracePart(); virtual TracePart* part() { return this; } virtual const TracePart* part() const { return this; } QString shortName() const; QString prettyName() const; /// @return Name of the file this part was loaded from QString name() const { return _name; } QString description() const { return _descr; } QString trigger() const { return _trigger; } QString timeframe() const { return _timeframe; } QString version() const { return _version; } int partNumber() const { return _number; } int threadID() const { return _tid; } int processID() const { return _pid; } void setDescription(const QString& d) { _descr = d; } void setTrigger(const QString& t) { _trigger = t; } void setTimeframe(const QString& t) { _timeframe = t; } void setVersion(const QString& v) { _version = v; } void setName(const QString& n) { _name = n; } void setPartNumber(int n); void setThreadID(int t); void setProcessID(int p); ProfileCostArray* totals() { return &_totals; } /* passes ownership of mapping */ void setEventMapping(EventTypeMapping* sm) { _eventTypeMapping = sm; } EventTypeMapping* eventTypeMapping() { return _eventTypeMapping; } // returns true if something changed bool activate(bool); bool isActive() const { return _active; } // for sorting bool operator<(const TracePart&) const; private: - QIODevice* _file; QString _name; QString _descr; QString _trigger; QString _timeframe; QString _version; int _number, _tid, _pid; bool _active; // the totals line ProfileCostArray _totals; // event type mapping for all fix costs of this part EventTypeMapping* _eventTypeMapping; }; /*----------------------------------------------------------------- * Classes for cost items summed up from multiple trace parts *----------------------------------------------------------------- */ /** * A jump from an instruction to another inside of a function */ class TraceInstrJump: public TraceJumpCost { public: TraceInstrJump(TraceInstr* instrFrom, TraceInstr* instrTo, bool isCondJump); virtual ~TraceInstrJump(); virtual QString name() const; virtual void update(); TraceInstr* instrFrom() const { return _instrFrom; } TraceInstr* instrTo() const { return _instrTo; } bool isCondJump() const { return _isCondJump; } // part factory TracePartInstrJump* partInstrJump(TracePart*); private: TraceInstr *_instrFrom, *_instrTo; bool _isCondJump; // list of parts for this InstrJump TracePartInstrJump* _first; }; /** * A jump from one line to another inside of a function. */ class TraceLineJump: public TraceJumpListCost { public: TraceLineJump(TraceLine* lineFrom, TraceLine* lineTo, bool isCondJump); virtual ~TraceLineJump(); virtual QString name() const; TraceLine* lineFrom() const { return _lineFrom; } TraceLine* lineTo() const { return _lineTo; } bool isCondJump() { return _isCondJump; } // part factory TracePartLineJump* partLineJump(TracePart*); protected: bool onlyActiveParts() { return true; } private: TraceLine *_lineFrom, *_lineTo; bool _isCondJump; }; /** * A call from an instruction of one function to another function */ class TraceInstrCall: public TraceCallListCost { public: TraceInstrCall(TraceCall* call, TraceInstr* instr); virtual ~TraceInstrCall(); virtual QString name() const; TraceInstr* instr() const { return _instr; } TraceCall* call() const { return _call; } // part factory TracePartInstrCall* partInstrCall(TracePart*, TracePartCall*); protected: bool onlyActiveParts() { return true; } private: TraceInstr* _instr; TraceCall* _call; }; /** * A call from a line of one function to another function. */ class TraceLineCall: public TraceCallListCost { public: TraceLineCall(TraceCall* call, TraceLine* line); virtual ~TraceLineCall(); virtual QString name() const; TraceLine* line() const { return _line; } TraceCall* call() const { return _call; } // part factory TracePartLineCall* partLineCall(TracePart*, TracePartCall*); protected: bool onlyActiveParts() { return true; } private: TraceLine* _line; TraceCall* _call; }; /** * A call from one to another function. * Consists of a list a TraceLineCalls */ class TraceCall: public TraceCallListCost { public: TraceCall(TraceFunction* caller, TraceFunction* called); virtual ~TraceCall(); virtual QString name() const; // calls a function itself? bool isRecursion() { return _caller == _called; } // return cycle number >0 if call is inside of a cycle int inCycle(); // we need some special handling for cycle calls void update(); void invalidateDynamicCost(); // factories TracePartCall* partCall(TracePart*, TracePartFunction*, TracePartFunction*); TraceLineCall* lineCall(TraceLine*); TraceInstrCall* instrCall(TraceInstr*); TraceFunction* caller(bool skipCycle=false) const; TraceFunction* called(bool skipCycle=false) const; QString callerName(bool skipCycle=false) const; QString calledName(bool skipCycle=false) const; const TraceLineCallList& lineCalls() const { return _lineCalls; } const TraceInstrCallList& instrCalls() const { return _instrCalls; } FixCallCost* setFirstFixCost(FixCallCost* fc) { FixCallCost* t = _firstFixCost; _firstFixCost = fc; return t; } protected: bool onlyActiveParts() { return true; } private: TraceInstrCallList _instrCalls; TraceLineCallList _lineCalls; TraceFunction* _caller; TraceFunction* _called; FixCallCost* _firstFixCost; }; /** * A code instruction address of the program. * Consists of a list a TracePartInstr from different trace files * and a list of TraceInstrCalls if there are calls from this address. */ class TraceInstr: public TraceListCost { public: TraceInstr(); virtual ~TraceInstr(); virtual QString name() const; QString prettyName() const; bool isValid() { return _addr != Addr(0); } // factories TracePartInstr* partInstr(TracePart* part, TracePartFunction* partFunction); TraceInstrJump* instrJump(TraceInstr* to, bool isCondJump); void addInstrCall(TraceInstrCall*); Addr addr() const { return _addr; } TraceFunction* function() const { return _function; } TraceLine* line() const { return _line; } const TraceInstrJumpList& instrJumps() const { return _instrJumps; } const TraceInstrCallList& instrCalls() const { return _instrCalls; } bool hasCost(EventType*); // only to be called after default constructor void setAddr(const Addr addr) { _addr = addr; } void setFunction(TraceFunction* f) { _function = f; } void setLine(TraceLine* l) { _line = l; } protected: bool onlyActiveParts() { return true; } private: Addr _addr; TraceFunction* _function; TraceLine* _line; TraceInstrJumpList _instrJumps; TraceInstrCallList _instrCalls; }; /** * A source line of the program. * Consists of a list a TracePartLines from different trace files * and a list of TraceLineCalls if there are calls from this line. */ class TraceLine: public TraceListCost { public: TraceLine(); virtual ~TraceLine(); virtual QString name() const; QString prettyName() const; // factories TracePartLine* partLine(TracePart* part, TracePartFunction* partFunction); TraceLineJump* lineJump(TraceLine* to, bool isCondJump); void addLineCall(TraceLineCall*); bool isValid() { return _sourceFile != 0; } bool hasCost(EventType*); TraceFunctionSource* functionSource() const { return _sourceFile; } uint lineno() const { return _lineno; } const TraceLineCallList& lineCalls() const { return _lineCalls; } const TraceLineJumpList& lineJumps() const { return _lineJumps; } // only to be called after default constructor void setSourceFile(TraceFunctionSource* sf) { _sourceFile = sf; } void setLineno(uint lineno) { _lineno = lineno; } protected: bool onlyActiveParts() { return true; } private: TraceFunctionSource* _sourceFile; uint _lineno; TraceLineJumpList _lineJumps; TraceLineCallList _lineCalls; }; /* * Base class for all costs which * represent "interesting" items or group of items * with settable name and inclusive cost */ class TraceCostItem: public TraceInclusiveListCost { public: explicit TraceCostItem(ProfileContext*); virtual ~TraceCostItem(); virtual QString name() const { return _name; } virtual void setName(const QString& name) { _name = name; } protected: bool onlyActiveParts() { return true; } protected: QString _name; }; /** * Cost of a source region. */ class TraceLineRegion: public TraceInclusiveListCost { public: TraceLineRegion(uint from, uint to, QString name); virtual ~TraceLineRegion(); virtual void update(); uint from() const { return _from; } uint to() const { return _to; } QString name() const { return _name; } // factories TracePartLine* partLineRegion(TracePart* part, TracePartFunction* partFunction); private: uint _from, _to; QString _name; }; /** * A container helper class for TraceFunction for source lines * where a function is implemented in. * With inlining, lines of the same function can come from * different source files. * An instance of this class holds all lines of one source file * for a function in a map */ class TraceFunctionSource: public ProfileCostArray { public: TraceFunctionSource(TraceFunction*, TraceFile*); virtual ~TraceFunctionSource(); virtual QString name() const; // reimplementation for dependency map virtual void update(); TraceFile* file() const { return _file; } TraceFunction* function() const { return _function; } uint firstLineno(); uint lastLineno(); TraceLineMap* lineMap(); void invalidateDynamicCost(); /* factories */ TraceLine* line(uint lineno, bool createNew = true); TraceLineRegion* region(uint from, uint to, QString name, bool createNew = true); private: TraceFile* _file; TraceFunction* _function; TraceLineMap* _lineMap; TraceLine* _line0; TraceLineRegionList* _regions; bool _lineMapFilled; }; /** * For temporary association of objects with TraceFunctions. * Used in coverage analysis and TreeMap drawing. */ class TraceAssociation { public: /** * Creates an invalid association. */ TraceAssociation(); virtual ~TraceAssociation(); // for runtime detection virtual int rtti() { return 0; } /** * Could we set the function association to ourself? * This only can return false if this is a unique association. */ bool isAssociated(); /** * reset function to associate this object to. * returns true if association could be established */ bool setFunction(TraceFunction*); TraceFunction* function() { return _function; } void invalidate() { _valid = false; } bool isValid() { return _valid; } /** * Delete all associations in TraceFunctions of data with * rtti runtime info. rtti = 0: delete ALL associations. */ static void clear(TraceData* data, int rtti); /** * Invalidate all associations in TraceFunctions of data with * rtti runtime info. rtti = 0: Invalidate ALL associations. */ static void invalidate(TraceData* data, int rtti); protected: TraceFunction* _function; bool _valid; }; typedef QList TraceAssociationList; /** * A traced function * * References to functions are stored in * (1) a function map in TraceData (by value) * (2) a TraceClass */ class TraceFunction: public TraceCostItem { public: TraceFunction(); TraceFunction(TraceData* data, const QString& name, TraceClass* cls, TraceFile* file, TraceObject* object); virtual ~TraceFunction(); virtual void update(); // this invalidate all subcosts of function depending on // active status of parts void invalidateDynamicCost(); void addCaller(TraceCall*); // factories TraceCall* calling(TraceFunction* called); TraceLine* line(TraceFile*, uint lineno, bool createNew = true); TraceInstr* instr(Addr addr, bool createNew = true); TracePartFunction* partFunction(TracePart*, TracePartFile*, TracePartObject*); /** * Returns empty string if location is fully unknown. * Use prettyLocation for single user-visible string. * A function can have a lot of code from different sources (inlined); * maxItems limits this list. Default is full list */ QString location(int maxFiles = 0) const; QString prettyName() const; QString formattedName() const; static QString prettyEmptyName(); QString prettyLocation(int maxFiles = 0) const; QString prettyNameWithLocation(int maxFiles = 1) const; void addPrettyLocation(QString&, int maxFiles = 1) const; // type + name + location QString info() const; TraceClass* cls() const { return _cls; } TraceFile* file() const { return _file; } TraceObject* object() const { return _object; } // get the source file with lines from function declaration (not inlined) TraceFunctionSource* sourceFile(TraceFile* file = 0, bool createNew = false); const TraceFunctionSourceList& sourceFiles() const { return _sourceFiles; } TraceCallList callers(bool skipCycle=false) const; const TraceCallList& callings(bool skipCycle=false) const; Addr firstAddress() const; Addr lastAddress() const; TraceInstrMap* instrMap(); // cost metrics SubCost calledCount(); SubCost callingCount(); QString prettyCalledCount(); QString prettyCallingCount(); int calledContexts(); int callingContexts(); // only to be called after default constructor void setFile(TraceFile* file) { _file = file; } void setObject(TraceObject* object) { _object = object; } void setClass(TraceClass* cls) { _cls = cls; } //void setMapIterator(TraceFunctionMap::Iterator it) { _myMapIterator = it; } // see TraceFunctionAssociation void addAssociation(TraceAssociation* a); void removeAssociation(TraceAssociation* a); void removeAssociation(int rtti, bool reallyDelete = true); void invalidateAssociation(int rtti); TraceAssociation* association(int rtti); // cycles void setCycle(TraceFunctionCycle* c) { _cycle = c; } TraceFunctionCycle* cycle() { return _cycle; } bool isCycle(); bool isCycleMember(); void cycleReset(); void cycleDFS(int d, int& pNo, TraceFunction** pTop); protected: TraceCallList _callers; // list of calls we are called from TraceCallList _callings; // list of calls we are calling (we are owner) TraceFunctionCycle* _cycle; private: bool isUniquePrefix(const QString&) const; //TraceFunctionMap::Iterator _myMapIterator; TraceClass* _cls; TraceObject* _object; TraceFile* _file; TraceFunctionSourceList _sourceFiles; // we are owner TraceInstrMap* _instrMap; // we are owner bool _instrMapFilled; // see TraceAssociation TraceAssociationList _associations; // for cycle detection int _cycleLow; TraceFunction* _cycleStackDown; // cached SubCost _calledCount, _callingCount; int _calledContexts, _callingContexts; }; /** * A cycle of recursive calling functions. * * This is itself shown as a function */ class TraceFunctionCycle: public TraceFunction { public: TraceFunctionCycle(TraceFunction*, int n); // this removes all members from this cycle void init(); void add(TraceFunction*); // this sets up the cycle once members are added void setup(); TraceFunction* base() const { return _base; } int cycleNo() const { return _cycleNo; } const TraceFunctionList& members() const { return _members; } private: TraceFunction* _base; int _cycleNo; TraceFunctionList _members; }; /** * A C++ Class / Namespace * * If a function symbol has a prefix ending in "::", * the prefix is supposed to be a class/namespace specifier. * Without such a prefix, we put a symbol in the "(global)" namespace. */ class TraceClass: public TraceCostItem { public: TraceClass(); virtual ~TraceClass(); virtual QString prettyName() const; static QString prettyEmptyName(); void addFunction(TraceFunction*); const TraceFunctionList& functions() const { return _functions; } // part factory TracePartClass* partClass(TracePart*); private: TraceFunctionList _functions; }; /** * A source file containing function definitions */ class TraceFile: public TraceCostItem { public: TraceFile(); virtual ~TraceFile(); void setDirectory(const QString& dir); void resetDirectory() { _dir = QString(); } QString directory(); void addFunction(TraceFunction*); void addSourceFile(TraceFunctionSource*); // without path QString shortName() const; QString prettyName() const; QString prettyLongName() const; static QString prettyEmptyName(); const TraceFunctionList& functions() const { return _functions; } const TraceFunctionSourceList& sourceFiles() const { return _sourceFiles; } // part factory TracePartFile* partFile(TracePart*); private: TraceFunctionList _functions; TraceFunctionSourceList _sourceFiles; QString _dir; }; /** * A object containing a text segment (shared lib/executable) * with defined functions */ class TraceObject: public TraceCostItem { public: TraceObject(); virtual ~TraceObject(); void setDirectory(const QString& dir); void resetDirectory() { _dir = QString(); } QString directory(); void addFunction(TraceFunction*); QString shortName() const; QString prettyName() const; static QString prettyEmptyName(); const TraceFunctionList& functions() const { return _functions; } // part factory TracePartObject* partObject(TracePart*); private: TraceFunctionList _functions; QString _dir; }; /** * This class holds profiling data of multiple tracefiles * generated with cachegrind on one command. * */ class TraceData: public ProfileCostArray { public: // profiled architecture (must be same for every part) enum Arch { ArchUnknown, ArchARM }; explicit TraceData(Logger* l = 0); virtual ~TraceData(); virtual TraceData* data() { return this; } virtual const TraceData* data() const { return this; } /** * Loads profile data files. * If a single file is given, it is assumed to be a prefix. * * This adjusts the EventTypeSet according to given cost types. * Returns the number of parts loaded */ int load(QStringList files); int load(QString file); int load(QIODevice*, const QString&); /** returns true if something changed. These do NOT * invalidate the dynamic costs on a activation change, * i.e. all cost items depends on active parts. * This has to be done by the caller when true is returned by * calling invalidateDynamicCost(). */ bool activateParts(const TracePartList&); bool activateParts(TracePartList, bool active); bool activatePart(TracePart*, bool active); bool activateAll(bool active=true); // to be used by loader void addPart(TracePart*); TracePartList parts() const { return _parts; } TracePart* partWithName(const QString& name); // with path QString traceName() const { return _traceName; } // without path QString shortTraceName() const; QString activePartRange(); EventTypeSet* eventTypes() { return &_eventTypes; } // memory pools FixPool* fixPool(); DynPool* dynPool(); // factories for object/file/class/function/line instances TraceObject* object(const QString& name); TraceFile* file(const QString& name); TraceClass* cls(const QString& fnName, QString& shortName); // function creation involves class creation if needed TraceFunction* function(const QString& name, TraceFile*, TraceObject*); // factory for function cycles TraceFunctionCycle* functionCycle(TraceFunction*); /** * Search for item with given name and highest subcost of given cost type. * * For some items, they will only be found if the parent cost is given: * Instr, Line, Call => need parent of type Function * For Function, a parent of type Obj/File/Class can be given, but * is not needed. */ ProfileCostArray* search(ProfileContext::Type, QString, EventType* ct = 0, ProfileCostArray* parent = 0); // for pretty function names without signature if unique... TraceFunctionMap::Iterator functionIterator(TraceFunction*); TraceFunctionMap::ConstIterator functionBeginIterator() const; TraceFunctionMap::ConstIterator functionEndIterator() const; TraceObjectMap& objectMap() { return _objectMap; } TraceFileMap& fileMap() { return _fileMap; } TraceClassMap& classMap() { return _classMap; } TraceFunctionMap& functionMap() { return _functionMap; } const TraceFunctionCycleList& functionCycles() { return _functionCycles; } ProfileCostArray* callMax() { return &_callMax; } SubCost maxCallCount() { return _maxCallCount; } void updateMaxCallCount(SubCost); void setCommand(const QString& command) { _command = command; } QString command() const { return _command; } void setArchitecture(Arch a) { _arch = a; } Arch architecture() const { return _arch; } ProfileCostArray* totals() { return &_totals; } void setMaxThreadID(int tid) { _maxThreadID = tid; } int maxThreadID() const { return _maxThreadID; } void setMaxPartNumber(int n) { _maxPartNumber = n; } int maxPartNumber() const { return _maxPartNumber; } // reset all manually set directories for source files void resetSourceDirs(); virtual void update(); // invalidates all cost items dependant on active state of parts void invalidateDynamicCost(); // cycle detection void updateFunctionCycles(); void updateObjectCycles(); void updateClassCycles(); void updateFileCycles(); bool inFunctionCycleUpdate() { return _inFunctionCycleUpdate; } private: void init(); // add profile parts from one file int internalLoad(QIODevice* file, const QString& filename); // for notification callbacks Logger* _logger; TracePartList _parts; // The set for all costs EventTypeSet _eventTypes; FixPool* _fixPool; DynPool* _dynPool; // always the trace totals (not dependent on active parts) ProfileCostArray _totals; int _maxThreadID; int _maxPartNumber; TraceObjectMap _objectMap; TraceClassMap _classMap; TraceFileMap _fileMap; TraceFunctionMap _functionMap; QString _command; Arch _arch; QString _traceName; // Max of all costs of calls: This allows to see if the incl. cost can // be hidden for a cost type, as it is always the same as self cost ProfileCostArray _callMax; SubCost _maxCallCount; // cycles TraceFunctionCycleList _functionCycles; int _functionCycleCount; bool _inFunctionCycleUpdate; }; #endif diff --git a/libviews/callgraphview.cpp b/libviews/callgraphview.cpp index 6c5411c..3d6b5ba 100644 --- a/libviews/callgraphview.cpp +++ b/libviews/callgraphview.cpp @@ -1,3189 +1,3189 @@ /* This file is part of KCachegrind. Copyright (c) 2007-2016 Josef Weidendorfer KCachegrind 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, version 2. 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; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /* * Callgraph View */ #include "callgraphview.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "config.h" #include "globalguiconfig.h" #include "listutils.h" #define DEBUG_GRAPH 0 // CallGraphView defaults #define DEFAULT_FUNCLIMIT .05 #define DEFAULT_CALLLIMIT 1. #define DEFAULT_MAXCALLER 2 #define DEFAULT_MAXCALLEE -1 #define DEFAULT_SHOWSKIPPED false #define DEFAULT_EXPANDCYCLES false #define DEFAULT_CLUSTERGROUPS false #define DEFAULT_DETAILLEVEL 1 #define DEFAULT_LAYOUT GraphOptions::TopDown #define DEFAULT_ZOOMPOS Auto // LessThen functors as helpers for sorting of graph edges // for keyboard navigation. Sorting is done according to // the angle at which a edge spline goes out or in of a function. // Sort angles of outgoing edges (edge seen as attached to the caller) class CallerGraphEdgeLessThan { public: bool operator()(const GraphEdge* ge1, const GraphEdge* ge2) const { const CanvasEdge* ce1 = ge1->canvasEdge(); const CanvasEdge* ce2 = ge2->canvasEdge(); // sort invisible edges (ie. without matching CanvasEdge) in front if (!ce1) return true; if (!ce2) return false; QPolygon p1 = ce1->controlPoints(); QPolygon p2 = ce2->controlPoints(); QPoint d1 = p1.point(1) - p1.point(0); QPoint d2 = p2.point(1) - p2.point(0); double angle1 = atan2(double(d1.y()), double(d1.x())); double angle2 = atan2(double(d2.y()), double(d2.x())); return (angle1 < angle2); } }; // Sort angles of ingoing edges (edge seen as attached to the callee) class CalleeGraphEdgeLessThan { public: bool operator()(const GraphEdge* ge1, const GraphEdge* ge2) const { const CanvasEdge* ce1 = ge1->canvasEdge(); const CanvasEdge* ce2 = ge2->canvasEdge(); // sort invisible edges (ie. without matching CanvasEdge) in front if (!ce1) return true; if (!ce2) return false; QPolygon p1 = ce1->controlPoints(); QPolygon p2 = ce2->controlPoints(); QPoint d1 = p1.point(p1.count()-2) - p1.point(p1.count()-1); QPoint d2 = p2.point(p2.count()-2) - p2.point(p2.count()-1); double angle1 = atan2(double(d1.y()), double(d1.x())); double angle2 = atan2(double(d2.y()), double(d2.x())); // for ingoing edges sort according to descending angles return (angle2 < angle1); } }; // // GraphNode // GraphNode::GraphNode() { _f=0; self = incl = 0; _cn = 0; _visible = false; _lastCallerIndex = _lastCalleeIndex = -1; _lastFromCaller = true; } void GraphNode::clearEdges() { callees.clear(); callers.clear(); } CallerGraphEdgeLessThan callerGraphEdgeLessThan; CalleeGraphEdgeLessThan calleeGraphEdgeLessThan; void GraphNode::sortEdges() { qSort(callers.begin(), callers.end(), callerGraphEdgeLessThan); qSort(callees.begin(), callees.end(), calleeGraphEdgeLessThan); } void GraphNode::addCallee(GraphEdge* e) { if (e) callees.append(e); } void GraphNode::addCaller(GraphEdge* e) { if (e) callers.append(e); } void GraphNode::addUniqueCallee(GraphEdge* e) { if (e && (callees.count(e) == 0)) callees.append(e); } void GraphNode::addUniqueCaller(GraphEdge* e) { if (e && (callers.count(e) == 0)) callers.append(e); } void GraphNode::removeEdge(GraphEdge* e) { callers.removeAll(e); callees.removeAll(e); } double GraphNode::calleeCostSum() { double sum = 0.0; foreach(GraphEdge* e, callees) sum += e->cost; return sum; } double GraphNode::calleeCountSum() { double sum = 0.0; foreach(GraphEdge* e, callees) sum += e->count; return sum; } double GraphNode::callerCostSum() { double sum = 0.0; foreach(GraphEdge* e, callers) sum += e->cost; return sum; } double GraphNode::callerCountSum() { double sum = 0.0; foreach(GraphEdge* e, callers) sum += e->count; return sum; } TraceCall* GraphNode::visibleCaller() { if (0) qDebug("GraphNode::visibleCaller %s: last %d, count %d", qPrintable(_f->prettyName()), _lastCallerIndex, callers.count()); // can not use at(): index can be -1 (out of bounds), result is 0 then GraphEdge* e = callers.value(_lastCallerIndex); if (e && !e->isVisible()) e = 0; if (!e) { double maxCost = 0.0; GraphEdge* maxEdge = 0; for(int i = 0; iisVisible() && (e->cost > maxCost)) { maxCost = e->cost; maxEdge = e; _lastCallerIndex = i; } } e = maxEdge; } return e ? e->call() : 0; } TraceCall* GraphNode::visibleCallee() { if (0) qDebug("GraphNode::visibleCallee %s: last %d, count %d", qPrintable(_f->prettyName()), _lastCalleeIndex, callees.count()); GraphEdge* e = callees.value(_lastCalleeIndex); if (e && !e->isVisible()) e = 0; if (!e) { double maxCost = 0.0; GraphEdge* maxEdge = 0; for(int i = 0; iisVisible() && (e->cost > maxCost)) { maxCost = e->cost; maxEdge = e; _lastCalleeIndex = i; } } e = maxEdge; } return e ? e->call() : 0; } void GraphNode::setCallee(GraphEdge* e) { _lastCalleeIndex = callees.indexOf(e); _lastFromCaller = false; } void GraphNode::setCaller(GraphEdge* e) { _lastCallerIndex = callers.indexOf(e); _lastFromCaller = true; } TraceFunction* GraphNode::nextVisible() { TraceCall* c; if (_lastFromCaller) { c = nextVisibleCaller(); if (c) return c->called(true); c = nextVisibleCallee(); if (c) return c->caller(true); } else { c = nextVisibleCallee(); if (c) return c->caller(true); c = nextVisibleCaller(); if (c) return c->called(true); } return 0; } TraceFunction* GraphNode::priorVisible() { TraceCall* c; if (_lastFromCaller) { c = priorVisibleCaller(); if (c) return c->called(true); c = priorVisibleCallee(); if (c) return c->caller(true); } else { c = priorVisibleCallee(); if (c) return c->caller(true); c = priorVisibleCaller(); if (c) return c->called(true); } return 0; } TraceCall* GraphNode::nextVisibleCaller(GraphEdge* e) { int idx = e ? callers.indexOf(e) : _lastCallerIndex; idx++; while(idx < callers.size()) { if (callers[idx]->isVisible()) { _lastCallerIndex = idx; return callers[idx]->call(); } idx++; } return 0; } TraceCall* GraphNode::nextVisibleCallee(GraphEdge* e) { int idx = e ? callees.indexOf(e) : _lastCalleeIndex; idx++; while(idx < callees.size()) { if (callees[idx]->isVisible()) { _lastCalleeIndex = idx; return callees[idx]->call(); } idx++; } return 0; } TraceCall* GraphNode::priorVisibleCaller(GraphEdge* e) { int idx = e ? callers.indexOf(e) : _lastCallerIndex; idx = (idx<0) ? callers.size()-1 : idx-1; while(idx >= 0) { if (callers[idx]->isVisible()) { _lastCallerIndex = idx; return callers[idx]->call(); } idx--; } return 0; } TraceCall* GraphNode::priorVisibleCallee(GraphEdge* e) { int idx = e ? callees.indexOf(e) : _lastCalleeIndex; idx = (idx<0) ? callees.size()-1 : idx-1; while(idx >= 0) { if (callees[idx]->isVisible()) { _lastCalleeIndex = idx; return callees[idx]->call(); } idx--; } return 0; } // // GraphEdge // GraphEdge::GraphEdge() { _c=0; _from = _to = 0; _fromNode = _toNode = 0; cost = count = 0; _ce = 0; _visible = false; _lastFromCaller = true; } QString GraphEdge::prettyName() { if (_c) return _c->prettyName(); if (_from) return QObject::tr("Call(s) from %1").arg(_from->prettyName()); if (_to) return QObject::tr("Call(s) to %1").arg(_to->prettyName()); return QObject::tr("(unknown call)"); } TraceFunction* GraphEdge::visibleCaller() { if (_from) { _lastFromCaller = true; if (_fromNode) _fromNode->setCallee(this); return _from; } return 0; } TraceFunction* GraphEdge::visibleCallee() { if (_to) { _lastFromCaller = false; if (_toNode) _toNode->setCaller(this); return _to; } return 0; } TraceCall* GraphEdge::nextVisible() { TraceCall* res = 0; if (_lastFromCaller && _fromNode) { res = _fromNode->nextVisibleCallee(this); if (!res && _toNode) res = _toNode->nextVisibleCaller(this); } else if (_toNode) { res = _toNode->nextVisibleCaller(this); if (!res && _fromNode) res = _fromNode->nextVisibleCallee(this); } return res; } TraceCall* GraphEdge::priorVisible() { TraceCall* res = 0; if (_lastFromCaller && _fromNode) { res = _fromNode->priorVisibleCallee(this); if (!res && _toNode) res = _toNode->priorVisibleCaller(this); } else if (_toNode) { res = _toNode->priorVisibleCaller(this); if (!res && _fromNode) res = _fromNode->priorVisibleCallee(this); } return res; } // // GraphOptions // QString GraphOptions::layoutString(Layout l) { if (l == Circular) return QStringLiteral("Circular"); if (l == LeftRight) return QStringLiteral("LeftRight"); return QStringLiteral("TopDown"); } GraphOptions::Layout GraphOptions::layout(QString s) { if (s == QStringLiteral("Circular")) return Circular; if (s == QStringLiteral("LeftRight")) return LeftRight; return TopDown; } // // StorableGraphOptions // StorableGraphOptions::StorableGraphOptions() { // default options _funcLimit = DEFAULT_FUNCLIMIT; _callLimit = DEFAULT_CALLLIMIT; _maxCallerDepth = DEFAULT_MAXCALLER; _maxCalleeDepth = DEFAULT_MAXCALLEE; _showSkipped = DEFAULT_SHOWSKIPPED; _expandCycles = DEFAULT_EXPANDCYCLES; _detailLevel = DEFAULT_DETAILLEVEL; _layout = DEFAULT_LAYOUT; } // // GraphExporter // GraphExporter::GraphExporter() { _go = this; _tmpFile = 0; _item = 0; reset(0, 0, 0, ProfileContext::InvalidType, QString()); } GraphExporter::GraphExporter(TraceData* d, TraceFunction* f, EventType* ct, ProfileContext::Type gt, QString filename) { _go = this; _tmpFile = 0; _item = 0; reset(d, f, ct, gt, filename); } GraphExporter::~GraphExporter() { if (_item && _tmpFile) { #if DEBUG_GRAPH _tmpFile->setAutoRemove(true); #endif delete _tmpFile; } } void GraphExporter::reset(TraceData*, CostItem* i, EventType* ct, ProfileContext::Type gt, QString filename) { _graphCreated = false; _nodeMap.clear(); _edgeMap.clear(); if (_item && _tmpFile) { _tmpFile->setAutoRemove(true); delete _tmpFile; } if (i) { switch (i->type()) { case ProfileContext::Function: case ProfileContext::FunctionCycle: case ProfileContext::Call: break; default: i = 0; } } _item = i; _eventType = ct; _groupType = gt; if (!i) return; if (filename.isEmpty()) { _tmpFile = new QTemporaryFile(); //_tmpFile->setSuffix(".dot"); _tmpFile->setAutoRemove(false); _tmpFile->open(); _dotName = _tmpFile->fileName(); _useBox = true; } else { _tmpFile = 0; _dotName = filename; _useBox = false; } } void GraphExporter::setGraphOptions(GraphOptions* go) { if (go == 0) go = this; _go = go; } void GraphExporter::createGraph() { if (!_item) return; if (_graphCreated) return; _graphCreated = true; if ((_item->type() == ProfileContext::Function) ||(_item->type() == ProfileContext::FunctionCycle)) { TraceFunction* f = (TraceFunction*) _item; double incl = f->inclusive()->subCost(_eventType); _realFuncLimit = incl * _go->funcLimit(); _realCallLimit = _realFuncLimit * _go->callLimit(); buildGraph(f, 0, true, 1.0); // down to callees // set costs of function back to 0, as it will be added again GraphNode& n = _nodeMap[f]; n.self = n.incl = 0.0; buildGraph(f, 0, false, 1.0); // up to callers } else { TraceCall* c = (TraceCall*) _item; double incl = c->subCost(_eventType); _realFuncLimit = incl * _go->funcLimit(); _realCallLimit = _realFuncLimit * _go->callLimit(); // create edge TraceFunction *caller, *called; caller = c->caller(false); called = c->called(false); QPair p(caller, called); GraphEdge& e = _edgeMap[p]; e.setCall(c); e.setCaller(p.first); e.setCallee(p.second); e.cost = c->subCost(_eventType); e.count = c->callCount(); SubCost s = called->inclusive()->subCost(_eventType); buildGraph(called, 0, true, e.cost / s); // down to callees s = caller->inclusive()->subCost(_eventType); buildGraph(caller, 0, false, e.cost / s); // up to callers } } void GraphExporter::writeDot(QIODevice* device) { if (!_item) return; QFile* file = 0; QTextStream* stream = 0; if (device) stream = new QTextStream(device); else { if (_tmpFile) stream = new QTextStream(_tmpFile); else { file = new QFile(_dotName); if ( !file->open(QIODevice::WriteOnly ) ) { qDebug() << "Can not write dot file '"<< _dotName << "'"; delete file; return; } stream = new QTextStream(file); } } if (!_graphCreated) createGraph(); /* Generate dot format... * When used for the CallGraphView (in contrast to "Export Callgraph..."), * the labels are only dummy placeholders to reserve space for our own * drawings. */ *stream << "digraph \"callgraph\" {\n"; if (_go->layout() == LeftRight) { *stream << QStringLiteral(" rankdir=LR;\n"); } else if (_go->layout() == Circular) { TraceFunction *f = 0; switch (_item->type()) { case ProfileContext::Function: case ProfileContext::FunctionCycle: f = (TraceFunction*) _item; break; case ProfileContext::Call: f = ((TraceCall*)_item)->caller(true); break; default: break; } if (f) *stream << QStringLiteral(" center=F%1;\n").arg((qptrdiff)f, 0, 16); *stream << QStringLiteral(" overlap=false;\n splines=true;\n"); } // for clustering QMap > nLists; GraphNodeMap::Iterator nit; for (nit = _nodeMap.begin(); nit != _nodeMap.end(); ++nit ) { GraphNode& n = *nit; if (n.incl <= _realFuncLimit) continue; // for clustering: get cost item group of function TraceCostItem* g; TraceFunction* f = n.function(); switch (_groupType) { case ProfileContext::Object: g = f->object(); break; case ProfileContext::Class: g = f->cls(); break; case ProfileContext::File: g = f->file(); break; case ProfileContext::FunctionCycle: g = f->cycle(); break; default: g = 0; break; } nLists[g].append(&n); } QMap >::Iterator lit; int cluster = 0; for (lit = nLists.begin(); lit != nLists.end(); ++lit, cluster++) { QList& l = lit.value(); TraceCostItem* i = lit.key(); if (_go->clusterGroups() && i) { QString iabr = GlobalConfig::shortenSymbol(i->prettyName()); // escape quotation marks in symbols to avoid invalid dot syntax iabr.replace("\"", "\\\""); *stream << QStringLiteral("subgraph \"cluster%1\" { label=\"%2\";\n") .arg(cluster).arg(iabr); } foreach(GraphNode* np, l) { TraceFunction* f = np->function(); QString abr = GlobalConfig::shortenSymbol(f->prettyName()); // escape quotation marks to avoid invalid dot syntax abr.replace("\"", "\\\""); *stream << QStringLiteral(" F%1 [").arg((qptrdiff)f, 0, 16); if (_useBox) { // we want a minimal size for cost display if ((int)abr.length() < 8) abr = abr + QString(8 - abr.length(),'_'); // make label 3 lines for CallGraphView *stream << QStringLiteral("shape=box,label=\"** %1 **\\n**\\n%2\"];\n") .arg(abr) .arg(SubCost(np->incl).pretty()); } else *stream << QStringLiteral("label=\"%1\\n%2\"];\n") .arg(abr) .arg(SubCost(np->incl).pretty()); } if (_go->clusterGroups() && i) *stream << QStringLiteral("}\n"); } GraphEdgeMap::Iterator eit; for (eit = _edgeMap.begin(); eit != _edgeMap.end(); ++eit ) { GraphEdge& e = *eit; if (e.cost < _realCallLimit) continue; if (!_go->expandCycles()) { // do not show inner cycle calls if (e.call()->inCycle()>0) continue; } GraphNode& from = _nodeMap[e.from()]; GraphNode& to = _nodeMap[e.to()]; e.setCallerNode(&from); e.setCalleeNode(&to); if ((from.incl <= _realFuncLimit) ||(to.incl <= _realFuncLimit)) continue; // remove dumped edges from n.callers/n.callees from.removeEdge(&e); to.removeEdge(&e); *stream << QStringLiteral(" F%1 -> F%2 [weight=%3") .arg((qptrdiff)e.from(), 0, 16) .arg((qptrdiff)e.to(), 0, 16) .arg((long)log(log(e.cost))); if (_go->detailLevel() ==1) { *stream << QStringLiteral(",label=\"%1 (%2x)\"") .arg(SubCost(e.cost).pretty()) .arg(SubCost(e.count).pretty()); } else if (_go->detailLevel() ==2) *stream << QStringLiteral(",label=\"%3\\n%4 x\"") .arg(SubCost(e.cost).pretty()) .arg(SubCost(e.count).pretty()); *stream << QStringLiteral("];\n"); } if (_go->showSkipped()) { // Create sum-edges for skipped edges GraphEdge* e; double costSum, countSum; for (nit = _nodeMap.begin(); nit != _nodeMap.end(); ++nit ) { GraphNode& n = *nit; if (n.incl <= _realFuncLimit) continue; // add edge for all skipped callers if cost sum is high enough costSum = n.callerCostSum(); countSum = n.callerCountSum(); if (costSum > _realCallLimit) { QPair p(0, n.function()); e = &(_edgeMap[p]); e->setCallee(p.second); e->cost = costSum; e->count = countSum; *stream << QStringLiteral(" R%1 [shape=point,label=\"\"];\n") .arg((qptrdiff)n.function(), 0, 16); *stream << QStringLiteral(" R%1 -> F%2 [label=\"%3\\n%4 x\",weight=%5];\n") .arg((qptrdiff)n.function(), 0, 16) .arg((qptrdiff)n.function(), 0, 16) .arg(SubCost(costSum).pretty()) .arg(SubCost(countSum).pretty()) .arg((int)log(costSum)); } // add edge for all skipped callees if cost sum is high enough costSum = n.calleeCostSum(); countSum = n.calleeCountSum(); if (costSum > _realCallLimit) { QPair p(n.function(), 0); e = &(_edgeMap[p]); e->setCaller(p.first); e->cost = costSum; e->count = countSum; *stream << QStringLiteral(" S%1 [shape=point,label=\"\"];\n") .arg((qptrdiff)n.function(), 0, 16); *stream << QStringLiteral(" F%1 -> S%2 [label=\"%3\\n%4 x\",weight=%5];\n") .arg((qptrdiff)n.function(), 0, 16) .arg((qptrdiff)n.function(), 0, 16) .arg(SubCost(costSum).pretty()) .arg(SubCost(countSum).pretty()) .arg((int)log(costSum)); } } } // clear edges here completely. // Visible edges are inserted again on parsing in CallGraphView::refresh for (nit = _nodeMap.begin(); nit != _nodeMap.end(); ++nit ) { GraphNode& n = *nit; n.clearEdges(); } *stream << "}\n"; if (!device) { if (_tmpFile) { stream->flush(); _tmpFile->seek(0); } else { file->close(); delete file; } } delete stream; } void GraphExporter::sortEdges() { GraphNodeMap::Iterator nit; for (nit = _nodeMap.begin(); nit != _nodeMap.end(); ++nit ) { GraphNode& n = *nit; n.sortEdges(); } } TraceFunction* GraphExporter::toFunc(QString s) { if (s[0] != 'F') return 0; bool ok; TraceFunction* f = (TraceFunction*) s.midRef(1).toULongLong(&ok, 16); if (!ok) return 0; return f; } GraphNode* GraphExporter::node(TraceFunction* f) { if (!f) return 0; GraphNodeMap::Iterator it = _nodeMap.find(f); if (it == _nodeMap.end()) return 0; return &(*it); } GraphEdge* GraphExporter::edge(TraceFunction* f1, TraceFunction* f2) { GraphEdgeMap::Iterator it = _edgeMap.find(qMakePair(f1, f2)); if (it == _edgeMap.end()) return 0; return &(*it); } /** * We do a DFS and do not stop on already visited nodes/edges, * but add up costs. We only stop if limits/max depth is reached. * * For a node/edge, it can happen that the first time visited the * cost will below the limit, so the search is stopped. * If on a further visit of the node/edge the limit is reached, * we use the whole node/edge cost and continue search. */ void GraphExporter::buildGraph(TraceFunction* f, int depth, bool toCallees, double factor) { #if DEBUG_GRAPH qDebug() << "buildGraph(" << f->prettyName() << "," << d << "," << factor << ") [to " << (toCallees ? "Callees":"Callers") << "]"; #endif double oldIncl = 0.0; GraphNode& n = _nodeMap[f]; if (n.function() == 0) { n.setFunction(f); } else oldIncl = n.incl; double incl = f->inclusive()->subCost(_eventType) * factor; n.incl += incl; n.self += f->subCost(_eventType) * factor; if (0) qDebug(" Added Incl. %f, now %f", incl, n.incl); // A negative depth limit means "unlimited" int maxDepth = toCallees ? _go->maxCalleeDepth() : _go->maxCallerDepth(); // Never go beyound a depth of 100 if ((maxDepth < 0) || (maxDepth>100)) maxDepth = 100; if (depth >= maxDepth) { if (0) qDebug(" Cutoff, max depth reached"); return; } // if we just reached the limit by summing, do a DFS // from here with full incl. cost because of previous cutoffs if ((n.incl >= _realFuncLimit) && (oldIncl < _realFuncLimit)) incl = n.incl; if (f->cycle()) { // for cycles members, we never stop on first visit, but always on 2nd // note: a 2nd visit never should happen, as we do not follow inner-cycle // calls if (oldIncl > 0.0) { if (0) qDebug(" Cutoff, 2nd visit to Cycle Member"); // and takeback cost addition, as it is added twice n.incl = oldIncl; n.self -= f->subCost(_eventType) * factor; return; } } else if (incl <= _realFuncLimit) { if (0) qDebug(" Cutoff, below limit"); return; } TraceFunction* f2; // on entering a cycle, only go the FunctionCycle TraceCallList l = toCallees ? f->callings(false) : f->callers(false); foreach(TraceCall* call, l) { f2 = toCallees ? call->called(false) : call->caller(false); double count = call->callCount() * factor; double cost = call->subCost(_eventType) * factor; // ignore function calls with absolute cost < 3 per call // No: This would skip a lot of functions e.g. with L2 cache misses // if (count>0.0 && (cost/count < 3)) continue; double oldCost = 0.0; QPair p(toCallees ? f : f2, toCallees ? f2 : f); GraphEdge& e = _edgeMap[p]; if (e.call() == 0) { e.setCall(call); e.setCaller(p.first); e.setCallee(p.second); } else oldCost = e.cost; e.cost += cost; e.count += count; if (0) qDebug(" Edge to %s, added cost %f, now %f", qPrintable(f2->prettyName()), cost, e.cost); // if this call goes into a FunctionCycle, we also show the real call if (f2->cycle() == f2) { TraceFunction* realF; realF = toCallees ? call->called(true) : call->caller(true); QPair realP(toCallees ? f : realF, toCallees ? realF : f); GraphEdge& e = _edgeMap[realP]; if (e.call() == 0) { e.setCall(call); e.setCaller(realP.first); e.setCallee(realP.second); } e.cost += cost; e.count += count; } // - do not do a DFS on calls in recursion/cycle if (call->inCycle()>0) continue; if (call->isRecursion()) continue; if (toCallees) n.addUniqueCallee(&e); else n.addUniqueCaller(&e); // if we just reached the call limit (=func limit by summing, do a DFS // from here with full incl. cost because of previous cutoffs if ((e.cost >= _realCallLimit) && (oldCost < _realCallLimit)) cost = e.cost; if ((cost <= 0) || (cost <= _realCallLimit)) { if (0) qDebug(" Edge Cutoff, limit not reached"); continue; } SubCost s; if (call->inCycle()) s = f2->cycle()->inclusive()->subCost(_eventType); else s = f2->inclusive()->subCost(_eventType); SubCost v = call->subCost(_eventType); // Never recurse if s or v is 0 (can happen with bogus input) if ((v == 0) || (s== 0)) continue; buildGraph(f2, depth+1, toCallees, factor * v / s); } } // // PannerView // PanningView::PanningView(QWidget * parent) : QGraphicsView(parent) { _movingZoomRect = false; // FIXME: Why does this not work? viewport()->setFocusPolicy(Qt::NoFocus); } void PanningView::setZoomRect(const QRectF& r) { _zoomRect = r; viewport()->update(); } void PanningView::drawForeground(QPainter * p, const QRectF&) { if (!_zoomRect.isValid()) return; QColor red(Qt::red); QPen pen(red.dark()); pen.setWidthF(2.0 / matrix().m11()); p->setPen(pen); QColor c(red.dark()); c.setAlphaF(0.05); p->setBrush(QBrush(c)); p->drawRect(QRectF(_zoomRect.x(), _zoomRect.y(), _zoomRect.width()-1, _zoomRect.height()-1)); } void PanningView::mousePressEvent(QMouseEvent* e) { QPointF sPos = mapToScene(e->pos()); if (_zoomRect.isValid()) { if (!_zoomRect.contains(sPos)) emit zoomRectMoved(sPos.x() - _zoomRect.center().x(), sPos.y() - _zoomRect.center().y()); _movingZoomRect = true; _lastPos = sPos; } } void PanningView::mouseMoveEvent(QMouseEvent* e) { QPointF sPos = mapToScene(e->pos()); if (_movingZoomRect) { emit zoomRectMoved(sPos.x() - _lastPos.x(), sPos.y() - _lastPos.y()); _lastPos = sPos; } } void PanningView::mouseReleaseEvent(QMouseEvent*) { _movingZoomRect = false; emit zoomRectMoveFinished(); } // // CanvasNode // CanvasNode::CanvasNode(CallGraphView* v, GraphNode* n, int x, int y, int w, int h) : QGraphicsRectItem(QRect(x, y, w, h)), _node(n), _view(v) { setPosition(0, DrawParams::TopCenter); setPosition(1, DrawParams::BottomCenter); updateGroup(); if (!_node || !_view) return; if (_node->function()) setText(0, _node->function()->prettyName()); ProfileCostArray* totalCost; if (GlobalConfig::showExpanded()) { if (_view->activeFunction()) { if (_view->activeFunction()->cycle()) totalCost = _view->activeFunction()->cycle()->inclusive(); else totalCost = _view->activeFunction()->inclusive(); } else totalCost = (ProfileCostArray*) _view->activeItem(); } else totalCost = ((TraceItemView*)_view)->data(); double total = totalCost->subCost(_view->eventType()); double inclP = 100.0 * n->incl/ total; if (GlobalConfig::showPercentage()) setText(1, QStringLiteral("%1 %") .arg(inclP, 0, 'f', GlobalConfig::percentPrecision())); else setText(1, SubCost(n->incl).pretty()); setPixmap(1, percentagePixmap(25, 10, (int)(inclP+.5), Qt::blue, true)); setToolTip(QStringLiteral("%1 (%2)").arg(text(0)).arg(text(1))); } void CanvasNode::setSelected(bool s) { StoredDrawParams::setSelected(s); update(); } void CanvasNode::updateGroup() { if (!_view || !_node) return; QColor c = GlobalGUIConfig::functionColor(_view->groupType(), _node->function()); setBackColor(c); update(); } void CanvasNode::paint(QPainter* p, const QStyleOptionGraphicsItem* option, QWidget*) { QRect r = rect().toRect(), origRect = r; r.setRect(r.x()+1, r.y()+1, r.width()-2, r.height()-2); RectDrawing d(r); d.drawBack(p, this); r.setRect(r.x()+2, r.y()+2, r.width()-4, r.height()-4); #if 0 if (StoredDrawParams::selected() && _view->hasFocus()) { _view->style().drawPrimitive( QStyle::PE_FocusRect, &p, r, _view->colorGroup()); } #endif // draw afterwards to always get a frame even when zoomed p->setPen(StoredDrawParams::selected() ? Qt::red : Qt::black); p->drawRect(QRect(origRect.x(), origRect.y(), origRect.width()-1, origRect.height()-1)); #if QT_VERSION >= 0x040600 if (option->levelOfDetailFromTransform(p->transform()) < .5) return; #else if (option->levelOfDetail < .5) return; #endif d.setRect(r); d.drawField(p, 0, this); d.drawField(p, 1, this); } // // CanvasEdgeLabel // CanvasEdgeLabel::CanvasEdgeLabel(CallGraphView* v, CanvasEdge* ce, int x, int y, int w, int h) : QGraphicsRectItem(QRect(x, y, w, h)), _ce(ce), _view(v), _percentage(0.0) { GraphEdge* e = ce->edge(); if (!e) return; setPosition(1, DrawParams::BottomCenter); ProfileCostArray* totalCost; if (GlobalConfig::showExpanded()) { if (_view->activeFunction()) { if (_view->activeFunction()->cycle()) totalCost = _view->activeFunction()->cycle()->inclusive(); else totalCost = _view->activeFunction()->inclusive(); } else totalCost = (ProfileCostArray*) _view->activeItem(); } else totalCost = ((TraceItemView*)_view)->data(); double total = totalCost->subCost(_view->eventType()); double inclP = 100.0 * e->cost/ total; if (GlobalConfig::showPercentage()) setText(1, QStringLiteral("%1 %") .arg(inclP, 0, 'f', GlobalConfig::percentPrecision())); else setText(1, SubCost(e->cost).pretty()); int pixPos = 1; if (((TraceItemView*)_view)->data()->maxCallCount() > 0) { setPosition(0, DrawParams::TopCenter); SubCost count((e->count < 1.0) ? 1.0 : e->count); setText(0, QStringLiteral("%1 x").arg(count.pretty())); pixPos = 0; setToolTip(QStringLiteral("%1 (%2)").arg(text(0)).arg(text(1))); } setPixmap(pixPos, percentagePixmap(25, 10, (int)(inclP+.5), Qt::blue, true)); _percentage = inclP; if (_percentage > 100.0) _percentage = 100.0; if (e->call() && (e->call()->isRecursion() || e->call()->inCycle())) { QFontMetrics fm(font()); QPixmap p = QIcon::fromTheme(QStringLiteral("edit-undo")).pixmap(fm.height()); setPixmap(pixPos, p); // replace percentage pixmap } } void CanvasEdgeLabel::paint(QPainter* p, const QStyleOptionGraphicsItem* option, QWidget*) { // draw nothing in PanningView #if QT_VERSION >= 0x040600 if (option->levelOfDetailFromTransform(p->transform()) < .5) return; #else if (option->levelOfDetail < .5) return; #endif QRect r = rect().toRect(); RectDrawing d(r); d.drawField(p, 0, this); d.drawField(p, 1, this); } // // CanvasEdgeArrow CanvasEdgeArrow::CanvasEdgeArrow(CanvasEdge* ce) : _ce(ce) {} void CanvasEdgeArrow::paint(QPainter* p, const QStyleOptionGraphicsItem *, QWidget *) { p->setRenderHint(QPainter::Antialiasing); p->setBrush(_ce->isSelected() ? Qt::red : Qt::black); p->drawPolygon(polygon(), Qt::OddEvenFill); } // // CanvasEdge // CanvasEdge::CanvasEdge(GraphEdge* e) : _edge(e) { _label = 0; _arrow = 0; _thickness = 0; setFlag(QGraphicsItem::ItemIsSelectable); } void CanvasEdge::setLabel(CanvasEdgeLabel* l) { _label = l; if (l) { QString tip = QStringLiteral("%1 (%2)").arg(l->text(0)).arg(l->text(1)); setToolTip(tip); if (_arrow) _arrow->setToolTip(tip); _thickness = log(l->percentage()); if (_thickness < .9) _thickness = .9; } } void CanvasEdge::setArrow(CanvasEdgeArrow* a) { _arrow = a; if (a && _label) a->setToolTip(QStringLiteral("%1 (%2)") .arg(_label->text(0)).arg(_label->text(1))); } void CanvasEdge::setSelected(bool s) { QGraphicsItem::setSelected(s); update(); } void CanvasEdge::setControlPoints(const QPolygon& pa) { _points = pa; QPainterPath path; path.moveTo(pa[0]); for (int i = 1; i < pa.size(); i += 3) path.cubicTo(pa[i], pa[(i + 1) % pa.size()], pa[(i + 2) % pa.size()]); setPath(path); } void CanvasEdge::paint(QPainter* p, const QStyleOptionGraphicsItem* option, QWidget*) { p->setRenderHint(QPainter::Antialiasing); qreal levelOfDetail; #if QT_VERSION >= 0x040600 levelOfDetail = option->levelOfDetailFromTransform(p->transform()); #else levelOfDetail = option->levelOfDetail; #endif QPen mypen = pen(); mypen.setWidthF(1.0/levelOfDetail * _thickness); p->setPen(mypen); p->drawPath(path()); if (isSelected()) { mypen.setColor(Qt::red); mypen.setWidthF(1.0/levelOfDetail * _thickness/2.0); p->setPen(mypen); p->drawPath(path()); } } // // CanvasFrame // QPixmap* CanvasFrame::_p = 0; CanvasFrame::CanvasFrame(CanvasNode* n) { if (!_p) { int d = 5; float v1 = 130.0f, v2 = 10.0f, v = v1, f = 1.03f; // calculate pix size QRect r(0, 0, 30, 30); while (v>v2) { r.setRect(r.x()-d, r.y()-d, r.width()+2*d, r.height()+2*d); v /= f; } _p = new QPixmap(r.size()); _p->fill(Qt::white); QPainter p(_p); p.setPen(Qt::NoPen); r.translate(-r.x(), -r.y()); while (vrect().center().x() - _p->width()/2, n->rect().center().y() - _p->height()/2, _p->width(), _p->height()) ); } void CanvasFrame::paint(QPainter* p, const QStyleOptionGraphicsItem* option, QWidget*) { qreal levelOfDetail; #if QT_VERSION >= 0x040600 levelOfDetail = option->levelOfDetailFromTransform(p->transform()); #else levelOfDetail = option->levelOfDetail; #endif if (levelOfDetail < .5) { QRadialGradient g(rect().center(), rect().width()/3); g.setColorAt(0.0, Qt::gray); g.setColorAt(1.0, Qt::white); p->setBrush(QBrush(g)); p->setPen(Qt::NoPen); p->drawRect(rect()); return; } p->drawPixmap(int( rect().x()),int( rect().y()), *_p ); } // // CallGraphView // CallGraphView::CallGraphView(TraceItemView* parentView, QWidget* parent, const char* name) : QGraphicsView(parent), TraceItemView(parentView) { setObjectName(name); _zoomPosition = DEFAULT_ZOOMPOS; _lastAutoPosition = TopLeft; _scene = 0; _xMargin = _yMargin = 0; _panningView = new PanningView(this); _panningZoom = 1; _selectedNode = 0; _selectedEdge = 0; _isMoving = false; _exporter.setGraphOptions(this); _panningView->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); _panningView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); _panningView->raise(); _panningView->hide(); setFocusPolicy(Qt::StrongFocus); setAttribute(Qt::WA_NoSystemBackground, true); connect(_panningView, &PanningView::zoomRectMoved, this, &CallGraphView::zoomRectMoved); connect(_panningView, &PanningView::zoomRectMoveFinished, this, &CallGraphView::zoomRectMoveFinished); this->setWhatsThis(whatsThis() ); // tooltips... //_tip = new CallGraphTip(this); _renderProcess = 0; _prevSelectedNode = 0; connect(&_renderTimer, &QTimer::timeout, this, &CallGraphView::showRenderWarning); } CallGraphView::~CallGraphView() { clear(); delete _panningView; } QString CallGraphView::whatsThis() const { return tr("Call Graph around active Function" "

Depending on configuration, this view shows " "the call graph environment of the active function. " "Note: the shown cost is only the cost which is " "spent while the active function was actually running; " "i.e. the cost shown for main() - if it is visible - should " "be the same as the cost of the active function, as that is " "the part of inclusive cost of main() spent while the active " "function was running.

" "

For cycles, blue call arrows indicate that this is an " "artificial call added for correct drawing which " "actually never happened.

" "

If the graph is larger than the widget area, an overview " "panner is shown in one edge. " "There are similar visualization options to the " "Call Treemap; the selected function is highlighted.

"); } void CallGraphView::updateSizes(QSize s) { if (!_scene) return; if (s == QSize(0, 0)) s = size(); // the part of the scene that should be visible int cWidth = (int)_scene->width() - 2*_xMargin + 100; int cHeight = (int)_scene->height() - 2*_yMargin + 100; // hide birds eye view if no overview needed if (!_data || !_activeItem || ((cWidth < s.width()) && (cHeight < s.height())) ) { _panningView->hide(); return; } // first, assume use of 1/3 of width/height (possible larger) double zoom = .33 * s.width() / cWidth; if (zoom * cHeight < .33 * s.height()) zoom = .33 * s.height() / cHeight; // fit to widget size if (cWidth * zoom > s.width()) zoom = s.width() / (double)cWidth; if (cHeight * zoom > s.height()) zoom = s.height() / (double)cHeight; // scale to never use full height/width zoom = zoom * 3/4; // at most a zoom of 1/3 if (zoom > .33) zoom = .33; if (zoom != _panningZoom) { _panningZoom = zoom; if (0) qDebug("Canvas Size: %fx%f, Content: %dx%d, Zoom: %f", _scene->width(), _scene->height(), cWidth, cHeight, zoom); QMatrix m; _panningView->setMatrix(m.scale(zoom, zoom)); // make it a little bigger to compensate for widget frame _panningView->resize(int(cWidth * zoom) + 4, int(cHeight * zoom) + 4); // update ZoomRect in panningView scrollContentsBy(0, 0); } _panningView->centerOn(_scene->width()/2, _scene->height()/2); int cvW = _panningView->width(); int cvH = _panningView->height(); int x = width()- cvW - verticalScrollBar()->width() -2; int y = height()-cvH - horizontalScrollBar()->height() -2; QPoint oldZoomPos = _panningView->pos(); QPoint newZoomPos = QPoint(0, 0); ZoomPosition zp = _zoomPosition; if (zp == Auto) { int tlCols = items(QRect(0,0, cvW,cvH)).count(); int trCols = items(QRect(x,0, cvW,cvH)).count(); int blCols = items(QRect(0,y, cvW,cvH)).count(); int brCols = items(QRect(x,y, cvW,cvH)).count(); int minCols = tlCols; zp = _lastAutoPosition; switch (zp) { case TopRight: minCols = trCols; break; case BottomLeft: minCols = blCols; break; case BottomRight: minCols = brCols; break; default: case TopLeft: minCols = tlCols; break; } if (minCols > tlCols) { minCols = tlCols; zp = TopLeft; } if (minCols > trCols) { minCols = trCols; zp = TopRight; } if (minCols > blCols) { minCols = blCols; zp = BottomLeft; } if (minCols > brCols) { minCols = brCols; zp = BottomRight; } _lastAutoPosition = zp; } switch (zp) { case TopLeft: newZoomPos = QPoint(0, 0); break; case TopRight: newZoomPos = QPoint(x, 0); break; case BottomLeft: newZoomPos = QPoint(0, y); break; case BottomRight: newZoomPos = QPoint(x, y); break; default: break; } if (newZoomPos != oldZoomPos) _panningView->move(newZoomPos); if (zp == Hide) _panningView->hide(); else _panningView->show(); } void CallGraphView::focusInEvent(QFocusEvent*) { if (!_scene) return; if (_selectedNode && _selectedNode->canvasNode()) { _selectedNode->canvasNode()->setSelected(true); // requests item update _scene->update(); } } void CallGraphView::focusOutEvent(QFocusEvent* e) { // trigger updates as in focusInEvent focusInEvent(e); } void CallGraphView::keyPressEvent(QKeyEvent* e) { if (!_scene) { e->ignore(); return; } if ((e->key() == Qt::Key_Return) ||(e->key() == Qt::Key_Space)) { if (_selectedNode) activated(_selectedNode->function()); else if (_selectedEdge && _selectedEdge->call()) activated(_selectedEdge->call()); return; } // move selected node/edge if (!(e->modifiers() & (Qt::ShiftModifier | Qt::ControlModifier)) &&(_selectedNode || _selectedEdge)&&((e->key() == Qt::Key_Up) ||(e->key() == Qt::Key_Down)||(e->key() == Qt::Key_Left)||(e->key() == Qt::Key_Right))) { TraceFunction* f = 0; TraceCall* c = 0; // rotate arrow key meaning for LeftRight layout int key = e->key(); if (_layout == LeftRight) { switch (key) { case Qt::Key_Up: key = Qt::Key_Left; break; case Qt::Key_Down: key = Qt::Key_Right; break; case Qt::Key_Left: key = Qt::Key_Up; break; case Qt::Key_Right: key = Qt::Key_Down; break; default: break; } } if (_selectedNode) { if (key == Qt::Key_Up) c = _selectedNode->visibleCaller(); if (key == Qt::Key_Down) c = _selectedNode->visibleCallee(); if (key == Qt::Key_Right) f = _selectedNode->nextVisible(); if (key == Qt::Key_Left) f = _selectedNode->priorVisible(); } else if (_selectedEdge) { if (key == Qt::Key_Up) f = _selectedEdge->visibleCaller(); if (key == Qt::Key_Down) f = _selectedEdge->visibleCallee(); if (key == Qt::Key_Right) c = _selectedEdge->nextVisible(); if (key == Qt::Key_Left) c = _selectedEdge->priorVisible(); } if (c) selected(c); if (f) selected(f); return; } // move canvas... QPointF center = mapToScene(viewport()->rect().center()); if (e->key() == Qt::Key_Home) centerOn(center + QPointF(-_scene->width(), 0)); else if (e->key() == Qt::Key_End) centerOn(center + QPointF(_scene->width(), 0)); else if (e->key() == Qt::Key_PageUp) { QPointF dy = mapToScene(0, height()) - mapToScene(0, 0); centerOn(center + QPointF(-dy.x()/2, -dy.y()/2)); } else if (e->key() == Qt::Key_PageDown) { QPointF dy = mapToScene(0, height()) - mapToScene(0, 0); centerOn(center + QPointF(dy.x()/2, dy.y()/2)); } else if (e->key() == Qt::Key_Left) { QPointF dx = mapToScene(width(), 0) - mapToScene(0, 0); centerOn(center + QPointF(-dx.x()/10, -dx.y()/10)); } else if (e->key() == Qt::Key_Right) { QPointF dx = mapToScene(width(), 0) - mapToScene(0, 0); centerOn(center + QPointF(dx.x()/10, dx.y()/10)); } else if (e->key() == Qt::Key_Down) { QPointF dy = mapToScene(0, height()) - mapToScene(0, 0); centerOn(center + QPointF(dy.x()/10, dy.y()/10)); } else if (e->key() == Qt::Key_Up) { QPointF dy = mapToScene(0, height()) - mapToScene(0, 0); centerOn(center + QPointF(-dy.x()/10, -dy.y()/10)); } else e->ignore(); } void CallGraphView::resizeEvent(QResizeEvent* e) { QGraphicsView::resizeEvent(e); if (_scene) updateSizes(e->size()); } CostItem* CallGraphView::canShow(CostItem* i) { if (i) { switch (i->type()) { case ProfileContext::Function: case ProfileContext::FunctionCycle: case ProfileContext::Call: return i; default: break; } } return 0; } void CallGraphView::doUpdate(int changeType, bool) { // Special case ? if (changeType == eventType2Changed) return; if (changeType == selectedItemChanged) { if (!_scene) return; if (!_selectedItem) return; GraphNode* n = 0; GraphEdge* e = 0; if ((_selectedItem->type() == ProfileContext::Function) ||(_selectedItem->type() == ProfileContext::FunctionCycle)) { n = _exporter.node((TraceFunction*)_selectedItem); if (n == _selectedNode) return; } else if (_selectedItem->type() == ProfileContext::Call) { TraceCall* c = (TraceCall*)_selectedItem; e = _exporter.edge(c->caller(false), c->called(false)); if (e == _selectedEdge) return; } // unselect any selected item if (_selectedNode && _selectedNode->canvasNode()) { _selectedNode->canvasNode()->setSelected(false); } _selectedNode = 0; if (_selectedEdge && _selectedEdge->canvasEdge()) { _selectedEdge->canvasEdge()->setSelected(false); } _selectedEdge = 0; // select CanvasNode* sNode = 0; if (n && n->canvasNode()) { _selectedNode = n; _selectedNode->canvasNode()->setSelected(true); if (!_isMoving) sNode = _selectedNode->canvasNode(); } if (e && e->canvasEdge()) { _selectedEdge = e; _selectedEdge->canvasEdge()->setSelected(true); #if 0 // do not change position when selecting edge if (!_isMoving) { if (_selectedEdge->fromNode()) sNode = _selectedEdge->fromNode()->canvasNode(); if (!sNode && _selectedEdge->toNode()) sNode = _selectedEdge->toNode()->canvasNode(); } #endif } if (sNode) ensureVisible(sNode); _scene->update(); return; } if (changeType == groupTypeChanged) { if (!_scene) return; if (_clusterGroups) { refresh(); return; } QList l = _scene->items(); for (int i = 0; i < l.size(); ++i) if (l[i]->type() == CANVAS_NODE) ((CanvasNode*)l[i])->updateGroup(); _scene->update(); return; } if (changeType & dataChanged) { // invalidate old selection and graph part _exporter.reset(_data, _activeItem, _eventType, _groupType); _selectedNode = 0; _selectedEdge = 0; } refresh(); } void CallGraphView::clear() { if (!_scene) return; _panningView->setScene(0); setScene(0); delete _scene; _scene = 0; } void CallGraphView::showText(QString s) { clear(); _renderTimer.stop(); _scene = new QGraphicsScene; _scene->addSimpleText(s); centerOn(0, 0); setScene(_scene); _scene->update(); _panningView->hide(); } void CallGraphView::showRenderWarning() { QString s; if (_renderProcess) s = tr("Warning: a long lasting graph layouting is in progress.\n" "Reduce node/edge limits for speedup.\n"); else s = tr("Layouting stopped.\n"); s.append(tr("The call graph has %1 nodes and %2 edges.\n") .arg(_exporter.nodeCount()).arg(_exporter.edgeCount())); showText(s); } void CallGraphView::showRenderError(QString s) { QString err; err = tr("No graph available because the layouting process failed.\n"); if (_renderProcess) err += tr("Trying to run the following command did not work:\n" "'%1'\n").arg(_renderProcessCmdLine); err += tr("Please check that 'dot' is installed (package GraphViz)."); if (!s.isEmpty()) err += QStringLiteral("\n\n%1").arg(s); showText(err); } void CallGraphView::stopRendering() { if (!_renderProcess) return; qDebug("CallGraphView::stopRendering: Killing QProcess %p", _renderProcess); _renderProcess->kill(); // forget about this process, not interesting any longer _renderProcess->deleteLater(); _renderProcess = 0; _unparsedOutput = QString(); _renderTimer.setSingleShot(true); _renderTimer.start(200); } void CallGraphView::refresh() { // trigger start of new layouting via 'dot' if (_renderProcess) stopRendering(); // we want to keep a selected node item at the same global position _prevSelectedNode = _selectedNode; _prevSelectedPos = QPoint(-1, -1); if (_selectedNode) { QPointF center = _selectedNode->canvasNode()->rect().center(); _prevSelectedPos = mapFromScene(center); } if (!_data || !_activeItem) { showText(tr("No item activated for which to " "draw the call graph.")); return; } ProfileContext::Type t = _activeItem->type(); switch (t) { case ProfileContext::Function: case ProfileContext::FunctionCycle: case ProfileContext::Call: break; default: showText(tr("No call graph can be drawn for " "the active item.")); return; } if (1) qDebug() << "CallGraphView::refresh"; _selectedNode = 0; _selectedEdge = 0; /* * Call 'dot' asynchronoulsy in the background with the aim to * - have responsive GUI while layout task runs (potentially long!) * - notify user about a long run, using a timer * - kill long running 'dot' processes when another layout is * requested, as old data is not needed any more * * Even after killing a process, the QProcess needs some time * to make sure the process is destroyed; also, stdout data * still can be delivered after killing. Thus, there can/should be * multiple QProcess's at one time. * The QProcess we currently wait for data from is <_renderProcess> * Signals from other QProcesses are ignored with the exception of * the finished() signal, which triggers QProcess destruction. */ QString renderProgram; QStringList renderArgs; if (_layout == GraphOptions::Circular) renderProgram = QStringLiteral("twopi"); else renderProgram = QStringLiteral("dot"); renderArgs << QStringLiteral("-Tplain"); _unparsedOutput = QString(); // display warning if layouting takes > 1s _renderTimer.setSingleShot(true); _renderTimer.start(1000); _renderProcess = new QProcess(this); connect(_renderProcess, &QProcess::readyReadStandardOutput, this, &CallGraphView::readDotOutput); connect(_renderProcess, SIGNAL(error(QProcess::ProcessError)), this, SLOT(dotError())); connect(_renderProcess, SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(dotExited())); _renderProcessCmdLine = renderProgram + " " + renderArgs.join(QStringLiteral(" ")); qDebug("CallGraphView::refresh: Starting process %p, '%s'", _renderProcess, qPrintable(_renderProcessCmdLine)); // _renderProcess can be set to 0 on error after start(). // thus, we use a local copy afterwards QProcess* p = _renderProcess; p->start(renderProgram, renderArgs); _exporter.reset(_data, _activeItem, _eventType, _groupType); _exporter.writeDot(p); p->closeWriteChannel(); } void CallGraphView::readDotOutput() { QProcess* p = qobject_cast(sender()); qDebug("CallGraphView::readDotOutput: QProcess %p", p); // signal from old/uninteresting process? if ((_renderProcess == 0) || (p != _renderProcess)) { p->deleteLater(); return; } _unparsedOutput.append(_renderProcess->readAllStandardOutput()); } void CallGraphView::dotError() { QProcess* p = qobject_cast(sender()); qDebug("CallGraphView::dotError: Got %d from QProcess %p", p->error(), p); // signal from old/uninteresting process? if ((_renderProcess == 0) || (p != _renderProcess)) { p->deleteLater(); return; } showRenderError(_renderProcess->readAllStandardError()); // not interesting any longer _renderProcess->deleteLater(); _renderProcess = 0; } void CallGraphView::dotExited() { QProcess* p = qobject_cast(sender()); qDebug("CallGraphView::dotExited: QProcess %p", p); // signal from old/uninteresting process? if ((_renderProcess == 0) || (p != _renderProcess)) { p->deleteLater(); return; } _unparsedOutput.append(_renderProcess->readAllStandardOutput()); _renderProcess->deleteLater(); _renderProcess = 0; QString line, cmd; CanvasNode *rItem; QGraphicsEllipseItem* eItem; CanvasEdge* sItem; CanvasEdgeLabel* lItem; QTextStream* dotStream; double scale = 1.0, scaleX = 1.0, scaleY = 1.0; double dotWidth = 0, dotHeight = 0; GraphNode* activeNode = 0; GraphEdge* activeEdge = 0; _renderTimer.stop(); viewport()->setUpdatesEnabled(false); clear(); dotStream = new QTextStream(&_unparsedOutput, QIODevice::ReadOnly); // First pass to adjust coordinate scaling by node height given from dot // Normal detail level (=1) should be 3 lines using general KDE font double nodeHeight = 0.0; while(1) { line = dotStream->readLine(); if (line.isNull()) break; if (line.isEmpty()) continue; QTextStream lineStream(&line, QIODevice::ReadOnly); lineStream >> cmd; if (cmd != QLatin1String("node")) continue; QString s, h; lineStream >> s /*name*/ >> s /*x*/ >> s /*y*/ >> s /*width*/ >> h /*height*/; nodeHeight = h.toDouble(); break; } if (nodeHeight > 0.0) { scaleY = (8 + (1 + 2 * _detailLevel) * fontMetrics().height()) / nodeHeight; scaleX = 80; } dotStream->seek(0); int lineno = 0; while (1) { line = dotStream->readLine(); if (line.isNull()) break; lineno++; if (line.isEmpty()) continue; QTextStream lineStream(&line, QIODevice::ReadOnly); lineStream >> cmd; if (0) qDebug("%s:%d - line '%s', cmd '%s'", qPrintable(_exporter.filename()), lineno, qPrintable(line), qPrintable(cmd)); if (cmd == QLatin1String("stop")) break; if (cmd == QLatin1String("graph")) { QString dotWidthString, dotHeightString; // scale will not be used lineStream >> scale >> dotWidthString >> dotHeightString; dotWidth = dotWidthString.toDouble(); dotHeight = dotHeightString.toDouble(); if (!_scene) { int w = (int)(scaleX * dotWidth); int h = (int)(scaleY * dotHeight); // We use as minimum canvas size the desktop size. // Otherwise, the canvas would have to be resized on widget resize. _xMargin = 50; if (w < QApplication::desktop()->width()) _xMargin += (QApplication::desktop()->width()-w)/2; _yMargin = 50; if (h < QApplication::desktop()->height()) _yMargin += (QApplication::desktop()->height()-h)/2; _scene = new QGraphicsScene( 0.0, 0.0, qreal(w+2*_xMargin), qreal(h+2*_yMargin)); // Change background color for call graph from default system color to // white. It has to blend into the gradient for the selected function. _scene->setBackgroundBrush(Qt::white); #if DEBUG_GRAPH qDebug() << qPrintable(_exporter.filename()) << ":" << lineno << " - graph (" << dotWidth << " x " << dotHeight << ") => (" << w << " x " << h << ")"; #endif } else qDebug() << "Ignoring 2nd 'graph' from dot (" << _exporter.filename() << ":"<< lineno << ")"; continue; } if ((cmd != QLatin1String("node")) && (cmd != QLatin1String("edge"))) { qDebug() << "Ignoring unknown command '"<< cmd << "' from dot ("<< _exporter.filename() << ":"<< lineno << ")"; continue; } if (_scene == 0) { qDebug() << "Ignoring '"<< cmd << "' without 'graph' from dot ("<< _exporter.filename() << ":"<< lineno << ")"; continue; } if (cmd == QLatin1String("node")) { // x, y are centered in node - QString nodeName, label, nodeX, nodeY, nodeWidth, nodeHeight; + QString nodeName, nodeX, nodeY, nodeWidth, nodeHeight; double x, y, width, height; lineStream >> nodeName >> nodeX >> nodeY >> nodeWidth >> nodeHeight; x = nodeX.toDouble(); y = nodeY.toDouble(); width = nodeWidth.toDouble(); height = nodeHeight.toDouble(); GraphNode* n = _exporter.node(_exporter.toFunc(nodeName)); int xx = (int)(scaleX * x + _xMargin); int yy = (int)(scaleY * (dotHeight - y)+ _yMargin); int w = (int)(scaleX * width); int h = (int)(scaleY * height); #if DEBUG_GRAPH qDebug() << _exporter.filename() << ":" << lineno << " - node '" << nodeName << "' ( " << x << "/" << y << " - " << width << "x" << height << " ) => (" << xx-w/2 << "/" << yy-h/2 << " - " << w << "x" << h << ")" << endl; #endif // Unnamed nodes with collapsed edges (with 'R' and 'S') if (nodeName[0] == 'R'|| nodeName[0] == 'S') { w = 10, h = 10; eItem = new QGraphicsEllipseItem( QRectF(xx-w/2, yy-h/2, w, h) ); _scene->addItem(eItem); eItem->setBrush(Qt::gray); eItem->setZValue(1.0); eItem->show(); continue; } if (!n) { qDebug("Warning: Unknown function '%s' ?!", qPrintable(nodeName)); continue; } n->setVisible(true); rItem = new CanvasNode(this, n, xx-w/2, yy-h/2, w, h); // limit symbol space to a maximal number of lines depending on detail level if (_detailLevel>0) rItem->setMaxLines(0, 2*_detailLevel); _scene->addItem(rItem); n->setCanvasNode(rItem); if (n) { if (n->function() == activeItem()) activeNode = n; if (n->function() == selectedItem()) _selectedNode = n; rItem->setSelected(n == _selectedNode); } rItem->setZValue(1.0); rItem->show(); continue; } // edge QString node1Name, node2Name, label, edgeX, edgeY; double x, y; QPolygon poly; int points, i; lineStream >> node1Name >> node2Name >> points; GraphEdge* e = _exporter.edge(_exporter.toFunc(node1Name), _exporter.toFunc(node2Name)); if (!e) { qDebug() << "Unknown edge '"<< node1Name << "'-'"<< node2Name << "' from dot ("<< _exporter.filename() << ":"<< lineno << ")"; continue; } e->setVisible(true); if (e->fromNode()) e->fromNode()->addCallee(e); if (e->toNode()) e->toNode()->addCaller(e); if (0) qDebug(" Edge with %d points:", points); poly.resize(points); for (i=0; i> edgeX >> edgeY; x = edgeX.toDouble(); y = edgeY.toDouble(); int xx = (int)(scaleX * x + _xMargin); int yy = (int)(scaleY * (dotHeight - y)+ _yMargin); if (0) qDebug(" P %d: ( %f / %f ) => ( %d / %d)", i, x, y, xx, yy); poly.setPoint(i, xx, yy); } if (i < points) { qDebug("CallGraphView: Can not read %d spline points (%s:%d)", points, qPrintable(_exporter.filename()), lineno); continue; } // calls into/out of cycles are special: make them blue QColor arrowColor = Qt::black; TraceFunction* caller = e->fromNode() ? e->fromNode()->function() : 0; TraceFunction* called = e->toNode() ? e->toNode()->function() : 0; if ( (caller && (caller->cycle() == caller)) || (called && (called->cycle() == called)) ) arrowColor = Qt::blue; sItem = new CanvasEdge(e); _scene->addItem(sItem); e->setCanvasEdge(sItem); sItem->setControlPoints(poly); // width of pen will be adjusted in CanvasEdge::paint() sItem->setPen(QPen(arrowColor)); sItem->setZValue(0.5); sItem->show(); if (e->call() == selectedItem()) _selectedEdge = e; if (e->call() == activeItem()) activeEdge = e; sItem->setSelected(e == _selectedEdge); // Arrow head QPoint arrowDir; int indexHead = -1; // check if head is at start of spline... // this is needed because dot always gives points from top to bottom CanvasNode* fromNode = e->fromNode() ? e->fromNode()->canvasNode() : 0; if (fromNode) { QPointF toCenter = fromNode->rect().center(); qreal dx0 = poly.point(0).x() - toCenter.x(); qreal dy0 = poly.point(0).y() - toCenter.y(); qreal dx1 = poly.point(points-1).x() - toCenter.x(); qreal dy1 = poly.point(points-1).y() - toCenter.y(); if (dx0*dx0+dy0*dy0 > dx1*dx1+dy1*dy1) { // start of spline is nearer to call target node indexHead=-1; while (arrowDir.isNull() && (indexHead1)) { indexHead--; arrowDir = poly.point(indexHead) - poly.point(indexHead-1); } } if (!arrowDir.isNull()) { // arrow around pa.point(indexHead) with direction arrowDir arrowDir *= 10.0/sqrt(double(arrowDir.x()*arrowDir.x() + arrowDir.y()*arrowDir.y())); QPolygonF a; a << QPointF(poly.point(indexHead) + arrowDir); a << QPointF(poly.point(indexHead) + QPoint(arrowDir.y()/2, -arrowDir.x()/2)); a << QPointF(poly.point(indexHead) + QPoint(-arrowDir.y()/2, arrowDir.x()/2)); if (0) qDebug(" Arrow: ( %f/%f, %f/%f, %f/%f)", a[0].x(), a[0].y(), a[1].x(), a[1].y(), a[2].x(), a[1].y()); CanvasEdgeArrow* aItem = new CanvasEdgeArrow(sItem); _scene->addItem(aItem); aItem->setPolygon(a); aItem->setBrush(arrowColor); aItem->setZValue(1.5); aItem->show(); sItem->setArrow(aItem); } if (lineStream.atEnd()) continue; // parse quoted label QChar c; lineStream >> c; while (c.isSpace()) lineStream >> c; if (c != '\"') { lineStream >> label; label = c + label; } else { lineStream >> c; while (!c.isNull() && (c != '\"')) { //if (c == '\\') lineStream >> c; label += c; lineStream >> c; } } lineStream >> edgeX >> edgeY; x = edgeX.toDouble(); y = edgeY.toDouble(); int xx = (int)(scaleX * x + _xMargin); int yy = (int)(scaleY * (dotHeight - y)+ _yMargin); if (0) qDebug(" Label '%s': ( %f / %f ) => ( %d / %d)", qPrintable(label), x, y, xx, yy); // Fixed Dimensions for Label: 100 x 40 int w = 100; int h = _detailLevel * 20; lItem = new CanvasEdgeLabel(this, sItem, xx-w/2, yy-h/2, w, h); _scene->addItem(lItem); // edge labels above nodes lItem->setZValue(1.5); sItem->setLabel(lItem); if (h>0) lItem->show(); } delete dotStream; // for keyboard navigation _exporter.sortEdges(); if (!_scene) { _scene = new QGraphicsScene; QString s = tr("Error running the graph layouting tool.\n"); s += tr("Please check that 'dot' is installed (package GraphViz)."); _scene->addSimpleText(s); centerOn(0, 0); } else if (!activeNode && !activeEdge) { QString s = tr("There is no call graph available for function\n" "\t'%1'\n" "because it has no cost of the selected event type.") .arg(_activeItem->name()); _scene->addSimpleText(s); centerOn(0, 0); } _panningView->setScene(_scene); setScene(_scene); // if we do not have a selection, or the old selection is not // in visible graph, make active function selected for this view if ((!_selectedNode || !_selectedNode->canvasNode()) && (!_selectedEdge || !_selectedEdge->canvasEdge())) { if (activeNode) { _selectedNode = activeNode; _selectedNode->canvasNode()->setSelected(true); } else if (activeEdge) { _selectedEdge = activeEdge; _selectedEdge->canvasEdge()->setSelected(true); } } CanvasNode* sNode = 0; if (_selectedNode) sNode = _selectedNode->canvasNode(); else if (_selectedEdge) { if (_selectedEdge->fromNode()) sNode = _selectedEdge->fromNode()->canvasNode(); if (!sNode && _selectedEdge->toNode()) sNode = _selectedEdge->toNode()->canvasNode(); } if (sNode) { if (_prevSelectedNode) { QPointF prevPos = mapToScene(_prevSelectedPos); if (rect().contains(_prevSelectedPos)) { QPointF wCenter = mapToScene(viewport()->rect().center()); centerOn(sNode->rect().center() + wCenter - prevPos); } else ensureVisible(sNode); } else centerOn(sNode); } if (activeNode) { CanvasNode* cn = activeNode->canvasNode(); CanvasFrame* f = new CanvasFrame(cn); _scene->addItem(f); f->setPos(cn->pos()); f->setZValue(-1); } _panningZoom = 0; updateSizes(); _scene->update(); viewport()->setUpdatesEnabled(true); delete _renderProcess; _renderProcess = 0; } // Called by QAbstractScrollArea to notify about scrollbar changes void CallGraphView::scrollContentsBy(int dx, int dy) { // call QGraphicsView implementation QGraphicsView::scrollContentsBy(dx, dy); QPointF topLeft = mapToScene(QPoint(0, 0)); QPointF bottomRight = mapToScene(QPoint(width(), height())); QRectF z(topLeft, bottomRight); if (0) qDebug("CallGraphView::scrollContentsBy(dx %d, dy %d) - to (%f,%f - %f,%f)", dx, dy, topLeft.x(), topLeft.y(), bottomRight.x(), bottomRight.y()); _panningView->setZoomRect(z); } void CallGraphView::zoomRectMoved(qreal dx, qreal dy) { //FIXME if (leftMargin()>0) dx = 0; //FIXME if (topMargin()>0) dy = 0; QScrollBar *hBar = horizontalScrollBar(); QScrollBar *vBar = verticalScrollBar(); hBar->setValue(hBar->value() + int(dx)); vBar->setValue(vBar->value() + int(dy)); } void CallGraphView::zoomRectMoveFinished() { if (_zoomPosition == Auto) updateSizes(); } void CallGraphView::mousePressEvent(QMouseEvent* e) { // clicking on the viewport sets focus setFocus(); // activate scroll mode on left click if (e->button() == Qt::LeftButton) _isMoving = true; QGraphicsItem* i = itemAt(e->pos()); if (i) { if (i->type() == CANVAS_NODE) { GraphNode* n = ((CanvasNode*)i)->node(); if (0) qDebug("CallGraphView: Got Node '%s'", qPrintable(n->function()->prettyName())); selected(n->function()); } // redirect from label / arrow to edge if (i->type() == CANVAS_EDGELABEL) i = ((CanvasEdgeLabel*)i)->canvasEdge(); if (i->type() == CANVAS_EDGEARROW) i = ((CanvasEdgeArrow*)i)->canvasEdge(); if (i->type() == CANVAS_EDGE) { GraphEdge* e = ((CanvasEdge*)i)->edge(); if (0) qDebug("CallGraphView: Got Edge '%s'", qPrintable(e->prettyName())); if (e->call()) selected(e->call()); } } _lastPos = e->pos(); } void CallGraphView::mouseMoveEvent(QMouseEvent* e) { if (_isMoving) { QPoint delta = e->pos() - _lastPos; QScrollBar *hBar = horizontalScrollBar(); QScrollBar *vBar = verticalScrollBar(); hBar->setValue(hBar->value() - delta.x()); vBar->setValue(vBar->value() - delta.y()); _lastPos = e->pos(); } } void CallGraphView::mouseReleaseEvent(QMouseEvent*) { _isMoving = false; if (_zoomPosition == Auto) updateSizes(); } void CallGraphView::mouseDoubleClickEvent(QMouseEvent* e) { QGraphicsItem* i = itemAt(e->pos()); if (i == 0) return; if (i->type() == CANVAS_NODE) { GraphNode* n = ((CanvasNode*)i)->node(); if (0) qDebug("CallGraphView: Double Clicked on Node '%s'", qPrintable(n->function()->prettyName())); activated(n->function()); } // redirect from label / arrow to edge if (i->type() == CANVAS_EDGELABEL) i = ((CanvasEdgeLabel*)i)->canvasEdge(); if (i->type() == CANVAS_EDGEARROW) i = ((CanvasEdgeArrow*)i)->canvasEdge(); if (i->type() == CANVAS_EDGE) { GraphEdge* e = ((CanvasEdge*)i)->edge(); if (e->call()) { if (0) qDebug("CallGraphView: Double Clicked On Edge '%s'", qPrintable(e->call()->prettyName())); activated(e->call()); } } } // helper functions for context menu: // submenu builders and trigger handlers QAction* CallGraphView::addCallerDepthAction(QMenu* m, QString s, int d) { QAction* a; a = m->addAction(s); a->setData(d); a->setCheckable(true); a->setChecked(_maxCallerDepth == d); return a; } QMenu* CallGraphView::addCallerDepthMenu(QMenu* menu) { QAction* a; QMenu* m; m = menu->addMenu(tr("Caller Depth")); a = addCallerDepthAction(m, tr("Unlimited"), -1); a->setEnabled(_funcLimit>0.005); m->addSeparator(); addCallerDepthAction(m, tr("Depth 0", "None"), 0); addCallerDepthAction(m, tr("max. 2"), 2); addCallerDepthAction(m, tr("max. 5"), 5); addCallerDepthAction(m, tr("max. 10"), 10); addCallerDepthAction(m, tr("max. 15"), 15); connect(m, &QMenu::triggered, this, &CallGraphView::callerDepthTriggered ); return m; } void CallGraphView::callerDepthTriggered(QAction* a) { _maxCallerDepth = a->data().toInt(0); refresh(); } QAction* CallGraphView::addCalleeDepthAction(QMenu* m, QString s, int d) { QAction* a; a = m->addAction(s); a->setData(d); a->setCheckable(true); a->setChecked(_maxCalleeDepth == d); return a; } QMenu* CallGraphView::addCalleeDepthMenu(QMenu* menu) { QAction* a; QMenu* m; m = menu->addMenu(tr("Callee Depth")); a = addCalleeDepthAction(m, tr("Unlimited"), -1); a->setEnabled(_funcLimit>0.005); m->addSeparator(); addCalleeDepthAction(m, tr("Depth 0", "None"), 0); addCalleeDepthAction(m, tr("max. 2"), 2); addCalleeDepthAction(m, tr("max. 5"), 5); addCalleeDepthAction(m, tr("max. 10"), 10); addCalleeDepthAction(m, tr("max. 15"), 15); connect(m, &QMenu::triggered, this, &CallGraphView::calleeDepthTriggered ); return m; } void CallGraphView::calleeDepthTriggered(QAction* a) { _maxCalleeDepth = a->data().toInt(0); refresh(); } QAction* CallGraphView::addNodeLimitAction(QMenu* m, QString s, double l) { QAction* a; a = m->addAction(s); a->setData(l); a->setCheckable(true); a->setChecked(_funcLimit == l); return a; } QMenu* CallGraphView::addNodeLimitMenu(QMenu* menu) { QAction* a; QMenu* m; m = menu->addMenu(tr("Min. Node Cost")); a = addNodeLimitAction(m, tr("No Minimum"), 0.0); // Unlimited node cost easily produces huge graphs such that 'dot' // would need a long time to layout. For responsiveness, we only allow // for unlimited node cost if a caller and callee depth limit is set. a->setEnabled((_maxCallerDepth>=0) && (_maxCalleeDepth>=0)); m->addSeparator(); addNodeLimitAction(m, tr("50 %"), .5); addNodeLimitAction(m, tr("20 %"), .2); addNodeLimitAction(m, tr("10 %"), .1); addNodeLimitAction(m, tr("5 %"), .05); addNodeLimitAction(m, tr("2 %"), .02); addNodeLimitAction(m, tr("1 %"), .01); connect(m, &QMenu::triggered, this, &CallGraphView::nodeLimitTriggered ); return m; } void CallGraphView::nodeLimitTriggered(QAction* a) { _funcLimit = a->data().toDouble(0); refresh(); } QAction* CallGraphView::addCallLimitAction(QMenu* m, QString s, double l) { QAction* a; a = m->addAction(s); a->setData(l); a->setCheckable(true); a->setChecked(_callLimit == l); return a; } QMenu* CallGraphView::addCallLimitMenu(QMenu* menu) { QMenu* m; m = menu->addMenu(tr("Min. Call Cost")); addCallLimitAction(m, tr("Same as Node"), 1.0); // xgettext: no-c-format addCallLimitAction(m, tr("50 % of Node"), .5); // xgettext: no-c-format addCallLimitAction(m, tr("20 % of Node"), .2); // xgettext: no-c-format addCallLimitAction(m, tr("10 % of Node"), .1); connect(m, &QMenu::triggered, this, &CallGraphView::callLimitTriggered ); return m; } void CallGraphView::callLimitTriggered(QAction* a) { _callLimit = a->data().toDouble(0); refresh(); } QAction* CallGraphView::addZoomPosAction(QMenu* m, QString s, CallGraphView::ZoomPosition p) { QAction* a; a = m->addAction(s); a->setData((int)p); a->setCheckable(true); a->setChecked(_zoomPosition == p); return a; } QMenu* CallGraphView::addZoomPosMenu(QMenu* menu) { QMenu* m = menu->addMenu(tr("Birds-eye View")); addZoomPosAction(m, tr("Top Left"), TopLeft); addZoomPosAction(m, tr("Top Right"), TopRight); addZoomPosAction(m, tr("Bottom Left"), BottomLeft); addZoomPosAction(m, tr("Bottom Right"), BottomRight); addZoomPosAction(m, tr("Automatic"), Auto); addZoomPosAction(m, tr("Hide"), Hide); connect(m, &QMenu::triggered, this, &CallGraphView::zoomPosTriggered ); return m; } void CallGraphView::zoomPosTriggered(QAction* a) { _zoomPosition = (ZoomPosition) a->data().toInt(0); updateSizes(); } QAction* CallGraphView::addLayoutAction(QMenu* m, QString s, GraphOptions::Layout l) { QAction* a; a = m->addAction(s); a->setData((int)l); a->setCheckable(true); a->setChecked(_layout == l); return a; } QMenu* CallGraphView::addLayoutMenu(QMenu* menu) { QMenu* m = menu->addMenu(tr("Layout")); addLayoutAction(m, tr("Top to Down"), TopDown); addLayoutAction(m, tr("Left to Right"), LeftRight); addLayoutAction(m, tr("Circular"), Circular); connect(m, &QMenu::triggered, this, &CallGraphView::layoutTriggered ); return m; } void CallGraphView::layoutTriggered(QAction* a) { _layout = (Layout) a->data().toInt(0); refresh(); } void CallGraphView::contextMenuEvent(QContextMenuEvent* e) { _isMoving = false; QGraphicsItem* i = itemAt(e->pos()); QMenu popup; TraceFunction *f = 0, *cycle = 0; TraceCall* c = 0; QAction* activateFunction = 0; QAction* activateCycle = 0; QAction* activateCall = 0; if (i) { if (i->type() == CANVAS_NODE) { GraphNode* n = ((CanvasNode*)i)->node(); if (0) qDebug("CallGraphView: Menu on Node '%s'", qPrintable(n->function()->prettyName())); f = n->function(); cycle = f->cycle(); QString name = f->prettyName(); QString menuStr = tr("Go to '%1'") .arg(GlobalConfig::shortenSymbol(name)); activateFunction = popup.addAction(menuStr); if (cycle && (cycle != f)) { name = GlobalConfig::shortenSymbol(cycle->prettyName()); activateCycle = popup.addAction(tr("Go to '%1'").arg(name)); } popup.addSeparator(); } // redirect from label / arrow to edge if (i->type() == CANVAS_EDGELABEL) i = ((CanvasEdgeLabel*)i)->canvasEdge(); if (i->type() == CANVAS_EDGEARROW) i = ((CanvasEdgeArrow*)i)->canvasEdge(); if (i->type() == CANVAS_EDGE) { GraphEdge* e = ((CanvasEdge*)i)->edge(); if (0) qDebug("CallGraphView: Menu on Edge '%s'", qPrintable(e->prettyName())); c = e->call(); if (c) { QString name = c->prettyName(); QString menuStr = tr("Go to '%1'") .arg(GlobalConfig::shortenSymbol(name)); activateCall = popup.addAction(menuStr); popup.addSeparator(); } } } QAction* stopLayout = 0; if (_renderProcess) { stopLayout = popup.addAction(tr("Stop Layouting")); popup.addSeparator(); } addGoMenu(&popup); popup.addSeparator(); QMenu* epopup = popup.addMenu(tr("Export Graph")); QAction* exportAsDot = epopup->addAction(tr("As DOT file...")); QAction* exportAsImage = epopup->addAction(tr("As Image...")); popup.addSeparator(); QMenu* gpopup = popup.addMenu(tr("Graph")); addCallerDepthMenu(gpopup); addCalleeDepthMenu(gpopup); addNodeLimitMenu(gpopup); addCallLimitMenu(gpopup); gpopup->addSeparator(); QAction* toggleSkipped; toggleSkipped = gpopup->addAction(tr("Arrows for Skipped Calls")); toggleSkipped->setCheckable(true); toggleSkipped->setChecked(_showSkipped); QAction* toggleExpand; toggleExpand = gpopup->addAction(tr("Inner-cycle Calls")); toggleExpand->setCheckable(true); toggleExpand->setChecked(_expandCycles); QAction* toggleCluster; toggleCluster = gpopup->addAction(tr("Cluster Groups")); toggleCluster->setCheckable(true); toggleCluster->setChecked(_clusterGroups); QMenu* vpopup = popup.addMenu(tr("Visualization")); QAction* layoutCompact = vpopup->addAction(tr("Compact")); layoutCompact->setCheckable(true); layoutCompact->setChecked(_detailLevel == 0); QAction* layoutNormal = vpopup->addAction(tr("Normal")); layoutNormal->setCheckable(true); layoutNormal->setChecked(_detailLevel == 1); QAction* layoutTall = vpopup->addAction(tr("Tall")); layoutTall->setCheckable(true); layoutTall->setChecked(_detailLevel == 2); addLayoutMenu(&popup); addZoomPosMenu(&popup); QAction* a = popup.exec(e->globalPos()); if (a == activateFunction) activated(f); else if (a == activateCycle) activated(cycle); else if (a == activateCall) activated(c); else if (a == stopLayout) stopRendering(); else if (a == exportAsDot) { TraceFunction* f = activeFunction(); if (!f) return; QString n; n = QFileDialog::getSaveFileName(this, tr("Export Graph As DOT file"), QString(), tr("Graphviz (*.dot)")); if (!n.isEmpty()) { GraphExporter ge(TraceItemView::data(), f, eventType(), groupType(), n); ge.setGraphOptions(this); ge.writeDot(); } } else if (a == exportAsImage) { // write current content of canvas as image to file if (!_scene) return; QString n; n = QFileDialog::getSaveFileName(this, tr("Export Graph As Image"), QString(), tr("Images (*.png *.jpg)")); if (!n.isEmpty()) { QRect r = _scene->sceneRect().toRect(); QPixmap pix(r.width(), r.height()); QPainter p(&pix); _scene->render( &p ); pix.save(n); } } else if (a == toggleSkipped) { _showSkipped = !_showSkipped; refresh(); } else if (a == toggleExpand) { _expandCycles = !_expandCycles; refresh(); } else if (a == toggleCluster) { _clusterGroups = !_clusterGroups; refresh(); } else if (a == layoutCompact) { _detailLevel = 0; refresh(); } else if (a == layoutNormal) { _detailLevel = 1; refresh(); } else if (a == layoutTall) { _detailLevel = 2; refresh(); } } CallGraphView::ZoomPosition CallGraphView::zoomPos(QString s) { if (s == QStringLiteral("TopLeft")) return TopLeft; if (s == QStringLiteral("TopRight")) return TopRight; if (s == QStringLiteral("BottomLeft")) return BottomLeft; if (s == QStringLiteral("BottomRight")) return BottomRight; if (s == QStringLiteral("Automatic")) return Auto; if (s == QStringLiteral("Hide")) return Hide; return DEFAULT_ZOOMPOS; } QString CallGraphView::zoomPosString(ZoomPosition p) { if (p == TopLeft) return QStringLiteral("TopLeft"); if (p == TopRight) return QStringLiteral("TopRight"); if (p == BottomLeft) return QStringLiteral("BottomLeft"); if (p == BottomRight) return QStringLiteral("BottomRight"); if (p == Auto) return QStringLiteral("Automatic"); if (p == Hide) return QStringLiteral("Hide"); return QString(); } void CallGraphView::restoreOptions(const QString& prefix, const QString& postfix) { ConfigGroup* g = ConfigStorage::group(prefix, postfix); _maxCallerDepth = g->value(QStringLiteral("MaxCaller"), DEFAULT_MAXCALLER).toInt(); _maxCalleeDepth = g->value(QStringLiteral("MaxCallee"), DEFAULT_MAXCALLEE).toInt(); _funcLimit = g->value(QStringLiteral("FuncLimit"), DEFAULT_FUNCLIMIT).toDouble(); _callLimit = g->value(QStringLiteral("CallLimit"), DEFAULT_CALLLIMIT).toDouble(); _showSkipped = g->value(QStringLiteral("ShowSkipped"), DEFAULT_SHOWSKIPPED).toBool(); _expandCycles = g->value(QStringLiteral("ExpandCycles"), DEFAULT_EXPANDCYCLES).toBool(); _clusterGroups = g->value(QStringLiteral("ClusterGroups"), DEFAULT_CLUSTERGROUPS).toBool(); _detailLevel = g->value(QStringLiteral("DetailLevel"), DEFAULT_DETAILLEVEL).toInt(); _layout = GraphOptions::layout(g->value(QStringLiteral("Layout"), layoutString(DEFAULT_LAYOUT)).toString()); _zoomPosition = zoomPos(g->value(QStringLiteral("ZoomPosition"), zoomPosString(DEFAULT_ZOOMPOS)).toString()); delete g; } void CallGraphView::saveOptions(const QString& prefix, const QString& postfix) { ConfigGroup* g = ConfigStorage::group(prefix + postfix); g->setValue(QStringLiteral("MaxCaller"), _maxCallerDepth, DEFAULT_MAXCALLER); g->setValue(QStringLiteral("MaxCallee"), _maxCalleeDepth, DEFAULT_MAXCALLEE); g->setValue(QStringLiteral("FuncLimit"), _funcLimit, DEFAULT_FUNCLIMIT); g->setValue(QStringLiteral("CallLimit"), _callLimit, DEFAULT_CALLLIMIT); g->setValue(QStringLiteral("ShowSkipped"), _showSkipped, DEFAULT_SHOWSKIPPED); g->setValue(QStringLiteral("ExpandCycles"), _expandCycles, DEFAULT_EXPANDCYCLES); g->setValue(QStringLiteral("ClusterGroups"), _clusterGroups, DEFAULT_CLUSTERGROUPS); g->setValue(QStringLiteral("DetailLevel"), _detailLevel, DEFAULT_DETAILLEVEL); g->setValue(QStringLiteral("Layout"), layoutString(_layout), layoutString(DEFAULT_LAYOUT)); g->setValue(QStringLiteral("ZoomPosition"), zoomPosString(_zoomPosition), zoomPosString(DEFAULT_ZOOMPOS)); delete g; } diff --git a/libviews/callgraphview.h b/libviews/callgraphview.h index 079af18..6eb3b31 100644 --- a/libviews/callgraphview.h +++ b/libviews/callgraphview.h @@ -1,686 +1,685 @@ /* This file is part of KCachegrind. Copyright (c) 2003-2016 Josef Weidendorfer KCachegrind 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, version 2. 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; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /* * Callgraph View */ #ifndef CALLGRAPHVIEW_H #define CALLGRAPHVIEW_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "treemap.h" // for DrawParams #include "tracedata.h" #include "traceitemview.h" class QProcess; class QTemporaryFile; class QIODevice; class CanvasNode; class CanvasEdge; class GraphEdge; class CallGraphView; // temporary parts of call graph to be shown class GraphNode { public: GraphNode(); TraceFunction* function() const { return _f; } void setFunction(TraceFunction* f) { _f = f; } CanvasNode* canvasNode() const { return _cn; } void setCanvasNode(CanvasNode* cn) { _cn = cn; } bool isVisible() const { return _visible; } void setVisible(bool v) { _visible = v; } void clearEdges(); void sortEdges(); void addCallee(GraphEdge*); void addCaller(GraphEdge*); void addUniqueCallee(GraphEdge*); void addUniqueCaller(GraphEdge*); void removeEdge(GraphEdge*); double calleeCostSum(); double calleeCountSum(); double callerCostSum(); double callerCountSum(); // keyboard navigation TraceCall* visibleCaller(); TraceCall* visibleCallee(); void setCallee(GraphEdge*); void setCaller(GraphEdge*); TraceFunction* nextVisible(); TraceFunction* priorVisible(); TraceCall* nextVisibleCaller(GraphEdge* = 0); TraceCall* nextVisibleCallee(GraphEdge* = 0); TraceCall* priorVisibleCaller(GraphEdge* = 0); TraceCall* priorVisibleCallee(GraphEdge* = 0); double self, incl; private: TraceFunction* _f; CanvasNode* _cn; bool _visible; QList callers, callees; // for keyboard navigation int _lastCallerIndex, _lastCalleeIndex; bool _lastFromCaller; }; class GraphEdge { public: GraphEdge(); CanvasEdge* canvasEdge() const { return _ce; } void setCanvasEdge(CanvasEdge* ce) { _ce = ce; } TraceCall* call() const { return _c; } void setCall(TraceCall* c) { _c = c; } bool isVisible() const { return _visible; } void setVisible(bool v) { _visible = v; } GraphNode* fromNode() const { return _fromNode; } GraphNode* toNode() const { return _toNode; } TraceFunction* from() const { return _from; } TraceFunction* to() const { return _to; } // has special cases for collapsed edges QString prettyName(); void setCaller(TraceFunction* f) { _from = f; } void setCallee(TraceFunction* f) { _to = f; } void setCallerNode(GraphNode* n) { _fromNode = n; } void setCalleeNode(GraphNode* n) { _toNode = n; } // keyboard navigation TraceFunction* visibleCaller(); TraceFunction* visibleCallee(); TraceCall* nextVisible(); TraceCall* priorVisible(); double cost, count; private: // we have a _c *and* _from/_to because for collapsed edges, // only _to or _from will be unequal NULL TraceCall* _c; TraceFunction * _from, * _to; GraphNode *_fromNode, *_toNode; CanvasEdge* _ce; bool _visible; // for keyboard navigation: have we last reached this edge via a caller? bool _lastFromCaller; }; typedef QMap GraphNodeMap; typedef QMap, GraphEdge> GraphEdgeMap; /* Abstract Interface for graph options */ class GraphOptions { public: enum Layout {TopDown, LeftRight, Circular}; virtual ~GraphOptions() {} virtual double funcLimit() = 0; virtual double callLimit() = 0; virtual int maxCallerDepth() = 0; virtual int maxCalleeDepth() = 0; virtual bool showSkipped() = 0; virtual bool expandCycles() = 0; virtual bool clusterGroups() = 0; virtual int detailLevel() = 0; virtual Layout layout() = 0; static QString layoutString(Layout); static Layout layout(QString); }; /* Graph Options Storage */ class StorableGraphOptions: public GraphOptions { public: StorableGraphOptions(); virtual ~StorableGraphOptions(){} // implementation of getters virtual double funcLimit() { return _funcLimit; } virtual double callLimit() { return _callLimit; } virtual int maxCallerDepth() { return _maxCallerDepth; } virtual int maxCalleeDepth() { return _maxCalleeDepth; } virtual bool showSkipped() { return _showSkipped; } virtual bool expandCycles() { return _expandCycles; } virtual bool clusterGroups() { return _clusterGroups; } virtual int detailLevel() { return _detailLevel; } virtual Layout layout() { return _layout; } // setters void setMaxCallerDepth(int d) { _maxCallerDepth = d; } void setMaxCalleeDepth(int d) { _maxCalleeDepth = d; } void setFuncLimit(double l) { _funcLimit = l; } void setCallLimit(double l) { _callLimit = l; } void setShowSkipped(bool b) { _showSkipped = b; } void setExpandCycles(bool b) { _expandCycles = b; } void setClusterGroups(bool b) { _clusterGroups = b; } void setDetailLevel(int l) { _detailLevel = l; } void setLayout(Layout l) { _layout = l; } protected: double _funcLimit, _callLimit; int _maxCallerDepth, _maxCalleeDepth; bool _showSkipped, _expandCycles, _clusterGroups; int _detailLevel; Layout _layout; }; /** * GraphExporter * * Generates a graph file for "dot" * Create an instance and */ class GraphExporter : public StorableGraphOptions { public: GraphExporter(); GraphExporter(TraceData*, TraceFunction*, EventType*, ProfileContext::Type, QString filename = QString()); virtual ~GraphExporter(); void reset(TraceData*, CostItem*, EventType*, ProfileContext::Type, QString filename = QString()); QString filename() { return _dotName; } int edgeCount() { return _edgeMap.count(); } int nodeCount() { return _nodeMap.count(); } // Set the object from which to get graph options for creation. // Default is this object itself (supply 0 for default) void setGraphOptions(GraphOptions* go = 0); // Create a subgraph with given limits/maxDepths void createGraph(); // calls createGraph before dumping of not already created void writeDot(QIODevice* = 0); // to map back to structures when parsing a layouted graph /* is a helper for node() and edge(). * Do not use the returned pointer directly, but only with * node() or edge(), because it could be a dangling pointer. */ TraceFunction* toFunc(QString); GraphNode* node(TraceFunction*); GraphEdge* edge(TraceFunction*, TraceFunction*); /* After CanvasEdges are attached to GraphEdges, we can * sort the incoming and outgoing edges of all nodes * regarding start/end points for keyboard navigation */ void sortEdges(); private: void buildGraph(TraceFunction*, int, bool, double); QString _dotName; CostItem* _item; EventType* _eventType; ProfileContext::Type _groupType; QTemporaryFile* _tmpFile; double _realFuncLimit, _realCallLimit; - int _maxDepth; bool _graphCreated; GraphOptions* _go; // optional graph attributes bool _useBox; // graph parts written to file GraphNodeMap _nodeMap; GraphEdgeMap _edgeMap; }; /** * A panner laid over a QGraphicsScene */ class PanningView : public QGraphicsView { Q_OBJECT public: explicit PanningView(QWidget * parent = 0); void setZoomRect(const QRectF& r); signals: void zoomRectMoved(qreal dx, qreal dy); void zoomRectMoveFinished(); protected: void mousePressEvent(QMouseEvent*); void mouseMoveEvent(QMouseEvent*); void mouseReleaseEvent(QMouseEvent*); void drawForeground(QPainter * p, const QRectF&); QRectF _zoomRect; bool _movingZoomRect; QPointF _lastPos; }; /* * Canvas Items: * - CanvasNode (Rectangular Area) * - CanvasEdge (Spline curve) * - CanvasEdgeLabel (Label for edges) * - CanvasEdgeArrow (Arrows at the end of the edge spline) * - CanvasFrame (Grey background blending to show active node) */ enum { CANVAS_NODE = 1122, CANVAS_EDGE, CANVAS_EDGELABEL, CANVAS_EDGEARROW, CANVAS_FRAME }; class CanvasNode : public QGraphicsRectItem, public StoredDrawParams { public: CanvasNode(CallGraphView*, GraphNode*, int, int, int, int); void updateGroup(); void setSelected(bool); void paint(QPainter*, const QStyleOptionGraphicsItem*, QWidget*); GraphNode* node() { return _node; } int type() const { return CANVAS_NODE; } private: GraphNode* _node; CallGraphView* _view; }; class CanvasEdgeLabel : public QGraphicsRectItem, public StoredDrawParams { public: CanvasEdgeLabel(CallGraphView*, CanvasEdge*, int, int, int, int); void paint(QPainter*, const QStyleOptionGraphicsItem*, QWidget*); CanvasEdge* canvasEdge() { return _ce; } int type() const { return CANVAS_EDGELABEL; } double percentage() const { return _percentage; } private: CanvasEdge* _ce; CallGraphView* _view; double _percentage; }; class CanvasEdgeArrow : public QGraphicsPolygonItem { public: explicit CanvasEdgeArrow(CanvasEdge*); void paint(QPainter*, const QStyleOptionGraphicsItem*, QWidget*); CanvasEdge* canvasEdge() { return _ce; } int type() const { return CANVAS_EDGEARROW; } private: CanvasEdge* _ce; }; class CanvasEdge : public QGraphicsPathItem { public: explicit CanvasEdge(GraphEdge*); void paint(QPainter*, const QStyleOptionGraphicsItem*, QWidget*); void setSelected(bool); CanvasEdgeLabel* label() { return _label; } void setLabel(CanvasEdgeLabel* l); CanvasEdgeArrow* arrow() { return _arrow; } void setArrow(CanvasEdgeArrow* a); const QPolygon& controlPoints() const { return _points; } void setControlPoints(const QPolygon& a); GraphEdge* edge() { return _edge; } int type() const { return CANVAS_EDGE; } private: GraphEdge* _edge; CanvasEdgeLabel* _label; CanvasEdgeArrow* _arrow; QPolygon _points; double _thickness; }; class CanvasFrame : public QGraphicsRectItem { public: explicit CanvasFrame(CanvasNode*); int type() const { return CANVAS_FRAME; } bool hit(const QPoint&) const { return false; } void paint(QPainter*, const QStyleOptionGraphicsItem*, QWidget*); private: static QPixmap* _p; }; class CallGraphTip; /** * A QGraphicsView showing a part of the call graph * and another zoomed out CanvasView in a border acting as * a panner to select to visible part (only if needed) */ class CallGraphView : public QGraphicsView, public TraceItemView, public StorableGraphOptions { Q_OBJECT public: enum ZoomPosition {TopLeft, TopRight, BottomLeft, BottomRight, Auto, Hide}; explicit CallGraphView(TraceItemView* parentView, QWidget* parent=0, const char* name = 0); ~CallGraphView(); void restoreOptions(const QString& prefix, const QString& postfix); void saveOptions(const QString& prefix, const QString& postfix); QWidget* widget() { return this; } QString whatsThis() const; ZoomPosition zoomPos() const { return _zoomPosition; } static ZoomPosition zoomPos(QString); static QString zoomPosString(ZoomPosition); public slots: void zoomRectMoved(qreal, qreal); void zoomRectMoveFinished(); void showRenderWarning(); void showRenderError(QString); void stopRendering(); void readDotOutput(); void dotError(); void dotExited(); // context menu trigger handlers void callerDepthTriggered(QAction*); void calleeDepthTriggered(QAction*); void nodeLimitTriggered(QAction*); void callLimitTriggered(QAction*); void zoomPosTriggered(QAction*); void layoutTriggered(QAction*); protected: void resizeEvent(QResizeEvent*); void mousePressEvent(QMouseEvent*); void mouseMoveEvent(QMouseEvent*); void mouseReleaseEvent(QMouseEvent*); void mouseDoubleClickEvent(QMouseEvent*); void contextMenuEvent(QContextMenuEvent*); void keyPressEvent(QKeyEvent*); void focusInEvent(QFocusEvent*); void focusOutEvent(QFocusEvent*); void scrollContentsBy(int dx, int dy); private: void updateSizes(QSize s = QSize(0,0)); CostItem* canShow(CostItem*); void doUpdate(int, bool); void refresh(); void makeFrame(CanvasNode*, bool active); void clear(); void showText(QString); // context menu builders QAction* addCallerDepthAction(QMenu*,QString,int); QMenu* addCallerDepthMenu(QMenu*); QAction* addCalleeDepthAction(QMenu*,QString,int); QMenu* addCalleeDepthMenu(QMenu*); QAction* addNodeLimitAction(QMenu*,QString,double); QMenu* addNodeLimitMenu(QMenu*); QAction* addCallLimitAction(QMenu*,QString,double); QMenu* addCallLimitMenu(QMenu*); QAction* addZoomPosAction(QMenu*,QString,ZoomPosition); QMenu* addZoomPosMenu(QMenu*); QAction* addLayoutAction(QMenu*,QString,Layout); QMenu* addLayoutMenu(QMenu*); QGraphicsScene *_scene; int _xMargin, _yMargin; PanningView *_panningView; double _panningZoom; CallGraphTip* _tip; bool _isMoving; QPoint _lastPos; GraphExporter _exporter; GraphNode* _selectedNode; GraphEdge* _selectedEdge; // widget options ZoomPosition _zoomPosition, _lastAutoPosition; // background rendering QProcess* _renderProcess; QString _renderProcessCmdLine; QTimer _renderTimer; GraphNode* _prevSelectedNode; QPoint _prevSelectedPos; QString _unparsedOutput; }; #endif diff --git a/libviews/eventtypeitem.cpp b/libviews/eventtypeitem.cpp index 371a26c..238fb74 100644 --- a/libviews/eventtypeitem.cpp +++ b/libviews/eventtypeitem.cpp @@ -1,156 +1,155 @@ /* This file is part of KCachegrind. Copyright (c) 2003-2016 Josef Weidendorfer KCachegrind 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, version 2. 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; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /* * Items of event type view. */ #include "eventtypeitem.h" #include #include "globalconfig.h" #include "listutils.h" // EventTypeItem EventTypeItem::EventTypeItem(TraceCostItem* costItem, EventType* ct, ProfileContext::Type gt) { _costItem = costItem; _eventType = ct; _groupType = gt; setTextAlignment(1, Qt::AlignRight); setTextAlignment(2, Qt::AlignRight); setTextAlignment(3, Qt::AlignRight); setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); if (ct) { setText(0, ct->longName()); setText(3, ct->name()); setText(5, ct->parsedFormula()); - QString formula = ct->formula(); if (!ct->isReal()) { setText(4, QStringLiteral("=")); // we have a virtual type: allow editing // FIXME: How to enable this only for columns 0,3,5 ?! setFlags(flags() | Qt::ItemIsEditable); } } else { setText(0, QObject::tr("Unknown Type")); } update(); } void EventTypeItem::setGroupType(ProfileContext::Type gt) { if (_groupType == gt) return; _groupType = gt; update(); } void EventTypeItem::update() { TraceData* d = _costItem ? _costItem->data() : 0; double total = d ? ((double)d->subCost(_eventType)) : 0.0; if (total == 0.0) { setText(1, QStringLiteral("-")); setIcon(1, QIcon()); setText(2, QStringLiteral("-")); setIcon(2, QIcon()); return; } TraceFunction* f = (_costItem && _costItem->type()==ProfileContext::Function) ? (TraceFunction*)_costItem : 0; ProfileCostArray* selfTotalCost = f ? f->data() : d; if (f && GlobalConfig::showExpanded()) { ProfileCostArray* parent = 0; switch(_groupType) { case ProfileContext::Object: parent = f->object(); break; case ProfileContext::Class: parent = f->cls(); break; case ProfileContext::File: parent = f->file(); break; case ProfileContext::FunctionCycle: parent = f->cycle(); break; default: break; } if (parent) selfTotalCost = parent; } if (_costItem && _costItem->type()==ProfileContext::FunctionCycle) { f = (TraceFunction*)_costItem; selfTotalCost = f->data(); } double selfTotal = selfTotalCost->subCost(_eventType); // for all cost items there is a self cost _pure = _costItem ? _costItem->subCost(_eventType) : SubCost(0); double pure = 100.0 * _pure / selfTotal; if (GlobalConfig::showPercentage()) { setText(2, QStringLiteral("%1") .arg(pure, 0, 'f', GlobalConfig::percentPrecision())); } else if (_costItem) setText(2, _costItem->prettySubCost(_eventType)); setIcon(2, QIcon(costPixmap(_eventType, _costItem, selfTotal, false))); if (!f) { setText(1, QStringLiteral("-")); setIcon(1, QIcon()); return; } _sum = f->inclusive()->subCost(_eventType); double sum = 100.0 * _sum / total; if (GlobalConfig::showPercentage()) { setText(1, QStringLiteral("%1") .arg(sum, 0, 'f', GlobalConfig::percentPrecision())); } else setText(1, _sum.pretty()); setIcon(1, QIcon(costPixmap(_eventType, f->inclusive(), total, false))); } bool EventTypeItem::operator<(const QTreeWidgetItem &other) const { int col = treeWidget()->sortColumn(); EventTypeItem* o = (EventTypeItem*) &other; if (col==0) return _sum < o->_sum; if (col==1) return _pure < o->_pure; return QTreeWidgetItem::operator<(other); } QVariant EventTypeItem::data(int column, int role) const { if ((column == 5) && (role == Qt::EditRole)) return QVariant(_eventType->formula()); return QTreeWidgetItem::data(column, role); } diff --git a/libviews/eventtypeview.cpp b/libviews/eventtypeview.cpp index 7085120..c7f46be 100644 --- a/libviews/eventtypeview.cpp +++ b/libviews/eventtypeview.cpp @@ -1,345 +1,344 @@ /* This file is part of KCachegrind. Copyright (c) 2003-2016 Josef Weidendorfer KCachegrind 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, version 2. 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; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /* * Event Type View */ #include "eventtypeview.h" #include #include #include #include "eventtypeitem.h" #include "toplevelbase.h" // // EventTypeView // EventTypeView::EventTypeView(TraceItemView* parentView, QWidget* parent, const char* name) : QTreeWidget(parent), TraceItemView(parentView) { setObjectName(name); // forbid scaling icon pixmaps to smaller size setIconSize(QSize(99,99)); setColumnCount(6); QStringList labels; labels << tr( "Event Type" ) << tr( "Incl." ) << tr( "Self" ) << tr( "Short" ) << QString() << tr( "Formula" ); setHeaderLabels(labels); // reduce minimum width for '=' column header()->setMinimumSectionSize(10); setRootIsDecorated(false); setSortingEnabled(false); setAllColumnsShowFocus(true); setMinimumHeight(50); setContextMenuPolicy(Qt::CustomContextMenu); connect( this, &QWidget::customContextMenuRequested, this, &EventTypeView::context); // FIXME: Endless jumping among 2 types possible! connect( this, SIGNAL(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)), SLOT(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)) ); connect(this, SIGNAL(itemDoubleClicked(QTreeWidgetItem*,int)), SLOT(itemDoubleClicked(QTreeWidgetItem*,int))); connect(this, SIGNAL(itemChanged(QTreeWidgetItem*,int)), SLOT(itemChanged(QTreeWidgetItem*,int))); setWhatsThis( whatsThis() ); } QString EventTypeView::whatsThis() const { return tr( "Cost Types List" "

This list shows all cost types available " "and what the self/inclusive cost of the " "current selected function is for that cost type.

" "

By choosing a cost type from the list, " "you change the cost type of costs shown " "all over KCachegrind to be the selected one.

"); } void EventTypeView::context(const QPoint & p) { QMenu popup; QTreeWidgetItem* i = itemAt(p); EventType* ct = i ? ((EventTypeItem*) i)->eventType() : 0; QAction* selectType2Action = 0; QAction* hideType2Action = 0; if (ct) selectType2Action = popup.addAction(tr("Set as Secondary Event Type")); if (_eventType2) hideType2Action = popup.addAction(tr("Hide Secondary Event Type")); if (!popup.isEmpty()) popup.addSeparator(); QAction* editLongNameAction = 0; QAction* editShortNameAction = 0; QAction* editFormulaAction = 0; QAction* removeTypeAction = 0; if (ct && !ct->isReal()) { editLongNameAction = popup.addAction(tr("Edit Long Name")); editShortNameAction = popup.addAction(tr("Edit Short Name")); editFormulaAction = popup.addAction(tr("Edit Formula")); removeTypeAction = popup.addAction(tr("Remove")); popup.addSeparator(); } addGoMenu(&popup); QAction* newTypeAction = 0; if( _data) { popup.addSeparator(); newTypeAction = popup.addAction(tr("New Event Type...")); } QAction* a = popup.exec(viewport()->mapToGlobal(p)); if (a == hideType2Action) selectedEventType2(0); else if (a == selectType2Action) selectedEventType2(ct); else if (a == editLongNameAction) editItem(i, 0); else if (a == editShortNameAction) editItem(i, 3); else if (a == editFormulaAction) editItem(i, 5); else if (a == removeTypeAction) { // search for a previous type EventType* prev = 0, *ct = 0; EventTypeSet* m = _data->eventTypes(); for (int i=0;irealCount();i++) { ct = m->realType(i); if (ct) prev = ct; } for (int i=0;iderivedCount();i++) { ct = m->derivedType(i); if (ct == _eventType) break; if (ct) prev = ct; } if (_data->eventTypes()->remove(ct)) { // select previous cost type selectedEventType(prev); if (_eventType2 == ct) selectedEventType2(prev); refresh(); } } else if (a == newTypeAction) { int i = 1; while(1) { if (!EventType::hasKnownDerivedType(tr("New%1").arg(i))) break; i++; } // add same new cost type to this set and to known types QString shortName = tr("New%1").arg(i); QString longName = tr("New Event Type %1").arg(i); EventType* et; et = new EventType(shortName, longName); et->setFormula(QString()); // event is derived EventType::add(et); // EventType::add() took ownership, need new object et = new EventType(shortName, longName); et->setFormula(QString()); // event is derived _data->eventTypes()->add(et); refresh(); } } void EventTypeView::currentItemChanged(QTreeWidgetItem* i, QTreeWidgetItem*) { EventType* ct = i ? ((EventTypeItem*) i)->eventType() : 0; if (ct) selectedEventType(ct); } void EventTypeView::itemDoubleClicked(QTreeWidgetItem* i, int) { EventType* ct = i ? ((EventTypeItem*) i)->eventType() : 0; if (ct) selectedEventType2(ct); } CostItem* EventTypeView::canShow(CostItem* i) { if (!i) return 0; switch(i->type()) { case ProfileContext::Object: case ProfileContext::Class: case ProfileContext::File: case ProfileContext::Call: case ProfileContext::FunctionCycle: case ProfileContext::Function: break; default: return 0; } return i; } void EventTypeView::doUpdate(int changeType, bool) { // Special case ? if (changeType == selectedItemChanged) return; if (changeType == eventType2Changed) return; if (changeType == groupTypeChanged) { for(int i = 0; i < topLevelItemCount(); i++) ((EventTypeItem*)topLevelItem(i))->setGroupType(_groupType); return; } if (changeType == eventTypeChanged) { for(int i = 0; i < topLevelItemCount(); i++) { EventTypeItem* item = (EventTypeItem*)topLevelItem(i); if ( item->eventType() == _eventType) { setCurrentItem(item); scrollToItem(item); break; } } return; } if (changeType == partsChanged) { for(int i = 0; i < topLevelItemCount(); i++) ((EventTypeItem*)topLevelItem(i))->update(); resizeColumnToContents(1); resizeColumnToContents(2); return; } refresh(); } void EventTypeView::refresh() { clear(); setColumnWidth(1, 50); setColumnWidth(2, 50); if (!_data || !_activeItem) return; switch(_activeItem->type()) { case ProfileContext::Object: case ProfileContext::Class: case ProfileContext::File: case ProfileContext::FunctionCycle: case ProfileContext::Function: break; default: return; } TraceCostItem* c = (TraceCostItem*) _activeItem; EventType* ct =0; QTreeWidgetItem* item = 0; QTreeWidgetItem* selected = 0; QList items; - QString sumStr, pureStr; EventTypeSet* m = _data->eventTypes(); for (int i=0; irealCount();i++) { ct = m->realType(i); item = new EventTypeItem(c, ct, _groupType); if (ct == _eventType) selected = item; items.append(item); } for (int i=0; iderivedCount();i++) { ct = m->derivedType(i); if (!ct) continue; item = new EventTypeItem(c, ct, _groupType); if (ct == _eventType) selected = item; items.append(item); } insertTopLevelItems(0,items); if (selected) { setCurrentItem(selected); scrollToItem(selected); } for(int c = 0; c<6; c++) resizeColumnToContents(c); } void EventTypeView::itemChanged(QTreeWidgetItem* item, int c) { EventType* ct = item ? ((EventTypeItem*) item)->eventType() : 0; if (!ct || ct->isReal()) return; // search for matching known Type int knownCount = EventType::knownTypeCount(); EventType* known = 0; for (int i=0; iname() == ct->name()) break; } QString t = item->text(c); if (c == 0) { ct->setLongName(t); if (known) known->setLongName(t); } else if (c == 3) { // not allowed to use already existing short name if (EventType::hasKnownRealType(t) || EventType::hasKnownDerivedType(t)) { if (_topLevel) _topLevel->showMessage("Error: Event type name already used", 5000); } else { ct->setName(t); if (known) known->setName(t); } } else if (c == 5) { ct->setFormula(t); if (known) known->setFormula(t); } else return; if (_topLevel) _topLevel->configChanged(); refresh(); } diff --git a/libviews/globalguiconfig.cpp b/libviews/globalguiconfig.cpp index 7a5d66b..485e1db 100644 --- a/libviews/globalguiconfig.cpp +++ b/libviews/globalguiconfig.cpp @@ -1,244 +1,242 @@ /* This file is part of KCachegrind. Copyright (c) 2010-2016 Josef Weidendorfer KCachegrind 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, version 2. 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; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /* * Global configuration for GUI components of KCachegrind */ #include "globalguiconfig.h" #include "config.h" // // ConfigColorSettings // ConfigColorSetting::ConfigColorSetting(QString n) { _name = n; reset(); } ConfigColorSetting::ConfigColorSetting(QString n, QColor c) { _name = n; setColor(c); } void ConfigColorSetting::setColor(const QColor& c) { _color = c; _automatic = (c == colorForName(_name)); } QColor ConfigColorSetting::colorForName(QString n) { int h = 0, s = 100; foreach(const QChar c, n) { h = (h * 37 + s* c.unicode()) % 256; s = (s * 17 + h* c.unicode()) % 192; } return QColor::fromHsv(h, 64+s, 192); } QColor ConfigColorSetting::autoColor() const { return colorForName(_name); } void ConfigColorSetting::reset() { _automatic = true; _color = colorForName(_name); } // // GlobalGUIConfig // GlobalGUIConfig::GlobalGUIConfig() : GlobalConfig() { } GlobalGUIConfig::~GlobalGUIConfig() { qDeleteAll(_colors); _colors.clear(); } GlobalGUIConfig* GlobalGUIConfig::config() { GlobalGUIConfig* gc; if (_config == 0) { gc = new GlobalGUIConfig(); _config = gc; } else { gc = dynamic_cast(_config); if (gc == 0) qFatal("Internal error: config object is not a GlobalGUIConfig."); } return gc; } void GlobalGUIConfig::saveOptions() { // color options ConfigGroup* colorConfig = ConfigStorage::group(QStringLiteral("CostColors")); int count = 1; foreach(ConfigColorSetting* cs, _colors) { if ( !cs->_automatic ) { colorConfig->setValue( QStringLiteral("Name%1").arg(count), cs->_name); colorConfig->setValue( QStringLiteral("Color%1").arg(count), cs->_color); count++; } } colorConfig->setValue(QStringLiteral("Count"), count-1); delete colorConfig; GlobalConfig::saveOptions(); } void GlobalGUIConfig::readOptions() { int i, count; // color options _colors.clear(); // colors for default event types: // red for LL/L2 misses colorSetting(QStringLiteral("EventType-I2mr"))->_color = QColor(240, 0, 0); colorSetting(QStringLiteral("EventType-D2mr"))->_color = QColor(180,40,40); colorSetting(QStringLiteral("EventType-D2mw"))->_color = QColor(120,80,80); colorSetting(QStringLiteral("EventType-ILmr"))->_color = QColor(240, 0, 0); colorSetting(QStringLiteral("EventType-DLmr"))->_color = QColor(180,40,40); colorSetting(QStringLiteral("EventType-DLmw"))->_color = QColor(120,80,80); // green for L1 misses colorSetting(QStringLiteral("EventType-I1mr"))->_color = QColor(0, 240, 0); colorSetting(QStringLiteral("EventType-D1mr"))->_color = QColor(40,180,40); colorSetting(QStringLiteral("EventType-D1mw"))->_color = QColor(80,120,80); // yellow for branches/mispredictions colorSetting(QStringLiteral("EventType-Bc")) ->_color = QColor(240,240, 0); colorSetting(QStringLiteral("EventType-Bcm"))->_color = QColor(200,200,30); colorSetting(QStringLiteral("EventType-Bi")) ->_color = QColor(160,160,60); colorSetting(QStringLiteral("EventType-Bim"))->_color = QColor(120,120,90); // blue for normal accesses colorSetting(QStringLiteral("EventType-Ir"))->_color = QColor(0, 0, 240); colorSetting(QStringLiteral("EventType-Dr"))->_color = QColor(40,40,180); colorSetting(QStringLiteral("EventType-Dw"))->_color = QColor(80,80,120); ConfigGroup* colorConfig = ConfigStorage::group(QStringLiteral("CostColors")); count = colorConfig->value(QStringLiteral("Count"), 0).toInt(); for(i=1; i<=count; ++i) { QString n = colorConfig->value(QStringLiteral("Name%1").arg(i), QString()).toString(); QColor color = colorConfig->value(QStringLiteral("Color%1").arg(i), QColor(Qt::black)); if (n.isEmpty()) continue; ConfigColorSetting* cs = new ConfigColorSetting(n,color); _colors.insert(n, cs); } delete colorConfig; GlobalConfig::readOptions(); } ConfigColorSetting* GlobalGUIConfig::groupColorSetting(CostItem* cost) { - QString n; - if (!cost) return colorSetting(QStringLiteral("default")); return groupColorSetting(cost->type(), cost->name()); } ConfigColorSetting* GlobalGUIConfig::groupColorSetting(ProfileContext::Type t, QString name) { QString n = ProfileContext::typeName(t) + '-' + name; return colorSetting(n); } QColor GlobalGUIConfig::groupColor(CostItem* cost) { return groupColorSetting(cost)->color(); } QColor GlobalGUIConfig::eventTypeColor(EventType* t) { QString n; if (!t) n = QStringLiteral("EventType-default"); else n = QStringLiteral("EventType-%1").arg(t->name()); return colorSetting(n)->color(); } QColor GlobalGUIConfig::functionColor(ProfileContext::Type gt, TraceFunction* f) { ProfileCostArray* group = f; QString n; switch(gt) { case ProfileContext::Object: group = f->object(); break; case ProfileContext::Class: group = f->cls(); break; case ProfileContext::File: group = f->file(); break; default: break; } if (group != f) { // first look for manual color of a function in a group n = ProfileContext::typeName(group->type()) + '-' + group->name() + '-' + f->name(); ConfigColorSetting* cs = colorSetting(n, false); if (cs) return cs->color(); } return groupColor(group); } ConfigColorSetting* GlobalGUIConfig::colorSetting(const QString& n, bool createNew) { // predefined ? GlobalGUIConfig* c = config(); ConfigColorSetting* cs = c->_colors.value(n, 0); if (cs || !createNew) return cs; cs = new ConfigColorSetting(n); c->_colors.insert(n, cs); return cs; } diff --git a/libviews/instritem.h b/libviews/instritem.h index 70a1492..5475b72 100644 --- a/libviews/instritem.h +++ b/libviews/instritem.h @@ -1,103 +1,104 @@ /* This file is part of KCachegrind. Copyright (c) 2003-2016 Josef Weidendorfer KCachegrind 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, version 2. 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; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /* * Items of instruction view. */ #ifndef INSTRITEM_H #define INSTRITEM_H #include #include #include "tracedata.h" class InstrView; class InstrItem: public QTreeWidgetItem { public: // for messages InstrItem(InstrView* iv, QTreeWidget* parent, Addr addr, const QString&); // for instruction lines InstrItem(InstrView* iv, QTreeWidget* parent, Addr addr, bool inside, const QString&, const QString&, const QString&, TraceInstr* instr); // for call instr InstrItem(InstrView* iv, QTreeWidgetItem* parent, Addr addr, TraceInstr* instr, TraceInstrCall* instrCall); // for jump lines InstrItem(InstrView* iv, QTreeWidgetItem* parent, Addr addr, TraceInstr* instr, TraceInstrJump* instrJump); Addr addr() const { return _addr; } bool inside() const { return _inside; } TraceInstr* instr() const { return _instr; } TraceInstrCall* instrCall() const { return _instrCall; } TraceInstrJump* instrJump() const { return _instrJump; } TraceInstrJump* jump(int i) const { return _jump[i]; } int jumpCount() const { return _jump.size(); } bool operator< ( const QTreeWidgetItem & other ) const; void updateGroup(); void updateCost(); // arrow lines void setJumpArray(const QVector& a); private: InstrView* _view; SubCost _pure, _pure2; Addr _addr; TraceInstr* _instr; TraceInstrJump* _instrJump; TraceInstrCall* _instrCall; bool _inside; QVector _jump; }; // Delegate for drawing the arrows column class InstrItemDelegate : public QItemDelegate { + Q_OBJECT public: explicit InstrItemDelegate(InstrView *parent); void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex & index ) const; QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const; protected: void paintArrows(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const; InstrView* _parent; }; #endif // INSTRITEM_H diff --git a/libviews/sourceitem.h b/libviews/sourceitem.h index 57f2f10..14b26ec 100644 --- a/libviews/sourceitem.h +++ b/libviews/sourceitem.h @@ -1,105 +1,106 @@ /* This file is part of KCachegrind. Copyright (c) 2011-2016 Josef Weidendorfer KCachegrind 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, version 2. 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; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /* * Items of source view. */ #ifndef SOURCEITEM_H #define SOURCEITEM_H #include #include #include "tracedata.h" class SourceView; class SourceItem: public QTreeWidgetItem { public: // for source lines SourceItem(SourceView* sv, QTreeWidget* parent, int fileno, unsigned int lineno, bool inside, const QString& src, TraceLine* line = 0); // for call lines SourceItem(SourceView* sv, QTreeWidgetItem* parent, int fileno, unsigned int lineno, TraceLine* line, TraceLineCall* lineCall); // for jump lines SourceItem(SourceView* sv, QTreeWidgetItem* parent, int fileno, unsigned int lineno, TraceLine* line, TraceLineJump* lineJump); uint lineno() const { return _lineno; } int fileNumber() const { return _fileno; } bool inside() const { return _inside; } TraceLine* line() const { return _line; } TraceLineCall* lineCall() const { return _lineCall; } TraceLineJump* lineJump() const { return _lineJump; } TraceLineJump* jump(int i) const { return _jump[i]; } int jumpCount() const { return _jump.size(); } bool operator< ( const QTreeWidgetItem & other ) const; void updateGroup(); void updateCost(); // arrow lines void setJumpArray(const QVector& a); QVector _jump; private: SourceView* _view; SubCost _pure, _pure2; uint _lineno; int _fileno; // for line sorting (even with multiple files) bool _inside; TraceLine* _line; TraceLineJump* _lineJump; TraceLineCall* _lineCall; }; // Delegate for drawing the arrows column class SourceItemDelegate : public QItemDelegate { + Q_OBJECT public: explicit SourceItemDelegate(SourceView *parent); void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex & index ) const; QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const; QWidget* createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const; protected: void paintArrows(QPainter *p, const QStyleOptionViewItem &option, const QModelIndex &index) const; SourceView* _parent; }; #endif // SOURCEITEM_H diff --git a/qcachegrind/colorsettings.cpp b/qcachegrind/colorsettings.cpp index 2042794..77e8453 100644 --- a/qcachegrind/colorsettings.cpp +++ b/qcachegrind/colorsettings.cpp @@ -1,182 +1,182 @@ /* This file is part of KCachegrind. Copyright (c) 2009-2016 Josef Weidendorfer KCachegrind 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, version 2. 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; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /* * Color settings config page */ #include "colorsettings.h" #include #include #include "tracedata.h" #include "globalguiconfig.h" #include "listutils.h" // // ColorSettings // static void insertColorItems(QTreeWidget* w, ProfileContext::Type type, QStringList items) { items.sort(); - foreach(QString s, items) { + foreach(const QString &s, items) { ConfigColorSetting* cs = GlobalGUIConfig::groupColorSetting(type, s); QTreeWidgetItem* i = new QTreeWidgetItem(w); i->setText(0, ProfileContext::i18nTypeName(type)); i->setData(0, Qt::UserRole, QVariant::fromValue((void*)cs)); i->setIcon(1, QIcon(colorPixmap(20,10, cs->color()))); i->setText(1, cs->automatic() ? QObject::tr("(auto)") : QString()); i->setData(1, Qt::UserRole, cs->color()); i->setText(2, s); } } ColorSettings::ColorSettings(TraceData* data, QWidget* parent) : ConfigPage(parent, QObject::tr("Group Colors"), QObject::tr("Color Settings for Function Groups")) { ui.setupUi(this); ui.colorList->setRootIsDecorated(false); ui.colorList->setSortingEnabled(false); QStringList items; if (data) { TraceObjectMap::Iterator oit = data->objectMap().begin(); for(; oit != data->objectMap().end(); ++oit) items << (*oit).prettyName(); } if (!items.contains(TraceObject::prettyEmptyName())) items << TraceObject::prettyEmptyName(); insertColorItems(ui.colorList, ProfileContext::Object, items); items.clear(); if (data) { TraceFileMap::Iterator fit = data->fileMap().begin(); for(; fit != data->fileMap().end(); ++fit) items << (*fit).prettyName(); } if (!items.contains(TraceFile::prettyEmptyName())) items << TraceFile::prettyEmptyName(); insertColorItems(ui.colorList, ProfileContext::File, items); items.clear(); if (data) { TraceClassMap::Iterator cit = data->classMap().begin(); for(; cit != data->classMap().end(); ++cit) items << (*cit).prettyName(); } if (!items.contains(TraceClass::prettyEmptyName())) items << TraceClass::prettyEmptyName(); insertColorItems(ui.colorList, ProfileContext::Class, items); ui.colorList->setSortingEnabled(true); ui.colorList->resizeColumnToContents(0); ui.colorList->resizeColumnToContents(1); ui.colorList->resizeColumnToContents(2); connect(ui.resetButton, &QAbstractButton::clicked, this, &ColorSettings::resetClicked); connect(ui.colorList, &QTreeWidget::currentItemChanged, this, &ColorSettings::colorListItemChanged); connect(ui.colorButton, &QtColorButton::colorChanged, this, &ColorSettings::colorChanged); _current = 0; update(); } ColorSettings::~ColorSettings() {} void ColorSettings::activate(QString s) { int idx = s.toInt(); if ((idx==0) || (idx>ui.colorList->topLevelItemCount())) return; ui.colorList->setCurrentItem(ui.colorList->topLevelItem(idx-1)); ui.colorButton->setFocus(); } void ColorSettings::update() { if (!_current) { ui.resetButton->setEnabled(false); ui.colorButton->setEnabled(false); return; } ui.resetButton->setEnabled(true); ui.colorButton->setEnabled(true); QColor c = _current->data(1, Qt::UserRole).value(); ui.colorButton->setColor(c); } void ColorSettings::resetClicked() { if (!_current) return; ConfigColorSetting*cs; cs = (ConfigColorSetting*) _current->data(0, Qt::UserRole).value(); QColor c = cs->autoColor(); _current->setIcon(1, QIcon(colorPixmap(20,10, c))); _current->setData(1, Qt::UserRole, c); _current->setText(1, QObject::tr("(auto)")); ui.colorButton->setColor(c); } void ColorSettings::colorListItemChanged(QTreeWidgetItem* current, QTreeWidgetItem*) { _current = current; update(); } void ColorSettings::colorChanged(const QColor& c) { if (!_current) return; _current->setIcon(1, QIcon(colorPixmap(20,10, c))); _current->setData(1, Qt::UserRole, c); _current->setText(1, QString()); } bool ColorSettings::check(QString&, QString&) { return true; } void ColorSettings::accept() { QTreeWidgetItem* item; ConfigColorSetting* cs; QColor c; for(int i = 0; i< ui.colorList->topLevelItemCount(); i++) { item = ui.colorList->topLevelItem(i); cs = (ConfigColorSetting*) item->data(0, Qt::UserRole).value(); c = item->data(1, Qt::UserRole).value(); if (cs->color() == c) continue; cs->setColor(c); } } diff --git a/qcachegrind/configpage.h b/qcachegrind/configpage.h index 0b65b42..475757e 100644 --- a/qcachegrind/configpage.h +++ b/qcachegrind/configpage.h @@ -1,57 +1,58 @@ /* This file is part of KCachegrind. Copyright (c) 2009-2016 Josef Weidendorfer KCachegrind 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, version 2. 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; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /* * Base class for pages in configuration dialog */ #ifndef CONFIGPAGE_H #define CONFIGPAGE_H #include #include #include class ConfigPage: public QWidget { + Q_OBJECT public: ConfigPage(QWidget* parent, QString title, QString longTitle = QString()); virtual ~ConfigPage() {} QString title() { return _title; } QString longTitle() { return _longTitle; } // the default implementation focuses on widget named via virtual void activate(QString); // called on OK; prohibits closing by returning false // an error message to show and item name to navigate to can be set virtual bool check(QString& errorMsg, QString& errorItem); virtual void accept(); protected: QString inRangeError(int, int); QMap _names; private: QString _title; QString _longTitle; }; #endif // CONFIGPAGE diff --git a/qcachegrind/generalsettings.h b/qcachegrind/generalsettings.h index 6174cd2..7346ca8 100644 --- a/qcachegrind/generalsettings.h +++ b/qcachegrind/generalsettings.h @@ -1,42 +1,43 @@ /* This file is part of KCachegrind. Copyright (c) 2009-2016 Josef Weidendorfer KCachegrind 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, version 2. 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; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /* * General settings config page */ #ifndef GENERALSETTINGS_H #define GENERALSETTINGS_H #include "configpage.h" #include "ui_generalsettings.h" class GeneralSettings: public ConfigPage { + Q_OBJECT public: explicit GeneralSettings(QWidget* parent); virtual ~GeneralSettings() {} bool check(QString&, QString&); void accept(); private: Ui::GeneralSettings ui; }; #endif // GENERALSETTINGS_H diff --git a/qcachegrind/qcgtoplevel.cpp b/qcachegrind/qcgtoplevel.cpp index 1ca55bc..9d3e195 100644 --- a/qcachegrind/qcgtoplevel.cpp +++ b/qcachegrind/qcgtoplevel.cpp @@ -1,2081 +1,2079 @@ /* This file is part of KCachegrind. Copyright (c) 2002-2016 Josef Weidendorfer KCachegrind 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, version 2. 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; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /* * QCachegrind top level window */ #define TRACE_UPDATES 0 #include "qcgtoplevel.h" #include // for system() #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef QT_DBUS_SUPPORT #include #endif #include "partselection.h" #include "functionselection.h" #include "stackselection.h" #include "stackbrowser.h" #include "tracedata.h" #include "config.h" #include "globalguiconfig.h" #include "multiview.h" #include "callgraphview.h" #include "configdialog.h" QCGTopLevel::QCGTopLevel() { #ifdef QT_DBUS_SUPPORT QDBusConnection con = QDBusConnection::sessionBus(); con.registerObject("/QCachegrind", this, QDBusConnection::ExportScriptableSlots); #endif _progressBar = 0; _statusbar = statusBar(); _statusLabel = new QLabel(_statusbar); _statusbar->addWidget(_statusLabel, 1); _layoutCount = 1; _layoutCurrent = 0; resetState(); GlobalGUIConfig::config()->readOptions(); createActions(); createDocks(); createMenu(); createToolbar(); _multiView = new MultiView(this, this); _multiView->setObjectName(QStringLiteral("MultiView")); setCentralWidget(_multiView); // restore current state settings (not configuration options) restoreCurrentState(QString::null); // restore docks & toolbars from config QByteArray state, geometry; ConfigGroup* topConfig = ConfigStorage::group(QStringLiteral("TopWindow")); _forcePartDock = topConfig->value(QStringLiteral("ForcePartDockVisible"), false).toBool(); state = topConfig->value(QStringLiteral("State"), QByteArray()).toByteArray(); geometry = topConfig->value(QStringLiteral("Geometry"), QByteArray()).toByteArray(); delete topConfig; if (!geometry.isEmpty()) restoreGeometry(geometry); if (!state.isEmpty()) restoreState(state); setWindowIcon(QIcon(QStringLiteral(":/app.png"))); setAttribute(Qt::WA_DeleteOnClose); } QCGTopLevel::~QCGTopLevel() { delete _data; } // reset the visualization state, e.g. before loading new data void QCGTopLevel::resetState() { _activeParts.clear(); _hiddenParts.clear(); _data = 0; _function = 0; _eventType = 0; _eventType2 = 0; _groupType = ProfileContext::InvalidType; _group = 0; // for delayed slots _traceItemDelayed = 0; _eventTypeDelayed = 0; _eventType2Delayed = 0; _groupTypeDelayed = ProfileContext::InvalidType; _groupDelayed = 0; _directionDelayed = TraceItemView::None; _lastSender = 0; } /** * This saves the current state of the main window and * sub widgets. * * No positions are saved. These is done automatically for * KToolbar, and manually in queryExit() for QT docks. */ void QCGTopLevel::saveCurrentState(const QString& postfix) { QString eventType, eventType2; if (_eventType) eventType = _eventType->name(); if (_eventType2) eventType2 = _eventType2->name(); ConfigGroup* stateConfig = ConfigStorage::group(QStringLiteral("CurrentState") + postfix); stateConfig->setValue(QStringLiteral("EventType"), eventType); stateConfig->setValue(QStringLiteral("EventType2"), eventType2); stateConfig->setValue(QStringLiteral("GroupType"), ProfileContext::typeName(_groupType)); delete stateConfig; _partSelection->saveOptions(QStringLiteral("PartOverview"), postfix); _multiView->saveLayout(QStringLiteral("MainView"), postfix); _multiView->saveOptions(QStringLiteral("MainView"), postfix); } /** * This function is called when a trace is closed. * Save browsing position for later restoring */ void QCGTopLevel::saveTraceSettings() { QString key = traceKey(); ConfigGroup* lConfig = ConfigStorage::group(QStringLiteral("Layouts")); lConfig->setValue(QStringLiteral("Count%1").arg(key), _layoutCount); lConfig->setValue(QStringLiteral("Current%1").arg(key), _layoutCurrent); delete lConfig; ConfigGroup* pConfig = ConfigStorage::group(QStringLiteral("TracePositions")); QString eventType, eventType2; if (_eventType) eventType = _eventType->name(); if (_eventType2) eventType2 = _eventType2->name(); pConfig->setValue(QStringLiteral("EventType%1").arg(key), eventType); pConfig->setValue(QStringLiteral("EventType2%1").arg(key), eventType2); if (_groupType != ProfileContext::InvalidType) pConfig->setValue(QStringLiteral("GroupType%1").arg(key), ProfileContext::typeName(_groupType)); if (_data) { if (_group) pConfig->setValue(QStringLiteral("Group%1").arg(key), _group->name()); saveCurrentState(key); } delete pConfig; } /** * This restores the current visualization state of the main window and * of the profile views. */ void QCGTopLevel::restoreCurrentState(const QString& postfix) { _partSelection->restoreOptions(QStringLiteral("PartOverview"), postfix); _multiView->restoreLayout(QStringLiteral("MainView"), postfix); _multiView->restoreOptions(QStringLiteral("MainView"), postfix); _splittedToggleAction->setChecked(_multiView->childCount()>1); _splitDirectionToggleAction->setEnabled(_multiView->childCount()>1); _splitDirectionToggleAction->setChecked(_multiView->orientation() == Qt::Horizontal); } void QCGTopLevel::sidebarMenuAboutToShow() { QAction* action; QMenu *popup = _sidebarMenuAction->menu(); popup->clear(); action = popup->addAction(tr("Parts Overview")); action->setCheckable(true); action->setChecked(_partDock->isVisible()); connect(action, &QAction::triggered, this, &QCGTopLevel::togglePartDock); action = popup->addAction(tr("Top Cost Call Stack")); action->setCheckable(true); action->setChecked(_stackDock->isVisible()); connect(action, &QAction::triggered, this, &QCGTopLevel::toggleStackDock); action = popup->addAction(tr("Flat Profile")); action->setCheckable(true); action->setChecked(_functionDock->isVisible()); connect(action, &QAction::triggered, this, &QCGTopLevel::toggleFunctionDock); } void QCGTopLevel::recentFilesMenuAboutToShow() { QStringList recentFiles; QMenu *popup = _recentFilesMenuAction->menu(); popup->clear(); ConfigGroup* generalConfig = ConfigStorage::group(QStringLiteral("GeneralSettings")); recentFiles = generalConfig->value(QStringLiteral("RecentFiles"), QStringList()).toStringList(); delete generalConfig; if (recentFiles.count() == 0) popup->addAction(tr("(No recent files)")); else { foreach(const QString& file, recentFiles) { // paths shown to user should use OS-native separators popup->addAction(QDir::toNativeSeparators(file)); } } } void QCGTopLevel::recentFilesTriggered(QAction* action) { if (action) load(QStringList(QDir::fromNativeSeparators(action->text()))); } void QCGTopLevel::primaryAboutToShow() { updateEventTypeMenu(_primaryMenuAction->menu(), false); } void QCGTopLevel::secondaryAboutToShow() { updateEventTypeMenu(_secondaryMenuAction->menu(), true); } void QCGTopLevel::groupingAboutToShow() { if (!_functionSelection) return; _functionSelection->updateGroupingMenu(_groupingMenuAction->menu()); } void QCGTopLevel::createDocks() { // part visualization/selection side bar _partDock = new QDockWidget(this); _partDock->setObjectName(QStringLiteral("part-dock")); _partDock->setWindowTitle(tr("Parts Overview")); _partSelection = new PartSelection(this, _partDock); _partDock->setWidget(_partSelection); connect(_partSelection, &PartSelection::partsHideSelected, this, &QCGTopLevel::partsHideSelectedSlotDelayed); connect(_partSelection, &PartSelection::partsUnhideAll, this, &QCGTopLevel::partsUnhideAllSlotDelayed); // stack selection side bar _stackDock = new QDockWidget(this); _stackDock->setObjectName(QStringLiteral("stack-dock")); _stackSelection = new StackSelection(_stackDock); _stackDock->setWidget(_stackSelection); _stackDock->setWindowTitle(tr("Top Cost Call Stack")); _stackSelection->setWhatsThis( tr( "The Top Cost Call Stack" "

This is a purely fictional 'most probable' call stack. " "It is built up by starting with the current selected " "function and adds the callers/callees with highest cost " "at the top and to bottom.

" "

The Cost and Calls columns show the " "cost used for all calls from the function in the line " "above.

")); connect(_stackSelection, SIGNAL(functionSelected(CostItem*)), this, SLOT(setTraceItemDelayed(CostItem*))); // actions are already created connect(_upAction, &QAction::triggered, _stackSelection, &StackSelection::browserUp ); connect(_backAction, &QAction::triggered, _stackSelection, &StackSelection::browserBack ); connect(_forwardAction, &QAction::triggered, _stackSelection, &StackSelection::browserForward); // flat function profile side bar _functionDock = new QDockWidget(this); _functionDock->setObjectName(QStringLiteral("function-dock")); _functionDock->setWindowTitle(tr("Flat Profile")); _functionSelection = new FunctionSelection(this, _functionDock); _functionDock->setWidget(_functionSelection); // functionDock needs call to updateView() when getting visible connect(_functionDock, &QDockWidget::visibilityChanged, this, &QCGTopLevel::functionVisibilityChanged); // defaults (later to be adjusted from stored state in config) addDockWidget(Qt::LeftDockWidgetArea, _partDock ); addDockWidget(Qt::LeftDockWidgetArea, _stackDock ); addDockWidget(Qt::LeftDockWidgetArea, _functionDock ); _stackDock->hide(); _partDock->hide(); } void QCGTopLevel::createActions() { QString hint; QIcon icon; // file menu actions _newAction = new QAction(tr("&New"), this); _newAction->setShortcuts(QKeySequence::New); _newAction->setStatusTip(tr("Open new empty window")); connect(_newAction, &QAction::triggered, this, &QCGTopLevel::newWindow); icon = QApplication::style()->standardIcon(QStyle::SP_DialogOpenButton); _openAction = new QAction(icon, tr("&Open..."), this); _openAction->setShortcuts(QKeySequence::Open); _openAction->setStatusTip(tr("Open profile data file")); connect(_openAction, SIGNAL(triggered()), this, SLOT(load())); _addAction = new QAction(tr( "&Add..." ), this); _addAction->setStatusTip(tr("Add profile data to current window")); connect(_addAction, SIGNAL(triggered(bool)), SLOT(add())); _exportAction = new QAction(tr("Export Graph"), this); _exportAction->setStatusTip(tr("Generate GraphViz file 'callgraph.dot'")); connect(_exportAction, &QAction::triggered, this, &QCGTopLevel::exportGraph); _recentFilesMenuAction = new QAction(tr("Open &Recent"), this); _recentFilesMenuAction->setMenu(new QMenu(this)); connect(_recentFilesMenuAction->menu(), &QMenu::aboutToShow, this, &QCGTopLevel::recentFilesMenuAboutToShow); connect(_recentFilesMenuAction->menu(), &QMenu::triggered, this, &QCGTopLevel::recentFilesTriggered); _exitAction = new QAction(tr("E&xit"), this); _exitAction->setShortcut(tr("Ctrl+Q")); _exitAction->setStatusTip(tr("Exit the application")); connect(_exitAction, &QAction::triggered, this, &QWidget::close); // view menu actions _primaryMenuAction = new QAction(tr( "Primary Event Type" ), this ); _primaryMenuAction->setMenu(new QMenu(this)); connect(_primaryMenuAction->menu(), &QMenu::aboutToShow, this, &QCGTopLevel::primaryAboutToShow ); _secondaryMenuAction = new QAction(tr( "Secondary Event Type" ), this ); _secondaryMenuAction->setMenu(new QMenu(this)); connect(_secondaryMenuAction->menu(), &QMenu::aboutToShow, this, &QCGTopLevel::secondaryAboutToShow ); _groupingMenuAction = new QAction(tr( "Grouping" ), this ); _groupingMenuAction->setMenu(new QMenu(this)); connect(_groupingMenuAction->menu(), &QMenu::aboutToShow, this, &QCGTopLevel::groupingAboutToShow ); icon = QApplication::style()->standardIcon(QStyle::SP_BrowserReload); _cyclesToggleAction = new QAction(icon, tr("Detect Cycles"), this); _cyclesToggleAction->setCheckable(true); _cyclesToggleAction->setStatusTip(tr("Do Cycle Detection")); hint = tr("Detect recursive cycles" "

If this is switched off, the treemap drawing will show " "black areas when a recursive call is made instead of drawing " "the recursion ad infinitum. Note that " "the size of black areas often will be wrong, as inside " "recursive cycles the cost of calls cannot be determined; " "the error is small, " "however, for false cycles (see documentation).

" "

The correct handling for cycles is to detect them and " "collapse all functions of a cycle into an artificial " "function, which is done when this option is selected. " "Unfortunately, with GUI applications, this often will " "lead to huge false cycles, making the analysis impossible; " "therefore, there is the option to switch this off.

"); _cyclesToggleAction->setWhatsThis(hint); connect(_cyclesToggleAction, &QAction::triggered, this, &QCGTopLevel::toggleCycles); _cyclesToggleAction->setChecked(GlobalConfig::showCycles()); _percentageToggleAction = new QAction(QIcon(QStringLiteral(":/percent.png")), tr("Relative Cost"), this); _percentageToggleAction->setCheckable(true); _percentageToggleAction->setStatusTip(tr("Show Relative Costs")); connect(_percentageToggleAction, &QAction::triggered, this, &QCGTopLevel::togglePercentage); _percentageToggleAction->setChecked(GlobalConfig::showPercentage()); _hideTemplatesToggleAction = new QAction(QIcon(QStringLiteral(":/hidetemplates.png")), tr("Shorten Templates"), this); _hideTemplatesToggleAction->setCheckable(true); _hideTemplatesToggleAction->setStatusTip(tr("Hide Template Parameters " "in C++ Symbols")); connect(_hideTemplatesToggleAction, &QAction::triggered, this, &QCGTopLevel::toggleHideTemplates); _hideTemplatesToggleAction->setChecked(GlobalConfig::hideTemplates()); hint = tr("Hide Template Parameters in C++ Symbols" "

If this is switched on, every symbol displayed will have " "any C++ template parameters hidden, just showing <> " "instead of a potentially nested template parameter.

" "

In this mode, you can hover the mouse pointer over the " "activated symbol label to show a tooltip with the " "unabbreviated symbol.

"); _hideTemplatesToggleAction->setWhatsThis(hint); _expandedToggleAction = new QAction(QIcon(QStringLiteral(":/move.png")), tr("Relative to Parent"), this); _expandedToggleAction->setCheckable(true); _expandedToggleAction->setStatusTip( tr("Show Percentage relative to Parent")); hint = tr("Show percentage costs relative to parent" "

If this is switched off, percentage costs are always " "shown relative to the total cost of the profile part(s) " "that are currently browsed. By turning on this option, " "percentage cost of shown cost items will be relative " "to the parent cost item.

" "
    " "" "" "" "" "" "
    Cost TypeParent Cost
    Function InclusiveTotal
    Function SelfFunction Group (*)/Total
    CallFunction Inclusive
    Source LineFunction Inclusive
" "

(*) Only if function grouping is switched on " "(e.g. ELF object grouping).

"); _expandedToggleAction->setWhatsThis( hint ); connect(_expandedToggleAction, &QAction::triggered, this, &QCGTopLevel::toggleExpanded); _expandedToggleAction->setChecked(GlobalConfig::showExpanded()); _splittedToggleAction = new QAction(tr("Splitted Visualization"), this); _splittedToggleAction->setCheckable(true); _splittedToggleAction->setStatusTip( tr("Show visualization of two cost items")); connect(_splittedToggleAction, &QAction::triggered, this, &QCGTopLevel::toggleSplitted); _splitDirectionToggleAction = new QAction(tr("Split Horizontal"), this); _splitDirectionToggleAction->setCheckable(true); _splitDirectionToggleAction->setStatusTip( tr("Split visualization area horizontally")); connect(_splitDirectionToggleAction, &QAction::triggered, this, &QCGTopLevel::toggleSplitDirection); _sidebarMenuAction = new QAction(tr("Sidebars"), this); _sidebarMenuAction->setMenu(new QMenu(this)); connect( _sidebarMenuAction->menu(), &QMenu::aboutToShow, this, &QCGTopLevel::sidebarMenuAboutToShow); _layoutDup = new QAction(tr("&Duplicate"), this); connect(_layoutDup, &QAction::triggered, this, &QCGTopLevel::layoutDuplicate); _layoutDup->setShortcut(Qt::CTRL + Qt::Key_Plus); _layoutDup->setStatusTip(tr("Duplicate current layout")); _layoutRemove = new QAction(tr("&Remove"), this); connect(_layoutRemove, &QAction::triggered, this, &QCGTopLevel::layoutRemove); _layoutRemove->setStatusTip(tr("Remove current layout")); _layoutNext = new QAction(tr("Go to &Next"), this); connect(_layoutNext, &QAction::triggered, this, &QCGTopLevel::layoutNext); _layoutNext->setShortcut(Qt::CTRL + Qt::Key_Right); _layoutNext->setStatusTip(tr("Switch to next layout")); _layoutPrev = new QAction(tr("Go to &Previous"), this); connect(_layoutPrev, &QAction::triggered, this, &QCGTopLevel::layoutPrevious); _layoutPrev->setShortcut(Qt::CTRL + Qt::Key_Left); _layoutPrev->setStatusTip(tr("Switch to previous layout")); _layoutRestore = new QAction(tr("&Restore to Default"), this); connect(_layoutRestore, &QAction::triggered, this, &QCGTopLevel::layoutRestore); _layoutRestore->setStatusTip(tr("Restore layouts to default")); _layoutSave = new QAction(tr("&Save as Default"), this); connect(_layoutSave, &QAction::triggered, this, &QCGTopLevel::layoutSave); _layoutSave->setStatusTip(tr("Save layouts as default")); // go menu actions icon = QApplication::style()->standardIcon(QStyle::SP_ArrowUp); _upAction = new QAction(icon, tr( "Up" ), this ); _upAction->setShortcut( QKeySequence(Qt::ALT+Qt::Key_Up) ); _upAction->setStatusTip(tr("Go Up in Call Stack")); _upAction->setMenu(new QMenu(this)); connect(_upAction->menu(), &QMenu::aboutToShow, this, &QCGTopLevel::upAboutToShow ); connect(_upAction->menu(), &QMenu::triggered, this, &QCGTopLevel::upTriggered ); hint = tr("Go to last selected caller of current function"); _upAction->setToolTip(hint); icon = QApplication::style()->standardIcon(QStyle::SP_ArrowBack); _backAction = new QAction(icon, tr("Back"), this); _backAction->setShortcut( QKeySequence(Qt::ALT+Qt::Key_Left) ); _backAction->setStatusTip(tr("Go Back")); _backAction->setMenu(new QMenu(this)); connect(_backAction->menu(), &QMenu::aboutToShow, this, &QCGTopLevel::backAboutToShow ); connect(_backAction->menu(), &QMenu::triggered, this, &QCGTopLevel::backTriggered ); hint = tr("Go back in function selection history"); _backAction->setToolTip(hint); icon = QApplication::style()->standardIcon(QStyle::SP_ArrowForward); _forwardAction = new QAction(icon, tr("Forward"), this); _forwardAction->setShortcut( QKeySequence(Qt::ALT+Qt::Key_Right) ); _forwardAction->setStatusTip(tr("Go Forward")); _forwardAction->setMenu(new QMenu(this)); connect(_forwardAction->menu(), &QMenu::aboutToShow, this, &QCGTopLevel::forwardAboutToShow ); connect(_forwardAction->menu(), &QMenu::triggered, this, &QCGTopLevel::forwardTriggered ); hint = tr("Go forward in function selection history"); _forwardAction->setToolTip( hint ); // settings menu actions _configureAction = new QAction(tr("&Configure..."), this); _configureAction->setStatusTip(tr("Configure QCachegrind")); connect(_configureAction, SIGNAL(triggered()), this, SLOT(configure())); // help menu actions _aboutAction = new QAction(tr("&About QCachegrind..."), this); _aboutAction->setStatusTip(tr("Show the application's About box")); connect(_aboutAction, &QAction::triggered, this, &QCGTopLevel::about); _aboutQtAction = new QAction(tr("About Qt..."), this); connect(_aboutQtAction, &QAction::triggered, qApp, &QApplication::aboutQt); // toolbar actions _eventTypeBox = new QComboBox(this); _eventTypeBox->setMinimumContentsLength(25); hint = tr("Select primary event type of costs"); _eventTypeBox->setToolTip( hint ); connect( _eventTypeBox, SIGNAL(activated(const QString&)), this, SLOT(eventTypeSelected(const QString&))); } void QCGTopLevel::createMenu() { QMenuBar* mBar = menuBar(); QMenu* fileMenu = mBar->addMenu(tr("&File")); fileMenu->addAction(_newAction); fileMenu->addAction(_openAction); fileMenu->addAction(_recentFilesMenuAction); fileMenu->addAction(_addAction); fileMenu->addSeparator(); fileMenu->addAction(_exportAction); fileMenu->addSeparator(); fileMenu->addAction(_exitAction); QMenu* layoutMenu = new QMenu(tr("&Layout"), this); layoutMenu->addAction(_layoutDup); layoutMenu->addAction(_layoutRemove); layoutMenu->addSeparator(); layoutMenu->addAction(_layoutPrev); layoutMenu->addAction(_layoutNext); layoutMenu->addSeparator(); layoutMenu->addAction(_layoutSave); layoutMenu->addAction(_layoutRestore); QMenu* viewMenu = mBar->addMenu(tr("&View")); viewMenu->addAction(_primaryMenuAction); viewMenu->addAction(_secondaryMenuAction); viewMenu->addAction(_groupingMenuAction); viewMenu->addSeparator(); viewMenu->addMenu(layoutMenu); viewMenu->addAction(_splittedToggleAction); viewMenu->addAction(_splitDirectionToggleAction); viewMenu->addSeparator(); viewMenu->addAction(_cyclesToggleAction); viewMenu->addAction(_percentageToggleAction); viewMenu->addAction(_expandedToggleAction); viewMenu->addAction(_hideTemplatesToggleAction); QMenu* goMenu = mBar->addMenu(tr("&Go")); goMenu->addAction(_backAction); goMenu->addAction(_forwardAction); goMenu->addAction(_upAction); QMenu* settingsMenu = mBar->addMenu(tr("&Settings")); settingsMenu->addAction(_sidebarMenuAction); settingsMenu->addSeparator(); settingsMenu->addAction(_configureAction); QMenu* helpMenu = mBar->addMenu(tr("&Help")); helpMenu->addAction(QWhatsThis::createAction(this)); helpMenu->addSeparator(); helpMenu->addAction(_aboutAction); helpMenu->addAction(_aboutQtAction); } void QCGTopLevel::createToolbar() { QToolBar* tb = new QToolBar(tr("Main Toolbar"), this); tb->setObjectName(QStringLiteral("main-toolbar")); addToolBar(Qt::TopToolBarArea, tb); tb->addAction(_openAction); tb->addSeparator(); tb->addAction(_cyclesToggleAction); tb->addAction(_percentageToggleAction); tb->addAction(_expandedToggleAction); tb->addAction(_hideTemplatesToggleAction); tb->addSeparator(); tb->addAction(_backAction); tb->addAction(_forwardAction); tb->addAction(_upAction); tb->addSeparator(); tb->addWidget(_eventTypeBox); } void QCGTopLevel::about() { QString text, version; version = QStringLiteral("0.8.0kde"); text = QStringLiteral("

QCachegrind %1

").arg(version); text += tr("

QCachegrind is a graphical user interface for analysing " "profiling data, which helps in the performance optimization " "phase of developing a computer program. " "QCachegrind is open-source, and it is distributed under the " "terms of the GPL v2. For details and source code, see the " "homepage of the " "KCachegrind project.

" "Main author and maintainer: " "" "Josef Weidendorfer
" "(with lots of bug fixes/porting help by the KDE community)"); QMessageBox::about(this, tr("About QCachegrind"), text); } void QCGTopLevel::configure(QString s) { static QString lastPage = QString::null; // if no specific config item should be focused, use last page if (s.isEmpty()) s = lastPage; ConfigDialog d(_data, this, s); if (d.exec() == QDialog::Accepted) { GlobalConfig::config()->saveOptions(); configChanged(); } lastPage = d.currentPage(); } void QCGTopLevel::togglePartDock() { if (!_partDock->isVisible()) _partDock->show(); else _partDock->hide(); } void QCGTopLevel::toggleStackDock() { if (!_stackDock->isVisible()) _stackDock->show(); else _stackDock->hide(); } void QCGTopLevel::toggleFunctionDock() { if (!_functionDock->isVisible()) _functionDock->show(); else _functionDock->hide(); } void QCGTopLevel::togglePercentage() { setPercentage(_percentageToggleAction->isChecked()); } void QCGTopLevel::setAbsoluteCost() { setPercentage(false); } void QCGTopLevel::setRelativeCost() { setPercentage(true); } void QCGTopLevel::setPercentage(bool show) { if (GlobalConfig::showPercentage() == show) return; if (_percentageToggleAction->isChecked() != show) _percentageToggleAction->setChecked(show); _expandedToggleAction->setEnabled(show); GlobalConfig::setShowPercentage(show); _partSelection->notifyChange(TraceItemView::configChanged); _stackSelection->refresh(); _functionSelection->notifyChange(TraceItemView::configChanged); _multiView->notifyChange(TraceItemView::configChanged); } void QCGTopLevel::toggleHideTemplates() { bool show = _hideTemplatesToggleAction->isChecked(); if (GlobalConfig::hideTemplates() == show) return; GlobalConfig::setHideTemplates(show); _partSelection->notifyChange(TraceItemView::configChanged); _stackSelection->refresh(); _functionSelection->notifyChange(TraceItemView::configChanged); _multiView->notifyChange(TraceItemView::configChanged); } void QCGTopLevel::toggleExpanded() { bool show = _expandedToggleAction->isChecked(); if (GlobalConfig::showExpanded() == show) return; GlobalConfig::setShowExpanded(show); _partSelection->notifyChange(TraceItemView::configChanged); _stackSelection->refresh(); _functionSelection->notifyChange(TraceItemView::configChanged); _multiView->notifyChange(TraceItemView::configChanged); } void QCGTopLevel::toggleCycles() { bool show = _cyclesToggleAction->isChecked(); if (GlobalConfig::showCycles() == show) return; GlobalConfig::setShowCycles(show); if (!_data) return; _data->invalidateDynamicCost(); _data->updateFunctionCycles(); _partSelection->notifyChange(TraceItemView::configChanged); _stackSelection->rebuildStackList(); _functionSelection->notifyChange(TraceItemView::configChanged); _multiView->notifyChange(TraceItemView::configChanged); } void QCGTopLevel::functionVisibilityChanged(bool v) { if (v) _functionSelection->updateView(); } void QCGTopLevel::newWindow() { QCGTopLevel* t = new QCGTopLevel(); t->show(); } void QCGTopLevel::load() { QStringList files; files = QFileDialog::getOpenFileNames(this, tr("Open Callgrind Data"), _lastFile, tr("Callgrind Files (callgrind.*);;All Files (*)")); load(files); } void QCGTopLevel::load(QStringList files, bool addToRecentFiles) { if (files.isEmpty()) return; _lastFile = files[0]; if (_data && _data->parts().count()>0) { // In new window QCGTopLevel* t = new QCGTopLevel(); t->show(); t->loadDelayed(files, addToRecentFiles); return; } // this constructor enables progress bar callbacks TraceData* d = new TraceData(this); int filesLoaded = d->load(files); if (filesLoaded >0) setData(d); if (!addToRecentFiles) return; // add to recent file list in config QStringList recentFiles; ConfigGroup* generalConfig = ConfigStorage::group(QStringLiteral("GeneralSettings")); recentFiles = generalConfig->value(QStringLiteral("RecentFiles"), QStringList()).toStringList(); - foreach(QString file, files) { + foreach(const QString& file, files) { recentFiles.removeAll(file); if (filesLoaded >0) recentFiles.prepend(file); if (recentFiles.count() >5) recentFiles.removeLast(); } generalConfig->setValue(QStringLiteral("RecentFiles"), recentFiles); delete generalConfig; } void QCGTopLevel::add() { QStringList files; files = QFileDialog::getOpenFileNames(this, tr("Add Callgrind Data"), _lastFile, tr("Callgrind Files (callgrind.*);;All Files (*)")); add(files); } void QCGTopLevel::add(QStringList files) { if (files.isEmpty()) return; _lastFile = files[0]; if (_data) { _data->load(files); // GUI update for added data configChanged(); return; } // this constructor enables progress bar callbacks TraceData* d = new TraceData(this); int filesLoaded = d->load(files); if (filesLoaded >0) setData(d); } void QCGTopLevel::loadDelayed(QString file, bool addToRecentFiles) { _loadFilesDelayed << file; _addToRecentFiles = addToRecentFiles; QTimer::singleShot(0, this, &QCGTopLevel::loadFilesDelayed); } void QCGTopLevel::loadDelayed(QStringList files, bool addToRecentFiles) { _loadFilesDelayed << files; _addToRecentFiles = addToRecentFiles; QTimer::singleShot(0, this, &QCGTopLevel::loadFilesDelayed); } void QCGTopLevel::loadFilesDelayed() { if (_loadFilesDelayed.isEmpty()) return; load(_loadFilesDelayed, _addToRecentFiles); _loadFilesDelayed.clear(); } void QCGTopLevel::exportGraph() { if (!_data || !_function) return; QString n = QStringLiteral("callgraph.dot"); GraphExporter ge(_data, _function, _eventType, _groupType, n); ge.writeDot(); #ifdef Q_OS_UNIX // shell commands only work in UNIX QString cmd = QStringLiteral("(dot %1 -Tps > %2.ps; xdg-open %3.ps)&") .arg(n).arg(n).arg(n); if (::system(QFile::encodeName( cmd ))<0) qDebug() << "QCGTopLevel::exportGraph: can not run " << cmd; #endif } bool QCGTopLevel::setEventType(QString s) { EventType* ct; ct = (_data) ? _data->eventTypes()->type(s) : 0; // if costtype with given name not found, use first available if (!ct && _data) ct = _data->eventTypes()->type(0); return setEventType(ct); } bool QCGTopLevel::setEventType2(QString s) { EventType* ct; // Special type tr("(Hidden)") gives 0 ct = (_data) ? _data->eventTypes()->type(s) : 0; return setEventType2(ct); } void QCGTopLevel::eventTypeSelected(const QString& s) { EventType* ct; ct = (_data) ? _data->eventTypes()->typeForLong(s) : 0; setEventType(ct); } void QCGTopLevel::eventType2Selected(const QString& s) { EventType* ct; ct = (_data) ? _data->eventTypes()->typeForLong(s) : 0; setEventType2(ct); } bool QCGTopLevel::setEventType(EventType* ct) { if (_eventType == ct) return false; _eventType = ct; if (ct) { int idx = _eventTypeBox->findText(ct->longName()); if (idx >=0) _eventTypeBox->setCurrentIndex(idx); } _partSelection->setEventType(_eventType); _stackSelection->setEventType(_eventType); _functionSelection->setEventType(_eventType); _multiView->setEventType(_eventType); updateStatusBar(); return true; } bool QCGTopLevel::setEventType2(EventType* ct) { if (_eventType2 == ct) return false; _eventType2 = ct; - QString longName = ct ? ct->longName() : tr("(Hidden)"); - _partSelection->setEventType2(_eventType2); _stackSelection->setEventType2(_eventType2); _functionSelection->setEventType2(_eventType2); _multiView->setEventType2(_eventType2); updateStatusBar(); return true; } void QCGTopLevel::groupTypeSelected(int cg) { switch(cg) { case 0: setGroupType( ProfileContext::Function ); break; case 1: setGroupType( ProfileContext::Object ); break; case 2: setGroupType( ProfileContext::File ); break; case 3: setGroupType( ProfileContext::Class ); break; case 4: setGroupType( ProfileContext::FunctionCycle ); break; default: break; } } bool QCGTopLevel::setGroupType(QString s) { ProfileContext::Type gt; gt = ProfileContext::type(s); // only allow Function/Object/File/Class as grouptype switch(gt) { case ProfileContext::Object: case ProfileContext::File: case ProfileContext::Class: case ProfileContext::FunctionCycle: break; default: gt = ProfileContext::Function; } return setGroupType(gt); } bool QCGTopLevel::setGroupType(ProfileContext::Type gt) { if (_groupType == gt) return false; _groupType = gt; int idx = -1; switch(gt) { case ProfileContext::Function: idx = 0; break; case ProfileContext::Object: idx = 1; break; case ProfileContext::File: idx = 2; break; case ProfileContext::Class: idx = 3; break; case ProfileContext::FunctionCycle: idx = 4; break; default: break; } if (idx==-1) return false; #if 0 if (saGroup->currentItem() != idx) saGroup->setCurrentItem(idx); #endif _stackSelection->setGroupType(_groupType); _partSelection->set(_groupType); _functionSelection->set(_groupType); _multiView->set(_groupType); updateStatusBar(); return true; } bool QCGTopLevel::setGroup(QString s) { TraceCostItem* ci = _functionSelection->group(s); if (!ci) return false; return setGroup(ci); } bool QCGTopLevel::setGroup(TraceCostItem* g) { if (_group == g) return false; _group = g; _functionSelection->setGroup(g); updateStatusBar(); return true; } bool QCGTopLevel::setFunction(QString s) { if (!_data) return false; ProfileCostArray* f = _data->search(ProfileContext::Function, s, _eventType); if (!f) return false; return setFunction((TraceFunction*)f); } bool QCGTopLevel::setFunction(TraceFunction* f) { if (_function == f) return false; _function = f; _multiView->activate(f); _functionSelection->activate(f); _partSelection->activate(f); _stackSelection->setFunction(_function); StackBrowser* b = _stackSelection->browser(); if (b) { // do not disable up: a press forces stack-up extending... _forwardAction->setEnabled(b->canGoForward()); _backAction->setEnabled(b->canGoBack()); } #if TRACE_UPDATES qDebug("QCGTopLevel::setFunction(%s), lastSender %s", f ? f->prettyName().toAscii() : "0", _lastSender ? _lastSender->name() :"0" ); #endif return true; } /** * Delayed versions. * We always have a pair of slots: One receiver to start the * delay with a singleShot Timer. It stores the parameter into a * temporary variable. And one parameterless slot for * forwarding, using this temporary. */ void QCGTopLevel::setEventTypeDelayed(EventType* ct) { _eventTypeDelayed = ct; QTimer::singleShot (0, this, SLOT(setEventTypeDelayed())); } void QCGTopLevel::setEventType2Delayed(EventType* ct) { _eventType2Delayed = ct; QTimer::singleShot (0, this, SLOT(setEventType2Delayed())); } void QCGTopLevel::setEventTypeDelayed() { setEventType(_eventTypeDelayed); } void QCGTopLevel::setEventType2Delayed() { setEventType2(_eventType2Delayed); } void QCGTopLevel::setGroupTypeDelayed(ProfileContext::Type gt) { _groupTypeDelayed = gt; QTimer::singleShot (0, this, SLOT(setGroupTypeDelayed())); } void QCGTopLevel::setGroupTypeDelayed() { setGroupType(_groupTypeDelayed); } void QCGTopLevel::setGroupDelayed(TraceCostItem* g) { #if TRACE_UPDATES qDebug("QCGTopLevel::setGroupDelayed(%s), sender %s", g ? g->prettyName().toAscii() : "0", _lastSender ? _lastSender->name() :"0" ); #endif _groupDelayed = g; QTimer::singleShot (0, this, SLOT(setGroupDelayed())); } void QCGTopLevel::setGroupDelayed() { setGroup(_groupDelayed); } void QCGTopLevel::setDirectionDelayed(TraceItemView::Direction d) { _directionDelayed = d; QTimer::singleShot (0, this, SLOT(setDirectionDelayed())); } void QCGTopLevel::setDirectionDelayed() { switch(_directionDelayed) { case TraceItemView::Back: _stackSelection->browserBack(); break; case TraceItemView::Forward: _stackSelection->browserForward(); break; case TraceItemView::Up: { StackBrowser* b = _stackSelection ? _stackSelection->browser() : 0; HistoryItem* hi = b ? b->current() : 0; TraceFunction* f = hi ? hi->function() : 0; if (!f) break; f = hi->stack()->caller(f, false); if (f) setFunction(f); } break; default: break; } _directionDelayed = TraceItemView::None; } void QCGTopLevel::setTraceItemDelayed(CostItem* i) { // no need to select same item a 2nd time... if (_traceItemDelayed == i) return; _traceItemDelayed = i; _lastSender = sender(); qDebug() << "Selected " << (i ? i->fullName() : QStringLiteral("(none)")); #if TRACE_UPDATES qDebug("QCGTopLevel::setTraceItemDelayed(%s), sender %s", i ? i->prettyName().toAscii() : "0", _lastSender ? _lastSender->name() :"0" ); #endif QTimer::singleShot (0, this, SLOT(setTraceItemDelayed())); } void QCGTopLevel::setTraceItemDelayed() { if (!_traceItemDelayed) return; switch(_traceItemDelayed->type()) { case ProfileContext::Function: case ProfileContext::FunctionCycle: setFunction((TraceFunction*)_traceItemDelayed); break; case ProfileContext::Object: case ProfileContext::File: case ProfileContext::Class: _multiView->activate(_traceItemDelayed); break; case ProfileContext::Instr: case ProfileContext::Line: // only for multiview _multiView->activate(_traceItemDelayed); break; default: break; } _traceItemDelayed = 0; _lastSender = 0; } /** * A TraceData object cannot be viewed many times in different * toplevel windows. Thus, this toplevel window takes ownership * of the TraceData object: on closing the window or opening * another trace, the object is destroyed. */ void QCGTopLevel::setData(TraceData* data) { if (data == _data) return; _lastSender = 0; saveTraceSettings(); if (_data) { _partSelection->setData(0); _stackSelection->setData(0); _functionSelection->setData(0); _multiView->setData(0); _multiView->updateView(true); // we are the owner... delete _data; } // reset members resetState(); _data = data; // fill cost type list QStringList types; if (_data) { /* add all supported virtual types */ EventTypeSet* m = _data->eventTypes(); m->addKnownDerivedTypes(); /* first, fill selection list with available cost types */ for (int i=0;irealCount();i++) types << m->realType(i)->longName(); for (int i=0;iderivedCount();i++) types << m->derivedType(i)->longName(); } _eventTypes = types; _eventTypeBox->addItems(types); _stackSelection->setData(_data); _partSelection->setData(_data); _functionSelection->setData(_data); _multiView->setData(_data); // Force update of _data in all children of _multiView // This is needed to make restoring of activeItem work! _multiView->updateView(true); /* this is needed to let the other widgets know the types */ restoreTraceTypes(); restoreTraceSettings(); QString caption; if (_data) { caption = QDir::toNativeSeparators(_data->traceName()); if (!_data->command().isEmpty()) caption += " [" + _data->command() + ']'; } setWindowTitle(caption); if (!_data || (!_forcePartDock && _data->parts().count()<2)) _partDock->hide(); else _partDock->show(); updateStatusBar(); } // Clears and repopulates the given menu with dynamic items for event types. // Menu item handlers for setting the types are installed. void QCGTopLevel::updateEventTypeMenu(QMenu* m, bool secondary) { QAction* action; if (!m) return; m->clear(); if (!_data) { // no data loaded yet m->addAction(tr("(None)")); return; } if (secondary) { connect(m, SIGNAL(triggered(QAction*)), this, SLOT(setEventType2(QAction*)), Qt::UniqueConnection); if (_eventType2 != 0) { action = m->addAction(tr("Hide")); action->setData(199); m->addSeparator(); } } else { connect(m, SIGNAL(triggered(QAction*)), this, SLOT(setEventType(QAction*)), Qt::UniqueConnection); } EventTypeSet* ets = _data->eventTypes(); EventType* et; EventType* selected = secondary ? _eventType2 : _eventType; for (int i = 0; i < ets->realCount(); i++) { et = ets->realType(i); action = m->addAction(et->longName()); action->setCheckable(true); action->setData(100+i); if (et == selected) action->setChecked(true); } for (int i = 0; i < ets->derivedCount(); i++) { et = ets->derivedType(i); action = m->addAction(et->longName()); action->setCheckable(true); action->setData(200+i); if (et == selected) action->setChecked(true); } } void QCGTopLevel::addEventTypeMenu(QMenu* popup, bool withCost2) { if (_data) { QMenu* menu = popup->addMenu(tr("Primary Event Type")); updateEventTypeMenu(menu, false); if (withCost2) { QMenu* menu = popup->addMenu(tr("Secondary Event Type")); updateEventTypeMenu(menu, true); } } if (GlobalConfig::showPercentage()) popup->addAction(tr("Show Absolute Cost"), this, SLOT(setAbsoluteCost())); else popup->addAction(tr("Show Relative Cost"), this, SLOT(setRelativeCost())); } bool QCGTopLevel::setEventType(QAction* action) { if (!_data) return false; int id = action->data().toInt(0); EventTypeSet* m = _data->eventTypes(); EventType* ct=0; if (id >=100 && id<199) ct = m->realType(id-100); if (id >=200 && id<299) ct = m->derivedType(id-200); return ct ? setEventType(ct) : false; } bool QCGTopLevel::setEventType2(QAction* action) { if (!_data) return false; int id = action->data().toInt(0); EventTypeSet* m = _data->eventTypes(); EventType* ct=0; if (id >=100 && id<199) ct = m->realType(id-100); if (id >=200 && id<299) ct = m->derivedType(id-200); return setEventType2(ct); } void QCGTopLevel::addGoMenu(QMenu* popup) { StackBrowser* b = _stackSelection->browser(); if (b) { if (b->canGoBack()) popup->addAction(tr("Go Back"), this, SLOT(goBack())); if (b->canGoForward()) popup->addAction(tr("Go Forward"), this, SLOT(goForward())); } // do not disable up: a press forces stack-up extending... popup->addAction(tr("Go Up"), this, SLOT(goUp())); } void QCGTopLevel::goBack() { setDirectionDelayed(TraceItemView::Back); } void QCGTopLevel::goForward() { setDirectionDelayed(TraceItemView::Forward); } void QCGTopLevel::goUp() { setDirectionDelayed(TraceItemView::Up); } QString QCGTopLevel::traceKey() { if (!_data || _data->command().isEmpty()) return QString(); QString name = _data->command(); QString key; for (int l=0;lvalue(QStringLiteral("GroupType%1").arg(key),QString()).toString(); eventType = pConfig->value(QStringLiteral("EventType%1").arg(key),QString()).toString(); eventType2 = pConfig->value(QStringLiteral("EventType2%1").arg(key),QString()).toString(); delete pConfig; ConfigGroup* cConfig = ConfigStorage::group(QStringLiteral("CurrentState")); if (groupType.isEmpty()) groupType = cConfig->value(QStringLiteral("GroupType"),QString()).toString(); if (eventType.isEmpty()) eventType = cConfig->value(QStringLiteral("EventType"),QString()).toString(); if (eventType2.isEmpty()) eventType2 = cConfig->value(QStringLiteral("EventType2"),QString()).toString(); delete cConfig; setGroupType(groupType); setEventType(eventType); setEventType2(eventType2); // if still no event type set, use first available if (!_eventType && !_eventTypes.isEmpty()) eventTypeSelected(_eventTypes.first()); ConfigGroup* aConfig = ConfigStorage::group(QStringLiteral("Layouts")); _layoutCount = aConfig->value(QStringLiteral("Count%1").arg(key), 0).toInt(); _layoutCurrent = aConfig->value(QStringLiteral("Current%1").arg(key), 0).toInt(); delete aConfig; if (_layoutCount == 0) layoutRestore(); updateLayoutActions(); } /** * This must be called after setting group/cost types in the function * selection widget, because the group/function choosing depends on * filled lists in the function selection widget */ void QCGTopLevel::restoreTraceSettings() { if (!_data) return; QString key = traceKey(); restoreCurrentState(key); ConfigGroup* pConfig = ConfigStorage::group(QStringLiteral("TracePositions")); QString group = pConfig->value(QStringLiteral("Group%1").arg(key),QString()).toString(); delete pConfig; if (!group.isEmpty()) setGroup(group); // restoreCurrentState() usually leads to a call to setTraceItemDelayed() // to restore last active item... if (!_traceItemDelayed) { // function not available any more.. try with "main" if (!setFunction(QStringLiteral("main"))) { #if 1 _functionSelection->selectTopFunction(); #else HighestCostList hc; hc.clear(50); TraceFunctionMap::Iterator it; for ( it = _data->functionMap().begin(); it != _data->functionMap().end(); ++it ) hc.addCost(&(*it), (*it).inclusive()->subCost(_eventType)); setFunction( (TraceFunction*) hc[0]); #endif } } } /* Layout */ void QCGTopLevel::layoutDuplicate() { // save current and allocate a new slot _multiView->saveLayout(QStringLiteral("Layout%1-MainView").arg(_layoutCurrent), traceKey()); _layoutCurrent = _layoutCount; _layoutCount++; updateLayoutActions(); qDebug() << "QCGTopLevel::layoutDuplicate: count " << _layoutCount; } void QCGTopLevel::layoutRemove() { if (_layoutCount <2) return; int from = _layoutCount-1; if (_layoutCurrent == from) { _layoutCurrent--; from--; } // restore from last and decrement count _multiView->restoreLayout(QStringLiteral("Layout%1-MainView").arg(from), traceKey()); _layoutCount--; updateLayoutActions(); qDebug() << "QCGTopLevel::layoutRemove: count " << _layoutCount; } void QCGTopLevel::layoutNext() { if (_layoutCount <2) return; QString key = traceKey(); QString layoutPrefix = QStringLiteral("Layout%1-MainView"); _multiView->saveLayout(layoutPrefix.arg(_layoutCurrent), key); _layoutCurrent++; if (_layoutCurrent == _layoutCount) _layoutCurrent = 0; _multiView->restoreLayout(layoutPrefix.arg(_layoutCurrent), key); qDebug() << "QCGTopLevel::layoutNext: current " << _layoutCurrent; } void QCGTopLevel::layoutPrevious() { if (_layoutCount <2) return; QString key = traceKey(); QString layoutPrefix = QStringLiteral("Layout%1-MainView"); _multiView->saveLayout(layoutPrefix.arg(_layoutCurrent), key); _layoutCurrent--; if (_layoutCurrent <0) _layoutCurrent = _layoutCount-1; _multiView->restoreLayout(layoutPrefix.arg(_layoutCurrent), key); qDebug() << "QCGTopLevel::layoutPrevious: current " << _layoutCurrent; } void QCGTopLevel::layoutSave() { QString key = traceKey(); QString layoutPrefix = QStringLiteral("Layout%1-MainView"); _multiView->saveLayout(layoutPrefix.arg(_layoutCurrent), key); // save all layouts as defaults (ie. without any group name postfix) for(int i=0;i<_layoutCount;i++) { _multiView->restoreLayout(layoutPrefix.arg(i), key); _multiView->saveLayout(layoutPrefix.arg(i), QString()); } // restore the previously saved current layout _multiView->restoreLayout(layoutPrefix.arg(_layoutCurrent), key); ConfigGroup* layoutConfig = ConfigStorage::group(QStringLiteral("Layouts")); layoutConfig->setValue(QStringLiteral("DefaultCount"), _layoutCount); layoutConfig->setValue(QStringLiteral("DefaultCurrent"), _layoutCurrent); delete layoutConfig; } void QCGTopLevel::layoutRestore() { ConfigGroup* layoutConfig = ConfigStorage::group(QStringLiteral("Layouts")); _layoutCount = layoutConfig->value(QStringLiteral("DefaultCount"), 0).toInt(); _layoutCurrent = layoutConfig->value(QStringLiteral("DefaultCurrent"), 0).toInt(); delete layoutConfig; if (_layoutCount == 0) { _layoutCount++; return; } QString layoutPrefix = QStringLiteral("Layout%1-MainView"); _multiView->restoreLayout( layoutPrefix.arg(_layoutCurrent), traceKey()); updateLayoutActions(); } void QCGTopLevel::updateLayoutActions() { if (_layoutNext) _layoutNext->setEnabled(_layoutCount>1); if (_layoutPrev) _layoutPrev->setEnabled(_layoutCount>1); if (_layoutRemove) _layoutRemove->setEnabled(_layoutCount>1); if (_statusbar) _statusbar->showMessage(tr("Layout Count: %1").arg(_layoutCount), 1000); } void QCGTopLevel::updateStatusBar() { if (!_data || _data->parts().count()==0) { _statusLabel->setText(tr("No profile data file loaded.")); return; } QString status = QStringLiteral("%1 [%2] - ") .arg(_data->shortTraceName()) .arg(_data->activePartRange()); if (_eventType) { status += tr("Total %1 Cost: %2") .arg(_eventType->longName()) .arg(_data->prettySubCost(_eventType)); /* this gets too long... if (_eventType2 && (_eventType2 != _eventType)) status += tr(", %1 Cost: %2") .arg(_eventType2->longName()) .arg(_data->prettySubCost(_eventType2)); */ } else status += tr("No event type selected"); /* Not working... should give group of selected function if (_groupType != ProfileContext::Function) { status += QString(" - %1 '%2'") .arg(ProfileContext::trTypeName(_groupType)) .arg(_group ? _group->prettyName() : tr("(None)")); } */ _statusLabel->setText(status); } void QCGTopLevel::closeEvent(QCloseEvent* event) { GlobalConfig::config()->saveOptions(); saveTraceSettings(); saveCurrentState(QString::null); // if part dock was chosen visible even for only 1 part loaded, // keep this choice... _forcePartDock = false; if (_data && (_data->parts().count()<2) && _partDock->isVisible()) _forcePartDock=true; ConfigGroup* topConfig = ConfigStorage::group(QStringLiteral("TopWindow")); topConfig->setValue(QStringLiteral("ForcePartDockVisible"), _forcePartDock, false); topConfig->setValue(QStringLiteral("State"), saveState()); topConfig->setValue(QStringLiteral("Geometry"), saveGeometry()); delete topConfig; event->accept(); } void QCGTopLevel::toggleSplitted() { int count = _multiView->childCount(); if (count<1) count = 1; if (count>2) count = 2; count = 3-count; _multiView->setChildCount(count); _splittedToggleAction->setChecked(count>1); _splitDirectionToggleAction->setEnabled(count>1); _splitDirectionToggleAction->setChecked(_multiView->orientation() == Qt::Horizontal); } void QCGTopLevel::toggleSplitDirection() { _multiView->setOrientation( _splitDirectionToggleAction->isChecked() ? Qt::Horizontal : Qt::Vertical ); } // this is called after a config change in the dialog void QCGTopLevel::configChanged() { // invalidate found/cached dirs of source files if (_data) _data->resetSourceDirs(); _partSelection->notifyChange(TraceItemView::configChanged); _stackSelection->refresh(); _functionSelection->notifyChange(TraceItemView::configChanged); _multiView->notifyChange(TraceItemView::configChanged); } void QCGTopLevel::activePartsChangedSlot(const TracePartList& list) { if (!_data) return; if (!_data->activateParts(list)) { // qDebug("QCGTopLevel::activePartsChangedSlot: No Change!"); return; } _activeParts = list; _partSelection->set(list); _stackSelection->refresh(); _functionSelection->set(list); _multiView->set(list); updateStatusBar(); } void QCGTopLevel::partsHideSelectedSlotDelayed() { QTimer::singleShot( 0, this, &QCGTopLevel::partsHideSelectedSlot ); } // this puts selected parts into hidden list, // deselects them and makes the remaining parts selected void QCGTopLevel::partsHideSelectedSlot() { if (!_data) return; TracePartList newHidden, newActive; foreach(TracePart* part, _data->parts()) { if (_activeParts.contains(part) || _hiddenParts.contains(part)) newHidden.append(part); else newActive.append(part); } _hiddenParts = newHidden; _partSelection->hiddenPartsChangedSlot(_hiddenParts); #if 0 _mainWidget1->hiddenPartsChangedSlot(_hiddenParts); _mainWidget2->hiddenPartsChangedSlot(_hiddenParts); #endif activePartsChangedSlot(newActive); } void QCGTopLevel::partsUnhideAllSlotDelayed() { QTimer::singleShot( 0, this, &QCGTopLevel::partsUnhideAllSlot ); } // this unhides all hidden parts. Does NOT change selection void QCGTopLevel::partsUnhideAllSlot() { if (!_data) return; _hiddenParts.clear(); _partSelection->hiddenPartsChangedSlot(_hiddenParts); #if 0 _mainWidget1->hiddenPartsChangedSlot(_hiddenParts); _mainWidget2->hiddenPartsChangedSlot(_hiddenParts); #endif } void QCGTopLevel::forwardAboutToShow() { QMenu *popup = _forwardAction->menu(); popup->clear(); StackBrowser* b = _stackSelection ? _stackSelection->browser() : 0; HistoryItem* hi = b ? b->current() : 0; TraceFunction* f; QAction* action; if (!hi) { popup->addAction(tr("(No Stack)")); return; } hi = hi->next(); if (!hi) { popup->addAction(tr("(No next function)")); return; } int count = 1; while (countfunction(); if (!f) break; QString name = GlobalConfig::shortenSymbol(f->prettyName()); //qDebug("forward: Adding %s", name.toAscii()); action = popup->addAction(name); action->setData(count); hi = hi->next(); count++; } } void QCGTopLevel::backAboutToShow() { QMenu *popup = _backAction->menu(); popup->clear(); StackBrowser* b = _stackSelection ? _stackSelection->browser() : 0; HistoryItem* hi = b ? b->current() : 0; TraceFunction* f; QAction* action; if (!hi) { popup->addAction(tr("(No Stack)")); return; } hi = hi->last(); if (!hi) { popup->addAction(tr("(No previous function)")); return; } int count = 1; while (countfunction(); if (!f) break; QString name = GlobalConfig::shortenSymbol(f->prettyName()); //qDebug("back: Adding %s", name.toAscii()); action = popup->addAction(name); action->setData(count); hi = hi->last(); count++; } } void QCGTopLevel::upAboutToShow() { QMenu *popup = _upAction->menu(); popup->clear(); StackBrowser* b = _stackSelection ? _stackSelection->browser() : 0; HistoryItem* hi = b ? b->current() : 0; TraceFunction* f = hi ? hi->function() : 0; QAction* action; if (!f) { popup->addAction(tr("(No Stack)")); return; } f = hi->stack()->caller(f, false); if (!f) { popup->addAction(tr("(No Function Up)")); return; } int count = 1; while (countprettyName()); action = popup->addAction(name); action->setData(count); f = hi->stack()->caller(f, false); count++; } } void QCGTopLevel::forwardTriggered(QAction* action) { int count = action->data().toInt(0); //qDebug("forwardTriggered: %d", count); if( count <= 0) return; StackBrowser* b = _stackSelection ? _stackSelection->browser() : 0; if (!b) return; while (count>1) { b->goForward(); count--; } _stackSelection->browserForward(); } void QCGTopLevel::backTriggered(QAction* action) { int count = action->data().toInt(0); //qDebug("backTriggered: %d", count); if( count <= 0) return; StackBrowser* b = _stackSelection ? _stackSelection->browser() : 0; if (!b) return; while (count>1) { b->goBack(); count--; } _stackSelection->browserBack(); } void QCGTopLevel::upTriggered(QAction* action) { int count = action->data().toInt(0); //qDebug("upTriggered: %d", count); if( count <= 0) return; StackBrowser* b = _stackSelection ? _stackSelection->browser() : 0; HistoryItem* hi = b ? b->current() : 0; if (!hi) return; TraceFunction* f = hi->function(); while (count>0 && f) { f = hi->stack()->caller(f, false); count--; } //qDebug("upActivated: %s", f ? f->prettyName().toAscii() : "??" ); if (f) setFunction(f); } void QCGTopLevel::showMessage(const QString& msg, int ms) { if (_statusbar) _statusbar->showMessage(msg, ms); } void QCGTopLevel::showStatus(const QString& msg, int progress) { static bool msgUpdateNeeded = true; if (!_statusbar) return; if (msg.isEmpty()) { //reset status if (_progressBar) { _statusbar->removeWidget(_progressBar); delete _progressBar; _progressBar = 0; } _statusbar->clearMessage(); _progressMsg = msg; return; } if (_progressMsg.isEmpty()) _progressStart.start(); if (msg != _progressMsg) { _progressMsg = msg; msgUpdateNeeded = true; } // do nothing if last change was less than 0.5 seconds ago if (_progressStart.elapsed() < 500) return; if (!_progressBar) { _progressBar = new QProgressBar(_statusbar); _progressBar->setMaximumSize(200, _statusbar->height()-4); _statusbar->addPermanentWidget(_progressBar, 1); _progressBar->show(); msgUpdateNeeded = true; } _progressStart.restart(); if (msgUpdateNeeded) { _statusbar->showMessage(msg); msgUpdateNeeded = false; } _progressBar->setValue(progress); // let the progress bar update itself qApp->processEvents(QEventLoop::ExcludeUserInputEvents); } void QCGTopLevel::loadStart(const QString& filename) { showStatus(QStringLiteral("Loading %1").arg(filename), 0); Logger::_filename = filename; } void QCGTopLevel::loadFinished(const QString& msg) { showStatus(QString::null, 0); if (!msg.isEmpty()) showMessage(QStringLiteral("Error loading %1: %2").arg(_filename).arg(msg), 2000); } void QCGTopLevel::loadProgress(int progress) { showStatus(QStringLiteral("Loading %1").arg(_filename), progress); } void QCGTopLevel::loadError(int line, const QString& msg) { qCritical() << "Loading" << _filename << ":" << line << ": " << msg; } void QCGTopLevel::loadWarning(int line, const QString& msg) { qWarning() << "Loading" << _filename << ":" << line << ": " << msg; }