您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
kdirstat/kdirstat/kdirtreeview.cpp

1948 行
44 KiB

/*
* File name: kdirtreeview.cpp
* Summary: High level classes for KDirStat
* License: LGPL - See file COPYING.LIB for details.
* Author: Stefan Hundhammer <sh@suse.de>
*
* Updated: 2005-01-07
*/
#include <time.h>
#include <stdlib.h>
#include <tqtimer.h>
#include <tqcolor.h>
#include <tqheader.h>
#include <tqpopupmenu.h>
#include <kapp.h>
#include <tdelocale.h>
#include <tdeglobal.h>
#include <tdeglobalsettings.h>
#include <kicontheme.h>
#include <kiconloader.h>
#include "kdirtreeview.h"
#include "kdirtreeiterators.h"
#include "kpacman.h"
#define SEPARATE_READ_JOBS_COL 0
#define VERBOSE_PROGRESS_INFO 0
using namespace KDirStat;
KDirTreeView::KDirTreeView( TQWidget * parent )
: KDirTreeViewParentClass( parent )
{
_tree = 0;
_updateTimer = 0;
_selection = 0;
_openLevel = 1;
_doLazyClone = true;
_doPacManAnimation = false;
_updateInterval = 333; // millisec
_sortCol = -1;
for ( int i=0; i < DEBUG_COUNTERS; i++ )
_debugCount[i] = 0;
setDebugFunc( 1, "KDirTreeViewItem::init()" );
setDebugFunc( 2, "KDirTreeViewItem::updateSummary()" );
setDebugFunc( 3, "KDirTreeViewItem::deferredClone()" );
setDebugFunc( 4, "KDirTreeViewItem::compare()" );
setDebugFunc( 5, "KDirTreeViewItem::paintCell()" );
#if SEPARATE_READ_JOBS_COL
_readJobsCol = -1;
#endif
setRootIsDecorated( false );
int numCol = 0;
addColumn( i18n( "Name" ) ); _nameCol = numCol;
_iconCol = numCol++;
addColumn( i18n( "Subtree Percentage" ) ); _percentBarCol = numCol++;
addColumn( i18n( "Percentage" ) ); _percentNumCol = numCol++;
addColumn( i18n( "Subtree Total" ) ); _totalSizeCol = numCol++;
_workingStatusCol = _totalSizeCol;
addColumn( i18n( "Own Size" ) ); _ownSizeCol = numCol++;
addColumn( i18n( "Items" ) ); _totalItemsCol = numCol++;
addColumn( i18n( "Files" ) ); _totalFilesCol = numCol++;
addColumn( i18n( "Subdirs" ) ); _totalSubDirsCol = numCol++;
addColumn( i18n( "Last Change" ) ); _latestMtimeCol = numCol++;
#if ! SEPARATE_READ_JOBS_COL
_readJobsCol = _percentBarCol;
#endif
setColumnAlignment ( _totalSizeCol, AlignRight );
setColumnAlignment ( _percentNumCol, AlignRight );
setColumnAlignment ( _ownSizeCol, AlignRight );
setColumnAlignment ( _totalItemsCol, AlignRight );
setColumnAlignment ( _totalFilesCol, AlignRight );
setColumnAlignment ( _totalSubDirsCol, AlignRight );
setColumnAlignment ( _readJobsCol, AlignRight );
setSorting( _totalSizeCol );
#define loadIcon(ICON) TDEGlobal::iconLoader()->loadIcon( (ICON), TDEIcon::Small )
_openDirIcon = loadIcon( "folder_open" );
_closedDirIcon = loadIcon( "folder" );
_openDotEntryIcon = loadIcon( "folder_orange_open");
_closedDotEntryIcon = loadIcon( "folder_orange" );
_unreadableDirIcon = loadIcon( "folder_locked" );
_mountPointIcon = loadIcon( "drive-harddisk-mounted" );
_fileIcon = loadIcon( "mime_empty" );
_symLinkIcon = loadIcon( "symlink" ); // The KDE standard link icon is ugly!
_blockDevIcon = loadIcon( "blockdevice" );
_charDevIcon = loadIcon( "chardevice" );
_fifoIcon = loadIcon( "socket" );
_stopIcon = loadIcon( "process-stop" );
_readyIcon = TQPixmap();
#undef loadIcon
setDefaultFillColors();
readConfig();
ensureContrast();
connect( kapp, TQ_SIGNAL( tdedisplayPaletteChanged() ),
this, TQ_SLOT ( paletteChanged() ) );
connect( this, TQ_SIGNAL( selectionChanged ( TQListViewItem * ) ),
this, TQ_SLOT ( selectItem ( TQListViewItem * ) ) );
connect( this, TQ_SIGNAL( rightButtonPressed ( TQListViewItem *, const TQPoint &, int ) ),
this, TQ_SLOT ( popupContextMenu ( TQListViewItem *, const TQPoint &, int ) ) );
connect( header(), TQ_SIGNAL( sizeChange ( int, int, int ) ),
this, TQ_SLOT ( columnResized( int, int, int ) ) );
_contextInfo = new TQPopupMenu;
_idContextInfo = _contextInfo->insertItem ( "dummy" );
}
KDirTreeView::~KDirTreeView()
{
if ( _tree )
delete _tree;
/*
* Don't delete _updateTimer here, it's already automatically deleted by TQt!
* (Since it's derived from TQObject and has a TQObject parent).
*/
}
void
KDirTreeView::setDebugFunc( int i, const TQString & functionName )
{
if ( i > 0 && i < DEBUG_COUNTERS )
_debugFunc[i] = functionName;
}
void
KDirTreeView::incDebugCount( int i )
{
if ( i > 0 && i < DEBUG_COUNTERS )
_debugCount[i]++;
}
void
KDirTreeView::busyDisplay()
{
#if SEPARATE_READ_JOBS_COL
if ( _readJobsCol < 0 )
{
_readJobsCol = header()->count();
addColumn( i18n( "Read Jobs" ) );
setColumnAlignment( _readJobsCol, AlignRight );
}
#else
_readJobsCol = _percentBarCol;
#endif
}
void
KDirTreeView::idleDisplay()
{
#if SEPARATE_READ_JOBS_COL
if ( _readJobsCol >= 0 )
{
removeColumn( _readJobsCol );
}
#else
if ( _sortCol == _readJobsCol && _sortCol >= 0 )
{
// A pathological case: The user requested sorting by read jobs, and
// now that everything is read, the items are still in that sort order.
// Not only is that sort order now useless (since all read jobs are
// done), it is contrary to the (now changed) semantics of this
// column. Calling TQListView::sort() might do the trick, but we can
// never know just how clever that TQListView widget tries to be and
// maybe avoid another sorting by the same column - so let's use the
// easy way out and sort by another column that has the same sorting
// semantics like the percentage bar column (that had doubled as the
// read job column while reading) now has.
setSorting( _percentNumCol );
}
#endif
_readJobsCol = -1;
}
void
KDirTreeView::openURL( KURL url )
{
// Clean up any old leftovers
clear();
_currentDir = "";
if ( _tree )
delete _tree;
// Create new (empty) dir tree
_tree = new KDirTree();
// Connect signals
connect( _tree, TQ_SIGNAL( progressInfo ( const TQString & ) ),
this, TQ_SLOT ( sendProgressInfo( const TQString & ) ) );
connect( _tree, TQ_SIGNAL( childAdded( KFileInfo * ) ),
this, TQ_SLOT ( addChild ( KFileInfo * ) ) );
connect( _tree, TQ_SIGNAL( deletingChild( KFileInfo * ) ),
this, TQ_SLOT ( deleteChild ( KFileInfo * ) ) );
connect( _tree, TQ_SIGNAL( startingReading() ),
this, TQ_SLOT ( prepareReading() ) );
connect( _tree, TQ_SIGNAL( finished() ),
this, TQ_SLOT ( slotFinished() ) );
connect( _tree, TQ_SIGNAL( aborted() ),
this, TQ_SLOT ( slotAborted() ) );
connect( _tree, TQ_SIGNAL( finalizeLocal( KDirInfo * ) ),
this, TQ_SLOT ( finalizeLocal( KDirInfo * ) ) );
connect( this, TQ_SIGNAL( selectionChanged( KFileInfo * ) ),
_tree, TQ_SLOT ( selectItem ( KFileInfo * ) ) );
connect( _tree, TQ_SIGNAL( selectionChanged( KFileInfo * ) ),
this, TQ_SLOT ( selectItem ( KFileInfo * ) ) );
// Implicitly calling prepareReading() via the tree's startingReading() signal
_tree->startReading( url );
logActivity( 30 );
}
void
KDirTreeView::prepareReading()
{
// Prepare cyclic update
if ( _updateTimer )
delete _updateTimer;
_updateTimer = new TQTimer( this );
if ( _updateTimer )
{
_updateTimer->changeInterval( _updateInterval );
connect( _updateTimer, TQ_SIGNAL( timeout() ),
this, TQ_SLOT ( updateSummary() ) );
connect( _updateTimer, TQ_SIGNAL( timeout() ),
this, TQ_SLOT ( sendProgressInfo() ) );
}
// Change display to busy state
setSorting( _totalSizeCol );
busyDisplay();
emit startingReading();
// Actually do something
_stopWatch.start();
}
void
KDirTreeView::refreshAll()
{
if ( _tree && _tree->root() )
{
clear();
// Implicitly calling prepareReading() via the tree's startingReading() signal
_tree->refresh( 0 );
}
}
void
KDirTreeView::refreshSelected()
{
if ( _tree && _tree->root() && _selection )
{
// Implicitly calling prepareReading() via the tree's startingReading() signal
_tree->refresh( _selection->orig() );
}
logActivity( 10 );
}
void
KDirTreeView::abortReading()
{
if ( _tree )
_tree->abortReading();
}
void
KDirTreeView::clear()
{
clearSelection();
KDirTreeViewParentClass::clear();
for ( int i=0; i < DEBUG_COUNTERS; i++ )
_debugCount[i] = 0;
}
void
KDirTreeView::addChild( KFileInfo *newChild )
{
if ( newChild->parent() )
{
KDirTreeViewItem *cloneParent = locate( newChild->parent(),
_doLazyClone, // lazy
true ); // doClone
if ( cloneParent )
{
if ( isOpen( cloneParent ) || ! _doLazyClone )
{
// kdDebug() << "Immediately cloning " << newChild << endl;
new KDirTreeViewItem( this, cloneParent, newChild );
}
}
else // Error
{
if ( ! _doLazyClone )
{
kdError() << k_funcinfo << "Can't find parent view item for "
<< newChild << endl;
}
}
}
else // No parent - top level item
{
// kdDebug() << "Immediately top level cloning " << newChild << endl;
new KDirTreeViewItem( this, newChild );
}
}
void
KDirTreeView::deleteChild( KFileInfo *child )
{
KDirTreeViewItem *clone = locate( child,
false, // lazy
false ); // doClone
KDirTreeViewItem *nextSelection = 0;
if ( clone )
{
if ( clone == _selection )
{
/**
* The selected item is about to be deleted. Select some other item
* so there is still something selected: Preferably the next item
* or the parent if there is no next. This cannot be done from
* outside because the order of items is not known to the outside;
* it might appear very random if the next item in the KFileInfo
* list would be selected. The order of that list is definitely
* different than the order of this view - which is what the user
* sees. So let's give the user a reasonable next selection so he
* can continue working without having to explicitly select another
* item.
*
* This is very useful if the user just activated a cleanup action
* that deleted an item: It makes sense to implicitly select the
* next item so he can clean up many items in a row.
**/
nextSelection = clone->next() ? clone->next() : clone->parent();
// kdDebug() << k_funcinfo << " Next selection: " << nextSelection << endl;
}
KDirTreeViewItem *parent = clone->parent();
delete clone;
while ( parent )
{
parent->updateSummary();
parent = parent->parent();
}
if ( nextSelection )
selectItem( nextSelection );
}
}
void
KDirTreeView::updateSummary()
{
KDirTreeViewItem *child = firstChild();
while ( child )
{
child->updateSummary();
child = child->next();
}
}
void
KDirTreeView::slotFinished()
{
emit progressInfo( i18n( "Finished. Elapsed time: %1" )
.arg( formatTime( _stopWatch.elapsed(), true ) ) );
if ( _updateTimer )
{
delete _updateTimer;
_updateTimer = 0;
}
idleDisplay();
updateSummary();
logActivity( 30 );
#if 0
for ( int i=0; i < DEBUG_COUNTERS; i++ )
{
kdDebug() << "Debug counter #" << i << ": " << _debugCount[i]
<< "\t" << _debugFunc[i]
<< endl;
}
kdDebug() << endl;
#endif
emit finished();
}
void
KDirTreeView::slotAborted()
{
emit progressInfo( i18n( "Aborted. Elapsed time: %1" )
.arg( formatTime( _stopWatch.elapsed(), true ) ) );
if ( _updateTimer )
{
delete _updateTimer;
_updateTimer = 0;
}
idleDisplay();
updateSummary();
emit aborted();
}
void
KDirTreeView::finalizeLocal( KDirInfo *dir )
{
if ( dir )
{
KDirTreeViewItem *clone = locate( dir,
false, // lazy
false ); // doClone
if ( clone )
clone->finalizeLocal();
}
}
void
KDirTreeView::sendProgressInfo( const TQString & newCurrentDir )
{
_currentDir = newCurrentDir;
#if VERBOSE_PROGRESS_INFO
emit progressInfo( i18n( "Elapsed time: %1 reading directory %2" )
.arg( formatTime( _stopWatch.elapsed() ) )
.arg( _currentDir ) );
#else
emit progressInfo( i18n( "Elapsed time: %1" )
.arg( formatTime( _stopWatch.elapsed() ) ) );
#endif
}
KDirTreeViewItem *
KDirTreeView::locate( KFileInfo *wanted, bool lazy, bool doClone )
{
KDirTreeViewItem *child = firstChild();
while ( child )
{
KDirTreeViewItem *wantedChild = child->locate( wanted, lazy, doClone, 0 );
if ( wantedChild )
return wantedChild;
else
child = child->next();
}
return 0;
}
int
KDirTreeView::openCount()
{
int count = 0;
KDirTreeViewItem *child = firstChild();
while ( child )
{
count += child->openCount();
child = child->next();
}
return count;
}
void
KDirTreeView::selectItem( TQListViewItem *listViewItem )
{
_selection = dynamic_cast<KDirTreeViewItem *>( listViewItem );
if ( _selection )
{
// kdDebug() << k_funcinfo << " Selecting item " << _selection << endl;
setSelected( _selection, true );
}
else
{
// kdDebug() << k_funcinfo << " Clearing selection" << endl;
clearSelection();
}
emit selectionChanged( _selection );
emit selectionChanged( _selection ? _selection->orig() : (KFileInfo *) 0 );
}
void
KDirTreeView::selectItem( KFileInfo *newSelection )
{
// Short-circuit for the most common case: The signal has been triggered by
// this view, and the KDirTree has sent it right back.
if ( _selection && _selection->orig() == newSelection )
return;
if ( ! newSelection )
clearSelection();
else
{
_selection = locate( newSelection,
false, // lazy
true ); // doClone
if ( _selection )
{
closeAllExcept( _selection );
_selection->setOpen( false );
ensureItemVisible( _selection );
emit selectionChanged( _selection );
setSelected( _selection, true );
}
else
kdError() << "Couldn't clone item " << newSelection << endl;
}
}
void
KDirTreeView::clearSelection()
{
// kdDebug() << k_funcinfo << endl;
_selection = 0;
TQListView::clearSelection();
emit selectionChanged( (KDirTreeViewItem *) 0 );
emit selectionChanged( (KFileInfo *) 0 );
}
void
KDirTreeView::closeAllExcept( KDirTreeViewItem *except )
{
if ( ! except )
{
kdError() << k_funcinfo << ": NULL pointer passed" << endl;
return;
}
except->closeAllExceptThis();
}
const TQColor &
KDirTreeView::fillColor( int level ) const
{
if ( level < 0 )
{
level = 0;
kdWarning() << k_funcinfo << "Invalid argument: " << level << endl;
}
return _fillColor [ level % _usedFillColors ];
}
const TQColor &
KDirTreeView::rawFillColor( int level ) const
{
if ( level < 0 || level > KDirTreeViewMaxFillColor )
{
level = 0;
kdWarning() << k_funcinfo << "Invalid argument: " << level << endl;
}
return _fillColor [ level % KDirTreeViewMaxFillColor ];
}
void
KDirTreeView::setFillColor( int level,
const TQColor & color )
{
if ( level >= 0 && level < KDirTreeViewMaxFillColor )
_fillColor[ level ] = color;
}
void
KDirTreeView::setUsedFillColors( int usedFillColors )
{
if ( usedFillColors < 1 )
{
kdWarning() << k_funcinfo << "Invalid argument: "<< usedFillColors << endl;
usedFillColors = 1;
}
else if ( usedFillColors >= KDirTreeViewMaxFillColor )
{
kdWarning() << k_funcinfo << "Invalid argument: "<< usedFillColors
<< " (max: " << KDirTreeViewMaxFillColor-1 << ")" << endl;
usedFillColors = KDirTreeViewMaxFillColor-1;
}
_usedFillColors = usedFillColors;
}
void
KDirTreeView::setDefaultFillColors()
{
int i;
for ( i=0; i < KDirTreeViewMaxFillColor; i++ )
{
_fillColor[i] = blue;
}
i = 0;
_usedFillColors = 4;
setFillColor ( i++, TQColor ( 0, 0, 255 ) );
setFillColor ( i++, TQColor ( 128, 0, 128 ) );
setFillColor ( i++, TQColor ( 231, 147, 43 ) );
setFillColor ( i++, TQColor ( 4, 113, 0 ) );
setFillColor ( i++, TQColor ( 176, 0, 0 ) );
setFillColor ( i++, TQColor ( 204, 187, 0 ) );
setFillColor ( i++, TQColor ( 162, 98, 30 ) );
setFillColor ( i++, TQColor ( 0, 148, 146 ) );
setFillColor ( i++, TQColor ( 217, 94, 0 ) );
setFillColor ( i++, TQColor ( 0, 194, 65 ) );
setFillColor ( i++, TQColor ( 194, 108, 187 ) );
setFillColor ( i++, TQColor ( 0, 179, 255 ) );
}
void
KDirTreeView::setTreeBackground( const TQColor &color )
{
_treeBackground = color;
_percentageBarBackground = _treeBackground.dark( 115 );
TQPalette pal = kapp->palette();
pal.setBrush( TQColorGroup::Base, _treeBackground );
setPalette( pal );
}
void
KDirTreeView::ensureContrast()
{
if ( colorGroup().base() == white ||
colorGroup().base() == black )
{
setTreeBackground( colorGroup().midlight() );
}
else
{
setTreeBackground( colorGroup().base() );
}
}
void
KDirTreeView::paletteChanged()
{
setTreeBackground( TDEGlobalSettings::baseColor() );
ensureContrast();
}
void
KDirTreeView::popupContextMenu( TQListViewItem * listViewItem,
const TQPoint & pos,
int column )
{
KDirTreeViewItem *item = (KDirTreeViewItem *) listViewItem;
if ( ! item )
return;
if ( column == _nameCol ||
column == _percentBarCol ||
column == _percentNumCol )
{
// Make the item the context menu is popping up over the current
// selection - all user operations refer to the current selection.
// Just right-clicking on an item does not make it the current
// item!
selectItem( item );
// Let somebody from outside pop up the context menu, if so desired.
emit contextMenu( item, pos );
}
// If the column is one with a large size in kB/MB/GB, open a
// info popup with the exact number.
if ( column == _ownSizeCol && ! item->orig()->isDotEntry() )
{
KFileInfo * orig = item->orig();
if ( orig->isSparseFile() || ( orig->links() > 1 && orig->isFile() ) )
{
TQString text;
if ( orig->isSparseFile() )
{
text = i18n( "Sparse file: %1 (%2 Bytes) -- allocated: %3 (%4 Bytes)" )
.arg( formatSize( orig->byteSize() ) )
.arg( formatSizeLong( orig->byteSize() ) )
.arg( formatSize( orig->allocatedSize() ) )
.arg( formatSizeLong( orig->allocatedSize() ) );
}
else
{
text = i18n( "%1 (%2 Bytes) with %3 hard links => effective size: %4 (%5 Bytes)" )
.arg( formatSize( orig->byteSize() ) )
.arg( formatSizeLong( orig->byteSize() ) )
.arg( orig->links() )
.arg( formatSize( orig->size() ) )
.arg( formatSizeLong( orig->size() ) );
}
popupContextInfo( pos, text );
}
else
{
popupContextSizeInfo( pos, orig->size() );
}
}
if ( column == _totalSizeCol &&
( item->orig()->isDir() || item->orig()->isDotEntry() ) )
{
popupContextSizeInfo( pos, item->orig()->totalSize() );
}
// Show alternate time / date format in time / date related columns.
if ( column == _latestMtimeCol )
{
popupContextInfo( pos, formatTimeDate( item->orig()->latestMtime() ) );
}
logActivity( 3 );
}
void
KDirTreeView::popupContextSizeInfo( const TQPoint & pos,
KFileSize size )
{
TQString info;
if ( size < 1024 )
{
info = formatSizeLong( size ) + " " + i18n( "Bytes" );
}
else
{
info = i18n( "%1 (%2 Bytes)" )
.arg( formatSize( size ) )
.arg( formatSizeLong( size ) );
}
popupContextInfo( pos, info );
}
void
KDirTreeView::popupContextInfo( const TQPoint & pos,
const TQString & info )
{
_contextInfo->changeItem( info, _idContextInfo );
_contextInfo->popup( pos );
}
void
KDirTreeView::readConfig()
{
TDEConfig *config = kapp->config();
TDEConfigGroupSaver saver( config, "Tree Colors" );
_usedFillColors = config->readNumEntry( "usedFillColors", -1 );
if ( _usedFillColors < 0 )
{
/*
* No 'usedFillColors' in the config file? Better forget that
* file and use default values. Otherwise, all colors would very
* likely become blue - the default color.
*/
setDefaultFillColors();
}
else
{
// Read the rest of the 'Tree Colors' section
TQColor defaultColor( blue );
for ( int i=0; i < KDirTreeViewMaxFillColor; i++ )
{
TQString name;
name.sprintf( "fillColor_%02d", i );
_fillColor [i] = config->readColorEntry( name, &defaultColor );
}
}
if ( isVisible() )
triggerUpdate();
}
void
KDirTreeView::saveConfig() const
{
TDEConfig *config = kapp->config();
TDEConfigGroupSaver saver( config, "Tree Colors" );
config->writeEntry( "usedFillColors", _usedFillColors );
for ( int i=0; i < KDirTreeViewMaxFillColor; i++ )
{
TQString name;
name.sprintf( "fillColor_%02d", i );
config->writeEntry ( name, _fillColor [i] );
}
}
void
KDirTreeView::setSorting( int column, bool increasing )
{
_sortCol = column;
TQListView::setSorting( column, increasing );
}
void
KDirTreeView::logActivity( int points )
{
emit userActivity( points );
}
void
KDirTreeView::columnResized( int column, int oldSize, int newSize )
{
NOT_USED( oldSize );
NOT_USED( newSize );
if ( column == _percentBarCol )
triggerUpdate();
}
void
KDirTreeView::sendMailToOwner()
{
if ( ! _selection )
{
kdError() << k_funcinfo << "Nothing selected!" << endl;
return;
}
TQString owner = KAnyDirReadJob::owner( fixedUrl( _selection->orig()->url() ) );
TQString subject = i18n( "Disk Usage" );
TQString body =
i18n("Please check your disk usage and clean up if you can. Thank you." )
+ "\n\n"
+ _selection->asciiDump()
+ "\n\n"
+ i18n( "Disk usage report generated by KDirStat" )
+ "\nhttp://kdirstat.sourceforge.net/";
// kdDebug() << "owner: " << owner << endl;
// kdDebug() << "subject: " << subject << endl;
// kdDebug() << "body:\n" << body << endl;
KURL mail;
mail.setProtocol( "mailto" );
mail.setPath( owner );
mail.setQuery( "?subject=" + KURL::encode_string( subject ) +
"&body=" + KURL::encode_string( body ) );
// TODO: Check for maximum command line length.
//
// The hard part with this is how to get this from all that 'autoconf'
// stuff into 'config.h' or some other include file without hardcoding
// anything - this is too system dependent.
kapp->invokeMailer( mail );
logActivity( 10 );
}
KDirTreeViewItem::KDirTreeViewItem( KDirTreeView * view,
KFileInfo * orig )
: TQListViewItem( view )
{
init( view, 0, orig );
}
KDirTreeViewItem::KDirTreeViewItem( KDirTreeView * view,
KDirTreeViewItem * parent,
KFileInfo * orig )
: TQListViewItem( parent )
{
TQ_CHECK_PTR( parent );
init( view, parent, orig );
}
void
KDirTreeViewItem::init( KDirTreeView * view,
KDirTreeViewItem * parent,
KFileInfo * orig )
{
_view = view;
_parent = parent;
_orig = orig;
_percent = 0.0;
_pacMan = 0;
_openCount = 0;
// _view->incDebugCount(1);
// kdDebug() << "new KDirTreeViewItem for " << orig << endl;
if ( _orig->isDotEntry() )
{
setText( view->nameCol(), i18n( "<Files>" ) );
TQListViewItem::setOpen ( false );
}
else
{
setText( view->nameCol(), _orig->name() );
if ( ! _orig->isDevice() )
{
TQString text;
if ( _orig->isFile() && ( _orig->links() > 1 ) ) // Regular file with multiple links
{
if ( _orig->isSparseFile() )
{
text = i18n( "%1 / %2 Links (allocated: %3)" )
.arg( formatSize( _orig->byteSize() ) )
.arg( formatSize( _orig->links() ) )
.arg( formatSize( _orig->allocatedSize() ) );
}
else
{
text = i18n( "%1 / %2 Links" )
.arg( formatSize( _orig->byteSize() ) )
.arg( _orig->links() );
}
}
else // No multiple links or no regular file
{
if ( _orig->isSparseFile() )
{
text = i18n( "%1 (allocated: %2)" )
.arg( formatSize( _orig->byteSize() ) )
.arg( formatSize( _orig->allocatedSize() ) );
}
else
{
text = formatSize( _orig->size() );
}
}
setText( view->ownSizeCol(), text );
}
TQListViewItem::setOpen ( _orig->treeLevel() < _view->openLevel() );
/*
* Don't use KDirTreeViewItem::setOpen() here since this might call
* KDirTreeViewItem::deferredClone() which would confuse bookkeeping
* with addChild() signals that might arrive, too - resulting in double
* dot entries.
*/
}
if ( _view->doLazyClone() &&
( _orig->isDir() || _orig->isDotEntry() ) )
{
/*
* Determine whether or not this item can be opened.
*
* Normally, TQt handles this very well, but when lazy cloning is in
* effect, TQt cannot know whether or not there are children - they may
* only be in the original tree until the user tries to open this
* item. So let's assume there may be children as long as the directory
* is still being read.
*/
if ( _orig->readState() == KDirQueued ||
_orig->readState() == KDirReading )
{
setExpandable( true );
}
else // KDirFinished, KDirError, KDirAborted
{
setExpandable( _orig->hasChildren() );
}
}
if ( ! parent || parent->isOpen() )
{
setIcon();
}
_openCount = isOpen() ? 1 : 0;
}
KDirTreeViewItem::~KDirTreeViewItem()
{
if ( _pacMan )
delete _pacMan;
if ( this == _view->selection() )
_view->clearSelection();
}
void
KDirTreeViewItem::setIcon()
{
TQPixmap icon;
if ( _orig->isDotEntry() )
{
icon = isOpen() ? _view->openDotEntryIcon() : _view->closedDotEntryIcon();
}
else if ( _orig->isDir() )
{
if ( _orig->readState() == KDirAborted ) icon = _view->stopIcon();
else if ( _orig->readState() == KDirError )
{
icon = _view->unreadableDirIcon();
setExpandable( false );
}
else
{
if ( _orig->isMountPoint() )
{
icon = _view->mountPointIcon();
}
else
{
icon = isOpen() ? _view->openDirIcon() : _view->closedDirIcon();
}
}
}
else if ( _orig->isFile() ) icon = _view->fileIcon();
else if ( _orig->isSymLink() ) icon = _view->symLinkIcon();
else if ( _orig->isBlockDevice() ) icon = _view->blockDevIcon();
else if ( _orig->isCharDevice() ) icon = _view->charDevIcon();
else if ( _orig->isSpecial() ) icon = _view->fifoIcon();
setPixmap( _view->iconCol(), icon );
}
void
KDirTreeViewItem::updateSummary()
{
// _view->incDebugCount(2);
// Update this item
setIcon();
setText( _view->latestMtimeCol(), " " + localeTimeDate( _orig->latestMtime() ) );
if ( _orig->isDir() || _orig->isDotEntry() )
{
TQString prefix = " ";
if ( _orig->readState() == KDirAborted )
prefix = " >";
setText( _view->totalSizeCol(), prefix + formatSize( _orig->totalSize() ) );
setText( _view->totalItemsCol(), prefix + formatCount( _orig->totalItems() ) );
setText( _view->totalFilesCol(), prefix + formatCount( _orig->totalFiles() ) );
if ( _view->readJobsCol() >= 0 )
{
#if SEPARATE_READ_JOBS_COL
setText( _view->readJobsCol(), " " + formatCount( _orig->pendingReadJobs(), true ) );
#else
int jobs = _orig->pendingReadJobs();
TQString text = "";
if ( jobs > 0 )
text = i18n( "[%1 Read Jobs]" ).arg( formatCount( _orig->pendingReadJobs(), true ) );
setText( _view->readJobsCol(), text );
#endif
}
}
if ( _orig->isDir() )
{
setText( _view->totalSubDirsCol(), " " + formatCount( _orig->totalSubDirs() ) );
}
// Calculate and display percentage
if ( _orig->parent() && // only if there is a parent as calculation base
_orig->parent()->pendingReadJobs() < 1 && // not before subtree is finished reading
_orig->parent()->totalSize() > 0 ) // avoid division by zero
{
_percent = ( 100.0 * _orig->totalSize() ) / (float) _orig->parent()->totalSize();
setText( _view->percentNumCol(), formatPercent ( _percent ) );
}
else
{
_percent = 0.0;
setText( _view->percentNumCol(), "" );
}
if ( _view->doPacManAnimation() && _orig->isBusy() )
{
if ( ! _pacMan )
_pacMan = new KPacManAnimation( _view, height()-4, true );
repaint();
}
if ( ! isOpen() ) // Lazy update: Nobody can see the children
return; // -> don't update them.
// Update all children
KDirTreeViewItem *child = firstChild();
while ( child )
{
child->updateSummary();
child = child->next();
}
}
KDirTreeViewItem *
KDirTreeViewItem::locate( KFileInfo * wanted,
bool lazy,
bool doClone,
int level )
{
if ( lazy && ! isOpen() )
{
/*
* In "lazy" mode, we don't bother searching all the children of this
* item if they are not visible (i.e. the branch is open) anyway. In
* this case, cloning that branch is deferred until the branch is
* actually opened - which in most cases will never happen anyway (most
* users don't manually open each and every subtree). If and when it
* happens, we'll probably be fast enough bringing the view tree in
* sync with the original tree since opening a branch requires manual
* interaction which is a whole lot slower than copying a couple of
* objects.
*
* Note that this mode is _independent_ of lazy cloning in general: The
* caller explicitly specifies if he wants to locate an item at all
* cost, even if that means deferred cloning children whose creation
* has been delayed until now.
*/
// kdDebug() << "Too lazy to search for " << wanted << " from " << this << endl;
return 0;
}
if ( _orig == wanted )
{
return this;
}
if ( level < 0 )
level = _orig->treeLevel();
if ( wanted->urlPart( level ) == _orig->name() )
{
// Search all children
KDirTreeViewItem *child = firstChild();
if ( ! child && _orig->hasChildren() && doClone )
{
// kdDebug() << "Deferred cloning " << this << " for children search of " << wanted << endl;
deferredClone();
child = firstChild();
}
while ( child )
{
KDirTreeViewItem *foundChild = child->locate( wanted, lazy, doClone, level+1 );
if ( foundChild )
return foundChild;
else
child = child->next();
}
}
return 0;
}
void
KDirTreeViewItem::deferredClone()
{
// _view->incDebugCount(3);
if ( ! _orig->hasChildren() )
{
// kdDebug() << k_funcinfo << "Oops, no children - sorry for bothering you!" << endl;
setExpandable( false );
return;
}
// Clone all normal children
int level = _orig->treeLevel();
bool startingClean = ! firstChild();
KFileInfo *origChild = _orig->firstChild();
while ( origChild )
{
if ( startingClean ||
! locate( origChild,
false, // lazy
true, // doClone
level ) )
{
// kdDebug() << "Deferred cloning " << origChild << endl;
new KDirTreeViewItem( _view, this, origChild );
}
origChild = origChild->next();
}
// Clone the dot entry
if ( _orig->dotEntry() &&
( startingClean ||
! locate( _orig->dotEntry(),
false, // lazy
true, // doClone
level )
)
)
{
// kdDebug() << "Deferred cloning dot entry for " << _orig << endl;
new KDirTreeViewItem( _view, this, _orig->dotEntry() );
}
}
void
KDirTreeViewItem::finalizeLocal()
{
// kdDebug() << k_funcinfo << _orig << endl;
cleanupDotEntries();
if ( _orig->totalItems() == 0 )
// _orig->hasChildren() would give a wrong answer here since it counts
// the dot entry, too - which might be removed a moment later.
{
setExpandable( false );
}
}
void
KDirTreeViewItem::cleanupDotEntries()
{
if ( ! _orig->dotEntry() )
return;
KDirTreeViewItem *dotEntry = findDotEntry();
if ( ! dotEntry )
return;
// Reparent dot entry children if there are no subdirectories on this level
if ( ! _orig->firstChild() )
{
// kdDebug() << "Removing solo dot entry clone " << _orig << endl;
KDirTreeViewItem *child = dotEntry->firstChild();
while ( child )
{
KDirTreeViewItem *nextChild = child->next();
// Reparent this child
// kdDebug() << "Reparenting clone " << child << endl;
dotEntry->removeItem( child );
insertItem( child );
child = nextChild;
}
/*
* Immediately delete the (now emptied) dot entry. The algorithm for
* the original tree doesn't quite fit here - there, the dot entry is
* actually deleted in the step below. But the 'no children' check for
* this fails here since the original dot entry still _has_ its
* children - they will be deleted only after all clones have been
* processed.
*
* This had been the cause for a core that took me quite some time to
* track down.
*/
delete dotEntry;
dotEntry = 0;
}
// Delete dot entries without any children
if ( ! _orig->dotEntry()->firstChild() && dotEntry )
{
// kdDebug() << "Removing empty dot entry clone " << _orig << endl;
delete dotEntry;
}
}
KDirTreeViewItem *
KDirTreeViewItem::findDotEntry() const
{
KDirTreeViewItem *child = firstChild();
while ( child )
{
if ( child->orig()->isDotEntry() )
return child;
child = child->next();
}
return 0;
}
void
KDirTreeViewItem::setOpen( bool open )
{
if ( open && _view->doLazyClone() )
{
// kdDebug() << "Opening " << this << endl;
deferredClone();
}
if ( isOpen() != open )
{
openNotify( open );
}
TQListViewItem::setOpen( open );
setIcon();
if ( open )
updateSummary();
// kdDebug() << _openCount << " open in " << this << endl;
// _view->logActivity( 1 );
}
void
KDirTreeViewItem::openNotify( bool open )
{
if ( open )
_openCount++;
else
_openCount--;
if ( _parent )
_parent->openNotify( open );
}
void
KDirTreeViewItem::openSubtree()
{
if ( parent() )
parent()->setOpen( true );
setOpen( true );
}
void
KDirTreeViewItem::closeSubtree()
{
setOpen( false );
if ( _openCount > 0 )
{
KDirTreeViewItem * child = firstChild();
while ( child )
{
child->closeSubtree();
child = child->next();
}
}
_openCount = 0; // just to be sure
}
void
KDirTreeViewItem::closeAllExceptThis()
{
KDirTreeViewItem *sibling = _parent ?
_parent->firstChild() : _view->firstChild();
while ( sibling )
{
if ( sibling != this )
sibling->closeSubtree(); // Recurse down
sibling = sibling->next();
}
setOpen( true );
if ( _parent )
_parent->closeAllExceptThis(); // Recurse up
}
TQString
KDirTreeViewItem::asciiDump()
{
TQString dump;
dump = TQString("%1 %2\n")
.arg(formatSize(_orig->totalSize()), 10)
.arg(_orig->debugUrl().local8Bit());
if ( isOpen() )
{
KDirTreeViewItem *child = firstChild();
while ( child )
{
dump += child->asciiDump();
child = child->next();
}
}
return dump;
}
/**
* Comparison function used for sorting the list.
* Returns:
* -1 if this < other
* 0 if this == other
* +1 if this > other
**/
int
KDirTreeViewItem::compare( TQListViewItem * otherListViewItem,
int column,
bool ascending ) const
{
// _view->incDebugCount(4);
KDirTreeViewItem * other = dynamic_cast<KDirTreeViewItem *> (otherListViewItem);
if ( other )
{
KFileInfo * otherOrig = other->orig();
#if ! SEPARATE_READ_JOBS_COL
if ( column == _view->readJobsCol() ) return - compare( _orig->pendingReadJobs(), otherOrig->pendingReadJobs() );
else
#endif
if ( column == _view->totalSizeCol() ||
column == _view->percentNumCol() ||
column == _view->percentBarCol() ) return - compare( _orig->totalSize(), otherOrig->totalSize() );
else if ( column == _view->ownSizeCol() ) return - compare( _orig->size(), otherOrig->size() );
else if ( column == _view->totalItemsCol() ) return - compare( _orig->totalItems(), otherOrig->totalItems() );
else if ( column == _view->totalFilesCol() ) return - compare( _orig->totalFiles(), otherOrig->totalFiles() );
else if ( column == _view->totalSubDirsCol() ) return - compare( _orig->totalSubDirs(), otherOrig->totalSubDirs() );
else if ( column == _view->latestMtimeCol() ) return - compare( _orig->latestMtime(), otherOrig->latestMtime() );
else
{
if ( _orig->isDotEntry() ) // make sure dot entries are last in the list
return 1;
if ( otherOrig->isDotEntry() )
return -1;
}
}
return TQListViewItem::compare( otherListViewItem, column, ascending );
}
void
KDirTreeViewItem::paintCell( TQPainter * painter,
const TQColorGroup & colorGroup,
int column,
int width,
int alignment )
{
// _view->incDebugCount(5);
if ( column == _view->percentBarCol() )
{
painter->setBackgroundColor( colorGroup.base() );
if ( _percent > 0.0 )
{
if ( _pacMan )
{
delete _pacMan;
_pacMan = 0;
}
int level = _orig->treeLevel();
paintPercentageBar ( _percent,
painter,
_view->treeStepSize() * ( level-1 ),
width,
_view->fillColor( level-1 ),
_view->percentageBarBackground() );
}
else
{
if ( _pacMan && _orig->isBusy() )
{
// kdDebug() << "Animating PacMan for " << _orig << endl;
// painter->setBackgroundColor( _view->treeBackground() );
_pacMan->animate( painter, TQRect( 0, 0, width, height() ) );
}
else
{
if ( _view->percentBarCol() == _view->readJobsCol()
&& ! _pacMan )
{
TQListViewItem::paintCell( painter,
colorGroup,
column,
width,
alignment );
}
else
{
painter->eraseRect( 0, 0, width, height() );
}
}
}
}
else
{
/*
* Call the parent's paintCell() method. We don't want to do
* all the hassle of drawing strings and pixmaps, regarding
* alignments etc.
*/
TQListViewItem::paintCell( painter,
colorGroup,
column,
width,
alignment );
}
}
void
KDirTreeViewItem::paintPercentageBar( float percent,
TQPainter * painter,
int indent,
int width,
const TQColor & fillColor,
const TQColor & barBackground )
{
int penWidth = 2;
int extraMargin = 3;
int x = _view->itemMargin();
int y = extraMargin;
int w = width - 2 * _view->itemMargin();
int h = height() - 2 * extraMargin;
int fillWidth;
painter->eraseRect( 0, 0, width, height() );
w -= indent;
x += indent;
if ( w > 0 )
{
TQPen pen( painter->pen() );
pen.setWidth( 0 );
painter->setPen( pen );
painter->setBrush( NoBrush );
fillWidth = (int) ( ( w - 2 * penWidth ) * percent / 100.0);
// Fill bar background.
painter->fillRect( x + penWidth, y + penWidth,
w - 2 * penWidth + 1, h - 2 * penWidth + 1,
barBackground );
/*
* Notice: The Xlib XDrawRectangle() function always fills one
* pixel less than specified. Altough this is very likely just a
* plain old bug, it is documented that way. Obviously, TQt just
* maps the fillRect() call directly to XDrawRectangle() so they
* inherited that bug (although the TQt doc stays silent about
* it). So it is really necessary to compensate for that missing
* pixel in each dimension.
*
* If you don't believe it, see for yourself.
* Hint: Try the xmag program to zoom into the drawn pixels.
**/
// Fill the desired percentage.
painter->fillRect( x + penWidth, y + penWidth,
fillWidth+1, h - 2 * penWidth+1,
fillColor );
// Draw 3D shadows.
pen.setColor( contrastingColor ( TQt::black,
painter->backgroundColor() ) );
painter->setPen( pen );
painter->drawLine( x, y, x+w, y );
painter->drawLine( x, y, x, y+h );
pen.setColor( contrastingColor( barBackground.dark(),
painter->backgroundColor() ) );
painter->setPen( pen );
painter->drawLine( x+1, y+1, x+w-1, y+1 );
painter->drawLine( x+1, y+1, x+1, y+h-1 );
pen.setColor( contrastingColor( barBackground.light(),
painter->backgroundColor() ) );
painter->setPen( pen );
painter->drawLine( x+1, y+h, x+w, y+h );
painter->drawLine( x+w, y, x+w, y+h );
pen.setColor( contrastingColor( TQt::white,
painter->backgroundColor() ) );
painter->setPen( pen );
painter->drawLine( x+2, y+h-1, x+w-1, y+h-1 );
painter->drawLine( x+w-1, y+1, x+w-1, y+h-1 );
}
}
TQString
KDirStat::formatSizeLong( KFileSize size )
{
TQString sizeText;
int count = 0;
while ( size > 0 )
{
sizeText = ( ( size % 10 ) + '0' ) + sizeText;
size /= 10;
if ( ++count == 3 && size > 0 )
{
sizeText = TDEGlobal::locale()->thousandsSeparator() + sizeText;
count = 0;
}
}
return sizeText;
}
TQString
KDirStat::hexKey( KFileSize size )
{
/**
* This is optimized for performance, not for aesthetics.
* And every now and then the old C hacker breaks through in most of us...
* ;-)
**/
static const char hexDigits[] = "0123456789ABCDEF";
char key[ sizeof( KFileSize ) * 2 + 1 ]; // 2 hex digits per byte required
char *cptr = key + sizeof( key ) - 1; // now points to last char of key
memset( key, '0', sizeof( key ) - 1 ); // fill with zeroes
*cptr-- = 0; // terminate string
while ( size > 0 )
{
*cptr-- = hexDigits[ size & 0xF ]; // same as size % 16
size >>= 4; // same as size /= 16
}
return TQString( key );
}
TQString
KDirStat::formatTime( long millisec, bool showMilliSeconds )
{
TQString formattedTime;
int hours;
int min;
int sec;
hours = millisec / 3600000L; // 60*60*1000
millisec %= 3600000L;
min = millisec / 60000L; // 60*1000
millisec %= 60000L;
sec = millisec / 1000L;
millisec %= 1000L;
if ( showMilliSeconds )
{
formattedTime.sprintf ( "%02d:%02d:%02d.%03ld",
hours, min, sec, millisec );
}
else
{
formattedTime.sprintf ( "%02d:%02d:%02d", hours, min, sec );
}
return formattedTime;
}
TQString
KDirStat::formatCount( int count, bool suppressZero )
{
if ( suppressZero && count == 0 )
return "";
TQString countString;
countString.setNum( count );
return countString;
}
TQString
KDirStat::formatPercent( float percent )
{
TQString percentString;
percentString.sprintf( "%.1f%%", percent );
return percentString;
}
TQString
KDirStat::formatTimeDate( time_t rawTime )
{
TQString timeDateString;
struct tm *t = localtime( &rawTime );
/*
* Format this as "yyyy-mm-dd hh:mm:ss".
*
* This format may not be POSIX'ly correct, but it is the ONLY of all those
* brain-dead formats today's computer users are confronted with that makes
* any sense to the average human.
*
* Agreed, it takes some getting used to, too, but once you got that far,
* you won't want to miss it.
*
* Who the hell came up with those weird formats like described in the
* ctime() man page? Don't those people ever actually use that?
*
* What sense makes a format like "Wed Jun 30 21:49:08 1993" ?
* The weekday (of all things!) first, then a partial month name, then the
* day of month, then the time and then - at the very end - the year.
* IMHO this is maximum brain-dead. Not only can't you do any kind of
* decent sorting or automatic processing with that disinformation
* hodge-podge, your brain runs in circles trying to make sense of it.
*
* I could put up with crap like that if the Americans and Brits like it
* that way, but unfortunately I as a German am confronted with that
* bullshit, too, on a daily basis - either because some localization stuff
* didn't work out right (again) or because some jerk decided to emulate
* this stuff in the German translation, too. I am sick and tired with
* that, and since this is MY program I am going to use a format that makes
* sense to ME.
*
* No, no exceptions for Americans or Brits. I had to put up with their
* crap long enough, now it's time for them to put up with mine.
* Payback time - though luck, folks.
* ;-)
*
* Stefan Hundhammer <sh@suse.de> 2001-05-28
* (in quite some fit of frustration)
*/
timeDateString.sprintf( "%4d-%02d-%02d %02d:%02d:%02d",
t->tm_year + 1900,
t->tm_mon + 1, // another brain-dead common pitfall - 0..11
t->tm_mday,
t->tm_hour, t->tm_min, t->tm_sec );
return timeDateString;
}
TQString
KDirStat::localeTimeDate( time_t rawTime )
{
TQDateTime timeDate;
timeDate.setTime_t( rawTime );
TQString timeDateString =
TDEGlobal::locale()->formatDate( timeDate.date(), true ) + " " + // short format
TDEGlobal::locale()->formatTime( timeDate.time(), true ); // include seconds
return timeDateString;
}
TQColor
KDirStat::contrastingColor( const TQColor &desiredColor,
const TQColor &contrastColor )
{
if ( desiredColor != contrastColor )
{
return desiredColor;
}
if ( contrastColor != contrastColor.light() )
{
// try a little lighter
return contrastColor.light();
}
else
{
// try a little darker
return contrastColor.dark();
}
}
#include "kdirtreeview.moc"
// EOF