File indexing completed on 2024-04-28 05:41:35

0001 /*
0002     This file is part of KCachegrind.
0003 
0004     SPDX-FileCopyrightText: 2003-2016 Josef Weidendorfer <Josef.Weidendorfer@gmx.de>
0005 
0006     SPDX-License-Identifier: GPL-2.0-only
0007 */
0008 
0009 /*
0010  * For part file selection, to be put into a QDockWindow
0011  */
0012 
0013 #include "partselection.h"
0014 
0015 #include <QLabel>
0016 #include <QPushButton>
0017 #include <QVBoxLayout>
0018 #include <QAction>
0019 #include <QMenu>
0020 
0021 #include "toplevelbase.h"
0022 #include "partgraph.h"
0023 #include "globalconfig.h"
0024 #include "config.h"
0025 
0026 //
0027 // PartSelection
0028 //
0029 
0030 // defaults
0031 #define DEFAULT_PARTITIONMODE "Inclusive"
0032 #define DEFAULT_DIAGRAMMODE   false
0033 #define DEFAULT_DRAWFRAMES    true
0034 #define DEFAULT_SHOWINFO      false
0035 #define DEFAULT_FUNCTIONZOOM  false
0036 #define DEFAULT_CALLEELEVELS  1
0037 #define DEFAULT_DRAWNAME      true
0038 #define DEFAULT_DRAWCOST      true
0039 #define DEFAULT_FORCESTRINGS  false
0040 #define DEFAULT_ALLOWROTATION true
0041 
0042 PartSelection::PartSelection( TopLevelBase* top,
0043                               QWidget* parent)
0044     : QWidget(parent), TraceItemView(nullptr, top)
0045 {
0046     _inSelectionUpdate = false;
0047 
0048     setWindowTitle(tr("Parts Overview"));
0049 
0050     QVBoxLayout* vboxLayout = new QVBoxLayout(this);
0051     vboxLayout->setSpacing(6);
0052     vboxLayout->setContentsMargins(6, 6, 6, 6);
0053 
0054     _partAreaWidget = new PartAreaWidget(this);
0055     _partAreaWidget->setMinimumHeight(50);
0056     _partAreaWidget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
0057     _partAreaWidget->setMaxSelectDepth(2);
0058     _partAreaWidget->setSelectionMode(TreeMapWidget::Extended);
0059     _partAreaWidget->setSplitMode(TreeMapItem::HAlternate);
0060     _partAreaWidget->setVisibleWidth(2, true);
0061     _partAreaWidget->setFieldType(0, tr("Name", "A thing's name"));
0062     _partAreaWidget->setFieldType(1, tr("Cost" ));
0063     vboxLayout->addWidget(_partAreaWidget);
0064 
0065     _rangeLabel = new QLabel(this);
0066     _rangeLabel->setWordWrap(false);
0067     _rangeLabel->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
0068     vboxLayout->addWidget(_rangeLabel);
0069     _rangeLabel->setText(tr("(no trace parts)"));
0070     _showInfo    = true; // label is currently shown
0071 
0072     _diagramMode = DEFAULT_DIAGRAMMODE;
0073     _drawFrames  = DEFAULT_DRAWFRAMES;
0074 
0075     // sets _showInfo
0076     showInfo(DEFAULT_SHOWINFO);
0077 
0078     _partAreaWidget->setAllowRotation(DEFAULT_ALLOWROTATION);
0079 
0080     connect(_partAreaWidget, SIGNAL(selectionChanged()),
0081             this, SLOT(selectionChanged()));
0082     connect(_partAreaWidget, &TreeMapWidget::currentChanged,
0083             this, &PartSelection::currentChangedSlot);
0084     connect(_partAreaWidget, &TreeMapWidget::doubleClicked,
0085             this, &PartSelection::doubleClicked);
0086     connect(_partAreaWidget,
0087             &TreeMapWidget::contextMenuRequested,
0088             this,
0089             &PartSelection::contextMenuRequested);
0090 
0091     setWhatsThis(whatsThis());
0092 }
0093 
0094 QString PartSelection::whatsThis() const
0095 {
0096     return tr( "<b>The Parts Overview</b>"
0097                "<p>A trace consists of multiple trace parts when "
0098                "there are several profile data files from one profile run. "
0099                "The Trace Part Overview dockable shows these, "
0100                "horizontally ordered in execution time; "
0101                "the rectangle sizes are proportional to the total "
0102                "cost spent in the parts. You can select one or several "
0103                "parts to constrain all costs shown to these parts only."
0104                "</p>"
0105                "<p>The parts are further subdivided: there is a "
0106                "partitioning and an callee split mode: "
0107                "<ul><li>Partitioning: You see the "
0108                "partitioning into groups for a trace part, according to "
0109                "the group type selected. E.g. if ELF object groups are "
0110                "selected, you see colored rectangles for each "
0111                "used ELF object (shared library or executable), sized "
0112                "according to the cost spent therein.</li>"
0113                "<li>Callee: A rectangle showing the inclusive "
0114                "cost of the current selected function in the trace part "
0115                "is shown. "
0116                "This is split up into smaller rectangles to show the costs of its "
0117                "callees.</li></ul></p>");
0118 }
0119 
0120 void PartSelection::setData(TraceData* data)
0121 {
0122     TraceItemView::setData(data);
0123     _partAreaWidget->setData(data);
0124 }
0125 
0126 
0127 CostItem* PartSelection::canShow(CostItem* i)
0128 {
0129     ProfileContext::Type t = i ? i->type() : ProfileContext::InvalidType;
0130 
0131     switch(t) {
0132     case ProfileContext::Function:
0133     case ProfileContext::FunctionCycle:
0134         return i;
0135     default:
0136         break;
0137     }
0138     return nullptr;
0139 }
0140 
0141 /* Helper for doUpdate(), called on partsChanged event.
0142  * This makes the graph selection the same to the parts in the list
0143  */
0144 void PartSelection::selectParts(const TracePartList& list)
0145 {
0146     _inSelectionUpdate = true;
0147 
0148     qDebug("Entering PartSelection::activePartsChangedSlot");
0149 
0150     TreeMapItemList l = *_partAreaWidget->base()->children();
0151     // first deselect inactive, then select active (makes current active)
0152     foreach(TreeMapItem* i, l) {
0153         TracePart* part = ((PartItem*)i)->part();
0154         bool active = list.contains(part);
0155         if (!active && _partAreaWidget->isSelected(i)) {
0156 #if 0
0157             qDebug("PartSelection::selectParts: Part %s changed to unselected.",
0158                    ((PartItem*)i)->part()->shortName());
0159 #endif
0160 
0161             _partAreaWidget->setSelected(i, false);
0162         }
0163     }
0164     foreach(TreeMapItem* i, l) {
0165         TracePart* part = ((PartItem*)i)->part();
0166         bool active = list.contains(part);
0167         if (active && !_partAreaWidget->isSelected(i)) {
0168 #if 0
0169             qDebug("PartSelection::selectParts: Part %s changed to selected.",
0170                    ((PartItem*)i)->part()->shortName());
0171 #endif
0172             _partAreaWidget->setSelected(i, true);
0173         }
0174     }
0175 
0176     _inSelectionUpdate = false;
0177 
0178     qDebug("Leaving PartSelection::activePartsChangedSlot");
0179 }
0180 
0181 
0182 void PartSelection::doUpdate(int changeType, bool)
0183 {
0184     if (changeType == eventType2Changed) return;
0185     if (changeType == selectedItemChanged) return;
0186 
0187     if (changeType & eventTypeChanged)
0188         _partAreaWidget->setEventType(_eventType);
0189 
0190     if (changeType & groupTypeChanged)
0191         _partAreaWidget->setGroupType(_groupType);
0192 
0193     if (changeType & activeItemChanged) {
0194         TraceFunction* f = nullptr;
0195 
0196         if (_activeItem) {
0197             switch(_activeItem->type()) {
0198             case ProfileContext::Function:
0199             case ProfileContext::FunctionCycle:
0200                 f = (TraceFunction*)_activeItem;
0201                 break;
0202             default:
0203                 break;
0204             }
0205         }
0206 
0207         _inSelectionUpdate = true;
0208         _partAreaWidget->setFunction(f);
0209         _inSelectionUpdate = false;
0210     }
0211 
0212     if (changeType & partsChanged)
0213         selectParts(_partList);
0214 
0215     _partAreaWidget->redraw();
0216     fillInfo();
0217 }
0218 
0219 
0220 
0221 void PartSelection::currentChangedSlot(TreeMapItem* i, bool kbd)
0222 {
0223     if (!i) return;
0224     if (!kbd) return;
0225     if (i->text(0).isEmpty()) return;
0226 
0227     if (_topLevel) {
0228         QString str = i->text(0);
0229         if (!i->text(1).isEmpty())
0230             str += " (" + i->text(1) + ')';
0231         QString msg = tr("Profile Part Overview: Current is '%1'").arg(str);
0232 
0233         _topLevel->showMessage(msg, 5000);
0234     }
0235 
0236     if (_showInfo) fillInfo();
0237 }
0238 
0239 
0240 void PartSelection::doubleClicked(TreeMapItem* i)
0241 {
0242     if (!i || i->rtti() != 3) return;
0243 
0244     ProfileCostArray* c = ((SubPartItem*) i)->partCostItem();
0245     TraceCostItem* ci = nullptr;
0246 
0247     switch(c->type()) {
0248     case ProfileContext::PartFunction:
0249     {
0250         TraceFunction* f = ((TracePartFunction*)c)->function();
0251         if (f)
0252             activated(f);
0253     }
0254         return;
0255 
0256     case ProfileContext::PartObject:
0257         ci = ((TracePartObject*)c)->object();
0258         break;
0259     case ProfileContext::PartClass:
0260         ci = ((TracePartClass*)c)->cls();
0261         break;
0262     case ProfileContext::PartFile:
0263         ci = ((TracePartFile*)c)->file();
0264         break;
0265     default:
0266         break;
0267     }
0268 
0269     if (ci)
0270         activated(ci);
0271 }
0272 
0273 
0274 void PartSelection::selectionChanged()
0275 {
0276     if (_inSelectionUpdate) return;
0277 
0278     qDebug("PartSelection::selectionChanged");
0279 
0280     bool something_changed = false;
0281     bool nothingSelected = true;
0282 
0283     TracePartList pList;
0284     TracePart* part;
0285 
0286     // if nothing is selected, activate all parts
0287     TreeMapItemList* list = _partAreaWidget->base()->children();
0288     if (!list) return;
0289 
0290     foreach(TreeMapItem* i, *list)
0291         if (_partAreaWidget->isSelected(i)) {
0292             nothingSelected = false;
0293             break;
0294         }
0295 
0296     foreach(TreeMapItem* i, *list) {
0297         part = ((PartItem*)i)->part();
0298         bool active = nothingSelected || _partAreaWidget->isSelected(i);
0299         if (active) {
0300             pList.append(part);
0301             something_changed = true;
0302         }
0303     }
0304 
0305     if (something_changed) {
0306         //qDebug("PartSelection: Something changed.");
0307         partsSelected(pList);
0308     }
0309 }
0310 
0311 void PartSelection::itemSelected()
0312 {
0313     QAction* a = qobject_cast<QAction*>(sender());
0314     if (a)
0315         doubleClicked( (TreeMapItem*) a->data().value<void*>() );
0316 }
0317 
0318 void PartSelection::contextMenuRequested(TreeMapItem* i,
0319                                          const QPoint & p)
0320 {
0321     if (!i) return;
0322 
0323     QMenu popup;
0324     QAction* a;
0325 
0326     QString str;
0327     TreeMapItem* s = nullptr;
0328 
0329     QAction* selectPartAction = nullptr;
0330     QAction* selectAllPartsAction = nullptr;
0331     QAction* hidePartsAction = nullptr;
0332     QAction* showPartsAction = nullptr;
0333     if (_data && (_data->parts().count()>1)) {
0334         s = _partAreaWidget->possibleSelection(i);
0335         if (!s->text(0).isEmpty()) {
0336             if (_partAreaWidget->isSelected(s))
0337                 str = tr("Deselect '%1'").arg(s->text(0));
0338             else
0339                 str = tr("Select '%1'").arg(s->text(0));
0340 
0341             selectPartAction = popup.addAction(str);
0342         }
0343 
0344         selectAllPartsAction = popup.addAction(tr("Select All Parts"));
0345         QMenu* ppopup = popup.addMenu(tr("Visible Parts"));
0346         hidePartsAction = ppopup->addAction(tr("Hide Selected Parts"));
0347         showPartsAction = ppopup->addAction(tr("Show Hidden Parts"));
0348 
0349         popup.addSeparator();
0350     }
0351 
0352     addGoMenu(&popup);
0353 
0354     if (i->rtti() == 3) {
0355         TreeMapItem* ni = i;
0356         while (ni && ni->rtti() == 3) {
0357             ProfileCostArray* c = ((SubPartItem*)ni)->partCostItem();
0358             if (c->type() == ProfileContext::PartFunction)
0359                 if ( ((TracePartFunction*)c)->function() == _selectedItem) break;
0360 
0361             str = tr("Go to '%1'").arg(GlobalConfig::shortenSymbol(ni->text(0)));
0362             a = popup.addAction(str);
0363             a->setData(QVariant::fromValue( (void*)ni ));
0364             connect(a, &QAction::triggered, this, &PartSelection::itemSelected);
0365             ni = ni->parent();
0366         }
0367     }
0368     popup.addSeparator();
0369 
0370     QMenu* vpopup = popup.addMenu(tr("Visualization"));
0371     QAction* showPartitioningAction = vpopup->addAction(tr("Partitioning Mode"));
0372     showPartitioningAction->setCheckable(true);
0373     QAction* zoomFunctionAction = vpopup->addAction(tr("Zoom Function"));
0374     zoomFunctionAction->setCheckable(true);
0375     QAction* directCallsAction = vpopup->addAction(tr("Show Direct Calls"));
0376     QAction* incCallsAction = vpopup->addAction(tr("Increment Shown Call Levels"));
0377     QAction* showDiagramAction = vpopup->addAction(tr("Diagram Mode"));
0378     showDiagramAction->setCheckable(true);
0379     if (_partAreaWidget->visualization() == PartAreaWidget::Partitioning) {
0380         showPartitioningAction->setChecked(true);
0381         zoomFunctionAction->setEnabled(false);
0382         directCallsAction->setEnabled(false);
0383         incCallsAction->setEnabled(false);
0384     }
0385     else {
0386         zoomFunctionAction->setChecked(_partAreaWidget->zoomFunction());
0387     }
0388     showDiagramAction->setChecked(_diagramMode);
0389 
0390     vpopup->addSeparator();
0391 
0392     QAction* drawNamesAction = vpopup->addAction(tr("Draw Names"));
0393     drawNamesAction->setCheckable(true);
0394     QAction* drawCostsAction = vpopup->addAction(tr("Draw Costs"));
0395     drawCostsAction->setCheckable(true);
0396     QAction* ignorePropsAction = vpopup->addAction(tr("Ignore Proportions"));
0397     ignorePropsAction->setCheckable(true);
0398     QAction* allowRotationAction = vpopup->addAction(tr("Allow Rotation"));
0399     allowRotationAction->setCheckable(true);
0400     QAction* drawFramesAction = vpopup->addAction(tr("Draw Frames"));
0401     drawFramesAction->setCheckable(true);
0402     if (!_partAreaWidget->fieldVisible(0) &&
0403         !_partAreaWidget->fieldVisible(1)) {
0404         ignorePropsAction->setEnabled(false);
0405         allowRotationAction->setEnabled(false);
0406     }
0407     else {
0408         drawNamesAction->setChecked(_partAreaWidget->fieldVisible(0));
0409         drawCostsAction->setChecked(_partAreaWidget->fieldVisible(1));
0410         ignorePropsAction->setChecked(_partAreaWidget->fieldForced(0));
0411         allowRotationAction->setChecked(_partAreaWidget->allowRotation());
0412         drawFramesAction->setChecked(_drawFrames);
0413     }
0414     QAction* showInfoAction = popup.addAction(_showInfo ? tr("Hide Info"):tr("Show Info"));
0415 
0416     a = popup.exec(_partAreaWidget->mapToGlobal(p));
0417 
0418     if (a == selectPartAction) {
0419         // select/deselect part under mouse
0420         _partAreaWidget->setSelected(s, !_partAreaWidget->isSelected(s));
0421     }
0422     else if (a == selectAllPartsAction) {
0423         // select all parts
0424         TreeMapItemList list = *_partAreaWidget->base()->children();
0425         _partAreaWidget->setRangeSelection(list.first(), list.last(), true);
0426     }
0427     else if (a == hidePartsAction)
0428         emit partsHideSelected();
0429     else if (a == showPartsAction)
0430         emit partsUnhideAll();
0431     else if (a == drawNamesAction)
0432         _partAreaWidget->setFieldVisible(0, !_partAreaWidget->fieldVisible(0));
0433     else if (a == drawCostsAction)
0434         _partAreaWidget->setFieldVisible(1, !_partAreaWidget->fieldVisible(1));
0435     else if (a == ignorePropsAction) {
0436         _partAreaWidget->setFieldForced(0, !_partAreaWidget->fieldForced(0));
0437         _partAreaWidget->setFieldForced(1, !_partAreaWidget->fieldForced(0));
0438     }
0439     else if (a == allowRotationAction)
0440         _partAreaWidget->setAllowRotation(!_partAreaWidget->allowRotation());
0441     else if (a == drawFramesAction) {
0442         _drawFrames = !_drawFrames;
0443         _partAreaWidget->drawFrame(2,_drawFrames);
0444         _partAreaWidget->drawFrame(3,_drawFrames);
0445     }
0446     else if (a == showInfoAction)
0447         showInfo(!_showInfo);
0448     else if (a == showPartitioningAction)
0449         _partAreaWidget->setVisualization(
0450                     (_partAreaWidget->visualization() != PartAreaWidget::Partitioning) ?
0451                         PartAreaWidget::Partitioning : PartAreaWidget::Inclusive );
0452     else if (a == zoomFunctionAction) {
0453         // zoom/unzoom function
0454         _partAreaWidget->setZoomFunction(!_partAreaWidget->zoomFunction());
0455     }
0456     else if (a == directCallsAction)
0457         _partAreaWidget->setCallLevels(1);
0458     else if (a == incCallsAction) {
0459         int l = _partAreaWidget->callLevels()+1;
0460         _partAreaWidget->setCallLevels(l);
0461     }
0462     else if (a == showDiagramAction) {
0463         _diagramMode = !_diagramMode;
0464         _partAreaWidget->setTransparent(2,_diagramMode);
0465     }
0466 }
0467 
0468 void PartSelection::hiddenPartsChangedSlot(const TracePartList& list)
0469 {
0470     _partAreaWidget->changeHidden(list);
0471 }
0472 
0473 void PartSelection::restoreOptions(const QString& prefix, const QString& postfix)
0474 {
0475     ConfigGroup* g = ConfigStorage::group(prefix, postfix);
0476 
0477     QString pmode = g->value(QStringLiteral("PartitionMode"),
0478                              QStringLiteral(DEFAULT_PARTITIONMODE)).toString();
0479     if (pmode == QLatin1String("Inclusive"))
0480         _partAreaWidget->setVisualization(PartAreaWidget::Inclusive);
0481     else
0482         _partAreaWidget->setVisualization(PartAreaWidget::Partitioning);
0483 
0484     _diagramMode = g->value(QStringLiteral("DiagramMode"), DEFAULT_DIAGRAMMODE).toBool();
0485     _partAreaWidget->setTransparent(2,_diagramMode);
0486 
0487     _drawFrames = g->value(QStringLiteral("DrawFrames"), DEFAULT_DRAWFRAMES).toBool();
0488     _partAreaWidget->drawFrame(2,_drawFrames);
0489     _partAreaWidget->drawFrame(3,_drawFrames);
0490 
0491     showInfo(g->value(QStringLiteral("ShowInfo"), DEFAULT_SHOWINFO).toBool());
0492 
0493     bool enable = g->value(QStringLiteral("FunctionZoom"), DEFAULT_FUNCTIONZOOM).toBool();
0494     _partAreaWidget->setZoomFunction(enable);
0495 
0496     int levels = g->value(QStringLiteral("CalleeLevels"), DEFAULT_CALLEELEVELS).toInt();
0497     _partAreaWidget->setCallLevels(levels);
0498 
0499     enable = g->value(QStringLiteral("DrawName"), DEFAULT_DRAWNAME).toBool();
0500     _partAreaWidget->setFieldVisible(0, enable);
0501 
0502     enable = g->value(QStringLiteral("DrawCost"), DEFAULT_DRAWCOST).toBool();
0503     _partAreaWidget->setFieldVisible(1, enable);
0504 
0505     enable = g->value(QStringLiteral("ForceStrings"), DEFAULT_FORCESTRINGS).toBool();
0506     _partAreaWidget->setFieldForced(0, enable);
0507     _partAreaWidget->setFieldForced(1, enable);
0508 
0509     enable = g->value(QStringLiteral("AllowRotation"), DEFAULT_ALLOWROTATION).toBool();
0510     _partAreaWidget->setAllowRotation(enable);
0511 
0512     delete g;
0513 }
0514 
0515 void PartSelection::saveOptions(const QString& prefix, const QString& postfix)
0516 {
0517     ConfigGroup* g = ConfigStorage::group(prefix + postfix);
0518 
0519     QString mode;
0520     if (_partAreaWidget->visualization() == PartAreaWidget::Inclusive)
0521         mode = QStringLiteral("Inclusive");
0522     else
0523         mode = QStringLiteral("Partitioning");
0524 
0525     g->setValue(QStringLiteral("PartitionMode"), mode, QStringLiteral(DEFAULT_PARTITIONMODE));
0526     g->setValue(QStringLiteral("DiagramMode"), _diagramMode, DEFAULT_DIAGRAMMODE);
0527     g->setValue(QStringLiteral("DrawFrames"), _drawFrames, DEFAULT_DRAWFRAMES);
0528     g->setValue(QStringLiteral("ShowInfo"), _showInfo, DEFAULT_SHOWINFO);
0529 
0530     g->setValue(QStringLiteral("FunctionZoom"),
0531                 _partAreaWidget->zoomFunction(), DEFAULT_FUNCTIONZOOM);
0532     g->setValue(QStringLiteral("CalleeLevels"),
0533                 _partAreaWidget->callLevels(), DEFAULT_CALLEELEVELS);
0534     g->setValue(QStringLiteral("DrawName"),
0535                 _partAreaWidget->fieldVisible(0), DEFAULT_DRAWNAME);
0536     g->setValue(QStringLiteral("DrawCost"),
0537                 _partAreaWidget->fieldVisible(1), DEFAULT_DRAWCOST);
0538     g->setValue(QStringLiteral("ForceStrings"),
0539                 _partAreaWidget->fieldForced(0), DEFAULT_FORCESTRINGS);
0540     g->setValue(QStringLiteral("AllowRotation"),
0541                 _partAreaWidget->allowRotation(), DEFAULT_ALLOWROTATION);
0542 
0543     delete g;
0544 }
0545 
0546 void PartSelection::showInfo(bool enable)
0547 {
0548     if (_showInfo == enable) return;
0549 
0550     _showInfo = enable;
0551     if (enable) {
0552         _rangeLabel->show();
0553         fillInfo();
0554     }
0555     else
0556         _rangeLabel->hide();
0557 }
0558 
0559 void PartSelection::fillInfo()
0560 {
0561     if (!_data) {
0562         _rangeLabel->setText(tr("(no trace loaded)"));
0563         return;
0564     }
0565 
0566     QString info = _data->activePartRange();
0567 
0568     TreeMapItem* i = _partAreaWidget->current();
0569     while (i && i->rtti()!=2) i = i->parent();
0570     if (i) {
0571         TracePart* part = ((PartItem*)i)->part();
0572 
0573         //if (!part->trigger().isEmpty()) info += ", " + part->trigger();
0574         if (!part->timeframe().isEmpty())
0575             info += ", Time " + part->timeframe() + " BBs";
0576     }
0577     else {
0578         TracePart* part = _data->parts().first();
0579 
0580         if (part && !part->version().isEmpty())
0581             info += ", Cachegrind " + part->version();
0582     }
0583 
0584 
0585     _rangeLabel->setText(info);
0586 }
0587 
0588 #include "moc_partselection.cpp"