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.
kdirstat/kdirstat/ktreemapview.cpp

746 lines
17 KiB

/*
* File name: ktreemapview.cpp
* Summary: High level classes for KDirStat
* License: LGPL - See file COPYING.LIB for details.
* Author: Stefan Hundhammer <sh@suse.de>
*
* Updated: 2003-10-20
*/
#include <sys/stat.h>
#include <tqevent.h>
#include <tqregexp.h>
#include <kapp.h>
#include <tdeconfig.h>
#include <tdeglobal.h>
#include <tdelocale.h>
#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<KTreemapTile *> (*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<KTreemapTile *> (*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();
}
}
#include "ktreemapview.moc"
// EOF