/* * File name: kdirtreeview.cpp * Summary: High level classes for KDirStat * License: LGPL - See file COPYING.LIB for details. * Author: Stefan Hundhammer * * Updated: 2005-01-07 */ #include #include #include #include #include #include #include #include #include #include #include #include #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, TQT_SIGNAL( tdedisplayPaletteChanged() ), this, TQT_SLOT ( paletteChanged() ) ); connect( this, TQT_SIGNAL( selectionChanged ( TQListViewItem * ) ), this, TQT_SLOT ( selectItem ( TQListViewItem * ) ) ); connect( this, TQT_SIGNAL( rightButtonPressed ( TQListViewItem *, const TQPoint &, int ) ), this, TQT_SLOT ( popupContextMenu ( TQListViewItem *, const TQPoint &, int ) ) ); connect( header(), TQT_SIGNAL( sizeChange ( int, int, int ) ), this, TQT_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, TQT_SIGNAL( progressInfo ( const TQString & ) ), this, TQT_SLOT ( sendProgressInfo( const TQString & ) ) ); connect( _tree, TQT_SIGNAL( childAdded( KFileInfo * ) ), this, TQT_SLOT ( addChild ( KFileInfo * ) ) ); connect( _tree, TQT_SIGNAL( deletingChild( KFileInfo * ) ), this, TQT_SLOT ( deleteChild ( KFileInfo * ) ) ); connect( _tree, TQT_SIGNAL( startingReading() ), this, TQT_SLOT ( prepareReading() ) ); connect( _tree, TQT_SIGNAL( finished() ), this, TQT_SLOT ( slotFinished() ) ); connect( _tree, TQT_SIGNAL( aborted() ), this, TQT_SLOT ( slotAborted() ) ); connect( _tree, TQT_SIGNAL( finalizeLocal( KDirInfo * ) ), this, TQT_SLOT ( finalizeLocal( KDirInfo * ) ) ); connect( this, TQT_SIGNAL( selectionChanged( KFileInfo * ) ), _tree, TQT_SLOT ( selectItem ( KFileInfo * ) ) ); connect( _tree, TQT_SIGNAL( selectionChanged( KFileInfo * ) ), this, TQT_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, TQT_SIGNAL( timeout() ), this, TQT_SLOT ( updateSummary() ) ); connect( _updateTimer, TQT_SIGNAL( timeout() ), this, TQT_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( 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 ) { 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( "" ) ); TQListViewItem::setOpen ( false ); } else { setText( view->nameCol(), TQString::fromLocal8Bit(_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.sprintf( "%10s %s\n", (const char *) formatSize( _orig->totalSize() ), (const char *) _orig->debugUrl() ); 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 (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 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(); } } // EOF