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