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

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