1 /***************************************************************************
2     qgsprofilerpanelwidget.cpp
3     -------------------------
4     begin                : May 2020
5     copyright            : (C) 2020 by Nyall Dawson
6     email                : nyall dot dawson at gmail dot com
7  ***************************************************************************
8  *                                                                         *
9  *   This program is free software; you can redistribute it and/or modify  *
10  *   it under the terms of the GNU General Public License as published by  *
11  *   the Free Software Foundation; either version 2 of the License, or     *
12  *   (at your option) any later version.                                   *
13  *                                                                         *
14  ***************************************************************************/
15 
16 #include "qgsprofilerpanelwidget.h"
17 #include "qgsruntimeprofiler.h"
18 #include "qgslogger.h"
19 #include "qgis.h"
20 #include <QPainter>
21 #include <cmath>
22 
23 //
24 // QgsProfilerPanelWidget
25 //
26 
QgsProfilerPanelWidget(QgsRuntimeProfiler * profiler,QWidget * parent)27 QgsProfilerPanelWidget::QgsProfilerPanelWidget( QgsRuntimeProfiler *profiler, QWidget *parent )
28   : QgsDevToolWidget( parent )
29   , mProfiler( profiler )
30 {
31   setupUi( this );
32 
33   mProxyModel = new QgsProfilerProxyModel( profiler, this );
34 
35   mTreeView->setModel( mProxyModel );
36   mTreeView->setSortingEnabled( true );
37 
38   //mTreeView->resizeColumnToContents( 0 );
39   //mTreeView->resizeColumnToContents( 1 );
40 
41   mTreeView->setItemDelegateForColumn( 1, new CostDelegate( QgsRuntimeProfilerNode::Elapsed, QgsRuntimeProfilerNode::ParentElapsed, mTreeView ) );
42 
43   connect( mProfiler, &QgsRuntimeProfiler::groupAdded, this, [ = ]( const QString & group )
44   {
45     mCategoryComboBox->addItem( QgsRuntimeProfiler::translateGroupName( group ).isEmpty() ? group : QgsRuntimeProfiler::translateGroupName( group ), group );
46     if ( mCategoryComboBox->count() == 1 )
47     {
48       mCategoryComboBox->setCurrentIndex( 0 );
49       mProxyModel->setGroup( mCategoryComboBox->currentData().toString() );
50     }
51   } );
52 
53   connect( mCategoryComboBox, qgis::overload< int >::of( &QComboBox::currentIndexChanged ), this, [ = ]( int )
54   {
55     mProxyModel->setGroup( mCategoryComboBox->currentData().toString() );
56   } );
57 
58   const QSet<QString> groups = mProfiler->groups();
59   for ( const QString &group : groups )
60   {
61     mCategoryComboBox->addItem( QgsRuntimeProfiler::translateGroupName( group ).isEmpty() ? group : QgsRuntimeProfiler::translateGroupName( group ), group );
62   }
63 
64   if ( mCategoryComboBox->count() > 0 )
65   {
66     mCategoryComboBox->setCurrentIndex( 0 );
67     mProxyModel->setGroup( mCategoryComboBox->currentData().toString() );
68   }
69 }
70 
71 //
72 // QgsProfilerProxyModel
73 //
74 
QgsProfilerProxyModel(QgsRuntimeProfiler * profiler,QObject * parent)75 QgsProfilerProxyModel::QgsProfilerProxyModel( QgsRuntimeProfiler *profiler, QObject *parent )
76   : QSortFilterProxyModel( parent )
77 {
78   setSourceModel( profiler );
79 }
80 
setGroup(const QString & group)81 void QgsProfilerProxyModel::setGroup( const QString &group )
82 {
83   mGroup = group;
84   invalidateFilter();
85 }
86 
filterAcceptsRow(int row,const QModelIndex & source_parent) const87 bool QgsProfilerProxyModel::filterAcceptsRow( int row, const QModelIndex &source_parent ) const
88 {
89   QModelIndex index = sourceModel()->index( row, 0, source_parent );
90   return sourceModel()->data( index, QgsRuntimeProfilerNode::Group ).toString() == mGroup;
91 }
92 
93 
94 //
95 // CostDelegate
96 //
97 
98 // adapted from KDAB's excellent "hotspot" application!
99 
CostDelegate(quint32 sortRole,quint32 totalCostRole,QObject * parent)100 CostDelegate::CostDelegate( quint32 sortRole, quint32 totalCostRole, QObject *parent )
101   : QStyledItemDelegate( parent )
102   , m_sortRole( sortRole )
103   , m_totalCostRole( totalCostRole )
104 {
105 }
106 
107 CostDelegate::~CostDelegate() = default;
108 
paint(QPainter * painter,const QStyleOptionViewItem & option,const QModelIndex & index) const109 void CostDelegate::paint( QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index ) const
110 {
111   // TODO: handle negative values
112   const auto cost = index.data( m_sortRole ).toDouble();
113   if ( cost == 0 )
114   {
115     QStyledItemDelegate::paint( painter, option, index );
116     return;
117   }
118 
119   const auto totalCost = index.data( m_totalCostRole ).toDouble();
120   const auto fraction = std::abs( float( cost ) / totalCost );
121 
122   auto rect = option.rect;
123   rect.setWidth( rect.width() * fraction );
124 
125   const auto &brush = painter->brush();
126   const auto &pen = painter->pen();
127 
128   painter->setPen( Qt::NoPen );
129 
130   if ( option.features & QStyleOptionViewItem::Alternate )
131   {
132     // we must handle this ourselves as otherwise the custom background
133     // would get painted over with the alternate background color
134     painter->setBrush( option.palette.alternateBase() );
135     painter->drawRect( option.rect );
136   }
137 
138   auto color = QColor::fromHsv( 120 - fraction * 120, 255, 255, ( -( ( fraction - 1 ) * ( fraction - 1 ) ) ) * 120 + 120 );
139   painter->setBrush( color );
140   painter->drawRect( rect );
141 
142   painter->setBrush( brush );
143   painter->setPen( pen );
144 
145   if ( option.features & QStyleOptionViewItem::Alternate )
146   {
147     auto o = option;
148     o.features &= ~QStyleOptionViewItem::Alternate;
149     QStyledItemDelegate::paint( painter, o, index );
150   }
151   else
152   {
153     QStyledItemDelegate::paint( painter, option, index );
154   }
155 }
156 
157