File indexing completed on 2024-04-28 17:02:19

0001 /*
0002    This file is part of Massif Visualizer
0003 
0004    Copyright 2010 Milian Wolff <mail@milianw.de>
0005    Copyright 2013 Arnold Dumas <contact@arnolddumas.fr>
0006 
0007    This program is free software; you can redistribute it and/or
0008    modify it under the terms of the GNU General Public License as
0009    published by the Free Software Foundation; either version 2 of
0010    the License or (at your option) version 3 or any later version
0011    accepted by the membership of KDE e.V. (or its successor approved
0012    by the membership of KDE e.V.), which shall act as a proxy
0013    defined in Section 14 of version 3 of the license.
0014 
0015    This program is distributed in the hope that it will be useful,
0016    but WITHOUT ANY WARRANTY; without even the implied warranty of
0017    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
0018    GNU General Public License for more details.
0019 
0020    You should have received a copy of the GNU General Public License
0021    along with this program.  If not, see <http://www.gnu.org/licenses/>.
0022 */
0023 
0024 #include "documentwidget.h"
0025 
0026 #include <QApplication>
0027 
0028 #include "massifdata/filedata.h"
0029 #include "massifdata/parser.h"
0030 #include "massifdata/parseworker.h"
0031 #include "massifdata/snapshotitem.h"
0032 #include "massifdata/treeleafitem.h"
0033 #include "massifdata/util.h"
0034 
0035 #include <KStandardAction>
0036 #include <KColorScheme>
0037 
0038 #include <KLocalizedString>
0039 // forward include not available until later KDE versions...
0040 #include <kmessagewidget.h>
0041 #include <KXMLGUIFactory>
0042 
0043 #include <QDebug>
0044 #include <QLabel>
0045 #include <QProgressBar>
0046 #include <QStackedWidget>
0047 #include <QTabWidget>
0048 #include <QToolButton>
0049 #include <QVBoxLayout>
0050 #include <QIcon>
0051 
0052 #include "charttab.h"
0053 #include "allocatorstab.h"
0054 
0055 #ifdef HAVE_KGRAPHVIEWER
0056 #include "callgraphtab.h"
0057 
0058 #include <KPluginLoader>
0059 #include <KPluginFactory>
0060 #include <KParts/ReadOnlyPart>
0061 #endif
0062 
0063 using namespace Massif;
0064 
0065 DocumentWidget::DocumentWidget(const QUrl& file, const QStringList& customAllocators,
0066                                KXMLGUIClient* guiParent, QWidget* parent)
0067     : QWidget(parent)
0068     , KXMLGUIClient(guiParent)
0069     , m_data(0)
0070     , m_parseWorker(new ParseWorker)
0071     , m_file(file)
0072     , m_currentTab(0)
0073     , m_stackedWidget(new QStackedWidget(this))
0074     , m_tabs(new QTabWidget(m_stackedWidget))
0075     , m_errorMessage(0)
0076     , m_loadingMessage(0)
0077     , m_loadingProgressBar(0)
0078     , m_stopParserButton(0)
0079     , m_isLoaded(false)
0080 {
0081     connect(m_parseWorker, &ParseWorker::finished,
0082             this, &DocumentWidget::parserFinished);
0083     connect(m_parseWorker, &ParseWorker::error,
0084             this, &DocumentWidget::showError);
0085     connect(m_parseWorker, &ParseWorker::progressRange,
0086             this, &DocumentWidget::setRange);
0087     connect(m_parseWorker, &ParseWorker::progress,
0088             this, &DocumentWidget::setProgress);
0089 
0090     // Create dedicated thread for this document.
0091     // TODO: use ThreadWeaver
0092     QThread* thread = new QThread(this);
0093     thread->start();
0094     m_parseWorker->moveToThread(thread);
0095     m_parseWorker->parse(file, customAllocators);
0096 
0097     setXMLFile(QStringLiteral("documentwidgetui.rc"), true);
0098 
0099     // Set m_stackedWidget as the main widget.
0100     setLayout(new QVBoxLayout(this));
0101     layout()->addWidget(m_stackedWidget);
0102 
0103     m_tabs->setTabPosition(QTabWidget::South);
0104     m_stackedWidget->addWidget(m_tabs);
0105 
0106     // Second widget : loadingPage
0107     QWidget* loadingPage = new QWidget(m_stackedWidget);
0108     QVBoxLayout* verticalLayout = new QVBoxLayout(loadingPage);
0109     QSpacerItem* upperSpacerItem = new QSpacerItem(20, 247, QSizePolicy::Minimum, QSizePolicy::Expanding);
0110     verticalLayout->addItem(upperSpacerItem);
0111 
0112     m_loadingMessage = new QLabel(loadingPage);
0113     m_loadingMessage->setText(i18n("loading file <i>%1</i>...", file.toString()));
0114     m_loadingMessage->setAlignment(Qt::AlignCenter);
0115     verticalLayout->addWidget(m_loadingMessage);
0116 
0117     m_loadingProgressBar = new QProgressBar(loadingPage);
0118     m_loadingProgressBar->setValue(24);
0119     m_loadingProgressBar->setRange(0, 0);
0120     verticalLayout->addWidget(m_loadingProgressBar);
0121 
0122     QWidget* stopParserWidget = new QWidget(loadingPage);
0123     stopParserWidget->setLayoutDirection(Qt::LeftToRight);
0124     QHBoxLayout* stopParserWidgetLayout = new QHBoxLayout(stopParserWidget);
0125     m_stopParserButton = new QToolButton(stopParserWidget);
0126     m_stopParserButton->setObjectName(QStringLiteral("stopParsing"));
0127     m_stopParserButton->setToolButtonStyle(Qt::ToolButtonTextUnderIcon);
0128     m_stopParserButton->setIcon(QIcon::fromTheme(QStringLiteral("process-stop")));
0129     m_stopParserButton->setIconSize(QSize(48, 48));
0130     connect(m_stopParserButton, &QToolButton::clicked,
0131             this, &DocumentWidget::requestClose);
0132     stopParserWidgetLayout->addWidget(m_stopParserButton);
0133     verticalLayout->addWidget(stopParserWidget);
0134 
0135     QSpacerItem* bottomSpacerItem = new QSpacerItem(20, 230, QSizePolicy::Minimum, QSizePolicy::Expanding);
0136     verticalLayout->addItem(bottomSpacerItem);
0137     m_stackedWidget->addWidget(loadingPage);
0138 
0139     // By default we show the loadingPage.
0140     m_stackedWidget->setCurrentIndex(1);
0141 }
0142 
0143 DocumentWidget::~DocumentWidget()
0144 {
0145     stopParser();
0146     if (m_data) {
0147         delete m_data;
0148         m_data = 0;
0149         m_file.clear();
0150     }
0151 }
0152 
0153 FileData* DocumentWidget::data() const
0154 {
0155     return m_data;
0156 }
0157 
0158 QUrl DocumentWidget::file() const
0159 {
0160     return m_file;
0161 }
0162 
0163 bool DocumentWidget::isLoaded() const
0164 {
0165     return m_isLoaded;
0166 }
0167 
0168 void DocumentWidget::settingsChanged()
0169 {
0170     for (int i = 0; i < m_tabs->count(); ++i) {
0171         static_cast<DocumentTabInterface*>(m_tabs->widget(i))->settingsChanged();
0172     }
0173 }
0174 
0175 void DocumentWidget::parserFinished(const QUrl& file, FileData* data)
0176 {
0177     Q_ASSERT(data->peak());
0178 
0179     qDebug() << "loaded massif file:" << file;
0180     qDebug() << "description:" << data->description();
0181     qDebug() << "command:" << data->cmd();
0182     qDebug() << "time unit:" << data->timeUnit();
0183     qDebug() << "snapshots:" << data->snapshots().size();
0184     qDebug() << "peak: snapshot #" << data->peak()->number() << "after" << data->peak()->time() << data->timeUnit();
0185     qDebug() << "peak cost:" << prettyCost(data->peak()->memHeap()) << "heap"
0186                              << prettyCost(data->peak()->memHeapExtra()) << "heap extra"
0187                              << prettyCost(data->peak()->memStacks()) << "stacks";
0188 
0189     m_data = data;
0190     m_file = file;
0191 
0192     m_tabs->addTab(new ChartTab(m_data, this, this), QIcon::fromTheme(QStringLiteral("office-chart-area-stacked")),
0193                    i18n("Memory Chart"));
0194 
0195 #ifdef HAVE_KGRAPHVIEWER
0196     static KPluginFactory *factory = KPluginLoader("kgraphviewerpart").factory();
0197     if (factory) {
0198         KParts::ReadOnlyPart* part = factory->create<KParts::ReadOnlyPart>("kgraphviewerpart", this);
0199         if (part) {
0200             m_tabs->addTab(new CallGraphTab(m_data, part, this, this), QIcon::fromTheme(QStringLiteral("kgraphviewer")),
0201                            i18n("Callgraph"));
0202         }
0203     }
0204 #endif
0205 
0206     m_tabs->addTab(new AllocatorsTab(m_data, this, this), QIcon::fromTheme(QStringLiteral("view-list-text")),
0207                    i18n("Allocators"));
0208 
0209     for (int i = 0; i < m_tabs->count(); ++i) {
0210         DocumentTabInterface* tab = static_cast<DocumentTabInterface*>(m_tabs->widget(i));
0211         connect(tab, &DocumentTabInterface::modelItemSelected,
0212                 this, &DocumentWidget::modelItemSelected);
0213         connect(tab, &DocumentTabInterface::contextMenuRequested,
0214                 this, &DocumentWidget::contextMenuRequested);
0215     }
0216 
0217     m_tabs->setCurrentIndex(0);
0218     connect(m_tabs, &QTabWidget::currentChanged,
0219             this, &DocumentWidget::slotTabChanged);
0220     slotTabChanged(0);
0221 
0222     m_isLoaded = true;
0223 
0224     // Switch to the display page and notify that everything is setup.
0225     m_stackedWidget->setCurrentIndex(0);
0226     emit loadingFinished();
0227 }
0228 
0229 void DocumentWidget::addGuiActions(KXMLGUIFactory* factory)
0230 {
0231     factory->addClient(this);
0232 
0233     // ensure only the current tab's client is in the factory
0234     // otherwise the actions from the other tabs are visible
0235     const int current = m_tabs->currentIndex();
0236     for (int i = 0; i < m_tabs->count(); ++i) {
0237         if (i != current) {
0238             factory->removeClient(static_cast<DocumentTabInterface*>(m_tabs->widget(i)));
0239         }
0240     }
0241 }
0242 
0243 void DocumentWidget::clearGuiActions(KXMLGUIFactory* factory)
0244 {
0245     factory->removeClient(this);
0246 }
0247 
0248 void DocumentWidget::slotTabChanged(int tab)
0249 {
0250     if (!factory()) {
0251         return;
0252     }
0253 
0254     if (m_currentTab) {
0255         factory()->removeClient(m_currentTab);
0256     }
0257 
0258     if (tab >= 0 && tab < m_tabs->count()) {
0259         m_currentTab = static_cast<DocumentTabInterface*>(m_tabs->widget(tab));
0260         factory()->addClient(m_currentTab);
0261     }
0262 }
0263 
0264 void DocumentWidget::selectModelItem(const ModelItem& item)
0265 {
0266     for (int i = 0; i < m_tabs->count(); ++i) {
0267         static_cast<DocumentTabInterface*>(m_tabs->widget(i))->selectModelItem(item);
0268     }
0269 }
0270 
0271 void DocumentWidget::setProgress(int value)
0272 {
0273     m_loadingProgressBar->setValue(value);
0274 }
0275 
0276 void DocumentWidget::setRange(int minimum, int maximum)
0277 {
0278     m_loadingProgressBar->setRange(minimum, maximum);
0279 }
0280 
0281 void DocumentWidget::showError(const QString& title, const QString& error)
0282 {
0283     if (!m_errorMessage) {
0284         m_errorMessage = new KMessageWidget(m_stackedWidget);
0285         m_stackedWidget->addWidget(m_errorMessage);
0286         m_errorMessage->setWordWrap(true);
0287         m_errorMessage->setMessageType(KMessageWidget::Error);
0288         m_errorMessage->setCloseButtonVisible(false);
0289     }
0290     m_errorMessage->setText(QString::fromLatin1("<b>%1</b><p style=\"text-align:left\">%2</p>").arg(title).arg(error));
0291     m_stackedWidget->setCurrentWidget(m_errorMessage);
0292 }
0293 
0294 void DocumentWidget::stopParser()
0295 {
0296     if (!m_parseWorker) {
0297         return;
0298     }
0299 
0300     QThread* thread = m_parseWorker->thread();
0301     m_parseWorker->stop();
0302     m_parseWorker->deleteLater();
0303     m_parseWorker = 0;
0304     thread->quit();
0305     thread->wait();
0306 }