1 /*
2     This file is part of KCachegrind.
3 
4     SPDX-FileCopyrightText: 2002-2016 Josef Weidendorfer <Josef.Weidendorfer@gmx.de>
5 
6     SPDX-License-Identifier: GPL-2.0-only
7 */
8 
9 /**
10  * A Widget for visualizing hierarchical metrics as areas.
11  * The API is similar to QListView.
12  *
13  * This file defines the following classes:
14  *  DrawParams, RectDrawing, TreeMapItem, TreeMapWidget
15  *
16  * DrawParams/RectDrawing allows reusing of TreeMap drawing
17  * functions in other widgets.
18  */
19 
20 #ifndef TREEMAP_H
21 #define TREEMAP_H
22 
23 #include <QString>
24 #include <QWidget>
25 #include <QPixmap>
26 #include <QColor>
27 #include <QStringList>
28 #include <QPaintEvent>
29 #include <QKeyEvent>
30 #include <QContextMenuEvent>
31 #include <QMouseEvent>
32 
33 class QMenu;
34 class TreeMapWidget;
35 class TreeMapItem;
36 class TreeMapItemList;
37 
38 
39 /**
40  * Drawing parameters for an object.
41  * A Helper Interface for RectDrawing.
42  */
43 class DrawParams
44 {
45 public:
46     /**
47      * Positions for drawing into a rectangle.
48      *
49      * The specified position assumes no rotation.
50      * If there is more than one text for one position, it is put
51      * nearer to the center of the item.
52      *
53      * Drawing at top positions cuts free space from top,
54      * drawing at bottom positions cuts from bottom.
55      * Default usually gives positions clockwise according to field number.
56      */
57     enum Position { TopLeft, TopCenter, TopRight,
58                     BottomLeft, BottomCenter, BottomRight,
59                     Default, Unknown};
60 
61     // no constructor as this is an abstract class
~DrawParams()62     virtual ~DrawParams() {}
63 
64     virtual QString  text(int) const = 0;
65     virtual QPixmap  pixmap(int) const = 0;
66     virtual Position position(int) const = 0;
67     // 0: no limit, negative: leave at least -maxLines() free
maxLines(int)68     virtual int      maxLines(int) const { return 0; }
69     // allow breaking up content into multiple lines?
allowBreak(int)70     virtual bool     allowBreak(int) const { return true; }
71     // truncate or show nothing if space not enough?
allowTruncation(int)72     virtual bool     allowTruncation(int) const { return true; }
fieldCount()73     virtual int      fieldCount() const { return 0; }
74 
backColor()75     virtual QColor   backColor() const { return Qt::white; }
76     virtual const QFont& font() const = 0;
77 
selected()78     virtual bool selected() const { return false; }
current()79     virtual bool current() const { return false; }
shaded()80     virtual bool shaded() const { return true; }
rotated()81     virtual bool rotated() const { return false; }
drawFrame()82     virtual bool drawFrame() const { return true; }
83 };
84 
85 
86 /*
87  * DrawParam with attributes stored
88  */
89 class StoredDrawParams: public DrawParams
90 {
91 public:
92     StoredDrawParams();
93     explicit StoredDrawParams(const QColor& c,
94                               bool selected = false, bool current = false);
95 
96     // getters
97     QString  text(int) const override;
98     QPixmap  pixmap(int) const override;
99     Position position(int) const override;
100     int      maxLines(int) const override;
fieldCount()101     int      fieldCount() const override { return _field.size(); }
102 
backColor()103     QColor   backColor() const override { return _backColor; }
selected()104     bool selected() const override { return _selected; }
current()105     bool current() const override { return _current; }
shaded()106     bool shaded() const override { return _shaded; }
rotated()107     bool rotated() const override { return _rotated; }
drawFrame()108     bool drawFrame() const override { return _drawFrame; }
109 
110     const QFont& font() const override;
111 
112     // attribute setters
113     void setField(int f, const QString& t, const QPixmap& pm = QPixmap(),
114                   Position p = Default, int maxLines = 0);
115     void setText(int f, const QString&);
116     void setPixmap(int f, const QPixmap&);
117     void setPosition(int f, Position);
118     void setMaxLines(int f, int);
setBackColor(const QColor & c)119     void setBackColor(const QColor& c) { _backColor = c; }
setSelected(bool b)120     void setSelected(bool b) { _selected = b; }
setCurrent(bool b)121     void setCurrent(bool b) { _current = b; }
setShaded(bool b)122     void setShaded(bool b) { _shaded = b; }
setRotated(bool b)123     void setRotated(bool b) { _rotated = b; }
drawFrame(bool b)124     void drawFrame(bool b) { _drawFrame = b; }
125 
126 protected:
127     QColor _backColor;
128     bool _selected :1;
129     bool _current :1;
130     bool _shaded :1;
131     bool _rotated :1;
132     bool _drawFrame :1;
133 
134 private:
135     // resize field array if needed to allow to access field <f>
136     void ensureField(int f);
137 
138     struct Field {
139         QString text;
140         QPixmap pix;
141         Position pos;
142         int maxLines;
143     };
144 
145     QVector<Field> _field;
146 };
147 
148 
149 /* State for drawing on a rectangle.
150  *
151  * Following drawing functions are provided:
152  * - background drawing with shading and 3D frame
153  * - successive pixmap/text drawing at various positions with wrap-around
154  *   optimized for minimal space usage (e.g. if a text is drawn at top right
155  *   after text on top left, the same line is used if space allows)
156  *
157  */
158 class RectDrawing
159 {
160 public:
161     explicit RectDrawing(const QRect&);
162     ~RectDrawing();
163 
164     // The default DrawParams object used.
165     DrawParams* drawParams();
166     // we take control over the given object (i.e. delete at destruction)
167     void setDrawParams(DrawParams*);
168 
169     // draw on a given QPainter, use this class as info provider per default
170     void drawBack(QPainter*, DrawParams* dp = nullptr);
171     /* Draw field at position() from pixmap()/text() with maxLines().
172      * Returns true if something was drawn
173      */
174     bool drawField(QPainter*, int f, DrawParams* dp = nullptr);
175 
176     // resets rectangle for free space
177     void setRect(const QRect&);
178 
179     // Returns the rectangle area still free of text/pixmaps after
180     // a number of drawText() calls.
181     QRect remainingRect(DrawParams* dp = nullptr);
182 
183 private:
184     int _usedTopLeft, _usedTopCenter, _usedTopRight;
185     int _usedBottomLeft, _usedBottomCenter, _usedBottomRight;
186     QRect _rect;
187 
188     // temporary
189     int _fontHeight;
190     QFontMetrics* _fm;
191     DrawParams* _dp;
192 };
193 
194 
195 class TreeMapItemList: public QList<TreeMapItem*>
196 {
197 public:
198     TreeMapItem* commonParent();
199 };
200 
201 
202 /**
203  * Base class of items in TreeMap.
204  *
205  * This class supports an arbitrary number of text() strings
206  * positioned counterclock-wise starting at TopLeft. Each item
207  * has its own static value(), sum() and sorting(). The
208  * splitMode() and borderWidth() is taken from a TreeMapWidget.
209  *
210  * If you want more flexibility, reimplement TreeMapItem and
211  * override the corresponding methods. For dynamic creation of child
212  * items on demand, reimplement children().
213  */
214 class TreeMapItem: public StoredDrawParams
215 {
216 public:
217 
218     /**
219      * Split direction for nested areas:
220      *  AlwaysBest: Choose split direction for every subitem according to
221      *              longest side of rectangle left for drawing
222      *  Best:       Choose split direction for all subitems of an area
223      *              depending on longest side
224      *  HAlternate: Horizontal at top;  alternate direction on depth step
225      *  VAlternate: Vertical at top; alternate direction on depth step
226      *  Horizontal: Always horizontal split direction
227      *  Vertical:   Always vertical split direction
228      */
229     enum SplitMode { Bisection, Columns, Rows,
230                      AlwaysBest, Best,
231                      HAlternate, VAlternate,
232                      Horizontal, Vertical };
233 
234     explicit TreeMapItem(TreeMapItem* parent = nullptr, double value = 1.0 );
235     TreeMapItem(TreeMapItem* parent, double value,
236                 const QString& text1, const QString& text2 = QString(),
237                 const QString& text3 = QString(), const QString& text4 = QString());
238     ~TreeMapItem() override;
239 
240     bool isChildOf(TreeMapItem*);
241 
242     TreeMapItem* commonParent(TreeMapItem* item);
243 
244     // force a redraw of this item
245     void redraw();
246 
247     // delete all children
248     void clear();
249 
250     // force new child generation & refresh
251     void refresh();
252 
253     // call in a reimplemented items() method to check if already called
254     // after a clear(), this will return false
255     bool initialized();
256 
257     /**
258      * Adds an item to a parent.
259      * When no sorting is used, the item is appended (drawn at bottom).
260      * This is only needed if the parent was not already specified in the
261      * construction of the item.
262      */
263     void addItem(TreeMapItem*);
264 
265     /**
266      * Returns a list of text strings of specified text number,
267      * from root up to this item.
268      */
269     QStringList path(int) const;
270 
271     /**
272      * Depth of this item. This is the distance to root.
273      */
274     int depth() const;
275 
276     /**
277      * Parent Item
278      */
parent()279     TreeMapItem* parent() const { return _parent; }
280 
281     /**
282      * Temporary rectangle used for drawing this item the last time.
283      * This is internally used to map from a point to an item.
284      */
setItemRect(const QRect & r)285     void setItemRect(const QRect& r) { _rect = r; }
286     void clearItemRect();
itemRect()287     const QRect& itemRect() const { return _rect; }
width()288     int width() const { return _rect.width(); }
height()289     int height() const { return _rect.height(); }
290 
291     /**
292      * Temporary rectangle list of free space of this item.
293      * Used internally to enable tooltip.
294      */
295     void clearFreeRects();
freeRects()296     const QList<QRect>& freeRects() const { return _freeRects; }
297     void addFreeRect(const QRect& r);
298 
299     /**
300      * Temporary child item index of the child that was current() recently.
301      */
index()302     int index() const { return _index; }
setIndex(int i)303     void setIndex(int i) { _index = i; }
304 
305 
306     /**
307      * TreeMap widget this item is put in.
308      */
widget()309     TreeMapWidget* widget() const { return _widget; }
310 
311     void setParent(TreeMapItem* p);
setWidget(TreeMapWidget * w)312     void setWidget(TreeMapWidget* w) { _widget = w; }
setSum(double s)313     void setSum(double s) { _sum = s; }
setValue(double s)314     void setValue(double s) { _value = s; }
315 
316     virtual double sum() const;
317     virtual double value() const;
318     // replace "Default" position with setting from TreeMapWidget
319     Position position(int) const override;
320     const QFont& font() const override;
321     virtual bool isMarked(int) const;
322 
323     virtual int borderWidth() const;
324 
325     /**
326      * Returns the text number after that sorting is done or
327      * -1 for no sorting, -2 for value() sorting (default).
328      * If ascending != 0, a bool value is written at that location
329      * to indicate if sorting should be ascending.
330      */
331     virtual int sorting(bool* ascending) const;
332 
333     /**
334      * Set the sorting for child drawing.
335      *
336      * Default is no sorting: @p textNo = -1
337      * For value() sorting, use @p textNo = -2
338      *
339      * For fast sorting, set this to -1 before child insertions and call
340      * again after inserting all children.
341      */
342     void setSorting(int textNo, bool ascending = true);
343 
344     /**
345      * Resort according to the already set sorting.
346      *
347      * This has to be done if the sorting base changes (e.g. text or values
348      * change). If this is only true for the children of this item, you can
349      * set the recursive parameter to false.
350      */
351     void resort(bool recursive = true);
352 
353     virtual SplitMode splitMode() const;
354     virtual int rtti() const;
355     // not const as this can create children on demand
356     virtual TreeMapItemList* children();
357 
358 protected:
359     TreeMapItemList* _children;
360     double _sum, _value;
361 
362 private:
363     TreeMapWidget* _widget;
364     TreeMapItem* _parent;
365 
366     int _sortTextNo;
367     bool _sortAscending;
368 
369     // temporary layout
370     QRect _rect;
371     QList<QRect> _freeRects;
372     int _depth;
373 
374     // temporary self value (when using level skipping)
375     double _unused_self;
376 
377     // index of last active subitem
378     int _index;
379 };
380 
381 
382 /**
383  * Class for visualization of a metric of hierarchically
384  * nested items as 2D areas.
385  */
386 class TreeMapWidget: public QWidget
387 {
388     Q_OBJECT
389 
390 public:
391 
392     /**
393      * Same as in QListBox/QListView
394      */
395     enum SelectionMode { Single, Multi, Extended, NoSelection };
396 
397     /* The widget gets owner of the base item */
398     explicit TreeMapWidget(TreeMapItem* base, QWidget* parent=nullptr);
399     ~TreeMapWidget() override;
400 
401     /**
402      * Returns the TreeMapItem filling out the widget space
403      */
base()404     TreeMapItem* base() const { return _base; }
405 
406     /**
407      * Returns a reference to the current widget font.
408      */
409     const QFont& currentFont() const;
410 
411     /**
412      * Returns the area item at position x/y, independent from any
413      * maxSelectDepth setting.
414      */
415     TreeMapItem* item(int x, int y) const;
416 
417     /**
418      * Returns the nearest item with a visible area; this
419      * can be the given item itself.
420      */
421     TreeMapItem* visibleItem(TreeMapItem*) const;
422 
423     /**
424      * Returns the item possible for selection. this returns the
425      * given item itself or a parent thereof,
426      * depending on setting of maxSelectDepth().
427      */
428     TreeMapItem* possibleSelection(TreeMapItem*) const;
429 
430     /**
431      * Selects or unselects an item.
432      * In multiselection mode, the constrain that a selected item
433      * has no selected children or parents stays true.
434      */
435     void setSelected(TreeMapItem*, bool selected = true);
436 
437     /**
438      * Switches on the marking @p markNo. Marking 0 switches off marking.
439      * This is mutually exclusive to selection, and is automatically
440      * switched off when selection is changed (also by the user).
441      * Marking is visually the same as selection, and is based on
442      * TreeMapItem::isMarked(@c markNo).
443      * This enables to programmatically show multiple selected items
444      * at once even in single selection mode.
445      */
446     void setMarked(int markNo = 1, bool redraw = true);
447 
448     /**
449      * Clear selection of all selected items which are children of
450      * parent. When parent == 0, clears whole selection
451      * Returns true if selection changed.
452      */
453     bool clearSelection(TreeMapItem* parent = nullptr);
454 
455     /**
456      * Selects or unselects items in a range.
457      * This is needed internally for Shift-Click in Extended mode.
458      * Range means for a hierarchical widget:
459      * - select/unselect i1 and i2 according selected
460      * - search common parent of i1 and i2, and select/unselect the
461      *   range of direct children between but excluding the child
462      *   leading to i1 and the child leading to i2.
463      */
464     void setRangeSelection(TreeMapItem* i1,
465                            TreeMapItem* i2, bool selected);
466 
467     /**
468      * Sets the current item.
469      * The current item is mainly used for keyboard navigation.
470      */
471     void setCurrent(TreeMapItem*, bool kbd=false);
472 
473     /**
474      * Set the maximal depth a selected item can have.
475      * If you try to select a item with higher depth, the ancestor holding
476      * this condition is used.
477      *
478      * See also possibleSelection().
479      */
setMaxSelectDepth(int d)480     void setMaxSelectDepth(int d) { _maxSelectDepth = d; }
481 
482 
setSelectionMode(SelectionMode m)483     void setSelectionMode(SelectionMode m) { _selectionMode = m; }
484 
485     /**
486      * for setting/getting global split direction
487      */
488     void setSplitMode(TreeMapItem::SplitMode m);
489     TreeMapItem::SplitMode splitMode() const;
490     // returns true if string was recognized
491     bool setSplitMode(const QString&);
492     QString splitModeString() const;
493 
494 
495     /*
496    * Shading of rectangles enabled ?
497    */
498     void setShadingEnabled(bool s);
isShadingEnabled()499     bool isShadingEnabled() const { return _shading; }
500 
501     /* Setting for a whole depth level: draw 3D frame (default) or solid */
502     void drawFrame(int d, bool b);
drawFrame(int d)503     bool drawFrame(int d) const { return (d<4)?_drawFrame[d]:true; }
504 
505     /* Setting for a whole depth level: draw items (default) or transparent */
506     void setTransparent(int d, bool b);
isTransparent(int d)507     bool isTransparent(int d) const { return (d<4)?_transparent[d]:false; }
508 
509     /**
510      * Items usually have a size proportional to their value().
511      * With @p width, you can give the minimum width
512      * of the resulting rectangle to still be drawn.
513      * For space not used because of to small items, you can specify
514      * with @p reuseSpace if the background should shine through or
515      * the space will be used to enlarge the next item to be drawn
516      * at this level.
517      */
518     void setVisibleWidth(int width, bool reuseSpace = false);
519 
520     /**
521      * If a children value() is almost the parents sum(),
522      * it can happen that the border to be drawn for visibility of
523      * nesting relations takes to much space, and the
524      * parent/child size relation can not be mapped to a correct
525      * area size relation.
526      *
527      * Either
528      * (1) Ignore the incorrect drawing, or
529      * (2) Skip drawing of the parent level altogether.
530      */
531     void setSkipIncorrectBorder(bool enable = true);
skipIncorrectBorder()532     bool skipIncorrectBorder() const { return _skipIncorrectBorder; }
533 
534     /**
535      * Maximal nesting depth
536      */
537     void setMaxDrawingDepth(int d);
maxDrawingDepth()538     int maxDrawingDepth() const { return _maxDrawingDepth; }
539 
540     /**
541      * Minimal area for rectangles to draw
542      */
543     void setMinimalArea(int area);
minimalArea()544     int minimalArea() const { return _minimalArea; }
545 
546     /* defaults for text attributes */
547     QString defaultFieldType(int) const;
548     QString defaultFieldStop(int) const;
549     bool    defaultFieldVisible(int) const;
550     bool    defaultFieldForced(int) const;
551     DrawParams::Position defaultFieldPosition(int) const;
552 
553     /**
554      * Set the type name of a field.
555      * This is important for the visualization menu generated
556      * with visualizationMenu()
557      */
558     void setFieldType(int, const QString&);
559     QString fieldType(int) const;
560 
561     /**
562      * Stop drawing at item with name
563      */
564     void setFieldStop(int, const QString&);
565     QString fieldStop(int) const;
566 
567     /**
568      * Should the text with number textNo be visible?
569      * This is only done if remaining space is enough to allow for
570      * proportional size constrains.
571      */
572     void setFieldVisible(int, bool);
573     bool fieldVisible(int) const;
574 
575     /**
576      * Should the drawing of the name into the rectangle be forced?
577      * This enables drawing of the name before drawing subitems, and
578      * thus destroys proportional constrains.
579      */
580     void setFieldForced(int, bool);
581     bool fieldForced(int) const;
582 
583     /**
584      * Set the field position in the area. See TreeMapItem::Position
585      */
586     void setFieldPosition(int, DrawParams::Position);
587     DrawParams::Position fieldPosition(int) const;
588     void setFieldPosition(int, const QString&);
589     QString fieldPositionString(int) const;
590 
591     /**
592      * Do we allow the texts to be rotated by 90 degrees for better fitting?
593      */
594     void setAllowRotation(bool);
allowRotation()595     bool allowRotation() const { return _allowRotation; }
596 
597     void setBorderWidth(int w);
borderWidth()598     int borderWidth() const { return _borderWidth; }
599 
600     /**
601      * Populate given menu with option items.
602      * The added items are automatically connected to handlers.
603      */
604     void addSplitDirectionItems(QMenu*);
605 
widget()606     TreeMapWidget* widget() { return this; }
current()607     TreeMapItem* current() const { return _current; }
selection()608     TreeMapItemList selection() const { return _selection; }
609     bool isSelected(TreeMapItem* i) const;
maxSelectDepth()610     int maxSelectDepth() const { return _maxSelectDepth; }
selectionMode()611     SelectionMode selectionMode() const { return _selectionMode; }
612 
613     /**
614      * Return tooltip string to show for a item (can be rich text)
615      * Default implementation gives lines with "text0 (text1)" going to root.
616      */
617     virtual QString tipString(TreeMapItem* i) const;
618 
619     /**
620      * Redraws an item with all children.
621      * This takes changed values(), sums(), colors() and text() into account.
622      */
623     void redraw(TreeMapItem*);
redraw()624     void redraw() { redraw(_base); }
625 
626     /**
627      * Resort all TreeMapItems. See TreeMapItem::resort().
628      */
resort()629     void resort() { _base->resort(true); }
630 
631     // internal
632     void drawTreeMap();
633 
634     // used internally when items are destroyed
635     void deletingItem(TreeMapItem*);
636 
637 protected Q_SLOTS:
638     void splitActivated(QAction*);
639 
640 Q_SIGNALS:
641     void selectionChanged();
642     void selectionChanged(TreeMapItem*);
643 
644     /**
645      * This signal is emitted if the current item changes.
646      * If the change is done because of keyboard navigation,
647      * the @p keyboard is set to true
648      */
649     void currentChanged(TreeMapItem*, bool keyboard);
650     void clicked(TreeMapItem*);
651     void returnPressed(TreeMapItem*);
652     void doubleClicked(TreeMapItem*);
653     void rightButtonPressed(TreeMapItem*, const QPoint &);
654     void contextMenuRequested(TreeMapItem*, const QPoint &);
655 
656 protected:
657     void mousePressEvent( QMouseEvent * ) override;
658     void contextMenuEvent( QContextMenuEvent * ) override;
659     void mouseReleaseEvent( QMouseEvent * ) override;
660     void mouseMoveEvent( QMouseEvent * ) override;
661     void mouseDoubleClickEvent( QMouseEvent * ) override;
662     void keyPressEvent( QKeyEvent* ) override;
663     void paintEvent( QPaintEvent * ) override;
664     void fontChange( const QFont& );
665     bool event(QEvent *event) override;
666 
667 private:
668     TreeMapItemList diff(TreeMapItemList&, TreeMapItemList&);
669     // returns true if selection changed
670     TreeMapItem* setTmpSelected(TreeMapItem*, bool selected = true);
671     TreeMapItem* setTmpRangeSelection(TreeMapItem* i1,
672                                       TreeMapItem* i2, bool selected);
673     bool isTmpSelected(TreeMapItem* i);
674 
675     void drawItem(QPainter* p, TreeMapItem*);
676     void drawItems(QPainter* p, TreeMapItem*);
677     bool horizontal(TreeMapItem* i, const QRect& r);
678     void drawFill(TreeMapItem*,QPainter* p, const QRect& r);
679     void drawFill(TreeMapItem*,QPainter* p, const QRect& r,
680                   TreeMapItemList* list, int idx, int len, bool goBack);
681     bool drawItemArray(QPainter* p, TreeMapItem*, const QRect& r, double,
682                        TreeMapItemList* list, int idx, int len, bool);
683     bool resizeAttr(int);
684 
685     void addSplitAction(QMenu*, const QString&, int);
686 
687     TreeMapItem* _base;
688     TreeMapItem *_current, *_pressed, *_lastOver, *_oldCurrent;
689     int _maxSelectDepth, _maxDrawingDepth;
690 
691     // attributes for field, per textNo
692     struct FieldAttr {
693         QString type, stop;
694         bool visible, forced;
695         DrawParams::Position pos;
696     };
697     QVector<FieldAttr> _attr;
698 
699     SelectionMode _selectionMode;
700     TreeMapItem::SplitMode _splitMode;
701     int _visibleWidth, _stopArea, _minimalArea, _borderWidth;
702     bool _reuseSpace, _skipIncorrectBorder, _drawSeparators, _shading;
703     bool _allowRotation;
704     bool _transparent[4], _drawFrame[4];
705     TreeMapItem * _needsRefresh;
706     TreeMapItemList _selection;
707     int _markNo;
708 
709     // temporary selection while dragging, used for drawing
710     // most of the time, _selection == _tmpSelection
711     TreeMapItemList _tmpSelection;
712     bool _inShiftDrag, _inControlDrag;
713 
714     // temporary widget font metrics while drawing
715     QFont _font;
716     int _fontHeight;
717 
718     // back buffer pixmap
719     QPixmap _pixmap;
720 };
721 
722 #endif
723