diff --git a/kcachegrind/configdlg.cpp b/kcachegrind/configdlg.cpp
index 4ada578..26e6836 100644
--- a/kcachegrind/configdlg.cpp
+++ b/kcachegrind/configdlg.cpp
@@ -1,372 +1,372 @@
/* 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
*/
#include "configdlg.h"
#include
#include
#include
#include
#include
#include "tracedata.h"
#include "globalguiconfig.h"
ConfigDlg::ConfigDlg(GlobalGUIConfig* c, TraceData* data,
QWidget* parent)
:ConfigDlgBase(parent)
{
_config = c;
_data = data;
_objectCS = 0;
_classCS = 0;
_fileCS = 0;
- connect(objectCombo, SIGNAL(activated(const QString &)),
- this, SLOT(objectActivated(const QString &)));
- connect(objectCombo, SIGNAL(editTextChanged(const QString &)),
- this, SLOT(objectActivated(const QString &)));
+ connect(objectCombo, SIGNAL(activated(QString)),
+ this, SLOT(objectActivated(QString)));
+ connect(objectCombo, SIGNAL(editTextChanged(QString)),
+ this, SLOT(objectActivated(QString)));
connect(objectCheck, &QAbstractButton::toggled,
this, &ConfigDlg::objectCheckChanged);
connect(objectColor, &KColorButton::changed,
this, &ConfigDlg::objectColorChanged);
- connect(classCombo, SIGNAL(activated(const QString &)),
- this, SLOT(classActivated(const QString &)));
- connect(classCombo, SIGNAL(editTextChanged(const QString &)),
- this, SLOT(classActivated(const QString &)));
+ connect(classCombo, SIGNAL(activated(QString)),
+ this, SLOT(classActivated(QString)));
+ connect(classCombo, SIGNAL(editTextChanged(QString)),
+ this, SLOT(classActivated(QString)));
connect(classCheck, &QAbstractButton::toggled,
this, &ConfigDlg::classCheckChanged);
connect(classColor, &KColorButton::changed,
this, &ConfigDlg::classColorChanged);
- connect(fileCombo, SIGNAL(activated(const QString &)),
- this, SLOT(fileActivated(const QString &)));
- connect(fileCombo, SIGNAL(editTextChanged(const QString &)),
- this, SLOT(fileActivated(const QString &)));
+ connect(fileCombo, SIGNAL(activated(QString)),
+ this, SLOT(fileActivated(QString)));
+ connect(fileCombo, SIGNAL(editTextChanged(QString)),
+ this, SLOT(fileActivated(QString)));
connect(fileCheck, &QAbstractButton::toggled,
this, &ConfigDlg::fileCheckChanged);
connect(fileColor, &KColorButton::changed,
this, &ConfigDlg::fileColorChanged);
connect(buttonBox, &QDialogButtonBox::accepted,this, &QDialog::accept);
connect(buttonBox, &QDialogButtonBox::rejected,this, &QDialog::reject);
QString objectPrefix = ProfileContext::typeName(ProfileContext::Object);
QString classPrefix = ProfileContext::typeName(ProfileContext::Class);
QString filePrefix = ProfileContext::typeName(ProfileContext::File);
objectCombo->setDuplicatesEnabled(false);
classCombo->setDuplicatesEnabled(false);
fileCombo->setDuplicatesEnabled(false);
objectCombo->setAutoCompletion(true);
classCombo->setAutoCompletion(true);
fileCombo->setAutoCompletion(true);
// first unspecified cost items from data
TraceObjectMap::Iterator oit;
QStringList oList;
if (data) {
for ( oit = data->objectMap().begin();
oit != data->objectMap().end(); ++oit )
oList.append((*oit).prettyName());
}
TraceClassMap::Iterator cit;
QStringList cList;
if (data) {
for ( cit = data->classMap().begin();
cit != data->classMap().end(); ++cit )
cList.append((*cit).prettyName());
}
TraceFileMap::Iterator fit;
QStringList fList;
if (data) {
for ( fit = data->fileMap().begin();
fit != data->fileMap().end(); ++fit )
fList.append((*fit).prettyName());
}
// then already defined colors (have to check for duplicates!)
foreach(ConfigColorSetting* cs, c->_colors) {
if (cs->_automatic) continue;
QString n = cs->_name;
if (n.startsWith(objectPrefix)) {
n = n.remove(0, objectPrefix.length()+1);
if (oList.indexOf(n) == -1) oList.append(n);
}
else if (n.startsWith(classPrefix)) {
n = n.remove(0, classPrefix.length()+1);
if (cList.indexOf(n) == -1) cList.append(n);
}
else if (n.startsWith(filePrefix)) {
n = n.remove(0, filePrefix.length()+1);
if (fList.indexOf(n) == -1) fList.append(n);
}
}
oList.sort();
cList.sort();
fList.sort();
objectCombo->addItems(oList);
classCombo->addItems(cList);
fileCombo->addItems(fList);
objectActivated(objectCombo->currentText());
classActivated(classCombo->currentText());
fileActivated(fileCombo->currentText());
maxListEdit->setValue(c->_maxListCount);
QTreeWidgetItem* i = new QTreeWidgetItem(dirList);
i->setText(0, i18n("(always)"));
i->setExpanded(true);
QTreeWidgetItem* root = i;
QStringList::const_iterator sit = c->_generalSourceDirs.constBegin();
for(; sit != c->_generalSourceDirs.constEnd(); ++sit ) {
QTreeWidgetItem *item = new QTreeWidgetItem(i);
item->setText(0, sit->isEmpty() ? QDir::rootPath() : *sit);
}
if (data) {
for ( oit = data->objectMap().begin();
oit != data->objectMap().end(); ++oit ) {
const QString n = (*oit).name();
if (n.isEmpty()) continue;
i = new QTreeWidgetItem(dirList);
i->setText(0, n);
i->setExpanded(true);
const QStringList dirs = c->_objectSourceDirs[n];
sit = dirs.constBegin();
for(; sit != dirs.constEnd(); ++sit ) {
QTreeWidgetItem *item = new QTreeWidgetItem(i);
item->setText(0, sit->isEmpty() ? QDir::rootPath() : *sit);
}
}
}
connect(dirList, &QTreeWidget::itemSelectionChanged,
this, &ConfigDlg::dirsItemChanged);
connect(addDirButton, &QAbstractButton::clicked,
this, &ConfigDlg::dirsAddPressed);
connect(deleteDirButton, &QAbstractButton::clicked,
this, &ConfigDlg::dirsDeletePressed);
dirList->setCurrentItem(root);
symbolCount->setValue(c->_maxSymbolCount);
symbolLength->setValue(c->_maxSymbolLength);
precisionEdit->setValue(c->_percentPrecision);
contextEdit->setValue(c->_context);
}
ConfigDlg::~ConfigDlg()
{
}
bool ConfigDlg::configure(GlobalGUIConfig* c, TraceData* d, QWidget* p)
{
ConfigDlg dlg(c, d, p);
if (dlg.exec()) {
// max 499 per definition
c->_maxListCount = dlg.maxListEdit->value();
c->_maxSymbolCount = dlg.symbolCount->value();
c->_maxSymbolLength = dlg.symbolLength->value();
c->_percentPrecision = dlg.precisionEdit->value();
c->_context = dlg.contextEdit->value();
return true;
}
return false;
}
void ConfigDlg::objectActivated(const QString & s)
{
// qDebug("objectActivated: %s", s.ascii());
if (s.isEmpty()) { _objectCS=0; return; }
QString n = ProfileContext::typeName(ProfileContext::Object) + '-' + s;
ConfigColorSetting* cs = _config->_colors[n];
if (!cs)
cs = GlobalGUIConfig::colorSetting(n);
// else
// qDebug("found color %s", n.ascii());
_objectCS = cs;
objectCheck->setChecked(cs->_automatic);
objectColor->setColor(cs->_color);
/*
qDebug("Found Color %s, automatic to %s",
_objectCS->name.ascii(),
_objectCS->automatic ? "true":"false");
*/
}
void ConfigDlg::objectCheckChanged(bool b)
{
if (_objectCS) {
_objectCS->_automatic = b;
/*
qDebug("Set Color %s automatic to %s",
_objectCS->name.ascii(),
_objectCS->automatic ? "true":"false");
*/
}
}
void ConfigDlg::objectColorChanged(const QColor & c)
{
if (_objectCS) _objectCS->_color = c;
}
void ConfigDlg::classActivated(const QString & s)
{
// qDebug("classActivated: %s", s.ascii());
if (s.isEmpty()) { _classCS=0; return; }
QString n = ProfileContext::typeName(ProfileContext::Class) + '-' + s;
ConfigColorSetting* cs = _config->_colors[n];
if (!cs)
cs = GlobalGUIConfig::colorSetting(n);
_classCS = cs;
classCheck->setChecked(cs->_automatic);
classColor->setColor(cs->_color);
}
void ConfigDlg::classCheckChanged(bool b)
{
if (_classCS) _classCS->_automatic = b;
}
void ConfigDlg::classColorChanged(const QColor & c)
{
if (_classCS) _classCS->_color = c;
}
void ConfigDlg::fileActivated(const QString & s)
{
// qDebug("fileActivated: %s", s.ascii());
if (s.isEmpty()) { _fileCS=0; return; }
QString n = ProfileContext::typeName(ProfileContext::File) + '-' + s;
ConfigColorSetting* cs = _config->_colors[n];
if (!cs)
cs = GlobalGUIConfig::colorSetting(n);
_fileCS = cs;
fileCheck->setChecked(cs->_automatic);
fileColor->setColor(cs->_color);
}
void ConfigDlg::fileCheckChanged(bool b)
{
if (_fileCS) _fileCS->_automatic = b;
}
void ConfigDlg::fileColorChanged(const QColor & c)
{
if (_fileCS) _fileCS->_color = c;
}
QTreeWidgetItem *ConfigDlg::getSelectedDirItem()
{
const QList selectedItems = dirList->selectedItems();
return selectedItems.count() ? selectedItems[0] : NULL;
}
void ConfigDlg::dirsItemChanged()
{
QTreeWidgetItem *dirItem = getSelectedDirItem();
deleteDirButton->setEnabled(dirItem && dirItem->parent() != NULL);
addDirButton->setEnabled(dirItem && dirItem->parent() == NULL);
}
void ConfigDlg::dirsDeletePressed()
{
QTreeWidgetItem *dirItem = getSelectedDirItem();
if (!dirItem || (dirItem->parent() == 0)) return;
QTreeWidgetItem* p = dirItem->parent();
if (!p) return;
QString objName = p->text(0);
QStringList* dirs;
if (objName == i18n("(always)"))
dirs = &(_config->_generalSourceDirs);
else
dirs = &(_config->_objectSourceDirs[objName]);
dirs->removeAll(dirItem->text(0));
delete dirItem;
deleteDirButton->setEnabled(false);
}
void ConfigDlg::dirsAddPressed()
{
QTreeWidgetItem *dirItem = getSelectedDirItem();
if (!dirItem || (dirItem->parent() != 0)) return;
QString objName = dirItem->text(0);
QStringList* dirs;
if (objName == i18n("(always)"))
dirs = &(_config->_generalSourceDirs);
else
dirs = &(_config->_objectSourceDirs[objName]);
QString newDir;
newDir = QFileDialog::getExistingDirectory(this,
i18n("Choose Source Folder"));
if (newDir.isEmpty()) return;
// even for '/', we strip the tailing slash
if (newDir.endsWith(QLatin1Char('/')))
newDir = newDir.left(newDir.length()-1);
if (dirs->indexOf(newDir)>=0) return;
dirs->append(newDir);
if (newDir.isEmpty()) newDir = QDir::rootPath();
QTreeWidgetItem *item = new QTreeWidgetItem(dirItem);
item->setText(0, newDir);
}
diff --git a/kcachegrind/toplevel.cpp b/kcachegrind/toplevel.cpp
index b93b9b5..f7d21c0 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
#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());
// 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("NewOpen new empty KCachegrind window.
");
action->setWhatsThis( hint );
action = actionCollection()->addAction( QStringLiteral("file_add") );
action->setText( i18n( "&Add..." ) );
- connect(action, SIGNAL(triggered(bool) ), SLOT(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&)),
+ _openRecent = KStandardAction::openRecent(this, SLOT(load(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 Type | Parent Cost |
"
"Function Cumulative | Total |
"
"Function Self | Function Group (*) / Total |
"
"Call | Function Inclusive |
"
"Source Line | Function 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&)));
+ connect( _saCost, SIGNAL(triggered(QString)),
+ this, SLOT(eventTypeSelected(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&)));
+ connect( _saCost2, SIGNAL(triggered(QString)),
+ this, SLOT(eventType2Selected(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() );
+ 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(QString::fromLocal8Bit(_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/costitem.cpp b/libcore/costitem.cpp
index 255ec03..e61f81a 100644
--- a/libcore/costitem.cpp
+++ b/libcore/costitem.cpp
@@ -1,622 +1,620 @@
/* 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.
*/
#include "costitem.h"
#include
#include "tracedata.h"
-#include
-
#define TRACE_DEBUG 0
#define TRACE_ASSERTIONS 0
//---------------------------------------------------
// ProfileCost
CostItem::CostItem(ProfileContext* c)
{
_position = 0;
_dep = 0;
_dirty = true;
_context = c;
}
CostItem::~CostItem()
{}
void CostItem::clear()
{
invalidate();
}
QString CostItem::costString(EventTypeSet*)
{
return QStringLiteral("(no cost)");
}
QString CostItem::name() const
{
if (part()) {
return QObject::tr("%1 from %2").arg(_dep->name()).arg(part()->name());
}
if (_dep)
return _dep->name();
return QObject::tr("(unknown)");
}
QString CostItem::prettyName() const
{
if (name().isEmpty()) return QObject::tr("(unknown)");
return name();
}
QString CostItem::formattedName() const
{
return QString();
}
QString CostItem::fullName() const
{
return QStringLiteral("%1 %2")
.arg(ProfileContext::typeName(type())).arg(prettyName());
}
QString CostItem::toString()
{
return QStringLiteral("%1\n [%3]").arg(fullName()).arg(costString(0));
}
void CostItem::invalidate()
{
if (_dirty) return;
_dirty = true;
if (_dep)
_dep->invalidate();
}
void CostItem::update()
{
_dirty = false;
}
TracePart* CostItem::part()
{
return _position ? _position->part() : 0;
}
const TracePart* CostItem::part() const
{
return _position ? _position->part() : 0;
}
TraceData* CostItem::data()
{
return _position ? _position->data() : 0;
}
const TraceData* CostItem::data() const
{
return _position ? _position->data() : 0;
}
//---------------------------------------------------
// ProfileCostArray
const int ProfileCostArray::MaxRealIndex = MaxRealIndexValue;
const int ProfileCostArray::InvalidIndex = -1;
ProfileCostArray::ProfileCostArray(ProfileContext* context)
: CostItem(context)
{
_cachedType = 0; // no virtual value cached
_allocCount = 0;
_count = 0;
_cost = 0;
}
ProfileCostArray::ProfileCostArray()
: CostItem(ProfileContext::context(ProfileContext::UnknownType))
{
_cachedType = 0; // no virtual value cached
_allocCount = 0;
_count = 0;
_cost = 0;
}
ProfileCostArray::~ProfileCostArray()
{
if (_cost) delete[] _cost;
}
void ProfileCostArray::clear()
{
_count = 0;
invalidate();
}
void ProfileCostArray::reserve(int count)
{
if (count <= _allocCount) return;
SubCost* newcost = new SubCost[count];
if (_cost) {
/* first _count values are valid and have to be preserved */
for(int i=0; i<_count; i++)
newcost[i] = _cost[i];
delete[] _cost;
}
_cost = newcost;
_allocCount = count;
}
void ProfileCostArray::set(EventTypeMapping* mapping, const char* s)
{
if (!mapping) return;
if (!s) {
clear();
return;
}
reserve(mapping->set()->realCount());
while(*s == ' ') s++;
if (mapping->isIdentity()) {
int i = 0;
while(icount()) {
if (!_cost[i].set(&s)) break;
i++;
}
_count = i;
}
else {
int i = 0, maxIndex = 0, index;
while(1) {
index = mapping->realIndex(i);
if (maxIndexfirstUnused(); i<=maxIndex; i=mapping->nextUnused(i))
_cost[i] = 0;
_count = maxIndex;
}
Q_ASSERT(_count <= _allocCount);
// a cost change has to be propagated (esp. in subclasses)
invalidate();
}
void ProfileCostArray::set(EventTypeMapping* mapping, FixString & s)
{
if (!mapping) return;
s.stripSpaces();
if (s.isEmpty()) {
clear();
return;
}
reserve(mapping->set()->realCount());
if (mapping->isIdentity()) {
int i = 0;
while(icount()) {
if (!s.stripUInt64(_cost[i])) break;
i++;
}
_count = i;
}
else {
int i = 0, maxIndex = 0, index;
while(1) {
index = mapping->realIndex(i);
if (maxIndexfirstUnused(); i<=maxIndex; i=mapping->nextUnused(i))
_cost[i] = 0;
_count = maxIndex+1;
}
Q_ASSERT(_count <= _allocCount);
invalidate();
}
void ProfileCostArray::addCost(EventTypeMapping* mapping, const char* s)
{
if (!mapping || !s) return;
reserve(mapping->set()->realCount());
SubCost v;
if (mapping->isIdentity()) {
int i = 0;
while(icount()) {
if (!v.set(&s)) break;
if (i<_count)
_cost[i] += v;
else
_cost[i] = v;
i++;
}
if (i > _count) _count = i;
}
else {
int i = 0, maxIndex = 0, index;
while(1) {
if (!v.set(&s)) break;
index = mapping->realIndex(i);
if (maxIndex= _count) {
/* we have to set all costs of unused indexes in the interval
* [_count;maxIndex] to zero */
for(i=mapping->nextUnused(_count-1); i<=maxIndex; i=mapping->nextUnused(i))
_cost[i] = 0;
_count = maxIndex+1;
}
}
Q_ASSERT(_count <= _allocCount);
invalidate();
#if TRACE_DEBUG
_dirty = false; // do not recurse !
qDebug("%s\n now %s", qPrintable( fullName() ),
qPrintable( ProfileCostArray::costString(0) ));
_dirty = true; // because of invalidate()
#endif
}
void ProfileCostArray::addCost(EventTypeMapping* mapping, FixString & s)
{
if (!mapping) return;
s.stripSpaces();
if (s.isEmpty()) return;
reserve(mapping->set()->realCount());
SubCost v;
if (mapping->isIdentity()) {
int i = 0;
while(icount()) {
if (!s.stripUInt64(v)) break;
if (i<_count)
_cost[i] += v;
else
_cost[i] = v;
i++;
}
if (i > _count) _count = i;
}
else {
int i = 0, maxIndex = 0, index;
while(1) {
if (!s.stripUInt64(v)) break;
index = mapping->realIndex(i);
if (maxIndex= _count) {
/* we have to set all costs of unused indexes in the interval
* [_count;maxIndex] to zero */
for(i=mapping->nextUnused(_count-1); i<=maxIndex; i=mapping->nextUnused(i))
_cost[i] = 0;
_count = maxIndex+1;
}
}
Q_ASSERT(_count <= _allocCount);
invalidate();
#if TRACE_DEBUG
_dirty = false; // do not recurse !
qDebug("%s\n now %s", qPrintable( fullName() ),
qPrintable( ProfileCostArray::costString(0 ) ) );
_dirty = true; // because of invalidate()
#endif
}
// update each subcost to be maximum of old and given costs
void ProfileCostArray::maxCost(EventTypeMapping* mapping, FixString & s)
{
if (!mapping) return;
s.stripSpaces();
if (s.isEmpty()) return;
reserve(mapping->set()->realCount());
SubCost v;
if (mapping->isIdentity()) {
int i = 0;
while(icount()) {
if (!s.stripUInt64(v)) break;
if (i<_count) {
if (v>_cost[i]) _cost[i] = v;
}
else
_cost[i] = v;
i++;
}
if (i > _count) _count = i;
}
else {
int i = 0, maxIndex = 0, index;
while(1) {
if (!s.stripUInt64(v)) break;
index = mapping->realIndex(i);
if (maxIndex_cost[index]) _cost[index] = v;
}
else
_cost[index] = v;
i++;
}
if (maxIndex >= _count) {
/* we have to set all costs of unused indexes in the interval
* [_count;maxIndex] to zero */
for(i=mapping->nextUnused(_count-1); i<=maxIndex; i=mapping->nextUnused(i))
_cost[i] = 0;
_count = maxIndex+1;
}
}
Q_ASSERT(_count <= _allocCount);
invalidate();
#if TRACE_DEBUG
_dirty = false; // do not recurse !
qDebug("%s\n now %s", qPrintable( fullName() ),
qPrintable(ProfileCostArray::costString(0)));
_dirty = true; // because of invalidate()
#endif
}
void ProfileCostArray::addCost(ProfileCostArray* item)
{
int i;
if (!item) return;
// we have to update the other item if needed
// because we access the item costs directly
if (item->_dirty) item->update();
// make sure we have enough space allocated
reserve(item->_count);
if (item->_count < _count) {
for (i = 0; i_count; ++i)
_cost[i] += item->_cost[i];
}
else {
for (i = 0; i<_count; ++i)
_cost[i] += item->_cost[i];
for (; i_count; ++i)
_cost[i] = item->_cost[i];
_count = item->_count;
}
Q_ASSERT(_count <= _allocCount);
invalidate();
#if TRACE_DEBUG
_dirty = false; // do not recurse !
qDebug("%s added cost item\n %s\n now %s",
qPrintable( fullName() ), qPrintable(item->fullName()),
qPrintable(ProfileCostArray::costString(0)));
_dirty = true; // because of invalidate()
#endif
}
void ProfileCostArray::maxCost(ProfileCostArray* item)
{
int i;
if (!item) return;
// we have to update the other item if needed
// because we access the item costs directly
if (item->_dirty) item->update();
// make sure we have enough space allocated
reserve(item->_count);
if (item->_count < _count) {
for (i = 0; i_count; ++i)
if (_cost[i] < item->_cost[i]) _cost[i] = item->_cost[i];
}
else {
for (i = 0; i<_count; ++i)
if (_cost[i] < item->_cost[i]) _cost[i] = item->_cost[i];
for (; i_count; ++i)
_cost[i] = item->_cost[i];
_count = item->_count;
}
Q_ASSERT(_count <= _allocCount);
invalidate();
#if TRACE_DEBUG
_dirty = false; // do not recurse !
qDebug("%s added cost item\n %s\n now %s",
qPrintable( fullName() ), qPrintable(item->fullName()),
qPrintable( ProfileCostArray::costString(0) ));
_dirty = true; // because of invalidate()
#endif
}
void ProfileCostArray::addCost(int realIndex, SubCost value)
{
if (realIndex<0 || realIndex>=MaxRealIndex) return;
if (value == 0) return;
reserve(realIndex+1);
if (realIndex < _count)
_cost[realIndex] += value;
else {
for(int i=_count;i=MaxRealIndex) return;
reserve(realIndex+1);
if (realIndex<_count) {
if (value>_cost[realIndex]) _cost[realIndex] = value;
}
else {
for(int i=_count;i_dirty) item->update();
int maxCount = (item->_count > _count) ? item->_count : _count;
res.reserve(maxCount);
for (int i=0; isubCost(i) - subCost(i);
res._count = maxCount;
Q_ASSERT(res._count <= res._allocCount);
return res;
}
QString ProfileCostArray::costString(EventTypeSet* set)
{
QString res;
if (_dirty) update();
int maxIndex = set ? set->realCount() : ProfileCostArray::MaxRealIndex;
for (int i = 0; itype(i)->name() + ' ';
res += subCost(i).pretty();
}
return res;
}
void ProfileCostArray::invalidate()
{
if (_dirty) return;
_dirty = true;
_cachedType = 0; // cached value is invalid, too
if (_dep)
_dep->invalidate();
}
void ProfileCostArray::update()
{
_dirty = false;
}
// this is only for real types
SubCost ProfileCostArray::subCost(int idx)
{
if (idx<0) return 0;
/* update if needed as cost could be calculated dynamically in subclasses
* this can change _count !! */
if (_dirty) update();
if (idx>=_count) return 0;
return _cost[idx];
}
SubCost ProfileCostArray::subCost(EventType* t)
{
if (!t) return 0;
if (_cachedType != t) {
_cachedType = t;
_cachedCost = t->subCost(this);
}
return _cachedCost;
}
QString ProfileCostArray::prettySubCost(EventType* t)
{
return subCost(t).pretty();
}
QString ProfileCostArray::prettySubCostPerCall(EventType* t, uint64 calls)
{
if (calls == 0) {
/* For callgrind, a call count of zero means that
* a function was already active when measuring started
* (even without that possibility, we never should crash).
* To show full cost, set to 1.
*/
calls = 1;
}
return SubCost(subCost(t) / calls).pretty();
}
diff --git a/libviews/treemap.cpp b/libviews/treemap.cpp
index af2aeb6..f31be23 100644
--- a/libviews/treemap.cpp
+++ b/libviews/treemap.cpp
@@ -1,2875 +1,2875 @@
/* 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.
*/
/*
* A Widget for visualizing hierarchical metrics as areas.
* The API is similar to QListView.
*/
#include "treemap.h"
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
// set this to 1 to enable debug output
#define DEBUG_DRAWING 0
#define MAX_FIELD 12
//
// StoredDrawParams
//
StoredDrawParams::StoredDrawParams()
{
_selected = false;
_current = false;
_shaded = true;
_rotated = false;
_drawFrame = true;
_backColor = Qt::white;
// field array has size 0
}
StoredDrawParams::StoredDrawParams(const QColor& c,
bool selected, bool current)
{
_backColor = c;
_selected = selected;
_current = current;
_shaded = true;
_rotated = false;
_drawFrame = true;
// field array has size 0
}
QString StoredDrawParams::text(int f) const
{
if ((f<0) || (f >= (int)_field.size()))
return QString();
return _field[f].text;
}
QPixmap StoredDrawParams::pixmap(int f) const
{
if ((f<0) || (f >= (int)_field.size()))
return QPixmap();
return _field[f].pix;
}
DrawParams::Position StoredDrawParams::position(int f) const
{
if ((f<0) || (f >= (int)_field.size()))
return Default;
return _field[f].pos;
}
int StoredDrawParams::maxLines(int f) const
{
if ((f<0) || (f >= (int)_field.size()))
return 0;
return _field[f].maxLines;
}
const QFont& StoredDrawParams::font() const
{
static QFont* f = 0;
if (!f) f = new QFont(QApplication::font());
return *f;
}
void StoredDrawParams::ensureField(int f)
{
if (f<0 || f>=MAX_FIELD) return;
if ((int)_field.size() < f+1) {
int oldSize = _field.size();
_field.resize(f+1);
while(oldSize < f+1) {
_field[oldSize].pos = Default;
_field[oldSize].maxLines = 0;
oldSize++;
}
}
}
void StoredDrawParams::setField(int f, const QString& t, const QPixmap& pm,
Position p, int maxLines)
{
if (f<0 || f>=MAX_FIELD) return;
ensureField(f);
_field[f].text = t;
_field[f].pix = pm;
_field[f].pos = p;
_field[f].maxLines = maxLines;
}
void StoredDrawParams::setText(int f, const QString& t)
{
if (f<0 || f>=MAX_FIELD) return;
ensureField(f);
_field[f].text = t;
}
void StoredDrawParams::setPixmap(int f, const QPixmap& pm)
{
if (f<0 || f>=MAX_FIELD) return;
ensureField(f);
_field[f].pix = pm;
}
void StoredDrawParams::setPosition(int f, Position p)
{
if (f<0 || f>=MAX_FIELD) return;
ensureField(f);
_field[f].pos = p;
}
void StoredDrawParams::setMaxLines(int f, int m)
{
if (f<0 || f>=MAX_FIELD) return;
ensureField(f);
_field[f].maxLines = m;
}
//
// RectDrawing
//
RectDrawing::RectDrawing(const QRect& r)
{
_fm = 0;
_dp = 0;
setRect(r);
}
RectDrawing::~RectDrawing()
{
delete _fm;
delete _dp;
}
DrawParams* RectDrawing::drawParams()
{
if (!_dp)
_dp = new StoredDrawParams();
return _dp;
}
void RectDrawing::setDrawParams(DrawParams* dp)
{
delete _dp;
_dp = dp;
}
void RectDrawing::setRect(const QRect& r)
{
_rect = r;
_usedTopLeft = 0;
_usedTopCenter = 0;
_usedTopRight = 0;
_usedBottomLeft = 0;
_usedBottomCenter = 0;
_usedBottomRight = 0;
_fontHeight = 0;
}
QRect RectDrawing::remainingRect(DrawParams* dp)
{
if (!dp) dp = drawParams();
if ((_usedTopLeft >0) ||
(_usedTopCenter >0) ||
(_usedTopRight >0)) {
if (dp->rotated())
_rect.setLeft(_rect.left() + _fontHeight);
else
_rect.setTop(_rect.top() + _fontHeight);
}
if ((_usedBottomLeft >0) ||
(_usedBottomCenter >0) ||
(_usedBottomRight >0)) {
if (dp->rotated())
_rect.setRight(_rect.right() - _fontHeight);
else
_rect.setBottom(_rect.bottom() - _fontHeight);
}
return _rect;
}
void RectDrawing::drawBack(QPainter* p, DrawParams* dp)
{
if (!dp) dp = drawParams();
if (_rect.width()<=0 || _rect.height()<=0) return;
QRect r = _rect;
QColor normal = dp->backColor();
if (dp->selected()) normal = normal.light();
bool isCurrent = dp->current();
if (dp->drawFrame() || isCurrent) {
// 3D raised/sunken frame effect...
QColor high = normal.light();
QColor low = normal.dark();
p->setPen( isCurrent ? low:high);
p->drawLine(r.left(), r.top(), r.right(), r.top());
p->drawLine(r.left(), r.top(), r.left(), r.bottom());
p->setPen( isCurrent ? high:low);
p->drawLine(r.right(), r.top(), r.right(), r.bottom());
p->drawLine(r.left(), r.bottom(), r.right(), r.bottom());
r.setRect(r.x()+1, r.y()+1, r.width()-2, r.height()-2);
}
if (r.width()<=0 || r.height()<=0) return;
if (dp->shaded() && (r.width()>0 && r.height()>0)) {
// adjustment for drawRect semantic in Qt4: decrement height/width
r.setRect(r.x(), r.y(), r.width()-1, r.height()-1);
// some shading
bool goDark = qGray(normal.rgb())>128;
int rBase, gBase, bBase;
normal.getRgb(&rBase, &gBase, &bBase);
p->setBrush(Qt::NoBrush);
// shade parameters:
int d = 7;
float factor = 0.1, forth=0.7, back1 =0.9, toBack2 = .7, back2 = 0.97;
// coefficient corrections because of rectangle size
int s = r.width();
if (s > r.height()) s = r.height();
if (s<100) {
forth -= .3 * (100-s)/100;
back1 -= .2 * (100-s)/100;
back2 -= .02 * (100-s)/100;
}
// maximal color difference
int rDiff = goDark ? -rBase/d : (255-rBase)/d;
int gDiff = goDark ? -gBase/d : (255-gBase)/d;
int bDiff = goDark ? -bBase/d : (255-bBase)/d;
QColor shadeColor;
while (factor<.95 && (r.width()>=0 && r.height()>=0)) {
shadeColor.setRgb((int)(rBase+factor*rDiff+.5),
(int)(gBase+factor*gDiff+.5),
(int)(bBase+factor*bDiff+.5));
p->setPen(shadeColor);
p->drawRect(r);
r.setRect(r.x()+1, r.y()+1, r.width()-2, r.height()-2);
factor = 1.0 - ((1.0 - factor) * forth);
}
// and back (1st half)
while (factor>toBack2 && (r.width()>=0 && r.height()>=0)) {
shadeColor.setRgb((int)(rBase+factor*rDiff+.5),
(int)(gBase+factor*gDiff+.5),
(int)(bBase+factor*bDiff+.5));
p->setPen(shadeColor);
p->drawRect(r);
r.setRect(r.x()+1, r.y()+1, r.width()-2, r.height()-2);
factor = 1.0 - ((1.0 - factor) / back1);
}
// and back (2nd half)
while (factor>.01 && (r.width()>=0 && r.height()>=0)) {
shadeColor.setRgb((int)(rBase+factor*rDiff+.5),
(int)(gBase+factor*gDiff+.5),
(int)(bBase+factor*bDiff+.5));
p->setPen(shadeColor);
p->drawRect(r);
r.setRect(r.x()+1, r.y()+1, r.width()-2, r.height()-2);
factor = factor * back2;
}
normal = shadeColor;
// for filling, width and height has to be incremented again
r.setRect(r.x(), r.y(), r.width()+1, r.height()+1);
}
// fill inside
p->fillRect(r, normal);
}
/* Helper for drawField
* Find a line break position in a string, given a font and maximum width
*
* Returns the actually used width, and sets
*/
static
int findBreak(int& breakPos, QString text, QFontMetrics* fm, int maxWidth)
{
int usedWidth;
// does full text fit?
breakPos = text.length();
usedWidth = fm->width(text);
if (usedWidth < maxWidth)
return usedWidth;
// binary search for best break position in [bottomPos,breakPos].
// We want the result of the binary search to be a bit too large
int bottomPos = 0;
while(1) {
int halfPos = (bottomPos + breakPos)/2;
int halfWidth = fm->width(text, halfPos);
if (halfWidth < maxWidth) {
bottomPos = halfPos+1;
continue;
}
breakPos = halfPos;
usedWidth = halfWidth;
if (breakPos - bottomPos <3) break;
}
// final position by taking break boundaries into account.
// possible break boundaries are changing char categories,
// but not middle of "Aa"
QChar::Category lastCat, cat;
int pos = breakPos;
lastCat = text[pos-1].category();
// at minimum 2 chars before break
while (pos > 2) {
pos--;
cat = text[pos-1].category();
if (cat == lastCat) continue;
// "Aa" has not a possible break inbetween
if ((cat == QChar::Letter_Uppercase) &&
(lastCat == QChar::Letter_Lowercase)) {
lastCat = cat;
continue;
}
lastCat = cat;
breakPos = pos;
usedWidth = fm->width(text, breakPos);
if (usedWidth < maxWidth) break;
}
return usedWidth;
}
/* Helper for drawField
* Find last line break position in a string from backwards,
* given a font and maximum width
*
* Returns the actually used width, and sets
*/
static
int findBreakBackwards(int& breakPos, QString text, QFontMetrics* fm, int maxWidth)
{
int usedWidth;
// does full text fit?
breakPos = 0;
usedWidth = fm->width(text);
if (usedWidth < maxWidth)
return usedWidth;
// binary search for best break position in [breakPos,topPos].
// We want the result of the binary search to be a bit too small
int topPos = text.length();
while(1) {
int halfPos = (breakPos + topPos)/2;
int halfWidth = fm->width(text.mid(halfPos));
if (halfWidth < maxWidth) {
topPos = halfPos-1;
continue;
}
breakPos = halfPos;
usedWidth = halfWidth;
if (topPos - breakPos <3) break;
}
// final position by taking break boundaries into account.
// possible break boundaries are changing char categories but not middle of "Aa"
QChar::Category lastCat, cat;
int pos = breakPos;
lastCat = text[pos].category();
// at minimum 2 chars before break
while (pos < text.length()-2) {
pos++;
cat = text[pos].category();
if (cat == lastCat) continue;
// "Aa" has not a possible break inbetween
if ((lastCat == QChar::Letter_Uppercase) &&
(cat == QChar::Letter_Lowercase)) {
lastCat = cat;
continue;
}
lastCat = cat;
breakPos = pos;
usedWidth = fm->width(text.mid(breakPos));
if (usedWidth < maxWidth) break;
}
return usedWidth;
}
bool RectDrawing::drawField(QPainter* p, int f, DrawParams* dp)
{
if (!dp) dp = drawParams();
if (!_fm) {
_fm = new QFontMetrics(dp->font());
_fontHeight = _fm->height();
}
QRect r = _rect;
if (0) qDebug() << "DrawField: Rect " << r.x() << "/" << r.y()
<< " - " << r.width() << "x" << r.height();
int h = _fontHeight;
bool rotate = dp->rotated();
int width = (rotate ? r.height() : r.width()) -4;
int height = (rotate ? r.width() : r.height());
int lines = height / h;
// stop if there is no space available
if (lines<1) return false;
// calculate free space in first line ()
int pos = dp->position(f);
if (pos == DrawParams::Default) {
switch(f%4) {
case 0: pos = DrawParams::TopLeft; break;
case 1: pos = DrawParams::TopRight; break;
case 2: pos = DrawParams::BottomRight; break;
case 3: pos = DrawParams::BottomLeft; break;
}
}
int unused = 0;
bool isBottom = false;
bool isCenter = false;
bool isRight = false;
int* used = 0;
switch(pos) {
case DrawParams::TopLeft:
used = &_usedTopLeft;
if (_usedTopLeft == 0) {
if (_usedTopCenter)
unused = (width - _usedTopCenter)/2;
else
unused = width - _usedTopRight;
}
break;
case DrawParams::TopCenter:
isCenter = true;
used = &_usedTopCenter;
if (_usedTopCenter == 0) {
if (_usedTopLeft > _usedTopRight)
unused = width - 2 * _usedTopLeft;
else
unused = width - 2 * _usedTopRight;
}
break;
case DrawParams::TopRight:
isRight = true;
used = &_usedTopRight;
if (_usedTopRight == 0) {
if (_usedTopCenter)
unused = (width - _usedTopCenter)/2;
else
unused = width - _usedTopLeft;
}
break;
case DrawParams::BottomLeft:
isBottom = true;
used = &_usedBottomLeft;
if (_usedBottomLeft == 0) {
if (_usedBottomCenter)
unused = (width - _usedBottomCenter)/2;
else
unused = width - _usedBottomRight;
}
break;
case DrawParams::BottomCenter:
isCenter = true;
isBottom = true;
used = &_usedBottomCenter;
if (_usedBottomCenter == 0) {
if (_usedBottomLeft > _usedBottomRight)
unused = width - 2 * _usedBottomLeft;
else
unused = width - 2 * _usedBottomRight;
}
break;
case DrawParams::BottomRight:
isRight = true;
isBottom = true;
used = &_usedBottomRight;
if (_usedBottomRight == 0) {
if (_usedBottomCenter)
unused = (width - _usedBottomCenter)/2;
else
unused = width - _usedBottomLeft;
}
break;
}
if (isBottom) {
if ((_usedTopLeft >0) ||
(_usedTopCenter >0) ||
(_usedTopRight >0))
lines--;
}
else if (!isBottom) {
if ((_usedBottomLeft >0) ||
(_usedBottomCenter >0) ||
(_usedBottomRight >0))
lines--;
}
if (lines<1) return false;
// keep some space between fields if part of line already used
if (unused > 0 && unused < width)
unused -= _fm->averageCharWidth();
int y = isBottom ? height - h : 0;
if (unused < 0) unused = 0;
if (unused == 0) {
// no space available in last line at this position
y = isBottom ? (y-h) : (y+h);
lines--;
if (lines<1) return false;
// new line: reset used space
if (isBottom)
_usedBottomLeft = _usedBottomCenter = _usedBottomRight = 0;
else
_usedTopLeft = _usedTopCenter = _usedTopRight = 0;
unused = width;
}
// stop as soon as possible when there is no space for "..."
static int dotW = 0;
if (!dotW) dotW = _fm->width(QStringLiteral("..."));
if (width < dotW) return false;
// get text and pixmap now, only if we need to, because it is possible
// that they are calculated on demand (and this can take some time)
QString name = dp->text(f);
if (name.isEmpty()) return 0;
QPixmap pix = dp->pixmap(f);
// check if pixmap can be drawn
int pixW = pix.width();
int pixH = pix.height();
int pixY = 0;
bool pixDrawn = true;
if (pixW>0) {
pixW += 2; // X distance from pix
if ((width < pixW + dotW) || (height < pixH)) {
// do not draw
pixW = 0;
}
else
pixDrawn = false;
}
// width of text and pixmap to be drawn
int w = pixW + _fm->width(name);
if (0) qDebug() << " For '" << name << "': Unused " << unused
<< ", StrW " << w << ", Width " << width;
// adjust available lines according to maxLines
int max = dp->maxLines(f);
if ((max > 0) && (lines>max)) lines = max;
// if we have limited space at 1st line:
// use it only if whole name does fit in last line...
if ((lines>1) && (unused < width) && (w > unused)) {
y = isBottom ? (y-h) : (y+h);
lines--;
if (lines<1) return false;
// new line: reset used space
if (isBottom)
_usedBottomLeft = _usedBottomCenter = _usedBottomRight = 0;
else
_usedTopLeft = _usedTopCenter = _usedTopRight = 0;
unused = width;
}
p->save();
p->setPen( (qGray(dp->backColor().rgb())>100) ? Qt::black : Qt::white);
p->setFont(dp->font());
if (rotate) {
//p->translate(r.x()+2, r.y()+r.height());
p->translate(r.x(), r.y()+r.height()-2);
p->rotate(270);
}
else
p->translate(r.x()+2, r.y());
/* loop over name parts to break up string depending on available width.
* every char category change is supposed a possible break,
* with the exception Uppercase=>Lowercase.
* It is good enough for numbers, Symbols...
*
* If the text is to be written at the bottom, we start with the
* end of the string (so everything is reverted)
*/
QString remaining;
int origLines = lines;
int unusedWidth = unused;
while (lines>0) {
// more than one line: search for line break
if (dp->allowBreak(f) && w>unusedWidth && lines>1) {
int breakPos;
if (!isBottom) {
w = pixW + findBreak(breakPos, name, _fm, unusedWidth - pixW);
remaining = name.mid(breakPos);
// remove space on break point
if (name[breakPos-1].category() == QChar::Separator_Space)
name = name.left(breakPos-1);
else
name = name.left(breakPos);
}
else { // bottom
w = pixW + findBreakBackwards(breakPos, name, _fm, unusedWidth - pixW);
remaining = name.left(breakPos);
// remove space on break point
if (name[breakPos].category() == QChar::Separator_Space)
name = name.mid(breakPos+1);
else
name = name.mid(breakPos);
}
}
else
remaining = QString();
/* truncate and add ... if needed */
if (dp->allowTruncation(f) && w > unusedWidth) {
name = _fm->elidedText(name, Qt::ElideRight, unusedWidth - pixW);
w = _fm->width(name) + pixW;
}
if (w > unusedWidth) {
name = QString();
w = pixW;
}
int x = 0;
if (isCenter)
x = (width - w)/2;
else if (isRight)
x = width - w;
if (!pixDrawn) {
pixY = y+(h-pixH)/2; // default: center vertically
if (pixH > h) pixY = isBottom ? y-(pixH-h) : y;
p->drawPixmap( x, pixY, pix);
// for distance to next text
pixY = isBottom ? (pixY - h - 2) : (pixY + pixH + 2);
pixDrawn = true;
}
if (0) qDebug() << " Drawing '" << name << "' at "
<< x+pixW << "/" << y;
p->drawText( x+pixW, y,
unusedWidth - pixW, h,
Qt::AlignLeft, name);
y = isBottom ? (y-h) : (y+h);
lines--;
if (remaining.isEmpty()) break;
name = remaining;
w = pixW + _fm->width(name);
unusedWidth = width;
}
// make sure the pix stays visible
if (pixDrawn && (pixY>0)) {
if (isBottom && (pixYy)) y = pixY;
}
if (origLines > lines) {
// if only 1 line written, do not reset _used* vars
if (lines - origLines >1) {
if (isBottom)
_usedBottomLeft = _usedBottomCenter = _usedBottomRight = 0;
else
_usedTopLeft = _usedTopCenter = _usedTopRight = 0;
}
// take back one line
y = isBottom ? (y+h) : (y-h);
if (used) *used = w;
}
// update free space
if (!isBottom) {
if (rotate)
_rect.setRect(r.x()+y, r.y(), r.width()-y, r.height());
else
_rect.setRect(r.x(), r.y()+y, r.width(), r.height()-y);
}
else {
if (rotate)
_rect.setRect(r.x(), r.y(), y+h, r.height());
else
_rect.setRect(r.x(), r.y(), r.width(), y+h);
}
p->restore();
return true;
}
//
// TreeMapItemList
//
TreeMapItem* TreeMapItemList::commonParent()
{
if (isEmpty()) return 0;
TreeMapItem* parent = first();
for(int i = 1; parent && icommonParent(at(i));
return parent;
}
class TreeMapItemLessThan
{
public:
bool operator()(const TreeMapItem* i1, const TreeMapItem* i2) const
{
TreeMapItem* p = i1->parent();
// should not happen
if (!p) return false;
bool ascending;
int textNo = p->sorting(&ascending);
if (textNo < 0) {
double v1 = i1->value();
double v2 = i2->value();
if (ascending)
return v1 < v2;
else
return v2 < v1;
}
QString t1 = i1->text(textNo);
QString t2 = i2->text(textNo);
if (ascending)
return t1 < t2;
else
return t2 < t1;
}
};
TreeMapItemLessThan treeMapItemLessThan;
// TreeMapItem
TreeMapItem::TreeMapItem(TreeMapItem* parent, double value)
{
_value = value;
_parent = parent;
_sum = 0;
_children = 0;
_widget = 0;
_index = -1;
_depth = -1; // not set
_unused_self = 0;
if (_parent) {
// take sorting from parent
_sortTextNo = _parent->sorting(&_sortAscending);
_parent->addItem(this);
}
else {
_sortAscending = false;
_sortTextNo = -1; // default: no sorting
}
}
TreeMapItem::TreeMapItem(TreeMapItem* parent, double value,
const QString& text1, const QString& text2,
const QString& text3, const QString& text4)
{
_value = value;
_parent = parent;
// this resizes the text vector only if needed
if (!text4.isEmpty()) setText(3, text4);
if (!text3.isEmpty()) setText(2, text3);
if (!text2.isEmpty()) setText(1, text2);
setText(0, text1);
_sum = 0;
_children = 0;
_widget = 0;
_index = -1;
_depth = -1; // not set
_unused_self = 0;
_sortAscending = false;
_sortTextNo = -1; // default: no sorting
if (_parent) _parent->addItem(this);
}
TreeMapItem::~TreeMapItem()
{
if (_children) {
qDeleteAll(*_children);
delete _children;
_children = 0;
}
// finally, notify widget about deletion
if (_widget) _widget->deletingItem(this);
}
void TreeMapItem::setParent(TreeMapItem* p)
{
_parent = p;
if (p) _widget = p->_widget;
}
bool TreeMapItem::isChildOf(TreeMapItem* item)
{
if (!item) return false;
TreeMapItem* i = this;
while (i) {
if (item == i) return true;
i = i->_parent;
}
return false;
}
TreeMapItem* TreeMapItem::commonParent(TreeMapItem* item)
{
while (item && !isChildOf(item)) {
item = item->parent();
}
return item;
}
void TreeMapItem::redraw()
{
if (_widget)
_widget->redraw(this);
}
void TreeMapItem::clear()
{
if (_children) {
// delete selected items below this item from selection
if (_widget) _widget->clearSelection(this);
qDeleteAll(*_children);
delete _children;
_children = 0;
}
}
// invalidates current children and forces redraw
// this is only useful when children are created on demand in items()
void TreeMapItem::refresh()
{
clear();
redraw();
}
QStringList TreeMapItem::path(int textNo) const
{
QStringList list(text(textNo));
TreeMapItem* i = _parent;
while (i) {
QString text = i->text(textNo);
if (!text.isEmpty())
list.prepend(i->text(textNo));
i = i->_parent;
}
return list;
}
int TreeMapItem::depth() const
{
if (_depth>0) return _depth;
if (_parent)
return _parent->depth() + 1;
return 1;
}
bool TreeMapItem::initialized()
{
if (!_children) {
_children = new TreeMapItemList;
return false;
}
return true;
}
void TreeMapItem::addItem(TreeMapItem* i)
{
if (!i) return;
if (!_children)
_children = new TreeMapItemList;
i->setParent(this);
_children->append(i); // preserve insertion order
if (sorting(0) != -1)
std::sort(_children->begin(), _children->end(), treeMapItemLessThan);
}
// default implementations of virtual functions
double TreeMapItem::value() const
{
return _value;
}
double TreeMapItem::sum() const
{
return _sum;
}
DrawParams::Position TreeMapItem::position(int f) const
{
Position p = StoredDrawParams::position(f);
if (_widget && (p == Default))
p = _widget->fieldPosition(f);
return p;
}
// use widget font
const QFont& TreeMapItem::font() const
{
return _widget->currentFont();
}
bool TreeMapItem::isMarked(int) const
{
return false;
}
int TreeMapItem::borderWidth() const
{
if (_widget)
return _widget->borderWidth();
return 2;
}
int TreeMapItem::sorting(bool* ascending) const
{
if (ascending) *ascending = _sortAscending;
return _sortTextNo;
}
// do *not* set sorting recursively
void TreeMapItem::setSorting(int textNo, bool ascending)
{
if (_sortTextNo == textNo) {
if(_sortAscending == ascending) return;
if (textNo == -1) {
// when no sorting is done, order change does not do anything
_sortAscending = ascending;
return;
}
}
_sortAscending = ascending;
_sortTextNo = textNo;
if (_children && _sortTextNo != -1)
std::sort(_children->begin(), _children->end(), treeMapItemLessThan);
}
void TreeMapItem::resort(bool recursive)
{
if (!_children) return;
if (_sortTextNo != -1)
std::sort(_children->begin(), _children->end(), treeMapItemLessThan);
if (recursive)
foreach(TreeMapItem* i, *_children)
i->resort(recursive);
}
TreeMapItem::SplitMode TreeMapItem::splitMode() const
{
if (_widget)
return _widget->splitMode();
return Best;
}
int TreeMapItem::rtti() const
{
return 0;
}
TreeMapItemList* TreeMapItem::children()
{
if (!_children)
_children = new TreeMapItemList;
return _children;
}
void TreeMapItem::clearItemRect()
{
_rect = QRect();
clearFreeRects();
}
void TreeMapItem::clearFreeRects()
{
_freeRects.clear();
}
void TreeMapItem::addFreeRect(const QRect& r)
{
// do not add invalid rects
if ((r.width() < 1) || (r.height() < 1)) return;
if (0) qDebug() << "addFree(" << path(0).join(QStringLiteral("/")) << ", "
<< r.x() << "/" << r.y() << "-"
<< r.width() << "x" << r.height() << ")";
if (_freeRects.isEmpty()) {
_freeRects.append(r);
return;
}
// join rect with last rect if possible
// this saves memory and does not make the tooltip flicker
QRect& last = _freeRects.last();
bool replaced = false;
if ((last.left() == r.left()) && (last.width() == r.width())) {
if ((last.bottom()+1 == r.top()) || (r.bottom()+1 == last.top())) {
last |= r;
replaced = true;
}
}
else if ((last.top() == r.top()) && (last.height() == r.height())) {
if ((last.right()+1 == r.left()) || (r.right()+1 == last.left())) {
last |= r;
replaced = true;
}
}
if (!replaced) {
_freeRects.append(r);
return;
}
if (0) qDebug() << " united with last to ("
<< last.x() << "/" << last.y() << "-"
<< last.width() << "x" << last.height() << ")";
}
// TreeMapWidget
TreeMapWidget::TreeMapWidget(TreeMapItem* base,
QWidget* parent)
: QWidget(parent)
{
_base = base;
_base->setWidget(this);
_font = font();
_fontHeight = fontMetrics().height();
// default behaviour
_selectionMode = Single;
_splitMode = TreeMapItem::AlwaysBest;
_visibleWidth = 2;
_reuseSpace = false;
_skipIncorrectBorder = false;
_drawSeparators = false;
_allowRotation = true;
_borderWidth = 2;
_shading = true; // beautiful is default!
_maxSelectDepth = -1; // unlimited
_maxDrawingDepth = -1; // unlimited
_minimalArea = -1; // unlimited
_markNo = 0;
for(int i=0;i<4;i++) {
_drawFrame[i] = true;
_transparent[i] = false;
}
// _stopAtText will be unset on resizing (per default)
// _textVisible will be true on resizing (per default)
// _forceText will be false on resizing (per default)
// start state: _selection is an empty list
_current = 0;
_oldCurrent = 0;
_pressed = 0;
_lastOver = 0;
_needsRefresh = _base;
setAttribute(Qt::WA_NoSystemBackground, true);
setFocusPolicy(Qt::StrongFocus);
}
TreeMapWidget::~TreeMapWidget()
{
delete _base;
}
const QFont& TreeMapWidget::currentFont() const
{
return _font;
}
void TreeMapWidget::setSplitMode(TreeMapItem::SplitMode m)
{
if (_splitMode == m) return;
_splitMode = m;
redraw();
}
TreeMapItem::SplitMode TreeMapWidget::splitMode() const
{
return _splitMode;
}
bool TreeMapWidget::setSplitMode(const QString& mode)
{
if (mode == QLatin1String("Bisection")) setSplitMode(TreeMapItem::Bisection);
else if (mode == QLatin1String("Columns")) setSplitMode(TreeMapItem::Columns);
else if (mode == QLatin1String("Rows")) setSplitMode(TreeMapItem::Rows);
else if (mode == QLatin1String("AlwaysBest")) setSplitMode(TreeMapItem::AlwaysBest);
else if (mode == QLatin1String("Best")) setSplitMode(TreeMapItem::Best);
else if (mode == QLatin1String("HAlternate")) setSplitMode(TreeMapItem::HAlternate);
else if (mode == QLatin1String("VAlternate")) setSplitMode(TreeMapItem::VAlternate);
else if (mode == QLatin1String("Horizontal")) setSplitMode(TreeMapItem::Horizontal);
else if (mode == QLatin1String("Vertical")) setSplitMode(TreeMapItem::Vertical);
else return false;
return true;
}
QString TreeMapWidget::splitModeString() const
{
QString mode;
switch(splitMode()) {
case TreeMapItem::Bisection: mode = QStringLiteral("Bisection"); break;
case TreeMapItem::Columns: mode = QStringLiteral("Columns"); break;
case TreeMapItem::Rows: mode = QStringLiteral("Rows"); break;
case TreeMapItem::AlwaysBest: mode = QStringLiteral("AlwaysBest"); break;
case TreeMapItem::Best: mode = QStringLiteral("Best"); break;
case TreeMapItem::HAlternate: mode = QStringLiteral("HAlternate"); break;
case TreeMapItem::VAlternate: mode = QStringLiteral("VAlternate"); break;
case TreeMapItem::Horizontal: mode = QStringLiteral("Horizontal"); break;
case TreeMapItem::Vertical: mode = QStringLiteral("Vertical"); break;
default: mode = QStringLiteral("Unknown"); break;
}
return mode;
}
void TreeMapWidget::setShadingEnabled(bool s)
{
if (_shading == s) return;
_shading = s;
redraw();
}
void TreeMapWidget::drawFrame(int d, bool b)
{
if ((d<0) || (d>=4) || (_drawFrame[d]==b)) return;
_drawFrame[d] = b;
redraw();
}
void TreeMapWidget::setTransparent(int d, bool b)
{
if ((d<0) || (d>=4) || (_transparent[d]==b)) return;
_transparent[d] = b;
redraw();
}
void TreeMapWidget::setAllowRotation(bool enable)
{
if (_allowRotation == enable) return;
_allowRotation = enable;
redraw();
}
void TreeMapWidget::setVisibleWidth(int width, bool reuseSpace)
{
if (_visibleWidth == width && _reuseSpace == reuseSpace) return;
_visibleWidth = width;
_reuseSpace = reuseSpace;
redraw();
}
void TreeMapWidget::setSkipIncorrectBorder(bool enable)
{
if (_skipIncorrectBorder == enable) return;
_skipIncorrectBorder = enable;
redraw();
}
void TreeMapWidget::setBorderWidth(int w)
{
if (_borderWidth == w) return;
_borderWidth = w;
redraw();
}
void TreeMapWidget::setMaxDrawingDepth(int d)
{
if (_maxDrawingDepth == d) return;
_maxDrawingDepth = d;
redraw();
}
QString TreeMapWidget::defaultFieldType(int f) const
{
return tr("Text %1").arg(f+1);
}
QString TreeMapWidget::defaultFieldStop(int) const
{
return QString();
}
bool TreeMapWidget::defaultFieldVisible(int f) const
{
return (f<2);
}
bool TreeMapWidget::defaultFieldForced(int) const
{
return false;
}
DrawParams::Position TreeMapWidget::defaultFieldPosition(int f) const
{
switch(f%4) {
case 0: return DrawParams::TopLeft;
case 1: return DrawParams::TopRight;
case 2: return DrawParams::BottomRight;
case 3: return DrawParams::BottomLeft;
default:break;
}
return DrawParams::TopLeft;
}
bool TreeMapWidget::resizeAttr(int size)
{
if (size<0 || size>=MAX_FIELD) return false;
if (size>(int)_attr.size()) {
int oldSize = _attr.size();
_attr.resize(size);
while (oldSizeparent() is existing.
_needsRefresh = i->parent();
}
}
QString TreeMapWidget::tipString(TreeMapItem* i) const
{
QString tip, itemTip;
while (i) {
if (!i->text(0).isEmpty()) {
itemTip = i->text(0);
if (!i->text(1).isEmpty())
itemTip += " (" + i->text(1) + ')';
if (!tip.isEmpty())
tip += '\n';
tip += itemTip;
}
i = i->parent();
}
return tip;
}
TreeMapItem* TreeMapWidget::item(int x, int y) const
{
if (!rect().contains(x, y)) return 0;
if (DEBUG_DRAWING) qDebug() << "item(" << x << "," << y << "):";
TreeMapItem* p = _base;
TreeMapItem* i;
while (1) {
TreeMapItemList* list = p->children();
i = 0;
if (list) {
int idx;
for (idx=0; idxsize(); idx++) {
i = list->at(idx);
if (DEBUG_DRAWING)
qDebug() << " Checking " << i->path(0).join(QStringLiteral("/")) << " ("
<< i->itemRect().x() << "/" << i->itemRect().y()
<< "-" << i->itemRect().width()
<< "x" << i->itemRect().height() << ")";
if (i->itemRect().contains(x, y)) {
if (DEBUG_DRAWING) qDebug() << " .. Got. Index " << idx;
p->setIndex(idx);
break;
}
}
if (idx == list->size()) i = 0; // not contained in child
}
if (!i) {
static TreeMapItem* last = 0;
if (p != last) {
last = p;
if (DEBUG_DRAWING)
qDebug() << "item(" << x << "," << y << "): Got "
<< p->path(0).join(QStringLiteral("/")) << " (Size "
<< p->itemRect().width() << "x" << p->itemRect().height()
<< ", Val " << p->value() << ")";
}
return p;
}
p = i;
}
return 0;
}
TreeMapItem* TreeMapWidget::possibleSelection(TreeMapItem* i) const
{
if (i) {
if (_maxSelectDepth>=0) {
int depth = i->depth();
while(i && depth > _maxSelectDepth) {
i = i->parent();
depth--;
}
}
}
return i;
}
TreeMapItem* TreeMapWidget::visibleItem(TreeMapItem* i) const
{
if (i) {
/* Must have a visible area */
while(i && ((i->itemRect().width() <1) ||
(i->itemRect().height() <1))) {
TreeMapItem* p = i->parent();
if (!p) break;
int idx = p->children()->indexOf(i);
idx--;
if (idx<0)
i = p;
else
i = p->children()->at(idx);
}
}
return i;
}
void TreeMapWidget::setSelected(TreeMapItem* item, bool selected)
{
if (!item) return;
item = possibleSelection(item);
setCurrent(item);
TreeMapItem* changed = setTmpSelected(item, selected);
if (!changed) return;
_selection = _tmpSelection;
if (_selectionMode == Single)
emit selectionChanged(item);
emit selectionChanged();
redraw(changed);
if (0) qDebug() << (selected ? "S":"Des") << "elected Item "
<< (item ? item->path(0).join(QString()) : QStringLiteral("(null)"))
<< " (depth " << (item ? item->depth() : -1)
<< ")";
}
void TreeMapWidget::setMarked(int markNo, bool redrawWidget)
{
// if there is no marking, return
if ((_markNo == 0) && (markNo == 0)) return;
_markNo = markNo;
if (!clearSelection() && redrawWidget) redraw();
}
/* Returns all items which appear only in one of the given lists */
TreeMapItemList TreeMapWidget::diff(TreeMapItemList& l1,
TreeMapItemList& l2)
{
TreeMapItemList l;
foreach(TreeMapItem* i, l1)
if (!l2.contains(i))
l.append(i);
foreach(TreeMapItem* i, l2)
if (!l1.contains(i))
l.append(i);
return l;
}
/* Only modifies _tmpSelection.
* Returns 0 when no change happened, otherwise the TreeMapItem that has
* to be redrawn for all changes.
*/
TreeMapItem* TreeMapWidget::setTmpSelected(TreeMapItem* item, bool selected)
{
if (!item) return 0;
if (_selectionMode == NoSelection) return 0;
TreeMapItemList old = _tmpSelection;
if (_selectionMode == Single) {
_tmpSelection.clear();
if (selected) _tmpSelection.append(item);
}
else {
if (selected) {
// first remove any selection which is parent or child of -
foreach(TreeMapItem* i, _tmpSelection)
if (i->isChildOf(item) || item->isChildOf(i))
_tmpSelection.removeAll(i);
_tmpSelection.append(item);
}
else
_tmpSelection.removeAll(item);
}
return diff(old, _tmpSelection).commonParent();
}
bool TreeMapWidget::clearSelection(TreeMapItem* parent)
{
TreeMapItemList old = _selection;
// remove any selection which is child of
foreach(TreeMapItem* i, _selection)
if (i->isChildOf(parent))
_selection.removeAll(i);
TreeMapItem* changed = diff(old, _selection).commonParent();
if (changed) {
changed->redraw();
emit selectionChanged();
}
return (changed != 0);
}
bool TreeMapWidget::isSelected(TreeMapItem* i) const
{
if (!i) return false;
return _selection.contains(i);
}
bool TreeMapWidget::isTmpSelected(TreeMapItem* i)
{
if (!i) return false;
return _tmpSelection.contains(i);
}
void TreeMapWidget::setCurrent(TreeMapItem* i, bool kbd)
{
TreeMapItem* old = _current;
_current = i;
if (_markNo >0) {
// remove mark
_markNo = 0;
if (i) qDebug() << "setCurrent(" << i->path(0).join(QStringLiteral("/"))
<< ") - mark removed";
// always complete redraw needed to remove mark
redraw();
if (old == _current) return;
}
else {
if (old == _current) return;
if (old) old->redraw();
if (i) i->redraw();
}
//qDebug() << "Current Item " << (i ? qPrintable(i->path()) : "(null)");
emit currentChanged(i, kbd);
}
void TreeMapWidget::setRangeSelection(TreeMapItem* i1,
TreeMapItem* i2, bool selected)
{
i1 = possibleSelection(i1);
i2 = possibleSelection(i2);
setCurrent(i2);
TreeMapItem* changed = setTmpRangeSelection(i1, i2, selected);
if (!changed) return;
_selection = _tmpSelection;
if (_selectionMode == Single)
emit selectionChanged(i2);
emit selectionChanged();
redraw(changed);
}
TreeMapItem* TreeMapWidget::setTmpRangeSelection(TreeMapItem* i1,
TreeMapItem* i2,
bool selected)
{
if ((i1 == 0) && (i2 == 0)) return 0;
if ((i1 == 0) || i1->isChildOf(i2)) return setTmpSelected(i2, selected);
if ((i2 == 0) || i2->isChildOf(i1)) return setTmpSelected(i1, selected);
TreeMapItem* changed = setTmpSelected(i1, selected);
TreeMapItem* changed2 = setTmpSelected(i2, selected);
if (changed2) changed = changed2->commonParent(changed);
TreeMapItem* commonParent = i1;
while (commonParent && !i2->isChildOf(commonParent)) {
i1 = commonParent;
commonParent = commonParent->parent();
}
if (!commonParent) return changed;
while (i2 && i2->parent() != commonParent)
i2 = i2->parent();
if (!i2) return changed;
TreeMapItemList* list = commonParent->children();
if (!list) return changed;
bool between = false;
foreach(TreeMapItem* i, *list) {
if (between) {
if (i==i1 || i==i2) break;
changed2 = setTmpSelected(i, selected);
if (changed2) changed = changed2->commonParent(changed);
}
else if (i==i1 || i==i2)
between = true;
}
return changed;
}
void TreeMapWidget::contextMenuEvent( QContextMenuEvent* e )
{
//qDebug() << "TreeMapWidget::contextMenuEvent";
- if ( receivers( SIGNAL(contextMenuRequested(TreeMapItem*, const QPoint &)) ) )
+ if ( receivers( SIGNAL(contextMenuRequested(TreeMapItem*,QPoint)) ) )
e->accept();
if ( e->reason() == QContextMenuEvent::Keyboard ) {
QRect r = (_current) ? _current->itemRect() : _base->itemRect();
QPoint p = QPoint(r.left() + r.width()/2, r.top() + r.height()/2);
emit contextMenuRequested(_current, p);
}
else {
TreeMapItem* i = item(e->x(), e->y());
emit contextMenuRequested(i, e->pos());
}
}
void TreeMapWidget::mousePressEvent( QMouseEvent* e )
{
//qDebug() << "TreeMapWidget::mousePressEvent";
_oldCurrent = _current;
TreeMapItem* i = item(e->x(), e->y());
_pressed = i;
_inShiftDrag = e->modifiers() & Qt::ShiftModifier;
_inControlDrag = e->modifiers() & Qt::ControlModifier;
_lastOver = _pressed;
TreeMapItem* changed = 0;
TreeMapItem* item = possibleSelection(_pressed);
switch(_selectionMode) {
case Single:
changed = setTmpSelected(item, true);
break;
case Multi:
changed = setTmpSelected(item, !isTmpSelected(item));
break;
case Extended:
if (_inControlDrag)
changed = setTmpSelected(item, !isTmpSelected(item));
else if (_inShiftDrag) {
TreeMapItem* sCurrent = possibleSelection(_current);
changed = setTmpRangeSelection(sCurrent, item,
!isTmpSelected(item));
}
else {
_selectionMode = Single;
changed = setTmpSelected(item, true);
_selectionMode = Extended;
}
break;
default:
break;
}
// item under mouse always selected on right button press
if (e->button() == Qt::RightButton) {
TreeMapItem* changed2 = setTmpSelected(item, true);
if (changed2) changed = changed2->commonParent(changed);
}
setCurrent(_pressed);
if (changed)
redraw(changed);
if (e->button() == Qt::RightButton) {
// emit selection change
if (! (_tmpSelection == _selection)) {
_selection = _tmpSelection;
if (_selectionMode == Single)
emit selectionChanged(_lastOver);
emit selectionChanged();
}
_pressed = 0;
_lastOver = 0;
emit rightButtonPressed(i, e->pos());
}
}
void TreeMapWidget::mouseMoveEvent( QMouseEvent* e )
{
//qDebug() << "TreeMapWidget::mouseMoveEvent";
if (!_pressed) return;
TreeMapItem* over = item(e->x(), e->y());
if (_lastOver == over) return;
setCurrent(over);
if (over == 0) {
_lastOver = 0;
return;
}
TreeMapItem* changed = 0;
TreeMapItem* item = possibleSelection(over);
switch(_selectionMode) {
case Single:
changed = setTmpSelected(item, true);
break;
case Multi:
changed = setTmpSelected(item, !isTmpSelected(item));
break;
case Extended:
if (_inControlDrag)
changed = setTmpSelected(item, !isTmpSelected(item));
else {
TreeMapItem* sLast = possibleSelection(_lastOver);
changed = setTmpRangeSelection(sLast, item, true);
}
break;
default:
break;
}
_lastOver = over;
if (changed)
redraw(changed);
}
void TreeMapWidget::mouseReleaseEvent( QMouseEvent* )
{
//qDebug() << "TreeMapWidget::mouseReleaseEvent";
if (!_pressed) return;
if (!_lastOver) {
// take back
setCurrent(_oldCurrent);
TreeMapItem* changed = diff(_tmpSelection, _selection).commonParent();
_tmpSelection = _selection;
if (changed)
redraw(changed);
}
else {
if (! (_tmpSelection == _selection)) {
_selection = _tmpSelection;
if (_selectionMode == Single)
emit selectionChanged(_lastOver);
emit selectionChanged();
}
if (!_inControlDrag && !_inShiftDrag && (_pressed == _lastOver))
emit clicked(_lastOver);
}
_pressed = 0;
_lastOver = 0;
}
void TreeMapWidget::mouseDoubleClickEvent( QMouseEvent* e )
{
TreeMapItem* over = item(e->x(), e->y());
emit doubleClicked(over);
}
/* returns -1 if nothing visible found */
int nextVisible(TreeMapItem* i)
{
TreeMapItem* p = i->parent();
if (!p || p->itemRect().isEmpty()) return -1;
int idx = p->children()->indexOf(i);
if (idx<0) return -1;
while (idx < (int)p->children()->count()-1) {
idx++;
QRect r = p->children()->at(idx)->itemRect();
if (r.width()>1 && r.height()>1)
return idx;
}
return -1;
}
/* returns -1 if nothing visible found */
int prevVisible(TreeMapItem* i)
{
TreeMapItem* p = i->parent();
if (!p || p->itemRect().isEmpty()) return -1;
int idx = p->children()->indexOf(i);
if (idx<0) return -1;
while (idx > 0) {
idx--;
QRect r = p->children()->at(idx)->itemRect();
if (r.width()>1 && r.height()>1)
return idx;
}
return -1;
}
void TreeMapWidget::keyPressEvent( QKeyEvent* e )
{
if (e->key() == Qt::Key_Escape && _pressed) {
// take back
if (_oldCurrent != _lastOver)
setCurrent(_oldCurrent);
if (! (_tmpSelection == _selection)) {
TreeMapItem* changed = diff(_tmpSelection, _selection).commonParent();
_tmpSelection = _selection;
if (changed)
redraw(changed);
}
_pressed = 0;
_lastOver = 0;
}
if ((e->key() == Qt::Key_Space) ||
(e->key() == Qt::Key_Return)) {
switch(_selectionMode) {
case NoSelection:
break;
case Single:
setSelected(_current, true);
break;
case Multi:
setSelected(_current, !isSelected(_current));
break;
case Extended:
if ((e->modifiers() & Qt::ControlModifier) ||
(e->modifiers() & Qt::ShiftModifier))
setSelected(_current, !isSelected(_current));
else {
_selectionMode = Single;
setSelected(_current, true);
_selectionMode = Extended;
}
}
if (_current && (e->key() == Qt::Key_Return))
emit returnPressed(_current);
return;
}
if (!_current) {
if (e->key() == Qt::Key_Down) {
setCurrent(_base, true);
}
return;
}
TreeMapItem* old = _current, *newItem;
TreeMapItem* p = _current->parent();
bool goBack;
if (_current->sorting(&goBack) == -1) {
// noSorting
goBack = false;
}
if ((e->key() == Qt::Key_Backspace) ||
(e->key() == Qt::Key_Up)) {
newItem = visibleItem(p);
setCurrent(newItem, true);
}
else if (e->key() == Qt::Key_Left) {
int newIdx = goBack ? nextVisible(_current) : prevVisible(_current);
if (p && newIdx>=0) {
p->setIndex(newIdx);
setCurrent(p->children()->at(newIdx), true);
}
}
else if (e->key() == Qt::Key_Right) {
int newIdx = goBack ? prevVisible(_current) : nextVisible(_current);
if (p && newIdx>=0) {
p->setIndex(newIdx);
setCurrent(p->children()->at(newIdx), true);
}
}
else if (e->key() == Qt::Key_Down) {
if (_current->children() && _current->children()->count()>0) {
int newIdx = _current->index();
if (newIdx<0)
newIdx = goBack ? (_current->children()->count()-1) : 0;
if (newIdx>=(int)_current->children()->count())
newIdx = _current->children()->count()-1;
newItem = visibleItem(_current->children()->at(newIdx));
setCurrent(newItem, true);
}
}
if (old == _current) return;
if (! (e->modifiers() & Qt::ControlModifier)) return;
if (! (e->modifiers() & Qt::ShiftModifier)) return;
switch(_selectionMode) {
case NoSelection:
break;
case Single:
setSelected(_current, true);
break;
case Multi:
setSelected(_current, !isSelected(_current));
break;
case Extended:
if (e->modifiers() & Qt::ControlModifier)
setSelected(_current, !isSelected(_current));
else
setSelected(_current, isSelected(old));
}
}
void TreeMapWidget::fontChange( const QFont& )
{
redraw();
}
// react on tooltip events
bool TreeMapWidget::event(QEvent *event)
{
if (event->type() == QEvent::ToolTip) {
QHelpEvent *helpEvent = static_cast(event);
TreeMapItem* i = item(helpEvent->pos().x(), helpEvent->pos().y());
bool hasTip = false;
if (i) {
const QList& rList = i->freeRects();
foreach(const QRect& r, rList) {
if (r.contains(helpEvent->pos())) {
hasTip = true;
break;
}
}
}
if (hasTip)
QToolTip::showText(helpEvent->globalPos(), tipString(i));
else
QToolTip::hideText();
}
return QWidget::event(event);
}
void TreeMapWidget::paintEvent( QPaintEvent * )
{
drawTreeMap();
}
// Updates screen from shadow buffer,
// but redraws before if needed
void TreeMapWidget::drawTreeMap()
{
// no need to draw if hidden
if (!isVisible()) return;
if (_pixmap.size() != size())
_needsRefresh = _base;
if (_needsRefresh) {
if (DEBUG_DRAWING)
qDebug() << "Redrawing " << _needsRefresh->path(0).join(QStringLiteral("/"));
if (_needsRefresh == _base) {
// redraw whole widget
_pixmap = QPixmap(size());
_pixmap.fill(palette().color(backgroundRole()));
}
QPainter p(&_pixmap);
if (_needsRefresh == _base) {
p.setPen(Qt::black);
p.drawRect(QRect(2, 2, QWidget::width()-5, QWidget::height()-5));
_base->setItemRect(QRect(3, 3, QWidget::width()-6, QWidget::height()-6));
}
else {
// only subitem
if (!_needsRefresh->itemRect().isValid()) return;
}
// reset cached font object; it could have been changed
_font = font();
_fontHeight = fontMetrics().height();
drawItems(&p, _needsRefresh);
_needsRefresh = 0;
}
QPainter p(this);
p.drawPixmap(0, 0, _pixmap, 0, 0,
QWidget::width(), QWidget::height());
if (hasFocus()) {
QStylePainter p(this);
QStyleOptionFocusRect opt;
opt.rect = rect();
opt.palette = palette();
opt.state = QStyle::State_None;
p.drawPrimitive( QStyle::PE_FrameFocusRect, opt );
}
}
void TreeMapWidget::redraw(TreeMapItem* i)
{
if (!i) return;
if (!_needsRefresh)
_needsRefresh = i;
else {
if (!i->isChildOf(_needsRefresh))
_needsRefresh = _needsRefresh->commonParent(i);
}
if (isVisible()) {
// delayed drawing if we have multiple redraw requests
update();
}
}
void TreeMapWidget::drawItem(QPainter* p,
TreeMapItem* item)
{
bool isSelected = false;
if (_markNo>0) {
for(TreeMapItem* i = item; i; i=i->parent()) {
if (i->isMarked(_markNo)) {
isSelected = true;
break;
}
}
}
else {
foreach(TreeMapItem* i, _tmpSelection) {
if (item->isChildOf(i)) {
isSelected = true;
break;
}
}
}
bool isCurrent = _current && item->isChildOf(_current);
int dd = item->depth();
if (isTransparent(dd)) return;
RectDrawing d(item->itemRect());
item->setSelected(isSelected);
item->setCurrent(isCurrent);
item->setShaded(_shading);
item->drawFrame(drawFrame(dd));
d.drawBack(p, item);
}
bool TreeMapWidget::horizontal(TreeMapItem* i, const QRect& r)
{
switch(i->splitMode()) {
case TreeMapItem::HAlternate:
return (i->depth()%2)==1;
case TreeMapItem::VAlternate:
return (i->depth()%2)==0;
case TreeMapItem::Horizontal:
return true;
case TreeMapItem::Vertical:
return false;
default:
return r.width() > r.height();
}
return false;
}
/**
* Draw TreeMapItems recursive, starting from item
*/
void TreeMapWidget::drawItems(QPainter* p,
TreeMapItem* item)
{
if (DEBUG_DRAWING)
qDebug() << "+drawItems(" << item->path(0).join(QStringLiteral("/")) << ", "
<< item->itemRect().x() << "/" << item->itemRect().y()
<< "-" << item->itemRect().width() << "x"
<< item->itemRect().height() << "), Val " << item->value()
<< ", Sum " << item->sum();
drawItem(p, item);
item->clearFreeRects();
QRect origRect = item->itemRect();
int bw = item->borderWidth();
QRect r = QRect(origRect.x()+bw, origRect.y()+bw,
origRect.width()-2*bw, origRect.height()-2*bw);
TreeMapItemList* list = item->children();
bool stopDrawing = false;
// only subdivide if there are children
if (!list || list->count()==0)
stopDrawing = true;
// only subdivide if there is enough space
if (!stopDrawing && (r.width()<=0 || r.height()<=0))
stopDrawing = true;
// stop drawing if maximum depth is reached
if (!stopDrawing &&
(_maxDrawingDepth>=0 && item->depth()>=_maxDrawingDepth))
stopDrawing = true;
// stop drawing if stopAtText is reached
if (!stopDrawing)
for (int no=0;no<(int)_attr.size();no++) {
QString stopAt = fieldStop(no);
if (!stopAt.isEmpty() && (item->text(no) == stopAt)) {
stopDrawing = true;
break;
}
}
// area size is checked later...
#if 0
// stop drawing if minimal area size is reached
if (!stopDrawing &&
(_minimalArea > 0) &&
(r.width() * r.height() < _minimalArea)) stopDrawing = true;
#endif
if (stopDrawing) {
if (list) {
// invalidate rects
foreach(TreeMapItem* i, *list)
i->clearItemRect();
}
// tooltip appears on whole item rect
item->addFreeRect(item->itemRect());
// if we have space for text...
if ((r.height() < _fontHeight) || (r.width() < _fontHeight)) return;
RectDrawing d(r);
// draw text fields rotated to split direction
bool rotate = !horizontal(item, r);
if (_allowRotation) rotate = (r.height() > r.width());
item->setRotated(rotate);
for (int no=0;no<(int)_attr.size();no++) {
if (!fieldVisible(no)) continue;
d.drawField(p, no, item);
}
r = d.remainingRect(item);
if (DEBUG_DRAWING)
qDebug() << "-drawItems(" << item->path(0).join(QStringLiteral("/")) << ")";
return;
}
double user_sum, child_sum, self;
// user supplied sum
user_sum = item->sum();
// own sum
child_sum = 0;
foreach(TreeMapItem* i, *list) {
child_sum += i->value();
if (DEBUG_DRAWING)
qDebug() << " child: " << i->text(0) << ", value "
<< i->value();
}
QRect orig = r;
// if we have space for text...
if ((r.height() >= _fontHeight) && (r.width() >= _fontHeight)) {
RectDrawing d(r);
// draw text fields rotated to split direction
bool rotate = !horizontal(item, r);
if (_allowRotation) rotate = (r.height() > r.width());
item->setRotated(rotate);
for (int no=0;no<(int)_attr.size();no++) {
if (!fieldVisible(no)) continue;
if (!fieldForced(no)) continue;
d.drawField(p, no, item);
}
r = d.remainingRect(item);
}
if (orig.x() == r.x()) {
// Strings on top
item->addFreeRect(QRect(orig.x(), orig.y(),
orig.width(), orig.height()-r.height()));
}
else {
// Strings on the left
item->addFreeRect(QRect(orig.x(), orig.y(),
orig.width()-r.width(), orig.height()));
}
if (user_sum == 0) {
// user did not supply any sum
user_sum = child_sum;
self = 0;
}
else {
if (user_sum < child_sum) {
//qDebug() << "TreeMWidget " <<
// item->path() << ": User sum " << user_sum << " < Child Items sum " << child_sum;
// invalid user supplied sum: ignore and use calculate sum
user_sum = child_sum;
self = 0.0;
}
else {
self = user_sum - child_sum;
}
}
// use requested splitting algorithm: we rotate for horizontal splits
bool rotate = horizontal(item, r);
int self_length = (int)( ((rotate) ? r.width() : r.height()) *
self / user_sum + .5);
// drawn border belongs to self (TODO: option _skipIncorrectBorder)
self_length -= 2 * item->borderWidth();
if (self_length > 0) {
// take space for self cost
QRect sr = r;
if (rotate) {
sr.setWidth( self_length );
r.setRect(r.x()+sr.width(), r.y(), r.width()-sr.width(), r.height());
}
else {
sr.setHeight( self_length );
r.setRect(r.x(), r.y()+sr.height(), r.width(), r.height()-sr.height());
}
// set selfRect (not occupied by children) for tooltip
item->addFreeRect(sr);
if (0) qDebug() << "Item " << item->path(0).join(QStringLiteral("/")) << ": SelfR "
<< sr.x() << "/" << sr.y() << "-" << sr.width()
<< "/" << sr.height() << ", self " << self << "/"
<< user_sum;
if ((sr.height() >= _fontHeight) && (sr.width() >= _fontHeight)) {
RectDrawing d(sr);
item->setRotated(_allowRotation && (r.height() > r.width()));
for (int no=0;no<(int)_attr.size();no++) {
if (!fieldVisible(no)) continue;
if (fieldForced(no)) continue;
d.drawField(p, no, item);
}
}
user_sum -= self;
}
bool goBack;
if (item->sorting(&goBack) == -1) {
// noSorting
goBack = false;
}
int idx = goBack ? (list->size()-1) : 0;
if (item->splitMode() == TreeMapItem::Columns) {
int len = list->count();
bool drawDetails = true;
while (len>0 && user_sum>0) {
int firstIdx = idx;
double valSum = 0;
int lenLeft = len;
int columns = (int)(sqrt((double)len * r.width()/r.height())+.5);
if (columns==0) columns = 1; //should never be needed
while (lenLeft>0 && ((double)valSum*(len-lenLeft) <
(double)len*user_sum/columns/columns)) {
valSum += list->at(idx)->value();
if (goBack) --idx; else ++idx;
lenLeft--;
}
// we always split horizontally
int nextPos = (int)((double)r.width() * valSum / user_sum);
QRect firstRect = QRect(r.x(), r.y(), nextPos, r.height());
if (nextPos < _visibleWidth) {
if (item->sorting(0) == -1) {
// fill current rect with hash pattern
drawFill(item, p, firstRect);
}
else {
// fill rest with hash pattern
drawFill(item, p, r, list, firstIdx, len, goBack);
break;
}
}
else {
drawDetails = drawItemArray(p, item, firstRect,
valSum, list, firstIdx, len-lenLeft, goBack);
}
r.setRect(r.x()+nextPos, r.y(), r.width()-nextPos, r.height());
user_sum -= valSum;
len = lenLeft;
if (!drawDetails) {
if (item->sorting(0) == -1)
drawDetails = true;
else {
drawFill(item, p, r, list, idx, len, goBack);
break;
}
}
}
}
else if (item->splitMode() == TreeMapItem::Rows) {
int len = list->count();
bool drawDetails = true;
while (len>0 && user_sum>0) {
int firstIdx = idx;
double valSum = 0;
int lenLeft = len;
int rows = (int)(sqrt((double)len * r.height()/r.width())+.5);
if (rows==0) rows = 1; //should never be needed
while (lenLeft>0 && ((double)valSum*(len-lenLeft) <
(double)len*user_sum/rows/rows)) {
valSum += list->at(idx)->value();
if (goBack) --idx; else ++idx;
lenLeft--;
}
// we always split horizontally
int nextPos = (int)((double)r.height() * valSum / user_sum);
QRect firstRect = QRect(r.x(), r.y(), r.width(), nextPos);
if (nextPos < _visibleWidth) {
if (item->sorting(0) == -1) {
drawFill(item, p, firstRect);
}
else {
drawFill(item, p, r, list, firstIdx, len, goBack);
break;
}
}
else {
drawDetails = drawItemArray(p, item, firstRect,
valSum, list, firstIdx, len-lenLeft, goBack);
}
r.setRect(r.x(), r.y()+nextPos, r.width(), r.height()-nextPos);
user_sum -= valSum;
len = lenLeft;
if (!drawDetails) {
if (item->sorting(0) == -1)
drawDetails = true;
else {
drawFill(item, p, r, list, idx, len, goBack);
break;
}
}
}
}
else
drawItemArray(p, item, r, user_sum, list, idx, list->count(), goBack);
if (DEBUG_DRAWING)
qDebug() << "-drawItems(" << item->path(0).join(QStringLiteral("/")) << ")";
}
// fills area with a pattern if to small to draw children
void TreeMapWidget::drawFill(TreeMapItem* i, QPainter* p, const QRect& r)
{
p->setBrush(Qt::Dense4Pattern);
p->setPen(Qt::NoPen);
p->drawRect(QRect(r.x(), r.y(), r.width()-1, r.height()-1));
i->addFreeRect(r);
}
// fills area with a pattern if to small to draw children
void TreeMapWidget::drawFill(TreeMapItem* i, QPainter* p, const QRect& r,
TreeMapItemList* list, int idx, int len, bool goBack)
{
if (DEBUG_DRAWING)
qDebug() << " +drawFill(" << r.x() << "/" << r.y()
<< "-" << r.width() << "x" << r.height()
<< ", len " << len << ")";
p->setBrush(Qt::Dense4Pattern);
p->setPen(Qt::NoPen);
p->drawRect(QRect(r.x(), r.y(), r.width()-1, r.height()-1));
i->addFreeRect(r);
// reset rects
while (len>0 && (i=list->value(idx))) {
if (DEBUG_DRAWING)
qDebug() << " Reset Rect " << i->path(0).join(QStringLiteral("/"));
i->clearItemRect();
if (goBack) --idx; else ++idx;
len--;
}
if (DEBUG_DRAWING)
qDebug() << " -drawFill(" << r.x() << "/" << r.y()
<< "-" << r.width() << "x" << r.height()
<< ", len " << len << ")";
}
// returns false if rect gets to small
bool TreeMapWidget::drawItemArray(QPainter* p, TreeMapItem* item,
const QRect& r, double user_sum,
TreeMapItemList* list, int idx, int len,
bool goBack)
{
if (user_sum == 0) return false;
static bool b2t = true;
// stop recursive bisection for small rectangles
if (((r.height() < _visibleWidth) &&
(r.width() < _visibleWidth)) ||
((_minimalArea > 0) &&
(r.width() * r.height() < _minimalArea))) {
drawFill(item, p, r, list, idx, len, goBack);
return false;
}
if (DEBUG_DRAWING)
qDebug() << " +drawItemArray(" << item->path(0).join(QStringLiteral("/"))
<< ", " << r.x() << "/" << r.y() << "-" << r.width()
<< "x" << r.height() << ")";
if (len>2 && (item->splitMode() == TreeMapItem::Bisection)) {
int firstIdx = idx;
double valSum = 0;
int lenLeft = len;
//while (lenLeft>0 && valSumlen/2) {
valSum += list->at(idx)->value();
if (goBack) --idx; else ++idx;
lenLeft--;
}
// draw first half...
bool drawOn;
QRect secondRect;
if (r.width() > r.height()) {
int halfPos = (int)((double)r.width() * valSum / user_sum);
QRect firstRect = QRect(r.x(), r.y(), halfPos, r.height());
drawOn = drawItemArray(p, item, firstRect,
valSum, list, firstIdx, len-lenLeft, goBack);
secondRect.setRect(r.x()+halfPos, r.y(), r.width()-halfPos, r.height());
}
else {
int halfPos = (int)((double)r.height() * valSum / user_sum);
QRect firstRect = QRect(r.x(), r.y(), r.width(), halfPos);
drawOn = drawItemArray(p, item, firstRect,
valSum, list, firstIdx, len-lenLeft, goBack);
secondRect.setRect(r.x(), r.y()+halfPos, r.width(), r.height()-halfPos);
}
// if no sorting, do not stop drawing
if (item->sorting(0) == -1) drawOn = true;
// second half
if (drawOn)
drawOn = drawItemArray(p, item, secondRect, user_sum - valSum,
list, idx, lenLeft, goBack);
else {
drawFill(item, p, secondRect, list, idx, len, goBack);
}
if (DEBUG_DRAWING)
qDebug() << " -drawItemArray(" << item->path(0).join(QStringLiteral("/"))
<< ")";
return drawOn;
}
bool hor = horizontal(item,r);
TreeMapItem* i;
QRect fullRect = r;
while (len>0) {
i = list->at(idx);
if (user_sum <= 0) {
if (DEBUG_DRAWING)
qDebug() << "drawItemArray: Reset " << i->path(0).join(QStringLiteral("/"));
i->clearItemRect();
if (goBack) --idx; else ++idx;
len--;
continue;
}
// stop drawing for small rectangles
if (((fullRect.height() < _visibleWidth) &&
(fullRect.width() < _visibleWidth)) ||
((_minimalArea > 0) &&
(fullRect.width() * fullRect.height() < _minimalArea))) {
drawFill(item, p, fullRect, list, idx, len, goBack);
if (DEBUG_DRAWING)
qDebug() << " -drawItemArray(" << item->path(0).join(QStringLiteral("/"))
<< "): Stop";
return false;
}
if (i->splitMode() == TreeMapItem::AlwaysBest)
hor = fullRect.width() > fullRect.height();
int lastPos = hor ? fullRect.width() : fullRect.height();
double val = i->value();
int nextPos = (user_sum <= 0.0) ? 0: (int)(lastPos * val / user_sum +.5);
if (nextPos>lastPos) nextPos = lastPos;
if ((item->sorting(0) != -1) && (nextPos < _visibleWidth)) {
drawFill(item, p, fullRect, list, idx, len, goBack);
if (DEBUG_DRAWING)
qDebug() << " -drawItemArray(" << item->path(0).join(QStringLiteral("/"))
<< "): Stop";
return false;
}
QRect currRect = fullRect;
if (hor)
currRect.setWidth(nextPos);
else {
if (b2t)
currRect.setRect(fullRect.x(), fullRect.bottom()-nextPos+1, fullRect.width(), nextPos);
else
currRect.setHeight(nextPos);
}
// do not draw very small rectangles:
if (nextPos >= _visibleWidth) {
i->setItemRect(currRect);
drawItems(p, i);
}
else {
i->clearItemRect();
drawFill(item, p, currRect);
}
// draw Separator
if (_drawSeparators && (nextPossetPen(Qt::black);
if (hor) {
if (fullRect.top() <= fullRect.bottom())
p->drawLine(fullRect.x() + nextPos, fullRect.top(), fullRect.x() + nextPos, fullRect.bottom());
}
else {
if (fullRect.left() <= fullRect.right())
p->drawLine(fullRect.left(), fullRect.y() + nextPos, fullRect.right(), fullRect.y() + nextPos);
}
nextPos++;
}
if (hor)
fullRect.setRect(fullRect.x() + nextPos, fullRect.y(),
lastPos - nextPos, fullRect.height());
else {
if (b2t)
fullRect.setRect(fullRect.x(), fullRect.y(),
fullRect.width(), lastPos-nextPos);
else
fullRect.setRect(fullRect.x(), fullRect.y() + nextPos,
fullRect.width(), lastPos-nextPos);
}
user_sum -= val;
if (goBack) --idx; else ++idx;
len--;
}
if (DEBUG_DRAWING)
qDebug() << " -drawItemArray(" << item->path(0).join(QStringLiteral("/"))
<< "): Continue";
return true;
}
/*----------------------------------------------------------------
* Popup menus for option setting
*/
void TreeMapWidget::splitActivated(QAction* a)
{
setSplitMode( (TreeMapItem::SplitMode) a->data().toInt());
}
void TreeMapWidget::addSplitAction(QMenu* m, const QString& s, int v)
{
QAction* a = m->addAction(s);
a->setData(v);
a->setCheckable(true);
a->setChecked(splitMode() == v);
}
void TreeMapWidget::addSplitDirectionItems(QMenu* m)
{
connect(m, &QMenu::triggered,
this, &TreeMapWidget::splitActivated );
addSplitAction(m, tr("Recursive Bisection"), TreeMapItem::Bisection);
addSplitAction(m, tr("Columns"), TreeMapItem::Columns);
addSplitAction(m, tr("Rows"), TreeMapItem::Rows);
addSplitAction(m, tr("Always Best"), TreeMapItem::AlwaysBest);
addSplitAction(m, tr("Best"), TreeMapItem::Best);
addSplitAction(m, tr("Alternate (V)"), TreeMapItem::VAlternate);
addSplitAction(m, tr("Alternate (H)"), TreeMapItem::HAlternate);
addSplitAction(m, tr("Horizontal"), TreeMapItem::Horizontal);
addSplitAction(m, tr("Vertical"), TreeMapItem::Vertical);
}
diff --git a/qcachegrind/qcgtoplevel.cpp b/qcachegrind/qcgtoplevel.cpp
index a897863..b424d43 100644
--- a/qcachegrind/qcgtoplevel.cpp
+++ b/qcachegrind/qcgtoplevel.cpp
@@ -1,2079 +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
+#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());
// 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 Type | Parent Cost |
"
"Function Inclusive | Total |
"
"Function Self | Function Group (*)/Total |
"
"Call | Function Inclusive |
"
"Source Line | Function 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&)));
+ connect( _eventTypeBox, SIGNAL(activated(QString)),
+ this, SLOT(eventTypeSelected(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;
// 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(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;
_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());
// 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(), 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;
}