summaryrefslogtreecommitdiffstats
path: root/src/app/DiskUsage/radialMap
diff options
context:
space:
mode:
Diffstat (limited to 'src/app/DiskUsage/radialMap')
-rw-r--r--src/app/DiskUsage/radialMap/Makefile.am13
-rw-r--r--src/app/DiskUsage/radialMap/builder.cpp139
-rw-r--r--src/app/DiskUsage/radialMap/builder.h37
-rw-r--r--src/app/DiskUsage/radialMap/labels.cpp342
-rw-r--r--src/app/DiskUsage/radialMap/map.cpp432
-rw-r--r--src/app/DiskUsage/radialMap/radialMap.h71
-rw-r--r--src/app/DiskUsage/radialMap/segmentTip.cpp163
-rw-r--r--src/app/DiskUsage/radialMap/segmentTip.h33
-rw-r--r--src/app/DiskUsage/radialMap/sincos.h17
-rw-r--r--src/app/DiskUsage/radialMap/widget.cpp199
-rw-r--r--src/app/DiskUsage/radialMap/widget.h111
-rw-r--r--src/app/DiskUsage/radialMap/widgetEvents.cpp241
12 files changed, 1798 insertions, 0 deletions
diff --git a/src/app/DiskUsage/radialMap/Makefile.am b/src/app/DiskUsage/radialMap/Makefile.am
new file mode 100644
index 0000000..ab5effd
--- /dev/null
+++ b/src/app/DiskUsage/radialMap/Makefile.am
@@ -0,0 +1,13 @@
+noinst_LIBRARIES = libradialmap.a
+
+INCLUDES = -I$(top_srcdir)/src/app/DiskUsage/filelightParts $(all_includes)
+
+METASOURCES = AUTO
+
+libradialmap_a_SOURCES = \
+ widget.cpp \
+ builder.cpp \
+ map.cpp \
+ widgetEvents.cpp \
+ labels.cpp \
+ segmentTip.cpp
diff --git a/src/app/DiskUsage/radialMap/builder.cpp b/src/app/DiskUsage/radialMap/builder.cpp
new file mode 100644
index 0000000..8edb70c
--- /dev/null
+++ b/src/app/DiskUsage/radialMap/builder.cpp
@@ -0,0 +1,139 @@
+//Author: Max Howell <max.howell@methylblue.com>, (C) 2003-4
+//Copyright: See COPYING file that comes with this distribution
+
+#include "builder.h"
+#include "Config.h"
+#include "fileTree.h"
+#include <tdeglobal.h> //locale object
+#include <tdelocale.h>
+#include "widget.h"
+
+
+//**** REMOVE NEED FOR the +1 with MAX_RING_DEPTH uses
+//**** add some angle bounds checking (possibly in Segment ctor? can I delete in a ctor?)
+//**** this class is a mess
+
+RadialMap::Builder::Builder( RadialMap::Map *m, const Directory* const d, bool fast )
+ : m_map( m )
+ , m_root( d )
+ , m_minSize( static_cast<FileSize>((d->size() * 3) / (PI * m->height() - m->MAP_2MARGIN )) )
+ , m_depth( &m->m_visibleDepth )
+{
+ m_signature = new Chain<Segment> [*m_depth + 1];
+
+ if( !fast )//|| *m_depth == 0 ) //depth 0 is special case usability-wise //**** WHY?!
+ {
+ //determine depth rather than use old one
+ findVisibleDepth( d ); //sets m_depth
+ }
+
+ m_map->setRingBreadth();
+ setLimits( m_map->m_ringBreadth );
+ build( d );
+
+ m_map->m_signature = m_signature;
+
+ delete []m_limits;
+}
+
+
+void
+RadialMap::Builder::findVisibleDepth( const Directory* const dir, const unsigned int depth )
+{
+ //**** because I don't use the same minimumSize criteria as in the visual function
+ // this can lead to incorrect visual representation
+ //**** BUT, you can't set those limits until you know m_depth!
+
+ //**** also this function doesn't check to see if anything is actually visible
+ // it just assumes that when it reaches a new level everything in it is visible
+ // automatically. This isn't right especially as there might be no files in the
+ // dir provided to this function!
+
+ static uint stopDepth = 0;
+
+ if( dir == m_root )
+ {
+ stopDepth = *m_depth;
+ *m_depth = 0;
+ }
+
+ if( *m_depth < depth ) *m_depth = depth;
+ if( *m_depth >= stopDepth ) return;
+
+ for( ConstIterator<File> it = dir->constIterator(); it != dir->end(); ++it )
+ if( (*it)->isDir() && (*it)->size() > m_minSize )
+ findVisibleDepth( (Directory *)*it, depth + 1 ); //if no files greater than min size the depth is still recorded
+}
+
+void
+RadialMap::Builder::setLimits( const uint &b ) //b = breadth?
+{
+ double size3 = m_root->size() * 3;
+ double pi2B = PI * 2 * b;
+
+ m_limits = new FileSize [*m_depth + 1]; //FIXME delete!
+
+ for( unsigned int d = 0; d <= *m_depth; ++d )
+ m_limits[d] = (FileSize)(size3 / (double)(pi2B * (d + 1))); //min is angle that gives 3px outer diameter for that depth
+}
+
+
+//**** segments currently overlap at edges (i.e. end of first is start of next)
+bool
+RadialMap::Builder::build( const Directory* const dir, const unsigned int depth, unsigned int a_start, const unsigned int a_end )
+{
+ //first iteration: dir == m_root
+
+ if( dir->fileCount() == 0 ) //we do fileCount rather than size to avoid chance of divide by zero later
+ return false;
+
+ FileSize hiddenSize = 0;
+ uint hiddenFileCount = 0;
+
+ for( ConstIterator<File> it = dir->constIterator(); it != dir->end(); ++it )
+ {
+ if( (*it)->size() > m_limits[depth] )
+ {
+ unsigned int a_len = (unsigned int)(5760 * ((double)(*it)->size() / (double)m_root->size()));
+
+ Segment *s = new Segment( *it, a_start, a_len );
+
+ (m_signature + depth)->append( s );
+
+ if( (*it)->isDir() )
+ {
+ if( depth != *m_depth )
+ {
+ //recurse
+ s->m_hasHiddenChildren = build( (Directory*)*it, depth + 1, a_start, a_start + a_len );
+ }
+ else s->m_hasHiddenChildren = true;
+ }
+
+ a_start += a_len; //**** should we add 1?
+
+ } else {
+
+ hiddenSize += (*it)->size();
+
+ if( (*it)->isDir() ) //**** considered virtual, but dir wouldn't count itself!
+ hiddenFileCount += static_cast<const Directory*>(*it)->fileCount(); //need to add one to count the dir as well
+
+ ++hiddenFileCount;
+ }
+ }
+
+ if( hiddenFileCount == dir->fileCount() && !Config::showSmallFiles )
+
+ return true;
+
+ else if( (Config::showSmallFiles && hiddenSize > m_limits[depth]) || (depth == 0 && (hiddenSize > dir->size()/8)) /*|| > size() * 0.75*/ )
+ {
+ //append a segment for unrepresented space - a "fake" segment
+
+ const TQString s = i18n( "%1 files: ~ %2" ).arg( TDEGlobal::locale()->formatNumber( hiddenFileCount, 0 ) ).arg( File::humanReadableSize( hiddenSize/hiddenFileCount ) );
+ (m_signature + depth)->append( new Segment( new File( s, hiddenSize ), a_start, a_end - a_start, true ) );
+ }
+
+ return false;
+}
diff --git a/src/app/DiskUsage/radialMap/builder.h b/src/app/DiskUsage/radialMap/builder.h
new file mode 100644
index 0000000..2fddbe0
--- /dev/null
+++ b/src/app/DiskUsage/radialMap/builder.h
@@ -0,0 +1,37 @@
+//Author: Max Howell <max.howell@methylblue.com>, (C) 2003-4
+//Copyright: See COPYING file that comes with this distribution
+
+#ifndef BUILDER_H
+#define BUILDER_H
+
+#include "radialMap.h" //Segment, defines
+#include "fileTree.h"
+
+template <class T> class Chain;
+
+namespace RadialMap
+{
+ class Map;
+
+ //temporary class that builds the Map signature
+
+ class Builder
+ {
+ public:
+ Builder( Map*, const Directory* const, bool fast=false );
+
+ private:
+ void findVisibleDepth( const Directory* const dir, const uint=0 );
+ void setLimits( const uint& );
+ bool build( const Directory* const, const uint=0, uint=0, const uint=5760 );
+
+ Map *m_map;
+ const Directory* const m_root;
+ const FileSize m_minSize;
+ uint *m_depth;
+ Chain<Segment> *m_signature;
+ FileSize *m_limits;
+ };
+}
+
+#endif
diff --git a/src/app/DiskUsage/radialMap/labels.cpp b/src/app/DiskUsage/radialMap/labels.cpp
new file mode 100644
index 0000000..5d61b7a
--- /dev/null
+++ b/src/app/DiskUsage/radialMap/labels.cpp
@@ -0,0 +1,342 @@
+//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;
+}
diff --git a/src/app/DiskUsage/radialMap/map.cpp b/src/app/DiskUsage/radialMap/map.cpp
new file mode 100644
index 0000000..329b8bc
--- /dev/null
+++ b/src/app/DiskUsage/radialMap/map.cpp
@@ -0,0 +1,432 @@
+//Author: Max Howell <max.howell@methylblue.com>, (C) 2003-4
+//Copyright: See COPYING file that comes with this distribution
+
+#include <kcursor.h> //make()
+#include <tdeglobalsettings.h> //kdeColours
+#include <kimageeffect.h> //desaturate()
+#include <tqapplication.h> //make()
+#include <tqimage.h> //make() & paint()
+#include <tqfont.h> //ctor
+#include <tqfontmetrics.h> //ctor
+#include <tqpainter.h>
+
+#include "builder.h"
+#include "Config.h"
+#include "fileTree.h"
+#include "sincos.h"
+#include "widget.h"
+
+#define COLOR_GREY TQColor( 0, 0, 140, TQColor::Hsv )
+
+
+RadialMap::Map::Map()
+ : m_signature( 0 )
+ , m_ringBreadth( MIN_RING_BREADTH )
+ , m_innerRadius( 0 )
+ , m_visibleDepth( DEFAULT_RING_DEPTH )
+{
+ //FIXME this is all broken. No longer is a maximum depth!
+ const int fmh = TQFontMetrics( TQFont() ).height();
+ const int fmhD4 = fmh / 4;
+ MAP_2MARGIN = 2 * ( fmh - (fmhD4 - LABEL_MAP_SPACER) ); //margin is dependent on fitting in labels at top and bottom
+}
+
+RadialMap::Map::~Map()
+{
+ delete [] m_signature;
+}
+
+void
+RadialMap::Map::invalidate( const bool desaturateTheImage )
+{
+ delete [] m_signature;
+ m_signature = 0;
+
+ if( desaturateTheImage )
+ {
+ TQImage img = this->convertToImage();
+
+ KImageEffect::desaturate( img, 0.7 );
+ KImageEffect::toGray( img, true );
+
+ this->convertFromImage( img );
+ }
+
+ m_visibleDepth = Config::defaultRingDepth;
+}
+
+void
+RadialMap::Map::make( const Directory *tree, bool refresh )
+{
+ //**** determineText seems pointless optimisation
+ // but is it good to keep the text consistent?
+ // even if it makes it a lie?
+
+ //slow operation so set the wait cursor
+ TQApplication::setOverrideCursor( KCursor::waitCursor() );
+
+ {
+ //build a signature of visible components
+ delete [] m_signature;
+ Builder builder( this, tree, refresh );
+ }
+
+ //colour the segments
+ colorise();
+
+ //determine centerText
+ if( !refresh )
+ {
+ int i;
+
+ for( i = 3; i > 0; --i )
+ if( tree->size() > File::DENOMINATOR[i] )
+ break;
+
+ m_centerText = tree->humanReadableSize( (File::UnitPrefix)i );
+ }
+
+ //paint the pixmap
+ aaPaint();
+
+ TQApplication::restoreOverrideCursor();
+}
+
+void
+RadialMap::Map::setRingBreadth()
+{
+ m_ringBreadth = (height() - MAP_2MARGIN) / (2 * m_visibleDepth + 4);
+
+ if( m_ringBreadth < MIN_RING_BREADTH ) m_ringBreadth = MIN_RING_BREADTH;
+ else if( m_ringBreadth > MAX_RING_BREADTH ) m_ringBreadth = MAX_RING_BREADTH;
+}
+
+bool
+RadialMap::Map::resize( const TQRect &rect )
+{
+ //there's a MAP_2MARGIN border
+
+ #define mw width()
+ #define mh height()
+ #define cw rect.width()
+ #define ch rect.height()
+
+ if( cw < mw || ch < mh || (cw > mw && ch > mh) )
+ {
+ uint size = (( cw < ch ) ? cw : ch) - MAP_2MARGIN;
+
+ //this also causes uneven sizes to always resize when resizing but map is small in that dimension
+ //size -= size % 2; //even sizes mean less staggered non-antialiased resizing
+
+ {
+ const uint minSize = MIN_RING_BREADTH * 2 * (m_visibleDepth + 2);
+ const uint mD2 = MAP_2MARGIN / 2;
+
+ if( size < minSize ) size = minSize;
+
+ //this TQRect is used by paint()
+ m_rect.setRect( mD2, mD2, size, size );
+ }
+
+ //resize the pixmap
+ size += MAP_2MARGIN;
+ KPixmap::resize( size, size );
+
+ if( m_signature != NULL )
+ {
+ setRingBreadth();
+ paint();
+ }
+ else fill(); //FIXME I don't like having to do this..
+
+ return true;
+ }
+
+ #undef mw
+ #undef mh
+ #undef cw
+ #undef ch
+
+ return false;
+}
+
+void
+RadialMap::Map::colorise()
+{
+ TQColor cp, cb;
+ double darkness = 1;
+ double contrast = (double)Config::contrast / (double)100;
+ int h, s1, s2, v1, v2;
+
+ TQColor kdeColour[2] = { TDEGlobalSettings::inactiveTitleColor(), TDEGlobalSettings::activeTitleColor() };
+
+ double deltaRed = (double)(kdeColour[0].red() - kdeColour[1].red()) / 2880; //2880 for semicircle
+ double deltaGreen = (double)(kdeColour[0].green() - kdeColour[1].green()) / 2880;
+ double deltaBlue = (double)(kdeColour[0].blue() - kdeColour[1].blue()) / 2880;
+
+ for( uint i = 0; i <= m_visibleDepth; ++i, darkness += 0.04 )
+ {
+ for( Iterator<Segment> it = m_signature[i].iterator(); it != m_signature[i].end(); ++it )
+ {
+ switch( Config::scheme )
+ {
+ case 2000: //HACK for summary view
+
+ if( (*it)->file()->name() == "Used" ) {
+ cb = TQApplication::palette().active().color( TQColorGroup::Highlight );
+ cb.hsv( &h, &s1, &v1 );
+
+ if( s1 > 80 )
+ s1 = 80;
+
+ v2 = v1 - int(contrast * v1);
+ s2 = s1 + int(contrast * (255 - s1));
+
+ cb.setHsv( h, s1, v1 );
+ cp.setHsv( h, s2, v2 );
+ }
+ else {
+ cp = TQt::gray;
+ cb = TQt::white;
+ }
+
+ (*it)->setPalette( cp, cb );
+
+ continue;
+ case Filelight::KDE:
+ {
+ //gradient will work by figuring out rgb delta values for 360 degrees
+ //then each component is angle*delta
+
+ int a = (*it)->start();
+
+ if( a > 2880 ) a = 2880 - (a - 2880);
+
+ h = (int)(deltaRed * a) + kdeColour[1].red();
+ s1 = (int)(deltaGreen * a) + kdeColour[1].green();
+ v1 = (int)(deltaBlue * a) + kdeColour[1].blue();
+
+ cb.setRgb( h, s1, v1 );
+ cb.hsv( &h, &s1, &v1 );
+
+ break;
+ }
+
+ case Filelight::HighContrast:
+
+ cp.setHsv( 0, 0, 0 ); //values of h, s and v are irrelevant
+ cb.setHsv( 180, 0, int(255.0 * contrast) );
+ (*it)->setPalette( cp, cb );
+ continue;
+
+ default:
+ h = int((*it)->start() / 16);
+ s1 = 160;
+ v1 = (int)(255.0 / darkness); //****doing this more often than once seems daft!
+ }
+
+ v2 = v1 - int(contrast * v1);
+ s2 = s1 + int(contrast * (255 - s1));
+
+ if( s1 < 80 ) s1 = 80; //can fall too low and makes contrast between the files hard to discern
+
+ if( (*it)->isFake() ) //multi-file
+ {
+ cb.setHsv( h, s2, (v2 < 90) ? 90 : v2 ); //too dark if < 100
+ cp.setHsv( h, 17, v1 );
+ }
+ else if( !(*it)->file()->isDir() ) //file
+ {
+ cb.setHsv( h, 17, v1 );
+ cp.setHsv( h, 17, v2 );
+ }
+ else //directory
+ {
+ cb.setHsv( h, s1, v1 ); //v was 225
+ cp.setHsv( h, s2, v2 ); //v was 225 - delta
+ }
+
+ (*it)->setPalette( cp, cb );
+
+ //**** may be better to store KDE colours as H and S and vary V as others
+ //**** perhaps make saturation difference for s2 dependent on contrast too
+ //**** fake segments don't work with highContrast
+ //**** may work better with cp = cb rather than TQt::white
+ //**** you have to ensure the grey of files is sufficient, currently it works only with rainbow (perhaps use contrast there too)
+ //**** change v1,v2 to vp, vb etc.
+ //**** using percentages is not strictly correct as the eye doesn't work like that
+ //**** darkness factor is not done for kde_colour scheme, and also value for files is incorrect really for files in this scheme as it is not set like rainbow one is
+ }
+ }
+}
+
+void
+RadialMap::Map::aaPaint()
+{
+ //paint() is called during continuous processes
+ //aaPaint() is not and is slower so set overidecursor (make sets it too)
+ TQApplication::setOverrideCursor( KCursor::waitCursor() );
+ paint( Config::antiAliasFactor );
+ TQApplication::restoreOverrideCursor();
+}
+
+void
+RadialMap::Map::paint( unsigned int scaleFactor )
+{
+ if( scaleFactor == 0 ) //just in case
+ scaleFactor = 1;
+
+ TQPainter paint;
+ TQRect rect = m_rect;
+ int step = m_ringBreadth;
+ int excess = -1;
+
+ //scale the pixmap, or do intelligent distribution of excess to prevent nasty resizing
+ if( scaleFactor > 1 )
+ {
+ int x1, y1, x2, y2;
+ rect.coords( &x1, &y1, &x2, &y2 );
+ x1 *= scaleFactor;
+ y1 *= scaleFactor;
+ x2 *= scaleFactor;
+ y2 *= scaleFactor;
+ rect.setCoords( x1, y1, x2, y2 );
+
+ step *= scaleFactor;
+ KPixmap::resize( this->size() * (int)scaleFactor );
+ }
+ else if( m_ringBreadth != MAX_RING_BREADTH && m_ringBreadth != MIN_RING_BREADTH ) {
+ excess = rect.width() % m_ringBreadth;
+ ++step;
+ }
+
+ //**** best option you can think of is to make the circles slightly less perfect,
+ // ** i.e. slightly eliptic when resizing inbetween
+
+
+ paint.begin( this );
+
+ fill(); //erase background
+
+ for( int x = m_visibleDepth; x >= 0; --x )
+ {
+ int width = rect.width() / 2;
+ //clever geometric trick to find largest angle that will give biggest arrow head
+ int a_max = int(acos( (double)width / double((width + 5) * scaleFactor) ) * (180*16 / M_PI));
+
+ for( ConstIterator<Segment> it = m_signature[x].constIterator(); it != m_signature[x].end(); ++it )
+ {
+ //draw the pie segments, most of this code is concerned with drawing the little
+ //arrows on the ends of segments when they have hidden files
+
+ paint.setPen( (*it)->pen() );
+
+ if( (*it)->hasHiddenChildren() )
+ {
+ //draw arrow head to indicate undisplayed files/directories
+ TQPointArray pts( 3 );
+ TQPoint pos, cpos = rect.center();
+ uint a[3] = { (*it)->start(), (*it)->length(), 0 };
+
+ a[2] = a[0] + (a[1] / 2); //assign to halfway between
+ if( a[1] > a_max )
+ {
+ a[1] = a_max;
+ a[0] = a[2] - a_max / 2;
+ }
+
+ a[1] += a[0];
+
+ for( int i = 0, radius = width; i < 3; ++i )
+ {
+ double ra = M_PI/(180*16) * a[i], sinra, cosra;
+
+ if( i == 2 )
+ radius += 5 * scaleFactor;
+#if 0
+ sincos( ra, &sinra, &cosra );
+#endif
+ sinra = sin(ra); cosra = cos(ra);
+ pos.rx() = cpos.x() + static_cast<int>(cosra * radius);
+ pos.ry() = cpos.y() - static_cast<int>(sinra * radius);
+ pts.setPoint( i, pos );
+ }
+
+ paint.setBrush( (*it)->pen() );
+ paint.drawPolygon( pts );
+ }
+
+ paint.setBrush( (*it)->brush() );
+ paint.drawPie( rect, (*it)->start(), (*it)->length() );
+
+ if( (*it)->hasHiddenChildren() )
+ {
+ //**** code is bloated!
+ paint.save();
+ TQPen pen = paint.pen();
+ int width = 2 * scaleFactor;
+ pen.setWidth( width );
+ paint.setPen( pen );
+ TQRect rect2 = rect;
+ width /= 2;
+ rect2.addCoords( width, width, -width, -width );
+ paint.drawArc( rect2, (*it)->start(), (*it)->length() );
+ paint.restore();
+ }
+ }
+
+ if( excess >= 0 ) { //excess allows us to resize more smoothly (still crud tho)
+ if( excess < 2 ) //only decrease rect by more if even number of excesses left
+ --step;
+ excess -= 2;
+ }
+
+ rect.addCoords( step, step, -step, -step );
+ }
+
+ // if( excess > 0 ) rect.addCoords( excess, excess, 0, 0 ); //ugly
+
+ paint.setPen( COLOR_GREY );
+ paint.setBrush( TQt::white );
+ paint.drawEllipse( rect );
+
+ if( scaleFactor > 1 )
+ {
+ //have to end in order to smoothscale()
+ paint.end();
+
+ int x1, y1, x2, y2;
+ rect.coords( &x1, &y1, &x2, &y2 );
+ x1 /= scaleFactor;
+ y1 /= scaleFactor;
+ x2 /= scaleFactor;
+ y2 /= scaleFactor;
+ rect.setCoords( x1, y1, x2, y2 );
+
+ TQImage img = this->convertToImage();
+ img = img.smoothScale( this->size() / (int)scaleFactor );
+ this->convertFromImage( img );
+
+ paint.begin( this );
+ paint.setPen( COLOR_GREY );
+ paint.setBrush( TQt::white );
+ }
+
+ paint.drawText( rect, TQt::AlignCenter, m_centerText );
+
+ m_innerRadius = rect.width() / 2; //rect.width should be multiple of 2
+
+ paint.end();
+}
+#if 0
+#if __GLIBC__ < 2 || __GLIBC__ == 2 && __GLIBC_MINOR__ < 1
+
+ void
+ sincos( double angleRadians, double *Sin, double *Cos )
+ {
+ *Sin = sin( angleRadians );
+ *Cos = cos( angleRadians );
+ }
+
+#endif
+#endif
diff --git a/src/app/DiskUsage/radialMap/radialMap.h b/src/app/DiskUsage/radialMap/radialMap.h
new file mode 100644
index 0000000..c5edf65
--- /dev/null
+++ b/src/app/DiskUsage/radialMap/radialMap.h
@@ -0,0 +1,71 @@
+//Author: Max Howell <max.howell@methylblue.com>, (C) 2003-4
+//Copyright: See COPYING file that comes with this distribution
+
+#ifndef RADIALMAP_H
+#define RADIALMAP_H
+
+#include <tqcolor.h>
+
+class File;
+
+
+namespace RadialMap
+{
+ class Segment //all angles are in 16ths of degrees
+ {
+ public:
+ Segment( const File *f, uint s, uint l, bool isFake = false )
+ : m_angleStart( s )
+ , m_angleSegment( l )
+ , m_file( f )
+ , m_hasHiddenChildren( false )
+ , m_fake( isFake ) {}
+ ~Segment();
+
+ uint start() const { return m_angleStart; }
+ uint length() const { return m_angleSegment; }
+ uint end() const { return m_angleStart + m_angleSegment; }
+ const File *file() const { return m_file; }
+ const TQColor& pen() const { return m_pen; }
+ const TQColor& brush() const { return m_brush; }
+
+ bool isFake() const { return m_fake; }
+ bool hasHiddenChildren() const { return m_hasHiddenChildren; }
+
+ bool intersects( uint a ) const { return ( ( a >= start() ) && ( a < end() ) ); }
+
+ friend class Map;
+ friend class Builder;
+
+ private:
+ void setPalette( const TQColor &p, const TQColor &b ) { m_pen = p; m_brush = b; }
+
+ const uint m_angleStart, m_angleSegment;
+ const File* const m_file;
+ TQColor m_pen, m_brush;
+ bool m_hasHiddenChildren;
+ const bool m_fake;
+ };
+}
+
+
+#ifndef PI
+#define PI 3.141592653589793
+#endif
+#ifndef M_PI
+#define M_PI 3.14159265358979323846264338327
+#endif
+
+#define MIN_RING_BREADTH 20
+#define MAX_RING_BREADTH 60
+#define DEFAULT_RING_DEPTH 4 //first level = 0
+#define MIN_RING_DEPTH 0
+
+#define LABEL_MAP_SPACER 7
+#define LABEL_HMARGIN 10
+#define LABEL_TEXT_HMARGIN 5
+#define LABEL_TEXT_VMARGIN 0
+#define LABEL_ANGLE_MARGIN 32
+#define LABEL_MIN_ANGLE_FACTOR 0.05
+
+#endif
diff --git a/src/app/DiskUsage/radialMap/segmentTip.cpp b/src/app/DiskUsage/radialMap/segmentTip.cpp
new file mode 100644
index 0000000..2f2b31c
--- /dev/null
+++ b/src/app/DiskUsage/radialMap/segmentTip.cpp
@@ -0,0 +1,163 @@
+//Author: Max Howell <max.howell@methylblue.com>, (C) 2003-4
+//Copyright: See COPYING file that comes with this distribution
+
+#include "fileTree.h"
+#include "segmentTip.h"
+
+#include <tdeapplication.h> //installing eventFilters
+#include <tdeglobal.h>
+#include <tdeglobalsettings.h>
+#include <tdelocale.h>
+#include <kpixmapeffect.h>
+#include <tqpainter.h>
+#include <tqtooltip.h> //for its palette
+
+
+
+namespace RadialMap {
+
+SegmentTip::SegmentTip( uint h )
+ : TQWidget( 0, 0, WNoAutoErase | WStyle_Customize | WStyle_NoBorder | WStyle_Tool | WStyle_StaysOnTop | WX11BypassWM )
+ , m_cursorHeight( -h )
+{
+ setBackgroundMode( TQt::NoBackground );
+}
+
+void
+SegmentTip::moveto( TQPoint p, const TQWidget &canvas, bool placeAbove )
+{
+ //**** this function is very slow and seems to be visibly influenced by operations like mapFromGlobal() (who knows why!)
+ // ** so any improvements are much desired
+
+ //TODO uints could improve the class
+ p.rx() -= rect().center().x();
+ p.ry() -= (placeAbove ? 8 + height() : m_cursorHeight - 8);
+
+ const TQRect screen = TDEGlobalSettings::desktopGeometry( parentWidget() );
+
+ const int x = p.x();
+ const int y = p.y();
+ const int x2 = x + width();
+ const int y2 = y + height(); //how's it ever gunna get below screen height?! (well you never know I spose)
+ const int sw = screen.width();
+ const int sh = screen.height();
+
+ if( x < 0 ) p.setX( 0 );
+ if( y < 0 ) p.setY( 0 );
+ if( x2 > sw ) p.rx() -= x2 - sw;
+ if( y2 > sh ) p.ry() -= y2 - sh;
+
+
+ //I'm using this TQPoint to determine where to offset the bitBlt in m_pixmap
+ TQPoint offset = canvas.mapToGlobal( TQPoint() ) - p;
+ if( offset.x() < 0 ) offset.setX( 0 );
+ if( offset.y() < 0 ) offset.setY( 0 );
+
+
+ const TQRect alphaMaskRect( canvas.mapFromGlobal( p ), size() );
+ const TQRect intersection( alphaMaskRect.intersect( canvas.rect() ) );
+
+ m_pixmap.resize( size() ); //move to updateTip once you are sure it can never be null
+ bitBlt( &m_pixmap, offset, &canvas, intersection, TQt::CopyROP );
+
+ TQPainter paint( &m_pixmap );
+ paint.setPen( TQt::black );
+ paint.setBrush( TQt::NoBrush );
+ paint.drawRect( rect() );
+ paint.end();
+
+ m_pixmap = KPixmapEffect::fade( m_pixmap, 0.6, TQToolTip::palette().color( TQPalette::Active, TQColorGroup::Background ) );
+
+ paint.begin( &m_pixmap );
+ paint.drawText( rect(), AlignCenter, m_text );
+ paint.end();
+
+ p += screen.topLeft(); //for Xinerama users
+
+ move( x, y );
+ show();
+ update();
+}
+
+void
+SegmentTip::updateTip( const File* const file, const Directory* const root )
+{
+ const TQString s1 = file->fullPath( root );
+ TQString s2 = file->humanReadableSize();
+ TDELocale *loc = TDEGlobal::locale();
+ const uint MARGIN = 3;
+ const uint pc = 100 * file->size() / root->size();
+ uint maxw = 0;
+ uint h = fontMetrics().height()*2 + 2*MARGIN;
+
+ if( pc > 0 ) s2 += TQString( " (%1%)" ).arg( loc->formatNumber( pc, 0 ) );
+
+ m_text = s1;
+ m_text += '\n';
+ m_text += s2;
+
+ if( file->isDir() )
+ {
+ double files = static_cast<const Directory*>(file)->fileCount();
+ const uint pc = uint((100 * files) / (double)root->fileCount());
+ TQString s3 = i18n( "Files: %1" ).arg( loc->formatNumber( files, 0 ) );
+
+ if( pc > 0 ) s3 += TQString( " (%1%)" ).arg( loc->formatNumber( pc, 0 ) );
+
+ maxw = fontMetrics().width( s3 );
+ h += fontMetrics().height();
+ m_text += '\n';
+ m_text += s3;
+ }
+
+ uint
+ w = fontMetrics().width( s1 ); if( w > maxw ) maxw = w;
+ w = fontMetrics().width( s2 ); if( w > maxw ) maxw = w;
+
+ resize( maxw + 2 * MARGIN, h );
+}
+
+bool
+SegmentTip::event( TQEvent *e )
+{
+ switch( e->type() )
+ {
+ case TQEvent::Show:
+ kapp->installEventFilter( this );
+ break;
+ case TQEvent::Hide:
+ kapp->removeEventFilter( this );
+ break;
+ case TQEvent::Paint:
+ {
+ //TQPainter( this ).drawPixmap( 0, 0, m_pixmap );
+ bitBlt( this, 0, 0, &m_pixmap );
+ return true;
+ }
+ default:
+ ;
+ }
+
+ return false/*TQWidget::event( e )*/;
+}
+
+bool
+SegmentTip::eventFilter( TQObject*, TQEvent *e )
+{
+ switch ( e->type() )
+ {
+ case TQEvent::Leave:
+// case TQEvent::MouseButtonPress:
+// case TQEvent::MouseButtonRelease:
+ case TQEvent::KeyPress:
+ case TQEvent::KeyRelease:
+ case TQEvent::FocusIn:
+ case TQEvent::FocusOut:
+ case TQEvent::Wheel:
+ hide(); //FALL THROUGH
+ default:
+ return false; //allow this event to passed to target
+ }
+}
+
+} //namespace RadialMap
diff --git a/src/app/DiskUsage/radialMap/segmentTip.h b/src/app/DiskUsage/radialMap/segmentTip.h
new file mode 100644
index 0000000..7f279cb
--- /dev/null
+++ b/src/app/DiskUsage/radialMap/segmentTip.h
@@ -0,0 +1,33 @@
+// Author: Max Howell <max.howell@methylblue.com>, (C) 2004
+// Copyright: See COPYING file that comes with this distribution
+
+#ifndef SEGMENTTIP_H
+#define SEGMENTTIP_H
+
+#include <kpixmap.h>
+#include <tqwidget.h>
+
+class File;
+class Directory;
+
+namespace RadialMap
+{
+ class SegmentTip : public TQWidget
+ {
+ public:
+ SegmentTip( uint );
+
+ void updateTip( const File*, const Directory* );
+ void moveto( TQPoint, const TQWidget&, bool );
+
+ private:
+ virtual bool eventFilter( TQObject*, TQEvent* );
+ virtual bool event( TQEvent* );
+
+ uint m_cursorHeight;
+ KPixmap m_pixmap;
+ TQString m_text;
+ };
+}
+
+#endif
diff --git a/src/app/DiskUsage/radialMap/sincos.h b/src/app/DiskUsage/radialMap/sincos.h
new file mode 100644
index 0000000..5e66e54
--- /dev/null
+++ b/src/app/DiskUsage/radialMap/sincos.h
@@ -0,0 +1,17 @@
+//Author: Max Howell <max.howell@methylblue.com>, (C) 2003-4
+//Copyright: See COPYING file that comes with this distribution
+
+#ifndef SINCOS_H
+#define SINCOS_H
+
+#include <math.h>
+#if 0
+#if __GLIBC__ < 2 || __GLIBC__ == 2 && __GLIBC_MINOR__ < 1
+
+ void
+ sincos( double angleRadians, double *Sin, double *Cos );
+
+#endif
+
+#endif
+#endif
diff --git a/src/app/DiskUsage/radialMap/widget.cpp b/src/app/DiskUsage/radialMap/widget.cpp
new file mode 100644
index 0000000..4ed038f
--- /dev/null
+++ b/src/app/DiskUsage/radialMap/widget.cpp
@@ -0,0 +1,199 @@
+//Author: Max Howell <max.howell@methylblue.com>, (C) 2003-4
+//Copyright: See COPYING file that comes with this distribution
+
+#include <kcursor.h> //ctor
+#include <tdelocale.h>
+#include <kurl.h>
+#include <tqapplication.h> //sendEvent
+#include <tqbitmap.h> //ctor - finding cursor size
+#include <tqcursor.h> //slotPostMouseEvent()
+#include <tqtimer.h> //member
+
+#include "Config.h"
+#include "debug.h"
+#include "fileTree.h"
+#include "radialMap.h" //constants
+#include "widget.h"
+
+
+
+RadialMap::Widget::Widget( TQWidget *parent, const char *name )
+ : TQWidget( parent, name, TQt::WNoAutoErase )
+ , m_tree( 0 )
+ , m_focus( 0 )
+ , m_tip( KCursor::handCursor().bitmap()->height() ) //needs to know cursor height
+ , m_rootSegment( 0 ) //TODO we don't delete it, *shrug*
+{
+ setBackgroundColor( TQt::white );
+
+ connect( this, TQ_SIGNAL(created( const Directory* )), TQ_SLOT(sendFakeMouseEvent()) );
+ connect( this, TQ_SIGNAL(created( const Directory* )), TQ_SLOT(update()) );
+ connect( &m_timer, TQ_SIGNAL(timeout()), TQ_SLOT(resizeTimeout()) );
+}
+
+TQString
+RadialMap::Widget::path() const
+{
+ if( m_tree == 0 )
+ return TQString();
+ return m_tree->fullPath();
+}
+
+KURL
+RadialMap::Widget::url( File const * const file ) const
+{
+ if( file == 0 && m_tree == 0 )
+ return KURL();
+
+ return KURL::fromPathOrURL( file ? file->fullPath() : m_tree->fullPath() );
+}
+
+void
+RadialMap::Widget::invalidate( const bool b )
+{
+ if( isValid() )
+ {
+ //**** have to check that only way to invalidate is this function frankly
+ //**** otherwise you may get bugs..
+
+ //disable mouse tracking
+ setMouseTracking( false );
+
+ KURL urlInv = url();
+
+ //ensure this class won't think we have a map still
+ m_tree = 0;
+ m_focus = 0;
+
+ delete m_rootSegment;
+ m_rootSegment = 0;
+
+ //FIXME move this disablement thing no?
+ // it is confusing in other areas, like the whole createFromCache() thing
+ m_map.invalidate( b ); //b signifies whether the pixmap is made to look disabled or not
+ if( b )
+ update();
+
+ //tell rest of Filelight
+ emit invalidated( urlInv );
+ }
+}
+
+void
+RadialMap::Widget::create( const Directory *tree )
+{
+ //it is not the responsibility of create() to invalidate first
+ //skip invalidation at your own risk
+
+ //FIXME make it the responsibility of create to invalidate first
+
+ if( tree )
+ {
+ m_focus = 0;
+ //generate the filemap image
+ m_map.make( tree );
+
+ //this is the inner circle in the center
+ m_rootSegment = new Segment( tree, 0, 16*360 );
+
+ setMouseTracking( true );
+ }
+
+ m_tree = tree;
+
+ //tell rest of Filelight
+ emit created( tree );
+}
+
+void
+RadialMap::Widget::createFromCache( const Directory *tree )
+{
+ //no scan was necessary, use cached tree, however we MUST still emit invalidate
+ invalidate( false );
+ create( tree );
+}
+
+void
+RadialMap::Widget::sendFakeMouseEvent() //slot
+{
+ TQMouseEvent me( TQEvent::MouseMove, mapFromGlobal( TQCursor::pos() ), TQt::NoButton, TQt::NoButton );
+ TQApplication::sendEvent( this, &me );
+}
+
+void
+RadialMap::Widget::resizeTimeout() //slot
+{
+ // the segments are about to erased!
+ // this was a horrid bug, and proves the OO programming should be obeyed always!
+ m_focus = 0;
+ if( m_tree )
+ m_map.make( m_tree, true );
+ update();
+}
+
+void
+RadialMap::Widget::refresh( int filth )
+{
+ //TODO consider a more direct connection
+
+ if( !m_map.isNull() )
+ {
+ switch( filth )
+ {
+ case 1:
+ m_focus = 0;
+ if( m_tree )
+ m_map.make( m_tree, true ); //true means refresh only
+ break;
+
+ case 2:
+ m_map.aaPaint();
+ break;
+
+ case 3:
+ m_map.colorise(); //FALL THROUGH!
+ case 4:
+ m_map.paint();
+
+ default:
+ break;
+ }
+
+ update();
+ }
+}
+
+void
+RadialMap::Widget::zoomIn() //slot
+{
+ if( m_map.m_visibleDepth > MIN_RING_DEPTH )
+ {
+ m_focus = 0;
+ --m_map.m_visibleDepth;
+ if( m_tree )
+ m_map.make( m_tree );
+ Config::defaultRingDepth = m_map.m_visibleDepth;
+ update();
+ }
+}
+
+void
+RadialMap::Widget::zoomOut() //slot
+{
+ m_focus = 0;
+ ++m_map.m_visibleDepth;
+ if( m_tree )
+ m_map.make( m_tree );
+ if( m_map.m_visibleDepth > Config::defaultRingDepth )
+ Config::defaultRingDepth = m_map.m_visibleDepth;
+ update();
+}
+
+
+RadialMap::Segment::~Segment()
+{
+ if( isFake() )
+ delete m_file; //created by us in Builder::build()
+}
+
+#include "widget.moc"
diff --git a/src/app/DiskUsage/radialMap/widget.h b/src/app/DiskUsage/radialMap/widget.h
new file mode 100644
index 0000000..83c6baa
--- /dev/null
+++ b/src/app/DiskUsage/radialMap/widget.h
@@ -0,0 +1,111 @@
+//Author: Max Howell <max.howell@methylblue.com>, (C) 2004
+//Copyright: See COPYING file that comes with this distribution
+
+#ifndef WIDGET_H
+#define WIDGET_H
+
+#include <kurl.h>
+#include <tqtimer.h>
+#include "segmentTip.h"
+
+template <class T> class Chain;
+class Directory;
+class File;
+namespace TDEIO { class Job; }
+class KURL;
+
+namespace RadialMap
+{
+ class Segment;
+
+ class Map : public KPixmap
+ {
+ public:
+ Map();
+ ~Map();
+
+ void make( const Directory *, bool = false );
+ bool resize( const TQRect& );
+
+ bool isNull() const { return ( m_signature == 0 ); }
+ void invalidate( const bool );
+
+ friend class Builder;
+ friend class Widget;
+
+ private:
+ void paint( uint = 1 );
+ void aaPaint();
+ void colorise();
+ void setRingBreadth();
+
+ Chain<Segment> *m_signature;
+
+ TQRect m_rect;
+ uint m_ringBreadth; ///ring breadth
+ uint m_innerRadius; ///radius of inner circle
+ uint m_visibleDepth; ///visible level depth of system
+ TQString m_centerText;
+
+ uint MAP_2MARGIN;
+ };
+
+ class Widget : public TQWidget
+ {
+ TQ_OBJECT
+
+
+ public:
+ Widget( TQWidget* = 0, const char* = 0 );
+
+ TQString path() const;
+ KURL url( File const * const = 0 ) const;
+
+ bool isValid() const { return m_tree != 0; }
+
+ friend class Label; //FIXME badness
+
+ public slots:
+ void zoomIn();
+ void zoomOut();
+ void create( const Directory* );
+ void invalidate( const bool = true );
+ void refresh( int );
+
+ private slots:
+ void resizeTimeout();
+ void sendFakeMouseEvent();
+ void deleteJobFinished( TDEIO::Job* );
+ void createFromCache( const Directory* );
+
+ signals:
+ void activated( const KURL& );
+ void invalidated( const KURL& );
+ void created( const Directory* );
+ void mouseHover( const TQString& );
+
+ protected:
+ virtual void paintEvent( TQPaintEvent* );
+ virtual void resizeEvent( TQResizeEvent* );
+ virtual void mouseMoveEvent( TQMouseEvent* );
+ virtual void mousePressEvent( TQMouseEvent* );
+
+ protected:
+ const Segment *segmentAt( TQPoint& ) const; //FIXME const reference for a library others can use
+ const Segment *rootSegment() const { return m_rootSegment; } ///never == 0
+ const Segment *focusSegment() const { return m_focus; } ///0 == nothing in focus
+
+ private:
+ void paintExplodedLabels( TQPainter& ) const;
+
+ const Directory *m_tree;
+ const Segment *m_focus;
+ TQPoint m_offset;
+ TQTimer m_timer;
+ Map m_map;
+ SegmentTip m_tip;
+ Segment *m_rootSegment;
+ };
+}
+
+#endif
diff --git a/src/app/DiskUsage/radialMap/widgetEvents.cpp b/src/app/DiskUsage/radialMap/widgetEvents.cpp
new file mode 100644
index 0000000..0cc11af
--- /dev/null
+++ b/src/app/DiskUsage/radialMap/widgetEvents.cpp
@@ -0,0 +1,241 @@
+//Author: Max Howell <max.howell@methylblue.com>, (C) 2003-4
+//Copyright: See COPYING file that comes with this distribution
+
+#include "fileTree.h"
+#include "radialMap.h" //class Segment
+#include "widget.h"
+
+#include <kcursor.h> //::mouseMoveEvent()
+#include <kiconeffect.h> //::mousePressEvent()
+#include <kiconloader.h> //::mousePressEvent()
+#include <tdeio/job.h> //::mousePressEvent()
+#include <tdelocale.h>
+#include <tdemessagebox.h> //::mousePressEvent()
+#include <tdepopupmenu.h> //::mousePressEvent()
+#include <krun.h> //::mousePressEvent()
+#include <math.h> //::segmentAt()
+#include <tqapplication.h>//TQApplication::setOverrideCursor()
+#include <tqpainter.h>
+#include <tqtimer.h> //::resizeEvent()
+
+
+
+void
+RadialMap::Widget::resizeEvent( TQResizeEvent* )
+{
+ if( m_map.resize( rect() ) )
+ m_timer.start( 500, true ); //will cause signature to rebuild for new size
+
+ //always do these as they need to be initialised on creation
+ m_offset.rx() = (width() - m_map.width()) / 2;
+ m_offset.ry() = (height() - m_map.height()) / 2;
+}
+
+void
+RadialMap::Widget::paintEvent( TQPaintEvent* )
+{
+ //bltBit for some TQt setups will bitBlt _after_ the labels are painted. Which buggers things up!
+ //shame as bitBlt is faster, possibly TQt bug? Should report the bug? - seems to be race condition
+ //bitBlt( this, m_offset, &m_map );
+
+ TQPainter paint( this );
+
+ paint.drawPixmap( m_offset, m_map );
+
+ //vertical strips
+ if( m_map.width() < width() )
+ {
+ paint.eraseRect( 0, 0, m_offset.x(), height() );
+ paint.eraseRect( m_map.width() + m_offset.x(), 0, m_offset.x() + 1, height() );
+ }
+ //horizontal strips
+ if( m_map.height() < height() )
+ {
+ paint.eraseRect( 0, 0, width(), m_offset.y() );
+ paint.eraseRect( 0, m_map.height() + m_offset.y(), width(), m_offset.y() + 1 );
+ }
+
+ //exploded labels
+ if( !m_map.isNull() && !m_timer.isActive() )
+ paintExplodedLabels( paint );
+}
+
+const RadialMap::Segment*
+RadialMap::Widget::segmentAt( TQPoint &e ) const
+{
+ //determine which segment TQPoint e is above
+
+ e -= m_offset;
+
+ if( e.x() <= m_map.width() && e.y() <= m_map.height() )
+ {
+ //transform to cartesian coords
+ e.rx() -= m_map.width() / 2; //should be an int
+ e.ry() = m_map.height() / 2 - e.y();
+
+ double length = hypot( e.x(), e.y() );
+
+ if( length >= m_map.m_innerRadius ) //not hovering over inner circle
+ {
+ uint depth = ((int)length - m_map.m_innerRadius) / m_map.m_ringBreadth;
+
+ if( depth <= m_map.m_visibleDepth ) //**** do earlier since you can //** check not outside of range
+ {
+ //vector calculation, reduces to simple trigonometry
+ //cos angle = (aibi + ajbj) / albl
+ //ai = x, bi=1, aj=y, bj=0
+ //cos angle = x / (length)
+
+ uint a = (uint)(acos( (double)e.x() / length ) * 916.736); //916.7324722 = #radians in circle * 16
+
+ //acos only understands 0-180 degrees
+ if( e.y() < 0 ) a = 5760 - a;
+
+ #define ring (m_map.m_signature + depth)
+ for( ConstIterator<Segment> it = ring->constIterator(); it != ring->end(); ++it )
+ if( (*it)->intersects( a ) )
+ return *it;
+ #undef ring
+ }
+ }
+ else return m_rootSegment; //hovering over inner circle
+ }
+
+ return 0;
+}
+
+void
+RadialMap::Widget::mouseMoveEvent( TQMouseEvent *e )
+{
+ //set m_focus to what we hover over, update UI if it's a new segment
+
+ Segment const * const oldFocus = m_focus;
+ TQPoint p = e->pos();
+
+ m_focus = segmentAt( p ); //NOTE p is passed by non-const reference
+
+ if( m_focus && m_focus->file() != m_tree )
+ {
+ if( m_focus != oldFocus ) //if not same as last time
+ {
+ setCursor( KCursor::handCursor() );
+ m_tip.updateTip( m_focus->file(), m_tree );
+ emit mouseHover( m_focus->file()->fullPath() );
+
+ //repaint required to update labels now before transparency is generated
+ repaint( false );
+ }
+
+ m_tip.moveto( e->globalPos(), *this, ( p.y() < 0 ) ); //updates tooltip psuedo-tranparent background
+ }
+ else if( oldFocus && oldFocus->file() != m_tree )
+ {
+ unsetCursor();
+ m_tip.hide();
+ update();
+
+ emit mouseHover( TQString() );
+ }
+}
+
+void
+RadialMap::Widget::mousePressEvent( TQMouseEvent *e )
+{
+ //m_tip is hidden already by event filter
+ //m_focus is set correctly (I've been strict, I assure you it is correct!)
+
+ if( m_focus && !m_focus->isFake() )
+ {
+ const KURL url = Widget::url( m_focus->file() );
+ const bool isDir = m_focus->file()->isDir();
+
+ if( e->button() == TQt::RightButton )
+ {
+ TDEPopupMenu popup;
+ popup.insertTitle( m_focus->file()->fullPath( m_tree ) );
+
+ if( isDir )
+ {
+ popup.insertItem( SmallIconSet( "konqueror" ), i18n( "Open &Konqueror Here" ), 0 );
+ if( url.protocol() == "file" )
+ popup.insertItem( SmallIconSet( "konsole" ), i18n( "Open &Konsole Here" ), 1 );
+
+ if( m_focus->file() != m_tree )
+ {
+ popup.insertSeparator();
+ popup.insertItem( SmallIconSet( "viewmag" ), i18n( "&Center Map Here" ), 2 );
+ }
+ }
+ else popup.insertItem( SmallIconSet( "document-open" ), i18n( "&Open" ), 3 );
+
+ popup.insertSeparator();
+ popup.insertItem( SmallIconSet( "edit-delete" ), i18n( "&Delete" ), 4 );
+
+ switch( popup.exec( e->globalPos(), 1 ) ) {
+ case 0:
+ //KRun::runCommand will show an error message if there was trouble
+ KRun::runCommand( TQString( "kfmclient openURL '%1'" ).arg( url.url() ) );
+ break;
+
+ case 1:
+ KRun::runCommand( TQString( "konsole --workdir '%1'" ).arg( url.url() ) );
+ break;
+
+ case 2:
+ case 3:
+ goto sectionTwo;
+
+ case 4:
+ {
+ const KURL url = Widget::url( m_focus->file() );
+ const TQString message = ( m_focus->file()->isDir()
+ ? i18n( "<qt>The directory at <i>'%1'</i> will be <b>recursively</b> and <b>permanently</b> deleted!</qt>" )
+ : i18n( "<qt><i>'%1'</i> will be <b>permanently</b> deleted!</qt>" )).arg( url.prettyURL() );
+ const int userIntention = KMessageBox::warningContinueCancel( this, message, TQString(), KGuiItem( i18n("&Delete"), "edit-delete" ) );
+
+ if( userIntention == KMessageBox::Continue ) {
+ TDEIO::Job *job = TDEIO::del( url );
+ job->setWindow( this );
+ connect( job, TQ_SIGNAL(result( TDEIO::Job* )), TQ_SLOT(deleteJobFinished( TDEIO::Job* )) );
+ TQApplication::setOverrideCursor( KCursor::workingCursor() );
+ }
+ }
+
+ default:
+ //ensure m_focus is set for new mouse position
+ sendFakeMouseEvent();
+ }
+
+ } else {
+
+ sectionTwo:
+
+ const TQRect rect( e->x() - 20, e->y() - 20, 40, 40 );
+
+ m_tip.hide(); //user expects this
+
+ if( !isDir || e->button() == TQt::MidButton )
+ {
+ TDEIconEffect::visualActivate( this, rect );
+ new KRun( url, this, true ); //FIXME see above
+ }
+ else if( m_focus->file() != m_tree ) //is left mouse button
+ {
+ TDEIconEffect::visualActivate( this, rect );
+ emit activated( url ); //activate first, this will cause UI to prepare itself
+ if( m_focus )
+ createFromCache( (Directory *)m_focus->file() );
+ }
+ }
+ }
+}
+
+void
+RadialMap::Widget::deleteJobFinished( TDEIO::Job *job )
+{
+ TQApplication::restoreOverrideCursor();
+ if( !job->error() )
+ invalidate();
+ else
+ job->showErrorDialog( this );
+}