diff --git a/src/flamegraph.cpp b/src/flamegraph.cpp index c73ba11d..aa2dce34 100644 --- a/src/flamegraph.cpp +++ b/src/flamegraph.cpp @@ -27,11 +27,11 @@ #include #include #include -#include #include #include #include #include +#include #include #include #include @@ -55,6 +55,29 @@ enum SearchMatchType DirectMatch, ChildMatch }; + +template +class CustomWidgetAction : public QWidgetAction +{ +public: + explicit CustomWidgetAction(CreateInstance createInstance, QWidget* parent) + : QWidgetAction(parent) + , _createInstance(createInstance) + { + } + + QWidget* createWidget(QWidget* parent) override + { + auto widget = new QWidget(parent); + auto layout = new QHBoxLayout(widget); + layout->setContentsMargins(0, 0, 0, 0); + _createInstance(widget, layout); + return widget; + } + +private: + CreateInstance _createInstance; +}; } class FrameGraphicsItem : public QGraphicsRectItem @@ -601,8 +624,6 @@ FlameGraph::FlameGraph(QWidget* parent, Qt::WindowFlags flags) , m_view(new QGraphicsView(this)) , m_displayLabel(new KSqueezedTextLabel(this)) , m_searchResultsLabel(new QLabel(this)) - , m_colorSchemeLabel(new QLabel(this)) - , m_colorSchemeSelector(new QComboBox(this)) { m_displayLabel->setTextElideMode(Qt::ElideRight); qRegisterMetaType(); @@ -627,91 +648,167 @@ FlameGraph::FlameGraph(QWidget* parent, Qt::WindowFlags flags) // fix for QTBUG-105237 view->setFont does not update fontMetrics only the rendered font m_scene->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); - m_backButton = new QPushButton(this); - m_backButton->setIcon(QIcon::fromTheme(QStringLiteral("go-previous"))); - m_backButton->setToolTip(QStringLiteral("Go back in symbol view history")); - m_forwardButton = new QPushButton(this); - m_forwardButton->setIcon(QIcon::fromTheme(QStringLiteral("go-next"))); - m_forwardButton->setToolTip(QStringLiteral("Go forward in symbol view history")); - - auto bottomUpCheckbox = new QCheckBox(i18n("Bottom-Up View"), this); - connect(this, &FlameGraph::uiResetRequested, bottomUpCheckbox, - [bottomUpCheckbox]() { bottomUpCheckbox->setChecked(false); }); - bottomUpCheckbox->setToolTip(i18n( - "Enable the bottom-up flame graph view. When this is unchecked, the top-down view is enabled by default.")); - bottomUpCheckbox->setChecked(m_showBottomUpData); - connect(bottomUpCheckbox, &QCheckBox::toggled, this, [this, bottomUpCheckbox] { - const auto showBottomUpData = bottomUpCheckbox->isChecked(); - if (showBottomUpData == m_showBottomUpData) { - return; - } - - m_showBottomUpData = showBottomUpData; - for (auto& stack : m_hoveredStacks) { - std::reverse(stack.begin(), stack.end()); - } - showData(); - }); - - auto collapseRecursionCheckbox = new QCheckBox(i18n("Collapse Recursion"), this); - connect(this, &FlameGraph::uiResetRequested, collapseRecursionCheckbox, - [collapseRecursionCheckbox]() { collapseRecursionCheckbox->setChecked(false); }); - collapseRecursionCheckbox->setChecked(m_collapseRecursion); - collapseRecursionCheckbox->setToolTip( - i18n("Collapse stack frames for functions calling themselves. " - "When this is unchecked, recursive frames will be visualized separately.")); - connect(collapseRecursionCheckbox, &QCheckBox::toggled, this, [this, collapseRecursionCheckbox] { - m_collapseRecursion = collapseRecursionCheckbox->isChecked(); - showData(); - }); + auto bottomUpAction = new CustomWidgetAction( + [this](QWidget* widget, QHBoxLayout* layout) { + auto bottomUpCheckbox = new QCheckBox(i18n("Bottom-Up View"), widget); + layout->addWidget(bottomUpCheckbox); + connect(this, &FlameGraph::uiResetRequested, bottomUpCheckbox, + [bottomUpCheckbox]() { bottomUpCheckbox->setChecked(false); }); + bottomUpCheckbox->setToolTip(i18n("Enable the bottom-up flame graph view. When this is unchecked, the " + "top-down view is enabled by default.")); + bottomUpCheckbox->setChecked(m_showBottomUpData); + connect(bottomUpCheckbox, &QCheckBox::toggled, this, [this, bottomUpCheckbox] { + const auto showBottomUpData = bottomUpCheckbox->isChecked(); + if (showBottomUpData == m_showBottomUpData) { + return; + } - auto costThreshold = new QDoubleSpinBox(this); - costThreshold->setDecimals(2); - costThreshold->setMinimum(0); - costThreshold->setMaximum(99.90); - costThreshold->setPrefix(i18n("Cost Threshold: ")); - costThreshold->setSuffix(QStringLiteral("%")); - costThreshold->setValue(DEFAULT_COST_THRESHOLD); - connect(this, &FlameGraph::uiResetRequested, costThreshold, - [costThreshold]() { costThreshold->setValue(DEFAULT_COST_THRESHOLD); }); - costThreshold->setSingleStep(0.01); - costThreshold->setToolTip( - i18n("The cost threshold defines a fractional cut-off value. " - "Items with a relative cost below this value will not be shown in the flame graph. " - "This is done as an optimization to quickly generate graphs for large data sets with " - "low memory overhead. If you need more details, decrease the threshold value, or set it to zero.")); - connect(costThreshold, static_cast(&QDoubleSpinBox::valueChanged), this, - [this](double threshold) { + m_showBottomUpData = showBottomUpData; + for (auto& stack : m_hoveredStacks) { + std::reverse(stack.begin(), stack.end()); + } + showData(); + }); + }, + this); + + m_costThreshold = DEFAULT_COST_THRESHOLD; + + auto costThresholdAction = new CustomWidgetAction( + [this](QWidget* widget, QHBoxLayout* layout) { + auto costThreshold = new QDoubleSpinBox(widget); + costThreshold->setDecimals(2); + costThreshold->setMinimum(0); + costThreshold->setMaximum(99.90); + costThreshold->setPrefix(i18n("Cost Threshold: ")); + costThreshold->setSuffix(QStringLiteral("%")); + costThreshold->setValue(m_costThreshold); + connect(this, &FlameGraph::uiResetRequested, costThreshold, + [costThreshold]() { costThreshold->setValue(DEFAULT_COST_THRESHOLD); }); + costThreshold->setSingleStep(0.01); + costThreshold->setToolTip( + i18n("The cost threshold defines a fractional cut-off value. " + "Items with a relative cost below this value will not be shown in the flame graph. " + "This is done as an optimization to quickly generate graphs for large data sets with " + "low memory overhead. If you need more details, decrease the threshold value, or set it to " + "zero.")); + + connect(costThreshold, qOverload(&QDoubleSpinBox::valueChanged), this, [this](double threshold) { m_costThreshold = threshold; showData(); }); + layout->addWidget(costThreshold); + }, + this); + + auto collapseRecursionAction = new CustomWidgetAction( + [this](QWidget* widget, QHBoxLayout* layout) { + auto checkbox = new QCheckBox(tr("Collapse Recursion"), widget); + checkbox->setChecked(m_collapseRecursion); + layout->addWidget(checkbox); + + connect(checkbox, &QCheckBox::clicked, widget, [this](bool checked) { + m_collapseRecursion = checked; + showData(); + }); + }, + this); + + auto costAggregation = new CustomWidgetAction( + [](QWidget* widget, QHBoxLayout* layout) { + auto costAggregationLabel = new QLabel(tr("Aggregate cost by:"), widget); + layout->addWidget(costAggregationLabel); + + auto costAggregation = new QComboBox(widget); + ResultsUtil::setupResultsAggregation(costAggregation); + layout->addWidget(costAggregation); + }, + this); + + auto colorSchemeSelector = new CustomWidgetAction( + [this](QWidget* widget, QHBoxLayout* layout) { + auto label = new QLabel(tr("Color Scheme:"), widget); + layout->addWidget(label); + + auto comboBox = new QComboBox(widget); + layout->addWidget(comboBox); + + comboBox->addItem(tr("Default"), QVariant::fromValue(Settings::ColorScheme::Default)); + comboBox->addItem(tr("Binary"), QVariant::fromValue(Settings::ColorScheme::Binary)); + comboBox->addItem(tr("Kernel"), QVariant::fromValue(Settings::ColorScheme::Kernel)); + comboBox->addItem(tr("System"), QVariant::fromValue(Settings::ColorScheme::System)); + comboBox->addItem(tr("Cost Ratio"), QVariant::fromValue(Settings::ColorScheme::CostRatio)); + + auto setColorScheme = [this](Settings::ColorScheme scheme) { + Settings::instance()->setColorScheme(scheme); + + if (m_rootItem) { + const auto children = m_rootItem->childItems(); + // don't recolor the root item + for (const auto& child : children) { + updateFlameGraphColorScheme(static_cast(child), scheme, m_rootItem->cost()); + } + } + }; - auto costAggregation = new QComboBox(this); - ResultsUtil::setupResultsAggregation(costAggregation); - auto costAggregationLabel = new QLabel(tr("Aggregate cost by:")); - costAggregationLabel->setBuddy(costAggregation); + connect(comboBox, QOverload::of(&QComboBox::currentIndexChanged), this, [comboBox, setColorScheme] { + setColorScheme(comboBox->currentData().value()); + }); - m_searchInput = new QLineEdit(this); - m_searchInput->setPlaceholderText(i18n("Search...")); - m_searchInput->setToolTip(i18n("Search the flame graph for a symbol.")); - m_searchInput->setClearButtonEnabled(true); - connect(m_searchInput, &QLineEdit::textChanged, this, &FlameGraph::setSearchValue); - connect(this, &FlameGraph::uiResetRequested, this, [this]() { m_searchInput->clear(); }); + connect(Settings::instance(), &Settings::pathsChanged, this, [setColorScheme] { + if (Settings::instance()->colorScheme() == Settings::ColorScheme::System) { + // setColorScheme triggers a redraw + // we only need to redraw the flamegraph if the current color scheme is affected by the changed + // paths + setColorScheme(Settings::ColorScheme::System); + } + }); + }, + this); + + auto searchInput = new CustomWidgetAction( + [this](QWidget* widget, QHBoxLayout* layout) { + auto searchInput = new QLineEdit(widget); + searchInput->setMinimumWidth(200); + layout->addWidget(searchInput); + + searchInput->setPlaceholderText(i18n("Search...")); + searchInput->setToolTip(i18n("Search the flame graph for a symbol.")); + searchInput->setClearButtonEnabled(true); + connect(searchInput, &QLineEdit::textChanged, this, &FlameGraph::setSearchValue); + connect(this, &FlameGraph::uiResetRequested, this, [this, searchInput] { + m_search.clear(); + searchInput->clear(); + }); + }, + this); + + m_backAction = KStandardAction::back(this, &FlameGraph::navigateBack, this); + m_backAction->setToolTip(QStringLiteral("Go back in symbol view history")); + m_forwardAction = KStandardAction::forward(this, &FlameGraph::navigateForward, this); + m_forwardAction->setToolTip(QStringLiteral("Go forward in symbol view history")); + + m_resetAction = new QAction(QIcon::fromTheme(QStringLiteral("go-first")), tr("Reset View"), this); + m_resetAction->setShortcut(tr("Escape")); + connect(m_resetAction, &QAction::triggered, this, [this]() { selectItem(0); }); - auto controls = new QWidget(this); - controls->setLayout(new QHBoxLayout); + // use a QToolBar to automatically hide widgets in a menu that don't fit into the window + auto controls = new QToolBar(this); controls->layout()->setContentsMargins(0, 0, 0, 0); - controls->layout()->addWidget(m_backButton); - controls->layout()->addWidget(m_forwardButton); - controls->layout()->addWidget(m_costSource); - controls->layout()->addWidget(bottomUpCheckbox); - controls->layout()->addWidget(collapseRecursionCheckbox); - controls->layout()->addWidget(costThreshold); - controls->layout()->addWidget(costAggregationLabel); - controls->layout()->addWidget(costAggregation); - controls->layout()->addWidget(m_searchInput); - controls->layout()->addWidget(m_colorSchemeLabel); - controls->layout()->addWidget(m_colorSchemeSelector); + + // these control widgets can stay as they are, since they should always be visible + controls->addAction(m_resetAction); + controls->addAction(m_backAction); + controls->addAction(m_forwardAction); + controls->addWidget(m_costSource); + + // these can be hidden as necessary + controls->addAction(searchInput); + controls->addAction(costAggregation); + controls->addAction(colorSchemeSelector); + controls->addAction(bottomUpAction); + controls->addAction(collapseRecursionAction); + controls->addAction(costThresholdAction); m_displayLabel->setWordWrap(true); m_displayLabel->setTextInteractionFlags(m_displayLabel->textInteractionFlags() | Qt::TextSelectableByMouse); @@ -728,51 +825,10 @@ FlameGraph::FlameGraph(QWidget* parent, Qt::WindowFlags flags) layout()->addWidget(m_displayLabel); layout()->addWidget(m_searchResultsLabel); - m_backAction = KStandardAction::back(this, SLOT(navigateBack()), this); addAction(m_backAction); - connect(m_backButton, &QPushButton::released, m_backAction, &QAction::trigger); - - m_forwardAction = KStandardAction::forward(this, SLOT(navigateForward()), this); addAction(m_forwardAction); - connect(m_forwardButton, &QPushButton::released, m_forwardAction, &QAction::trigger); - - m_resetAction = new QAction(QIcon::fromTheme(QStringLiteral("go-first")), tr("Reset View"), this); - m_resetAction->setShortcut(tr("Escape")); - connect(m_resetAction, &QAction::triggered, this, [this]() { selectItem(0); }); addAction(m_resetAction); updateNavigationActions(); - - m_colorSchemeLabel->setText(tr("Color Scheme:")); - - m_colorSchemeSelector->addItem(tr("Default"), QVariant::fromValue(Settings::ColorScheme::Default)); - m_colorSchemeSelector->addItem(tr("Binary"), QVariant::fromValue(Settings::ColorScheme::Binary)); - m_colorSchemeSelector->addItem(tr("Kernel"), QVariant::fromValue(Settings::ColorScheme::Kernel)); - m_colorSchemeSelector->addItem(tr("System"), QVariant::fromValue(Settings::ColorScheme::System)); - m_colorSchemeSelector->addItem(tr("Cost Ratio"), QVariant::fromValue(Settings::ColorScheme::CostRatio)); - - auto setColorScheme = [this](Settings::ColorScheme scheme) { - Settings::instance()->setColorScheme(scheme); - - if (m_rootItem) { - const auto children = m_rootItem->childItems(); - // don't recolor the root item - for (const auto& child : children) { - updateFlameGraphColorScheme(static_cast(child), scheme, m_rootItem->cost()); - } - } - }; - - connect(m_colorSchemeSelector, QOverload::of(&QComboBox::currentIndexChanged), this, [this, setColorScheme] { - setColorScheme(m_colorSchemeSelector->currentData().value()); - }); - - connect(Settings::instance(), &Settings::pathsChanged, this, [setColorScheme] { - if (Settings::instance()->colorScheme() == Settings::ColorScheme::System) { - // setColorScheme triggers a redraw - // we only need to redraw the flamegraph if the current color scheme is affected by the changed paths - setColorScheme(Settings::ColorScheme::System); - } - }); } FlameGraph::~FlameGraph() = default; @@ -1047,8 +1103,8 @@ void FlameGraph::setData(FrameGraphicsItem* rootItem) rootItem->setRect(0, 0, 800, m_view->fontMetrics().height() + 4); m_scene->addItem(rootItem); - if (!m_searchInput->text().isEmpty()) { - setSearchValue(m_searchInput->text()); + if (!m_search.isEmpty()) { + setSearchValue(m_search); } if (!m_hoveredStacks.isEmpty()) { hoverStacks(rootItem, m_hoveredStacks); @@ -1110,6 +1166,8 @@ void FlameGraph::setSearchValue(const QString& value) return; } + m_search = value; + auto match = applySearch(m_rootItem, value); if (value.isEmpty()) { @@ -1143,6 +1201,4 @@ void FlameGraph::updateNavigationActions() m_backAction->setEnabled(hasItems); m_forwardAction->setEnabled(isNotLastItem); m_resetAction->setEnabled(hasItems); - m_backButton->setEnabled(hasItems); - m_forwardButton->setEnabled(isNotLastItem); } diff --git a/src/flamegraph.h b/src/flamegraph.h index 67bb7ee6..8aa1aab7 100644 --- a/src/flamegraph.h +++ b/src/flamegraph.h @@ -17,7 +17,6 @@ class QGraphicsView; class QComboBox; class QLabel; class QLineEdit; -class QPushButton; class KSqueezedTextLabel; @@ -75,14 +74,9 @@ private slots: QGraphicsView* m_view; KSqueezedTextLabel* m_displayLabel; QLabel* m_searchResultsLabel; - QLineEdit* m_searchInput = nullptr; QAction* m_forwardAction = nullptr; QAction* m_backAction = nullptr; QAction* m_resetAction = nullptr; - QPushButton* m_backButton = nullptr; - QPushButton* m_forwardButton = nullptr; - QLabel* m_colorSchemeLabel = nullptr; - QComboBox* m_colorSchemeSelector = nullptr; const FrameGraphicsItem* m_tooltipItem = nullptr; FrameGraphicsItem* m_rootItem = nullptr; QVector m_selectionHistory; @@ -91,6 +85,7 @@ private slots: bool m_showBottomUpData = false; bool m_collapseRecursion = false; bool m_buildingScene = false; + QString m_search; // cost threshold in percent, items below that value will not be shown static const constexpr double DEFAULT_COST_THRESHOLD = 0.1; double m_costThreshold = DEFAULT_COST_THRESHOLD; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 692bb453..f9a1cf21 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -172,7 +172,7 @@ MainWindow::MainWindow(QWidget* parent) ui->fileMenu->addSeparator(); connect(m_resultsPage, &ResultsPage::navigateToCode, this, &MainWindow::navigateToCode); - ui->fileMenu->addAction(KStandardAction::open(this, SLOT(onOpenFileButtonClicked()), this)); + ui->fileMenu->addAction(KStandardAction::open(this, &MainWindow::onOpenFileButtonClicked, this)); auto openNewWindow = new QAction(QIcon::fromTheme(QStringLiteral("document-open")), tr("Open in new window"), this); openNewWindow->setShortcut(tr("Ctrl+Shift+O")); @@ -182,20 +182,20 @@ MainWindow::MainWindow(QWidget* parent) openInNewWindow(fileName); }); ui->fileMenu->addAction(openNewWindow); - m_recentFilesAction = KStandardAction::openRecent(this, SLOT(openFile(QUrl)), this); + m_recentFilesAction = KStandardAction::openRecent(this, qOverload(&MainWindow::openFile), this); m_recentFilesAction->loadEntries(m_config->group("RecentFiles")); ui->fileMenu->addAction(m_recentFilesAction); ui->fileMenu->addSeparator(); - m_reloadAction = KStandardAction::redisplay(this, SLOT(reload()), this); + m_reloadAction = KStandardAction::redisplay(this, &MainWindow::reload, this); m_reloadAction->setText(tr("Reload")); ui->fileMenu->addAction(m_reloadAction); ui->fileMenu->addSeparator(); - m_exportAction = KStandardAction::saveAs(this, SLOT(saveAs()), this); + m_exportAction = KStandardAction::saveAs(this, qOverload<>(&MainWindow::saveAs), this); ui->fileMenu->addAction(m_exportAction); ui->fileMenu->addSeparator(); - ui->fileMenu->addAction(KStandardAction::close(this, SLOT(clear()), this)); + ui->fileMenu->addAction(KStandardAction::close(this, qOverload<>(&MainWindow::clear), this)); ui->fileMenu->addSeparator(); - ui->fileMenu->addAction(KStandardAction::quit(this, SLOT(close()), this)); + ui->fileMenu->addAction(KStandardAction::quit(this, &MainWindow::close, this)); connect(ui->actionAbout_Qt, &QAction::triggered, qApp, &QApplication::aboutQt); connect(ui->actionAbout_KDAB, &QAction::triggered, this, &MainWindow::aboutKDAB); connect(ui->settingsAction, &QAction::triggered, this, &MainWindow::openSettingsDialog);