You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tdesvn/src/svnfrontend/graphtree/drawparams.cpp

709 lines
17 KiB

/* This file is part of KCachegrind.
Copyright (C) 2002, 2003 Josef Weidendorfer <Josef.Weidendorfer@gmx.de>
Adapted for the needs of tdesvn by Rajko Albrecht <ral@alwins-world.de>
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 <math.h>
#include <tqpainter.h>
#include <tqtooltip.h>
#include <tqregexp.h>
#include <tqstyle.h>
#include <tqpopupmenu.h>
#include <klocale.h>
#include <kconfig.h>
#include <kdebug.h>
#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 (<unused>)
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 && (pixY<y)) y = pixY;
if (!isBottom && (pixY>y)) 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;
}