Add stacks model to display full backtrace for selection.
authorMilian Wolff <mail@milianw.de>
Tue, 23 Feb 2016 22:45:45 +0000 (23:45 +0100)
committerMilian Wolff <mail@milianw.de>
Tue, 23 Feb 2016 22:45:45 +0000 (23:45 +0100)
gui/CMakeLists.txt
gui/gui.cpp
gui/mainwindow.cpp
gui/mainwindow.h
gui/mainwindow.ui
gui/stacksmodel.cpp [new file with mode: 0644]
gui/stacksmodel.h [new file with mode: 0644]

index 2b86a1c..8683694 100644 (file)
@@ -24,6 +24,7 @@ add_executable(heaptrack_gui
     modeltest.cpp
     parser.cpp
     flamegraph.cpp
+    stacksmodel.cpp
     ${UIFILES}
 )
 
@@ -43,4 +44,4 @@ target_link_libraries(heaptrack_gui
     KChart
     sharedprint)
 
-install(TARGETS heaptrack_gui ${INSTALL_TARGETS_DEFAULT_ARGS})
\ No newline at end of file
+install(TARGETS heaptrack_gui ${INSTALL_TARGETS_DEFAULT_ARGS})
index 64b0b7f..4667904 100644 (file)
@@ -58,16 +58,20 @@ int main(int argc, char** argv)
     parser.process(app);
     aboutData.processCommandLine(&parser);
 
-    foreach (const QString &file, parser.positionalArguments()) {
-        MainWindow* window = new MainWindow;
-        window->loadFile(file);
+    auto createWindow = [] () -> MainWindow* {
+        auto window = new MainWindow;
+        window->setAttribute(Qt::WA_DeleteOnClose);
         window->show();
+        return window;
+    };
+
+    foreach (const QString &file, parser.positionalArguments()) {
+        createWindow()->loadFile(file);
     }
 
     if (parser.positionalArguments().isEmpty()) {
-        MainWindow* window = new MainWindow;
-        window->show();
+        createWindow();
     }
 
     return app.exec();
-}
\ No newline at end of file
+}
index e623f83..d874ed0 100644 (file)
@@ -27,6 +27,8 @@
 
 #include <QFileDialog>
 #include <QStatusBar>
+#include <KConfigGroup>
+#include <QDebug>
 
 #include "treemodel.h"
 #include "treeproxy.h"
 #include "chartmodel.h"
 #include "chartproxy.h"
 #include "histogrammodel.h"
+#include "stacksmodel.h"
 
 using namespace std;
 
+namespace {
+const int MAINWINDOW_VERSION = 1;
+namespace Config {
+namespace Groups {
+const char MainWindow[] = "MainWindow";
+}
+namespace Entries {
+const char State[] = "State";
+}
+}
+}
+
 MainWindow::MainWindow(QWidget* parent)
     : QMainWindow(parent)
     , m_ui(new Ui::MainWindow)
     , m_parser(new Parser(this))
