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.
343 lines
9.5 KiB
343 lines
9.5 KiB
//Author: Max Howell <max.howell@methylblue.com>, (C) 2003-4
|
|
//Copyright: See COPYING file that comes with this distribution
|
|
|
|
#include <kstringhandler.h>
|
|
#include <tqfont.h>
|
|
#include <tqfontmetrics.h>
|
|
#include <tqpainter.h>
|
|
#include <tqptrlist.h>
|
|
|
|
#include "Config.h"
|
|
#include "fileTree.h"
|
|
#include "radialMap.h"
|
|
#include "sincos.h"
|
|
#include "widget.h"
|
|
|
|
|
|
|
|
namespace RadialMap
|
|
{
|
|
struct Label
|
|
{
|
|
Label( const RadialMap::Segment *s, int l ) : segment( s ), lvl( l ), a( segment->start() + (segment->length() / 2) ) { }
|
|
|
|
bool tooClose( const int &aa ) const { return ( a > aa - LABEL_ANGLE_MARGIN && a < aa + LABEL_ANGLE_MARGIN ); }
|
|
|
|
const RadialMap::Segment *segment;
|
|
const unsigned int lvl;
|
|
const int a;
|
|
|
|
int x1, y1, x2, y2, x3;
|
|
int tx, ty;
|
|
|
|
TQString qs;
|
|
};
|
|
|
|
class LabelList : public TQPtrList<Label>
|
|
{
|
|
protected:
|
|
int compareItems( TQPtrCollection::Item item1, TQPtrCollection::Item item2 )
|
|
{
|
|
//you add 1440 to work round the fact that later you want the circle split vertically
|
|
//and as it is you start at 3 o' clock. It's to do with rightPrevY, stops annoying bug
|
|
|
|
int a1 = ((Label*)item1)->a + 1440;
|
|
int a2 = ((Label*)item2)->a + 1440;
|
|
|
|
if( a1 == a2 )
|
|
return 0;
|
|
|
|
if( a1 > 5760 ) a1 -= 5760;
|
|
if( a2 > 5760 ) a2 -= 5760;
|
|
|
|
if( a1 > a2 )
|
|
return 1;
|
|
|
|
return -1;
|
|
}
|
|
};
|
|
}
|
|
|
|
|
|
void
|
|
RadialMap::Widget::paintExplodedLabels( TQPainter &paint ) const
|
|
{
|
|
//we are a friend of RadialMap::Map
|
|
|
|
LabelList list; list.setAutoDelete( true );
|
|
TQPtrListIterator<Label> it( list );
|
|
unsigned int startLevel = 0;
|
|
|
|
|
|
//1. Create list of labels sorted in the order they will be rendered
|
|
|
|
if( m_focus != NULL && m_focus->file() != m_tree ) //separate behavior for selected vs unselected segments
|
|
{
|
|
//don't bother with files
|
|
if( m_focus->file() == 0 || !m_focus->file()->isDir() ) return;
|
|
|
|
//find the range of levels we will be potentially drawing labels for
|
|
for( const Directory *p = (const Directory *)m_focus->file();
|
|
p != m_tree;
|
|
++startLevel ) //startLevel is the level above whatever m_focus is in
|
|
{
|
|
p = p->parent();
|
|
}
|
|
|
|
//range=2 means 2 levels to draw labels for
|
|
|
|
unsigned int a1, a2, minAngle;
|
|
|
|
a1 = m_focus->start();
|
|
a2 = m_focus->end(); //boundry angles
|
|
minAngle = int(m_focus->length() * LABEL_MIN_ANGLE_FACTOR);
|
|
|
|
|
|
#define segment (*it)
|
|
#define ring (m_map.m_signature + i)
|
|
|
|
//**** Levels should be on a scale starting with 0
|
|
//**** range is a useless parameter
|
|
//**** keep a topblock var which is the lowestLevel OR startLevel for identation purposes
|
|
for( unsigned int i = startLevel; i <= m_map.m_visibleDepth; ++i )
|
|
{
|
|
for( Iterator<Segment> it = ring->iterator(); it != ring->end(); ++it )
|
|
if( segment->start() >= a1 && segment->end() <= a2 )
|
|
if( segment->length() > minAngle )
|
|
list.inSort( new Label( segment, i ) );
|
|
}
|
|
|
|
#undef ring
|
|
#undef segment
|
|
|
|
} else {
|
|
|
|
#define ring m_map.m_signature
|
|
|
|
for( Iterator<Segment> it = ring->iterator(); it != ring->end(); ++it )
|
|
if( (*it)->length() > 288 )
|
|
list.inSort( new Label( (*it), 0 ) );
|
|
|
|
#undef ring
|
|
|
|
}
|
|
|
|
//2. Check to see if any adjacent labels are too close together
|
|
// if so, remove the least significant labels
|
|
|
|
it.toFirst();
|
|
TQPtrListIterator<Label> jt( it );
|
|
++jt;
|
|
|
|
while( jt ) //**** no need to check _it_ as jt will be NULL if _it_ was too
|
|
{
|
|
//this method is fairly efficient
|
|
|
|
if( (*it)->tooClose( (*jt)->a ) )
|
|
{
|
|
if( (*it)->lvl > (*jt)->lvl )
|
|
{
|
|
list.remove( *it );
|
|
it = jt;
|
|
}
|
|
else
|
|
list.remove( *jt );
|
|
}
|
|
else
|
|
++it;
|
|
|
|
jt = it;
|
|
++jt;
|
|
}
|
|
|
|
//used in next two steps
|
|
bool varySizes;
|
|
//**** should perhaps use doubles
|
|
int *sizes = new int [ m_map.m_visibleDepth + 1 ]; //**** make sizes an array of floats I think instead (or doubles)
|
|
|
|
do
|
|
{
|
|
//3. Calculate font sizes
|
|
|
|
{
|
|
//determine current range of levels to draw for
|
|
unsigned int range = 0;
|
|
|
|
for( it.toFirst(); it != 0; ++it )
|
|
{
|
|
unsigned int lvl = (*it)->lvl;
|
|
if( lvl > range )
|
|
range = lvl;
|
|
|
|
//**** better way would just be to assign if nothing is range
|
|
}
|
|
|
|
range -= startLevel; //range 0 means 1 level of labels
|
|
|
|
varySizes = Config::varyLabelFontSizes && (range != 0);
|
|
|
|
if( varySizes )
|
|
{
|
|
//create an array of font sizes for various levels
|
|
//will exceed normal font pitch automatically if necessary, but not minPitch
|
|
//**** this needs to be checked lots
|
|
|
|
//**** what if this is negative (min size gtr than default size)
|
|
unsigned int step = (paint.font().pointSize() - Config::minFontPitch) / range;
|
|
if( step == 0 ) step = 1;
|
|
|
|
for( unsigned int x = range + startLevel, y = Config::minFontPitch;
|
|
x >= startLevel;
|
|
y += step, --x )
|
|
{
|
|
sizes[x] = y;
|
|
}
|
|
}
|
|
}
|
|
|
|
//4. determine label co-ordinates
|
|
|
|
int x1, y1, x2, y2, x3, tx, ty; //coords
|
|
double sinra, cosra, ra; //angles
|
|
|
|
int cx = m_map.width() / 2 + m_offset.x(); //centre relative to canvas
|
|
int cy = m_map.height() / 2 + m_offset.y();
|
|
|
|
int spacer, preSpacer = int(m_map.m_ringBreadth * 0.5) + m_map.m_innerRadius;
|
|
int fullStrutLength = ( m_map.width() - m_map.MAP_2MARGIN ) / 2 + LABEL_MAP_SPACER; //full length of a strut from map center
|
|
|
|
int prevLeftY = 0;
|
|
int prevRightY = height();
|
|
|
|
bool rightSide;
|
|
|
|
TQFont font;
|
|
|
|
for( it.toFirst(); it != 0; ++it )
|
|
{
|
|
//** bear in mind that text is drawn with TQPoint param as BOTTOM left corner of text box
|
|
if( varySizes ) font.setPointSize( sizes[(*it)->lvl] );
|
|
TQFontMetrics fm( font );
|
|
int fmh = fm.height(); //used to ensure label texts don't overlap
|
|
int fmhD4 = fmh / 4;
|
|
|
|
fmh += LABEL_TEXT_VMARGIN;
|
|
|
|
rightSide = ( (*it)->a < 1440 || (*it)->a > 4320 );
|
|
|
|
ra = M_PI/2880 * (*it)->a; //convert to radians
|
|
#if 0
|
|
sincos( ra, &sinra, &cosra );
|
|
#endif
|
|
sinra = sin(ra); cosra = cos(ra);
|
|
|
|
spacer = preSpacer + m_map.m_ringBreadth * (*it)->lvl;
|
|
|
|
x1 = cx + (int)(cosra * spacer);
|
|
y1 = cy - (int)(sinra * spacer);
|
|
y2 = y1 - (int)(sinra * (fullStrutLength - spacer));
|
|
|
|
if( rightSide ) { //righthand side, going upwards
|
|
|
|
if( y2 > prevRightY /*- fmh*/ ) //then it is too low, needs to be drawn higher
|
|
y2 = prevRightY /*- fmh*/;
|
|
|
|
} else { //lefthand side, going downwards
|
|
|
|
if( y2 < prevLeftY/* + fmh*/ ) //then we're too high, need to be drawn lower
|
|
y2 = prevLeftY /*+ fmh*/;
|
|
}
|
|
|
|
x2 = x1 - int(double(y2 - y1) / tan( ra ));
|
|
ty = y2 + fmhD4;
|
|
|
|
TQString qs;
|
|
if( rightSide ) {
|
|
|
|
if( x2 > width() || ty < fmh || x2 < x1 )
|
|
{
|
|
//skip this strut
|
|
//**** don't duplicate this code
|
|
list.remove( *it ); //will delete the label and set it to list.current() which _should_ be the next ptr
|
|
break;
|
|
}
|
|
|
|
prevRightY = ty - fmh - fmhD4; //must be after above's "continue"
|
|
|
|
qs = KStringHandler::cPixelSqueeze( (*it)->segment->file()->name(), fm, width() - x2 );
|
|
|
|
x3 = width() - fm.width( qs )
|
|
- LABEL_HMARGIN //outer margin
|
|
- LABEL_TEXT_HMARGIN //margin between strut and text
|
|
//- ((*it)->lvl - startLevel) * LABEL_HMARGIN //indentation
|
|
;
|
|
if( x3 < x2 ) x3 = x2;
|
|
tx = x3 + LABEL_TEXT_HMARGIN;
|
|
|
|
} else {
|
|
|
|
if( x2 < 0 || ty > height() || x2 > x1 )
|
|
{
|
|
//skip this strut
|
|
list.remove( *it ); //will delete the label and set it to list.current() which _should_ be the next ptr
|
|
break;
|
|
}
|
|
|
|
prevLeftY = ty + fmh - fmhD4;
|
|
|
|
qs = KStringHandler::cPixelSqueeze( (*it)->segment->file()->name(), fm, x2 );
|
|
|
|
//**** needs a little tweaking:
|
|
|
|
tx = fm.width( qs ) + LABEL_HMARGIN/* + ((*it)->lvl - startLevel) * LABEL_HMARGIN*/;
|
|
if( tx > x2 ) { //text is too long
|
|
tx = LABEL_HMARGIN + x2 - tx; //some text will be lost from sight
|
|
x3 = x2; //no text margin (right side of text here)
|
|
} else {
|
|
x3 = tx + LABEL_TEXT_HMARGIN;
|
|
tx = LABEL_HMARGIN /* + ((*it)->lvl - startLevel) * LABEL_HMARGIN*/;
|
|
}
|
|
}
|
|
|
|
(*it)->x1 = x1;
|
|
(*it)->y1 = y1;
|
|
(*it)->x2 = x2;
|
|
(*it)->y2 = y2;
|
|
(*it)->x3 = x3;
|
|
(*it)->tx = tx;
|
|
(*it)->ty = ty;
|
|
(*it)->qs = qs;
|
|
}
|
|
|
|
//if an element is deleted at this stage, we need to do this whole
|
|
//iteration again, thus the following loop
|
|
//**** in rare case that deleted label was last label in top level
|
|
// and last in labelList too, this will not work as expected (not critical)
|
|
|
|
} while( it != 0 );
|
|
|
|
|
|
//5. Render labels
|
|
|
|
paint.setPen( TQPen( TQt::black, 1 ) );
|
|
|
|
for( it.toFirst(); it != 0; ++it )
|
|
{
|
|
if( varySizes )
|
|
{
|
|
//**** how much overhead in making new TQFont each time?
|
|
// (implicate sharing remember)
|
|
TQFont font = paint.font();
|
|
font.setPointSize( sizes[(*it)->lvl] );
|
|
paint.setFont( font );
|
|
}
|
|
|
|
paint.drawEllipse( (*it)->x1 - 3, (*it)->y1 - 3, 7, 7 ); //**** CPU intensive! better to use a pixmap
|
|
paint.drawLine( (*it)->x1, (*it)->y1, (*it)->x2, (*it)->y2 );
|
|
paint.drawLine( (*it)->x2, (*it)->y2, (*it)->x3, (*it)->y2);
|
|
paint.drawText( (*it)->tx, (*it)->ty, (*it)->qs );
|
|
}
|
|
|
|
delete [] sizes;
|
|
}
|