Logo Search packages:      
Sourcecode: k4dirstat version File versions  Download package

kdirtreeview.cpp

/*
 *   File name:   kdirtreeview.cpp
 *   Summary:     High level classes for KDirStat
 *   License:     LGPL - See file COPYING.LIB for details.
 *   Author:      Stefan Hundhammer <sh@suse.de>
 *              Joshua Hodosh <kdirstat@grumpypenguin.org>
 *
 *   Updated:     2010-02-01
 */


#ifdef HAVE_CONFIG_H
#   include <config.h>
#endif

#include <time.h>
#include <stdlib.h>

#include <qtimer.h>
#include <qcolor.h>
#include <q3header.h>
#include <q3popupmenu.h>

#include <kapplication.h>
#include <klocale.h>
#include <kglobal.h>
#include <kglobalsettings.h>
#include <kicontheme.h>
#include <kiconloader.h>
#include <kexcluderules.h>
#include <kconfiggroup.h>
#include <kiconloader.h>
#include <kcolorscheme.h>
#include <ktoolinvocation.h>

#include "kdirtreeview.h"
#include "kdirreadjob.h"
#include "kdirtreeiterators.h"
#include "kpacman.h"

#define SEPARATE_READ_JOBS_COL      0
#define VERBOSE_PROGRESS_INFO 0

using namespace KDirStat;


00047 KDirTreeView::KDirTreeView( QWidget * 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,         Qt::AlignRight );
    setColumnAlignment ( _percentNumCol,  Qt::AlignRight );
    setColumnAlignment ( _ownSizeCol,           Qt::AlignRight );
    setColumnAlignment ( _totalItemsCol,  Qt::AlignRight );
    setColumnAlignment ( _totalFilesCol,  Qt::AlignRight );
    setColumnAlignment ( _totalSubDirsCol,      Qt::AlignRight );
    setColumnAlignment ( _readJobsCol,          Qt::AlignRight );


    setSorting( _totalSizeCol );


#define loadIcon(ICON)  KIconLoader::global()->loadIcon( (ICON), KIconLoader::Small )

    _openDirIcon  = loadIcon( "folder-open"     );
    _closedDirIcon      = loadIcon( "folder"          );
    _openDotEntryIcon   = loadIcon( "folder-orange"     );
    _closedDotEntryIcon = loadIcon( "folder-orange"   );
    _unreadableDirIcon  = loadIcon( "folder-locked"   );
    _mountPointIcon     = loadIcon( "drive-harddisk"  );
    _fileIcon           = loadIcon( "mime_empty"      );
    _symLinkIcon  = loadIcon( "emblem-symbolic-link");      // The KDE standard link icon is ugly!
    _blockDevIcon = loadIcon( "blockdevice"     );
    _charDevIcon  = loadIcon( "chardevice"      );
    _fifoIcon           = loadIcon( "socket"          );
    _stopIcon           = loadIcon( "process-stop"    );
    _readyIcon          = QPixmap();

#undef loadIcon

    setDefaultFillColors();
    readConfig();
    ensureContrast();


    connect( kapp,      SIGNAL( kdisplayPaletteChanged()    ),
           this,  SLOT  ( paletteChanged()            ) );

    connect( this,      SIGNAL( selectionChanged      ( Q3ListViewItem * ) ),
           this,  SLOT  ( selectItem            ( Q3ListViewItem * ) ) );

    connect( this,      SIGNAL( rightButtonPressed    ( Q3ListViewItem *, const QPoint &, int ) ),
           this,  SLOT  ( popupContextMenu      ( Q3ListViewItem *, const QPoint &, int ) ) );

    connect( header(),  SIGNAL( sizeChange   ( int, int, int ) ),
           this,  SLOT  ( columnResized( int, int, int ) ) );

   _contextInfo     = new Q3PopupMenu;
   _idContextInfo = _contextInfo->insertItem ( "dummy" );

   createTree();
}


00144 KDirTreeView::~KDirTreeView()
{
    if ( _tree )
      delete _tree;

    /*
     * Don't delete _updateTimer here, it's already automatically deleted by Qt!
     * (Since it's derived from QObject and has a QObject parent).
     */
}


void
00157 KDirTreeView::setDebugFunc( int i, const QString & functionName )
{
    if ( i > 0 && i < DEBUG_COUNTERS )
      _debugFunc[i] = functionName;
}


void
00165 KDirTreeView::incDebugCount( int i )
{
    if ( i > 0 && i < DEBUG_COUNTERS )
      _debugCount[i]++;
}


