/* This file is part of KCachegrind. Copyright (C) 2002, 2003 Josef Weidendorfer Adapted for the needs of tdesvn by Rajko Albrecht KCachegrind is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; see the file COPYING. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /* * A Widget for visualizing hierarchical metrics as areas. * The API is similar to TQListView. */ #include #include #include #include #include #include #include #include #include #include "drawparams.h" // set this to 1 to enable debug output #define DEBUG_DRAWING 0 #define MAX_FIELD 12 // // StoredDrawParams // StoredDrawParams::StoredDrawParams() { _selected = false; _current = false; _shaded = true; _rotated = false; _backColor = TQt::white; // field array has size 0 } StoredDrawParams::StoredDrawParams(TQColor c, bool selected, bool current) { _backColor = c; _selected = selected; _current = current; _shaded = true; _rotated = false; _drawFrame = true; // field array has size 0 } TQString StoredDrawParams::text(int f) const { if ((f<0) || (f >= (int)_field.size())) return TQString(); return _field[f].text; } TQPixmap StoredDrawParams::pixmap(int f) const { if ((f<0) || (f >= (int)_field.size())) return TQPixmap(); return _field[f].pix; } DrawParams::Position StoredDrawParams::position(int f) const { if ((f<0) || (f >= (int)_field.size())) return Default; return _field[f].pos; } int StoredDrawParams::maxLines(int f) const { if ((f<0) || (f >= (int)_field.size())) return 0; return _field[f].maxLines; } const TQFont& StoredDrawParams::font() const { static TQFont* f = 0; if (!f) f = new TQFont(TQApplication::font()); return *f; } void StoredDrawParams::ensureField(int f) { static Field* def = 0; if (!def) { def = new Field(); def->pos = Default; def->maxLines = 0; } if (f<0 || f>=MAX_FIELD) return; if ((int)_field.size() < f+1) _field.resize(f+1, *def); } void StoredDrawParams::setField(int f, TQString t, TQPixmap pm, Position p, int maxLines) { if (f<0 || f>=MAX_FIELD) return; ensureField(f); _field[f].text = t; _field[f].pix = pm; _field[f].pos = p; _field[f].maxLines = maxLines; } void StoredDrawParams::setText(int f, TQString t) { if (f<0 || f>=MAX_FIELD) return; ensureField(f); _field[f].text = t; } void StoredDrawParams::setPixmap(int f, TQPixmap pm) { if (f<0 || f>=MAX_FIELD) return; ensureField(f); _field[f].pix = pm; } void StoredDrawParams::setPosition(int f, Position p) { if (f<0 || f>=MAX_FIELD) return; ensureField(f); _field[f].pos = p; } void StoredDrawParams::setMaxLines(int f, int m) { if (f<0 || f>=MAX_FIELD) return; ensureField(f); _field[f].maxLines = m; } // // RectDrawing // RectDrawing::RectDrawing(TQRect r) { _fm = 0; _dp = 0; setRect(r); } RectDrawing::~RectDrawing() { delete _fm; delete _dp; } DrawParams* RectDrawing::drawParams() { if (!_dp) _dp = new StoredDrawParams(); return _dp; } void RectDrawing::setDrawParams(DrawParams* dp) { if (_dp) delete _dp; _dp = dp; } void RectDrawing::setRect(TQRect r) { _rect = r; _usedTopLeft = 0; _usedTopCenter = 0; _usedTopRight = 0; _usedBottomLeft = 0; _usedBottomCenter = 0; _usedBottomRight = 0; _fontHeight = 0; } TQRect RectDrawing::remainingRect(DrawParams* dp) { if (!dp) dp = drawParams(); if ((_usedTopLeft >0) || (_usedTopCenter >0) || (_usedTopRight >0)) { if (dp->rotated()) _rect.setLeft(_rect.left() + _fontHeight); else _rect.setTop(_rect.top() + _fontHeight); } if ((_usedBottomLeft >0) || (_usedBottomCenter >0) || (_usedBottomRight >0)) { if (dp->rotated()) _rect.setRight(_rect.right() - _fontHeight); else _rect.setBottom(_rect.bottom() - _fontHeight); } return _rect; } void RectDrawing::drawBack(TQPainter* p, DrawParams* dp) { if (!dp) dp = drawParams(); if (_rect.width()<=0 || _rect.height()<=0) return; TQRect r = _rect; TQColor normal = dp->backColor(); if (dp->selected()) normal = normal.light(); bool isCurrent = dp->current(); if (dp->drawFrame() || isCurrent) { // 3D raised/sunken frame effect... TQColor high = normal.light(); TQColor low = normal.dark(); p->setPen( isCurrent ? low:high); p->drawLine(r.left(), r.top(), r.right(), r.top()); p->drawLine(r.left(), r.top(), r.left(), r.bottom()); p->setPen( isCurrent ? high:low); p->drawLine(r.right(), r.top(), r.right(), r.bottom()); p->drawLine(r.left(), r.bottom(), r.right(), r.bottom()); r.setRect(r.x()+1, r.y()+1, r.width()-2, r.height()-2); } if (r.width()<=0 || r.height()<=0) return; if (dp->shaded()) { // some shading bool goDark = tqGray(normal.rgb())>128; int rBase, gBase, bBase; normal.rgb(&rBase, &gBase, &bBase); p->setBrush(TQBrush::NoBrush); // shade parameters: int d = 7; float factor = 0.1, forth=0.7, back1 =0.9, toBack2 = .7, back2 = 0.97; // coefficient corrections because of rectangle size int s = r.width(); if (s > r.height()) s = r.height(); if (s<100) { forth -= .3 * (100-s)/100; back1 -= .2 * (100-s)/100; back2 -= .02 * (100-s)/100; } // maximal color difference int rDiff = goDark ? -rBase/d : (255-rBase)/d; int gDiff = goDark ? -gBase/d : (255-gBase)/d; int bDiff = goDark ? -bBase/d : (255-bBase)/d; TQColor shadeColor; while (factor<.95) { shadeColor.setRgb((int)(rBase+factor*rDiff+.5), (int)(gBase+factor*gDiff+.5), (int)(bBase+factor*bDiff+.5)); p->setPen(shadeColor); p->drawRect(r); r.setRect(r.x()+1, r.y()+1, r.width()-2, r.height()-2); if (r.width()<=0 || r.height()<=0) return; factor = 1.0 - ((1.0 - factor) * forth); } // and back (1st half) while (factor>toBack2) { shadeColor.setRgb((int)(rBase+factor*rDiff+.5), (int)(gBase+factor*gDiff+.5), (int)(bBase+factor*bDiff+.5)); p->setPen(shadeColor); p->drawRect(r); r.setRect(r.x()+1, r.y()+1, r.width()-2, r.height()-2); if (r.width()<=0 || r.height()<=0) return; factor = 1.0 - ((1.0 - factor) / back1); } // and back (2nd half) while ( factor>.01) { shadeColor.setRgb((int)(rBase+factor*rDiff+.5), (int)(gBase+factor*gDiff+.5), (int)(bBase+factor*bDiff+.5)); p->setPen(shadeColor); p->drawRect(r); r.setRect(r.x()+1, r.y()+1, r.width()-2, r.height()-2); if (r.width()<=0 || r.height()<=0) return; factor = factor * back2; } } // fill inside p->setPen(TQPen::NoPen); p->setBrush(normal); p->drawRect(r); } bool RectDrawing::drawField(TQPainter* p, int f, DrawParams* dp) { if (!dp) dp = drawParams(); if (!_fm) { _fm = new TQFontMetrics(dp->font()); _fontHeight = _fm->height(); } TQRect r = _rect; if (0) kdDebug(90100) << "DrawField: Rect " << r.x() << "/" << r.y() << " - " << r.width() << "x" << r.height() << endl; int h = _fontHeight; bool rotate = dp->rotated(); int width = (rotate ? r.height() : r.width()) -4; int height = (rotate ? r.width() : r.height()); int lines = height / h; // stop if we have no space available if (lines<1) return false; // calculate free space in first line () int pos = dp->position(f); if (pos == DrawParams::Default) { switch(f%4) { case 0: pos = DrawParams::TopLeft; break; case 1: pos = DrawParams::TopRight; break; case 2: pos = DrawParams::BottomRight; break; case 3: pos = DrawParams::BottomLeft; break; } } int unused = 0; bool isBottom = false; bool isCenter = false; bool isRight = false; int* used = 0; switch(pos) { case DrawParams::TopLeft: used = &_usedTopLeft; if (_usedTopLeft == 0) { if (_usedTopCenter) unused = (width - _usedTopCenter)/2; else unused = width - _usedTopRight; } break; case DrawParams::TopCenter: isCenter = true; used = &_usedTopCenter; if (_usedTopCenter == 0) { if (_usedTopLeft > _usedTopRight) unused = width - 2 * _usedTopLeft; else unused = width - 2 * _usedTopRight; } break; case DrawParams::TopRight: isRight = true; used = &_usedTopRight; if (_usedTopRight == 0) { if (_usedTopCenter) unused = (width - _usedTopCenter)/2; else unused = width - _usedTopLeft; } break; case DrawParams::BottomLeft: isBottom = true; used = &_usedBottomLeft; if (_usedBottomLeft == 0) { if (_usedBottomCenter) unused = (width - _usedBottomCenter)/2; else unused = width - _usedBottomRight; } break; case DrawParams::BottomCenter: isCenter = true; isBottom = true; used = &_usedBottomCenter; if (_usedBottomCenter == 0) { if (_usedBottomLeft > _usedBottomRight) unused = width - 2 * _usedBottomLeft; else unused = width - 2 * _usedBottomRight; } break; case DrawParams::BottomRight: isRight = true; isBottom = true; used = &_usedBottomRight; if (_usedBottomRight == 0) { if (_usedBottomCenter) unused = (width - _usedBottomCenter)/2; else unused = width - _usedBottomLeft; } break; } if (isBottom) { if ((_usedTopLeft >0) || (_usedTopCenter >0) || (_usedTopRight >0)) lines--; } else if (!isBottom) { if ((_usedBottomLeft >0) || (_usedBottomCenter >0) || (_usedBottomRight >0)) lines--; } if (lines<1) return false; int y = isBottom ? height - h : 0; if (unused < 0) unused = 0; if (unused == 0) { // no space available in last line at this position y = isBottom ? (y-h) : (y+h); lines--; if (lines<1) return false; // new line: reset used space if (isBottom) _usedBottomLeft = _usedBottomCenter = _usedBottomRight = 0; else _usedTopLeft = _usedTopCenter = _usedTopRight = 0; unused = width; } // stop as soon as possible when there's no space for "..." static int dotW = 0; if (!dotW) dotW = _fm->width("..."); if (width < dotW) return false; // get text and pixmap now, only if we need to, because it is possible // that they are calculated on demand (and this can take some time) TQString name = dp->text(f); if (name.isEmpty()) return 0; TQPixmap pix = dp->pixmap(f); // check if pixmap can be drawn int pixW = pix.width(); int pixH = pix.height(); int pixY = 0; bool pixDrawn = true; if (pixW>0) { pixW += 2; // X distance from pix if ((width < pixW + dotW) || (height < pixH)) { // don't draw pixW = 0; } else pixDrawn = false; } // width of text and pixmap to be drawn int w = pixW + _fm->width(name); if (0) kdDebug(90100) << " For '" << name << "': Unused " << unused << ", StrW " << w << ", Width " << width << endl; // if we have limited space at 1st line: // use it only if whole name does fit in last line... if ((unused < width) && (w > unused)) { y = isBottom ? (y-h) : (y+h); lines--; if (lines<1) return false; // new line: reset used space if (isBottom) _usedBottomLeft = _usedBottomCenter = _usedBottomRight = 0; else _usedTopLeft = _usedTopCenter = _usedTopRight = 0; } p->save(); p->setPen( (tqGray(dp->backColor().rgb())>100) ? TQt::black : TQt::white); p->setFont(dp->font()); if (rotate) { //p->translate(r.x()+2, r.y()+r.height()); p->translate(r.x(), r.y()+r.height()-2); p->rotate(270); } else p->translate(r.x()+2, r.y()); // adjust available lines according to maxLines int max = dp->maxLines(f); if ((max > 0) && (lines>max)) lines = max; /* loop over name parts to break up string depending on available width. * every char category change is supposed a possible break, * with the exception Uppercase=>Lowercase. * It's good enough for numbers, Symbols... * * If the text is to be written at the bottom, we start with the * end of the string (so everything is reverted) */ TQString remaining; int origLines = lines; while (lines>0) { if (w>width && lines>1) { int lastBreakPos = name.length(), lastWidth = w; int len = name.length(); TQChar::Category caOld, ca; if (!isBottom) { // start with comparing categories of last 2 chars caOld = name[len-1].category(); while (len>2) { len--; ca = name[len-1].category(); if (ca != caOld) { // "Aa" has no break between... if (ca == TQChar::Letter_Uppercase && caOld == TQChar::Letter_Lowercase) { caOld = ca; continue; } caOld = ca; lastBreakPos = len; w = pixW + _fm->width(name, len); lastWidth = w; if (w <= width) break; } } w = lastWidth; remaining = name.mid(lastBreakPos); // remove space on break point if (name[lastBreakPos-1].category() == TQChar::Separator_Space) name = name.left(lastBreakPos-1); else name = name.left(lastBreakPos); } else { // bottom int l = len; caOld = name[l-len].category(); while (len>2) { len--; ca = name[l-len].category(); if (ca != caOld) { // "Aa" has no break between... if (caOld == TQChar::Letter_Uppercase && ca == TQChar::Letter_Lowercase) { caOld = ca; continue; } caOld = ca; lastBreakPos = len; w = pixW + _fm->width(name.right(len)); lastWidth = w; if (w <= width) break; } } w = lastWidth; remaining = name.left(l-lastBreakPos); // remove space on break point if (name[l-lastBreakPos].category() == TQChar::Separator_Space) name = name.right(lastBreakPos-1); else name = name.right(lastBreakPos); } } else remaining = TQString(); /* truncate and add ... if needed */ if (w>width) { int len = name.length(); w += dotW; while (len>2 && (w > width)) { len--; w = pixW + _fm->width(name, len) + dotW; } // stop drawing: we cannot draw 2 chars + "..." if (w>width) break; name = name.left(len) + "..."; } int x = 0; if (isCenter) x = (width - w)/2; else if (isRight) x = width - w; if (!pixDrawn) { pixY = y+(h-pixH)/2; // default: center vertically if (pixH > h) pixY = isBottom ? y-(pixH-h) : y; p->drawPixmap( x, pixY, pix); // for distance to next text pixY = isBottom ? (pixY - h - 2) : (pixY + pixH + 2); pixDrawn = true; } if (0) kdDebug(90100) << " Drawing '" << name << "' at " << x+pixW << "/" << y << endl; p->drawText( x+pixW, y, width - pixW, h, TQt::AlignLeft, name); y = isBottom ? (y-h) : (y+h); lines--; if (remaining.isEmpty()) break; name = remaining; w = pixW + _fm->width(name); } // make sure the pix stays visible if (pixDrawn && (pixY>0)) { if (isBottom && (pixYy)) y = pixY; } if (origLines > lines) { // if only 1 line written, don't reset _used* vars if (lines - origLines >1) { if (isBottom) _usedBottomLeft = _usedBottomCenter = _usedBottomRight = 0; else _usedTopLeft = _usedTopCenter = _usedTopRight = 0; } // take back one line y = isBottom ? (y+h) : (y-h); if (used) *used = w; } // update free space if (!isBottom) { if (rotate) _rect.setRect(r.x()+y, r.y(), r.width()-y, r.height()); else _rect.setRect(r.x(), r.y()+y, r.width(), r.height()-y); } else { if (rotate) _rect.setRect(r.x(), r.y(), y+h, r.height()); else _rect.setRect(r.x(), r.y(), r.width(), y+h); } p->restore(); return true; }