File indexing completed on 2024-05-12 05:43:25

0001 /* This file is part of KCachegrind.
0002    Copyright (c) 2002-2016 Josef Weidendorfer <Josef.Weidendorfer@gmx.de>
0003 
0004    KCachegrind is free software; you can redistribute it and/or
0005    modify it under the terms of the GNU General Public
0006    License as published by the Free Software Foundation, version 2.
0007 
0008    This program is distributed in the hope that it will be useful,
0009    but WITHOUT ANY WARRANTY; without even the implied warranty of
0010    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0011    General Public License for more details.
0012 
0013    You should have received a copy of the GNU General Public License
0014    along with this program; see the file COPYING.  If not, write to
0015    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0016    Boston, MA 02110-1301, USA.
0017 */
0018 
0019 /*
0020  * A Widget for visualizing hierarchical metrics as areas.
0021  * The API is similar to QListView.
0022  */
0023 
0024 #include "treemap.h"
0025 
0026 #include <math.h>
0027 #include <QAction>
0028 #include <QMenu>
0029 #include <QApplication>
0030 #include <QDebug>
0031 #include <QPainter>
0032 #include <QStyle>
0033 #include <QPixmap>
0034 #include <QPaintEvent>
0035 #include <QKeyEvent>
0036 #include <QShowEvent>
0037 #include <QContextMenuEvent>
0038 #include <QMouseEvent>
0039 #include <QToolTip>
0040 #include <QStylePainter>
0041 #include <QStyleOptionFocusRect>
0042 
0043 
0044 // set this to 1 to enable debug output
0045 #define DEBUG_DRAWING 0
0046 #define MAX_FIELD 12
0047 
0048 
0049 //
0050 // StoredDrawParams
0051 //
0052 StoredDrawParams::StoredDrawParams()
0053 {
0054     _selected = false;
0055     _current = false;
0056     _shaded = true;
0057     _rotated = false;
0058     _drawFrame = true;
0059 
0060     _backColor = Qt::white;
0061 
0062     // field array has size 0
0063 }
0064 
0065 StoredDrawParams::StoredDrawParams(const QColor& c,
0066                                    bool selected, bool current)
0067 {
0068     _backColor = c;
0069 
0070     _selected = selected;
0071     _current = current;
0072     _shaded = true;
0073     _rotated = false;
0074     _drawFrame = true;
0075 
0076     // field array has size 0
0077 }
0078 
0079 QString StoredDrawParams::text(int f) const
0080 {
0081     if ((f<0) || (f >= (int)_field.size()))
0082         return QString();
0083 
0084     return _field[f].text;
0085 }
0086 
0087 QPixmap StoredDrawParams::pixmap(int f) const
0088 {
0089     if ((f<0) || (f >= (int)_field.size()))
0090         return QPixmap();
0091 
0092     return _field[f].pix;
0093 }
0094 
0095 DrawParams::Position StoredDrawParams::position(int f) const
0096 {
0097     if ((f<0) || (f >= (int)_field.size()))
0098         return Default;
0099 
0100     return _field[f].pos;
0101 }
0102 
0103 int StoredDrawParams::maxLines(int f) const
0104 {
0105     if ((f<0) || (f >= (int)_field.size()))
0106         return 0;
0107 
0108     return _field[f].maxLines;
0109 }
0110 
0111 const QFont& StoredDrawParams::font() const
0112 {
0113     static QFont* f = nullptr;
0114     if (!f) f = new QFont(QApplication::font());
0115 
0116     return *f;
0117 }
0118 
0119 void StoredDrawParams::ensureField(int f)
0120 {
0121     if (f<0 || f>=MAX_FIELD) return;
0122 
0123     if ((int)_field.size() < f+1) {
0124         int oldSize = _field.size();
0125         _field.resize(f+1);
0126         while(oldSize < f+1) {
0127             _field[oldSize].pos = Default;
0128             _field[oldSize].maxLines = 0;
0129             oldSize++;
0130         }
0131     }
0132 }
0133 
0134 
0135 void StoredDrawParams::setField(int f, const QString& t, const QPixmap& pm,
0136                                 Position p, int maxLines)
0137 {
0138     if (f<0 || f>=MAX_FIELD) return;
0139     ensureField(f);
0140 
0141     _field[f].text = t;
0142     _field[f].pix  = pm;
0143     _field[f].pos  = p;
0144     _field[f].maxLines = maxLines;
0145 }
0146 
0147 void StoredDrawParams::setText(int f, const QString& t)
0148 {
0149     if (f<0 || f>=MAX_FIELD) return;
0150     ensureField(f);
0151 
0152     _field[f].text = t;
0153 }
0154 
0155 void StoredDrawParams::setPixmap(int f, const QPixmap& pm)
0156 {
0157     if (f<0 || f>=MAX_FIELD) return;
0158     ensureField(f);
0159 
0160     _field[f].pix = pm;
0161 }
0162 
0163 void StoredDrawParams::setPosition(int f, Position p)
0164 {
0165     if (f<0 || f>=MAX_FIELD) return;
0166     ensureField(f);
0167 
0168     _field[f].pos = p;
0169 }
0170 
0171 void StoredDrawParams::setMaxLines(int f, int m)
0172 {
0173     if (f<0 || f>=MAX_FIELD) return;
0174     ensureField(f);
0175 
0176     _field[f].maxLines = m;
0177 }
0178 
0179 
0180 
0181 //
0182 // RectDrawing
0183 //
0184 
0185 RectDrawing::RectDrawing(const QRect& r)
0186 {
0187     _fm = nullptr;
0188     _dp = nullptr;
0189     setRect(r);
0190 }
0191 
0192 
0193 RectDrawing::~RectDrawing()
0194 {
0195     delete _fm;
0196     delete _dp;
0197 }
0198 
0199 DrawParams* RectDrawing::drawParams()
0200 {
0201     if (!_dp)
0202         _dp = new StoredDrawParams();
0203 
0204     return _dp;
0205 }
0206 
0207 
0208 void RectDrawing::setDrawParams(DrawParams* dp)
0209 {
0210     delete _dp;
0211     _dp = dp;
0212 }
0213 
0214 void RectDrawing::setRect(const QRect& r)
0215 {
0216     _rect = r;
0217 
0218     _usedTopLeft = 0;
0219     _usedTopCenter = 0;
0220     _usedTopRight = 0;
0221     _usedBottomLeft = 0;
0222     _usedBottomCenter = 0;
0223     _usedBottomRight = 0;
0224 
0225     _fontHeight = 0;
0226 }
0227 
0228 QRect RectDrawing::remainingRect(DrawParams* dp)
0229 {
0230     if (!dp) dp = drawParams();
0231 
0232     if ((_usedTopLeft >0) ||
0233         (_usedTopCenter >0) ||
0234         (_usedTopRight >0)) {
0235         if (dp->rotated())
0236             _rect.setLeft(_rect.left() + _fontHeight);
0237         else
0238             _rect.setTop(_rect.top() + _fontHeight);
0239     }
0240 
0241     if ((_usedBottomLeft >0) ||
0242         (_usedBottomCenter >0) ||
0243         (_usedBottomRight >0)) {
0244         if (dp->rotated())
0245             _rect.setRight(_rect.right() - _fontHeight);
0246         else
0247             _rect.setBottom(_rect.bottom() - _fontHeight);
0248     }
0249     return _rect;
0250 }
0251 
0252 
0253 void RectDrawing::drawBack(QPainter* p, DrawParams* dp)
0254 {
0255     if (!dp) dp = drawParams();
0256     if (_rect.width()<=0 || _rect.height()<=0) return;
0257 
0258     QRect r = _rect;
0259     QColor normal = dp->backColor();
0260     if (dp->selected()) normal = normal.lighter();
0261     bool isCurrent = dp->current();
0262 
0263     if (dp->drawFrame() || isCurrent) {
0264         // 3D raised/sunken frame effect...
0265         QColor high = normal.lighter();
0266         QColor low = normal.darker();
0267         p->setPen( isCurrent ? low:high);
0268         p->drawLine(r.left(), r.top(), r.right(), r.top());
0269         p->drawLine(r.left(), r.top(), r.left(), r.bottom());
0270         p->setPen( isCurrent ? high:low);
0271         p->drawLine(r.right(), r.top(), r.right(), r.bottom());
0272         p->drawLine(r.left(), r.bottom(), r.right(), r.bottom());
0273         r.setRect(r.x()+1, r.y()+1, r.width()-2, r.height()-2);
0274     }
0275     if (r.width()<=0 || r.height()<=0) return;
0276 
0277     if (dp->shaded() && (r.width()>0 && r.height()>0)) {
0278         // adjustment for drawRect semantic in Qt4: decrement height/width
0279         r.setRect(r.x(), r.y(), r.width()-1, r.height()-1);
0280 
0281         // some shading
0282         bool goDark = qGray(normal.rgb())>128;
0283         int rBase, gBase, bBase;
0284         normal.getRgb(&rBase, &gBase, &bBase);
0285         p->setBrush(Qt::NoBrush);
0286 
0287         // shade parameters:
0288         int d = 7;
0289         float factor = 0.1, forth=0.7, back1 =0.9, toBack2 = .7, back2 = 0.97;
0290 
0291         // coefficient corrections because of rectangle size
0292         int s = r.width();
0293         if (s > r.height()) s = r.height();
0294         if (s<100) {
0295             forth -= .3  * (100-s)/100;
0296             back1 -= .2  * (100-s)/100;
0297             back2 -= .02 * (100-s)/100;
0298         }
0299 
0300 
0301         // maximal color difference
0302         int rDiff = goDark ? -rBase/d : (255-rBase)/d;
0303         int gDiff = goDark ? -gBase/d : (255-gBase)/d;
0304         int bDiff = goDark ? -bBase/d : (255-bBase)/d;
0305 
0306         QColor shadeColor;
0307         while (factor<.95 && (r.width()>=0 && r.height()>=0)) {
0308             shadeColor.setRgb((int)(rBase+factor*rDiff+.5),
0309                               (int)(gBase+factor*gDiff+.5),
0310                               (int)(bBase+factor*bDiff+.5));
0311             p->setPen(shadeColor);
0312             p->drawRect(r);
0313             r.setRect(r.x()+1, r.y()+1, r.width()-2, r.height()-2);
0314             factor = 1.0 - ((1.0 - factor) * forth);
0315         }
0316 
0317         // and back (1st half)
0318         while (factor>toBack2 && (r.width()>=0 && r.height()>=0)) {
0319             shadeColor.setRgb((int)(rBase+factor*rDiff+.5),
0320                               (int)(gBase+factor*gDiff+.5),
0321                               (int)(bBase+factor*bDiff+.5));
0322             p->setPen(shadeColor);
0323             p->drawRect(r);
0324             r.setRect(r.x()+1, r.y()+1, r.width()-2, r.height()-2);
0325             factor = 1.0 - ((1.0 - factor) / back1);
0326         }
0327 
0328         // and back (2nd half)
0329         while (factor>.01 && (r.width()>=0 && r.height()>=0)) {
0330             shadeColor.setRgb((int)(rBase+factor*rDiff+.5),
0331                               (int)(gBase+factor*gDiff+.5),
0332                               (int)(bBase+factor*bDiff+.5));
0333             p->setPen(shadeColor);
0334             p->drawRect(r);
0335             r.setRect(r.x()+1, r.y()+1, r.width()-2, r.height()-2);
0336             factor = factor * back2;
0337         }
0338 
0339         normal = shadeColor;
0340         // for filling, width and height has to be incremented again
0341         r.setRect(r.x(), r.y(), r.width()+1, r.height()+1);
0342     }
0343 
0344     // fill inside
0345     p->fillRect(r, normal);
0346 }
0347 
0348 
0349 /* Helper for drawField
0350  * Find a line break position in a string, given a font and maximum width
0351  *
0352  * Returns the actually used width, and sets <breakPos>
0353  */
0354 static
0355 int findBreak(int& breakPos, QString text, QFontMetrics* fm, int maxWidth)
0356 {
0357     int usedWidth;
0358 
0359     // does full text fit?
0360     breakPos = text.length();
0361     usedWidth = fm->boundingRect(text).width();
0362     if (usedWidth < maxWidth)
0363         return usedWidth;
0364 
0365     // binary search for best break position in [bottomPos,breakPos].
0366     // We want the result of the binary search to be a bit too large
0367     int bottomPos = 0;
0368     while(1) {
0369         int halfPos = (bottomPos + breakPos)/2;
0370         int halfWidth = fm->horizontalAdvance(text, halfPos);
0371         if (halfWidth < maxWidth) {
0372             bottomPos = halfPos+1;
0373             continue;
0374         }
0375         breakPos = halfPos;
0376         usedWidth = halfWidth;
0377         if (breakPos - bottomPos <3) break;
0378     }
0379 
0380     // final position by taking break boundaries into account.
0381     // possible break boundaries are changing char categories,
0382     // but not middle of "Aa"
0383     QChar::Category lastCat, cat;
0384     int pos = breakPos;
0385     lastCat = text[pos-1].category();
0386     // at minimum 2 chars before break
0387     while (pos > 2) {
0388         pos--;
0389         cat = text[pos-1].category();
0390         if (cat == lastCat) continue;
0391 
0392         // "Aa" has not a possible break inbetween
0393         if ((cat == QChar::Letter_Uppercase) &&
0394             (lastCat == QChar::Letter_Lowercase)) {
0395             lastCat = cat;
0396             continue;
0397         }
0398         lastCat = cat;
0399 
0400         breakPos = pos;
0401         usedWidth = fm->horizontalAdvance(text, breakPos);
0402         if (usedWidth < maxWidth) break;
0403     }
0404     return usedWidth;
0405 }
0406 
0407 
0408 /* Helper for drawField
0409  * Find last line break position in a string from backwards,
0410  * given a font and maximum width
0411  *
0412  * Returns the actually used width, and sets <breakPos>
0413  */
0414 static
0415 int findBreakBackwards(int& breakPos, QString text, QFontMetrics* fm, int maxWidth)
0416 {
0417     int usedWidth;
0418 
0419     // does full text fit?
0420     breakPos = 0;
0421     usedWidth = fm->boundingRect(text).width();
0422     if (usedWidth < maxWidth)
0423         return usedWidth;
0424 
0425     // binary search for best break position in [breakPos,topPos].
0426     // We want the result of the binary search to be a bit too small
0427     int topPos = text.length();
0428     while(1) {
0429         int halfPos = (breakPos + topPos)/2;
0430         int halfWidth = fm->boundingRect(text.mid(halfPos)).width();
0431         if (halfWidth < maxWidth) {
0432             topPos = halfPos-1;
0433             continue;
0434         }
0435         breakPos = halfPos;
0436         usedWidth = halfWidth;
0437         if (topPos - breakPos <3) break;
0438     }
0439 
0440     // final position by taking break boundaries into account.
0441     // possible break boundaries are changing char categories but not middle of "Aa"
0442     QChar::Category lastCat, cat;
0443     int pos = breakPos;
0444     lastCat = text[pos].category();
0445     // at minimum 2 chars before break
0446     while (pos < text.length()-2) {
0447         pos++;
0448         cat = text[pos].category();
0449         if (cat == lastCat) continue;
0450 
0451         // "Aa" has not a possible break inbetween
0452         if ((lastCat == QChar::Letter_Uppercase) &&
0453             (cat == QChar::Letter_Lowercase)) {
0454             lastCat = cat;
0455             continue;
0456         }
0457         lastCat = cat;
0458 
0459         breakPos = pos;
0460         usedWidth = fm->boundingRect(text.mid(breakPos)).width();
0461         if (usedWidth < maxWidth) break;
0462     }
0463     return usedWidth;
0464 }
0465 
0466 
0467 
0468 bool RectDrawing::drawField(QPainter* p, int f, DrawParams* dp)
0469 {
0470     if (!dp) dp = drawParams();
0471 
0472     if (!_fm) {
0473         _fm = new QFontMetrics(dp->font());
0474         _fontHeight = _fm->height();
0475     }
0476 
0477     QRect r = _rect;
0478 
0479     if (0) qDebug() << "DrawField: Rect " << r.x() << "/" << r.y()
0480                     << " - " << r.width() << "x" << r.height();
0481 
0482     int h = _fontHeight;
0483     bool rotate = dp->rotated();
0484     int width   = (rotate ? r.height() : r.width()) -4;
0485     int height  = (rotate ? r.width() : r.height());
0486     int lines   = height / h;
0487 
0488     // stop if there is no space available
0489     if (lines<1) return false;
0490 
0491     // calculate free space in first line (<unused>)
0492     int pos = dp->position(f);
0493     if (pos == DrawParams::Default) {
0494         switch(f%4) {
0495         case 0: pos = DrawParams::TopLeft; break;
0496         case 1: pos = DrawParams::TopRight; break;
0497         case 2: pos = DrawParams::BottomRight; break;
0498         case 3: pos = DrawParams::BottomLeft; break;
0499         }
0500     }
0501 
0502     int unused = 0;
0503     bool isBottom = false;
0504     bool isCenter = false;
0505     bool isRight = false;
0506     int* used = nullptr;
0507     switch(pos) {
0508     case DrawParams::TopLeft:
0509         used = &_usedTopLeft;
0510         if (_usedTopLeft == 0) {
0511             if (_usedTopCenter)
0512                 unused = (width - _usedTopCenter)/2;
0513             else
0514                 unused = width - _usedTopRight;
0515         }
0516         break;
0517 
0518     case DrawParams::TopCenter:
0519         isCenter = true;
0520         used = &_usedTopCenter;
0521         if (_usedTopCenter == 0) {
0522             if (_usedTopLeft > _usedTopRight)
0523                 unused = width - 2 * _usedTopLeft;
0524             else
0525                 unused = width - 2 * _usedTopRight;
0526         }
0527         break;
0528 
0529     case DrawParams::TopRight:
0530         isRight = true;
0531         used = &_usedTopRight;
0532         if (_usedTopRight == 0) {
0533             if (_usedTopCenter)
0534                 unused = (width - _usedTopCenter)/2;
0535             else
0536                 unused = width - _usedTopLeft;
0537         }
0538         break;
0539 
0540     case DrawParams::BottomLeft:
0541         isBottom = true;
0542         used = &_usedBottomLeft;
0543         if (_usedBottomLeft == 0) {
0544             if (_usedBottomCenter)
0545                 unused = (width - _usedBottomCenter)/2;
0546             else
0547                 unused = width - _usedBottomRight;
0548         }
0549         break;
0550 
0551     case DrawParams::BottomCenter:
0552         isCenter = true;
0553         isBottom = true;
0554         used = &_usedBottomCenter;
0555         if (_usedBottomCenter == 0) {
0556             if (_usedBottomLeft > _usedBottomRight)
0557                 unused = width - 2 * _usedBottomLeft;
0558             else
0559                 unused = width - 2 * _usedBottomRight;
0560         }
0561         break;
0562 
0563     case DrawParams::BottomRight:
0564         isRight = true;
0565         isBottom = true;
0566         used = &_usedBottomRight;
0567         if (_usedBottomRight == 0) {
0568             if (_usedBottomCenter)
0569                 unused = (width - _usedBottomCenter)/2;
0570             else
0571                 unused = width - _usedBottomLeft;
0572         }
0573         break;
0574     }
0575 
0576     if (isBottom) {
0577         if ((_usedTopLeft >0) ||
0578             (_usedTopCenter >0) ||
0579             (_usedTopRight >0))
0580             lines--;
0581     }
0582     else if (!isBottom) {
0583         if ((_usedBottomLeft >0) ||
0584             (_usedBottomCenter >0) ||
0585             (_usedBottomRight >0))
0586             lines--;
0587     }
0588     if (lines<1) return false;
0589 
0590     // keep some space between fields if part of line already used
0591     if (unused > 0 && unused < width)
0592         unused -= _fm->averageCharWidth();
0593 
0594     int y = isBottom ? height - h : 0;
0595 
0596     if (unused < 0) unused = 0;
0597     if (unused == 0) {
0598         // no space available in last line at this position
0599         y = isBottom ? (y-h) : (y+h);
0600         lines--;
0601 
0602         if (lines<1) return false;
0603 
0604         // new line: reset used space
0605         if (isBottom)
0606             _usedBottomLeft = _usedBottomCenter = _usedBottomRight = 0;
0607         else
0608             _usedTopLeft = _usedTopCenter = _usedTopRight = 0;
0609 
0610         unused = width;
0611     }
0612 
0613     // stop as soon as possible when there is no space for "..."
0614     static int dotW = 0;
0615     if (!dotW) dotW = _fm->boundingRect(QStringLiteral("...")).width();
0616     if (width < dotW) return false;
0617 
0618     // get text and pixmap now, only if we need to, because it is possible
0619     // that they are calculated on demand (and this can take some time)
0620     QString name = dp->text(f);
0621     if (name.isEmpty()) return 0;
0622     QPixmap pix = dp->pixmap(f);
0623 
0624     // check if pixmap can be drawn
0625     int pixW = pix.width();
0626     int pixH = pix.height();
0627     int pixY = 0;
0628     bool pixDrawn = true;
0629     if (pixW>0) {
0630         pixW += 2; // X distance from pix
0631         if ((width < pixW + dotW) || (height < pixH)) {
0632             // do not draw
0633             pixW = 0;
0634         }
0635         else
0636             pixDrawn = false;
0637     }
0638 
0639     // width of text and pixmap to be drawn
0640     int w = pixW + _fm->boundingRect(name).width();
0641 
0642     if (0) qDebug() << "  For '" << name << "': Unused " << unused
0643                     << ", StrW " << w << ", Width " << width;
0644 
0645     // adjust available lines according to maxLines
0646     int max = dp->maxLines(f);
0647     if ((max > 0) && (lines>max)) lines = max;
0648 
0649     // if we have limited space at 1st line:
0650     // use it only if whole name does fit in last line...
0651     if ((lines>1) && (unused < width) && (w > unused)) {
0652         y = isBottom ? (y-h) : (y+h);
0653         lines--;
0654 
0655         if (lines<1) return false;
0656 
0657         // new line: reset used space
0658         if (isBottom)
0659             _usedBottomLeft = _usedBottomCenter = _usedBottomRight = 0;
0660         else
0661             _usedTopLeft = _usedTopCenter = _usedTopRight = 0;
0662         unused = width;
0663     }
0664 
0665     p->save();
0666     p->setPen( (qGray(dp->backColor().rgb())>100) ? Qt::black : Qt::white);
0667     p->setFont(dp->font());
0668     if (rotate) {
0669         //p->translate(r.x()+2, r.y()+r.height());
0670         p->translate(r.x(), r.y()+r.height()-2);
0671         p->rotate(270);
0672     }
0673     else
0674         p->translate(r.x()+2, r.y());
0675 
0676     /* loop over name parts to break up string depending on available width.
0677      * every char category change is supposed a possible break,
0678      * with the exception Uppercase=>Lowercase.
0679      * It is good enough for numbers, Symbols...
0680      *
0681      * If the text is to be written at the bottom, we start with the
0682      * end of the string (so everything is reverted)
0683      */
0684     QString remaining;
0685     int origLines = lines;
0686     int unusedWidth = unused;
0687     while (lines>0) {
0688 
0689         // more than one line: search for line break
0690         if (dp->allowBreak(f) && w>unusedWidth && lines>1) {
0691             int breakPos;
0692 
0693             if (!isBottom) {
0694                 w = pixW + findBreak(breakPos, name, _fm, unusedWidth - pixW);
0695 
0696                 remaining = name.mid(breakPos);
0697                 // remove space on break point
0698                 if (name[breakPos-1].category() == QChar::Separator_Space)
0699                     name = name.left(breakPos-1);
0700                 else
0701                     name.truncate(breakPos);
0702             }
0703             else { // bottom
0704                 w = pixW + findBreakBackwards(breakPos, name, _fm, unusedWidth - pixW);
0705 
0706                 remaining = name.left(breakPos);
0707                 // remove space on break point
0708                 if (name[breakPos].category() == QChar::Separator_Space)
0709                     name = name.mid(breakPos+1);
0710                 else
0711                     name = name.mid(breakPos);
0712             }
0713         }
0714         else
0715             remaining = QString();
0716 
0717         /* truncate and add ... if needed */
0718         if (dp->allowTruncation(f) && w > unusedWidth) {
0719             name = _fm->elidedText(name, Qt::ElideRight, unusedWidth - pixW);
0720             w = _fm->boundingRect(name).width() + pixW;
0721         }
0722 
0723         if (w > unusedWidth) {
0724             name = QString();
0725             w = pixW;
0726         }
0727 
0728         int x = 0;
0729         if (isCenter)
0730             x = (width - w)/2;
0731         else if (isRight)
0732             x = width - w;
0733 
0734         if (!pixDrawn) {
0735             pixY = y+(h-pixH)/2; // default: center vertically
0736             if (pixH > h) pixY = isBottom ? y-(pixH-h) : y;
0737 
0738             p->drawPixmap( x, pixY, pix);
0739 
0740             // for distance to next text
0741             pixY = isBottom ? (pixY - h - 2) : (pixY + pixH + 2);
0742             pixDrawn = true;
0743         }
0744 
0745 
0746         if (0) qDebug() << "  Drawing '" << name << "' at "
0747                         << x+pixW << "/" << y;
0748 
0749         p->drawText( x+pixW, y,
0750                      unusedWidth - pixW, h,
0751                      Qt::AlignLeft, name);
0752         y = isBottom ? (y-h) : (y+h);
0753         lines--;
0754 
0755         if (remaining.isEmpty()) break;
0756         name = remaining;
0757         w = pixW + _fm->boundingRect(name).width();
0758         unusedWidth = width;
0759     }
0760 
0761     // make sure the pix stays visible
0762     if (pixDrawn && (pixY>0)) {
0763         if (isBottom && (pixY<y)) y = pixY;
0764         if (!isBottom && (pixY>y)) y = pixY;
0765     }
0766 
0767     if (origLines > lines) {
0768         // if only 1 line written, do not reset _used* vars
0769         if (lines - origLines >1) {
0770             if (isBottom)
0771                 _usedBottomLeft = _usedBottomCenter = _usedBottomRight = 0;
0772             else
0773                 _usedTopLeft = _usedTopCenter = _usedTopRight = 0;
0774         }
0775 
0776         // take back one line
0777         y = isBottom ? (y+h) : (y-h);
0778         if (used) *used = w;
0779     }
0780 
0781     // update free space
0782     if (!isBottom) {
0783         if (rotate)
0784             _rect.setRect(r.x()+y, r.y(), r.width()-y, r.height());
0785         else
0786             _rect.setRect(r.x(), r.y()+y, r.width(), r.height()-y);
0787     }
0788     else {
0789         if (rotate)
0790             _rect.setRect(r.x(), r.y(), y+h, r.height());
0791         else
0792             _rect.setRect(r.x(), r.y(), r.width(), y+h);
0793     }
0794 
0795     p->restore();
0796 
0797     return true;
0798 }
0799 
0800 
0801 
0802 
0803 
0804 
0805 //
0806 // TreeMapItemList
0807 //
0808 
0809 
0810 TreeMapItem* TreeMapItemList::commonParent()
0811 {
0812     if (isEmpty()) return nullptr;
0813 
0814     TreeMapItem* parent = first();
0815     for(int i = 1; parent && i<size(); i++)
0816         parent = parent->commonParent(at(i));
0817 
0818     return parent;
0819 }
0820 
0821 class TreeMapItemLessThan
0822 {
0823 public:
0824     bool operator()(const TreeMapItem* i1, const TreeMapItem* i2) const
0825     {
0826         TreeMapItem* p = i1->parent();
0827         // should not happen
0828         if (!p) return false;
0829 
0830         bool ascending;
0831         int textNo = p->sorting(&ascending);
0832         if (textNo < 0) {
0833             double v1 = i1->value();
0834             double v2 = i2->value();
0835             if (ascending)
0836                 return v1 < v2;
0837             else
0838                 return v2 < v1;
0839         }
0840 
0841         QString t1 = i1->text(textNo);
0842         QString t2 = i2->text(textNo);
0843         if (ascending)
0844             return t1 < t2;
0845         else
0846             return t2 < t1;
0847     }
0848 };
0849 
0850 TreeMapItemLessThan treeMapItemLessThan;
0851 
0852 // TreeMapItem
0853 
0854 TreeMapItem::TreeMapItem(TreeMapItem* parent, double value)
0855 {
0856     _value = value;
0857     _parent = parent;
0858 
0859     _sum = 0;
0860     _children = nullptr;
0861     _widget = nullptr;
0862     _index = -1;
0863     _depth = -1; // not set
0864     _unused_self = 0;
0865 
0866     if (_parent) {
0867         // take sorting from parent
0868         _sortTextNo = _parent->sorting(&_sortAscending);
0869         _parent->addItem(this);
0870     }
0871     else {
0872         _sortAscending = false;
0873         _sortTextNo = -1; // default: no sorting
0874     }
0875 }
0876 
0877 
0878 TreeMapItem::TreeMapItem(TreeMapItem* parent, double value,
0879                          const QString& text1, const QString& text2,
0880                          const QString& text3, const QString& text4)
0881 {
0882     _value = value;
0883     _parent = parent;
0884 
0885     // this resizes the text vector only if needed
0886     if (!text4.isEmpty()) setText(3, text4);
0887     if (!text3.isEmpty()) setText(2, text3);
0888     if (!text2.isEmpty()) setText(1, text2);
0889     setText(0, text1);
0890 
0891     _sum = 0;
0892     _children = nullptr;
0893     _widget = nullptr;
0894     _index = -1;
0895     _depth = -1; // not set
0896     _unused_self = 0;
0897     _sortAscending = false;
0898     _sortTextNo = -1; // default: no sorting
0899 
0900     if (_parent) _parent->addItem(this);
0901 }
0902 
0903 TreeMapItem::~TreeMapItem()
0904 {
0905     if (_children) {
0906         qDeleteAll(*_children);
0907         delete _children;
0908         _children = nullptr;
0909     }
0910 
0911     // finally, notify widget about deletion
0912     if (_widget) _widget->deletingItem(this);
0913 }
0914 
0915 void TreeMapItem::setParent(TreeMapItem* p)
0916 {
0917     _parent = p;
0918     if (p) _widget = p->_widget;
0919 }
0920 
0921 bool TreeMapItem::isChildOf(TreeMapItem* item)
0922 {
0923     if (!item) return false;
0924 
0925     TreeMapItem* i = this;
0926     while (i) {
0927         if (item == i) return true;
0928         i = i->_parent;
0929     }
0930     return false;
0931 }
0932 
0933 TreeMapItem* TreeMapItem::commonParent(TreeMapItem* item)
0934 {
0935     while (item && !isChildOf(item)) {
0936         item = item->parent();
0937     }
0938     return item;
0939 }
0940 
0941 void TreeMapItem::redraw()
0942 {
0943     if (_widget)
0944         _widget->redraw(this);
0945 }
0946 
0947 void TreeMapItem::clear()
0948 {
0949     if (_children) {
0950         // delete selected items below this item from selection
0951         if (_widget) _widget->clearSelection(this);
0952 
0953         qDeleteAll(*_children);
0954         delete _children;
0955         _children = nullptr;
0956     }
0957 }
0958 
0959 
0960 // invalidates current children and forces redraw
0961 // this is only useful when children are created on demand in items()
0962 void TreeMapItem::refresh()
0963 {
0964     clear();
0965     redraw();
0966 }
0967 
0968 
0969 QStringList TreeMapItem::path(int textNo) const
0970 {
0971     QStringList list(text(textNo));
0972 
0973     TreeMapItem* i = _parent;
0974     while (i) {
0975         QString text = i->text(textNo);
0976         if (!text.isEmpty())
0977             list.prepend(i->text(textNo));
0978         i = i->_parent;
0979     }
0980     return list;
0981 }
0982 
0983 int TreeMapItem::depth() const
0984 {
0985     if (_depth>0) return _depth;
0986 
0987     if (_parent)
0988         return _parent->depth() + 1;
0989     return 1;
0990 }
0991 
0992 
0993 bool TreeMapItem::initialized()
0994 {
0995     if (!_children) {
0996         _children = new TreeMapItemList;
0997         return false;
0998     }
0999     return true;
1000 }
1001 
1002 void TreeMapItem::addItem(TreeMapItem* i)
1003 {
1004     if (!i) return;
1005 
1006     if (!_children)
1007         _children = new TreeMapItemList;
1008 
1009     i->setParent(this);
1010 
1011     _children->append(i); // preserve insertion order
1012     if (sorting(nullptr) != -1)
1013         std::sort(_children->begin(), _children->end(), treeMapItemLessThan);
1014 }
1015 
1016 
1017 // default implementations of virtual functions
1018 
1019 double TreeMapItem::value() const
1020 {
1021     return _value;
1022 }
1023 
1024 double TreeMapItem::sum() const
1025 {
1026     return _sum;
1027 }
1028 
1029 DrawParams::Position TreeMapItem::position(int f) const
1030 {
1031     Position p = StoredDrawParams::position(f);
1032     if (_widget && (p == Default))
1033         p = _widget->fieldPosition(f);
1034 
1035     return p;
1036 }
1037 
1038 // use widget font
1039 const QFont& TreeMapItem::font() const
1040 {
1041     return _widget->currentFont();
1042 }
1043 
1044 
1045 bool TreeMapItem::isMarked(int) const
1046 {
1047     return false;
1048 }
1049 
1050 
1051 int TreeMapItem::borderWidth() const
1052 {
1053     if (_widget)
1054         return _widget->borderWidth();
1055 
1056     return 2;
1057 }
1058 
1059 int TreeMapItem::sorting(bool* ascending) const
1060 {
1061     if (ascending) *ascending = _sortAscending;
1062     return _sortTextNo;
1063 }
1064 
1065 // do *not* set sorting recursively
1066 void TreeMapItem::setSorting(int textNo, bool ascending, bool recursive)
1067 {
1068     if (recursive && _children) {
1069         foreach(TreeMapItem* child, *_children) {
1070             child->setSorting(textNo, ascending, recursive);
1071         }
1072     }
1073 
1074     if (_sortTextNo == textNo) {
1075         if(_sortAscending == ascending) return;
1076         if (textNo == -1) {
1077             // when no sorting is done, order change does not do anything
1078             _sortAscending = ascending;
1079             return;
1080         }
1081     }
1082     _sortAscending = ascending;
1083     _sortTextNo = textNo;
1084 
1085     if (_children && _sortTextNo != -1)
1086         std::sort(_children->begin(), _children->end(), treeMapItemLessThan);
1087 }
1088 
1089 void TreeMapItem::resort(bool recursive)
1090 {
1091     if (!_children) return;
1092 
1093     if (_sortTextNo != -1)
1094         std::sort(_children->begin(), _children->end(), treeMapItemLessThan);
1095 
1096     if (recursive)
1097         foreach(TreeMapItem* i, *_children)
1098             i->resort(recursive);
1099 }
1100 
1101 
1102 TreeMapItem::SplitMode TreeMapItem::splitMode() const
1103 {
1104     if (_widget)
1105         return _widget->splitMode();
1106 
1107     return Best;
1108 }
1109 
1110 int TreeMapItem::rtti() const
1111 {
1112     return 0;
1113 }
1114 
1115 TreeMapItemList* TreeMapItem::children()
1116 {
1117     if (!_children)
1118         _children = new TreeMapItemList;
1119 
1120     return _children;
1121 }
1122 
1123 void TreeMapItem::clearItemRect()
1124 {
1125     _rect = QRect();
1126     clearFreeRects();
1127 }
1128 
1129 void TreeMapItem::clearFreeRects()
1130 {
1131     _freeRects.clear();
1132 }
1133 
1134 void TreeMapItem::addFreeRect(const QRect& r)
1135 {
1136     // do not add invalid rects
1137     if ((r.width() < 1) || (r.height() < 1)) return;
1138 
1139     if (0) qDebug() << "addFree(" << path(0).join(QLatin1Char('/')) << ", "
1140                     << r.x() << "/" << r.y() << "-"
1141                     << r.width() << "x" << r.height() << ")";
1142 
1143     if (_freeRects.isEmpty()) {
1144         _freeRects.append(r);
1145         return;
1146     }
1147 
1148     // join rect with last rect if possible
1149     // this saves memory and does not make the tooltip flicker
1150     QRect& last = _freeRects.last();
1151     bool replaced = false;
1152     if ((last.left() == r.left()) && (last.width() == r.width())) {
1153         if ((last.bottom()+1 == r.top()) || (r.bottom()+1 == last.top())) {
1154             last |= r;
1155             replaced = true;
1156         }
1157     }
1158     else if ((last.top() == r.top()) && (last.height() == r.height())) {
1159         if ((last.right()+1 == r.left()) || (r.right()+1 == last.left())) {
1160             last |= r;
1161             replaced = true;
1162         }
1163     }
1164 
1165     if (!replaced) {
1166         _freeRects.append(r);
1167         return;
1168     }
1169 
1170     if (0) qDebug() << "  united with last to ("
1171                     << last.x() << "/" << last.y() << "-"
1172                     << last.width() << "x" << last.height() << ")";
1173 }
1174 
1175 
1176 
1177 // TreeMapWidget
1178 
1179 TreeMapWidget::TreeMapWidget(TreeMapItem* base,
1180                              QWidget* parent)
1181     : QWidget(parent)
1182 {
1183     _base = base;
1184     _base->setWidget(this);
1185 
1186     _font = font();
1187     _fontHeight = fontMetrics().height();
1188 
1189 
1190     // default behaviour
1191     _selectionMode = Single;
1192     _splitMode = TreeMapItem::AlwaysBest;
1193     _visibleWidth = 2;
1194     _reuseSpace = false;
1195     _skipIncorrectBorder = false;
1196     _drawSeparators = false;
1197     _allowRotation = true;
1198     _borderWidth = 2;
1199     _shading = true; // beautiful is default!
1200     _maxSelectDepth = -1; // unlimited
1201     _maxDrawingDepth = -1; // unlimited
1202     _minimalArea = -1; // unlimited
1203     _markNo = 0;
1204 
1205     for(int i=0;i<4;i++) {
1206         _drawFrame[i] = true;
1207         _transparent[i] = false;
1208     }
1209 
1210     // _stopAtText will be unset on resizing (per default)
1211     // _textVisible will be true on resizing (per default)
1212     // _forceText will be false on resizing (per default)
1213 
1214     // start state: _selection is an empty list
1215     _current = nullptr;
1216     _oldCurrent = nullptr;
1217     _pressed = nullptr;
1218     _lastOver = nullptr;
1219     _needsRefresh = _base;
1220 
1221     setAttribute(Qt::WA_NoSystemBackground, true);
1222     setFocusPolicy(Qt::StrongFocus);
1223 }
1224 
1225 TreeMapWidget::~TreeMapWidget()
1226 {
1227     delete _base;
1228 }
1229 
1230 const QFont& TreeMapWidget::currentFont() const
1231 {
1232     return _font;
1233 }
1234 
1235 void TreeMapWidget::setSplitMode(TreeMapItem::SplitMode m)
1236 {
1237     if (_splitMode == m) return;
1238 
1239     _splitMode = m;
1240     redraw();
1241 }
1242 
1243 TreeMapItem::SplitMode TreeMapWidget::splitMode() const
1244 {
1245     return _splitMode;
1246 }
1247 
1248 bool TreeMapWidget::setSplitMode(const QString& mode)
1249 {
1250     if (mode == QLatin1String("Bisection")) setSplitMode(TreeMapItem::Bisection);
1251     else if (mode == QLatin1String("Columns")) setSplitMode(TreeMapItem::Columns);
1252     else if (mode == QLatin1String("Rows")) setSplitMode(TreeMapItem::Rows);
1253     else if (mode == QLatin1String("AlwaysBest")) setSplitMode(TreeMapItem::AlwaysBest);
1254     else if (mode == QLatin1String("Best"))       setSplitMode(TreeMapItem::Best);
1255     else if (mode == QLatin1String("HAlternate")) setSplitMode(TreeMapItem::HAlternate);
1256     else if (mode == QLatin1String("VAlternate")) setSplitMode(TreeMapItem::VAlternate);
1257     else if (mode == QLatin1String("Horizontal")) setSplitMode(TreeMapItem::Horizontal);
1258     else if (mode == QLatin1String("Vertical"))   setSplitMode(TreeMapItem::Vertical);
1259     else return false;
1260 
1261     return true;
1262 }
1263 
1264 QString TreeMapWidget::splitModeString() const
1265 {
1266     QString mode;
1267     switch(splitMode()) {
1268     case TreeMapItem::Bisection:  mode = QStringLiteral("Bisection"); break;
1269     case TreeMapItem::Columns:    mode = QStringLiteral("Columns"); break;
1270     case TreeMapItem::Rows:       mode = QStringLiteral("Rows"); break;
1271     case TreeMapItem::AlwaysBest: mode = QStringLiteral("AlwaysBest"); break;
1272     case TreeMapItem::Best:       mode = QStringLiteral("Best"); break;
1273     case TreeMapItem::HAlternate: mode = QStringLiteral("HAlternate"); break;
1274     case TreeMapItem::VAlternate: mode = QStringLiteral("VAlternate"); break;
1275     case TreeMapItem::Horizontal: mode = QStringLiteral("Horizontal"); break;
1276     case TreeMapItem::Vertical:   mode = QStringLiteral("Vertical"); break;
1277     default: mode = QStringLiteral("Unknown"); break;
1278     }
1279     return mode;
1280 }
1281 
1282 
1283 void TreeMapWidget::setShadingEnabled(bool s)
1284 {
1285     if (_shading == s) return;
1286 
1287     _shading = s;
1288     redraw();
1289 }
1290 
1291 void TreeMapWidget::drawFrame(int d, bool b)
1292 {
1293     if ((d<0) || (d>=4) || (_drawFrame[d]==b)) return;
1294 
1295     _drawFrame[d] = b;
1296     redraw();
1297 }
1298 
1299 void TreeMapWidget::setTransparent(int d, bool b)
1300 {
1301     if ((d<0) || (d>=4) || (_transparent[d]==b)) return;
1302 
1303     _transparent[d] = b;
1304     redraw();
1305 }
1306 
1307 void TreeMapWidget::setAllowRotation(bool enable)
1308 {
1309     if (_allowRotation == enable) return;
1310 
1311     _allowRotation = enable;
1312     redraw();
1313 }
1314 
1315 void TreeMapWidget::setVisibleWidth(int width, bool reuseSpace)
1316 {
1317     if (_visibleWidth == width && _reuseSpace == reuseSpace) return;
1318 
1319     _visibleWidth = width;
1320     _reuseSpace = reuseSpace;
1321     redraw();
1322 }
1323 
1324 void TreeMapWidget::setSkipIncorrectBorder(bool enable)
1325 {
1326     if (_skipIncorrectBorder == enable) return;
1327 
1328     _skipIncorrectBorder = enable;
1329     redraw();
1330 }
1331 
1332 void TreeMapWidget::setBorderWidth(int w)
1333 {
1334     if (_borderWidth == w) return;
1335 
1336     _borderWidth = w;
1337     redraw();
1338 }
1339 
1340 void TreeMapWidget::setMaxDrawingDepth(int d)
1341 {
1342     if (_maxDrawingDepth == d) return;
1343 
1344     _maxDrawingDepth = d;
1345     redraw();
1346 }
1347 
1348 QString TreeMapWidget::defaultFieldType(int f) const
1349 {
1350     return tr("Text %1").arg(f+1);
1351 }
1352 
1353 QString TreeMapWidget::defaultFieldStop(int) const
1354 {
1355     return QString();
1356 }
1357 
1358 bool TreeMapWidget::defaultFieldVisible(int f) const
1359 {
1360     return (f<2);
1361 }
1362 
1363 bool TreeMapWidget::defaultFieldForced(int) const
1364 {
1365     return false;
1366 }
1367 
1368 DrawParams::Position TreeMapWidget::defaultFieldPosition(int f) const
1369 {
1370     switch(f%4) {
1371     case 0: return DrawParams::TopLeft;
1372     case 1: return DrawParams::TopRight;
1373     case 2: return DrawParams::BottomRight;
1374     case 3: return DrawParams::BottomLeft;
1375     default:break;
1376     }
1377     return DrawParams::TopLeft;
1378 }
1379 
1380 bool TreeMapWidget::resizeAttr(int size)
1381 {
1382     if (size<0 || size>=MAX_FIELD) return false;
1383 
1384     if (size>(int)_attr.size()) {
1385         int oldSize = _attr.size();
1386         _attr.resize(size);
1387         while (oldSize<size) {
1388             _attr[oldSize].type    = defaultFieldType(oldSize);
1389             _attr[oldSize].stop    = defaultFieldStop(oldSize);
1390             _attr[oldSize].visible = defaultFieldVisible(oldSize);
1391             _attr[oldSize].forced  = defaultFieldForced(oldSize);
1392             _attr[oldSize].pos     = defaultFieldPosition(oldSize);
1393             oldSize++;
1394         }
1395     }
1396     return true;
1397 }
1398 
1399 void TreeMapWidget::setFieldType(int f, const QString& type)
1400 {
1401     if (((int)_attr.size() < f+1) &&
1402         (type == defaultFieldType(f))) return;
1403     if (resizeAttr(f+1)) _attr[f].type = type;
1404 
1405     // no need to redraw: the type string is not visible in the TreeMap
1406 }
1407 
1408 QString TreeMapWidget::fieldType(int f) const
1409 {
1410     if (f<0 || (int)_attr.size()<f+1) return defaultFieldType(f);
1411     return _attr[f].type;
1412 }
1413 
1414 void TreeMapWidget::setFieldStop(int f, const QString& stop)
1415 {
1416     if (((int)_attr.size() < f+1) &&
1417         (stop == defaultFieldStop(f))) return;
1418     if (resizeAttr(f+1)) {
1419         _attr[f].stop = stop;
1420         redraw();
1421     }
1422 }
1423 
1424 QString TreeMapWidget::fieldStop(int f) const
1425 {
1426     if (f<0 || (int)_attr.size()<f+1) return defaultFieldStop(f);
1427     return _attr[f].stop;
1428 }
1429 
1430 void TreeMapWidget::setFieldVisible(int f, bool enable)
1431 {
1432     if (((int)_attr.size() < f+1) &&
1433         (enable == defaultFieldVisible(f))) return;
1434 
1435     if (resizeAttr(f+1)) {
1436         _attr[f].visible = enable;
1437         redraw();
1438     }
1439 }
1440 
1441 bool TreeMapWidget::fieldVisible(int f) const
1442 {
1443     if (f<0 || (int)_attr.size()<f+1)
1444         return defaultFieldVisible(f);
1445 
1446     return _attr[f].visible;
1447 }
1448 
1449 void TreeMapWidget::setFieldForced(int f, bool enable)
1450 {
1451     if (((int)_attr.size() < f+1) &&
1452         (enable == defaultFieldForced(f))) return;
1453 
1454     if (resizeAttr(f+1)) {
1455         _attr[f].forced = enable;
1456         if (_attr[f].visible) redraw();
1457     }
1458 }
1459 
1460 bool TreeMapWidget::fieldForced(int f) const
1461 {
1462     if (f<0 || (int)_attr.size()<f+1)
1463         return defaultFieldForced(f);
1464 
1465     return _attr[f].forced;
1466 }
1467 
1468 void TreeMapWidget::setFieldPosition(int f, TreeMapItem::Position pos)
1469 {
1470     if (((int)_attr.size() < f+1) &&
1471         (pos == defaultFieldPosition(f))) return;
1472 
1473     if (resizeAttr(f+1)) {
1474         _attr[f].pos = pos;
1475         if (_attr[f].visible) redraw();
1476     }
1477 }
1478 
1479 DrawParams::Position TreeMapWidget::fieldPosition(int f) const
1480 {
1481     if (f<0 || (int)_attr.size()<f+1)
1482         return defaultFieldPosition(f);
1483 
1484     return _attr[f].pos;
1485 }
1486 
1487 void TreeMapWidget::setFieldPosition(int f, const QString& pos)
1488 {
1489     if (pos == QLatin1String("TopLeft"))
1490         setFieldPosition(f, DrawParams::TopLeft);
1491     else if (pos == QLatin1String("TopCenter"))
1492         setFieldPosition(f, DrawParams::TopCenter);
1493     else if (pos == QLatin1String("TopRight"))
1494         setFieldPosition(f, DrawParams::TopRight);
1495     else if (pos == QLatin1String("BottomLeft"))
1496         setFieldPosition(f, DrawParams::BottomLeft);
1497     else if (pos == QLatin1String("BottomCenter"))
1498         setFieldPosition(f, DrawParams::BottomCenter);
1499     else if (pos == QLatin1String("BottomRight"))
1500         setFieldPosition(f, DrawParams::BottomRight);
1501     else if (pos == QLatin1String("Default"))
1502         setFieldPosition(f, DrawParams::Default);
1503 }
1504 
1505 QString TreeMapWidget::fieldPositionString(int f) const
1506 {
1507     TreeMapItem::Position pos = fieldPosition(f);
1508     if (pos == DrawParams::TopLeft) return QStringLiteral("TopLeft");
1509     if (pos == DrawParams::TopCenter) return QStringLiteral("TopCenter");
1510     if (pos == DrawParams::TopRight) return QStringLiteral("TopRight");
1511     if (pos == DrawParams::BottomLeft) return QStringLiteral("BottomLeft");
1512     if (pos == DrawParams::BottomCenter) return QStringLiteral("BottomCenter");
1513     if (pos == DrawParams::BottomRight) return QStringLiteral("BottomRight");
1514     if (pos == DrawParams::Default) return QStringLiteral("Default");
1515     return QStringLiteral("unknown");
1516 }
1517 
1518 void TreeMapWidget::setMinimalArea(int area)
1519 {
1520     if (_minimalArea == area) return;
1521 
1522     _minimalArea = area;
1523     redraw();
1524 }
1525 
1526 
1527 void TreeMapWidget::deletingItem(TreeMapItem* i)
1528 {
1529     // remove any references to the item to be deleted
1530     _selection.removeAll(i);
1531     _tmpSelection.removeAll(i);
1532 
1533     if (_current == i) _current = nullptr;
1534     if (_oldCurrent == i) _oldCurrent = nullptr;
1535     if (_pressed == i) _pressed = nullptr;
1536     if (_lastOver == i) _lastOver = nullptr;
1537 
1538     // do not redraw a deleted item
1539     if (_needsRefresh == i) {
1540         // we can safely redraw the parent, as deleting order is
1541         // from child to parent; i.e. i->parent() is existing.
1542         _needsRefresh = i->parent();
1543     }
1544 }
1545 
1546 
1547 QString TreeMapWidget::tipString(TreeMapItem* i) const
1548 {
1549     QString tip, itemTip;
1550 
1551     while (i) {
1552         if (!i->text(0).isEmpty()) {
1553             itemTip = i->text(0);
1554             if (!i->text(1).isEmpty())
1555                 itemTip += " (" + i->text(1) + ')';
1556 
1557             if (!tip.isEmpty())
1558                 tip += '\n';
1559 
1560             tip += itemTip;
1561         }
1562         i = i->parent();
1563     }
1564     return tip;
1565 }
1566 
1567 TreeMapItem* TreeMapWidget::item(int x, int y) const
1568 {
1569 
1570     if (!rect().contains(x, y)) return nullptr;
1571     if (DEBUG_DRAWING) qDebug() << "item(" << x << "," << y << "):";
1572 
1573     TreeMapItem* p = _base;
1574     TreeMapItem* i;
1575     while (1) {
1576         TreeMapItemList* list = p->children();
1577         i = nullptr;
1578         if (list) {
1579             int idx;
1580             for (idx=0; idx<list->size(); idx++) {
1581                 i = list->at(idx);
1582 
1583                 if (DEBUG_DRAWING)
1584                     qDebug() << "  Checking " << i->path(0).join(QLatin1Char('/')) << " ("
1585                              << i->itemRect().x() << "/" << i->itemRect().y()
1586                              << "-" << i->itemRect().width()
1587                              << "x" << i->itemRect().height() << ")";
1588 
1589                 if (i->itemRect().contains(x, y)) {
1590 
1591                     if (DEBUG_DRAWING) qDebug() << "  .. Got. Index " << idx;
1592 
1593                     p->setIndex(idx);
1594                     break;
1595                 }
1596             }
1597             if (idx == list->size()) i = nullptr; // not contained in child
1598         }
1599 
1600         if (!i) {
1601             static TreeMapItem* last = nullptr;
1602             if (p != last) {
1603                 last = p;
1604 
1605                 if (DEBUG_DRAWING)
1606                     qDebug() << "item(" << x << "," << y << "): Got "
1607                              << p->path(0).join(QLatin1Char('/')) << " (Size "
1608                              << p->itemRect().width() << "x" << p->itemRect().height()
1609                              << ", Val " << p->value() << ")";
1610             }
1611 
1612             return p;
1613         }
1614         p = i;
1615     }
1616     return nullptr;
1617 }
1618 
1619 TreeMapItem* TreeMapWidget::possibleSelection(TreeMapItem* i) const
1620 {
1621     if (i) {
1622         if (_maxSelectDepth>=0) {
1623             int depth = i->depth();
1624             while(i && depth > _maxSelectDepth) {
1625                 i = i->parent();
1626                 depth--;
1627             }
1628         }
1629     }
1630     return i;
1631 }
1632 
1633 TreeMapItem* TreeMapWidget::visibleItem(TreeMapItem* i) const
1634 {
1635     if (i) {
1636         /* Must have a visible area */
1637         while(i && ((i->itemRect().width() <1) ||
1638                     (i->itemRect().height() <1))) {
1639             TreeMapItem* p = i->parent();
1640             if (!p) break;
1641             int idx = p->children()->indexOf(i);
1642             idx--;
1643             if (idx<0)
1644                 i = p;
1645             else
1646                 i = p->children()->at(idx);
1647         }
1648     }
1649     return i;
1650 }
1651 
1652 void TreeMapWidget::setSelected(TreeMapItem* item, bool selected)
1653 {
1654     if (!item) return;
1655     item = possibleSelection(item);
1656     setCurrent(item);
1657 
1658     TreeMapItem* changed = setTmpSelected(item, selected);
1659     if (!changed) return;
1660 
1661     _selection = _tmpSelection;
1662     if (_selectionMode == Single)
1663         emit selectionChanged(item);
1664     emit selectionChanged();
1665     redraw(changed);
1666 
1667     if (0) qDebug() << (selected ? "S":"Des") << "elected Item "
1668                     << (item ? item->path(0).join(QString()) : QStringLiteral("(null)"))
1669                     << " (depth " << (item ? item->depth() : -1)
1670                     << ")";
1671 }
1672 
1673 void TreeMapWidget::setMarked(int markNo, bool redrawWidget)
1674 {
1675     // if there is no marking, return
1676     if ((_markNo == 0) && (markNo == 0)) return;
1677 
1678     _markNo = markNo;
1679     if (!clearSelection() && redrawWidget) redraw();
1680 }
1681 
1682 /* Returns all items which appear only in one of the given lists */
1683 TreeMapItemList TreeMapWidget::diff(TreeMapItemList& l1,
1684                                     TreeMapItemList& l2)
1685 {
1686     TreeMapItemList l;
1687 
1688     foreach(TreeMapItem* i, l1)
1689         if (!l2.contains(i))
1690             l.append(i);
1691 
1692     foreach(TreeMapItem* i, l2)
1693         if (!l1.contains(i))
1694             l.append(i);
1695 
1696     return l;
1697 }
1698 
1699 /* Only modifies _tmpSelection.
1700  * Returns 0 when no change happened, otherwise the TreeMapItem that has
1701  * to be redrawn for all changes.
1702  */
1703 TreeMapItem* TreeMapWidget::setTmpSelected(TreeMapItem* item, bool selected)
1704 {
1705     if (!item) return nullptr;
1706     if (_selectionMode == NoSelection) return nullptr;
1707 
1708     TreeMapItemList old = _tmpSelection;
1709 
1710     if (_selectionMode == Single) {
1711         _tmpSelection.clear();
1712         if (selected) _tmpSelection.append(item);
1713     }
1714     else {
1715         if (selected) {
1716             // first remove any selection which is parent or child of <item>
1717             foreach(TreeMapItem* i, _tmpSelection)
1718                 if (i->isChildOf(item) || item->isChildOf(i))
1719                     _tmpSelection.removeAll(i);
1720 
1721             _tmpSelection.append(item);
1722         }
1723         else
1724             _tmpSelection.removeAll(item);
1725     }
1726 
1727     return diff(old, _tmpSelection).commonParent();
1728 }
1729 
1730 
1731 bool TreeMapWidget::clearSelection(TreeMapItem* parent)
1732 {
1733     TreeMapItemList old = _selection;
1734 
1735     // remove any selection which is child of <parent>
1736     foreach(TreeMapItem* i, _selection)
1737         if (i->isChildOf(parent))
1738             _selection.removeAll(i);
1739 
1740     TreeMapItem* changed = diff(old, _selection).commonParent();
1741     if (changed) {
1742         changed->redraw();
1743         emit selectionChanged();
1744     }
1745     return (changed != nullptr);
1746 }
1747 
1748 bool TreeMapWidget::isSelected(TreeMapItem* i) const
1749 {
1750     if (!i) return false;
1751     return _selection.contains(i);
1752 }
1753 
1754 bool TreeMapWidget::isTmpSelected(TreeMapItem* i)
1755 {
1756     if (!i) return false;
1757     return _tmpSelection.contains(i);
1758 }
1759 
1760 
1761 void TreeMapWidget::setCurrent(TreeMapItem* i, bool kbd)
1762 {
1763     TreeMapItem* old = _current;
1764     _current = i;
1765 
1766     if (_markNo >0) {
1767         // remove mark
1768         _markNo = 0;
1769 
1770         if (i) qDebug() << "setCurrent(" << i->path(0).join(QLatin1Char('/'))
1771                         << ") - mark removed";
1772 
1773         // always complete redraw needed to remove mark
1774         redraw();
1775 
1776         if (old == _current) return;
1777     }
1778     else {
1779         if (old == _current) return;
1780 
1781         if (old) old->redraw();
1782         if (i) i->redraw();
1783     }
1784 
1785     //qDebug() << "Current Item " << (i ? qPrintable(i->path()) : "(null)");
1786 
1787     emit currentChanged(i, kbd);
1788 }
1789 
1790 void TreeMapWidget::setRangeSelection(TreeMapItem* i1,
1791                                       TreeMapItem* i2, bool selected)
1792 {
1793     i1 = possibleSelection(i1);
1794     i2 = possibleSelection(i2);
1795     setCurrent(i2);
1796 
1797     TreeMapItem* changed = setTmpRangeSelection(i1, i2, selected);
1798     if (!changed) return;
1799 
1800     _selection = _tmpSelection;
1801     if (_selectionMode == Single)
1802         emit selectionChanged(i2);
1803     emit selectionChanged();
1804     redraw(changed);
1805 }
1806 
1807 TreeMapItem* TreeMapWidget::setTmpRangeSelection(TreeMapItem* i1,
1808                                                  TreeMapItem* i2,
1809                                                  bool selected)
1810 {
1811     if ((i1 == nullptr) && (i2 == nullptr)) return nullptr;
1812     if ((i1 == nullptr) || i1->isChildOf(i2)) return setTmpSelected(i2, selected);
1813     if ((i2 == nullptr) || i2->isChildOf(i1)) return setTmpSelected(i1, selected);
1814 
1815     TreeMapItem* changed = setTmpSelected(i1, selected);
1816     TreeMapItem* changed2 = setTmpSelected(i2, selected);
1817     if (changed2) changed = changed2->commonParent(changed);
1818 
1819     TreeMapItem* commonParent = i1;
1820     while (commonParent && !i2->isChildOf(commonParent)) {
1821         i1 = commonParent;
1822         commonParent = commonParent->parent();
1823     }
1824     if (!commonParent) return changed;
1825     while (i2 && i2->parent() != commonParent)
1826         i2 = i2->parent();
1827     if (!i2) return changed;
1828 
1829     TreeMapItemList* list = commonParent->children();
1830     if (!list) return changed;
1831 
1832     bool between = false;
1833     foreach(TreeMapItem* i, *list) {
1834         if (between) {
1835             if (i==i1 || i==i2) break;
1836             changed2 = setTmpSelected(i, selected);
1837             if (changed2) changed = changed2->commonParent(changed);
1838         }
1839         else if (i==i1 || i==i2)
1840             between = true;
1841     }
1842 
1843     return changed;
1844 }
1845 
1846 void TreeMapWidget::contextMenuEvent( QContextMenuEvent* e )
1847 {
1848     //qDebug() << "TreeMapWidget::contextMenuEvent";
1849 
1850     if ( receivers( SIGNAL(contextMenuRequested(TreeMapItem*,QPoint)) ) )
1851         e->accept();
1852 
1853     if ( e->reason() == QContextMenuEvent::Keyboard ) {
1854         QRect r = (_current) ? _current->itemRect() : _base->itemRect();
1855         QPoint p = QPoint(r.left() + r.width()/2, r.top() + r.height()/2);
1856         emit contextMenuRequested(_current, p);
1857     }
1858     else {
1859         TreeMapItem* i = item(e->x(), e->y());
1860         emit contextMenuRequested(i, e->pos());
1861     }
1862 }
1863 
1864 
1865 void TreeMapWidget::mousePressEvent( QMouseEvent* e )
1866 {
1867     //qDebug() << "TreeMapWidget::mousePressEvent";
1868 
1869     _oldCurrent = _current;
1870 
1871     TreeMapItem* i = item(e->x(), e->y());
1872 
1873     _pressed = i;
1874 
1875     _inShiftDrag = e->modifiers() & Qt::ShiftModifier;
1876     _inControlDrag = e->modifiers() & Qt::ControlModifier;
1877     _lastOver = _pressed;
1878 
1879     TreeMapItem* changed = nullptr;
1880     TreeMapItem* item = possibleSelection(_pressed);
1881 
1882     switch(_selectionMode) {
1883     case Single:
1884         changed = setTmpSelected(item, true);
1885         break;
1886     case Multi:
1887         changed = setTmpSelected(item, !isTmpSelected(item));
1888         break;
1889     case Extended:
1890         if (_inControlDrag)
1891             changed = setTmpSelected(item, !isTmpSelected(item));
1892         else if (_inShiftDrag) {
1893             TreeMapItem* sCurrent = possibleSelection(_current);
1894             changed = setTmpRangeSelection(sCurrent, item,
1895                                            !isTmpSelected(item));
1896         }
1897         else {
1898             _selectionMode = Single;
1899             changed = setTmpSelected(item, true);
1900             _selectionMode = Extended;
1901         }
1902         break;
1903     default:
1904         break;
1905     }
1906 
1907     // item under mouse always selected on right button press
1908     if (e->button() == Qt::RightButton) {
1909         TreeMapItem* changed2 = setTmpSelected(item, true);
1910         if (changed2) changed = changed2->commonParent(changed);
1911     }
1912 
1913     setCurrent(_pressed);
1914 
1915     if (changed)
1916         redraw(changed);
1917 
1918     if (e->button() == Qt::RightButton) {
1919 
1920         // emit selection change
1921         if (! (_tmpSelection == _selection)) {
1922             _selection = _tmpSelection;
1923             if (_selectionMode == Single)
1924                 emit selectionChanged(_lastOver);
1925             emit selectionChanged();
1926         }
1927         _pressed = nullptr;
1928         _lastOver = nullptr;
1929         emit rightButtonPressed(i, e->pos());
1930     }
1931 }
1932 
1933 void TreeMapWidget::mouseMoveEvent( QMouseEvent* e )
1934 {
1935     //qDebug() << "TreeMapWidget::mouseMoveEvent";
1936 
1937     if (!_pressed) return;
1938     TreeMapItem* over = item(e->x(), e->y());
1939     if (_lastOver == over) return;
1940 
1941     setCurrent(over);
1942     if (over == nullptr) {
1943         _lastOver = nullptr;
1944         return;
1945     }
1946 
1947     TreeMapItem* changed = nullptr;
1948     TreeMapItem* item = possibleSelection(over);
1949 
1950     switch(_selectionMode) {
1951     case Single:
1952         changed = setTmpSelected(item, true);
1953         break;
1954     case Multi:
1955         changed = setTmpSelected(item, !isTmpSelected(item));
1956         break;
1957     case Extended:
1958         if (_inControlDrag)
1959             changed = setTmpSelected(item, !isTmpSelected(item));
1960         else {
1961             TreeMapItem* sLast = possibleSelection(_lastOver);
1962             changed = setTmpRangeSelection(sLast, item, true);
1963         }
1964         break;
1965 
1966     default:
1967         break;
1968     }
1969 
1970     _lastOver = over;
1971 
1972     if (changed)
1973         redraw(changed);
1974 }
1975 
1976 void TreeMapWidget::mouseReleaseEvent( QMouseEvent* )
1977 {
1978     //qDebug() << "TreeMapWidget::mouseReleaseEvent";
1979 
1980     if (!_pressed) return;
1981 
1982     if (!_lastOver) {
1983         // take back
1984         setCurrent(_oldCurrent);
1985         TreeMapItem* changed = diff(_tmpSelection, _selection).commonParent();
1986         _tmpSelection = _selection;
1987         if (changed)
1988             redraw(changed);
1989     }
1990     else {
1991         if (! (_tmpSelection == _selection)) {
1992             _selection = _tmpSelection;
1993             if (_selectionMode == Single)
1994                 emit selectionChanged(_lastOver);
1995             emit selectionChanged();
1996         }
1997         if (!_inControlDrag && !_inShiftDrag && (_pressed == _lastOver))
1998             emit clicked(_lastOver);
1999     }
2000 
2001     _pressed = nullptr;
2002     _lastOver = nullptr;
2003 }
2004 
2005 
2006 void TreeMapWidget::mouseDoubleClickEvent( QMouseEvent* e )
2007 {
2008     TreeMapItem* over = item(e->x(), e->y());
2009 
2010     emit doubleClicked(over);
2011 }
2012 
2013 
2014 /* returns -1 if nothing visible found */
2015 int nextVisible(TreeMapItem* i)
2016 {
2017     TreeMapItem* p = i->parent();
2018     if (!p || p->itemRect().isEmpty()) return -1;
2019 
2020     int idx = p->children()->indexOf(i);
2021     if (idx<0) return -1;
2022 
2023     while (idx < (int)p->children()->count()-1) {
2024         idx++;
2025         QRect r = p->children()->at(idx)->itemRect();
2026         if (r.width()>1 && r.height()>1)
2027             return idx;
2028     }
2029     return -1;
2030 }
2031 
2032 /* returns -1 if nothing visible found */
2033 int prevVisible(TreeMapItem* i)
2034 {
2035     TreeMapItem* p = i->parent();
2036     if (!p || p->itemRect().isEmpty()) return -1;
2037 
2038     int idx = p->children()->indexOf(i);
2039     if (idx<0) return -1;
2040 
2041     while (idx > 0) {
2042         idx--;
2043         QRect r = p->children()->at(idx)->itemRect();
2044         if (r.width()>1 && r.height()>1)
2045             return idx;
2046     }
2047     return -1;
2048 }
2049 
2050 
2051 
2052 
2053 void TreeMapWidget::keyPressEvent( QKeyEvent* e )
2054 {
2055     if (e->key() == Qt::Key_Escape && _pressed) {
2056 
2057         // take back
2058         if (_oldCurrent != _lastOver)
2059             setCurrent(_oldCurrent);
2060         if (! (_tmpSelection == _selection)) {
2061             TreeMapItem* changed = diff(_tmpSelection, _selection).commonParent();
2062             _tmpSelection = _selection;
2063             if (changed)
2064                 redraw(changed);
2065         }
2066         _pressed = nullptr;
2067         _lastOver = nullptr;
2068     }
2069 
2070     if ((e->key() == Qt::Key_Space) ||
2071         (e->key() == Qt::Key_Return)) {
2072 
2073         switch(_selectionMode) {
2074         case NoSelection:
2075             break;
2076         case Single:
2077             setSelected(_current, true);
2078             break;
2079         case Multi:
2080             setSelected(_current, !isSelected(_current));
2081             break;
2082         case Extended:
2083             if ((e->modifiers() & Qt::ControlModifier) ||
2084                 (e->modifiers() & Qt::ShiftModifier))
2085                 setSelected(_current, !isSelected(_current));
2086             else {
2087                 _selectionMode = Single;
2088                 setSelected(_current, true);
2089                 _selectionMode = Extended;
2090             }
2091         }
2092 
2093         if (_current && (e->key() == Qt::Key_Return))
2094             emit returnPressed(_current);
2095 
2096         return;
2097     }
2098 
2099     if (!_current) {
2100         if (e->key() == Qt::Key_Down) {
2101             setCurrent(_base, true);
2102         }
2103         return;
2104     }
2105 
2106     TreeMapItem* old = _current,  *newItem;
2107     TreeMapItem* p = _current->parent();
2108 
2109     bool goBack;
2110     if (_current->sorting(&goBack) == -1) {
2111         // noSorting
2112         goBack = false;
2113     }
2114 
2115 
2116     if ((e->key() == Qt::Key_Backspace) ||
2117         (e->key() == Qt::Key_Up)) {
2118         newItem = visibleItem(p);
2119         setCurrent(newItem, true);
2120     }
2121     else if (e->key() == Qt::Key_Left) {
2122         int newIdx = goBack ? nextVisible(_current) : prevVisible(_current);
2123         if (p && newIdx>=0) {
2124             p->setIndex(newIdx);
2125             setCurrent(p->children()->at(newIdx), true);
2126         }
2127     }
2128     else if (e->key() == Qt::Key_Right) {
2129         int newIdx = goBack ? prevVisible(_current) : nextVisible(_current);
2130         if (p && newIdx>=0) {
2131             p->setIndex(newIdx);
2132             setCurrent(p->children()->at(newIdx), true);
2133         }
2134     }
2135     else if (e->key() == Qt::Key_Down) {
2136         if (_current->children() && _current->children()->count()>0) {
2137             int newIdx = _current->index();
2138             if (newIdx<0)
2139                 newIdx = goBack ? (_current->children()->count()-1) : 0;
2140             if (newIdx>=(int)_current->children()->count())
2141                 newIdx = _current->children()->count()-1;
2142             newItem = visibleItem(_current->children()->at(newIdx));
2143             setCurrent(newItem, true);
2144         }
2145     }
2146 
2147     if (old == _current) return;
2148     if (! (e->modifiers() & Qt::ControlModifier)) return;
2149     if (! (e->modifiers() & Qt::ShiftModifier)) return;
2150 
2151     switch(_selectionMode) {
2152     case NoSelection:
2153         break;
2154     case Single:
2155         setSelected(_current, true);
2156         break;
2157     case Multi:
2158         setSelected(_current, !isSelected(_current));
2159         break;
2160     case Extended:
2161         if (e->modifiers() & Qt::ControlModifier)
2162             setSelected(_current, !isSelected(_current));
2163         else
2164             setSelected(_current, isSelected(old));
2165     }
2166 }
2167 
2168 void TreeMapWidget::fontChange( const QFont& )
2169 {
2170     redraw();
2171 }
2172 
2173 // react on tooltip events
2174 bool TreeMapWidget::event(QEvent *event)
2175 {
2176     if (event->type() == QEvent::ToolTip) {
2177         QHelpEvent *helpEvent = static_cast<QHelpEvent *>(event);
2178         TreeMapItem* i = item(helpEvent->pos().x(), helpEvent->pos().y());
2179         bool hasTip = false;
2180         if (i) {
2181             const QList<QRect>& rList = i->freeRects();
2182             foreach(const QRect& r, rList) {
2183                 if (r.contains(helpEvent->pos())) {
2184                     hasTip = true;
2185                     break;
2186                 }
2187             }
2188         }
2189         if (hasTip)
2190             QToolTip::showText(helpEvent->globalPos(), tipString(i));
2191         else
2192             QToolTip::hideText();
2193     }
2194     return QWidget::event(event);
2195 }
2196 
2197 void TreeMapWidget::paintEvent( QPaintEvent * )
2198 {
2199     drawTreeMap();
2200 }
2201 
2202 // Updates screen from shadow buffer,
2203 // but redraws before if needed
2204 void TreeMapWidget::drawTreeMap()
2205 {
2206     // no need to draw if hidden
2207     if (!isVisible()) return;
2208 
2209     if (_pixmap.size() != size())
2210         _needsRefresh = _base;
2211 
2212     if (_needsRefresh) {
2213 
2214         if (DEBUG_DRAWING)
2215             qDebug() << "Redrawing " << _needsRefresh->path(0).join(QLatin1Char('/'));
2216 
2217         if (_needsRefresh == _base) {
2218             // redraw whole widget
2219             _pixmap = QPixmap(size());
2220             _pixmap.fill(palette().color(backgroundRole()));
2221         }
2222         QPainter p(&_pixmap);
2223         if (_needsRefresh == _base) {
2224             p.setPen(Qt::black);
2225             p.drawRect(QRect(2, 2, QWidget::width()-5, QWidget::height()-5));
2226             _base->setItemRect(QRect(3, 3, QWidget::width()-6, QWidget::height()-6));
2227         }
2228         else {
2229             // only subitem
2230             if (!_needsRefresh->itemRect().isValid()) return;
2231         }
2232 
2233         // reset cached font object; it could have been changed
2234         _font = font();
2235         _fontHeight = fontMetrics().height();
2236 
2237         drawItems(&p, _needsRefresh);
2238         _needsRefresh = nullptr;
2239     }
2240 
2241     QPainter p(this);
2242     p.drawPixmap(0, 0, _pixmap, 0, 0,
2243                  QWidget::width(), QWidget::height());
2244 
2245     if (hasFocus()) {
2246         QStylePainter p(this);
2247         QStyleOptionFocusRect opt;
2248         opt.rect = rect();
2249         opt.palette = palette();
2250         opt.state = QStyle::State_None;
2251         p.drawPrimitive( QStyle::PE_FrameFocusRect, opt );
2252     }
2253 }
2254 
2255 
2256 
2257 void TreeMapWidget::redraw(TreeMapItem* i)
2258 {
2259     if (!i) return;
2260 
2261     if (!_needsRefresh)
2262         _needsRefresh = i;
2263     else {
2264         if (!i->isChildOf(_needsRefresh))
2265             _needsRefresh = _needsRefresh->commonParent(i);
2266     }
2267 
2268     if (isVisible()) {
2269         // delayed drawing if we have multiple redraw requests
2270         update();
2271     }
2272 }
2273 
2274 void TreeMapWidget::drawItem(QPainter* p,
2275                              TreeMapItem* item)
2276 {
2277     bool isSelected = false;
2278 
2279     if (_markNo>0) {
2280         for(TreeMapItem* i = item; i; i=i->parent()) {
2281             if (i->isMarked(_markNo)) {
2282                 isSelected = true;
2283                 break;
2284             }
2285         }
2286     }
2287     else {
2288         foreach(TreeMapItem* i, _tmpSelection) {
2289             if (item->isChildOf(i)) {
2290                 isSelected = true;
2291                 break;
2292             }
2293         }
2294     }
2295 
2296     bool isCurrent = _current && item->isChildOf(_current);
2297     int dd = item->depth();
2298     if (isTransparent(dd)) return;
2299 
2300     RectDrawing d(item->itemRect());
2301     item->setSelected(isSelected);
2302     item->setCurrent(isCurrent);
2303     item->setShaded(_shading);
2304     item->drawFrame(drawFrame(dd));
2305     d.drawBack(p, item);
2306 }
2307 
2308 
2309 bool TreeMapWidget::horizontal(TreeMapItem* i, const QRect& r)
2310 {
2311     switch(i->splitMode()) {
2312     case TreeMapItem::HAlternate:
2313         return (i->depth()%2)==1;
2314     case TreeMapItem::VAlternate:
2315         return (i->depth()%2)==0;
2316     case TreeMapItem::Horizontal:
2317         return true;
2318     case TreeMapItem::Vertical:
2319         return false;
2320     default:
2321         return r.width() > r.height();
2322     }
2323     return false;
2324 }
2325 
2326 
2327 /**
2328  * Draw TreeMapItems recursive, starting from item
2329  */
2330 void TreeMapWidget::drawItems(QPainter* p,
2331                               TreeMapItem* item)
2332 {
2333     if (DEBUG_DRAWING)
2334         qDebug() << "+drawItems(" << item->path(0).join(QLatin1Char('/')) << ", "
2335                  << item->itemRect().x() << "/" << item->itemRect().y()
2336                  << "-" << item->itemRect().width() << "x"
2337                  << item->itemRect().height() << "), Val " << item->value()
2338                  << ", Sum " << item->sum();
2339 
2340     drawItem(p, item);
2341     item->clearFreeRects();
2342 
2343     QRect origRect = item->itemRect();
2344     int bw = item->borderWidth();
2345     QRect r = QRect(origRect.x()+bw, origRect.y()+bw,
2346                     origRect.width()-2*bw, origRect.height()-2*bw);
2347 
2348     TreeMapItemList* list = item->children();
2349 
2350     bool stopDrawing = false;
2351 
2352     // only subdivide if there are children
2353     if (!list || list->count()==0)
2354         stopDrawing = true;
2355 
2356     // only subdivide if there is enough space
2357     if (!stopDrawing && (r.width()<=0 || r.height()<=0))
2358         stopDrawing = true;
2359 
2360     // stop drawing if maximum depth is reached
2361     if (!stopDrawing &&
2362         (_maxDrawingDepth>=0 && item->depth()>=_maxDrawingDepth))
2363         stopDrawing = true;
2364 
2365     // stop drawing if stopAtText is reached
2366     if (!stopDrawing)
2367         for (int no=0;no<(int)_attr.size();no++) {
2368             QString stopAt = fieldStop(no);
2369             if (!stopAt.isEmpty() && (item->text(no) == stopAt)) {
2370                 stopDrawing = true;
2371                 break;
2372             }
2373         }
2374 
2375     // area size is checked later...
2376 #if 0
2377     // stop drawing if minimal area size is reached
2378     if (!stopDrawing &&
2379         (_minimalArea > 0) &&
2380         (r.width() * r.height() < _minimalArea)) stopDrawing = true;
2381 #endif
2382 
2383     if (stopDrawing) {
2384         if (list) {
2385             // invalidate rects
2386             foreach(TreeMapItem* i, *list)
2387                 i->clearItemRect();
2388         }
2389         // tooltip appears on whole item rect
2390         item->addFreeRect(item->itemRect());
2391 
2392         // if we have space for text...
2393         if ((r.height() < _fontHeight) || (r.width() < _fontHeight)) return;
2394 
2395         RectDrawing d(r);
2396         // draw text fields rotated to split direction
2397         bool rotate = !horizontal(item, r);
2398         if (_allowRotation) rotate = (r.height() > r.width());
2399         item->setRotated(rotate);
2400         for (int no=0;no<(int)_attr.size();no++) {
2401             if (!fieldVisible(no)) continue;
2402             d.drawField(p, no, item);
2403         }
2404         r = d.remainingRect(item);
2405 
2406         if (DEBUG_DRAWING)
2407             qDebug() << "-drawItems(" << item->path(0).join(QLatin1Char('/')) << ")";
2408         return;
2409     }
2410 
2411     double user_sum, child_sum, self;
2412 
2413     // user supplied sum
2414     user_sum = item->sum();
2415 
2416     // own sum
2417     child_sum = 0;
2418     foreach(TreeMapItem* i, *list) {
2419         child_sum += i->value();
2420         if (DEBUG_DRAWING)
2421             qDebug() << "  child: " << i->text(0) << ", value "
2422                      << i->value();
2423     }
2424 
2425     QRect orig = r;
2426 
2427     // if we have space for text...
2428     if ((r.height() >= _fontHeight) && (r.width() >= _fontHeight)) {
2429 
2430         RectDrawing d(r);
2431         // draw text fields rotated to split direction
2432         bool rotate = !horizontal(item, r);
2433         if (_allowRotation) rotate = (r.height() > r.width());
2434         item->setRotated(rotate);
2435         for (int no=0;no<(int)_attr.size();no++) {
2436             if (!fieldVisible(no)) continue;
2437             if (!fieldForced(no)) continue;
2438             d.drawField(p, no, item);
2439         }
2440         r = d.remainingRect(item);
2441     }
2442 
2443     if (orig.x() == r.x()) {
2444         // Strings on top
2445         item->addFreeRect(QRect(orig.x(), orig.y(),
2446                                 orig.width(), orig.height()-r.height()));
2447     }
2448     else {
2449         // Strings on the left
2450         item->addFreeRect(QRect(orig.x(), orig.y(),
2451                                 orig.width()-r.width(), orig.height()));
2452     }
2453 
2454     if (user_sum == 0) {
2455         // user did not supply any sum
2456         user_sum = child_sum;
2457         self = 0;
2458     }
2459     else {
2460         if (user_sum < child_sum) {
2461             //qDebug() << "TreeMWidget " <<
2462             //       item->path() << ": User sum " << user_sum << " < Child Items sum " << child_sum;
2463 
2464             // invalid user supplied sum: ignore and use calculate sum
2465             user_sum = child_sum;
2466             self = 0.0;
2467         }
2468         else {
2469             self = user_sum - child_sum;
2470         }
2471     }
2472 
2473     // use requested splitting algorithm: we rotate for horizontal splits
2474     bool rotate = horizontal(item, r);
2475     int self_length = (int)( ((rotate) ? r.width() : r.height()) *
2476                              self / user_sum + .5);
2477     // drawn border belongs to self (TODO: option _skipIncorrectBorder)
2478     self_length -= 2 * item->borderWidth();
2479 
2480     if (self_length > 0) {
2481         // take space for self cost
2482         QRect sr = r;
2483         if (rotate) {
2484             sr.setWidth( self_length );
2485             r.setRect(r.x()+sr.width(), r.y(), r.width()-sr.width(), r.height());
2486         }
2487         else {
2488             sr.setHeight( self_length );
2489             r.setRect(r.x(), r.y()+sr.height(), r.width(), r.height()-sr.height());
2490         }
2491 
2492         // set selfRect (not occupied by children) for tooltip
2493         item->addFreeRect(sr);
2494 
2495         if (0) qDebug() << "Item " << item->path(0).join(QLatin1Char('/')) << ": SelfR "
2496                         << sr.x() << "/" << sr.y() << "-" << sr.width()
2497                         << "/" << sr.height() << ", self " << self << "/"
2498                         << user_sum;
2499 
2500         if ((sr.height() >= _fontHeight) && (sr.width() >= _fontHeight)) {
2501 
2502             RectDrawing d(sr);
2503             item->setRotated(_allowRotation && (r.height() > r.width()));
2504             for (int no=0;no<(int)_attr.size();no++) {
2505                 if (!fieldVisible(no)) continue;
2506                 if (fieldForced(no)) continue;
2507                 d.drawField(p, no, item);
2508             }
2509         }
2510 
2511         user_sum -= self;
2512     }
2513 
2514     bool goBack;
2515     if (item->sorting(&goBack) == -1) {
2516         // noSorting
2517         goBack = false;
2518     }
2519 
2520     int idx = goBack ? (list->size()-1) : 0;
2521 
2522     if (item->splitMode() == TreeMapItem::Columns) {
2523         int len = list->count();
2524         bool drawDetails = true;
2525 
2526         while (len>0 && user_sum>0) {
2527             int firstIdx = idx;
2528             double valSum = 0;
2529             int lenLeft = len;
2530             int columns = (int)(sqrt((double)len * r.width()/r.height())+.5);
2531             if (columns==0) columns = 1; //should never be needed
2532 
2533             while (lenLeft>0 && ((double)valSum*(len-lenLeft) <
2534                                  (double)len*user_sum/columns/columns)) {
2535                 valSum += list->at(idx)->value();
2536                 if (goBack) --idx; else ++idx;
2537                 lenLeft--;
2538             }
2539 
2540             // we always split horizontally
2541             int nextPos = (int)((double)r.width() * valSum / user_sum);
2542             QRect firstRect = QRect(r.x(), r.y(), nextPos, r.height());
2543 
2544             if (nextPos < _visibleWidth) {
2545                 if (item->sorting(nullptr) == -1) {
2546                     // fill current rect with hash pattern
2547                     drawFill(item, p, firstRect);
2548                 }
2549                 else {
2550                     // fill rest with hash pattern
2551                     drawFill(item, p, r, list, firstIdx, len, goBack);
2552                     break;
2553                 }
2554             }
2555             else {
2556                 drawDetails = drawItemArray(p, item, firstRect,
2557                                             valSum, list, firstIdx, len-lenLeft, goBack);
2558             }
2559             r.setRect(r.x()+nextPos, r.y(), r.width()-nextPos, r.height());
2560             user_sum -= valSum;
2561             len = lenLeft;
2562 
2563             if (!drawDetails) {
2564                 if (item->sorting(nullptr) == -1)
2565                     drawDetails = true;
2566                 else {
2567                     drawFill(item, p, r, list, idx, len, goBack);
2568                     break;
2569                 }
2570             }
2571         }
2572     }
2573     else if (item->splitMode() == TreeMapItem::Rows) {
2574         int len = list->count();
2575         bool drawDetails = true;
2576 
2577         while (len>0 && user_sum>0) {
2578             int firstIdx = idx;
2579             double valSum = 0;
2580             int lenLeft = len;
2581             int rows = (int)(sqrt((double)len * r.height()/r.width())+.5);
2582             if (rows==0) rows = 1; //should never be needed
2583 
2584             while (lenLeft>0 && ((double)valSum*(len-lenLeft) <
2585                                  (double)len*user_sum/rows/rows)) {
2586                 valSum += list->at(idx)->value();
2587                 if (goBack) --idx; else ++idx;
2588                 lenLeft--;
2589             }
2590 
2591             // we always split horizontally
2592             int nextPos = (int)((double)r.height() * valSum / user_sum);
2593             QRect firstRect = QRect(r.x(), r.y(), r.width(), nextPos);
2594 
2595             if (nextPos < _visibleWidth) {
2596                 if (item->sorting(nullptr) == -1) {
2597                     drawFill(item, p, firstRect);
2598                 }
2599                 else {
2600                     drawFill(item, p, r, list, firstIdx, len, goBack);
2601                     break;
2602                 }
2603             }
2604             else {
2605                 drawDetails = drawItemArray(p, item, firstRect,
2606                                             valSum, list, firstIdx, len-lenLeft, goBack);
2607             }
2608             r.setRect(r.x(), r.y()+nextPos, r.width(), r.height()-nextPos);
2609             user_sum -= valSum;
2610             len = lenLeft;
2611 
2612             if (!drawDetails) {
2613                 if (item->sorting(nullptr) == -1)
2614                     drawDetails = true;
2615                 else {
2616                     drawFill(item, p, r, list, idx, len, goBack);
2617                     break;
2618                 }
2619             }
2620         }
2621     }
2622     else
2623         drawItemArray(p, item, r, user_sum, list, idx, list->count(), goBack);
2624 
2625     if (DEBUG_DRAWING)
2626         qDebug() << "-drawItems(" << item->path(0).join(QLatin1Char('/')) << ")";
2627 }
2628 
2629 // fills area with a pattern if to small to draw children
2630 void TreeMapWidget::drawFill(TreeMapItem* i, QPainter* p, const QRect& r)
2631 {
2632     p->setBrush(Qt::Dense4Pattern);
2633     p->setPen(Qt::NoPen);
2634     p->drawRect(QRect(r.x(), r.y(), r.width()-1, r.height()-1));
2635     i->addFreeRect(r);
2636 }
2637 
2638 // fills area with a pattern if to small to draw children
2639 void TreeMapWidget::drawFill(TreeMapItem* i, QPainter* p, const QRect& r,
2640                              TreeMapItemList* list, int idx, int len, bool goBack)
2641 {
2642     if (DEBUG_DRAWING)
2643         qDebug() << "  +drawFill(" << r.x() << "/" << r.y()
2644                  << "-" << r.width() << "x" << r.height()
2645                  << ", len " << len << ")";
2646 
2647     p->setBrush(Qt::Dense4Pattern);
2648     p->setPen(Qt::NoPen);
2649     p->drawRect(QRect(r.x(), r.y(), r.width()-1, r.height()-1));
2650     i->addFreeRect(r);
2651 
2652     // reset rects
2653     while (len>0 && (i=list->value(idx))) {
2654 
2655         if (DEBUG_DRAWING)
2656             qDebug() << "   Reset Rect " << i->path(0).join(QLatin1Char('/'));
2657 
2658         i->clearItemRect();
2659         if (goBack) --idx; else ++idx;
2660         len--;
2661     }
2662     if (DEBUG_DRAWING)
2663         qDebug() << "  -drawFill(" << r.x() << "/" << r.y()
2664                  << "-" << r.width() << "x" << r.height()
2665                  << ", len " << len << ")";
2666 }
2667 
2668 // returns false if rect gets to small
2669 bool TreeMapWidget::drawItemArray(QPainter* p, TreeMapItem* item,
2670                                   const QRect& r, double user_sum,
2671                                   TreeMapItemList* list, int idx, int len,
2672                                   bool goBack)
2673 {
2674     if (user_sum == 0) return false;
2675 
2676     static bool b2t = true;
2677 
2678     // stop recursive bisection for small rectangles
2679     if (((r.height() < _visibleWidth) &&
2680          (r.width() < _visibleWidth)) ||
2681         ((_minimalArea > 0) &&
2682          (r.width() * r.height() < _minimalArea))) {
2683 
2684         drawFill(item, p, r, list, idx, len, goBack);
2685         return false;
2686     }
2687 
2688     if (DEBUG_DRAWING)
2689         qDebug() << " +drawItemArray(" << item->path(0).join(QLatin1Char('/'))
2690                  << ", " << r.x() << "/" << r.y() << "-" << r.width()
2691                  << "x" << r.height() << ")";
2692 
2693     if (len>2 && (item->splitMode() == TreeMapItem::Bisection)) {
2694 
2695         int firstIdx = idx;
2696         double valSum = 0;
2697         int lenLeft = len;
2698         //while (lenLeft>0 && valSum<user_sum/2) {
2699         while (lenLeft>len/2) {
2700             valSum += list->at(idx)->value();
2701             if (goBack) --idx; else ++idx;
2702             lenLeft--;
2703         }
2704 
2705         // draw first half...
2706         bool drawOn;
2707         QRect secondRect;
2708 
2709         if (r.width() > r.height()) {
2710             int halfPos = (int)((double)r.width() * valSum / user_sum);
2711             QRect firstRect = QRect(r.x(), r.y(), halfPos, r.height());
2712             drawOn = drawItemArray(p, item, firstRect,
2713                                    valSum, list, firstIdx, len-lenLeft, goBack);
2714             secondRect.setRect(r.x()+halfPos, r.y(), r.width()-halfPos, r.height());
2715         }
2716         else {
2717             int halfPos = (int)((double)r.height() * valSum / user_sum);
2718             QRect firstRect = QRect(r.x(), r.y(), r.width(), halfPos);
2719             drawOn = drawItemArray(p, item, firstRect,
2720                                    valSum, list, firstIdx, len-lenLeft, goBack);
2721             secondRect.setRect(r.x(), r.y()+halfPos, r.width(), r.height()-halfPos);
2722         }
2723 
2724         // if no sorting, do not stop drawing
2725         if (item->sorting(nullptr) == -1) drawOn = true;
2726 
2727         // second half
2728         if (drawOn)
2729             drawOn = drawItemArray(p, item, secondRect, user_sum - valSum,
2730                                    list, idx, lenLeft, goBack);
2731         else {
2732             drawFill(item, p, secondRect, list, idx, len, goBack);
2733         }
2734 
2735         if (DEBUG_DRAWING)
2736             qDebug() << " -drawItemArray(" << item->path(0).join(QLatin1Char('/'))
2737                      << ")";
2738 
2739         return drawOn;
2740     }
2741 
2742     bool hor = horizontal(item,r);
2743 
2744     TreeMapItem* i;
2745     QRect fullRect = r;
2746     while (len>0) {
2747         i = list->at(idx);
2748         if (user_sum <= 0) {
2749 
2750             if (DEBUG_DRAWING)
2751                 qDebug() << "drawItemArray: Reset " << i->path(0).join(QLatin1Char('/'));
2752 
2753             i->clearItemRect();
2754             if (goBack) --idx; else ++idx;
2755             len--;
2756             continue;
2757         }
2758 
2759         // stop drawing for small rectangles
2760         if (((fullRect.height() < _visibleWidth) &&
2761              (fullRect.width() < _visibleWidth)) ||
2762             ((_minimalArea > 0) &&
2763              (fullRect.width() * fullRect.height() < _minimalArea))) {
2764 
2765             drawFill(item, p, fullRect, list, idx, len, goBack);
2766             if (DEBUG_DRAWING)
2767                 qDebug() << " -drawItemArray(" << item->path(0).join(QLatin1Char('/'))
2768                          << "): Stop";
2769             return false;
2770         }
2771 
2772         if (i->splitMode() == TreeMapItem::AlwaysBest)
2773             hor = fullRect.width() > fullRect.height();
2774 
2775         int lastPos = hor ? fullRect.width() : fullRect.height();
2776         double val = i->value();
2777         int nextPos = (user_sum <= 0.0) ? 0: (int)(lastPos * val / user_sum +.5);
2778         if (nextPos>lastPos) nextPos = lastPos;
2779 
2780         if ((item->sorting(nullptr) != -1) && (nextPos < _visibleWidth)) {
2781             drawFill(item, p, fullRect, list, idx, len, goBack);
2782             if (DEBUG_DRAWING)
2783                 qDebug() << " -drawItemArray(" << item->path(0).join(QLatin1Char('/'))
2784                          << "): Stop";
2785             return false;
2786         }
2787 
2788         QRect currRect = fullRect;
2789 
2790         if (hor)
2791             currRect.setWidth(nextPos);
2792         else {
2793             if (b2t)
2794                 currRect.setRect(fullRect.x(), fullRect.bottom()-nextPos+1, fullRect.width(), nextPos);
2795             else
2796                 currRect.setHeight(nextPos);
2797         }
2798 
2799         // do not draw very small rectangles:
2800         if (nextPos >= _visibleWidth) {
2801             i->setItemRect(currRect);
2802             drawItems(p, i);
2803         }
2804         else {
2805             i->clearItemRect();
2806             drawFill(item, p, currRect);
2807         }
2808 
2809         // draw Separator
2810         if (_drawSeparators && (nextPos<lastPos)) {
2811             p->setPen(Qt::black);
2812             if (hor) {
2813                 if (fullRect.top() <= fullRect.bottom())
2814                     p->drawLine(fullRect.x() + nextPos, fullRect.top(), fullRect.x() + nextPos, fullRect.bottom());
2815             }
2816             else {
2817                 if (fullRect.left() <= fullRect.right())
2818                     p->drawLine(fullRect.left(), fullRect.y() + nextPos, fullRect.right(), fullRect.y() + nextPos);
2819             }
2820             nextPos++;
2821         }
2822 
2823         if (hor)
2824             fullRect.setRect(fullRect.x() + nextPos, fullRect.y(),
2825                              lastPos - nextPos, fullRect.height());
2826         else {
2827             if (b2t)
2828                 fullRect.setRect(fullRect.x(), fullRect.y(),
2829                                  fullRect.width(), lastPos-nextPos);
2830             else
2831                 fullRect.setRect(fullRect.x(), fullRect.y() + nextPos,
2832                                  fullRect.width(), lastPos-nextPos);
2833         }
2834 
2835         user_sum -= val;
2836         if (goBack) --idx; else ++idx;
2837         len--;
2838     }
2839 
2840     if (DEBUG_DRAWING)
2841         qDebug() << " -drawItemArray(" << item->path(0).join(QLatin1Char('/'))
2842                  << "): Continue";
2843 
2844     return true;
2845 }
2846 
2847 
2848 /*----------------------------------------------------------------
2849  * Popup menus for option setting
2850  */
2851 
2852 void TreeMapWidget::splitActivated(QAction* a)
2853 {
2854     setSplitMode( (TreeMapItem::SplitMode) a->data().toInt());
2855 }
2856 
2857 void TreeMapWidget::addSplitAction(QMenu* m, const QString& s, int v)
2858 {
2859     QAction* a = m->addAction(s);
2860     a->setData(v);
2861     a->setCheckable(true);
2862     a->setChecked(splitMode() == v);
2863 }
2864 
2865 void TreeMapWidget::addSplitDirectionItems(QMenu* m)
2866 {
2867     connect(m, &QMenu::triggered,
2868             this, &TreeMapWidget::splitActivated );
2869 
2870     addSplitAction(m, tr("Recursive Bisection"), TreeMapItem::Bisection);
2871     addSplitAction(m, tr("Columns"), TreeMapItem::Columns);
2872     addSplitAction(m, tr("Rows"), TreeMapItem::Rows);
2873     addSplitAction(m, tr("Always Best"), TreeMapItem::AlwaysBest);
2874     addSplitAction(m, tr("Best"), TreeMapItem::Best);
2875     addSplitAction(m, tr("Alternate (V)"), TreeMapItem::VAlternate);
2876     addSplitAction(m, tr("Alternate (H)"), TreeMapItem::HAlternate);
2877     addSplitAction(m, tr("Horizontal"), TreeMapItem::Horizontal);
2878     addSplitAction(m, tr("Vertical"), TreeMapItem::Vertical);
2879 }
2880 
2881