void
00173 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
00189 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 QListView::sort() might do the trick, but we can
      // never know just how clever that QListView 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

    setColumnAlignment ( _percentBarCol, Qt::AlignLeft );
    _readJobsCol = -1;
}


void
00220 KDirTreeView::openURL( KUrl url )
{
    clear();
    _tree->clear();

    // Implicitly calling prepareReading() via the tree's startingReading() signal
    _tree->startReading( url );

    logActivity( 30 );
}


void
00233 KDirTreeView::createTree()
{
    // Clean up any old leftovers

    clear();
    _currentDir = "";

    if ( _tree )
      delete _tree;


    // Create new (empty) dir tree

    _tree = new KDirTree();


    // Connect signals

    connect( _tree, SIGNAL( progressInfo    ( const QString & ) ),
           this,  SLOT  ( sendProgressInfo( const QString & ) ) );

    connect( _tree, SIGNAL( childAdded( KFileInfo * ) ),
           this,  SLOT  ( addChild  ( KFileInfo * ) ) );

    connect( _tree, SIGNAL( deletingChild( KFileInfo * ) ),
           this,  SLOT  ( deleteChild  ( KFileInfo * ) ) );

    connect( _tree, SIGNAL( startingReading() ),
           this,  SLOT  ( prepareReading()  ) );

    connect( _tree, SIGNAL( finished()     ),
           this,  SLOT  ( slotFinished() ) );

    connect( _tree, SIGNAL( aborted()     ),
           this,  SLOT  ( slotAborted() ) );

    connect( _tree, SIGNAL( finalizeLocal( KDirInfo * ) ),
           this,  SLOT  ( finalizeLocal( KDirInfo * ) ) );

    connect( this,  SIGNAL( selectionChanged( KFileInfo * ) ),
           _tree, SLOT  ( selectItem      ( KFileInfo * ) ) );

    connect( _tree, SIGNAL( selectionChanged( KFileInfo * ) ),
           this,  SLOT  ( selectItem      ( KFileInfo * ) ) );
}


void
00281 KDirTreeView::prepareReading()
{
    // Prepare cyclic update

    if ( _updateTimer )
      delete _updateTimer;

    _updateTimer = new QTimer( this );

    if ( _updateTimer )
    {
      _updateTimer->changeInterval( _updateInterval );
      connect( _updateTimer,  SIGNAL( timeout() ),
             this,      SLOT  ( updateSummary() ) );

      connect( _updateTimer, SIGNAL( timeout() ),
             this,      SLOT  ( sendProgressInfo() ) );
    }


    // Change display to busy state

    setSorting( _totalSizeCol );
    busyDisplay();
    emit startingReading();

    _stopWatch.start();
}


void
00312 KDirTreeView::refreshAll()
{
    if ( _tree && _tree->root() )
    {
      clear();
      // Implicitly calling prepareReading() via the tree's startingReading() signal
      _tree->refresh( 0 );
    }
}


void
00324 KDirTreeView::refreshSelected()
{
    if ( _tree && _tree->root() && _selection )
    {
      // Implicitly calling prepareReading() via the tree's startingReading() signal
      _tree->refresh( _selection->orig() );
    }

    logActivity( 10 );
}


void
00337 KDirTreeView::abortReading()
{
    if ( _tree )
      _tree->abortReading();
}


void
00345 KDirTreeView::clear()
{
    clearSelection();
    KDirTreeViewParentClass::clear();

    for ( int i=0; i < DEBUG_COUNTERS; i++ )
      _debugCount[i]    = 0;
}


bool
00356 KDirTreeView::writeCache( const QString & cacheFileName )
{
    if ( _tree )
      return _tree->writeCache( cacheFileName );

    return false;
}


void
00366 KDirTreeView::readCache( const QString & cacheFileName )
{
    clear();
    _tree->clear();
    _tree->readCache( cacheFileName );
}


void
00375 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
00409 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
00457 KDirTreeView::updateSummary()
{
    KDirTreeViewItem *child = firstChild();

    while ( child )
    {
      child->updateSummary();
      child = child->next();
    }
}


void
00470 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 ( _tree->root() &&
       _tree->root()->totalSubDirs() == 0 &&    // No subdirs
       _tree->root()->totalItems() > 0 )  // but file children
    {
      Q3ListViewItem * root = firstChild();

      if ( root )
          root->setOpen( true );
    }



#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
00512 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
00531 KDirTreeView::finalizeLocal( KDirInfo *dir )
{
    if ( dir )
    {
      KDirTreeViewItem *clone = locate( dir,
                                false,    // lazy
                                false );  // doClone
      if ( clone )
          clone->finalizeLocal();
    }
}