+    , m_config(KSharedConfig::openConfig(QStringLiteral("heaptrack_gui")))
 {
     m_ui->setupUi(this);
 
+    auto group = m_config->group(Config::Groups::MainWindow);
+    auto state = group.readEntry(Config::Entries::State, QByteArray());
+    restoreState(state, MAINWINDOW_VERSION);
+
     m_ui->pages->setCurrentWidget(m_ui->openPage);
     // TODO: proper progress report
     m_ui->loadingProgress->setMinimum(0);
@@ -79,6 +99,7 @@ MainWindow::MainWindow(QWidget* parent)
         statusBar()->addWidget(m_ui->progressLabel, 1);
         statusBar()->addWidget(m_ui->loadingProgress);
         m_ui->pages->setCurrentWidget(m_ui->resultsPage);
+        m_ui->stacksDock->setVisible(true);
     });
     connect(m_parser, &Parser::topDownDataAvailable,
             this, [=] (const TreeData& data) {
@@ -156,11 +177,53 @@ MainWindow::MainWindow(QWidget* parent)
     auto openFile = KStandardAction::open(this, SLOT(openFile()), this);
     m_ui->openFile->setDefaultAction(openFile);
 
+    auto stacksModel = new StacksModel(this);
+    m_ui->stacksTree->setModel(stacksModel);
+    m_ui->stacksTree->setRootIsDecorated(false);
+    auto updateStackSpinner = [this] (int stacks) {
+        m_ui->stackSpinner->setMinimum(min(stacks, 1));
+        m_ui->stackSpinner->setSuffix(i18n(" / %1", stacks));
+        m_ui->stackSpinner->setMaximum(stacks);
+    };
+    updateStackSpinner(0);
+    connect(stacksModel, &StacksModel::stacksFound,
+            this, updateStackSpinner);
+    connect(m_ui->stackSpinner, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged),
+            stacksModel, &StacksModel::setStackIndex);
+    auto fillFromIndex = [stacksModel] (const QModelIndex& current) {
+        if (!current.isValid()) {
+            stacksModel->clear();
+        } else {
+            auto proxy = qobject_cast<const TreeProxy*>(current.model());
+            Q_ASSERT(proxy);
+            auto leaf = proxy->mapToSource(current);
+            stacksModel->fillFromIndex(leaf);
+        }
+    };
+    connect(m_ui->bottomUpResults->selectionModel(), &QItemSelectionModel::currentChanged,
+            this, fillFromIndex);
+    connect(m_ui->topDownResults->selectionModel(), &QItemSelectionModel::currentChanged,
+            this, fillFromIndex);
+    connect(m_ui->tabWidget, &QTabWidget::currentChanged, this, [this, fillFromIndex] (int tabIndex) {
+        const auto widget = m_ui->tabWidget->widget(tabIndex);
+        const bool showDocks = (widget == m_ui->topDownTab || widget == m_ui->bottomUpTab);
+        m_ui->stacksDock->setVisible(showDocks);
+        if (showDocks) {
+            auto tree = (widget == m_ui->topDownTab) ? m_ui->topDownResults : m_ui->bottomUpResults;
+            fillFromIndex(tree->selectionModel()->currentIndex());
+        }
+    });
+    m_ui->stacksDock->setVisible(false);
+    m_ui->stacksDock->setFeatures(QDockWidget::DockWidgetMovable);
+
     setWindowTitle(i18n("Heaptrack"));
 }
 
 MainWindow::~MainWindow()
 {
+    auto state = saveState(MAINWINDOW_VERSION);
+    auto group = m_config->group(Config::Groups::MainWindow);
+    group.writeEntry(Config::Entries::State, state);
 }
 
 void MainWindow::loadFile(const QString& file)
index eec98d2..d9c83ba 100644 (file)
@@ -21,6 +21,7 @@
 #define MAINWINDOW_H
 
 #include <QMainWindow>
+#include <KSharedConfig>
 
 namespace Ui {
 class MainWindow;
@@ -44,6 +45,7 @@ public slots:
 private:
     QScopedPointer<Ui::MainWindow> m_ui;
     Parser* m_parser;
+    KSharedConfig::Ptr m_config;
 };
 
 #endif // MAINWINDOW_H
index 9946b19..6a6e728 100644 (file)
@@ -6,8 +6,8 @@
    <rect>
     <x>0</x>
     <y>0</y>
-    <width>800</width>
-    <height>600</height>
+    <width>1128</width>
+    <height>896</height>
    </rect>
   </property>
   <property name="windowTitle">
         <item>
          <widget class="QTabWidget" name="tabWidget">
           <property name="currentIndex">
-           <number>0</number>
+           <number>7</number>
           </property>
           <widget class="QWidget" name="bottomUpTab">
            <attribute name="title">
     </item>
    </layout>
   </widget>
+  <widget class="QDockWidget" name="stacksDock">
+   <property name="windowTitle">
+    <string>S&amp;tacks</string>
+   </property>
+   <attribute name="dockWidgetArea">
+    <number>2</number>
+   </attribute>
+   <widget class="QWidget" name="stacksDockContents">
+    <layout class="QVBoxLayout" name="verticalLayout_5">
+     <item>
+      <widget class="QWidget" name="widget_3" native="true">
+       <layout class="QHBoxLayout" name="horizontalLayout_5">
+        <property name="leftMargin">
+         <number>0</number>
+        </property>
+        <property name="topMargin">
+         <number>0</number>
+        </property>
+        <property name="rightMargin">
+         <number>0</number>
+        </property>
+        <property name="bottomMargin">
+         <number>0</number>
+        </property>
+        <item>
+         <widget class="QLabel" name="stackSpinnerLabel">
+          <property name="text">
+           <string>Selected Stack:</string>
+          </property>
+         </widget>
+        </item>
+        <item>
+         <widget class="QSpinBox" name="stackSpinner"/>
+        </item>
+       </layout>
+      </widget>
+     </item>
+     <item>
+      <widget class="QTreeView" name="stacksTree">
+       <property name="rootIsDecorated">
+        <bool>false</bool>
+       </property>
+       <property name="uniformRowHeights">
+        <bool>true</bool>
+       </property>
+      </widget>
+     </item>
+    </layout>
+   </widget>
+  </widget>
  </widget>
  <customwidgets>
   <customwidget>
