/* * File name: ktreemapview.cpp * Summary: High level classes for KDirStat * License: LGPL - See file COPYING.LIB for details. * Author: Stefan Hundhammer * * Updated: 2003-10-20 */ #include #include #include #include #include #include #include #include "kdirtree.h" #include "ktreemapview.h" #include "ktreemaptile.h" using namespace KDirStat; #define UpdateMinSize 20 KTreemapView::KTreemapView( KDirTree * tree, TQWidget * parent, const TQSize & initialSize ) : TQCanvasView( parent ) , _tree( tree ) , _rootTile( 0 ) , _selectedTile( 0 ) , _selectionRect( 0 ) { // kdDebug() << k_funcinfo << endl; readConfig(); // Default values for light sources taken from Wiik / Wetering's paper // about "cushion treemaps". _lightX = 0.09759; _lightY = 0.19518; _lightZ = 0.9759; if ( _autoResize ) { setHScrollBarMode( AlwaysOff ); setVScrollBarMode( AlwaysOff ); } if ( initialSize.isValid() ) resize( initialSize ); if ( tree && tree->root() ) { if ( ! _rootTile ) { // The treemap might already be created indirectly by // rebuildTreemap() called from resizeEvent() triggered by resize() // above. If this is so, don't do it again. rebuildTreemap( tree->root() ); } } connect( this, TQT_SIGNAL( selectionChanged( KFileInfo * ) ), tree, TQT_SLOT ( selectItem ( KFileInfo * ) ) ); connect( tree, TQT_SIGNAL( selectionChanged( KFileInfo * ) ), this, TQT_SLOT ( selectTile ( KFileInfo * ) ) ); connect( tree, TQT_SIGNAL( deletingChild ( KFileInfo * ) ), this, TQT_SLOT ( deleteNotify ( KFileInfo * ) ) ); connect( tree, TQT_SIGNAL( childDeleted() ), this, TQT_SLOT ( rebuildTreemap() ) ); } KTreemapView::~KTreemapView() { } void KTreemapView::clear() { if ( canvas() ) deleteAllItems( canvas() ); _selectedTile = 0; _selectionRect = 0; _rootTile = 0; } void KTreemapView::deleteAllItems( TQCanvas * canvas ) { if ( ! canvas ) return; TQCanvasItemList all = canvas->allItems(); for ( TQCanvasItemList::Iterator it = all.begin(); it != all.end(); ++it ) delete *it; } void KTreemapView::readConfig() { TDEConfig * config = kapp->config(); config->setGroup( "Treemaps" ); _ambientLight = config->readNumEntry( "AmbientLight" , DefaultAmbientLight ); _heightScaleFactor = config->readDoubleNumEntry( "HeightScaleFactor" , DefaultHeightScaleFactor ); _autoResize = config->readBoolEntry( "AutoResize" , true ); _squarify = config->readBoolEntry( "Squarify" , true ); _doCushionShading = config->readBoolEntry( "CushionShading" , true ); _ensureContrast = config->readBoolEntry( "EnsureContrast" , true ); _forceCushionGrid = config->readBoolEntry( "ForceCushionGrid" , false ); _minTileSize = config->readNumEntry ( "MinTileSize" , DefaultMinTileSize ); _highlightColor = readColorEntry( config, "HighlightColor" , red ); _cushionGridColor = readColorEntry( config, "CushionGridColor" , TQColor( 0x80, 0x80, 0x80 ) ); _outlineColor = readColorEntry( config, "OutlineColor" , black ); _fileFillColor = readColorEntry( config, "FileFillColor" , TQColor( 0xde, 0x8d, 0x53 ) ); _dirFillColor = readColorEntry( config, "DirFillColor" , TQColor( 0x10, 0x7d, 0xb4 ) ); if ( _autoResize ) { setHScrollBarMode( AlwaysOff ); setVScrollBarMode( AlwaysOff ); } else { setHScrollBarMode( TQScrollView::Auto ); setVScrollBarMode( TQScrollView::Auto ); } } TQColor KTreemapView::readColorEntry( TDEConfig * config, const char * entryName, TQColor defaultColor ) { return config->readColorEntry( entryName, &defaultColor ); } KTreemapTile * KTreemapView::tileAt( TQPoint pos ) { KTreemapTile * tile = 0; TQCanvasItemList coll = canvas()->collisions( pos ); TQCanvasItemList::Iterator it = coll.begin(); while ( it != coll.end() && tile == 0 ) { tile = dynamic_cast (*it); ++it; } return tile; } void KTreemapView::contentsMousePressEvent( TQMouseEvent * event ) { // kdDebug() << k_funcinfo << endl; KTreemapTile * tile = tileAt( event->pos() ); switch ( event->button() ) { case Qt::LeftButton: selectTile( tile ); emit userActivity( 1 ); break; case Qt::MidButton: // Select clicked tile's parent, if available if ( _selectedTile && _selectedTile->rect().contains( event->pos() ) ) { if ( _selectedTile->parentTile() ) tile = _selectedTile->parentTile(); } // Intentionally handling the middle button like the left button if // the user clicked outside the (old) selected tile: Simply select // the clicked tile. This makes using this middle mouse button // intuitive: It can be used very much like the left mouse button, // but it has added functionality. Plus, it cycles back to the // clicked tile if the user has already clicked all the way up the // hierarchy (i.e. the topmost directory is highlighted). selectTile( tile ); emit userActivity( 1 ); break; case Qt::RightButton: if ( tile ) { if ( _selectedTile && _selectedTile->rect().contains( event->pos() ) ) { // If a directory (non-leaf tile) is already selected, // don't override this by emit contextMenu( _selectedTile, event->globalPos() ); } else { selectTile( tile ); emit contextMenu( tile, event->globalPos() ); } emit userActivity( 3 ); } break; default: // event->button() is an enum, so g++ complains // if there are unhandled cases. break; } } void KTreemapView::contentsMouseDoubleClickEvent( TQMouseEvent * event ) { // kdDebug() << k_funcinfo << endl; KTreemapTile * tile = tileAt( event->pos() ); switch ( event->button() ) { case Qt::LeftButton: if ( tile ) { selectTile( tile ); zoomIn(); emit userActivity( 5 ); } break; case Qt::MidButton: zoomOut(); emit userActivity( 5 ); break; case Qt::RightButton: // Double-clicking the right mouse button is pretty useless - the // first click opens the context menu: Single clicks are always // delivered first. Even if that would be caught by using timers, // it would still be very awkward to use: Click too slow, and // you'll get the context menu rather than what you really wanted - // then you'd have to get rid of the context menu first. break; default: // Prevent compiler complaints about missing enum values in switch break; } } void KTreemapView::zoomIn() { if ( ! _selectedTile || ! _rootTile ) return; KTreemapTile * newRootTile = _selectedTile; while ( newRootTile->parentTile() != _rootTile && newRootTile->parentTile() ) // This should never happen, but who knows? { newRootTile = newRootTile->parentTile(); } if ( newRootTile ) { KFileInfo * newRoot = newRootTile->orig(); if ( newRoot->isDir() || newRoot->isDotEntry() ) rebuildTreemap( newRoot ); } } void KTreemapView::zoomOut() { if ( _rootTile ) { KFileInfo * root = _rootTile->orig(); if ( root->parent() ) root = root->parent(); rebuildTreemap( root ); } } void KTreemapView::selectParent() { if ( _selectedTile && _selectedTile->parentTile() ) selectTile( _selectedTile->parentTile() ); } bool KTreemapView::canZoomIn() const { if ( ! _selectedTile || ! _rootTile ) return false; if ( _selectedTile == _rootTile ) return false; KTreemapTile * newRootTile = _selectedTile; while ( newRootTile->parentTile() != _rootTile && newRootTile->parentTile() ) // This should never happen, but who knows? { newRootTile = newRootTile->parentTile(); } if ( newRootTile ) { KFileInfo * newRoot = newRootTile->orig(); if ( newRoot->isDir() || newRoot->isDotEntry() ) return true; } return false; } bool KTreemapView::canZoomOut() const { if ( ! _rootTile || ! _tree->root() ) return false; return _rootTile->orig() != _tree->root(); } bool KTreemapView::canSelectParent() const { return _selectedTile && _selectedTile->parentTile(); } void KTreemapView::rebuildTreemap() { KFileInfo * root = 0; if ( ! _savedRootUrl.isEmpty() ) { // kdDebug() << "Restoring old treemap with root " << _savedRootUrl << endl; root = _tree->locate( _savedRootUrl, true ); // node, findDotEntries } if ( ! root ) root = _rootTile ? _rootTile->orig() : _tree->root(); rebuildTreemap( root, canvas()->size() ); _savedRootUrl = ""; } void KTreemapView::rebuildTreemap( KFileInfo * newRoot, const TQSize & newSz ) { // kdDebug() << k_funcinfo << endl; TQSize newSize = newSz; if ( newSz.isEmpty() ) newSize = visibleSize(); // Delete all old stuff. clear(); // Re-create a new canvas if ( ! canvas() ) { TQCanvas * canv = new TQCanvas( TQT_TQOBJECT(this) ); TQ_CHECK_PTR( canv ); setCanvas( canv ); } canvas()->resize( newSize.width(), newSize.height() ); if ( newSize.width() >= UpdateMinSize && newSize.height() >= UpdateMinSize ) { // The treemap contents is displayed if larger than a certain minimum // visible size. This is an easy way for the user to avoid // time-consuming delays when deleting a lot of files: Simply make the // treemap (sub-) window very small. // Fill the new canvas if ( newRoot ) { _rootTile = new KTreemapTile( this, // parentView 0, // parentTile newRoot, // orig TQRect( TQPoint( 0, 0), newSize ), KTreemapAuto ); } // Synchronize selection with the tree if ( _tree->selection() ) selectTile( _tree->selection() ); } else { // kdDebug() << "Too small - suppressing treemap contents" << endl; } emit treemapChanged(); } void KTreemapView::deleteNotify( KFileInfo * ) { if ( _rootTile ) { if ( _rootTile->orig() != _tree->root() ) { // If the user zoomed the treemap in, save the root's URL so the // current state can be restored upon the next rebuildTreemap() // call (which is triggered by the childDeleted() signal that the // tree emits after deleting is done). // // Intentionally using debugUrl() here rather than just url() so // the correct zoom can be restored even when a dot entry is the // current treemap root. _savedRootUrl = _rootTile->orig()->debugUrl(); } else { // A shortcut for the most common case: No zoom. Simply use the // tree's root for the next treemap rebuild. _savedRootUrl = ""; } } else { // Intentionally leaving _savedRootUrl alone: Otherwise multiple // deleteNotify() calls might cause a previously saved _savedRootUrl to // be unnecessarily deleted, thus the treemap couldn't be restored as // it was. } clear(); } void KTreemapView::resizeEvent( TQResizeEvent * event ) { TQCanvasView::resizeEvent( event ); if ( _autoResize ) { bool tooSmall = event->size().width() < UpdateMinSize || event->size().height() < UpdateMinSize; if ( tooSmall && _rootTile ) { // kdDebug() << "Suppressing treemap contents" << endl; rebuildTreemap( _rootTile->orig() ); } else if ( ! tooSmall && ! _rootTile ) { if ( _tree->root() ) { // kdDebug() << "Redisplaying suppressed treemap contents" << endl; rebuildTreemap( _tree->root() ); } } else if ( _rootTile ) { // kdDebug() << "Auto-resizing treemap" << endl; rebuildTreemap( _rootTile->orig() ); } } } void KTreemapView::selectTile( KTreemapTile * tile ) { // kdDebug() << k_funcinfo << endl; KTreemapTile * oldSelection = _selectedTile; _selectedTile = tile; // Handle selection (highlight) rectangle if ( _selectedTile ) { if ( ! _selectionRect ) _selectionRect = new KTreemapSelectionRect( canvas(), _highlightColor ); } if ( _selectionRect ) _selectionRect->highlight( _selectedTile ); canvas()->update(); if ( oldSelection != _selectedTile ) { emit selectionChanged( _selectedTile ? _selectedTile->orig() : 0 ); } } void KTreemapView::selectTile( KFileInfo * node ) { selectTile( findTile( node ) ); } KTreemapTile * KTreemapView::findTile( KFileInfo * node ) { if ( ! node ) return 0; TQCanvasItemList itemList = canvas()->allItems(); TQCanvasItemList::Iterator it = itemList.begin(); while ( it != itemList.end() ) { KTreemapTile * tile = dynamic_cast (*it); if ( tile && tile->orig() == node ) return tile; ++it; } return 0; } TQSize KTreemapView::visibleSize() { ScrollBarMode oldHMode = hScrollBarMode(); ScrollBarMode oldVMode = vScrollBarMode(); setHScrollBarMode( AlwaysOff ); setVScrollBarMode( AlwaysOff ); TQSize size = TQSize( TQCanvasView::visibleWidth(), TQCanvasView::visibleHeight() ); setHScrollBarMode( oldHMode ); setVScrollBarMode( oldVMode ); return size; } TQColor KTreemapView::tileColor( KFileInfo * file ) { if ( file ) { if ( file->isFile() ) { // Find the filename extension: Everything after the first '.' TQString ext = file->name().section( '.', 1 ); while ( ! ext.isEmpty() ) { TQString lowerExt = ext.lower(); // Try case sensitive comparisions first if ( ext == "~" ) return TQt::red; if ( ext == "bak" ) return TQt::red; if ( ext == "c" ) return TQt::blue; if ( ext == "cpp" ) return TQt::blue; if ( ext == "cc" ) return TQt::blue; if ( ext == "h" ) return TQt::blue; if ( ext == "hpp" ) return TQt::blue; if ( ext == "el" ) return TQt::blue; if ( ext == "o" ) return TQColor( 0xff, 0xa0, 0x00 ); if ( ext == "lo" ) return TQColor( 0xff, 0xa0, 0x00 ); if ( ext == "Po" ) return TQColor( 0xff, 0xa0, 0x00 ); if ( ext == "al" ) return TQColor( 0xff, 0xa0, 0x00 ); if ( ext == "moc.cpp" ) return TQColor( 0xff, 0xa0, 0x00 ); if ( ext == "moc.cc" ) return TQColor( 0xff, 0xa0, 0x00 ); if ( ext == "elc" ) return TQColor( 0xff, 0xa0, 0x00 ); if ( ext == "la" ) return TQColor( 0xff, 0xa0, 0x00 ); if ( ext == "a" ) return TQColor( 0xff, 0xa0, 0x00 ); if ( ext == "rpm" ) return TQColor( 0xff, 0xa0, 0x00 ); if ( lowerExt == "tar.bz2" ) return TQt::green; if ( lowerExt == "tar.gz" ) return TQt::green; if ( lowerExt == "tgz" ) return TQt::green; if ( lowerExt == "bz2" ) return TQt::green; if ( lowerExt == "bz" ) return TQt::green; if ( lowerExt == "gz" ) return TQt::green; if ( lowerExt == "html" ) return TQt::blue; if ( lowerExt == "htm" ) return TQt::blue; if ( lowerExt == "txt" ) return TQt::blue; if ( lowerExt == "doc" ) return TQt::blue; if ( lowerExt == "png" ) return TQt::cyan; if ( lowerExt == "jpg" ) return TQt::cyan; if ( lowerExt == "jpeg" ) return TQt::cyan; if ( lowerExt == "gif" ) return TQt::cyan; if ( lowerExt == "tif" ) return TQt::cyan; if ( lowerExt == "tiff" ) return TQt::cyan; if ( lowerExt == "bmp" ) return TQt::cyan; if ( lowerExt == "xpm" ) return TQt::cyan; if ( lowerExt == "tga" ) return TQt::cyan; if ( lowerExt == "wav" ) return TQt::yellow; if ( lowerExt == "mp3" ) return TQt::yellow; if ( lowerExt == "avi" ) return TQColor( 0xa0, 0xff, 0x00 ); if ( lowerExt == "mov" ) return TQColor( 0xa0, 0xff, 0x00 ); if ( lowerExt == "mpg" ) return TQColor( 0xa0, 0xff, 0x00 ); if ( lowerExt == "mpeg" ) return TQColor( 0xa0, 0xff, 0x00 ); if ( lowerExt == "pdf" ) return TQt::blue; if ( lowerExt == "ps" ) return TQt::cyan; // Some DOS/Windows types if ( lowerExt == "exe" ) return TQt::magenta; if ( lowerExt == "com" ) return TQt::magenta; if ( lowerExt == "dll" ) return TQColor( 0xff, 0xa0, 0x00 ); if ( lowerExt == "zip" ) return TQt::green; if ( lowerExt == "arj" ) return TQt::green; // No match so far? Try the next extension. Some files might have // more than one, e.g., "tar.bz2" - if there is no match for // "tar.bz2", there might be one for just "bz2". ext = ext.section( '.', 1 ); } // Shared libs if ( TQRegExp( "lib.*\\.so.*" ).exactMatch( file->name() ) ) return TQColor( 0xff, 0xa0, 0x00 ); // Very special, but common: Core dumps if ( file->name() == "core" ) return TQt::red; // Special case: Executables if ( ( file->mode() & S_IXUSR ) == S_IXUSR ) return TQt::magenta; } else // Directories { // TO DO return TQt::blue; } } return TQt::white; } KTreemapSelectionRect::KTreemapSelectionRect( TQCanvas * canvas, const TQColor & color ) : TQCanvasRectangle( canvas ) { setPen( TQPen( color, 2 ) ); setZ( 1e10 ); // Higher than everything else } void KTreemapSelectionRect::highlight( KTreemapTile * tile ) { if ( tile ) { TQRect tileRect = tile->rect(); move( tileRect.x(), tileRect.y() ); setSize( tileRect.width(), tileRect.height() ); if ( ! isVisible() ) show(); } else { if ( isVisible() ) hide(); } } // EOF