void
00545 KDirTreeView::sendProgressInfo( const QString & 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 *
00561 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
00581 KDirTreeView::openCount()
{
    int count = 0;
    KDirTreeViewItem *child = firstChild();

    while ( child )
    {
      count += child->openCount();
      child  = child->next();
    }

    return count;
}


void
00597 KDirTreeView::selectItem( Q3ListViewItem *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
00619 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
00649 KDirTreeView::clearSelection()
{
    // kdDebug() << k_funcinfo << endl;
    _selection = 0;
    Q3ListView::clearSelection();

    emit selectionChanged( (KDirTreeViewItem *) 0 );
    emit selectionChanged( (KFileInfo *) 0 );
}


void
00661 KDirTreeView::closeAllExcept( KDirTreeViewItem *except )
{
    if ( ! except )
    {
      kdError() << k_funcinfo << ": NULL pointer passed" << endl;
      return;
    }

    except->closeAllExceptThis();
}


const QColor &
00674 KDirTreeView::fillColor( int level ) const
{
    if ( level < 0 )
    {
      kdWarning() << k_funcinfo << "Invalid argument: " << level << endl;
      level = 0;
    }

    return _fillColor [ level % _usedFillColors ];
}


const QColor &
00687 KDirTreeView::rawFillColor( int level ) const
{
    if ( level < 0 || level > KDirTreeViewMaxFillColor )
    {
      level = 0;
      kdWarning() << k_funcinfo << "Invalid argument: " << level << endl;
    }

    return _fillColor [ level % KDirTreeViewMaxFillColor ];
}


void
00700 KDirTreeView::setFillColor( int                 level,
                      const QColor &      color )
{
    if ( level >= 0 && level < KDirTreeViewMaxFillColor )
      _fillColor[ level ] = color;
}



void
00710 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
00729 KDirTreeView::setDefaultFillColors()
{
    int i;

    for ( i=0; i < KDirTreeViewMaxFillColor; i++ )
    {
      _fillColor[i] = Qt::blue;
    }

    i = 0;
    _usedFillColors = 4;

    setFillColor ( i++, QColor ( 0,     0, 255 ) );
    setFillColor ( i++, QColor ( 128,   0, 128 ) );
    setFillColor ( i++, QColor ( 231, 147,  43 ) );
    setFillColor ( i++, QColor (   4, 113,   0 ) );
    setFillColor ( i++, QColor ( 176,   0,   0 ) );
    setFillColor ( i++, QColor ( 204, 187,   0 ) );
    setFillColor ( i++, QColor ( 162,  98,  30 ) );
    setFillColor ( i++, QColor (   0, 148, 146 ) );
    setFillColor ( i++, QColor ( 217,  94,   0 ) );
    setFillColor ( i++, QColor (   0, 194,  65 ) );
    setFillColor ( i++, QColor ( 194, 108, 187 ) );
    setFillColor ( i++, QColor (   0, 179, 255 ) );
}


void
00757 KDirTreeView::setTreeBackground( const QColor &color )
{
    _treeBackground = color;
    _percentageBarBackground = _treeBackground.dark( 115 );

    QPalette pal = kapp->palette();
    pal.setBrush( QColorGroup::Base, _treeBackground );
    setPalette( pal );
}


void
00769 KDirTreeView::ensureContrast()
{
    if ( colorGroup().base() == Qt::white ||
       colorGroup().base() == Qt::black   )
    {
      setTreeBackground( colorGroup().midlight() );
    }
    else
    {
      setTreeBackground( colorGroup().base() );
    }
}


void
00784 KDirTreeView::paletteChanged()
{
    setTreeBackground( KColorScheme::ActiveBackground );
    ensureContrast();
}


void
00792 KDirTreeView::popupContextMenu( Q3ListViewItem *      listViewItem,
                        const QPoint &    pos,
                        int         column )
{
    KDirTreeViewItem *item = (KDirTreeViewItem *) listViewItem;

    if ( ! item )
      return;

    KFileInfo * orig = item->orig();

    if ( ! orig )
    {
      kdError() << "NULL item->orig()" << endl;
      return;
    }

    if ( column == _nameCol         ||
       column == _percentBarCol     ||
       column == _percentNumCol       )
    {
      if ( orig->isExcluded() && column == _percentBarCol )
      {
          // Show with exclude rule caused the exclusion

          const KExcludeRule * rule = KExcludeRules::excludeRules()->matchingRule( orig->url() );

          QString text;

          if ( rule )
          {
            text = i18n( "Matching exclude rule:   %1" ).arg( rule->regexp().pattern() );
          }
          else
          {
            text = i18n( "<Unknown exclude rule>" );
          }

          popupContextInfo( pos, text );
      }
      else
      {
          // 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() )
    {
      if ( orig->isSparseFile() || ( orig->links() > 1 && orig->isFile() ) )
      {
          QString 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
00900 KDirTreeView::popupContextSizeInfo( const QPoint &    pos,
                            KFileSize           size )
{
    QString 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
00921 KDirTreeView::popupContextInfo( const QPoint  & pos,
                        const QString & info )
{
    _contextInfo->changeItem( _idContextInfo, info );
    _contextInfo->popup( pos );
}


void
00930 KDirTreeView::readConfig()
{
    KConfigGroup config = KGlobal::config()->group( "Tree Colors" );
    _usedFillColors = config.readEntry( "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

      QColor defaultColor( Qt::blue );

      for ( int i=0; i < KDirTreeViewMaxFillColor; i++ )
      {
          QString name;
          name.sprintf( "fillColor_%02d", i );
          _fillColor [i] = config.readEntry( name, defaultColor );
      }
    }

    if ( isVisible() )
      triggerUpdate();
}


void
00964 KDirTreeView::saveConfig() const
{
    KConfigGroup config = KGlobal::config()->group( "Tree Colors" );

    config.writeEntry( "usedFillColors", _usedFillColors );

    for ( int i=0; i < KDirTreeViewMaxFillColor; i++ )
    {
      QString name;
      name.sprintf( "fillColor_%02d", i );
      config.writeEntry ( name, _fillColor [i] );
    }
}


void
00980 KDirTreeView::setSorting( int column, bool increasing )
{
    _sortCol = column;
    Q3ListView::setSorting( column, increasing );
}


void
00988 KDirTreeView::logActivity( int points )
{
    emit userActivity( points );
}


void
00995 KDirTreeView::columnResized( int column, int oldSize, int newSize )
{
    NOT_USED( oldSize );
    NOT_USED( newSize );

    if ( column == _percentBarCol )
      triggerUpdate();
}

void
01005 KDirTreeView::sendMailToOwner()
{
    if ( ! _selection )
    {
      kdError() << k_funcinfo << "Nothing selected!" << endl;
      return;
    }

    QString owner = KioDirReadJob::owner( fixedUrl( _selection->orig()->url() ) );
    QString subject = i18n( "Disk Usage" );
    QString 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.

    KToolInvocation::invokeMailer(mail);
    logActivity( 10 );
}






01048 KDirTreeViewItem::KDirTreeViewItem( KDirTreeView *    view,
                            KFileInfo *         orig )
    : Q3ListViewItem( view )
{
    init( view, 0, orig );
}


01056 KDirTreeViewItem::KDirTreeViewItem( KDirTreeView *    view,
                            KDirTreeViewItem *  parent,
                            KFileInfo *         orig )
    : Q3ListViewItem( parent )
{
    Q_CHECK_PTR( parent );
    init( view, parent, orig );
}


void
01067 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>" ) );
       Q3ListViewItem::setOpen ( false );
    }
    else
    {
      setText( view->nameCol(), QString::fromLocal8Bit(_orig->name()) );

      if ( ! _orig->isDevice() )
      {
          QString 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 );

#ifdef NEVER_EXECUTED
          // Never executed because during init() a read job for this
          // directory will still be pending. Only the read job will figure
          // out if an exclude rule applies.

          if ( _orig->isExcluded() )
          {
            setText( _view->percentBarCol(), i18n( "[excluded]" ) );
          }
#endif
      }

      Q3ListViewItem::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, Qt handles this very well, but when lazy cloning is in
       * effect, Qt 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 ||
           _orig->readState() == KDirCached    )
      {
          setExpandable( true );
      }
      else  // KDirFinished, KDirError, KDirAborted
      {
          setExpandable( _orig->hasChildren() );
      }
    }

    if ( ! parent || parent->isOpen() )
    {
      setIcon();
    }

    _openCount = isOpen() ? 1 : 0;
}


01181 KDirTreeViewItem::~KDirTreeViewItem()
{
    if ( _pacMan )
      delete _pacMan;

    if ( this == _view->selection() )
      _view->clearSelection();
}


void
01192 KDirTreeViewItem::setIcon()
{
    QPixmap 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
01234 KDirTreeViewItem::updateSummary()
{
    // _view->incDebugCount(2);

    // Update this item

    setIcon();
    setText( _view->latestMtimeCol(),           "  " + localeTimeDate( _orig->latestMtime() ) );

    if ( _orig->isDir() || _orig->isDotEntry() )
    {
      QString 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();
          QString 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() ) );

      if ( _orig->isExcluded() )
          setText( _view->percentBarCol(),      i18n( "[excluded]" ) );
    }


    // 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
       ! _orig->isExcluded() )                  // not if this is an excluded object (dir)
    {
       _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 *
01323 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
01388 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
01441 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
01456 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 *
01515 KDirTreeViewItem::findDotEntry() const
{
    KDirTreeViewItem *child = firstChild();

    while ( child )
    {
      if ( child->orig()->isDotEntry() )
          return child;

      child = child->next();
    }

    return 0;
}


void
01532 KDirTreeViewItem::setOpen( bool open )
{
    if ( open && _view->doLazyClone() )
    {
      // kdDebug() << "Opening " << this << endl;
      deferredClone();
    }

    if ( isOpen() != open )
    {
      openNotify( open );
    }

    Q3ListViewItem::setOpen( open );
    setIcon();

    if ( open )
      updateSummary();

    // kdDebug() << _openCount << " open in " << this << endl;

    // _view->logActivity( 1 );
}


void
01558 KDirTreeViewItem::openNotify( bool open )
{
    if ( open )
      _openCount++;
    else
      _openCount--;

    if ( _parent )
      _parent->openNotify( open );
}


void
01571 KDirTreeViewItem::openSubtree()
{
    if ( parent() )
      parent()->setOpen( true );

    setOpen( true );
}


void
01581 KDirTreeViewItem::closeSubtree()
{
    setOpen( false );

    if ( _openCount > 0 )
    {
      KDirTreeViewItem * child = firstChild();

      while ( child )
      {
          child->closeSubtree();
          child = child->next();
      }
    }

    _openCount = 0;     // just to be sure
}


void
01601 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
}


QString
01622 KDirTreeViewItem::asciiDump()
{
    QString 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
01653 KDirTreeViewItem::compare( Q3ListViewItem *     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 Q3ListViewItem::compare( otherListViewItem, column, ascending );
}


void
01692 KDirTreeViewItem::paintCell( Q3Painter *        painter,
                       const QColorGroup &      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, QRect( 0, 0, width, height() ) );
          }
          else
          {
            if ( ( _view->percentBarCol() == _view->readJobsCol()
                   && ! _pacMan )
                 || _orig->isExcluded() )
            {
                Q3ListViewItem::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.
       */
      Q3ListViewItem::paintCell( painter,
                          colorGroup,
                          column,
                          width,
                          alignment );
    }
}


void
01764 KDirTreeViewItem::paintPercentageBar( float           percent,
                              Q3Painter * painter,
                              int         indent,
                              int         width,
                              const QColor &    fillColor,
                              const QColor &    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 )
    {
      QPen pen( painter->pen() );
      pen.setWidth( 0 );
      painter->setPen( pen );
      painter->setBrush( Qt::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, Qt just
       * maps the fillRect() call directly to XDrawRectangle() so they
       * inherited that bug (although the Qt 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 ( Qt::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( Qt::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 );
    }
}








QString
KDirStat::formatSizeLong( KFileSize size )
{
    return KGlobal::locale()->formatLong(size);
}


QString
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 QString( key );
}


QString
KDirStat::formatTime( long millisec, bool showMilliSeconds )
{
    QString 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;
}


QString
KDirStat::formatCount( int count, bool suppressZero )
{
    if ( suppressZero && count == 0 )
      return "";

    QString countString;
    countString.setNum( count );

    return countString;
}


QString
KDirStat::formatPercent( float percent )
{
    QString percentString;

    percentString.sprintf( "%.1f%%", percent );

    return percentString;
}


QString
KDirStat::formatTimeDate( time_t rawTime )
{
    QString 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;
}


QString
KDirStat::localeTimeDate( time_t rawTime )
{
    QDateTime timeDate;
    timeDate.setTime_t( rawTime );
    QString timeDateString =
      KGlobal::locale()->formatDate( timeDate.date(), KLocale::ShortDate) + "  " +  // short format
      KGlobal::locale()->formatTime( timeDate.time(), true );           // include seconds

    return timeDateString;
}


QColor
KDirStat::contrastingColor( const QColor &desiredColor,
                      const QColor &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

Generated by  Doxygen 1.6.0   Back to index