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