diff --git a/gui/stacksmodel.cpp b/gui/stacksmodel.cpp
new file mode 100644 (file)
index 0000000..3d165e3
--- /dev/null
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2015 Milian Wolff <mail@milianw.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "stacksmodel.h"
+#include "treemodel.h"
+
+#include <KLocalizedString>
+
+#include <QDebug>
+
+StacksModel::StacksModel(QObject* parent)
+    : QAbstractListModel(parent)
+{
+}
+
+StacksModel::~StacksModel() = default;
+
+void StacksModel::setStackIndex(int index)
+{
+    beginResetModel();
+    m_stackIndex = index - 1;
+    endResetModel();
+}
+
+static void findLeafs(const QModelIndex& index, QVector<QModelIndex>* leafs)
+{
+    int rows = index.model()->rowCount(index);
+    if (!rows) {
+        leafs->append(index);
+        return;
+    }
+    for (int i = 0; i < rows; ++i) {
+        findLeafs(index.child(i, 0), leafs);
+    }
+}
+
+void StacksModel::fillFromIndex(const QModelIndex& index)
+{
+    if (index.column() != 0) {
+        // only the first column has children
+        fillFromIndex(index.sibling(index.row(), 0));
+        return;
+    }
+
+    QVector<QModelIndex> leafs;
+    findLeafs(index, &leafs);
+
+    beginResetModel();
+    m_data.clear();
+    m_data.resize(leafs.size());
+    m_stackIndex = 0;
+    int stackIndex = 0;
+    foreach (QModelIndex leaf, leafs) {
+        auto& stack = m_data[stackIndex];
+        while (leaf.isValid()) {
+            stack << leaf.sibling(leaf.row(), TreeModel::LocationColumn);
+            leaf = leaf.parent();
+        }
+        std::reverse(stack.begin(), stack.end());
+        ++stackIndex;
+    }
+    endResetModel();
+
+    emit stacksFound(m_data.size());
+}
+
+void StacksModel::clear()
+{
+    beginResetModel();
+    m_data.clear();
+    endResetModel();
+    emit stacksFound(0);
+}
+
+int StacksModel::rowCount(const QModelIndex& parent) const
+{
+    if (parent.isValid() || m_data.isEmpty()) {
+        return 0;
+    }
+    return m_data.value(m_stackIndex).size();
+}
+
+QVariant StacksModel::data(const QModelIndex& index, int role) const
+{
+    if (!hasIndex(index.row(), index.column(), index.parent())) {
+        return {};
+    }
+    return m_data.value(m_stackIndex).value(index.row()).data(role);
+}
+
+QVariant StacksModel::headerData(int section, Qt::Orientation orientation, int role) const
+{
+    if (section == 0 && role == Qt::DisplayRole && orientation == Qt::Horizontal) {
+        return i18n("Backtrace");
+    }
+    return {};
+}
diff --git a/gui/stacksmodel.h b/gui/stacksmodel.h
new file mode 100644 (file)
index 0000000..8d0d9b8
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2015 Milian Wolff <mail@milianw.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef STACKSMODEL_H
+#define STACKSMODEL_H
+
+#include <QAbstractListModel>
+
+class StacksModel : public QAbstractListModel
+{
+    Q_OBJECT
+public:
+    explicit StacksModel(QObject* parent = nullptr);
+    ~StacksModel();
+
+    void setStackIndex(int index);
+    void fillFromIndex(const QModelIndex& leaf);
+    void clear();
+
+    int rowCount(const QModelIndex& parent) const override;
+    QVariant data(const QModelIndex& index, int role) const override;
+    QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
+
+signals:
+    void stacksFound(int stacks);
+
+private:
+    QVector<QVector<QModelIndex>> m_data;
+    int m_stackIndex = 0;
+};
+
+#endif // STACKSMODEL_H