partition_widget.cpp
Go to the documentation of this file.
00001 // *****************************************************************************
00002 //
00003 // Copyright (c) 2015, Southwest Research Institute® (SwRI®)
00004 // All rights reserved.
00005 //
00006 // Redistribution and use in source and binary forms, with or without
00007 // modification, are permitted provided that the following conditions are met:
00008 //     * Redistributions of source code must retain the above copyright
00009 //       notice, this list of conditions and the following disclaimer.
00010 //     * Redistributions in binary form must reproduce the above copyright
00011 //       notice, this list of conditions and the following disclaimer in the
00012 //       documentation and/or other materials provided with the distribution.
00013 //     * Neither the name of Southwest Research Institute® (SwRI®) nor the
00014 //       names of its contributors may be used to endorse or promote products
00015 //       derived from this software without specific prior written permission.
00016 //
00017 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
00018 // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
00019 // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
00020 // ARE DISCLAIMED. IN NO EVENT SHALL Southwest Research Institute® BE LIABLE 
00021 // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 
00022 // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
00023 // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 
00024 // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 
00025 // LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 
00026 // OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
00027 // DAMAGE.
00028 //
00029 // *****************************************************************************
00030 #include <swri_profiler_tools/partition_widget.h>
00031 
00032 #include <QPainter>
00033 #include <QVBoxLayout>
00034 #include <QToolTip>
00035 #include <QHelpEvent>
00036 #include <QMouseEvent>
00037 #include <QDebug>
00038 
00039 #include <swri_profiler_tools/util.h>
00040 #include <swri_profiler_tools/profile_database.h>
00041 #include <swri_profiler_tools/variant_animation.h>
00042 
00043 namespace swri_profiler_tools
00044 {
00045 static QColor colorFromString(const QString &name)
00046 {
00047   size_t name_hash = std::hash<std::string>{}(name.toStdString());
00048   
00049   int h = (name_hash >> 0) % 255;
00050   int s = (name_hash >> 8) % 200 + 55;
00051   int v = (name_hash >> 16) % 200 + 55;
00052   return QColor::fromHsv(h, s, v);
00053 }
00054 
00055 PartitionWidget::PartitionWidget(QWidget *parent)
00056   :
00057   QWidget(parent),
00058   db_(NULL)
00059 {
00060   view_animator_ = new VariantAnimation(this);
00061   view_animator_->setEasingCurve(QEasingCurve::InOutCubic);
00062   QObject::connect(view_animator_, SIGNAL(valueChanged(const QVariant &)),
00063                    this, SLOT(update()));
00064 }
00065 
00066 PartitionWidget::~PartitionWidget()
00067 {
00068 }
00069 
00070 void PartitionWidget::setDatabase(ProfileDatabase *db)
00071 {
00072   if (db_) {
00073     // note(exjohnson): we can implement this later if desired, but
00074     // currently no use case for it.
00075     qWarning("PartitionWidget: Cannot change the profile database.");
00076     return;
00077   }
00078 
00079   db_ = db;
00080 
00081   updateData();
00082   QObject::connect(db_, SIGNAL(dataAdded(int)),    this, SLOT(updateData()));
00083   QObject::connect(db_, SIGNAL(profileAdded(int)), this, SLOT(updateData()));
00084   QObject::connect(db_, SIGNAL(nodesAdded(int)),   this, SLOT(updateData()));
00085 }
00086 
00087 void PartitionWidget::updateData()
00088 {
00089   if (!active_key_.isValid()) {
00090     return;
00091   }    
00092 
00093   const Profile &profile = db_->profile(active_key_.profileKey());
00094   Layout layout = layoutProfile(profile);
00095   QRectF data_rect = dataRect(layout);
00096   view_animator_->setEndValue(data_rect);
00097   current_layout_ = layout;
00098 
00099   update();
00100 }
00101 
00102 void PartitionWidget::paintEvent(QPaintEvent *)
00103 {
00104   QPainter painter(this);
00105     
00106   if (current_layout_.empty()) {
00107     QRect win_rect(0,0,width(), height());
00108     painter.setBrush(Qt::white);
00109     painter.drawRect(win_rect.adjusted(1,1,-1,-1));
00110     return;
00111   }
00112 
00113   QRectF data_rect = view_animator_->currentValue().toRectF();
00114   QRectF win_rect(QPointF(0, 0), QPointF(width()-1, height()-1));
00115   win_rect = win_rect.adjusted(1,1,-1,-1);
00116   win_from_data_ = getTransform(win_rect, data_rect);
00117 
00118   const Profile &profile = db_->profile(active_key_.profileKey());
00119   renderLayout(painter, win_from_data_, current_layout_, profile);
00120 }
00121 
00122 QRectF PartitionWidget::dataRect(const Layout &layout) const
00123 {
00124   if (layout.empty()) {
00125     return QRectF(0.0, 0.0, 1.0, 1.0);
00126   }
00127 
00128   double right = layout.back().rect.right();
00129 
00130   for (auto const &item : layout) {
00131     if (active_key_.nodeKey() == item.node_key && item.exclusive == false) {
00132       QRectF rect = item.rect;
00133         
00134       rect.setLeft(std::max(0.0, rect.left()-0.2));
00135       rect.setRight(right);
00136         
00137       double margin = 0.05 * rect.height();
00138       rect.setTop(std::max(0.0, rect.top() - margin));
00139       rect.setBottom(std::min(1.0, rect.bottom() + margin));
00140       return rect;
00141     }
00142   }
00143 
00144   qWarning("Active node key was not found in layout");
00145   return QRectF(QPointF(0.0, 0.0), QPointF(right, 1.0));
00146 }
00147 
00148 void PartitionWidget::setActiveNode(int profile_key, int node_key)
00149 {
00150   const DatabaseKey new_key(profile_key, node_key);
00151 
00152   if (new_key == active_key_) {
00153     return;
00154   }
00155 
00156   bool first = true;
00157   if (active_key_.isValid()) {
00158     first = false;
00159   }
00160     
00161   active_key_ = new_key;
00162   
00163   const Profile &profile = db_->profile(active_key_.profileKey());
00164   Layout layout = layoutProfile(profile);
00165   QRectF data_rect = dataRect(layout);
00166   current_layout_ = layout;
00167 
00168   if (!first) {
00169     view_animator_->stop();
00170     view_animator_->setStartValue(view_animator_->endValue());
00171     view_animator_->setEndValue(data_rect);
00172     view_animator_->setDuration(500);
00173     view_animator_->start();
00174   } else {
00175     view_animator_->setStartValue(data_rect);
00176     view_animator_->setEndValue(data_rect);
00177   }
00178 
00179   emit activeNodeChanged(profile_key, node_key);
00180 }
00181 
00182 PartitionWidget::Layout PartitionWidget::layoutProfile(const Profile &profile)
00183 {
00184   Layout layout;
00185   
00186   const ProfileNode &root_node = profile.rootNode();
00187   if (!root_node.isValid()) {
00188     qWarning("Profile returned invalid root node.");
00189     return layout;
00190   }
00191 
00192   if (root_node.data().empty()) {
00193     return layout;
00194   }
00195 
00196   double time_scale = root_node.data().back().cumulative_inclusive_duration_ns;
00197 
00198   int column = 0;
00199   LayoutItem root_item;
00200   root_item.node_key = root_node.nodeKey();
00201   root_item.exclusive = false;
00202   root_item.rect = QRectF(column, 0.0, 1, 1.0);
00203   layout.push_back(root_item);
00204 
00205   bool keep_going = root_node.hasChildren();
00206 
00207   std::vector<LayoutItem> parents;
00208   std::vector<LayoutItem> children;
00209   parents.push_back(root_item);
00210   
00211   while (keep_going) {
00212     // We going to stop unless we see some children.
00213     keep_going = false;
00214     column++;
00215     
00216     double span_start = 0.0;
00217     for (auto const &parent_item : parents) {      
00218       const ProfileNode &parent_node = profile.node(parent_item.node_key);      
00219       
00220       // Add the carry-over exclusive item.
00221       {
00222         double height =  parent_node.data().back().cumulative_exclusive_duration_ns/time_scale;
00223         LayoutItem item;
00224         item.node_key = parent_item.node_key;
00225         item.exclusive = true;
00226         item.rect = QRectF(column, span_start, 1, height);
00227         children.push_back(item);
00228         span_start = item.rect.bottom();
00229       }
00230 
00231       // Don't add children for an exclusive item because they've already been added.
00232       if (parent_item.exclusive) {
00233         continue;
00234       }
00235       
00236       for (int child_key : parent_node.childKeys()) {
00237         const ProfileNode &child_node = profile.node(child_key);
00238         double height = child_node.data().back().cumulative_inclusive_duration_ns / time_scale;
00239         
00240         LayoutItem item;
00241         item.node_key = child_key;
00242         item.exclusive = false;
00243         item.rect = QRectF(column, span_start, 1, height);
00244         children.push_back(item);
00245         span_start = item.rect.bottom();
00246 
00247         keep_going |= child_node.hasChildren();
00248       }
00249     }
00250 
00251     layout.insert(layout.end(), children.begin(), children.end());
00252     parents.swap(children);
00253     children.clear();
00254   }
00255 
00256   return layout;
00257 }
00258 
00259 void PartitionWidget::renderLayout(QPainter &painter,
00260                                    const QTransform &win_from_data,
00261                                    const Layout &layout,
00262                                    const Profile &profile)
00263 {
00264   // Set painter to use a single-pixel black pen.
00265   painter.setPen(Qt::black);  
00266 
00267   double right = layout.back().rect.right();
00268   
00269   for (auto const &item : layout) {
00270     if (item.exclusive) {
00271       continue;
00272     }
00273       
00274     const ProfileNode &node = profile.node(item.node_key);
00275     QColor color = colorFromString(node.name());
00276 
00277     QRectF data_rect = item.rect;
00278     data_rect.setRight(right);
00279     QRectF win_rect = win_from_data.mapRect(data_rect);
00280     QRect int_rect = roundRectF(win_rect);
00281 
00282     painter.setBrush(color);
00283     painter.drawRect(int_rect.adjusted(0,0,-1,-1));
00284   }
00285 }
00286 
00287 QTransform PartitionWidget::getTransform(const QRectF &win_rect,
00288                                          const QRectF &data_rect)
00289 {
00290   double sx = win_rect.width() / data_rect.width();
00291   double sy = win_rect.height() / data_rect.height();
00292   double tx = win_rect.topLeft().x() - sx*data_rect.topLeft().x();
00293   double ty = win_rect.topLeft().y() - sy*data_rect.topLeft().y();
00294   
00295   QTransform win_from_data(sx, 0.0, 0.0,
00296                            0.0, sy, 0.0,
00297                            tx, ty, 1.0);
00298   return win_from_data;
00299 }
00300 
00301 int PartitionWidget::itemAtPoint(const QPointF &point) const
00302 {
00303   for (size_t i = 0; i < current_layout_.size(); i++) {
00304     auto const &item = current_layout_[i];
00305     if (item.rect.contains(point)) {
00306       return i;
00307     }
00308   }
00309   return -1;
00310 }
00311 
00312 bool PartitionWidget::event(QEvent *event)
00313 {
00314   if (event->type() == QEvent::ToolTip) {
00315     toolTipEvent(static_cast<QHelpEvent*>(event));
00316     event->accept();
00317     return true;
00318   }
00319   return QWidget::event(event);
00320 }
00321 
00322 void PartitionWidget::toolTipEvent(QHelpEvent *event)
00323 {
00324   QTransform data_from_win = win_from_data_.inverted();
00325   int index = itemAtPoint(data_from_win.map(QPointF(event->pos())));
00326 
00327   if (index < 0) {
00328     QToolTip::hideText();
00329     return;
00330   }
00331 
00332   const Profile &profile = db_->profile(active_key_.profileKey());
00333   const LayoutItem &item = current_layout_[index];
00334 
00335   QString tool_tip;
00336   if (item.node_key == profile.rootKey()) {
00337     tool_tip = profile.name();
00338   } else {  
00339     tool_tip = profile.node(item.node_key).path();
00340     if (item.exclusive) {
00341       tool_tip += " [exclusive]";
00342     }
00343   }
00344   
00345   QRectF win_rect = win_from_data_.mapRect(item.rect);
00346   QRect int_rect = roundRectF(win_rect);  
00347   QToolTip::showText(event->globalPos(), tool_tip, this, int_rect);  
00348 }
00349 
00350 void PartitionWidget::mousePressEvent(QMouseEvent *event)
00351 {
00352 }
00353 
00354 void PartitionWidget::mouseDoubleClickEvent(QMouseEvent *event)
00355 {
00356   QTransform data_from_win = win_from_data_.inverted();
00357 #if QT_VERSION >= 0x050000
00358   int index = itemAtPoint(data_from_win.map(event->localPos()));
00359 #else
00360   int index = itemAtPoint(data_from_win.map(event->posF()));
00361 #endif
00362 
00363   if (index < 0) {
00364     return;
00365   }
00366 
00367   const LayoutItem &item = current_layout_[index];
00368   setActiveNode(active_key_.profileKey(), item.node_key);
00369 }    
00370 }  // namespace swri_profiler_tools


swri_profiler_tools
Author(s):
autogenerated on Sat Apr 27 2019 03:08:49