/**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of Qt Creator. ** ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3 as published by the Free Software ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ****************************************************************************/ #include "qmlprofilerstatisticsview.h" #include #include #include #include #include #include #include #include #include #include #include #include "qmlprofilerviewmanager.h" #include "qmlprofilertool.h" #include #include #include using namespace QmlDebug; namespace QmlProfiler { namespace Internal { struct Colors { Colors () : noteBackground(QColor("orange")), defaultBackground(QColor("white")) {} QColor noteBackground; QColor defaultBackground; }; struct RootEventType : public QmlProfilerDataModel::QmlEventTypeData { RootEventType() { QString rootEventName = QmlProfilerStatisticsMainView::tr(""); displayName = rootEventName; location = QmlEventLocation(rootEventName, 1, 1); message = MaximumMessage; rangeType = MaximumRangeType; detailType = -1; data = QmlProfilerStatisticsMainView::tr("Main Program"); } }; Q_GLOBAL_STATIC(Colors, colors) Q_GLOBAL_STATIC(RootEventType, rootEventType) class StatisticsViewItem : public QStandardItem { public: StatisticsViewItem(const QString &text) : QStandardItem(text) {} virtual bool operator<(const QStandardItem &other) const { if (column() == 0) { // first column is special int filenameDiff = QUrl(data(FilenameRole).toString()).fileName().compare( QUrl(other.data(FilenameRole).toString()).fileName(), Qt::CaseInsensitive); if (filenameDiff != 0) return filenameDiff < 0; return data(LineRole).toInt() == other.data(LineRole).toInt() ? data(ColumnRole).toInt() < other.data(ColumnRole).toInt() : data(LineRole).toInt() < other.data(LineRole).toInt(); } else if (data(SortRole).type() == QVariant::String) { // Strings should be case-insensitive compared return data(SortRole).toString().compare(other.data(SortRole).toString(), Qt::CaseInsensitive) < 0; } else { // For everything else the standard comparison should be OK return QStandardItem::operator<(other); } } }; class QmlProfilerStatisticsView::QmlProfilerStatisticsViewPrivate { public: QmlProfilerStatisticsViewPrivate(QmlProfilerStatisticsView *qq) : q(qq) {} ~QmlProfilerStatisticsViewPrivate() {} QmlProfilerStatisticsView *q; QmlProfilerStatisticsMainView *m_eventTree; QmlProfilerStatisticsRelativesView *m_eventChildren; QmlProfilerStatisticsRelativesView *m_eventParents; QmlProfilerStatisticsModel *model; qint64 rangeStart; qint64 rangeEnd; }; static void setViewDefaults(Utils::TreeView *view) { view->setFrameStyle(QFrame::NoFrame); QHeaderView *header = view->header(); header->setSectionResizeMode(QHeaderView::Interactive); header->setDefaultSectionSize(100); header->setMinimumSectionSize(50); } static QString displayHeader(Fields header) { static const char ctxt[] = "QmlProfiler::Internal::QmlProfilerEventsMainView"; switch (header) { case Callee: return QCoreApplication::translate(ctxt, "Callee"); case CalleeDescription: return QCoreApplication::translate(ctxt, "Callee Description"); case Caller: return QCoreApplication::translate(ctxt, "Caller"); case CallerDescription: return QCoreApplication::translate(ctxt, "Caller Description"); case CallCount: return QCoreApplication::translate(ctxt, "Calls"); case Details: return QCoreApplication::translate(ctxt, "Details"); case Location: return QCoreApplication::translate(ctxt, "Location"); case MaxTime: return QCoreApplication::translate(ctxt, "Longest Time"); case TimePerCall: return QCoreApplication::translate(ctxt, "Mean Time"); case SelfTime: return QCoreApplication::translate(ctxt, "Self Time"); case SelfTimeInPercent: return QCoreApplication::translate(ctxt, "Self Time in Percent"); case MinTime: return QCoreApplication::translate(ctxt, "Shortest Time"); case TimeInPercent: return QCoreApplication::translate(ctxt, "Time in Percent"); case TotalTime: return QCoreApplication::translate(ctxt, "Total Time"); case Type: return QCoreApplication::translate(ctxt, "Type"); case MedianTime: return QCoreApplication::translate(ctxt, "Median Time"); default: return QString(); } } static void getSourceLocation(QStandardItem *infoItem, std::function receiver) { int line = infoItem->data(LineRole).toInt(); int column = infoItem->data(ColumnRole).toInt(); QString fileName = infoItem->data(FilenameRole).toString(); if (line != -1 && !fileName.isEmpty()) receiver(fileName, line, column); } QmlProfilerStatisticsView::QmlProfilerStatisticsView(QWidget *parent, QmlProfilerModelManager *profilerModelManager) : QmlProfilerEventsView(parent), d(new QmlProfilerStatisticsViewPrivate(this)) { setObjectName(QLatin1String("QmlProfilerStatisticsView")); setWindowTitle(tr("Statistics")); d->model = new QmlProfilerStatisticsModel(profilerModelManager, this); d->m_eventTree = new QmlProfilerStatisticsMainView(this, d->model); connect(d->m_eventTree, &QmlProfilerStatisticsMainView::gotoSourceLocation, this, &QmlProfilerStatisticsView::gotoSourceLocation); connect(d->m_eventTree, &QmlProfilerStatisticsMainView::typeSelected, this, &QmlProfilerStatisticsView::typeSelected); d->m_eventChildren = new QmlProfilerStatisticsRelativesView( new QmlProfilerStatisticsChildrenModel(profilerModelManager, d->model, this), this); d->m_eventParents = new QmlProfilerStatisticsRelativesView( new QmlProfilerStatisticsParentsModel(profilerModelManager, d->model, this), this); connect(d->m_eventTree, &QmlProfilerStatisticsMainView::typeSelected, d->m_eventChildren, &QmlProfilerStatisticsRelativesView::displayType); connect(d->m_eventTree, &QmlProfilerStatisticsMainView::typeSelected, d->m_eventParents, &QmlProfilerStatisticsRelativesView::displayType); connect(d->m_eventChildren, &QmlProfilerStatisticsRelativesView::typeClicked, d->m_eventTree, &QmlProfilerStatisticsMainView::selectType); connect(d->m_eventParents, &QmlProfilerStatisticsRelativesView::typeClicked, d->m_eventTree, &QmlProfilerStatisticsMainView::selectType); connect(d->m_eventChildren, &QmlProfilerStatisticsRelativesView::gotoSourceLocation, this, &QmlProfilerStatisticsView::gotoSourceLocation); connect(d->m_eventParents, &QmlProfilerStatisticsRelativesView::gotoSourceLocation, this, &QmlProfilerStatisticsView::gotoSourceLocation); // widget arrangement QVBoxLayout *groupLayout = new QVBoxLayout; groupLayout->setContentsMargins(0,0,0,0); groupLayout->setSpacing(0); Core::MiniSplitter *splitterVertical = new Core::MiniSplitter; splitterVertical->addWidget(d->m_eventTree); Core::MiniSplitter *splitterHorizontal = new Core::MiniSplitter; splitterHorizontal->addWidget(d->m_eventParents); splitterHorizontal->addWidget(d->m_eventChildren); splitterHorizontal->setOrientation(Qt::Horizontal); splitterVertical->addWidget(splitterHorizontal); splitterVertical->setOrientation(Qt::Vertical); splitterVertical->setStretchFactor(0,5); splitterVertical->setStretchFactor(1,2); groupLayout->addWidget(splitterVertical); setLayout(groupLayout); d->rangeStart = d->rangeEnd = -1; } QmlProfilerStatisticsView::~QmlProfilerStatisticsView() { delete d->model; delete d; } void QmlProfilerStatisticsView::clear() { d->m_eventTree->clear(); d->m_eventChildren->clear(); d->m_eventParents->clear(); } void QmlProfilerStatisticsView::restrictToRange(qint64 rangeStart, qint64 rangeEnd) { d->rangeStart = rangeStart; d->rangeEnd = rangeEnd; d->model->limitToRange(rangeStart, rangeEnd); } QModelIndex QmlProfilerStatisticsView::selectedModelIndex() const { return d->m_eventTree->selectedModelIndex(); } void QmlProfilerStatisticsView::contextMenuEvent(QContextMenuEvent *ev) { QMenu menu; QAction *copyRowAction = 0; QAction *copyTableAction = 0; QAction *showExtendedStatsAction = 0; QAction *getGlobalStatsAction = 0; QPoint position = ev->globalPos(); QList commonActions = QmlProfilerTool::profilerContextMenuActions(); foreach (QAction *act, commonActions) menu.addAction(act); if (mouseOnTable(position)) { menu.addSeparator(); if (selectedModelIndex().isValid()) copyRowAction = menu.addAction(tr("Copy Row")); copyTableAction = menu.addAction(tr("Copy Table")); showExtendedStatsAction = menu.addAction(tr("Extended Event Statistics")); showExtendedStatsAction->setCheckable(true); showExtendedStatsAction->setChecked(showExtendedStatistics()); } menu.addSeparator(); getGlobalStatsAction = menu.addAction(tr("Show Full Range")); if (!isRestrictedToRange()) getGlobalStatsAction->setEnabled(false); QAction *selectedAction = menu.exec(position); if (selectedAction) { if (selectedAction == copyRowAction) copyRowToClipboard(); if (selectedAction == copyTableAction) copyTableToClipboard(); if (selectedAction == getGlobalStatsAction) emit showFullRange(); if (selectedAction == showExtendedStatsAction) setShowExtendedStatistics(!showExtendedStatistics()); } } bool QmlProfilerStatisticsView::mouseOnTable(const QPoint &position) const { QPoint tableTopLeft = d->m_eventTree->mapToGlobal(QPoint(0,0)); QPoint tableBottomRight = d->m_eventTree->mapToGlobal(QPoint(d->m_eventTree->width(), d->m_eventTree->height())); return (position.x() >= tableTopLeft.x() && position.x() <= tableBottomRight.x() && position.y() >= tableTopLeft.y() && position.y() <= tableBottomRight.y()); } void QmlProfilerStatisticsView::copyTableToClipboard() const { d->m_eventTree->copyTableToClipboard(); } void QmlProfilerStatisticsView::copyRowToClipboard() const { d->m_eventTree->copyRowToClipboard(); } void QmlProfilerStatisticsView::selectByTypeId(int typeIndex) { if (d->m_eventTree->selectedTypeId() != typeIndex) d->m_eventTree->selectType(typeIndex); } void QmlProfilerStatisticsView::onVisibleFeaturesChanged(quint64 features) { for (int i = 0; i < MaximumRangeType; ++i) { RangeType range = static_cast(i); quint64 featureFlag = 1ULL << featureFromRangeType(range); if (QmlDebug::Constants::QML_JS_RANGE_FEATURES & featureFlag) d->model->setEventTypeAccepted(range, features & featureFlag); } d->model->limitToRange(d->rangeStart, d->rangeEnd); } bool QmlProfilerStatisticsView::isRestrictedToRange() const { return d->rangeStart != -1 || d->rangeEnd != -1; } void QmlProfilerStatisticsView::setShowExtendedStatistics(bool show) { d->m_eventTree->setShowExtendedStatistics(show); } bool QmlProfilerStatisticsView::showExtendedStatistics() const { return d->m_eventTree->showExtendedStatistics(); } class QmlProfilerStatisticsMainView::QmlProfilerStatisticsMainViewPrivate { public: QmlProfilerStatisticsMainViewPrivate(QmlProfilerStatisticsMainView *qq) : q(qq) {} int getFieldCount(); QString textForItem(QStandardItem *item, bool recursive = false) const; QmlProfilerStatisticsMainView *q; QmlProfilerStatisticsModel *model; QStandardItemModel *m_model; QList m_fieldShown; QHash m_columnIndex; // maps field enum to column index bool m_showExtendedStatistics; int m_firstNumericColumn; }; QmlProfilerStatisticsMainView::QmlProfilerStatisticsMainView( QWidget *parent, QmlProfilerStatisticsModel *model) : Utils::TreeView(parent), d(new QmlProfilerStatisticsMainViewPrivate(this)) { setViewDefaults(this); setObjectName(QLatin1String("QmlProfilerEventsTable")); setSortingEnabled(false); d->m_model = new QStandardItemModel(this); d->m_model->setSortRole(SortRole); setModel(d->m_model); connect(this, &QAbstractItemView::activated, this, &QmlProfilerStatisticsMainView::jumpToItem); d->model = model; connect(d->model, &QmlProfilerStatisticsModel::dataAvailable, this, &QmlProfilerStatisticsMainView::buildModel); connect(d->model, &QmlProfilerStatisticsModel::notesAvailable, this, &QmlProfilerStatisticsMainView::updateNotes); d->m_firstNumericColumn = 0; d->m_showExtendedStatistics = false; setFieldViewable(Name, true); setFieldViewable(Type, true); setFieldViewable(TimeInPercent, true); setFieldViewable(TotalTime, true); setFieldViewable(SelfTimeInPercent, true); setFieldViewable(SelfTime, true); setFieldViewable(CallCount, true); setFieldViewable(TimePerCall, true); setFieldViewable(MaxTime, true); setFieldViewable(MinTime, true); setFieldViewable(MedianTime, true); setFieldViewable(Details, true); buildModel(); } QmlProfilerStatisticsMainView::~QmlProfilerStatisticsMainView() { clear(); delete d->m_model; delete d; } void QmlProfilerStatisticsMainView::setFieldViewable(Fields field, bool show) { if (field < MaxFields) { int length = d->m_fieldShown.count(); if (field >= length) { for (int i=length; im_fieldShown << false; } d->m_fieldShown[field] = show; } } void QmlProfilerStatisticsMainView::setHeaderLabels() { int fieldIndex = 0; d->m_firstNumericColumn = 0; d->m_columnIndex.clear(); if (d->m_fieldShown[Name]) { d->m_columnIndex[Name] = fieldIndex; d->m_model->setHeaderData(fieldIndex++, Qt::Horizontal, QVariant(displayHeader(Location))); d->m_firstNumericColumn++; } if (d->m_fieldShown[Type]) { d->m_columnIndex[Type] = fieldIndex; d->m_model->setHeaderData(fieldIndex++, Qt::Horizontal, QVariant(displayHeader(Type))); d->m_firstNumericColumn++; } if (d->m_fieldShown[TimeInPercent]) { d->m_columnIndex[TimeInPercent] = fieldIndex; d->m_model->setHeaderData(fieldIndex++, Qt::Horizontal, QVariant(displayHeader(TimeInPercent))); } if (d->m_fieldShown[TotalTime]) { d->m_columnIndex[TotalTime] = fieldIndex; d->m_model->setHeaderData(fieldIndex++, Qt::Horizontal, QVariant(displayHeader(TotalTime))); } if (d->m_fieldShown[SelfTimeInPercent]) { d->m_columnIndex[Type] = fieldIndex; d->m_model->setHeaderData(fieldIndex++, Qt::Horizontal, QVariant(displayHeader(SelfTimeInPercent))); } if (d->m_fieldShown[SelfTime]) { d->m_columnIndex[SelfTime] = fieldIndex; d->m_model->setHeaderData(fieldIndex++, Qt::Horizontal, QVariant(displayHeader(SelfTime))); } if (d->m_fieldShown[CallCount]) { d->m_columnIndex[CallCount] = fieldIndex; d->m_model->setHeaderData(fieldIndex++, Qt::Horizontal, QVariant(displayHeader(CallCount))); } if (d->m_fieldShown[TimePerCall]) { d->m_columnIndex[TimePerCall] = fieldIndex; d->m_model->setHeaderData(fieldIndex++, Qt::Horizontal, QVariant(displayHeader(TimePerCall))); } if (d->m_fieldShown[MedianTime]) { d->m_columnIndex[MedianTime] = fieldIndex; d->m_model->setHeaderData(fieldIndex++, Qt::Horizontal, QVariant(displayHeader(MedianTime))); } if (d->m_fieldShown[MaxTime]) { d->m_columnIndex[MaxTime] = fieldIndex; d->m_model->setHeaderData(fieldIndex++, Qt::Horizontal, QVariant(displayHeader(MaxTime))); } if (d->m_fieldShown[MinTime]) { d->m_columnIndex[MinTime] = fieldIndex; d->m_model->setHeaderData(fieldIndex++, Qt::Horizontal, QVariant(displayHeader(MinTime))); } if (d->m_fieldShown[Details]) { d->m_columnIndex[Details] = fieldIndex; d->m_model->setHeaderData(fieldIndex++, Qt::Horizontal, QVariant(displayHeader(Details))); } } void QmlProfilerStatisticsMainView::setShowExtendedStatistics(bool show) { // Not checking if already set because we don't want the first call to skip d->m_showExtendedStatistics = show; if (show) { if (d->m_fieldShown[MedianTime]) showColumn(d->m_columnIndex[MedianTime]); if (d->m_fieldShown[MaxTime]) showColumn(d->m_columnIndex[MaxTime]); if (d->m_fieldShown[MinTime]) showColumn(d->m_columnIndex[MinTime]); } else{ if (d->m_fieldShown[MedianTime]) hideColumn(d->m_columnIndex[MedianTime]); if (d->m_fieldShown[MaxTime]) hideColumn(d->m_columnIndex[MaxTime]); if (d->m_fieldShown[MinTime]) hideColumn(d->m_columnIndex[MinTime]); } } bool QmlProfilerStatisticsMainView::showExtendedStatistics() const { return d->m_showExtendedStatistics; } void QmlProfilerStatisticsMainView::clear() { d->m_model->clear(); d->m_model->setColumnCount(d->getFieldCount()); setHeaderLabels(); setSortingEnabled(false); } int QmlProfilerStatisticsMainView::QmlProfilerStatisticsMainViewPrivate::getFieldCount() { int count = 0; for (int i=0; i < m_fieldShown.count(); ++i) if (m_fieldShown[i]) count++; return count; } void QmlProfilerStatisticsMainView::buildModel() { clear(); parseModel(); setShowExtendedStatistics(d->m_showExtendedStatistics); setRootIsDecorated(false); setSortingEnabled(true); sortByColumn(d->m_firstNumericColumn,Qt::DescendingOrder); expandAll(); if (d->m_fieldShown[Name]) resizeColumnToContents(0); if (d->m_fieldShown[Type]) resizeColumnToContents(d->m_fieldShown[Name]?1:0); collapseAll(); } void QmlProfilerStatisticsMainView::updateNotes(int typeIndex) { const QHash &eventList = d->model->getData(); const QHash ¬eList = d->model->getNotes(); QStandardItem *parentItem = d->m_model->invisibleRootItem(); for (int rowIndex = 0; rowIndex < parentItem->rowCount(); ++rowIndex) { int rowType = parentItem->child(rowIndex, 0)->data(TypeIdRole).toInt(); if (rowType != typeIndex && typeIndex != -1) continue; const QmlProfilerStatisticsModel::QmlEventStats &stats = eventList[rowType]; for (int columnIndex = 0; columnIndex < parentItem->columnCount(); ++columnIndex) { QStandardItem *item = parentItem->child(rowIndex, columnIndex); QHash::ConstIterator it = noteList.find(rowType); if (it != noteList.end()) { item->setBackground(colors()->noteBackground); item->setToolTip(it.value()); } else if (stats.isBindingLoop) { item->setBackground(colors()->noteBackground); item->setToolTip(tr("Binding loop detected.")); } else if (!item->toolTip().isEmpty()){ item->setBackground(colors()->defaultBackground); item->setToolTip(QString()); } } } } void QmlProfilerStatisticsMainView::parseModel() { const QHash &eventList = d->model->getData(); const QVector &typeList = d->model->getTypes(); QHash::ConstIterator it; for (it = eventList.constBegin(); it != eventList.constEnd(); ++it) { int typeIndex = it.key(); const QmlProfilerStatisticsModel::QmlEventStats &stats = it.value(); const QmlProfilerDataModel::QmlEventTypeData &event = (typeIndex != -1 ? typeList[typeIndex] : *rootEventType()); QStandardItem *parentItem = d->m_model->invisibleRootItem(); QList newRow; if (d->m_fieldShown[Name]) newRow << new StatisticsViewItem(event.displayName.isEmpty() ? tr("") : event.displayName); if (d->m_fieldShown[Type]) { QString typeString = QmlProfilerStatisticsMainView::nameForType(event.rangeType); QString toolTipText; if (event.rangeType == Binding) { if (event.detailType == (int)OptimizedBinding) { typeString = typeString + QLatin1Char(' ') + tr("(Opt)"); toolTipText = tr("Binding is evaluated by the optimized engine."); } else if (event.detailType == (int)V8Binding) { toolTipText = tr("Binding not optimized (might have side effects or assignments,\n" "references to elements in other files, loops, and so on.)"); } } newRow << new StatisticsViewItem(typeString); newRow.last()->setData(QVariant(typeString)); if (!toolTipText.isEmpty()) newRow.last()->setToolTip(toolTipText); } if (d->m_fieldShown[TimeInPercent]) { newRow << new StatisticsViewItem(QString::number(stats.percentOfTime,'f',2) + QLatin1String(" %")); newRow.last()->setData(QVariant(stats.percentOfTime)); } if (d->m_fieldShown[TotalTime]) { newRow << new StatisticsViewItem(QmlProfilerDataModel::formatTime(stats.duration)); newRow.last()->setData(QVariant(stats.duration)); } if (d->m_fieldShown[SelfTimeInPercent]) { newRow << new StatisticsViewItem(QString::number(stats.percentSelf, 'f', 2) + QLatin1String(" %")); newRow.last()->setData(QVariant(stats.percentSelf)); } if (d->m_fieldShown[SelfTime]) { newRow << new StatisticsViewItem(QmlProfilerDataModel::formatTime(stats.durationSelf)); newRow.last()->setData(QVariant(stats.durationSelf)); } if (d->m_fieldShown[CallCount]) { newRow << new StatisticsViewItem(QString::number(stats.calls)); newRow.last()->setData(QVariant(stats.calls)); } if (d->m_fieldShown[TimePerCall]) { newRow << new StatisticsViewItem(QmlProfilerDataModel::formatTime(stats.timePerCall)); newRow.last()->setData(QVariant(stats.timePerCall)); } if (d->m_fieldShown[MedianTime]) { newRow << new StatisticsViewItem(QmlProfilerDataModel::formatTime(stats.medianTime)); newRow.last()->setData(QVariant(stats.medianTime)); } if (d->m_fieldShown[MaxTime]) { newRow << new StatisticsViewItem(QmlProfilerDataModel::formatTime(stats.maxTime)); newRow.last()->setData(QVariant(stats.maxTime)); } if (d->m_fieldShown[MinTime]) { newRow << new StatisticsViewItem(QmlProfilerDataModel::formatTime(stats.minTime)); newRow.last()->setData(QVariant(stats.minTime)); } if (d->m_fieldShown[Details]) { newRow << new StatisticsViewItem(event.data.isEmpty() ? tr("Source code not available") : event.data); newRow.last()->setData(QVariant(event.data)); } if (!newRow.isEmpty()) { // no edit foreach (QStandardItem *item, newRow) item->setEditable(false); // metadata newRow.at(0)->setData(QVariant(typeIndex),TypeIdRole); newRow.at(0)->setData(QVariant(event.location.filename),FilenameRole); newRow.at(0)->setData(QVariant(event.location.line),LineRole); newRow.at(0)->setData(QVariant(event.location.column),ColumnRole); // append parentItem->appendRow(newRow); } } } QStandardItem *QmlProfilerStatisticsMainView::itemFromIndex(const QModelIndex &index) const { QStandardItem *indexItem = d->m_model->itemFromIndex(index); if (indexItem->parent()) return indexItem->parent()->child(indexItem->row(), 0); else return d->m_model->item(index.row(), 0); } QString QmlProfilerStatisticsMainView::nameForType(RangeType typeNumber) { switch (typeNumber) { case Painting: return QmlProfilerStatisticsMainView::tr("Paint"); case Compiling: return QmlProfilerStatisticsMainView::tr("Compile"); case Creating: return QmlProfilerStatisticsMainView::tr("Create"); case Binding: return QmlProfilerStatisticsMainView::tr("Binding"); case HandlingSignal: return QmlProfilerStatisticsMainView::tr("Signal"); case Javascript: return QmlProfilerStatisticsMainView::tr("JavaScript"); default: return QString(); } } void QmlProfilerStatisticsMainView::getStatisticsInRange(qint64 rangeStart, qint64 rangeEnd) { d->model->limitToRange(rangeStart, rangeEnd); } int QmlProfilerStatisticsMainView::selectedTypeId() const { QModelIndex index = selectedModelIndex(); if (!index.isValid()) return -1; QStandardItem *item = d->m_model->item(index.row(), 0); return item->data(TypeIdRole).toInt(); } void QmlProfilerStatisticsMainView::jumpToItem(const QModelIndex &index) { QStandardItem *infoItem = itemFromIndex(index); // show in editor getSourceLocation(infoItem, [this](const QString &fileName, int line, int column) { emit gotoSourceLocation(fileName, line, column); }); // show in callers/callees subwindow emit typeSelected(infoItem->data(TypeIdRole).toInt()); } void QmlProfilerStatisticsMainView::selectItem(const QStandardItem *item) { // If the same item is already selected, don't reselect it. QModelIndex index = d->m_model->indexFromItem(item); if (index != currentIndex()) { setCurrentIndex(index); // show in callers/callees subwindow emit typeSelected(itemFromIndex(index)->data(TypeIdRole).toInt()); } } void QmlProfilerStatisticsMainView::selectType(int typeIndex) { for (int i=0; im_model->rowCount(); i++) { QStandardItem *infoItem = d->m_model->item(i, 0); if (infoItem->data(TypeIdRole).toInt() == typeIndex) { selectItem(infoItem); return; } } } QModelIndex QmlProfilerStatisticsMainView::selectedModelIndex() const { QModelIndexList sel = selectedIndexes(); if (sel.isEmpty()) return QModelIndex(); else return sel.first(); } QString QmlProfilerStatisticsMainView::QmlProfilerStatisticsMainViewPrivate::textForItem( QStandardItem *item, bool recursive) const { QString str; if (recursive) { // indentation QStandardItem *itemParent = item->parent(); while (itemParent) { str += QLatin1String(" "); itemParent = itemParent->parent(); } } // item's data int colCount = m_model->columnCount(); for (int j = 0; j < colCount; ++j) { QStandardItem *colItem = item->parent() ? item->parent()->child(item->row(),j) : m_model->item(item->row(),j); str += colItem->data(Qt::DisplayRole).toString(); if (j < colCount-1) str += QLatin1Char('\t'); } str += QLatin1Char('\n'); // recursively print children if (recursive && item->child(0)) for (int j = 0; j != item->rowCount(); j++) str += textForItem(item->child(j)); return str; } void QmlProfilerStatisticsMainView::copyTableToClipboard() const { QString str; // headers int columnCount = d->m_model->columnCount(); for (int i = 0; i < columnCount; ++i) { str += d->m_model->headerData(i, Qt::Horizontal, Qt::DisplayRole).toString(); if (i < columnCount - 1) str += QLatin1Char('\t'); else str += QLatin1Char('\n'); } // data int rowCount = d->m_model->rowCount(); for (int i = 0; i != rowCount; ++i) { str += d->textForItem(d->m_model->item(i)); } QClipboard *clipboard = QApplication::clipboard(); clipboard->setText(str, QClipboard::Selection); clipboard->setText(str, QClipboard::Clipboard); } void QmlProfilerStatisticsMainView::copyRowToClipboard() const { QString str; str = d->textForItem(d->m_model->itemFromIndex(selectedModelIndex()), false); QClipboard *clipboard = QApplication::clipboard(); clipboard->setText(str, QClipboard::Selection); clipboard->setText(str, QClipboard::Clipboard); } class QmlProfilerStatisticsRelativesView::QmlProfilerStatisticsRelativesViewPrivate { public: QmlProfilerStatisticsRelativesViewPrivate(QmlProfilerStatisticsRelativesView *qq):q(qq) {} ~QmlProfilerStatisticsRelativesViewPrivate() {} QmlProfilerStatisticsRelativesModel *model; QmlProfilerStatisticsRelativesView *q; }; QmlProfilerStatisticsRelativesView::QmlProfilerStatisticsRelativesView( QmlProfilerStatisticsRelativesModel *model, QWidget *parent) : Utils::TreeView(parent), d(new QmlProfilerStatisticsRelativesViewPrivate(this)) { setViewDefaults(this); setSortingEnabled(false); d->model = model; QStandardItemModel *itemModel = new QStandardItemModel(this); itemModel->setSortRole(SortRole); setModel(itemModel); setRootIsDecorated(false); updateHeader(); connect(this, &QAbstractItemView::activated, this, &QmlProfilerStatisticsRelativesView::jumpToItem); // Clear when new data available as the selection may be invalid now. connect(d->model, &QmlProfilerStatisticsRelativesModel::dataAvailable, this, &QmlProfilerStatisticsRelativesView::clear); } QmlProfilerStatisticsRelativesView::~QmlProfilerStatisticsRelativesView() { delete d; } void QmlProfilerStatisticsRelativesView::displayType(int typeIndex) { rebuildTree(d->model->getData(typeIndex)); updateHeader(); resizeColumnToContents(0); setSortingEnabled(true); sortByColumn(2); } void QmlProfilerStatisticsRelativesView::rebuildTree( const QmlProfilerStatisticsRelativesModel::QmlStatisticsRelativesMap &map) { Q_ASSERT(treeModel()); treeModel()->clear(); QStandardItem *topLevelItem = treeModel()->invisibleRootItem(); const QVector &typeList = d->model->getTypes(); QmlProfilerStatisticsRelativesModel::QmlStatisticsRelativesMap::const_iterator it; for (it = map.constBegin(); it != map.constEnd(); ++it) { const QmlProfilerStatisticsRelativesModel::QmlStatisticsRelativesData &event = it.value(); int typeIndex = it.key(); const QmlProfilerDataModel::QmlEventTypeData &type = (typeIndex != -1 ? typeList[typeIndex] : *rootEventType()); QList newRow; // ToDo: here we were going to search for the data in the other model // maybe we should store the data in this model and get it here // no indirections at this level of abstraction! newRow << new StatisticsViewItem(type.displayName.isEmpty() ? tr("") : type.displayName); newRow << new StatisticsViewItem(QmlProfilerStatisticsMainView::nameForType( type.rangeType)); newRow << new StatisticsViewItem(QmlProfilerDataModel::formatTime(event.duration)); newRow << new StatisticsViewItem(QString::number(event.calls)); newRow << new StatisticsViewItem(type.data.isEmpty() ? tr("Source code not available") : type.data); newRow.at(0)->setData(QVariant(typeIndex), TypeIdRole); newRow.at(0)->setData(QVariant(type.location.filename),FilenameRole); newRow.at(0)->setData(QVariant(type.location.line),LineRole); newRow.at(0)->setData(QVariant(type.location.column),ColumnRole); newRow.at(1)->setData(QVariant(QmlProfilerStatisticsMainView::nameForType(type.rangeType))); newRow.at(2)->setData(QVariant(event.duration)); newRow.at(3)->setData(QVariant(event.calls)); newRow.at(4)->setData(QVariant(type.data)); if (event.isBindingLoop) { foreach (QStandardItem *item, newRow) { item->setBackground(colors()->noteBackground); item->setToolTip(tr("Part of binding loop.")); } } foreach (QStandardItem *item, newRow) item->setEditable(false); topLevelItem->appendRow(newRow); } } void QmlProfilerStatisticsRelativesView::clear() { if (treeModel()) { treeModel()->clear(); updateHeader(); } } void QmlProfilerStatisticsRelativesView::updateHeader() { bool calleesView = qobject_cast(d->model) != 0; if (treeModel()) { treeModel()->setColumnCount(5); int columnIndex = 0; if (calleesView) treeModel()->setHeaderData(columnIndex++, Qt::Horizontal, QVariant(displayHeader(Callee))); else treeModel()->setHeaderData(columnIndex++, Qt::Horizontal, QVariant(displayHeader(Caller))); treeModel()->setHeaderData(columnIndex++, Qt::Horizontal, QVariant(displayHeader(Type))); treeModel()->setHeaderData(columnIndex++, Qt::Horizontal, QVariant(displayHeader(TotalTime))); treeModel()->setHeaderData(columnIndex++, Qt::Horizontal, QVariant(displayHeader(CallCount))); if (calleesView) treeModel()->setHeaderData(columnIndex++, Qt::Horizontal, QVariant(displayHeader(CalleeDescription))); else treeModel()->setHeaderData(columnIndex++, Qt::Horizontal, QVariant(displayHeader(CallerDescription))); } } QStandardItemModel *QmlProfilerStatisticsRelativesView::treeModel() { return qobject_cast(model()); } void QmlProfilerStatisticsRelativesView::jumpToItem(const QModelIndex &index) { if (treeModel()) { QStandardItem *infoItem = treeModel()->item(index.row(), 0); // show in editor getSourceLocation(infoItem, [this](const QString &fileName, int line, int column) { emit gotoSourceLocation(fileName, line, column); }); emit typeClicked(infoItem->data(TypeIdRole).toInt()); } } } // namespace Internal } // namespace QmlProfiler