//Author: Max Howell , (C) 2003-4 //Copyright: See COPYING file that comes with this distribution #include //make() #include //kdeColours #include //desaturate() #include //make() #include //make() & paint() #include //ctor #include //ctor #include #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 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 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(); int 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(cosra * radius); pos.ry() = cpos.y() - static_cast(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