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