File indexing completed on 2024-04-28 08:50:23

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