summaryrefslogtreecommitdiffstats
path: root/kdirstat
diff options
context:
space:
mode:
authortpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da>2010-02-10 01:15:27 +0000
committertpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da>2010-02-10 01:15:27 +0000
commitb6e09a3a8ea5f1338d089a29eb6e08f00f03f1aa (patch)
tree7dee2cbb5c94d3371357f796d42e344e1305ce9c /kdirstat
downloadkdirstat-b6e09a3a8ea5f1338d089a29eb6e08f00f03f1aa.tar.gz
kdirstat-b6e09a3a8ea5f1338d089a29eb6e08f00f03f1aa.zip
Added abandoned KDE3 version of kdirstat
git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/applications/kdirstat@1088039 283d02a7-25f6-0310-bc7c-ecb5cbfe19da
Diffstat (limited to 'kdirstat')
-rw-r--r--kdirstat/Makefile.am98
-rwxr-xr-xkdirstat/fix_move_to_trash_bin.pl9
-rw-r--r--kdirstat/hi16-app-kdirstat.pngbin0 -> 303 bytes
-rw-r--r--kdirstat/hi32-app-kdirstat.pngbin0 -> 538 bytes
-rw-r--r--kdirstat/kactivitytracker.cpp93
-rw-r--r--kdirstat/kactivitytracker.h109
-rw-r--r--kdirstat/kcleanup.cpp432
-rw-r--r--kdirstat/kcleanup.h360
-rw-r--r--kdirstat/kcleanupcollection.cpp283
-rw-r--r--kdirstat/kcleanupcollection.h225
-rw-r--r--kdirstat/kdirsaver.cpp71
-rw-r--r--kdirstat/kdirsaver.h75
-rw-r--r--kdirstat/kdirstat.desktop20
-rw-r--r--kdirstat/kdirstat.upd6
-rw-r--r--kdirstat/kdirstat_part.rc14
-rw-r--r--kdirstat/kdirstatapp.cpp847
-rw-r--r--kdirstat/kdirstatapp.h421
-rw-r--r--kdirstat/kdirstatfeedback.cpp184
-rw-r--r--kdirstat/kdirstatmain.cpp115
-rw-r--r--kdirstat/kdirstatsettings.cpp1056
-rw-r--r--kdirstat/kdirstatsettings.h744
-rw-r--r--kdirstat/kdirstatui.rc189
-rw-r--r--kdirstat/kdirtree.cpp1636
-rw-r--r--kdirstat/kdirtree.h1437
-rw-r--r--kdirstat/kdirtreeiterators.cpp417
-rw-r--r--kdirstat/kdirtreeiterators.h386
-rw-r--r--kdirstat/kdirtreeview.cpp1956
-rw-r--r--kdirstat/kdirtreeview.h889
-rw-r--r--kdirstat/kfeedback.cpp503
-rw-r--r--kdirstat/kfeedback.h460
-rw-r--r--kdirstat/kpacman.cpp311
-rw-r--r--kdirstat/kpacman.h265
-rw-r--r--kdirstat/kstdcleanup.cpp150
-rw-r--r--kdirstat/kstdcleanup.h65
-rw-r--r--kdirstat/ktreemaptile.cpp608
-rw-r--r--kdirstat/ktreemaptile.h323
-rw-r--r--kdirstat/ktreemapview.cpp745
-rw-r--r--kdirstat/ktreemapview.h446
-rw-r--r--kdirstat/lo16-app-kdirstat.pngbin0 -> 303 bytes
-rw-r--r--kdirstat/lo32-app-kdirstat.pngbin0 -> 538 bytes
-rw-r--r--kdirstat/pics/Makefile.am2
-rw-r--r--kdirstat/pics/hi16-action-symlink.pngbin0 -> 433 bytes
-rw-r--r--kdirstat/pics/hi16-action-symlink.xcfbin0 -> 1863 bytes
-rw-r--r--kdirstat/pics/hi32-action-symlink.pngbin0 -> 1140 bytes
-rw-r--r--kdirstat/pics/hi48-action-symlink.pngbin0 -> 1745 bytes
-rw-r--r--kdirstat/pics/hi48-action-symlink.xcfbin0 -> 3926 bytes
-rw-r--r--kdirstat/pics/lo16-action-symlink.pngbin0 -> 239 bytes
47 files changed, 15950 insertions, 0 deletions
diff --git a/kdirstat/Makefile.am b/kdirstat/Makefile.am
new file mode 100644
index 0000000..8ae3a22
--- /dev/null
+++ b/kdirstat/Makefile.am
@@ -0,0 +1,98 @@
+
+# Makefile.am for kdirstat/kdirstat
+#
+# Initially generated by KDevelop, cleaned up by <sh@suse.de>
+
+SUBDIRS = pics
+
+bin_PROGRAMS = kdirstat
+
+
+kdirstat_SOURCES = \
+ kdirstatmain.cpp \
+ kdirstatapp.cpp \
+ kdirstatfeedback.cpp \
+ kfeedback.cpp \
+ kdirtreeview.cpp \
+ kdirtreeiterators.cpp \
+ kdirtree.cpp \
+ ktreemapview.cpp \
+ ktreemaptile.cpp \
+ kcleanup.cpp \
+ kstdcleanup.cpp \
+ kcleanupcollection.cpp \
+ kdirstatsettings.cpp \
+ kdirsaver.cpp \
+ kactivitytracker.cpp \
+ kpacman.cpp
+
+
+noinst_HEADERS = \
+ kdirstatapp.h \
+ kfeedback.h \
+ kdirtreeview.h \
+ kdirtreeiterators.h \
+ kdirtree.h \
+ ktreemapview.h \
+ ktreemaptile.h \
+ kcleanup.h \
+ kstdcleanup.h \
+ kcleanupcollection.h \
+ kdirstatsettings.h \
+ kdirsaver.h \
+ kactivitytracker.h \
+ kpacman.h
+
+
+EXTRA_DIST = \
+ kdirstatui.rc \
+ kdirstat.desktop \
+ lo32-app-kdirstat.png \
+ lo16-app-kdirstat.png \
+ hi32-app-kdirstat.png \
+ hi16-app-kdirstat.png
+
+
+updatedir = $(kde_datadir)/kconf_update
+update_DATA = kdirstat.upd
+update_SCRIPTS = fix_move_to_trash_bin.pl
+
+
+kdirstat_LDADD = $(LIB_KFILE)
+
+KDE_ICON = kdirstat
+
+applnkdir = $(kde_appsdir)/Utilities
+applnk_DATA = kdirstat.desktop
+
+####### kdevelop will overwrite this part!!! (end)############
+# this 10 paths are KDE specific. Use them:
+# kde_htmldir Where your docs should go to. (contains lang subdirs)
+# kde_appsdir Where your application file (.kdelnk) should go to.
+# kde_icondir Where your icon should go to.
+# kde_minidir Where your mini icon should go to.
+# kde_datadir Where you install application data. (Use a subdir)
+# kde_locale Where translation files should go to.(contains lang subdirs)
+# kde_cgidir Where cgi-bin executables should go to.
+# kde_confdir Where config files should go to.
+# kde_mimedir Where mimetypes should go to.
+# kde_toolbardir Where general toolbar icons should go to.
+# kde_wallpaperdir Where general wallpapers should go to.
+
+# set the include path for X, qt and KDE
+INCLUDES= $(all_includes)
+
+METASOURCES = AUTO
+
+# the library search path.
+kdirstat_LDFLAGS = $(all_libraries) $(KDE_RPATH)
+
+rcdir = $(kde_datadir)/kdirstat
+rc_DATA = kdirstatui.rc
+
+messages: rc.cpp
+ LIST=`find . -name \*.h -o -name \*.hh -o -name \*.H -o -name \*.hxx -o -name \*.hpp -o -name \*.cpp -o -name \*.cc -o -name \*.cxx -o -name \*.ecpp -o -name \*.C`; \
+ if test -n "$$LIST"; then \
+ $(XGETTEXT) $$LIST -o $(podir)/kdirstat.pot; \
+ fi
+
diff --git a/kdirstat/fix_move_to_trash_bin.pl b/kdirstat/fix_move_to_trash_bin.pl
new file mode 100755
index 0000000..6a42af8
--- /dev/null
+++ b/kdirstat/fix_move_to_trash_bin.pl
@@ -0,0 +1,9 @@
+#!/usr/bin/perl
+#
+# Replace ~/KDesktop/Trash to %t
+#
+while( <> )
+{
+ s:~?\S*/\S*Trash\S*:%t: if ( /^\s*command\s*=\s*kfmclient\s+move/ );
+ print $_;
+}
diff --git a/kdirstat/hi16-app-kdirstat.png b/kdirstat/hi16-app-kdirstat.png
new file mode 100644
index 0000000..4bd140e
--- /dev/null
+++ b/kdirstat/hi16-app-kdirstat.png
Binary files differ
diff --git a/kdirstat/hi32-app-kdirstat.png b/kdirstat/hi32-app-kdirstat.png
new file mode 100644
index 0000000..d6c8d99
--- /dev/null
+++ b/kdirstat/hi32-app-kdirstat.png
Binary files differ
diff --git a/kdirstat/kactivitytracker.cpp b/kdirstat/kactivitytracker.cpp
new file mode 100644
index 0000000..a7a1826
--- /dev/null
+++ b/kdirstat/kactivitytracker.cpp
@@ -0,0 +1,93 @@
+
+/*
+ * File name: kactivitytracker.cpp
+ * Summary: Utility object to track user activity
+ * License: LGPL - See file COPYING.LIB for details.
+ * Author: Stefan Hundhammer <sh@suse.de>
+ *
+ * Updated: 2003-01-07
+ */
+
+
+#include <kapp.h>
+#include <kdebug.h>
+#include <kconfig.h>
+#include "kactivitytracker.h"
+
+
+KActivityTracker::KActivityTracker( QObject * parent,
+ const QString & id,
+ long initialThreshold )
+ : QObject( parent )
+{
+ _id = id;
+
+ KConfig * config = kapp->config();
+ config->setGroup( _id );
+ _sum = config->readNumEntry( "activityPoints", 0 );
+ _lastSignal = config->readNumEntry( "lastSignal" , 0 );
+ _threshold = config->readNumEntry( "threshold", initialThreshold );
+}
+
+
+KActivityTracker::~KActivityTracker()
+{
+ // NOP
+}
+
+
+void
+KActivityTracker::setThreshold( long threshold )
+{
+ _threshold = threshold;
+
+ KConfig * config = kapp->config();
+ config->setGroup( _id );
+ config->writeEntry( "threshold", _threshold );
+
+ checkThreshold();
+}
+
+
+void
+KActivityTracker::trackActivity( int points )
+{
+ _sum += points;
+
+ if ( _sum < 0 ) // handle long int overflow
+ _sum = 0;
+
+#if 0
+ kdDebug() << "Adding " << points << " activity points."
+ << " Total: " << _sum << " threshold: " << _threshold
+ << endl;
+#endif
+
+ KConfig * config = kapp->config();
+ config->setGroup( _id );
+ config->writeEntry( "activityPoints", _sum );
+
+ checkThreshold();
+}
+
+
+void
+KActivityTracker::checkThreshold()
+{
+ if ( _sum > _threshold && _lastSignal < _threshold )
+ {
+ // kdDebug() << "Activity threshold reached for " << _id << endl;
+
+ _lastSignal = _sum;
+ KConfig * config = kapp->config();
+ config->setGroup( _id );
+ config->writeEntry( "lastSignal", _lastSignal );
+
+ emit thresholdReached();
+ }
+}
+
+
+
+
+// EOF
diff --git a/kdirstat/kactivitytracker.h b/kdirstat/kactivitytracker.h
new file mode 100644
index 0000000..081d1d8
--- /dev/null
+++ b/kdirstat/kactivitytracker.h
@@ -0,0 +1,109 @@
+/*
+ * File name: kactivitytracker.h
+ * Summary: Utility object to track user activity
+ * License: LGPL - See file COPYING.LIB for details.
+ * Author: Stefan Hundhammer <sh@suse.de>
+ *
+ * Updated: 2003-01-07
+ */
+
+
+#ifndef KActivityTracker_h
+#define KActivityTracker_h
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <qobject.h>
+
+
+/**
+ * Helper class to track user activity of any kind: When the user uses an
+ * application's actions (menu items etc.), those actions notify this object of
+ * that fact. Each action has an amount of "activity points" assigned
+ * (i.e. what this action is "worth"). Those points are summed up here, and
+ * when a certain number of points is reached, a signal is triggered. This
+ * signal can be used for example to ask the user if he wouldn't like to rate
+ * this program - or register it if this is a shareware program.
+ *
+ * @short User activity tracker
+ **/
+class KActivityTracker: public QObject
+{
+ Q_OBJECT
+public:
+ /**
+ * Constructor. The ID is a name for the KConfig object to look in for
+ * accumulated activity points so far. 'initialThreshold' is only used if
+ * the application's @ref KConfig object doesn't contain a corresponding
+ * entry yet.
+ **/
+ KActivityTracker( QObject * parent,
+ const QString & id,
+ long initialThreshold );
+
+ /**
+ * Destructor.
+ **/
+ virtual ~KActivityTracker();
+
+ /**
+ * Returns the number of activity points accumulated so far.
+ **/
+ long sum() const { return _sum; }
+
+ /**
+ * Sets the activity threshold, i.e. when a signal will be sent.
+ **/
+ void setThreshold( long threshold );
+
+ /**
+ * Returns the current threshold.
+ **/
+ long threshold() const { return _threshold; }
+
+ /**
+ * Check the sum of activity points accumulated so far against the current
+ * threshold and emit a signal if appropriate.
+ **/
+ void checkThreshold();
+
+
+public slots:
+
+ /**
+ * Track an activity, i.e. add the specified amount of activity points to
+ * the accumulated sum.
+ **/
+ void trackActivity( int points );
+
+ /**
+ * Set the threshold to its double value.
+ **/
+ void doubleThreshold() { setThreshold( 2 * threshold() ); }
+
+
+signals:
+
+ /**
+ * Emitted when the activity threshold is reached.
+ *
+ * You might want to set the threshold to a new value when this signal is
+ * emitted. You can simply connect it to @ref doubleThreshold().
+ **/
+ void thresholdReached( void );
+
+
+protected:
+
+ long _sum;
+ long _threshold;
+ long _lastSignal;
+ QString _id;
+};
+
+#endif // KActivityTracker_h
+
+
+// EOF
diff --git a/kdirstat/kcleanup.cpp b/kdirstat/kcleanup.cpp
new file mode 100644
index 0000000..01251a2
--- /dev/null
+++ b/kdirstat/kcleanup.cpp
@@ -0,0 +1,432 @@
+/*
+ * File name: kcleanup.cpp
+ * Summary: Support classes for KDirStat
+ * License: LGPL - See file COPYING.LIB for details.
+ * Author: Stefan Hundhammer <sh@suse.de>
+ *
+ * Updated: 2004-11-23
+ */
+
+
+#include <stdlib.h>
+#include <qapplication.h>
+#include <qregexp.h>
+
+#include <kapp.h>
+#include <kprocess.h>
+#include <kdebug.h>
+#include <kmessagebox.h>
+#include <klocale.h>
+#include <kglobalsettings.h>
+
+#include "kcleanup.h"
+#include "kdirsaver.h"
+
+#define VERBOSE_RUN_COMMAND 1
+#define SIMULATE_COMMAND 0
+
+using namespace KDirStat;
+
+
+KCleanup::KCleanup( QString id,
+ QString command,
+ QString title,
+ KActionCollection * parent )
+
+ : KAction( title,
+ 0, // accel
+ parent,
+ id )
+
+ , _id ( id )
+ , _command ( command )
+ , _title ( title )
+{
+ _selection = 0;
+ _enabled = true;
+ _worksForDir = true;
+ _worksForFile = false;
+ _worksForDotEntry = false;
+ _worksLocalOnly = true;
+ _recurse = false;
+ _askForConfirmation = false;
+ _refreshPolicy = noRefresh;
+
+ KAction::setEnabled( false );
+}
+
+
+KCleanup::KCleanup( const KCleanup &src )
+ : KAction()
+{
+ copy( src );
+}
+
+
+KCleanup &
+KCleanup::operator= ( const KCleanup &src )
+{
+ copy( src );
+
+ return *this;
+}
+
+
+void
+KCleanup::copy( const KCleanup &src )
+{
+ setTitle( src.title() );
+ _selection = src.selection();
+ _id = src.id();
+ _command = src.command();
+ _enabled = src.enabled();
+ _worksForDir = src.worksForDir();
+ _worksForFile = src.worksForFile();
+ _worksForDotEntry = src.worksForDotEntry();
+ _worksLocalOnly = src.worksLocalOnly();
+ _recurse = src.recurse();
+ _askForConfirmation = src.askForConfirmation();
+ _refreshPolicy = src.refreshPolicy();
+}
+
+
+void
+KCleanup::setTitle( const QString &title )
+{
+ _title = title;
+ KAction::setText( _title );
+}
+
+
+bool
+KCleanup::worksFor( KFileInfo *item ) const
+{
+ if ( ! _enabled || ! item )
+ return false;
+
+ if ( worksLocalOnly() && ! item->tree()->isFileProtocol() )
+ return false;
+
+ if ( item->isDotEntry() ) return worksForDotEntry();
+ if ( item->isDir() ) return worksForDir();
+
+ return worksForFile();
+}
+
+
+void
+KCleanup::selectionChanged( KFileInfo *selection )
+{
+ bool enabled = false;
+ _selection = selection;
+
+ if ( selection )
+ {
+ enabled = worksFor( selection );
+
+ if ( ! selection->isFinished() )
+ {
+ // This subtree isn't finished reading yet
+
+ switch ( _refreshPolicy )
+ {
+ // Refresh policies that would cause this subtree to be deleted
+ case refreshThis:
+ case refreshParent:
+ case assumeDeleted:
+
+ // Prevent premature deletion of this tree - this would
+ // cause a core dump for sure.
+ enabled = false;
+ break;
+
+ default:
+ break;
+ }
+
+ }
+ }
+
+ KAction::setEnabled( enabled );
+}
+
+
+void
+KCleanup::executeWithSelection()
+{
+ if ( _selection )
+ execute( _selection );
+}
+
+
+bool
+KCleanup::confirmation( KFileInfo * item )
+{
+ QString msg;
+
+ if ( item->isDir() || item->isDotEntry() )
+ {
+ msg = i18n( "%1\nin directory %2" ).arg( cleanTitle() ).arg( item->url() );
+ }
+ else
+ {
+ msg = i18n( "%1\nfor file %2" ).arg( cleanTitle() ).arg( item->url() );
+ }
+
+ if ( KMessageBox::warningContinueCancel( 0, // parentWidget
+ msg, // message
+ i18n( "Please Confirm" ), // caption
+ i18n( "Confirm" ) // confirmButtonLabel
+ ) == KMessageBox::Continue )
+ return true;
+ else
+ return false;
+}
+
+
+void
+KCleanup::execute( KFileInfo *item )
+{
+ if ( worksFor( item ) )
+ {
+ if ( _askForConfirmation && ! confirmation( item ) )
+ return;
+
+ KDirTree * tree = item->tree();
+
+ executeRecursive( item );
+
+ switch ( _refreshPolicy )
+ {
+ case noRefresh:
+ // Do nothing.
+ break;
+
+
+ case refreshThis:
+ tree->refresh( item );
+ break;
+
+
+ case refreshParent:
+ tree->refresh( item->parent() );
+ break;
+
+
+ case assumeDeleted:
+
+ // Assume the cleanup action has deleted the item.
+ // Modify the KDirTree accordingly.
+
+ tree->deleteSubtree( item );
+
+ // Don't try to figure out a reasonable next selection - the
+ // views have to do that while handling the subtree
+ // deletion. Only the views have any knowledge about a
+ // reasonable strategy for choosing a next selection. Unlike
+ // the view items, the KFileInfo items don't have an order that
+ // makes any sense to the user.
+
+ break;
+ }
+ }
+
+ emit executed();
+}
+
+
+void
+KCleanup::executeRecursive( KFileInfo *item )
+{
+ if ( worksFor( item ) )
+ {
+ if ( _recurse )
+ {
+ // Recurse into all subdirectories.
+
+ KFileInfo * subdir = item->firstChild();
+
+ while ( subdir )
+ {
+ if ( subdir->isDir() )
+ {
+ /**
+ * Recursively execute in this subdirectory, but only if it
+ * really is a directory: File children might have been
+ * reparented to the directory (normally, they reside in
+ * the dot entry) if there are no real subdirectories on
+ * this directory level.
+ **/
+ executeRecursive( subdir );
+ }
+ subdir = subdir->next();
+ }
+ }
+
+
+ // Perform cleanup for this directory.
+
+ runCommand( item, _command );
+ }
+}
+
+
+const QString
+KCleanup::itemDir( const KFileInfo *item ) const
+{
+ QString dir = item->url();
+
+ if ( ! item->isDir() && ! item->isDotEntry() )
+ {
+ dir.replace ( QRegExp ( "/[^/]*$" ), "" );
+ }
+
+ return dir;
+}
+
+
+QString
+KCleanup::cleanTitle() const
+{
+ // Use the cleanup action's title, if possible.
+
+ QString title = _title;
+
+ if ( title.isEmpty() )
+ {
+ title = _id;
+ }
+
+ // Get rid of any "&" characters in the text that denote keyboard
+ // shortcuts in menus.
+ title.replace( QRegExp( "&" ), "" );
+
+ return title;
+}
+
+
+QString
+KCleanup::expandVariables( const KFileInfo * item,
+ const QString & unexpanded ) const
+{
+ QString expanded = unexpanded;
+
+ expanded.replace( QRegExp( "%p" ),
+ "\"" + QString::fromLocal8Bit( item->url() ) + "\"" );
+ expanded.replace( QRegExp( "%n" ),
+ "\"" + QString::fromLocal8Bit( item->name() ) + "\"" );
+
+ if ( KDE::versionMajor() >= 3 && KDE::versionMinor() >= 4 )
+ expanded.replace( QRegExp( "%t" ), "trash:/" );
+ else
+ expanded.replace( QRegExp( "%t" ), KGlobalSettings::trashPath() );
+
+ return expanded;
+}
+
+#include <qtextcodec.h>
+void
+KCleanup::runCommand ( const KFileInfo * item,
+ const QString & command ) const
+{
+ KProcess proc;
+ KDirSaver dir( itemDir( item ) );
+ QString cmd( expandVariables( item, command ));
+
+#if VERBOSE_RUN_COMMAND
+ printf( "\ncd " );
+ fflush( stdout );
+ system( "pwd" );
+ QTextCodec * codec = QTextCodec::codecForLocale();
+ printf( "%s\n", (const char *) codec->fromUnicode( cmd ) );
+ fflush( stdout );
+#endif
+
+#if ! SIMULATE_COMMAND
+ proc << "sh";
+ proc << "-c";
+ proc << cmd;
+
+ switch ( _refreshPolicy )
+ {
+ case noRefresh:
+ case assumeDeleted:
+
+ // In either case it is no use waiting for the command to
+ // finish, so we are starting the command as a pure
+ // background process.
+
+ proc.start( KProcess::DontCare );
+ break;
+
+
+ case refreshThis:
+ case refreshParent:
+
+ // If a display refresh is due after the command, we need to
+ // wait for the command to be finished in order to avoid
+ // performing the update prematurely, so we are starting this
+ // process in blocking mode.
+
+ QApplication::setOverrideCursor( waitCursor );
+ proc.start( KProcess::Block );
+ QApplication::restoreOverrideCursor();
+ break;
+ }
+
+#endif
+}
+
+
+void
+KCleanup::readConfig()
+{
+ KConfig *config = kapp->config();
+ KConfigGroupSaver saver( config, _id );
+
+ bool valid = config->readBoolEntry( "valid", false );
+
+ // If the config section requested exists, it should contain a
+ // "valid" field with a true value. If not, there is no such
+ // section within the config file. In this case, just leave this
+ // cleanup action undisturbed - we'd rather have a good default
+ // value (as provided - hopefully - by our application upon
+ // startup) than a generic empty cleanup action.
+
+ if ( valid )
+ {
+ _command = config->readEntry ( "command" );
+ _enabled = config->readBoolEntry ( "enabled" );
+ _worksForDir = config->readBoolEntry ( "worksForDir" );
+ _worksForFile = config->readBoolEntry ( "worksForFile" );
+ _worksForDotEntry = config->readBoolEntry ( "worksForDotEntry" );
+ _worksLocalOnly = config->readBoolEntry ( "worksLocalOnly" );
+ _recurse = config->readBoolEntry ( "recurse" , false );
+ _askForConfirmation = config->readBoolEntry ( "askForConfirmation" , false );
+ _refreshPolicy = (KCleanup::RefreshPolicy) config->readNumEntry( "refreshPolicy" );
+ setTitle( config->readEntry( "title" ) );
+ }
+}
+
+
+void
+KCleanup::saveConfig() const
+{
+ KConfig *config = kapp->config();
+ KConfigGroupSaver saver( config, _id );
+
+ config->writeEntry( "valid", true );
+ config->writeEntry( "command", _command );
+ config->writeEntry( "title", _title );
+ config->writeEntry( "enabled", _enabled );
+ config->writeEntry( "worksForDir", _worksForDir );
+ config->writeEntry( "worksForFile", _worksForFile );
+ config->writeEntry( "worksForDotEntry", _worksForDotEntry );
+ config->writeEntry( "worksLocalOnly", _worksLocalOnly );
+ config->writeEntry( "recurse", _recurse );
+ config->writeEntry( "askForConfirmation", _askForConfirmation );
+ config->writeEntry( "refreshPolicy", (int) _refreshPolicy );
+}
+
+
+// EOF
diff --git a/kdirstat/kcleanup.h b/kdirstat/kcleanup.h
new file mode 100644
index 0000000..0cb7f13
--- /dev/null
+++ b/kdirstat/kcleanup.h
@@ -0,0 +1,360 @@
+/*
+ * File name: kcleanup.h
+ * Summary: Support classes for KDirStat
+ * License: LGPL - See file COPYING.LIB for details.
+ * Author: Stefan Hundhammer <sh@suse.de>
+ *
+ * Updated: 2003-01-07
+ */
+
+
+#ifndef KCleanup_h
+#define KCleanup_h
+
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+
+#include <qdict.h>
+#include <qptrlist.h>
+#include <qintdict.h>
+#include <kaction.h>
+#include <kdebug.h>
+#include "kdirtree.h"
+
+
+namespace KDirStat
+{
+ /**
+ * Cleanup action to be performed for @ref KDirTree items.
+ *
+ * @short KDirStat cleanup action
+ **/
+
+ class KCleanup: public KAction
+ {
+ Q_OBJECT
+
+ public:
+
+ enum RefreshPolicy { noRefresh, refreshThis, refreshParent, assumeDeleted };
+
+ /**
+ * Constructor.
+ *
+ * 'id' is the name of this cleanup action as used in the XML UI file
+ * and config files, 'title' is the human readable menu title.
+ * 'command' is the shell command to execute.
+ *
+ * Most applications will want to pass KMainWindow::actionCollection()
+ * for 'parent' so the menus and toolbars can be created using the XML
+ * UI description ('kdirstatui.rc' for KDirStat).
+ **/
+ KCleanup( QString id = "",
+ QString command = "",
+ QString title = "",
+ KActionCollection * parent = 0 );
+
+ /**
+ * Copy Constructor.
+ *
+ * Notice that this is a not quite complete copy constructor: Since
+ * there is no KAction copy constructor, the inherited KAction members
+ * will be constructed with the KAction default constructor. Thus, an
+ * object created with this copy constructor can rely only on its
+ * KCleanup members. This is intended for save/restore operations only,
+ * not for general use. In particular, DO NOT connect an object thus
+ * constructed with signals. The results will be undefined (at best).
+ **/
+ KCleanup( const KCleanup &src );
+
+ /**
+ * Assignment operator.
+ *
+ * This will not modify the KAction members, just the KCleanup
+ * members. Just like the copy constructor, this is intended for
+ * save/restore operations, not for general use.
+ **/
+ KCleanup & operator= ( const KCleanup &src );
+
+ /**
+ * Return the ID (name) of this cleanup action as used for setup files
+ * and the XML UI description. This ID should be unique within the
+ * application.
+ **/
+ const QString & id() const { return _id; }
+
+ /**
+ * Return the command line that will be executed upon calling @ref
+ * KCleanup::execute(). This command line may contain %p for the
+ * complete path of the directory or file concerned or %n for the pure
+ * file or directory name without path.
+ **/
+ const QString & command() const { return _command; }
+
+ /**
+ * Return the user title of this command as displayed in menus.
+ * This may include '&' characters for keyboard shortcuts.
+ * See also @ref cleanTitle() .
+ **/
+ const QString & title() const { return _title; }
+
+ /**
+ * Returns the cleanup action's title without '&' keyboard shortcuts.
+ * Uses the ID as fallback if the name is empty.
+ **/
+ QString cleanTitle() const;
+
+ /**
+ * Return whether or not this cleanup action is generally enabled.
+ **/
+ bool enabled() const { return _enabled; }
+
+ /**
+ * Return this cleanup's internally stored @ref KDirTree
+ * selection. Important only for copy constructor etc.
+ **/
+ KFileInfo * selection() const { return _selection; }
+
+ /**
+ * Return whether or not this cleanup action works for this particular
+ * KFileInfo. Checks all the other conditions (enabled(),
+ * worksForDir(), worksForFile(), ...) accordingly.
+ **/
+ bool worksFor( KFileInfo *item ) const;
+
+ /**
+ * Return whether or not this cleanup action works for directories,
+ * i.e. whether or not @ref KCleanup::execute() will be successful if
+ * the object passed is a directory.
+ **/
+ bool worksForDir() const { return _worksForDir; }
+
+ /**
+ * Return whether or not this cleanup action works for plain files.
+ **/
+ bool worksForFile() const { return _worksForFile; }
+
+ /**
+ * Return whether or not this cleanup action works for KDirStat's
+ * special 'Dot Entry' items, i.e. the pseudo nodes created in most
+ * directories that hold the plain files.
+ **/
+ bool worksForDotEntry() const { return _worksForDotEntry; }
+
+ /**
+ * Return whether or not this cleanup action works for simple local
+ * files and directories only ('file:/' protocol) or network
+ * transparent, i.e. all protocols KDE supports ('ftp', 'smb' - but
+ * even 'tar', even though it is - strictly spoken - local).
+ **/
+ bool worksLocalOnly() const { return _worksLocalOnly; }
+
+ /**
+ * Return whether or not the cleanup action should be performed
+ * recursively in subdirectories of the initial KFileInfo.
+ **/
+ bool recurse() const { return _recurse; }
+
+ /**
+ * Return whether or not this cleanup should ask the user for
+ * confirmation when it is executed.
+ *
+ * The default is 'false'. Use with caution - not only can this become
+ * very annoying, people also tend to automatically click on 'OK' when
+ * too many confirmation dialogs pop up!
+ **/
+ bool askForConfirmation() const { return _askForConfirmation; }
+
+ /**
+ * Return the refresh policy of this cleanup action - i.e. the action
+ * to perform after each call to KCleanup::execute(). This is supposed
+ * to bring the corresponding KDirTree back into sync after the cleanup
+ * action - the underlying file tree might have changed due to that
+ * cleanup action.
+ *
+ * noRefresh: Don't refresh anything. Assume nothing has changed.
+ * This is the default.
+ *
+ * refreshThis: Refresh the KDirTree from the item on that was passed
+ * to KCleanup::execute().
+ *
+ * refreshParent: Refresh the KDirTree from the parent of the item on
+ * that was passed to KCleanup::execute(). If there is no such parent,
+ * refresh the entire tree.
+ *
+ * assumeDeleted: Do not actually refresh the KDirTree. Instead,
+ * blindly assume the cleanup action has deleted the item that was
+ * passed to KCleanup::execute() and delete the corresponding subtree
+ * in the KDirTree accordingly. This will work well for most deleting
+ * actions as long as they can be performed without problems. If there
+ * are any problems, however, the KDirTree might easily run out of sync
+ * with the directory tree: The KDirTree will show the subtree as
+ * deleted (i.e. it will not show it any more), but it still exists on
+ * disk. This is the tradeoff to a very quick response. On the other
+ * hand, the user can easily at any time hit one of the explicit
+ * refresh buttons and everything will be back into sync again.
+ **/
+ enum RefreshPolicy refreshPolicy() const { return _refreshPolicy; }
+
+
+ void setTitle ( const QString &title );
+ void setId ( const QString &id ) { _id = id; }
+ void setCommand ( const QString &command) { _command = command; }
+ void setEnabled ( bool enabled ) { _enabled = enabled; }
+ void setWorksForDir ( bool canDo ) { _worksForDir = canDo; }
+ void setWorksForFile ( bool canDo ) { _worksForFile = canDo; }
+ void setWorksForDotEntry ( bool canDo ) { _worksForDotEntry = canDo; }
+ void setWorksLocalOnly ( bool canDo ) { _worksLocalOnly = canDo; }
+ void setRecurse ( bool recurse ) { _recurse = recurse; }
+ void setAskForConfirmation ( bool ask ) { _askForConfirmation = ask; }
+ void setRefreshPolicy ( enum RefreshPolicy refreshPolicy ) { _refreshPolicy = refreshPolicy; }
+
+
+ public slots:
+
+ /**
+ * The heart of the matter: Perform the cleanup with the KFileInfo
+ * specified.
+ **/
+ void execute( KFileInfo *item );
+
+ /**
+ * Perform the cleanup with the current KDirTree selection if there is
+ * any.
+ **/
+ void executeWithSelection();
+
+ /**
+ * Set enabled/disabled status according to 'selection' and internally
+ * store 'selection' - this will also be used upon calling
+ * @ref executeWithSelection() . '0' means "nothing selected".
+ **/
+ void selectionChanged( KFileInfo *selection );
+
+ /**
+ * Read configuration.
+ **/
+ void readConfig();
+
+ /**
+ * Save configuration.
+ **/
+ void saveConfig() const;
+
+
+ signals:
+
+ /**
+ * Emitted after the action is executed.
+ *
+ * Please note that there intentionally is no reference as to which
+ * object the action was executed upon since this object very likely
+ * doesn't exist any more.
+ **/
+ void executed();
+
+
+ protected slots:
+
+ /**
+ * Inherited from @ref KAction : Perform the action.
+ * In this case, execute the cleanup with the current selection.
+ **/
+ virtual void slotActivated() { executeWithSelection(); }
+
+
+ protected:
+
+ /**
+ * Recursively perform the cleanup.
+ **/
+ void executeRecursive( KFileInfo *item );
+
+ /**
+ * Ask user for confirmation to execute this cleanup action for
+ * 'item'. Returns 'true' if user accepts, 'false' otherwise.
+ **/
+ bool confirmation( KFileInfo *item );
+
+ /**
+ * Retrieve the directory part of a KFileInfo's path.
+ **/
+ const QString itemDir( const KFileInfo *item ) const;
+
+ /**
+ * Expand some variables in string 'unexpanded' to information from
+ * within 'item'. Multiple expansion is performed as needed, i.e. the
+ * string may contain more than one variable to expand. The resulting
+ * string is returned.
+ *
+ * %p expands to item->path(), i.e. the item's full path name.
+ *
+ * /usr/local/bin for that directory
+ * /usr/local/bin/doit for a file within it
+ *
+ * %n expands to item->name(), i.e. the last component of the pathname.
+ * The examples above would expand to:
+ *
+ * bin
+ * doit
+ *
+ * For commands that are to be executed from within the 'Clean up'
+ * menu, you might specify something like:
+ *
+ * "kfmclient openURL %p"
+ * "tar czvf %{name}.tgz && rm -rf %{name}"
+ **/
+ QString expandVariables ( const KFileInfo * item,
+ const QString & unexpanded ) const;
+
+ /**
+ * Run a command with 'item' as base to expand variables.
+ **/
+ void runCommand ( const KFileInfo * item,
+ const QString & command ) const;
+
+ /**
+ * Internal implementation of the copy constructor and assignment
+ * operator: Copy all data members from 'src'.
+ **/
+ void copy ( const KCleanup &src );
+
+
+ //
+ // Data members
+ //
+
+ KFileInfo * _selection;
+ QString _id;
+ QString _command;
+ QString _title;
+ bool _enabled;
+ bool _worksForDir;
+ bool _worksForFile;
+ bool _worksForDotEntry;
+ bool _worksLocalOnly;
+ bool _recurse;
+ bool _askForConfirmation;
+ enum RefreshPolicy _refreshPolicy;
+ };
+
+
+ inline kdbgstream & operator<< ( kdbgstream & stream, const KCleanup * cleanup )
+ {
+ if ( cleanup )
+ stream << cleanup->id();
+ else
+ stream << "<NULL>";
+
+ return stream;
+ }
+} // namespace KDirStat
+
+
+#endif // ifndef KCleanup_h
+
+
+// EOF
diff --git a/kdirstat/kcleanupcollection.cpp b/kdirstat/kcleanupcollection.cpp
new file mode 100644
index 0000000..fffa9b4
--- /dev/null
+++ b/kdirstat/kcleanupcollection.cpp
@@ -0,0 +1,283 @@
+/*
+ * File name: kcleanupcollection.cpp
+ * Summary: Support classes for KDirStat
+ * License: LGPL - See file COPYING.LIB for details.
+ * Author: Stefan Hundhammer <sh@suse.de>
+ *
+ * Updated: 2004-11-23
+ */
+
+
+#include <klocale.h>
+#include "kcleanup.h"
+#include "kstdcleanup.h"
+#include "kcleanupcollection.h"
+
+
+using namespace KDirStat;
+
+
+KCleanupCollection::KCleanupCollection( KActionCollection * actionCollection )
+ : QObject()
+ , _actionCollection( actionCollection )
+{
+ /**
+ * All cleanups beloningt to this collection are stored in two separate Qt
+ * collections, a QList and a QDict. Make _one_ of them manage the cleanup
+ * objects, i.e. have them clear the KCleanup objects upon deleting. The
+ * QList is the master collection, the QDict the slave.
+ **/
+
+ _cleanupList.setAutoDelete( true );
+ _cleanupDict.setAutoDelete( false );
+
+ _nextUserCleanupNo = 0;
+}
+
+
+KCleanupCollection::KCleanupCollection( const KCleanupCollection &src )
+ : QObject()
+{
+ deepCopy( src );
+
+ // Keep consistent with the KCleanup copy constructor: It explicitly uses a
+ // zero KActionCollecton to make sure no duplicates of cleanups get into
+ // the action collection.
+ _actionCollection = 0;
+}
+
+
+KCleanupCollection::~KCleanupCollection()
+{
+ // No need to delete the cleanups: _cleanupList takes care of that
+ // (autoDelete!).
+}
+
+
+KCleanupCollection &
+KCleanupCollection::operator= ( const KCleanupCollection &src )
+{
+ if ( size() != src.size() )
+ {
+ /**
+ * If the sizes are different, we really need to make a deep copy -
+ * i.e. discard all the existing cleanups in this collection and create
+ * new ones with the KCleanup copy constructor.
+ **/
+
+ // kdDebug() << k_funcinfo << "Sizes different - deep copy" << endl;
+
+ deepCopy( src );
+ }
+ else
+ {
+ /**
+ * If the sizes are the same, we'd rather just use the KCleanup
+ * assignment operator to individually assign each cleanup in the
+ * source collection to the corresponding one in this collection.
+ *
+ * The background of this seemingly awkward solution are (again) the
+ * limitations of the KCleanup copy constructor: It doesn't make a
+ * truly identical copy of the entire KCleanup object. Rather, it
+ * copies only the KCleanup members and leaves most of the KAction
+ * members (the parent class) untouched.
+ *
+ * The behaviour implemented here comes handy in the most common
+ * situation where this assignment operator is used:
+ *
+ * KCleanupCollection tmpCollection( origCollection );
+ * ...
+ * ... // let use change settings in settings dialog
+ * ...
+ * origCollection = tmpCollection;
+ *
+ * 'tmpCollection' here is an incomplete copy of 'origCollection' -
+ * which represents what the user really can see in the menus, i.e. all
+ * the KAction stuff in there really needs to work.
+ *
+ * During changing preferences in the 'settings' dialog, the user only
+ * changes 'tmpCollection' - if he chooses to abandon his changes
+ * (e.g., he clicks on the 'cancel' button), no harm is done -
+ * 'tmpCollection' is simply not copied back to
+ * 'origCollection'. Anyway, since 'tmpCollection' is merely a
+ * container for the true KCleanup members, the KAction members don't
+ * matter here: There is no representation of 'tmpCollection' in any
+ * menu or tool bar.
+ *
+ * As soon as the user clicks on 'apply' or 'ok' in the 'settings'
+ * dialog, however, 'tmpCollection' is copied back to 'origCollection'
+ * - that is, its KCleanup members. Most of the KAction members (other
+ * than 'text()' which is explicitly copied back) remain untouched,
+ * thus maintaining consistency with the user interface is guaranteed.
+ **/
+
+ // kdDebug() << k_funcinfo << "Same sizes - individual assignment" << endl;
+
+ KCleanupList srcList = src.cleanupList();
+ KCleanupListIterator srcIt( srcList );
+ KCleanupListIterator destIt( _cleanupList );
+
+ while ( *srcIt && *destIt )
+ {
+ // kdDebug() << "Assigning " << *srcIt << endl;
+ **destIt = **srcIt;
+ ++srcIt;
+ ++destIt;
+ }
+ }
+
+ // Intentionally leaving '_actionCollection' untouched!
+
+ return *this;
+}
+
+
+void
+KCleanupCollection::deepCopy( const KCleanupCollection &src )
+{
+ // Copy simple values
+ _nextUserCleanupNo = src.nextUserCleanupNo();
+
+ // Just to make sure - clear the internal collections
+ _cleanupList.clear();
+ _cleanupDict.clear();
+
+
+ // Make a deep copy of all the cleanups in the source collection
+
+ KCleanupList srcList = src.cleanupList();
+ KCleanupListIterator it( srcList );
+
+ while ( *it )
+ {
+ // kdDebug() << k_funcinfo << "Creating new " << *it << endl;
+
+ add( new KCleanup( **it ) );
+ ++it;
+ }
+}
+
+
+void
+KCleanupCollection::add( KCleanup *newCleanup )
+{
+ CHECK_PTR( newCleanup );
+
+ if ( _cleanupDict[ newCleanup->id() ] ) // Already there?
+ {
+ // Delete any old instance in the list.
+ //
+ // The instance in the dict will be deleted automatically by inserting
+ // the new one.
+
+ _cleanupList.first(); // Moves _cleanupList.current() to beginning
+
+ while ( _cleanupList.current() )
+ {
+ if ( _cleanupList.current()->id() == newCleanup->id() )
+ {
+ // Found a cleanup with the same ID -
+ // remove the current list item, delete it (autoDelete!) and
+ // move _cleanupList.current() to the next item.
+
+ _cleanupList.remove();
+ }
+ else
+ _cleanupList.next();
+ }
+ }
+
+ _cleanupList.append( newCleanup );
+ _cleanupDict.insert( newCleanup->id(), newCleanup );
+
+ connect( this, SIGNAL( selectionChanged( KFileInfo * ) ),
+ newCleanup, SLOT ( selectionChanged( KFileInfo * ) ) );
+
+ connect( this, SIGNAL( readConfig() ),
+ newCleanup, SLOT ( readConfig() ) );
+
+ connect( this, SIGNAL( saveConfig() ),
+ newCleanup, SLOT ( saveConfig() ) );
+
+ connect( newCleanup, SIGNAL( executed() ),
+ this, SLOT ( cleanupExecuted() ) );
+}
+
+
+void
+KCleanupCollection::addStdCleanups()
+{
+ add( KStdCleanup::openInKonqueror ( _actionCollection ) );
+ add( KStdCleanup::openInTerminal ( _actionCollection ) );
+ add( KStdCleanup::compressSubtree ( _actionCollection ) );
+ add( KStdCleanup::makeClean ( _actionCollection ) );
+ add( KStdCleanup::deleteTrash ( _actionCollection ) );
+ add( KStdCleanup::moveToTrashBin ( _actionCollection ) );
+ add( KStdCleanup::hardDelete ( _actionCollection ) );
+}
+
+
+void
+KCleanupCollection::addUserCleanups( int number )
+{
+ for ( int i=0; i < number; i++ )
+ {
+ QString id;
+ id.sprintf( "cleanup_user_defined_%d", _nextUserCleanupNo );
+ QString title;
+
+ if ( _nextUserCleanupNo <= 9 )
+ // Provide a keyboard shortcut for cleanup #0..#9
+ title=i18n( "User Defined Cleanup #&%1" ).arg(_nextUserCleanupNo);
+ else
+ // No keyboard shortcuts for cleanups #10.. - they would be duplicates
+ title=i18n( "User Defined Cleanup #%1" ).arg(_nextUserCleanupNo);
+
+ _nextUserCleanupNo++;
+
+ KCleanup *cleanup = new KCleanup( id, "", title, _actionCollection );
+ CHECK_PTR( cleanup );
+ cleanup->setEnabled( false );
+
+ if ( i <= 9 )
+ {
+ // Provide an application-wide keyboard accelerator for cleanup #0..#9
+ cleanup->setShortcut( Qt::CTRL + Qt::Key_0 + i );
+ }
+
+ add( cleanup );
+ }
+}
+
+
+KCleanup *
+KCleanupCollection::cleanup( const QString & id )
+{
+ return _cleanupDict[ id ];
+}
+
+
+void
+KCleanupCollection::clear()
+{
+ _cleanupList.clear();
+ _cleanupDict.clear();
+ _nextUserCleanupNo = 0;
+}
+
+
+void
+KCleanupCollection::slotReadConfig()
+{
+ emit readConfig();
+}
+
+
+void
+KCleanupCollection::cleanupExecuted()
+{
+ emit userActivity( 10 );
+}
+
+
+// EOF
diff --git a/kdirstat/kcleanupcollection.h b/kdirstat/kcleanupcollection.h
new file mode 100644
index 0000000..74f0900
--- /dev/null
+++ b/kdirstat/kcleanupcollection.h
@@ -0,0 +1,225 @@
+/*
+ * File name: kcleanupcollection.h
+ * Summary: Support classes for KDirStat
+ * License: LGPL - See file COPYING.LIB for details.
+ * Author: Stefan Hundhammer <sh@suse.de>
+ *
+ * Updated: 2003-01-07
+ */
+
+
+#ifndef KCleanupCollection_h
+#define KCleanupCollection_h
+
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+
+#include "kcleanup.h"
+
+// Forward declarations
+class KActionCollection;
+
+
+namespace KDirStat
+{
+ typedef QDict<KCleanup> KCleanupDict;
+ typedef QDictIterator<KCleanup> KCleanupDictIterator;
+
+ typedef QPtrList<KCleanup> KCleanupList;
+ typedef QPtrListIterator<KCleanup> KCleanupListIterator;
+
+
+ /**
+ * Set of @ref KCleanup actions to be performed for @ref KDirTree items,
+ * consisting of a number of predefined and a number of user-defined
+ * cleanups. The prime purpose of this is to make save/restore operations
+ * with a number of cleanups easier. Thus, it provides a copy constructor,
+ * an assignment operator and various methods to directly access individual
+ * cleanups.
+ *
+ * @short KDirStat cleanup action collection
+ **/
+
+ class KCleanupCollection: public QObject
+ {
+ Q_OBJECT
+
+ public:
+
+ /**
+ * Constructor.
+ *
+ * Most applications will want to pass KMainWindow::actionCollection()
+ * for 'actionCollection' so the menus and toolbars can be created
+ * using the XML UI description ('kdirstatui.rc' for KDirStat).
+ *
+ * All @ref KCleanup actions ever added to this collection will get
+ * this as their parent.
+ **/
+ KCleanupCollection( KActionCollection * actionCollection = 0 );
+
+ /**
+ * Copy Constructor.
+ *
+ * Makes a deep copy of this collection with 'actionCollection' set to
+ * 0 for all copied cleanups. Please note that since there is no
+ * complete copy constructor for @ref KCleanup, all restrictions to the
+ * @ref KCleanup copy constructor apply to the KCleanupCollection, too:
+ * This copy constructor is intended for save/restore operations only,
+ * not for general use. In particular, DO NOT connect an object thus
+ * constructed with signals. The results will be undefined (at best).
+ **/
+ KCleanupCollection( const KCleanupCollection &src );
+
+ /**
+ * Assignment operator.
+ *
+ * This operator has the same restrictions as the copy constructor:
+ * Just like the copy constructor, this is intended for save/restore
+ * operations, not for general use.
+ *
+ * For details, see the extensive comments in the source file.
+ **/
+ KCleanupCollection & operator= ( const KCleanupCollection &src );
+
+ /**
+ * Destructor
+ **/
+ virtual ~KCleanupCollection();
+
+ /**
+ * Add the standard cleanups to this collection.
+ **/
+ void addStdCleanups();
+
+ /**
+ * Add 'number' user-defined cleanups to this collection.
+ **/
+ void addUserCleanups( int number );
+
+ /**
+ * Add one single cleanup to this collection. The collection assumes
+ * ownerwhip of this cleanup - don't delete it!
+ **/
+ void add( KCleanup *cleanup );
+
+ /**
+ * Retrieve a cleanup by its ID (internal name).
+ * Returns 0 if there is no such cleanup.
+ **/
+ KCleanup * cleanup( const QString & id );
+
+ /**
+ * An alias to @ref cleanup() for convenience: Thus, you can use
+ * collection[ "cleanup_id" ] to access any particular cleanup.
+ **/
+ KCleanup * operator[] ( const QString & id )
+ { return cleanup( id ); }
+
+ /**
+ * Remove all cleanups from this collection.
+ **/
+ void clear();
+
+ /**
+ * Return (a shallow copy of) the internal cleanup list.
+ *
+ * Use this and a KCleanupListIterator to iterate over all cleanups in
+ * this collection. Remember to keep the list until you no longer need
+ * the iterator!
+ *
+ * KCleanupCollection *coll = ...
+ * KCleanupList cleanup_list = coll->cleanupList();
+ * KCleanupListIterator it( cleanup_list );
+ *
+ * while ( *it )
+ * {
+ * kdDebug() << "Found cleanup " << *it << endl;
+ * ++it;
+ * }
+ **/
+ KCleanupList cleanupList() const { return _cleanupList; }
+
+ /**
+ * Return the number of cleanup actions in this collection.
+ **/
+ int size() const { return _cleanupList.count(); }
+
+ /**
+ * For internal use only: Returns the number to be assigned to the next
+ * user cleanup that may be added.
+ **/
+ int nextUserCleanupNo() const { return _nextUserCleanupNo; }
+
+ public slots:
+
+ /**
+ * Emit the readConfig() signal for all cleanups.
+ **/
+ void slotReadConfig();
+
+
+ signals:
+
+ /**
+ * Emitted when the currently selected item changes.
+ * 'item' may be 0 when the selection is cleared.
+ *
+ * Connect a view's selectionChanged() signal to this
+ * selectionChanged() signal to have the cleanup collection pass this
+ * signal to its cleanups.
+ **/
+ void selectionChanged( KFileInfo *item );
+
+ /**
+ * Read collection for all cleanups.
+ **/
+ void readConfig();
+
+ /**
+ * Save configuration for all cleanups.
+ **/
+ void saveConfig();
+
+ /**
+ * Emitted at user activity, i.e. when the user executes a cleanup.
+ * This is intended for use together with a @ref KActivityTracker.
+ **/
+ void userActivity( int points );
+
+
+ protected slots:
+
+ /**
+ * Connected to each cleanup's @ref executed() signal to track user
+ * activity.
+ **/
+ void cleanupExecuted();
+
+
+ protected:
+
+ /**
+ * Internal implementation of copy constructor and assignment operator:
+ * Make a deep copy of the collection.
+ **/
+ void deepCopy( const KCleanupCollection &src );
+
+
+ // Data members
+
+ KActionCollection * _actionCollection;
+ int _nextUserCleanupNo;
+ KCleanupList _cleanupList;
+ KCleanupDict _cleanupDict;
+ };
+} // namespace KDirStat
+
+
+#endif // ifndef KCleanupCollection_h
+
+
+// EOF
diff --git a/kdirstat/kdirsaver.cpp b/kdirstat/kdirsaver.cpp
new file mode 100644
index 0000000..2be9074
--- /dev/null
+++ b/kdirstat/kdirsaver.cpp
@@ -0,0 +1,71 @@
+/*
+ * File name: kdirsaver.cpp
+ * Summary: Utility object to save current working directory
+ * License: LGPL - See file COPYING.LIB for details.
+ * Author: Stefan Hundhammer <sh@suse.de>
+ *
+ * Updated: 2003-01-07
+ */
+
+
+#include <unistd.h>
+#include <kdebug.h>
+#include "kdirsaver.h"
+
+
+KDirSaver::KDirSaver( const QString & newPath )
+{
+ /*
+ * No need to actually save the current working directory: This object
+ * includes a QDir whose default constructor constructs a directory object
+ * that contains the current working directory. Just what is needed here.
+ */
+
+ cd( newPath );
+}
+
+
+KDirSaver::KDirSaver( const KURL & url )
+{
+ if ( url.isLocalFile() )
+ {
+ cd( url.path() );
+ }
+ else
+ {
+ kdError() << k_funcinfo << "Can't change dir to remote location " << url.url() << endl;
+ }
+}
+
+
+KDirSaver::~KDirSaver()
+{
+ restore();
+}
+
+
+void
+KDirSaver::cd( const QString & newPath )
+{
+ if ( ! newPath.isEmpty() )
+ {
+ chdir( newPath );
+ }
+}
+
+
+QString
+KDirSaver::currentDirPath() const
+{
+ return QDir::currentDirPath();
+}
+
+
+void
+KDirSaver::restore()
+{
+ chdir( oldWorkingDir.path() );
+}
+
+
+// EOF
diff --git a/kdirstat/kdirsaver.h b/kdirstat/kdirsaver.h
new file mode 100644
index 0000000..4fbe6e2
--- /dev/null
+++ b/kdirstat/kdirsaver.h
@@ -0,0 +1,75 @@
+/*
+ * File name: kdirsaver.h
+ * Summary: Utility object to save current working directory
+ * License: LGPL - See file COPYING.LIB for details.
+ * Author: Stefan Hundhammer <sh@suse.de>
+ *
+ * Updated: 2003-01-07
+ */
+
+
+#ifndef KDirSaver_h
+#define KDirSaver_h
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <kurl.h>
+#include <qdir.h>
+
+
+/**
+ * Helper class to change directories without losing the current context.
+ * Will change back to the old working directory when destroyed.
+ *
+ * @short Directory changer with automatic restore
+ **/
+class KDirSaver
+{
+public:
+ /**
+ * Constructor. Will save the current working directory and change to the
+ * path supplied. The old working directory will be restored when this
+ * object is destroyed.
+ **/
+ KDirSaver( const QString & newPath = "" );
+
+ /**
+ * Constructor from a KURL. Will issue error messages on stdout for
+ * non-local objects.
+ **/
+ KDirSaver( const KURL & url );
+
+ /**
+ * Destructor. Restores the original working directory.
+ **/
+ virtual ~KDirSaver();
+
+ /**
+ * Change directory. Unlike @ref QDir::cd(), this method really performs a
+ * system chdir() so subsequent system calls will have the directory
+ * specified as the new current working directory.
+ **/
+ void cd( const QString & newPath );
+
+ /**
+ * Obtain the current working directory's absolute path.
+ * This is useful for resolving/simplifying relative paths.
+ **/
+ QString currentDirPath() const;
+
+ /**
+ * (Prematurely) restore the working directory. Unnecessary when this
+ * object will be destroyed anyway since the destructor does exactly that.
+ **/
+ void restore();
+
+protected:
+ QDir oldWorkingDir;
+};
+
+#endif // KDirSaver_h
+
+
+// EOF
diff --git a/kdirstat/kdirstat.desktop b/kdirstat/kdirstat.desktop
new file mode 100644
index 0000000..77f815a
--- /dev/null
+++ b/kdirstat/kdirstat.desktop
@@ -0,0 +1,20 @@
+# KDE Config File
+[Desktop Entry]
+Type=Application
+Exec=kdirstat -caption "%c" %i %m
+Icon=kdirstat.png
+MiniIcon=kdirstat.png
+DocPath=kdirstat/index.html
+Encoding=UTF-8
+Comment=Directory statistics and disk usage
+Comment[de]=Verzeichnisstatistik und Platzverbrauch
+Comment[hu]=Könyvtárstatisztikák és szabad hely
+Terminal=0
+Name=KDirStat - Directory Statistics
+Name[de]=KDirStat - Verzeichnisstatistik
+Name[hu]=KDirStat könyvtárstatisztika
+
+MimeType=inode/directory
+
+
+
diff --git a/kdirstat/kdirstat.upd b/kdirstat/kdirstat.upd
new file mode 100644
index 0000000..13520f1
--- /dev/null
+++ b/kdirstat/kdirstat.upd
@@ -0,0 +1,6 @@
+# Update for KDirStat configuration
+Id=kdirstat_2004_11_24_11_36
+File=kdirstatrc
+Group=cleanup_move_to_trash_bin
+Options=overwrite
+Script=fix_move_to_trash_bin.pl,perl
diff --git a/kdirstat/kdirstat_part.rc b/kdirstat/kdirstat_part.rc
new file mode 100644
index 0000000..fce2dcc
--- /dev/null
+++ b/kdirstat/kdirstat_part.rc
@@ -0,0 +1,14 @@
+<!DOCTYPE kpartgui SYSTEM "kpartgui.dtd">
+
+<kpartgui name="KDirStatPart" version="1">
+
+<MenuBar>
+ <Menu name="Edit"><Text>&amp;Edit</Text>
+ <Action name="selectall"/>
+ </Menu>
+</MenuBar>
+
+<StatusBar/>
+
+</kpartgui>
+
diff --git a/kdirstat/kdirstatapp.cpp b/kdirstat/kdirstatapp.cpp
new file mode 100644
index 0000000..22726e9
--- /dev/null
+++ b/kdirstat/kdirstatapp.cpp
@@ -0,0 +1,847 @@
+/*
+ * File name: kdirstatapp.cpp
+ * Summary: The KDirStat application - menu bar, tool bar, ...
+ * License: GPL - See file COPYING for details.
+ *
+ * Author: Stefan Hundhammer <sh@suse.de>
+ * Parts auto-generated by KDevelop
+ *
+ * Updated: 2004-12-06
+ */
+
+
+#include <qclipboard.h>
+#include <qpopupmenu.h>
+#include <qsplitter.h>
+
+#include <kaccel.h>
+#include <kaction.h>
+#include <kapp.h>
+#include <kconfig.h>
+#include <kfiledialog.h>
+#include <kiconloader.h>
+#include <klocale.h>
+#include <kmenubar.h>
+#include <kmessagebox.h>
+#include <krun.h>
+#include <kstatusbar.h>
+#include <kstdaction.h>
+#include <kurlrequesterdlg.h>
+
+#include "kdirstatapp.h"
+#include "kcleanupcollection.h"
+#include "kdirtree.h"
+#include "kpacman.h"
+#include "ktreemapview.h"
+#include "ktreemaptile.h"
+#include "kcleanupcollection.h"
+#include "kactivitytracker.h"
+#include "kdirtreeview.h"
+#include "kdirstatsettings.h"
+
+
+#define USER_CLEANUPS 10 // Number of user cleanup actions
+
+#define ID_STATUS_MSG 1
+#define ID_PACMAN 42
+#define PACMAN_WIDTH 350
+#define PACMAN_INTERVAL 75 // millisec
+
+#define INITIAL_FEEDBACK_REMINDER 2000L
+#define FEEDBACK_REMINDER_INTERVAL 1000L
+
+
+using namespace KDirStat;
+
+
+KDirStatApp::KDirStatApp( QWidget* , const char* name )
+ : KMainWindow( 0, name )
+{
+ // Simple inits
+
+ _activityTracker = 0; // Might or might not be needed
+
+
+ // Those will be created delayed, only when needed
+
+ _settingsDialog = 0;
+ _feedbackDialog = 0;
+ _treemapView = 0;
+ _pacMan = 0;
+ _pacManDelimiter = 0;
+
+
+ // Set up internal (mainWin -> mainWin) connections
+
+ connect( this, SIGNAL( readConfig ( void ) ),
+ this, SLOT ( readMainWinConfig( void ) ) );
+
+ connect( this, SIGNAL( saveConfig ( void ) ),
+ this, SLOT ( saveMainWinConfig( void ) ) );
+
+
+ // Create main window
+
+ _splitter = new QSplitter( QSplitter::Vertical, this );
+ setCentralWidget( _splitter );
+
+ _treeView = new KDirTreeView( _splitter );
+
+ connect( _treeView, SIGNAL( progressInfo( const QString & ) ),
+ this, SLOT ( statusMsg ( const QString & ) ) );
+
+ connect( _treeView, SIGNAL( selectionChanged( KFileInfo * ) ),
+ this, SLOT ( selectionChanged( KFileInfo * ) ) );
+
+ connect( _treeView, SIGNAL( contextMenu( KDirTreeViewItem *, const QPoint & ) ),
+ this, SLOT ( contextMenu( KDirTreeViewItem *, const QPoint & ) ) );
+
+ connect( this, SIGNAL( readConfig() ), _treeView, SLOT ( readConfig() ) );
+ connect( this, SIGNAL( saveConfig() ), _treeView, SLOT ( saveConfig() ) );
+
+ connect( _treeView, SIGNAL( finished() ), this, SLOT( createTreemapView() ) );
+ connect( _treeView, SIGNAL( aborted() ), this, SLOT( createTreemapView() ) );
+ connect( _treeView, SIGNAL( startingReading() ), this, SLOT( deleteTreemapView() ) );
+
+ connect( _treeView, SIGNAL( startingReading() ), this, SLOT( updateActions() ) );
+ connect( _treeView, SIGNAL( finished() ), this, SLOT( updateActions() ) );
+ connect( _treeView, SIGNAL( aborted() ), this, SLOT( updateActions() ) );
+
+ // Call inits to invoke all other construction parts
+
+ initStatusBar();
+ initActions();
+ initCleanups();
+ createGUI();
+ initActivityTracker();
+
+ _treeViewContextMenu = (QPopupMenu *) factory()->container( "treeViewContextMenu", this );
+ _treemapContextMenu = (QPopupMenu *) factory()->container( "treemapContextMenu", this );
+
+ readMainWinConfig();
+
+
+ // Disable certain actions at startup
+
+ _editCopy->setEnabled( false );
+ _reportMailToOwner->setEnabled( false );
+ _fileRefreshAll->setEnabled( false );
+ _fileRefreshSelected->setEnabled( false );
+ updateActions();
+}
+
+
+KDirStatApp::~KDirStatApp()
+{
+ delete _cleanupCollection;
+}
+
+
+
+void
+KDirStatApp::initActions()
+{
+ _fileAskOpenDir = KStdAction::open ( this, SLOT( fileAskOpenDir() ), actionCollection() );
+
+ _fileAskOpenUrl = new KAction( i18n( "Open &URL..." ), "konqueror", 0,
+ this, SLOT( fileAskOpenUrl() ),
+ actionCollection(), "file_open_url" );
+
+ _fileOpenRecent = KStdAction::openRecent ( this, SLOT( fileOpenRecent( const KURL& ) ), actionCollection() );
+ _fileCloseDir = KStdAction::close ( this, SLOT( fileCloseDir() ), actionCollection() );
+
+ _fileRefreshAll = new KAction( i18n( "Refresh &All" ), "reload", 0,
+ this, SLOT( refreshAll() ),
+ actionCollection(), "file_refresh_all" );
+
+ _fileRefreshSelected = new KAction( i18n( "Refresh &Selected" ), 0,
+ this, SLOT( refreshSelected() ),
+ actionCollection(), "file_refresh_selected" );
+
+ _fileContinueReadingAtMountPoint = new KAction( i18n( "Continue Reading at &Mount Point" ), "hdd_mount", 0,
+ this, SLOT( refreshSelected() ), actionCollection(),
+ "file_continue_reading_at_mount_point" );
+
+ _fileStopReading = new KAction( i18n( "Stop Rea&ding" ), "stop", 0,
+ this, SLOT( stopReading() ), actionCollection(),
+ "file_stop_reading" );
+
+ _fileQuit = KStdAction::quit ( kapp, SLOT( quit() ), actionCollection() );
+ _editCopy = KStdAction::copy ( this, SLOT( editCopy() ), actionCollection() );
+ _showToolBar = KStdAction::showToolbar ( this, SLOT( toggleToolBar() ), actionCollection() );
+ _showStatusBar = KStdAction::showStatusbar ( this, SLOT( toggleStatusBar() ), actionCollection() );
+
+ _cleanupOpenWith = new KAction( i18n( "Open With" ), 0,
+ this, SLOT( cleanupOpenWith() ),
+ actionCollection(), "cleanup_open_with" );
+
+ _treemapZoomIn = new KAction( i18n( "Zoom in" ), "viewmag+", Key_Plus,
+ this, SLOT( treemapZoomIn() ),
+ actionCollection(), "treemap_zoom_in" );
+
+ _treemapZoomOut = new KAction( i18n( "Zoom out" ), "viewmag-", Key_Minus,
+ this, SLOT( treemapZoomOut() ),
+ actionCollection(), "treemap_zoom_out" );
+
+ _treemapSelectParent= new KAction( i18n( "Select Parent" ), "up", Key_Asterisk,
+ this, SLOT( treemapSelectParent() ),
+ actionCollection(), "treemap_select_parent" );
+
+ _treemapRebuild = new KAction( i18n( "Rebuild Treemap" ), 0,
+ this, SLOT( treemapRebuild() ),
+ actionCollection(), "treemap_rebuild" );
+
+ _showTreemapView = new KToggleAction( i18n( "Show Treemap" ), Key_F9,
+ this, SLOT( toggleTreemapView() ),
+ actionCollection(), "options_show_treemap" );
+
+ new KAction( i18n( "Help about Treemaps" ), "help", 0,
+ this, SLOT( treemapHelp() ),
+ actionCollection(), "treemap_help" );
+
+ KAction * pref = KStdAction::preferences( this, SLOT( preferences() ), actionCollection() );
+
+ _reportMailToOwner = new KAction( i18n( "Send &Mail to Owner" ), "mail_generic", 0,
+ _treeView, SLOT( sendMailToOwner() ),
+ actionCollection(), "report_mail_to_owner" );
+
+ _helpSendFeedbackMail = new KAction( i18n( "Send &Feedback Mail..." ), 0,
+ this, SLOT( sendFeedbackMail() ),
+ actionCollection(), "help_send_feedback_mail" );
+
+
+ _fileAskOpenDir->setStatusText ( i18n( "Opens a directory" ) );
+ _fileAskOpenUrl->setStatusText ( i18n( "Opens a (possibly remote) directory" ) );
+ _fileOpenRecent->setStatusText ( i18n( "Opens a recently used directory" ) );
+ _fileCloseDir->setStatusText ( i18n( "Closes the current directory" ) );
+ _fileRefreshAll->setStatusText ( i18n( "Re-reads the entire directory tree" ) );
+ _fileRefreshSelected->setStatusText ( i18n( "Re-reads the selected subtree" ) );
+ _fileContinueReadingAtMountPoint->setStatusText( i18n( "Scan mounted file systems" ) );
+ _fileStopReading->setStatusText ( i18n( "Stops directory reading" ) );
+ _fileQuit->setStatusText ( i18n( "Quits the application" ) );
+ _editCopy->setStatusText ( i18n( "Copies the URL of the selected item to the clipboard" ) );
+ _showToolBar->setStatusText ( i18n( "Enables/disables the toolbar" ) );
+ _showStatusBar->setStatusText ( i18n( "Enables/disables the statusbar" ) );
+ _cleanupOpenWith->setStatusText ( i18n( "Open file or directory with arbitrary application" ) );
+ _showTreemapView->setStatusText ( i18n( "Enables/disables the treemap view" ) );
+ _treemapZoomIn->setStatusText ( i18n( "Zoom treemap in" ) );
+ _treemapZoomOut->setStatusText ( i18n( "Zoom treemap out" ) );
+ _treemapSelectParent->setStatusText ( i18n( "Select parent" ) );
+ _treemapRebuild->setStatusText ( i18n( "Rebuild treemap to fit into available space" ) );
+ pref->setStatusText ( i18n( "Opens the preferences dialog" ) );
+ _reportMailToOwner->setStatusText ( i18n( "Sends a mail to the owner of the selected subtree" ) );
+}
+
+
+void
+KDirStatApp::initCleanups()
+{
+ _cleanupCollection = new KCleanupCollection( actionCollection() );
+ CHECK_PTR( _cleanupCollection );
+ _cleanupCollection->addStdCleanups();
+ _cleanupCollection->addUserCleanups( USER_CLEANUPS );
+ _cleanupCollection->slotReadConfig();
+
+ connect( _treeView, SIGNAL( selectionChanged( KFileInfo * ) ),
+ _cleanupCollection, SIGNAL( selectionChanged( KFileInfo * ) ) );
+
+ connect( this, SIGNAL( readConfig( void ) ),
+ _cleanupCollection, SIGNAL( readConfig( void ) ) );
+
+ connect( this, SIGNAL( saveConfig( void ) ),
+ _cleanupCollection, SIGNAL( saveConfig( void ) ) );
+}
+
+
+void
+KDirStatApp::revertCleanupsToDefaults()
+{
+ KCleanupCollection defaultCollection;
+ defaultCollection.addStdCleanups();
+ defaultCollection.addUserCleanups( USER_CLEANUPS );
+ *_cleanupCollection = defaultCollection;
+}
+
+
+void
+KDirStatApp::initPacMan( bool enablePacMan )
+{
+ if ( enablePacMan )
+ {
+ if ( ! _pacMan )
+ {
+ _pacMan = new KPacMan( toolBar(), 16, false, "kde toolbar widget" );
+ _pacMan->setInterval( PACMAN_INTERVAL ); // millisec
+ int id = ID_PACMAN;
+ toolBar()->insertWidget( id, PACMAN_WIDTH, _pacMan );
+ toolBar()->setItemAutoSized( id, false );
+
+ _pacManDelimiter = new QWidget( toolBar() );
+ toolBar()->insertWidget( ++id, 1, _pacManDelimiter );
+
+ connect( _treeView, SIGNAL( startingReading() ), _pacMan, SLOT( start() ) );
+ connect( _treeView, SIGNAL( finished() ), _pacMan, SLOT( stop () ) );
+ connect( _treeView, SIGNAL( aborted() ), _pacMan, SLOT( stop () ) );
+ }
+ }
+ else
+ {
+ if ( _pacMan )
+ {
+ delete _pacMan;
+ _pacMan = 0;
+ }
+
+ if ( _pacManDelimiter )
+ {
+ delete _pacManDelimiter;
+ _pacManDelimiter = 0;
+ }
+ }
+}
+
+
+void
+KDirStatApp::initStatusBar()
+{
+ statusBar()->insertItem( i18n( "Ready." ), ID_STATUS_MSG );
+}
+
+
+void
+KDirStatApp::initActivityTracker()
+{
+ if ( ! doFeedbackReminder() )
+ return;
+
+ _activityTracker = new KActivityTracker( this, "Feedback",
+ INITIAL_FEEDBACK_REMINDER );
+
+ connect( _activityTracker, SIGNAL( thresholdReached() ),
+ this, SLOT ( askForFeedback() ) );
+
+ connect( _treeView, SIGNAL( userActivity( int ) ),
+ _activityTracker, SLOT ( trackActivity( int ) ) );
+
+ connect( _cleanupCollection, SIGNAL( userActivity( int ) ),
+ _activityTracker, SLOT ( trackActivity( int ) ) );
+}
+
+
+void
+KDirStatApp::openURL( const KURL& url )
+{
+ statusMsg( i18n( "Opening directory..." ) );
+
+ _treeView->openURL( url );
+ _fileOpenRecent->addURL( url );
+ _fileRefreshAll->setEnabled( true );
+ setCaption( url.fileName(), false );
+
+ statusMsg( i18n( "Ready." ) );
+}
+
+
+void KDirStatApp::readMainWinConfig()
+{
+
+ KConfig * config = kapp->config();
+ config->setGroup( "General Options" );
+
+ // Status settings of the various bars and views
+
+ _showToolBar->setChecked( config->readBoolEntry( "Show Toolbar", true ) );
+ toggleToolBar();
+
+ _showStatusBar->setChecked( config->readBoolEntry( "Show Statusbar", true ) );
+ toggleStatusBar();
+
+ _showTreemapView->setChecked( config->readBoolEntry( "Show Treemap", true ) );
+ toggleTreemapView();
+
+
+ // Position settings of the various bars
+
+ KToolBar::BarPosition toolBarPos;
+ toolBarPos = ( KToolBar::BarPosition ) config->readNumEntry( "ToolBarPos", KToolBar::Top );
+ toolBar( "mainToolBar" )->setBarPos( toolBarPos );
+
+ _treemapViewHeight = config->readNumEntry( "TreemapViewHeight", 250 );
+
+ // initialize the recent file list
+ _fileOpenRecent->loadEntries( config,"Recent Files" );
+
+ QSize size = config->readSizeEntry( "Geometry" );
+
+ if( ! size.isEmpty() )
+ resize( size );
+
+ config->setGroup( "Animation" );
+ initPacMan( config->readBoolEntry( "ToolbarPacMan", true ) );
+ _treeView->enablePacManAnimation( config->readBoolEntry( "DirTreePacMan", false ) );
+}
+
+
+void
+KDirStatApp::saveMainWinConfig()
+{
+ KConfig * config = kapp->config();
+
+ config->setGroup( "General Options" );
+
+ config->writeEntry( "Geometry", size() );
+ config->writeEntry( "Show Toolbar", _showToolBar->isChecked() );
+ config->writeEntry( "Show Statusbar", _showStatusBar->isChecked() );
+ config->writeEntry( "Show Treemap", _showTreemapView->isChecked() );
+ config->writeEntry( "ToolBarPos", (int) toolBar( "mainToolBar" )->barPos() );
+
+ if ( _treemapView )
+ config->writeEntry( "TreemapViewHeight", _treemapView->height() );
+
+ _fileOpenRecent->saveEntries( config,"Recent Files" );
+}
+
+
+void
+KDirStatApp::saveProperties( KConfig *config )
+{
+ (void) config;
+ // TODO
+}
+
+
+void
+KDirStatApp::readProperties( KConfig *config )
+{
+ (void) config;
+ // TODO
+}
+
+
+bool
+KDirStatApp::queryClose()
+{
+ return true;
+}
+
+bool
+KDirStatApp::queryExit()
+{
+ emit saveConfig();
+
+ return true;
+}
+
+
+//============================================================================
+// Slots
+//============================================================================
+
+
+void
+KDirStatApp::fileAskOpenDir()
+{
+ statusMsg( i18n( "Opening directory..." ) );
+
+ KURL url = KFileDialog::getExistingDirectory( QString::null, this, i18n( "Open Directory..." ) );
+
+ if( ! url.isEmpty() )
+ openURL( fixedUrl( url.url() ) );
+
+ statusMsg( i18n( "Ready." ) );
+}
+
+
+void
+KDirStatApp::fileAskOpenUrl()
+{
+ statusMsg( i18n( "Opening URL..." ) );
+
+ KURL url = KURLRequesterDlg::getURL( QString::null, // startDir
+ this, i18n( "Open URL..." ) );
+
+ if( ! url.isEmpty() )
+ openURL( fixedUrl( url.url() ) );
+
+ statusMsg( i18n( "Ready." ) );
+}
+
+
+void
+KDirStatApp::fileOpenRecent( const KURL& url )
+{
+ statusMsg( i18n( "Opening directory..." ) );
+
+ if( ! url.isEmpty() )
+ openURL( fixedUrl( url.url() ) );
+
+ statusMsg( i18n( "Ready." ) );
+}
+
+
+void
+KDirStatApp::fileCloseDir()
+{
+ statusMsg( i18n( "Closing directory..." ) );
+
+ _treeView->clear();
+ _fileRefreshAll->setEnabled( false );
+ close();
+
+ statusMsg( i18n( "Ready." ) );
+}
+
+
+void
+KDirStatApp::refreshAll()
+{
+ statusMsg( i18n( "Refreshing directory tree..." ) );
+ _treeView->refreshAll();
+ statusMsg( i18n( "Ready." ) );
+}
+
+
+void
+KDirStatApp::refreshSelected()
+{
+ if ( ! _treeView->selection() )
+ return;
+
+ statusMsg( i18n( "Refreshing selected subtree..." ) );
+ _treeView->refreshSelected();
+ statusMsg( i18n( "Ready." ) );
+}
+
+
+void
+KDirStatApp::stopReading()
+{
+ _treeView->abortReading();
+}
+
+
+void
+KDirStatApp::editCopy()
+{
+ if ( _treeView->selection() )
+ kapp->clipboard()->setText( QString::fromLocal8Bit(_treeView->selection()->orig()->url()) );
+
+#if 0
+#warning debug
+ if ( _activityTracker )
+ _activityTracker->trackActivity( 800 );
+#endif
+}
+
+
+void
+KDirStatApp::cleanupOpenWith()
+{
+ if ( ! _treeView->selection() )
+ return;
+
+ KFileInfo * sel = _treeView->selection()->orig();
+
+ if ( sel->isDotEntry() )
+ return;
+
+ KURL::List urlList( KURL( sel->url() ) );
+ KRun::displayOpenWithDialog( urlList, false );
+}
+
+
+void
+KDirStatApp::selectionChanged( KFileInfo *selection )
+{
+ if ( selection )
+ {
+ _editCopy->setEnabled( true );
+ _reportMailToOwner->setEnabled( true );
+ _fileRefreshSelected->setEnabled( ! selection->isDotEntry() );
+ _cleanupOpenWith->setEnabled( ! selection->isDotEntry() );
+
+ if ( selection->isMountPoint() &&
+ selection->readState() == KDirOnRequestOnly )
+ {
+ _fileContinueReadingAtMountPoint->setEnabled( true );
+ }
+ else
+ _fileContinueReadingAtMountPoint->setEnabled( false );
+
+ statusMsg( QString::fromLocal8Bit(selection->url()) );
+ }
+ else
+ {
+ _editCopy->setEnabled( false );
+ _reportMailToOwner->setEnabled( false );
+ _fileRefreshSelected->setEnabled( false );
+ _fileContinueReadingAtMountPoint->setEnabled( false );
+ _cleanupOpenWith->setEnabled( false );
+ statusMsg( "" );
+ }
+
+ updateActions();
+}
+
+
+void
+KDirStatApp::updateActions()
+{
+ _treemapZoomIn->setEnabled ( _treemapView && _treemapView->canZoomIn() );
+ _treemapZoomOut->setEnabled( _treemapView && _treemapView->canZoomOut() );
+ _treemapRebuild->setEnabled( _treemapView && _treemapView->rootTile() );
+ _treemapSelectParent->setEnabled( _treemapView && _treemapView->canSelectParent() );
+
+ if ( _treeView->tree() && _treeView->tree()->isBusy() )
+ _fileStopReading->setEnabled( true );
+ else
+ _fileStopReading->setEnabled( false );
+}
+
+
+void
+KDirStatApp::treemapZoomIn()
+{
+ if ( _treemapView )
+ {
+ _treemapView->zoomIn();
+ updateActions();
+ }
+}
+
+
+void
+KDirStatApp::treemapZoomOut()
+{
+ if ( _treemapView )
+ {
+ _treemapView->zoomOut();
+ updateActions();
+ }
+}
+
+
+void
+KDirStatApp::treemapSelectParent()
+{
+ if ( _treemapView )
+ {
+ _treemapView->selectParent();
+ updateActions();
+ }
+}
+
+
+void
+KDirStatApp::treemapRebuild()
+{
+ if ( _treemapView )
+ {
+ _treemapView->rebuildTreemap();
+ updateActions();
+ }
+}
+
+
+void
+KDirStatApp::treemapHelp()
+{
+ kapp->invokeHelp( "treemap_intro" );
+}
+
+
+void
+KDirStatApp::toggleToolBar()
+{
+ if ( _showToolBar->isChecked() ) toolBar( "mainToolBar" )->show();
+ else toolBar( "mainToolBar" )->hide();
+}
+
+
+void
+KDirStatApp::toggleStatusBar()
+{
+ if ( _showStatusBar->isChecked() ) statusBar()->show();
+ else statusBar()->hide();
+}
+
+
+void
+KDirStatApp::toggleTreemapView()
+{
+ if ( _showTreemapView->isChecked() )
+ {
+ if ( ! _treemapView )
+ createTreemapView();
+ }
+ else
+ {
+ if ( _treemapView )
+ deleteTreemapView();
+ }
+}
+
+
+void
+KDirStatApp::preferences()
+{
+ if ( ! _settingsDialog )
+ {
+ _settingsDialog = new KDirStat::KSettingsDialog( this );
+ CHECK_PTR( _settingsDialog );
+ }
+
+ if ( ! _settingsDialog->isVisible() )
+ _settingsDialog->show();
+}
+
+
+void
+KDirStatApp::askForFeedback()
+{
+ if ( ! doFeedbackReminder() )
+ return;
+
+ KConfig * config = kapp->config();
+
+ switch ( KMessageBox::warningYesNoCancel( this,
+ i18n( "Now that you know this program for some time,\n"
+ "wouldn't you like to tell the authors your opinion about it?\n"
+ "\n"
+ "Open Source software depends on user feedback.\n"
+ "Your opinion can help us make the software better." ),
+ i18n( "Please tell us your opinion!" ), // caption
+ i18n( "Open &Feedback Form..." ), // yesButton
+ i18n( "&No, and don't ask again!" ) // noButton
+ )
+ )
+ {
+ case KMessageBox::Yes:
+ sendFeedbackMail();
+ break;
+
+ case KMessageBox::No: // ...and don't ask again
+ config->setGroup( "Feedback" );
+ config->writeEntry( "dontAsk", true );
+ config->sync(); // make sure this doesn't get lost even if the app is killed or crashes
+ break;
+
+ case KMessageBox::Cancel:
+ break;
+ }
+
+ config->setGroup( "Feedback" );
+ int remindersCount = config->readNumEntry ( "remindersCount", 0 );
+ config->writeEntry( "remindersCount", ++remindersCount );
+
+ if ( _activityTracker )
+ {
+ _activityTracker->setThreshold( _activityTracker->threshold()
+ + FEEDBACK_REMINDER_INTERVAL );
+ }
+}
+
+
+void
+KDirStatApp::feedbackMailSent()
+{
+ KConfig * config = kapp->config();
+ config->setGroup( "Feedback" );
+ config->writeEntry( "mailSent", true );
+ config->sync();
+}
+
+
+bool
+KDirStatApp::doFeedbackReminder()
+{
+ KConfig * config = kapp->config();
+ config->setGroup( "Feedback" );
+
+ bool mailSent = config->readBoolEntry( "mailSent", false );
+ bool dontAsk = config->readBoolEntry( "dontAsk", false );
+ int remindersCount = config->readNumEntry ( "remindersCount", 0 );
+
+ return !mailSent && !dontAsk && remindersCount < 5;
+}
+
+
+void
+KDirStatApp::statusMsg( const QString &text )
+{
+ // Change status message permanently
+
+ statusBar()->clear();
+ statusBar()->changeItem( text, ID_STATUS_MSG );
+}
+
+
+void
+KDirStatApp::contextMenu( KDirTreeViewItem * item, const QPoint &pos )
+{
+ NOT_USED( item );
+
+ if ( _treeViewContextMenu )
+ _treeViewContextMenu->popup( pos );
+}
+
+
+void
+KDirStatApp::contextMenu( KTreemapTile * tile, const QPoint &pos )
+{
+ NOT_USED( tile );
+
+ if ( _treemapContextMenu )
+ _treemapContextMenu->popup( pos );
+}
+
+
+void
+KDirStatApp::createTreemapView()
+{
+ if ( ! _showTreemapView->isChecked() || ! _treeView->tree() )
+ return;
+
+ if ( _treemapView )
+ delete _treemapView;
+
+ _treemapView = new KTreemapView( _treeView->tree(), _splitter,
+ QSize( _splitter->width(), _treemapViewHeight ) );
+ CHECK_PTR( _treemapView );
+
+ connect( _treemapView, SIGNAL( contextMenu( KTreemapTile *, const QPoint & ) ),
+ this, SLOT ( contextMenu( KTreemapTile *, const QPoint & ) ) );
+
+ connect( _treemapView, SIGNAL( treemapChanged() ),
+ this, SLOT ( updateActions() ) );
+
+ connect( _treemapView, SIGNAL( selectionChanged( KFileInfo * ) ),
+ this, SLOT ( selectionChanged( KFileInfo * ) ) );
+
+ if ( _activityTracker )
+ {
+ connect( _treemapView, SIGNAL( userActivity ( int ) ),
+ _activityTracker, SLOT ( trackActivity( int ) ) );
+ }
+
+ _treemapView->show(); // QSplitter needs explicit show() for new children
+ updateActions();
+}
+
+
+void
+KDirStatApp::deleteTreemapView()
+{
+ if ( _treemapView )
+ {
+ _treemapViewHeight = _treemapView->height();
+ delete _treemapView;
+ }
+
+ _treemapView = 0;
+ updateActions();
+}
+
+
+
+// EOF
diff --git a/kdirstat/kdirstatapp.h b/kdirstat/kdirstatapp.h
new file mode 100644
index 0000000..0f8b875
--- /dev/null
+++ b/kdirstat/kdirstatapp.h
@@ -0,0 +1,421 @@
+/*
+ * File name: kdirstatapp.h
+ * Summary: The KDirStat application - menu bar, tool bar, ...
+ * License: GPL - See file COPYING for details.
+ *
+ * Author: Stefan Hundhammer <sh@suse.de>
+ * Parts auto-generated by KDevelop
+ *
+ * Updated: 2004-12-06
+ */
+
+
+#ifndef KDirStatApp_h
+#define KDirStatApp_h
+
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <kapp.h>
+#include <kmainwindow.h>
+#include "kdirtree.h"
+
+
+// Forward declarations
+class QPopupMenu;
+class QSplitter;
+
+class KAction;
+class KActivityTracker;
+class KFeedbackDialog;
+class KFeedbackDialog;
+class KFeedbackQuestion;
+class KPacMan;
+class KPacMan;
+class KRecentFilesAction;
+class KToggleAction;
+
+namespace KDirStat
+{
+ class KCleanupCollection;
+ class KDirTreeView;
+ class KDirTreeViewItem;
+ class KFileInfo;
+ class KSettingsDialog;
+ class KTreemapView;
+ class KTreemapTile;
+}
+
+using namespace KDirStat;
+
+
+/**
+ * The base class for KDirStat application windows. It sets up the main window
+ * and reads the config file as well as providing a menubar, toolbar and
+ * statusbar. An instance of KDirStatView creates your center view, which is
+ * connected to the window's Doc object. KDirStatApp reimplements the methods
+ * that KMainWindow provides for main window handling and supports full
+ * session management as well as using KActions.
+ *
+ * @see KMainWindow
+ * @see KApplication
+ * @see KConfig
+ *
+ * @author Source Framework Automatically Generated by KDevelop,
+ * (c) The KDevelop Team.
+ *
+ * @version KDevelop version 1.2 code generation
+ **/
+class KDirStatApp : public KMainWindow
+{
+ Q_OBJECT
+
+public:
+
+ /**
+ * Construtor of KDirStatApp, calls all init functions to create the
+ * application.
+ **/
+ KDirStatApp( QWidget* parent=0, const char* name=0 );
+
+ /**
+ * Destructor.
+ **/
+ virtual ~KDirStatApp();
+
+ /**
+ * Open an URL specified by command line argument.
+ **/
+ void openURL( const KURL & url );
+
+ /**
+ * Return the main window's @ref KDirTreeView.
+ **/
+ KDirTreeView * treeView() const { return _treeView; }
+
+ /**
+ * Returns the main window's @ref KTreemapView or 0 if there is none.
+ *
+ * Caution: Do not try to cache this value. The treemap view is destroyed
+ * and re-created frequently!
+ **/
+ KTreemapView * treemapView() const { return _treemapView; }
+
+
+public slots:
+
+ /**
+ * Open a directory tree.
+ **/
+ void fileAskOpenDir();
+
+ /**
+ * Open a (possibly remote) directory tree.
+ **/
+ void fileAskOpenUrl();
+
+ /**
+ * Refresh the entire directory tree, i.e. re-read everything from disk.
+ **/
+ void refreshAll();
+
+ /**
+ * Refresh the selected subtree, i.e. re-read it from disk.
+ **/
+ void refreshSelected();
+
+ /**
+ * Refresh the entire directory tree, i.e. re-read everything from disk.
+ **/
+ void stopReading();
+
+ /**
+ * Open a directory tree from the "recent" menu.
+ **/
+ void fileOpenRecent( const KURL& url );
+
+ /**
+ * asks for saving if the file is modified, then closes the current file
+ * and window
+ **/
+ void fileCloseDir();
+
+ /**
+ * put the marked text/object into the clipboard
+ **/
+ void editCopy();
+
+ /**
+ * Notification that the view's selection has changed.
+ * Enable/disable user actions as appropriate.
+ **/
+ void selectionChanged( KFileInfo *selection );
+
+ /**
+ * Ask user what application to open a file or directory with
+ **/
+ void cleanupOpenWith();
+
+ /**
+ * Toggle tool bar
+ **/
+ void toggleToolBar();
+
+ /**
+ * Toggle status bar
+ **/
+ void toggleStatusBar();
+
+ /**
+ * Toggle treemap view
+ **/
+ void toggleTreemapView();
+
+ /**
+ * Zoom in the treemap at the currently selected tile.
+ **/
+ void treemapZoomIn();
+
+ /**
+ * Zoom out the treemap after zooming in.
+ **/
+ void treemapZoomOut();
+
+ /**
+ * Select the parent of the currently selected treemap tile.
+ **/
+ void treemapSelectParent();
+
+ /**
+ * Rebuild the treemap.
+ **/
+ void treemapRebuild();
+
+ /**
+ * Invoke online help about treemaps.
+ **/
+ void treemapHelp();
+
+ /**
+ * Open settings dialog
+ **/
+ void preferences();
+
+ /**
+ * Changes the statusbar contents for the standard label permanently, used
+ * to indicate current actions.
+ *
+ * @param text the text that is displayed in the statusbar
+ **/
+ void statusMsg( const QString &text );
+
+ /**
+ * Opens a context menu for tree view items.
+ **/
+ void contextMenu( KDirTreeViewItem * item, const QPoint &pos );
+
+ /**
+ * Opens a context menu for treemap tiles.
+ **/
+ void contextMenu( KTreemapTile * tile, const QPoint &pos );
+
+ /**
+ * Create a treemap view. This makes only sense after a directory tree is
+ * completely read.
+ **/
+ void createTreemapView();
+
+ /**
+ * Delete an existing treemap view if there is one.
+ **/
+ void deleteTreemapView();
+
+ /**
+ * Sends a user feedback mail.
+ **/
+ void sendFeedbackMail();
+
+ /**
+ * Read configuration for the main window.
+ **/
+ void readMainWinConfig();
+
+ /**
+ * Save the main window's configuration.
+ **/
+ void saveMainWinConfig();
+
+ /**
+ * Revert all cleanups to default values.
+ **/
+ void revertCleanupsToDefaults();
+
+ /**
+ * For the settings dialog only: Return the internal cleanup collection.
+ **/
+ KCleanupCollection * cleanupCollection() { return _cleanupCollection; }
+
+ /**
+ * Initialize @ref KPacMan animation in the tool bar.
+ **/
+ void initPacMan( bool enablePacMan = true );
+
+ /**
+ * Returns true if the pacman animation in the tool bar is enabled, false
+ * otherwise.
+ **/
+ bool pacManEnabled() const { return _pacMan != 0; }
+
+ /**
+ * Ask user if he wouldn't like to rate this program.
+ **/
+ void askForFeedback();
+
+ /**
+ * Notification that a feedback mail has been sent, thus don't remind
+ * the user any more.
+ **/
+ void feedbackMailSent();
+
+ /**
+ * Update enabled/disabled state of the user actions.
+ **/
+ void updateActions();
+
+
+signals:
+
+ /**
+ * Emitted when the configuration is to be read - other than at program
+ * startup / object creation where each object is responsible for reading
+ * its configuraton at an appropriate time.
+ **/
+ void readConfig();
+
+ /**
+ * Emitted when the configuration is to be saved.
+ **/
+ void saveConfig();
+
+
+protected:
+
+ /**
+ * Initialize the KActions of the application.
+ **/
+ void initActions();
+
+ /**
+ * Initialize @ref KCleanup actions.
+ **/
+ void initCleanups();
+
+ /**
+ * Set up status bar for the main window by initializing a status label.
+ **/
+ void initStatusBar();
+
+ /**
+ * Set up the activity tracker.
+ **/
+ void initActivityTracker();
+
+ /**
+ * Called when a main window is to be closed.
+ *
+ * Returns "true" when closing this window is OK, "false" to abort closing.
+ **/
+ virtual bool queryClose();
+
+ /**
+ * Called when the application is to be shut down alltogether, i.e. when
+ * all windows are to be closed.
+ *
+ * Returns "true" when exiting is OK, "false" otherwise.
+ **/
+ virtual bool queryExit();
+
+ /**
+ * Save the window properties for each open window during session end to
+ * the session config file, including saving the currently opened file by a
+ * temporary filename provided by KApplication.
+ *
+ * @see KTMainWindow#saveProperties
+ **/
+ virtual void saveProperties( KConfig * config );
+
+ /**
+ * Reads session config file and restore application state including the
+ * last opened files and documents by reading the temporary files saved by
+ * saveProperties().
+ *
+ * @see KTMainWindow#readProperties
+ **/
+ virtual void readProperties( KConfig * config );
+
+
+ /**
+ * Add a list of features of this program to a feedback question
+ **/
+ void addFeatureList( KFeedbackQuestion * question );
+
+ /**
+ * Check if the user should be reminded to submit feedback.
+ **/
+ bool doFeedbackReminder();
+
+
+ //
+ // Data members
+ //
+
+ // Widgets
+
+ QSplitter * _splitter;
+ KDirTreeView * _treeView;
+ KTreemapView * _treemapView;
+ KPacMan * _pacMan;
+ QWidget * _pacManDelimiter;
+ QPopupMenu * _treeViewContextMenu;
+ QPopupMenu * _treemapContextMenu;
+ KDirStat::KSettingsDialog * _settingsDialog;
+ KFeedbackDialog * _feedbackDialog;
+ KActivityTracker * _activityTracker;
+
+
+ // Actions
+
+ KAction * _fileAskOpenDir;
+ KAction * _fileAskOpenUrl;
+ KRecentFilesAction * _fileOpenRecent;
+ KAction * _fileCloseDir;
+ KAction * _fileRefreshAll;
+ KAction * _fileRefreshSelected;
+ KAction * _fileContinueReadingAtMountPoint;
+ KAction * _fileStopReading;
+ KAction * _fileQuit;
+ KAction * _editCopy;
+ KAction * _cleanupOpenWith;
+ KAction * _treemapZoomIn;
+ KAction * _treemapZoomOut;
+ KAction * _treemapSelectParent;
+ KAction * _treemapRebuild;
+
+ KAction * _reportMailToOwner;
+ KAction * _helpSendFeedbackMail;
+ KToggleAction * _showToolBar;
+ KToggleAction * _showStatusBar;
+ KToggleAction * _showTreemapView;
+
+ KCleanupCollection * _cleanupCollection;
+
+
+ // Misc
+
+ int _treemapViewHeight;
+};
+
+
+#endif // KDirStatApp_h
diff --git a/kdirstat/kdirstatfeedback.cpp b/kdirstat/kdirstatfeedback.cpp
new file mode 100644
index 0000000..0483499
--- /dev/null
+++ b/kdirstat/kdirstatfeedback.cpp
@@ -0,0 +1,184 @@
+/*
+ * File name: kdirstatfeedback.cpp
+ * Summary: User feedback questions for KDirStat
+ * License: GPL - See file COPYING for details.
+ *
+ * Author: Stefan Hundhammer <sh@suse.de>
+ *
+ * Updated: 2003-01-07
+ */
+
+
+#include <klocale.h>
+
+#include "kdirstatapp.h"
+#include "kfeedback.h"
+
+
+
+void
+KDirStatApp::sendFeedbackMail()
+{
+ if ( ! _feedbackDialog )
+ {
+ _feedbackDialog = new KFeedbackDialog( "sh@suse.de", "feedback_mail" );
+ CHECK_PTR( _feedbackDialog );
+
+ connect( _feedbackDialog->form(), SIGNAL( mailSent() ),
+ this, SLOT( feedbackMailSent() ) );
+
+ KFeedbackQuestionList * list = _feedbackDialog->form()->questionList();
+
+ KFeedbackQuestion * question =
+ list->addQuestion( i18n( "What is your general opinion about this program?" ), "general_opinion", true, true );
+
+ question->addAnswer( i18n( "It's one of my favourites" ), "1/8_favourite" );
+ question->addAnswer( i18n( "I like it" ), "2/8_like_it" );
+ question->addAnswer( i18n( "It's sometimes useful" ), "3/8_sometimes_useful" );
+ question->addAnswer( i18n( "It's average" ), "4/8_average" );
+ question->addAnswer( i18n( "Nice try, but this could be done better" ), "5/8_nice_try" );
+ question->addAnswer( i18n( "It's poor" ), "6/8_poor" );
+ question->addAnswer( i18n( "It's useless" ), "7/8_useless" );
+ question->addAnswer( i18n( "It's crap" ), "8/8_crap" );
+
+ question = list->addQuestion( i18n( "Which features of this program do you like?" ), "features_liked", false );
+ addFeatureList( question );
+
+ question = list->addQuestion( i18n( "Which features don't you like?" ), "features_not_liked", false );
+ addFeatureList( question );
+
+ question = list->addQuestion( i18n( "Which features do you never use?" ), "features_never_used", false );
+ addFeatureList( question );
+
+ question = list->addQuestion( i18n( "What is your favourite feature?" ), "favourite_feature", true );
+ addFeatureList( question );
+
+ question = list->addQuestion( i18n( "Are there features you are missing?" ), "features_missing", true );
+ question->addAnswer( i18n( "Yes, a lot! (please add comment below)" ), "1/4_lots" );
+ question->addAnswer( i18n( "Some (please add comment below)" ), "2/4_some" );
+ question->addAnswer( i18n( "None" ), "3/4_none" );
+ question->addAnswer( i18n( "It has too many features already!" ), "4/4_too_many_already" );
+
+ question = list->addQuestion( i18n( "How do you rate the stability of this program?" ), "stability", true, true );
+ question->addAnswer( i18n( "Rock solid" ), "1/5_rock_solid" );
+ question->addAnswer( i18n( "Good" ), "2/5_good" );
+ question->addAnswer( i18n( "Average" ), "3/5_average" );
+ question->addAnswer( i18n( "Poor" ), "4/5_poor" );
+ question->addAnswer( i18n( "It keeps crashing all the time" ), "5/5_keeps_crashing" );
+
+ question = list->addQuestion( i18n( "How do you rate the performance of this program?" ), "performance", true );
+ question->addAnswer( i18n( "Great" ), "1/5_great" );
+ question->addAnswer( i18n( "Good" ), "2/5_good" );
+ question->addAnswer( i18n( "Average" ), "3/5_average" );
+ question->addAnswer( i18n( "Poor" ), "4/5_poor" );
+ question->addAnswer( i18n( "It's so slow it drives me nuts" ), "5/5_drives_me_nuts" );
+
+ question = list->addQuestion( i18n( "What is your experience with computers in general?" ), "computer_experience", true );
+ question->addAnswer( i18n( "Expert" ), "1/5_expert" );
+ question->addAnswer( i18n( "Fair" ), "2/5_fair" );
+ question->addAnswer( i18n( "Average" ), "3/5_average" );
+ question->addAnswer( i18n( "Learning" ), "4/5_learning" );
+ question->addAnswer( i18n( "Newbie" ), "5/5_newbie" );
+
+ question = list->addQuestion( i18n( "What is your experience with Unix/Linux systems?" ), "unix_experience", true );
+ question->addAnswer( i18n( "Expert" ), "1/5_expert" );
+ question->addAnswer( i18n( "Fair" ), "2/5_fair" );
+ question->addAnswer( i18n( "Average" ), "3/5_average" );
+ question->addAnswer( i18n( "Learning" ), "4/5_learning" );
+ question->addAnswer( i18n( "Newbie" ), "5/5_newbie" );
+
+ question = list->addQuestion( i18n( "Did you have trouble figuring out how to work with this program in general?" ),
+ "learning_curve", true, true );
+ question->addAnswer( i18n( "No problem" ), "1/5_no_problem" );
+ question->addAnswer( i18n( "Some" ), "2/5_some_problems" );
+ question->addAnswer( i18n( "I'm still learning" ), "3/5_still_learing" );
+ question->addAnswer( i18n( "I didn't have a clue what to do at first" ), "4/5_no_clue_at_first" );
+ question->addAnswer( i18n( "I still don't have a clue what to do" ), "5/5_still_no_clue" );
+
+ question = list->addQuestion( i18n( "Where do you use this program most?" ), "usage_where", true );
+ question->addAnswer( i18n( "At work" ), "at_work" );
+ question->addAnswer( i18n( "At home" ), "at_home" );
+ question->addAnswer( i18n( "At university / school" ), "university" );
+
+ question = list->addQuestion( i18n( "What is your primary role there?" ), "primary_role", true );
+ question->addAnswer( i18n( "Home user" ), "home_user" );
+ question->addAnswer( i18n( "Student" ), "student" );
+ question->addAnswer( i18n( "Educational (teacher / professor)" ), "educational" );
+ question->addAnswer( i18n( "Non-computer related work" ), "non_computer" );
+ question->addAnswer( i18n( "Developer" ), "developer" );
+ question->addAnswer( i18n( "System administrator" ), "sysadmin" );
+
+ question = list->addQuestion( i18n( "Do you have any other roles there?" ), "other_roles", false );
+ question->addAnswer( i18n( "Home user" ), "home_user" );
+ question->addAnswer( i18n( "Student" ), "student" );
+ question->addAnswer( i18n( "Educational (teacher / professor)" ), "educational" );
+ question->addAnswer( i18n( "Non-computer related work" ), "non_computer" );
+ question->addAnswer( i18n( "Developer" ), "developer" );
+ question->addAnswer( i18n( "System administrator" ), "sysadmin" );
+
+ question = list->addQuestion( i18n( "How did you get to know this program?" ), "first_contact", true );
+ question->addAnswer( i18n( "In a menu on my machine" ), "menu" );
+ question->addAnswer( i18n( "Somebody told me about it" ), "told" );
+ question->addAnswer( i18n( "On the internet" ), "internet" );
+ question->addAnswer( i18n( "Printed magazine / book" ), "print_media" );
+ question->addAnswer( i18n( "Other (please add comment below)" ), "other" );
+
+ list->addYesNoQuestion( i18n( "Did you ever get a KDirStat mail report telling you to clean up disk space?" ),
+ "got_mail_report" );
+
+ question = list->addQuestion( i18n( "Could you figure yet out how to work with the treemaps?" ), "learning_treemaps", true );
+ question->addAnswer( i18n( "I became an expert at it" ), "1/5_expert" );
+ question->addAnswer( i18n( "I got a fairly good idea of it" ), "2/5_ok" );
+ question->addAnswer( i18n( "I'm still learning" ), "3/5_still_learing" );
+ question->addAnswer( i18n( "I still don't have a clue what to do" ), "4/5_no_clue" );
+ question->addAnswer( i18n( "Treemaps? Huh? What the hell is that?" ), "5/5_say_what" );
+
+ question = list->addQuestion( i18n( "What do you think about the treemaps?" ), "treemaps", false );
+ question->addAnswer( i18n( "They are useless" ), "useless" );
+ question->addAnswer( i18n( "The display is confusing" ), "display_confusing" );
+ question->addAnswer( i18n( "They look ugly" ), "look_ugly" );
+ question->addAnswer( i18n( "They look nice" ), "look_nice" );
+ question->addAnswer( i18n( "They help finding large files" ), "good_for_large_files" );
+ question->addAnswer( i18n( "I could do with the treemap view alone" ), "treemaps_alone" );
+ question->addAnswer( i18n( "The combination of tree view and treemaps is great" ), "like_combined_views");
+ question->addAnswer( i18n( "I want more info inside the treemap view" ), "more_info" );
+ question->addAnswer( i18n( "Leave the treemaps as they are right now" ), "leave_like_this" );
+
+ list->addYesNoQuestion( i18n( "Would you recommend this program to a friend?" ), "recommend", true );
+ }
+
+ if ( ! _feedbackDialog->isVisible() )
+ _feedbackDialog->show();
+}
+
+
+void
+KDirStatApp::addFeatureList( KFeedbackQuestion * question )
+{
+ question->addAnswer( i18n( "The directory tree display in general" ), "tree_view" );
+ question->addAnswer( i18n( "Percentage bars as graphical display of relative sizes" ), "percentage_bars" );
+ question->addAnswer( i18n( "Files apart from directories in a separate <Files> item"), "files_item" );
+
+ question->addAnswer( i18n( "Treemaps in general" ), "treemaps" );
+ question->addAnswer( i18n( "The cushioned treemap rendering" ), "treemap_cushions" );
+
+ question->addAnswer( i18n( "Cleanup actions in general" ), "cleanups_general" );
+ question->addAnswer( i18n( "Predefined cleanup actions" ), "predefined_cleanups" );
+ question->addAnswer( i18n( "User defined cleanup actions" ), "user_cleanups" );
+ question->addAnswer( i18n( "Cleanup action configuration" ), "cleanup_config" );
+
+ question->addAnswer( i18n( "Different colors in percentage bars" ), "tree_colors" );
+ question->addAnswer( i18n( "Tree color configuration" ), "tree_color_config" );
+ question->addAnswer( i18n( "Staying on one file system" ), "stay_on_one_filesys" );
+ question->addAnswer( i18n( "The \"mail to owner\" facility" ), "mail_to_owner" );
+ question->addAnswer( i18n( "This \"feedback mail\" facility" ), "feedback" );
+
+ question->addAnswer( i18n( "Human readable sizes (kB, MB, ...)" ), "human_readable_sizes" );
+ question->addAnswer( i18n( "All the numbers in the tree display" ), "numeric_display" );
+ question->addAnswer( i18n( "Last change time of an entire directory tree" ), "last_change_time" );
+ question->addAnswer( i18n( "The PacMan animation" ), "pacman" );
+}
+
+
+
+// EOF
diff --git a/kdirstat/kdirstatmain.cpp b/kdirstat/kdirstatmain.cpp
new file mode 100644
index 0000000..dd957db
--- /dev/null
+++ b/kdirstat/kdirstatmain.cpp
@@ -0,0 +1,115 @@
+/*
+ * File name: kdirstatmain.cpp
+ * Summary: Main program for KDirStat
+ * License: GPL - See file COPYING for details.
+ *
+ * Author: Stefan Hundhammer <sh@suse.de>
+ * Parts auto-generated by KDevelop
+ *
+ * Updated: 2003-01-07
+ */
+
+
+#include <kcmdlineargs.h>
+#include <kaboutdata.h>
+#include <klocale.h>
+
+#include "kdirstatapp.h"
+
+
+static const char *description =
+ I18N_NOOP("KDirStat - Directory statistics.\n"
+ "\n"
+ "Shows where all your disk space has gone\n"
+ "and helps you clean it up."
+ "\n"
+ "\n"
+ "\n"
+ "If you have any comments or if you would simply like to tell your opinion\n"
+ "about this program, please use \"Send Feedback Mail\" from the \"Help\" menu.\n"
+ "\n"
+ "Any feedback (even negative!) is appreciated."
+ );
+
+
+static KCmdLineOptions options[] =
+{
+ { "+[Dir/URL]", I18N_NOOP("Directory or URL to open"), 0 },
+ { 0, 0, 0 }
+};
+
+int main(int argc, char *argv[])
+{
+
+ KAboutData aboutData( "kdirstat", I18N_NOOP("KDirStat"),
+ VERSION, description, KAboutData::License_GPL,
+ "(c) 1999-2003 Stefan Hundhammer", 0, 0,
+ "sh@suse.de" );
+
+ aboutData.addAuthor( "Stefan Hundhammer",
+ I18N_NOOP("\n"
+ "If you have any comments or if you would simply like to tell\n"
+ "your opinion about this program, please use \n"
+ "\"Send Feedback Mail\" from the \"Help\" menu.\n"
+ "\n"
+ "Any feedback (even negative!) is appreciated." ),
+ "sh@suse.de", "http://kdirstat.sourceforge.net/" );
+
+
+ aboutData.addCredit( I18N_NOOP( "All the people who worked on SequoiaView" ),
+ I18N_NOOP( "for showing just how useful treemaps really can be.\n" ),
+ 0, // e-mail
+ "http://www.win.tue.nl/sequoiaview" );
+
+ aboutData.addCredit( I18N_NOOP( "Jarke J. van Wijk, Huub van de Wetering, and Mark Bruls" ),
+ I18N_NOOP( "for their papers about treemaps.\n" ),
+ "vanwijk@win.tue.nl",
+ "http://www.win.tue.nl/~vanwijk/" );
+
+ aboutData.addCredit( "Ben Shneiderman",
+ I18N_NOOP( "for his ingenious idea of treemaps -\n"
+ "a truly intuitive way of visualizing tree contents.\n" ),
+ "", // E-Mail
+ "http://www.cs.umd.edu/hcil/treemaps/" );
+
+ aboutData.addCredit( "All the users who gave feedback of any kind",
+ I18N_NOOP( "for showing that all the work involved with such a project\n"
+ "is really appreciated out there.\n" ) );
+
+ KCmdLineArgs::init( argc, argv, &aboutData );
+ KCmdLineArgs::addCmdLineOptions( options ); // Add our own options.
+
+ KApplication app;
+
+ if ( app.isRestored() )
+ {
+ RESTORE(KDirStatApp);
+ }
+ else
+ {
+ KDirStatApp *kdirstat = new KDirStatApp();
+ kdirstat->show();
+
+ KCmdLineArgs *args = KCmdLineArgs::parsedArgs();
+
+ if ( args->count() )
+ {
+ // Process command line arguments as URLs or paths to scan
+
+ KURL url = fixedUrl( args->arg( 0 ) );
+ // kdDebug() << "Opening " << url.url() << endl;
+ kdirstat->openURL( url );
+ }
+ else
+ {
+ kdirstat->fileAskOpenDir();
+ }
+
+ args->clear();
+ }
+
+ // kdDebug() << "Entering main loop" << endl;
+
+ return app.exec();
+}
+
diff --git a/kdirstat/kdirstatsettings.cpp b/kdirstat/kdirstatsettings.cpp
new file mode 100644
index 0000000..c647178
--- /dev/null
+++ b/kdirstat/kdirstatsettings.cpp
@@ -0,0 +1,1056 @@
+/*
+ * File name: kdirstatsettings.cpp
+ * Summary: Settings dialog for KDirStat
+ * License: GPL - See file COPYING for details.
+ * Author: Stefan Hundhammer <sh@suse.de>
+ *
+ * Updated: 2003-01-30
+ */
+
+
+#include <qbuttongroup.h>
+#include <qcheckbox.h>
+#include <qcombobox.h>
+#include <qlabel.h>
+#include <qlayout.h>
+#include <qlineedit.h>
+#include <qslider.h>
+#include <qvbox.h>
+#include <qhgroupbox.h>
+#include <qvgroupbox.h>
+#include <qspinbox.h>
+
+#include <kcolorbutton.h>
+#include <klocale.h>
+#include <kmessagebox.h>
+
+#include "kdirtreeview.h"
+#include "ktreemapview.h"
+#include "kdirstatsettings.h"
+
+
+using namespace KDirStat;
+
+
+KSettingsDialog::KSettingsDialog( KDirStatApp *mainWin )
+ : KDialogBase( Tabbed, // dialogFace
+ i18n( "Settings" ), // caption
+ Ok | Apply | Default | Cancel | Help, // buttonMask
+ Ok, // defaultButton
+ 0, // parent
+ 0, // name
+ false ) // modal
+ , _mainWin( mainWin )
+{
+ /**
+ * This may seem like overkill, but I didn't find any other way to get
+ * geometry management right with KDialogBase, yet maintain a modular and
+ * object-oriented design:
+ *
+ * Each individual settings page is added with 'addVBoxPage()' to get some
+ * initial geometry management. Only then can some generic widget be added
+ * into this - and I WANT my settings pages to be generic widgets. I want
+ * them to be self-sufficient - no monolithic mess of widget creation in my
+ * code, intermixed with all kinds of layout objects.
+ *
+ * The ordinary KDialogBase::addPage() just creates a QFrame which is too
+ * dumb for any kind of geometry management - it cannot even handle one
+ * single child right. So, let's have KDialogBase create something more
+ * intelligent: A QVBox (which is derived from QFrame anyway). This QVBox
+ * gets only one child - the KSettingsPage. This KSettingsPage handles its
+ * own layout.
+ **/
+
+ QWidget * page;
+
+ page = addVBoxPage( i18n( "&Cleanups" ) );
+ _cleanupsPageIndex = pageIndex( page );
+ new KCleanupPage( this, page, _mainWin );
+
+ page = addVBoxPage( i18n( "&Tree Colors" ) );
+ _treeColorsPageIndex = pageIndex( page );
+ new KTreeColorsPage( this, page, _mainWin );
+
+ page = addVBoxPage( i18n( "Tree&map" ) );
+ _treemapPageIndex = pageIndex( page );
+ new KTreemapPage( this, page, _mainWin );
+
+ page = addVBoxPage( i18n( "&General" ) );
+ _generalSettingsPageIndex = pageIndex( page );
+ new KGeneralSettingsPage( this, page, _mainWin );
+
+ // resize( sizeHint() );
+}
+
+
+KSettingsDialog::~KSettingsDialog()
+{
+ // NOP
+}
+
+
+void
+KSettingsDialog::show()
+{
+ emit aboutToShow();
+ KDialogBase::show();
+}
+
+
+void
+KSettingsDialog::slotDefault()
+{
+ if ( KMessageBox::warningContinueCancel( this,
+ i18n( "Really revert all settings to their default values?\n"
+ "You will lose all changes you ever made!" ),
+ i18n( "Please Confirm" ), // caption
+ i18n( "&Really Revert to Defaults" ) // continueButton
+ ) == KMessageBox::Continue )
+ {
+ emit defaultClicked();
+ emit applyClicked();
+ }
+}
+
+
+void
+KSettingsDialog::slotHelp()
+{
+ QString helpTopic = "";
+
+ if ( activePageIndex() == _cleanupsPageIndex ) helpTopic = "configuring_cleanups";
+ else if ( activePageIndex() == _treeColorsPageIndex ) helpTopic = "tree_colors";
+ else if ( activePageIndex() == _treemapPageIndex ) helpTopic = "treemap_settings";
+ else if ( activePageIndex() == _generalSettingsPageIndex) helpTopic = "general_settings";
+
+ // kdDebug() << "Help topic: " << helpTopic << endl;
+ kapp->invokeHelp( helpTopic );
+}
+
+
+/*--------------------------------------------------------------------------*/
+
+
+KSettingsPage::KSettingsPage( KSettingsDialog * dialog,
+ QWidget * parent )
+ : QWidget( parent )
+{
+ connect( dialog, SIGNAL( aboutToShow ( void ) ),
+ this, SLOT ( setup ( void ) ) );
+
+ connect( dialog, SIGNAL( okClicked ( void ) ),
+ this, SLOT ( apply ( void ) ) );
+
+ connect( dialog, SIGNAL( applyClicked ( void ) ),
+ this, SLOT ( apply ( void ) ) );
+
+ connect( dialog, SIGNAL( defaultClicked ( void ) ),
+ this, SLOT ( revertToDefaults( void ) ) );
+}
+
+
+KSettingsPage::~KSettingsPage()
+{
+ // NOP
+}
+
+
+/*--------------------------------------------------------------------------*/
+
+
+KTreeColorsPage::KTreeColorsPage( KSettingsDialog * dialog,
+ QWidget * parent,
+ KDirStatApp * mainWin )
+ : KSettingsPage( dialog, parent )
+ , _mainWin( mainWin )
+ , _treeView( mainWin->treeView() )
+ , _maxButtons( KDirStatSettingsMaxColorButton )
+{
+ // Outer layout box
+
+ QHBoxLayout * outerBox = new QHBoxLayout( this,
+ 0, // border
+ dialog->spacingHint() );
+
+
+ // Inner layout box with a column of color buttons
+
+ QGridLayout *grid = new QGridLayout( _maxButtons, // rows
+ _maxButtons + 1, // cols
+ dialog->spacingHint() );
+ outerBox->addLayout( grid, 1 );
+ grid->setColStretch( 0, 0 ); // label column - dont' stretch
+
+ for ( int i=1; i < _maxButtons; i++ )
+ {
+ grid->setColStretch( i, 1 ); // all other columns stretch as you like
+ }
+
+ for ( int i=0; i < _maxButtons; i++ )
+ {
+ QString labelText;
+
+ labelText=i18n( "Tree Level %1" ).arg(i+1);
+ _colorLabel[i] = new QLabel( labelText, this );
+ grid->addWidget( _colorLabel [i], i, 0 );
+
+ _colorButton[i] = new KColorButton( this );
+ _colorButton[i]->setMinimumSize( QSize( 80, 10 ) );
+ grid->addMultiCellWidget( _colorButton [i], i, i, i+1, _maxButtons );
+ grid->setRowStretch( i, 1 );
+ }
+
+
+ // Vertical slider
+
+ _slider = new QSlider( 1, // minValue
+ _maxButtons, // maxValue
+ 1, // pageStep
+ 1, // value
+ QSlider::Vertical,
+ this );
+ outerBox->addWidget( _slider, 0 );
+ outerBox->activate();
+
+ connect( _slider, SIGNAL( valueChanged( int ) ),
+ this, SLOT ( enableColors( int ) ) );
+}
+
+
+KTreeColorsPage::~KTreeColorsPage()
+{
+ // NOP
+}
+
+
+void
+KTreeColorsPage::apply()
+{
+ _treeView->setUsedFillColors( _slider->value() );
+
+ for ( int i=0; i < _maxButtons; i++ )
+ {
+ _treeView->setFillColor( i, _colorButton [i]->color() );
+ }
+
+ _treeView->triggerUpdate();
+}
+
+
+void
+KTreeColorsPage::revertToDefaults()
+{
+ _treeView->setDefaultFillColors();
+ setup();
+}
+
+
+void
+KTreeColorsPage::setup()
+{
+ for ( int i=0; i < _maxButtons; i++ )
+ {
+ _colorButton [i]->setColor( _treeView->rawFillColor(i) );
+ }
+
+ _slider->setValue( _treeView->usedFillColors() );
+ enableColors( _treeView->usedFillColors() );
+}
+
+
+void
+KTreeColorsPage::enableColors( int maxColors )
+{
+ for ( int i=0; i < _maxButtons; i++ )
+ {
+ _colorButton [i]->setEnabled( i < maxColors );
+ _colorLabel [i]->setEnabled( i < maxColors );
+ }
+}
+
+
+/*--------------------------------------------------------------------------*/
+
+
+
+KCleanupPage::KCleanupPage( KSettingsDialog * dialog,
+ QWidget * parent,
+ KDirStatApp * mainWin )
+ : KSettingsPage( dialog, parent )
+ , _mainWin( mainWin )
+ , _currentCleanup( 0 )
+{
+ // Copy the main window's cleanup collection.
+
+ _workCleanupCollection = *mainWin->cleanupCollection();
+
+ // Create layout and widgets.
+
+ QHBoxLayout * layout = new QHBoxLayout( this,
+ 0, // border
+ dialog->spacingHint() ); // spacing
+ _listBox = new KCleanupListBox( this );
+ _props = new KCleanupPropertiesPage( this, mainWin );
+
+
+ // Connect list box signals to reflect changes in the list
+ // selection in the cleanup properties page - whenever the user
+ // clicks on a different cleanup in the list, the properties page
+ // will display that cleanup's values.
+
+ connect( _listBox, SIGNAL( selectCleanup( KCleanup * ) ),
+ this, SLOT ( changeCleanup( KCleanup * ) ) );
+
+
+ // Fill list box so it can determine a reasonable startup geometry - that
+ // doesn't work if it happens only later.
+
+ setup();
+
+ // Now that _listBox will (hopefully) have determined a reasonable
+ // default geometry, add the widgets to the layout.
+
+ layout->addWidget( _listBox, 0 );
+ layout->addWidget( _props , 1 );
+ layout->activate();
+}
+
+
+KCleanupPage::~KCleanupPage()
+{
+ // NOP
+}
+
+
+void
+KCleanupPage::changeCleanup( KCleanup * newCleanup )
+{
+ if ( _currentCleanup && newCleanup != _currentCleanup )
+ {
+ storeProps( _currentCleanup );
+ }
+
+ _currentCleanup = newCleanup;
+ _props->setFields( _currentCleanup );
+}
+
+
+void
+KCleanupPage::apply()
+{
+ exportCleanups();
+}
+
+
+void
+KCleanupPage::revertToDefaults()
+{
+ _mainWin->revertCleanupsToDefaults();
+ setup();
+}
+
+
+void
+KCleanupPage::setup()
+{
+ importCleanups();
+
+ // Fill the list box.
+
+ _listBox->clear();
+ KCleanupList cleanupList = _workCleanupCollection.cleanupList();
+ KCleanupListIterator it( cleanupList );
+
+ while ( *it )
+ {
+ _listBox->insert( *it );
+ ++it;
+ }
+
+
+ // (Re-) Initialize list box.
+
+ // _listBox->resize( _listBox->sizeHint() );
+ _listBox->setSelected( 0, true );
+}
+
+
+void
+KCleanupPage::importCleanups()
+{
+ // Copy the main window's cleanup collecton to _workCleanupCollection.
+
+ _workCleanupCollection = * _mainWin->cleanupCollection();
+
+
+ // Pointers to the old collection contents are now invalid!
+
+ _currentCleanup = 0;
+}
+
+
+void
+KCleanupPage::exportCleanups()
+{
+ // Retrieve any pending changes from the properties page and store
+ // them in the current cleanup.
+
+ storeProps( _currentCleanup );
+
+
+ // Copy the _workCleanupCollection to the main window's cleanup
+ // collection.
+
+ * _mainWin->cleanupCollection() = _workCleanupCollection;
+}
+
+
+void
+KCleanupPage::storeProps( KCleanup * cleanup )
+{
+ if ( cleanup )
+ {
+ // Retrieve the current fields contents and store them in the current
+ // cleanup.
+
+ *cleanup = _props->fields();
+
+ // Update the list box accordingly - the cleanup's title may have
+ // changed, too!
+
+ _listBox->updateTitle( cleanup );
+ }
+}
+
+/*--------------------------------------------------------------------------*/
+
+
+KCleanupListBox::KCleanupListBox( QWidget *parent )
+ : QListBox( parent )
+{
+ _selection = 0;
+
+ connect( this,
+ SIGNAL( selectionChanged( QListBoxItem *) ),
+ SLOT ( selectCleanup ( QListBoxItem *) ) );
+}
+
+
+QSize
+KCleanupListBox::sizeHint() const
+{
+ // FIXME: Is this still needed with Qt 2.x?
+
+ if ( count() < 1 )
+ {
+ // As long as the list is empty, sizeHint() would default to
+ // (0,0) which is ALWAYS just a pain in the ass. We'd rather
+ // have an absolutely random value than this.
+ return QSize( 100, 100 );
+ }
+ else
+ {
+ // Calculate the list contents and take 3D frames (2*2 pixels)
+ // into account.
+ return QSize ( maxItemWidth() + 5,
+ count() * itemHeight( 0 ) + 4 );
+ }
+}
+
+
+void
+KCleanupListBox::insert( KCleanup * cleanup )
+{
+ // Create a new listbox item - this will insert itself (!) automatically.
+ // It took me half an afternoon to figure _this_ out. Not too intuitive
+ // when there is an insertItem() method, too, eh?
+
+ new KCleanupListBoxItem( this, cleanup );
+}
+
+
+void
+KCleanupListBox::selectCleanup( QListBoxItem * listBoxItem )
+{
+ KCleanupListBoxItem * item = (KCleanupListBoxItem *) listBoxItem;
+
+ _selection = item->cleanup();
+ emit selectCleanup( _selection );
+}
+
+
+void
+KCleanupListBox::updateTitle( KCleanup * cleanup )
+{
+ KCleanupListBoxItem * item = (KCleanupListBoxItem *) firstItem();
+
+ while ( item )
+ {
+ if ( ! cleanup || item->cleanup() == cleanup )
+ item->updateTitle();
+
+ item = (KCleanupListBoxItem *) item->next();
+ }
+}
+
+
+/*--------------------------------------------------------------------------*/
+
+
+KCleanupListBoxItem::KCleanupListBoxItem( KCleanupListBox * listBox,
+ KCleanup * cleanup )
+ : QListBoxText( listBox )
+ , _cleanup( cleanup )
+{
+ CHECK_PTR( cleanup );
+ setText( cleanup->cleanTitle() );
+}
+
+
+void
+KCleanupListBoxItem::updateTitle()
+{
+ setText( _cleanup->cleanTitle() );
+}
+
+
+/*--------------------------------------------------------------------------*/
+
+
+KCleanupPropertiesPage::KCleanupPropertiesPage( QWidget * parent,
+ KDirStatApp * mainWin )
+ : QWidget( parent )
+ , _mainWin( mainWin )
+{
+ QVBoxLayout *outerBox = new QVBoxLayout( this, 0, 0 ); // border, spacing
+
+ // The topmost check box: "Enabled".
+
+ _enabled = new QCheckBox( i18n( "&Enabled" ), this );
+ outerBox->addWidget( _enabled, 0 );
+ outerBox->addSpacing( 7 );
+ outerBox->addStretch();
+
+ connect( _enabled, SIGNAL( toggled ( bool ) ),
+ this, SLOT ( enableFields( bool ) ) );
+
+
+ // All other widgets of this page are grouped together in a
+ // separate subwidget so they can all be enabled / disabled
+ // together.
+ _fields = new QWidget( this );
+ outerBox->addWidget( _fields, 1 );
+
+ QVBoxLayout *fieldsBox = new QVBoxLayout( _fields );
+
+
+ // Grid layout for the edit fields, their labels, some
+ // explanatory text and the "recurse?" check box.
+
+ QGridLayout *grid = new QGridLayout( 7, // rows
+ 2, // cols
+ 4 ); // spacing
+ fieldsBox->addLayout( grid, 0 );
+ fieldsBox->addStretch();
+ fieldsBox->addSpacing( 5 );
+
+ grid->setColStretch( 0, 0 ); // column for field labels - dont' stretch
+ grid->setColStretch( 1, 1 ); // column for edit fields - stretch as you like
+
+
+ // Edit fields for cleanup action title and command line.
+
+ QLabel *label;
+ _title = new QLineEdit( _fields ); grid->addWidget( _title, 0, 1 );
+ _command = new QLineEdit( _fields ); grid->addWidget( _command, 1, 1 );
+ label = new QLabel( _title, i18n( "&Title:" ), _fields ); grid->addWidget( label, 0, 0 );
+ label = new QLabel( _command, i18n( "&Command Line:" ), _fields ); grid->addWidget( label, 1, 0 );
+
+ label = new QLabel( i18n( "%p Full Path" ), _fields );
+ grid->addWidget( label, 2, 1 );
+
+ label = new QLabel( i18n( "%n File / Directory Name Without Path" ), _fields );
+ grid->addWidget( label, 3, 1 );
+
+ label = new QLabel( i18n( "%t KDE Trash Directory" ), _fields );
+ grid->addWidget( label, 4, 1 );
+
+
+ // "Recurse into subdirs" check box
+
+ _recurse = new QCheckBox( i18n( "&Recurse into Subdirectories" ), _fields );
+ grid->addWidget( _recurse, 5, 1 );
+
+ // "Ask for confirmation" check box
+
+ _askForConfirmation = new QCheckBox( i18n( "&Ask for Confirmation" ), _fields );
+ grid->addWidget( _askForConfirmation, 6, 1 );
+
+
+ // The "Works for..." check boxes, grouped together in a button group.
+
+ QButtonGroup *worksFor = new QButtonGroup( i18n( "Works for..." ), _fields );
+ QVBoxLayout *worksForBox = new QVBoxLayout( worksFor, 15, 2 );
+ fieldsBox->addWidget( worksFor, 0 );
+ fieldsBox->addSpacing( 5 );
+ fieldsBox->addStretch();
+
+ _worksForDir = new QCheckBox( i18n( "&Directories" ), worksFor );
+ _worksForFile = new QCheckBox( i18n( "&Files" ), worksFor );
+ _worksForDotEntry = new QCheckBox( i18n( "<Files> P&seudo Entries"), worksFor );
+
+ worksForBox->addWidget( _worksForDir , 1 );
+ worksForBox->addWidget( _worksForFile , 1 );
+ worksForBox->addWidget( _worksForDotEntry , 1 );
+
+ worksForBox->addSpacing( 5 );
+ _worksForProtocols = new QComboBox( false, worksFor );
+ worksForBox->addWidget( _worksForProtocols, 1 );
+
+ _worksForProtocols->insertItem( i18n( "On Local Machine Only ('file:/' Protocol)" ) );
+ _worksForProtocols->insertItem( i18n( "Network Transparent (ftp, smb, tar, ...)" ) );
+
+
+ // Grid layout for combo boxes at the bottom
+
+ grid = new QGridLayout( 1, // rows
+ 2, // cols
+ 4 ); // spacing
+
+ fieldsBox->addLayout( grid, 0 );
+ fieldsBox->addSpacing( 5 );
+ fieldsBox->addStretch();
+ int row = 0;
+
+
+ // The "Refresh policy" combo box
+
+ _refreshPolicy = new QComboBox( false, _fields );
+ grid->addWidget( _refreshPolicy, row, 1 );
+
+ label = new QLabel( _refreshPolicy, i18n( "Refresh &Policy:" ), _fields );
+ grid->addWidget( label, row++, 0 );
+
+
+ // Caution: The order of those entries must match the order of
+ // 'enum RefreshPolicy' in 'kcleanup.h'!
+ //
+ // I don't like this one bit. The ComboBox should provide something better
+ // than mere numeric IDs. One of these days I'm going to rewrite this
+ // thing!
+
+ _refreshPolicy->insertItem( i18n( "No Refresh" ) );
+ _refreshPolicy->insertItem( i18n( "Refresh This Entry" ) );
+ _refreshPolicy->insertItem( i18n( "Refresh This Entry's Parent" ) );
+ _refreshPolicy->insertItem( i18n( "Assume Entry Has Been Deleted" ) );
+
+
+ outerBox->activate();
+ setMinimumSize( sizeHint() );
+}
+
+
+void
+KCleanupPropertiesPage::enableFields( bool active )
+{
+ _fields->setEnabled( active );
+}
+
+
+void
+KCleanupPropertiesPage::setFields( const KCleanup * cleanup )
+{
+ _id = cleanup->id();
+ _enabled->setChecked ( cleanup->enabled() );
+ _title->setText ( cleanup->title() );
+ _command->setText ( cleanup->command() );
+ _recurse->setChecked ( cleanup->recurse() );
+ _askForConfirmation->setChecked ( cleanup->askForConfirmation() );
+ _worksForDir->setChecked ( cleanup->worksForDir() );
+ _worksForFile->setChecked ( cleanup->worksForFile() );
+ _worksForDotEntry->setChecked ( cleanup->worksForDotEntry() );
+ _worksForProtocols->setCurrentItem ( cleanup->worksLocalOnly() ? 0 : 1 );
+ _refreshPolicy->setCurrentItem ( cleanup->refreshPolicy() );
+
+ enableFields( cleanup->enabled() );
+}
+
+
+KCleanup
+KCleanupPropertiesPage::fields() const
+{
+ KCleanup cleanup( _id );
+
+ cleanup.setEnabled ( _enabled->isChecked() );
+ cleanup.setTitle ( _title->text() );
+ cleanup.setCommand ( _command->text() );
+ cleanup.setRecurse ( _recurse->isChecked() );
+ cleanup.setAskForConfirmation ( _askForConfirmation->isChecked() );
+ cleanup.setWorksForDir ( _worksForDir->isChecked() );
+ cleanup.setWorksForFile ( _worksForFile->isChecked() );
+ cleanup.setWorksLocalOnly ( _worksForProtocols->currentItem() == 0 ? true : false );
+ cleanup.setWorksForDotEntry ( _worksForDotEntry->isChecked() );
+ cleanup.setRefreshPolicy ( (KCleanup::RefreshPolicy) _refreshPolicy->currentItem() );
+
+ return cleanup;
+}
+
+
+/*--------------------------------------------------------------------------*/
+
+
+KGeneralSettingsPage::KGeneralSettingsPage( KSettingsDialog * dialog,
+ QWidget * parent,
+ KDirStatApp * mainWin )
+ : KSettingsPage( dialog, parent )
+ , _mainWin( mainWin )
+ , _treeView( mainWin->treeView() )
+{
+
+ // Create layout and widgets.
+
+ QVBoxLayout * layout = new QVBoxLayout( this, 5, // border
+ dialog->spacingHint() ); // spacing
+
+ QVGroupBox * gbox = new QVGroupBox( i18n( "Directory Reading" ), this );
+ layout->addWidget( gbox );
+
+ _crossFileSystems = new QCheckBox( i18n( "Cross &File System Boundaries" ), gbox );
+ _enableLocalDirReader = new QCheckBox( i18n( "Use Optimized &Local Directory Read Methods" ), gbox );
+
+ connect( _enableLocalDirReader, SIGNAL( stateChanged( int ) ),
+ this, SLOT ( checkEnabledState() ) );
+
+ layout->addSpacing( 10 );
+
+ gbox = new QVGroupBox( i18n( "Animation" ), this );
+ layout->addWidget( gbox );
+
+ _enableToolBarAnimation = new QCheckBox( i18n( "P@cM@n Animation in Tool &Bar" ), gbox );
+ _enableTreeViewAnimation = new QCheckBox( i18n( "P@cM@n Animation in Directory &Tree" ), gbox );
+}
+
+
+KGeneralSettingsPage::~KGeneralSettingsPage()
+{
+ // NOP
+}
+
+
+void
+KGeneralSettingsPage::apply()
+{
+ KConfig * config = kapp->config();
+
+ config->setGroup( "Directory Reading" );
+ config->writeEntry( "CrossFileSystems", _crossFileSystems->isChecked() );
+ config->writeEntry( "EnableLocalDirReader", _enableLocalDirReader->isChecked() );
+
+ config->setGroup( "Animation" );
+ config->writeEntry( "ToolbarPacMan", _enableToolBarAnimation->isChecked() );
+ config->writeEntry( "DirTreePacMan", _enableTreeViewAnimation->isChecked() );
+
+ _mainWin->initPacMan( _enableToolBarAnimation->isChecked() );
+ _treeView->enablePacManAnimation( _enableTreeViewAnimation->isChecked() );
+}
+
+
+void
+KGeneralSettingsPage::revertToDefaults()
+{
+ _crossFileSystems->setChecked( false );
+ _enableLocalDirReader->setChecked( true );
+
+ _enableToolBarAnimation->setChecked( true );
+ _enableTreeViewAnimation->setChecked( false );
+}
+
+
+void
+KGeneralSettingsPage::setup()
+{
+ KConfig * config = kapp->config();
+ config->setGroup( "Directory Reading" );
+
+ _crossFileSystems->setChecked ( config->readBoolEntry( "CrossFileSystems" , false) );
+ _enableLocalDirReader->setChecked ( config->readBoolEntry( "EnableLocalDirReader" , true ) );
+
+ _enableToolBarAnimation->setChecked ( _mainWin->pacManEnabled() );
+ _enableTreeViewAnimation->setChecked( _treeView->doPacManAnimation() );
+
+ checkEnabledState();
+}
+
+
+void
+KGeneralSettingsPage::checkEnabledState()
+{
+ _crossFileSystems->setEnabled( _enableLocalDirReader->isChecked() );
+}
+
+
+/*--------------------------------------------------------------------------*/
+
+
+KTreemapPage::KTreemapPage( KSettingsDialog * dialog,
+ QWidget * parent,
+ KDirStatApp * mainWin )
+ : KSettingsPage( dialog, parent )
+ , _mainWin( mainWin )
+{
+ // kdDebug() << k_funcinfo << endl;
+
+ QVBoxLayout * layout = new QVBoxLayout( this, 0, 0 ); // parent, border, spacing
+
+ QVBox * vbox = new QVBox( this );
+ vbox->setSpacing( dialog->spacingHint() );
+ layout->addWidget( vbox );
+
+ _squarify = new QCheckBox( i18n( "S&quarify Treemap" ), vbox );
+ _doCushionShading = new QCheckBox( i18n( "Use C&ushion Shading" ), vbox );
+
+
+ // Cushion parameters
+
+ QVGroupBox * gbox = new QVGroupBox( i18n( "Cushion Parameters" ), vbox );
+ _cushionParams = gbox;
+ gbox->addSpace( 7 );
+ gbox->setSizePolicy( QSizePolicy( QSizePolicy::Expanding, QSizePolicy::Expanding ) );
+
+ QLabel * label = new QLabel( i18n( "Ambient &Light" ), gbox );
+ QHBox * hbox = new QHBox( gbox );
+ _ambientLight = new QSlider ( MinAmbientLight, MaxAmbientLight, 10, // min, max, pageStep
+ DefaultAmbientLight, Horizontal, hbox );
+ _ambientLightSB = new QSpinBox( MinAmbientLight, MaxAmbientLight, 1, // min, max, step
+ hbox );
+ _ambientLightSB->setSizePolicy( QSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum ) );
+ label->setBuddy( _ambientLightSB );
+
+ gbox->addSpace( 7 );
+ label = new QLabel( i18n( "&Height Scale" ), gbox );
+ hbox = new QHBox( gbox );
+ _heightScalePercent = new QSlider( MinHeightScalePercent, MaxHeightScalePercent, 10, // min, max, pageStep
+ DefaultHeightScalePercent, Horizontal, hbox );
+ _heightScalePercentSB = new QSpinBox( MinHeightScalePercent, MaxHeightScalePercent, 1, // min, max, step
+ hbox );
+ _heightScalePercentSB->setSizePolicy( QSizePolicy( QSizePolicy::Minimum, QSizePolicy::Minimum ) );
+ label->setBuddy( _heightScalePercentSB );
+
+ gbox->addSpace( 10 );
+ _ensureContrast = new QCheckBox( i18n( "Draw Lines if Lo&w Contrast" ), gbox );
+
+
+ hbox = new QHBox( gbox );
+ _forceCushionGrid = new QCheckBox( i18n( "Always Draw &Grid" ), hbox );
+ addHStretch( hbox );
+
+ _cushionGridColorL = new QLabel( " " + i18n( "Gr&id Color: " ), hbox );
+ _cushionGridColor = new KColorButton( hbox );
+ _cushionGridColorL->setBuddy( _cushionGridColor );
+ _cushionGridColorL->setAlignment( AlignRight | AlignVCenter );
+
+ // addVStretch( vbox );
+
+
+ // Plain treemaps parameters
+
+ _plainTileParams = new QHGroupBox( i18n( "Colors for Plain Treemaps" ), vbox );
+
+ _plainTileParams->addSpace( 7 );
+ label = new QLabel( i18n( "&Files: " ), _plainTileParams );
+ _fileFillColor = new KColorButton( _plainTileParams );
+ label->setBuddy( _fileFillColor );
+ label->setAlignment( AlignRight | AlignVCenter );
+
+ label = new QLabel( " " + i18n( "&Directories: " ), _plainTileParams );
+ _dirFillColor = new KColorButton( _plainTileParams );
+ label->setBuddy( _dirFillColor );
+ label->setAlignment( AlignRight | AlignVCenter );
+
+ label = new QLabel( i18n( "Gr&id: " ), _plainTileParams );
+ _outlineColor = new KColorButton( _plainTileParams );
+ label->setBuddy( _outlineColor );
+ label->setAlignment( AlignRight | AlignVCenter );
+
+
+ // Misc
+
+ QWidget * gridBox = new QWidget( vbox );
+ QGridLayout * grid = new QGridLayout( gridBox, 2, 3, dialog->spacingHint() ); // rows, cols, spacing
+ grid->setColStretch( 0, 0 ); // (col, stretch) don't stretch this column
+ grid->setColStretch( 1, 0 ); // don't stretch
+ grid->setColStretch( 2, 1 ); // stretch this as you like
+
+ label = new QLabel( i18n( "Hi&ghlight R&ectangle: " ), gridBox );
+ _highlightColor = new KColorButton( gridBox );
+ label->setBuddy( _highlightColor );
+
+ grid->addWidget( label, 0, 0 );
+ grid->addWidget( _highlightColor, 0, 1 );
+
+
+ label = new QLabel( i18n( "Minim&um Treemap Tile Size: " ), gridBox );
+ _minTileSize = new QSpinBox( 0, 30, 1, gridBox ); // min, max, step, parent
+ label->setBuddy( _minTileSize );
+
+ grid->addWidget( label, 1, 0 );
+ grid->addWidget( _minTileSize, 1, 1 );
+
+ _autoResize = new QCheckBox( i18n( "Auto-&Resize Treemap" ), vbox );
+
+
+ // Connections
+
+
+ connect( _ambientLight, SIGNAL( valueChanged(int) ),
+ _ambientLightSB, SLOT ( setValue (int) ) );
+
+ connect( _ambientLightSB, SIGNAL( valueChanged(int) ),
+ _ambientLight, SLOT ( setValue (int) ) );
+
+
+ connect( _heightScalePercent, SIGNAL( valueChanged(int) ),
+ _heightScalePercentSB, SLOT ( setValue (int) ) );
+
+ connect( _heightScalePercentSB, SIGNAL( valueChanged(int) ),
+ _heightScalePercent, SLOT ( setValue (int) ) );
+
+
+ connect( _doCushionShading, SIGNAL( stateChanged( int ) ), this, SLOT( checkEnabledState() ) );
+ connect( _forceCushionGrid, SIGNAL( stateChanged( int ) ), this, SLOT( checkEnabledState() ) );
+
+ checkEnabledState();
+}
+
+
+KTreemapPage::~KTreemapPage()
+{
+ // NOP
+}
+
+
+void
+KTreemapPage::apply()
+{
+ KConfig * config = kapp->config();
+
+ config->setGroup( "Treemaps" );
+
+ config->writeEntry( "Squarify", _squarify->isChecked() );
+ config->writeEntry( "CushionShading", _doCushionShading->isChecked() );
+ config->writeEntry( "AmbientLight", _ambientLight->value() );
+ config->writeEntry( "HeightScaleFactor", _heightScalePercent->value() / 100.0 );
+ config->writeEntry( "EnsureContrast", _ensureContrast->isChecked() );
+ config->writeEntry( "ForceCushionGrid", _forceCushionGrid->isChecked() );
+ config->writeEntry( "MinTileSize", _minTileSize->value() );
+ config->writeEntry( "AutoResize", _autoResize->isChecked() );
+ config->writeEntry( "CushionGridColor", _cushionGridColor->color() );
+ config->writeEntry( "OutlineColor", _outlineColor->color() );
+ config->writeEntry( "FileFillColor", _fileFillColor->color() );
+ config->writeEntry( "DirFillColor", _dirFillColor->color() );
+ config->writeEntry( "HighlightColor", _highlightColor->color() );
+
+ if ( treemapView() )
+ {
+ treemapView()->readConfig();
+ treemapView()->rebuildTreemap();
+ }
+}
+
+
+void
+KTreemapPage::revertToDefaults()
+{
+ _squarify->setChecked( true );
+ _doCushionShading->setChecked( true );
+
+ _ambientLight->setValue( DefaultAmbientLight );
+ _heightScalePercent->setValue( DefaultHeightScalePercent );
+ _ensureContrast->setChecked( true );
+ _forceCushionGrid->setChecked( false );
+ _minTileSize->setValue( DefaultMinTileSize );
+ _autoResize->setChecked( true );
+
+ _cushionGridColor->setColor ( QColor( 0x80, 0x80, 0x80 ) );
+ _outlineColor->setColor ( black );
+ _fileFillColor->setColor ( QColor( 0xde, 0x8d, 0x53 ) );
+ _dirFillColor->setColor ( QColor( 0x10, 0x7d, 0xb4 ) );
+ _highlightColor->setColor ( red );
+}
+
+
+void
+KTreemapPage::setup()
+{
+ KConfig * config = kapp->config();
+ config->setGroup( "Treemaps" );
+
+ _squarify->setChecked ( config->readBoolEntry( "Squarify" , true ) );
+ _doCushionShading->setChecked ( config->readBoolEntry( "CushionShading" , true ) );
+
+ _ambientLight->setValue ( config->readNumEntry( "AmbientLight" , DefaultAmbientLight ) );
+ _heightScalePercent->setValue( (int) ( 100 * config->readDoubleNumEntry ( "HeightScaleFactor", DefaultHeightScaleFactor ) ) );
+ _ensureContrast->setChecked ( config->readBoolEntry( "EnsureContrast" , true ) );
+ _forceCushionGrid->setChecked ( config->readBoolEntry( "ForceCushionGrid" , false ) );
+ _minTileSize->setValue ( config->readNumEntry ( "MinTileSize" , DefaultMinTileSize ) );
+ _autoResize->setChecked ( config->readBoolEntry( "AutoResize" , true ) );
+
+ _cushionGridColor->setColor ( readColorEntry( config, "CushionGridColor" , QColor( 0x80, 0x80, 0x80 ) ) );
+ _outlineColor->setColor ( readColorEntry( config, "OutlineColor" , black ) );
+ _fileFillColor->setColor ( readColorEntry( config, "FileFillColor" , QColor( 0xde, 0x8d, 0x53 ) ) );
+ _dirFillColor->setColor ( readColorEntry( config, "DirFillColor" , QColor( 0x10, 0x7d, 0xb4 ) ) );
+ _highlightColor->setColor ( readColorEntry( config, "HighlightColor" , red ) );
+
+ _ambientLightSB->setValue( _ambientLight->value() );
+ _heightScalePercentSB->setValue( _heightScalePercent->value() );
+
+ checkEnabledState();
+}
+
+
+void
+KTreemapPage::checkEnabledState()
+{
+ _cushionParams->setEnabled( _doCushionShading->isChecked() );
+ _plainTileParams->setEnabled( ! _doCushionShading->isChecked() );
+
+ if ( _doCushionShading->isChecked() )
+ {
+ _cushionGridColor->setEnabled ( _forceCushionGrid->isChecked() );
+ _cushionGridColorL->setEnabled( _forceCushionGrid->isChecked() );
+ _ensureContrast->setEnabled ( ! _forceCushionGrid->isChecked() );
+ }
+}
+
+
+QColor
+KTreemapPage::readColorEntry( KConfig * config, const char * entryName, QColor defaultColor )
+{
+ return config->readColorEntry( entryName, &defaultColor );
+}
+
+
+
+void
+addHStretch( QWidget * parent )
+{
+ QWidget * stretch = new QWidget( parent );
+ stretch->setSizePolicy( QSizePolicy( QSizePolicy::Expanding, // hor
+ QSizePolicy::Minimum, // vert
+ 1, // hstretch
+ 0 ) ); // vstretch
+}
+
+
+void
+addVStretch( QWidget * parent )
+{
+ QWidget * stretch = new QWidget( parent );
+ stretch->setSizePolicy( QSizePolicy( QSizePolicy::Minimum, // hor
+ QSizePolicy::Expanding, // vert
+ 0, // hstretch
+ 1 ) ); // vstretch
+}
+
+
+// EOF
diff --git a/kdirstat/kdirstatsettings.h b/kdirstat/kdirstatsettings.h
new file mode 100644
index 0000000..0091f09
--- /dev/null
+++ b/kdirstat/kdirstatsettings.h
@@ -0,0 +1,744 @@
+/*
+ * File name: kdirstatsettings.h
+ * Summary: Settings dialog for KDirStat
+ * License: GPL - See file COPYING for details.
+ * Author: Stefan Hundhammer <sh@suse.de>
+ *
+ * Updated: 2003-01-07
+ */
+
+
+#ifndef KDirStatSettings_h
+#define KDirStatSettings_h
+
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <qlistbox.h>
+#include <kdialogbase.h>
+#include "kcleanup.h"
+#include "kcleanupcollection.h"
+#include "kdirstatapp.h"
+
+
+class QCheckBox;
+class QComboBox;
+class QHGroupBox;
+class QLabel;
+class QLineEdit;
+class QRadioButton;
+class QSlider;
+class QSpinBox;
+class QVGroupBox;
+class QWidget;
+
+class KColorButton;
+
+
+#define KDirStatSettingsMaxColorButton 12
+
+
+namespace KDirStat
+{
+ class KCleanupListBox;
+ class KCleanupPropertiesPage;
+ class KDirTreeView;
+ class KTreemapView;
+
+
+ /**
+ * Settings dialog for KDirStat
+ *
+ * @short Settings dialog for KDirStat
+ **/
+ class KSettingsDialog: public KDialogBase
+ {
+ Q_OBJECT
+
+ public:
+
+ /**
+ * Constructor.
+ *
+ * Notice there is no parent widget passed but the application's main
+ * window so its functions can be accessed. The parent of this widget
+ * is always 0 since this is a dialog.
+ **/
+
+ KSettingsDialog( KDirStatApp * mainWin );
+
+ /**
+ * Destructor.
+ **/
+ virtual ~KSettingsDialog();
+
+
+ /**
+ * Overwritten from @ref QDialog() to get any chance to set up the
+ * dialog contents when the dialog gets shown - every time, not just at
+ * program startup when the settings dialog is created (!).
+ *
+ * QTabDialog used to have 'aboutToShow()' for a good reason, but the
+ * creators of @ref KDialogBase in their infinite wisdom chose not to
+ * include anything similar. How is that supposed to work, anyway?
+ * Everything I saw in any other KDE sources looked to me like ugly
+ * hacks to work around this. Am I really supposed to destroy my
+ * settings dialog and create a new one every time it pops up? This can
+ * certainly not be the way to go.
+ *
+ * This overwritten show() method sends that @ref aboutToShow() signal
+ * before calling the parent class show() method.
+ **/
+ virtual void show();
+
+
+ public slots:
+
+ /**
+ * Reimplemented from @ref KDialogBase to ask for confirmation.
+ * Emits signal @ref defaultClicked() when the user confirms.
+ **/
+ virtual void slotDefault();
+
+ /**
+ * Reimplemented from @ref KDialogBase to set the appropriate help
+ * topic prior to invoking online help.
+ **/
+ virtual void slotHelp();
+
+
+ signals:
+
+ /**
+ * Emitted when (you might have guessed it) the dialog is about to be
+ * shown. Connect this to slots that fill the individual dialog pages'
+ * widgets contents (input fields etc.)
+ **/
+ void aboutToShow();
+
+ protected:
+
+ KDirStatApp * _mainWin;
+ int _cleanupsPageIndex;
+ int _treeColorsPageIndex;
+ int _treemapPageIndex;
+ int _generalSettingsPageIndex;
+
+ }; // class KSettingsDialog
+
+
+
+ /**
+ * Abstract base class for all settings pages. Contains stubs for methods
+ * that all settings pages have in common: setup(), apply(),
+ * revertToDefaults().
+ *
+ * Note: This class contains pure virtuals - it cannot be
+ * instantiated. Rather, derive your own classes from this one.
+ **/
+ class KSettingsPage: public QWidget
+ {
+ Q_OBJECT
+
+ public:
+
+ /**
+ * Constructor.
+ *
+ * Sets up standard connections to the methods defined in this class,
+ * e.g., apply(), setup(), revertToDefaults().
+ **/
+ KSettingsPage( KSettingsDialog * dialog,
+ QWidget * parent );
+
+ /**
+ * Destructor.
+ **/
+ virtual ~KSettingsPage();
+
+
+ public slots:
+
+ /**
+ * Apply the changes.
+ *
+ * Derived classes need to reimplement this method.
+ **/
+ virtual void apply() = 0;
+
+ /**
+ * Revert all values to their defaults.
+ *
+ * Derived classes need to reimplement this method.
+ **/
+ virtual void revertToDefaults() = 0;
+
+ /**
+ * Set up all fields prior to displaying the dialog.
+ *
+ * Derived classes need to reimplement this method.
+ **/
+ virtual void setup() = 0;
+
+
+ public:
+
+ /**
+ * Returns the page index of this page.
+ * This seems to be the only way to find out which settings page is in
+ * the foreground for a @ref KDialogBase page.
+ **/
+ int pageIndex() { return _pageIndex; }
+
+ protected:
+
+ int _pageIndex;
+
+ }; // class KSettingsPage
+
+
+
+ /**
+ * Settings tab page for the tree colors.
+ *
+ * Uses a vertical slider on the left side and a column of color
+ * selection buttons on the right side. The slider enables/disables
+ * the color buttons from top to bottom (at least one button is always
+ * enabled). Each button represents the percentage fill color of one
+ * directory level within the tree. When the tree widget runs out of
+ * colors (i.e. there are more directory levels than different
+ * colors), it will wrap around to the first color.
+ *
+ * @short settings page for tree colors
+ * @author Stefan Hundhammer <sh@suse.de>
+ **/
+ class KTreeColorsPage: public KSettingsPage
+ {
+ Q_OBJECT
+
+ public:
+
+ /**
+ * Constructor
+ **/
+ KTreeColorsPage( KSettingsDialog * dialog,
+ QWidget * parent,
+ KDirStatApp * mainWin );
+
+ /**
+ * Destructor
+ **/
+ virtual ~KTreeColorsPage();
+
+
+ public slots:
+
+ /**
+ * Apply the changes.
+ *
+ * Inherited from @ref KSettingsPage.
+ **/
+ virtual void apply();
+
+ /**
+ * Revert all values to their defaults.
+ *
+ * Inherited from @ref KSettingsPage.
+ **/
+ virtual void revertToDefaults();
+
+ /**
+ * Set up all fields prior to displaying the dialog.
+ *
+ * Inherited from @ref KSettingsPage.
+ **/
+ virtual void setup();
+
+
+ protected slots:
+
+ /**
+ * Enable all colors up to color no. 'maxColors'.
+ **/
+ void enableColors( int maxColors );
+
+
+ protected:
+
+
+ KDirStatApp * _mainWin;
+ KDirTreeView * _treeView;
+ QSlider * _slider;
+ KColorButton * _colorButton [ KDirStatSettingsMaxColorButton ];
+ QLabel * _colorLabel [ KDirStatSettingsMaxColorButton ];
+
+ int _maxButtons;
+
+ }; // class KTreeColorsPage
+
+
+
+ /**
+ * Settings tab page for cleanup actions.
+ *
+ * Uses a KCleanupListBox for selection of one cleanup action and a
+ * KCleanupPropertiesPage for editing this cleanup action's
+ * properties. This class handles just the switching between the individual
+ * cleanups. It copies the cleanup actions inserted and works with the
+ * copies only until it is requested to save the changes or revert all
+ * values to their defaults.
+ *
+ * @short settings page for cleanup actions
+ **/
+ class KCleanupPage: public KSettingsPage
+ {
+ Q_OBJECT
+
+ public:
+
+ /**
+ * Constructor
+ **/
+ KCleanupPage( KSettingsDialog * dialog,
+ QWidget * parent,
+ KDirStatApp * mainWin );
+
+ /**
+ * Destructor
+ **/
+ virtual ~KCleanupPage();
+
+ /**
+ * Insert an entry for a cleanup action. This is the original value
+ * that will be changed only when receiving the apply() or
+ * defaultValues() signals.
+ **/
+ void insert( KCleanup *cleanup );
+
+ /**
+ * Import all cleanup actions from the originals (from the main
+ * window) to internal working copies.
+ **/
+ void importCleanups();
+
+ /**
+ * Copy the internal working copies of the cleanup actions back to
+ * the main window's originals. Take care of pending changes within
+ * the current properties page's fields prior to that.
+ **/
+ void exportCleanups();
+
+
+ public slots:
+
+ /**
+ * Apply the changes.
+ *
+ * Inherited from @ref KSettingsPage.
+ **/
+ virtual void apply();
+
+ /**
+ * Revert all values to their defaults.
+ *
+ * Inherited from @ref KSettingsPage.
+ **/
+ virtual void revertToDefaults();
+
+ /**
+ * Set up all fields prior to displaying the dialog.
+ *
+ * Inherited from @ref KSettingsPage.
+ **/
+ virtual void setup();
+
+ /**
+ * Switch back and forth between all the cleanup actions very much
+ * like in a tab dialog: Exchange field contents of the cleanup
+ * properties page with the cleanup specified. Store the old
+ * properties page contents in the working copies of the cleanups.
+ **/
+ void changeCleanup( KCleanup * cleanup );
+
+
+ protected:
+
+ /**
+ * Retrieve any pending changes from the properties page and store
+ * them in the cleanup specified.
+ **/
+ void storeProps( KCleanup * cleanup );
+
+
+ //
+ // Data members
+ //
+
+ KCleanupListBox * _listBox;
+ KCleanupPropertiesPage * _props;
+ KDirStatApp * _mainWin;
+
+ KCleanupCollection _workCleanupCollection;
+ KCleanup * _currentCleanup;
+
+ }; // class KCleanupPage
+
+
+
+ /**
+ * List box for cleanup actions.
+ *
+ * This is meant as a substitute for a tabbed dialog inside the tabbed
+ * dialog which would be much too wide and possibly confusing. Plus, this
+ * list box is supposed to take care of its own geometry - the normal
+ * dumbass list box obviously cannot do that. It just uses some random
+ * geometry, relying on scroll bars for everything else. But in this
+ * special case we want all items to be visible at all times without scroll
+ * bars.
+ *
+ * @short cleanup list box
+ **/
+ class KCleanupListBox: public QListBox
+ {
+ Q_OBJECT
+
+ public:
+
+ /**
+ * Constructor.
+ **/
+ KCleanupListBox( QWidget * parent = 0 );
+
+ /**
+ * Destructor.
+ **/
+ virtual ~KCleanupListBox() {};
+
+ /**
+ * Reimplemented so we can make sure all items are visible at all times
+ * without scrolling. In fact, we never want to see a scroll bar with
+ * this kind of list box.
+ **/
+ virtual QSize sizeHint() const;
+
+ /**
+ * Insert an entry for a cleanup action into the list box. Uses the
+ * cleanup action's internally stored title for display.
+ **/
+ void insert( KCleanup * cleanup );
+
+ /**
+ * Returns the currently selected cleanup of 0 if nothing is selected.
+ **/
+ KCleanup * selection() { return _selection; }
+
+ /**
+ * Update the list item's text that corresponds to 'cleanup' - the user
+ * may have entered a new cleanup name. '0' means "check all items".
+ **/
+ void updateTitle( KCleanup * cleanup = 0 );
+
+
+ signals:
+
+ /**
+ * Emitted when the user selects a list item, i.e. a cleanup action.
+ **/
+ void selectCleanup( KCleanup * cleanup );
+
+
+ protected slots:
+
+ /**
+ * Select an item.
+ **/
+ void selectCleanup( QListBoxItem * item );
+
+
+ protected:
+
+ KCleanup * _selection;
+
+ }; // class KCleanupListBox
+
+
+
+ /**
+ * List box item for a KCleanupListBox.
+ **/
+ class KCleanupListBoxItem: public QListBoxText
+ {
+ public:
+
+ /**
+ * Constructor.
+ **/
+ KCleanupListBoxItem( KCleanupListBox * listBox,
+ KCleanup * cleanup );
+
+ /**
+ * Returns the corresponding cleanup.
+ **/
+ KCleanup * cleanup() { return _cleanup; }
+
+ /**
+ * Update the list box display with the cleanup's name which may have
+ * changed - the user may have entered a new one.
+ **/
+ void updateTitle();
+
+
+ protected:
+
+
+ // Data members
+
+ KCleanup * _cleanup;
+
+ }; // class KCleanupListBoxItem
+
+
+
+ /**
+ * Properties page for one cleanup action.
+ **/
+ class KCleanupPropertiesPage: public QWidget
+ {
+ Q_OBJECT
+
+ public:
+
+ /**
+ * Constructor
+ **/
+ KCleanupPropertiesPage( QWidget * parent,
+ KDirStatApp * mainWin );
+
+ /**
+ * Retrieve the page's fields' values and store them in the cleanup
+ * action.
+ **/
+ KCleanup fields( void ) const;
+
+
+ public slots:
+
+ /**
+ * Set the page's fields' values with the cleanup action's
+ * contents.
+ **/
+ void setFields( const KCleanup * cleanup );
+
+ /**
+ * Enable / disable all of the properties page's fields except the
+ * 'enabled' check box.
+ **/
+ void enableFields( bool active );
+
+
+ protected:
+
+ QString _id;
+ QCheckBox * _enabled;
+ QWidget * _fields;
+ QLineEdit * _title;
+ QLineEdit * _command;
+ QCheckBox * _recurse;
+ QCheckBox * _askForConfirmation;
+ QCheckBox * _worksForDir;
+ QCheckBox * _worksForFile;
+ QCheckBox * _worksForDotEntry;
+ QComboBox * _worksForProtocols;
+ QComboBox * _refreshPolicy;
+
+ KDirStatApp * _mainWin;
+
+ }; // class KCleanupPropertiesPage
+
+
+
+ /**
+ * Settings tab page for general/misc settings.
+ **/
+ class KGeneralSettingsPage: public KSettingsPage
+ {
+ Q_OBJECT
+
+ public:
+
+ /**
+ * Constructor
+ **/
+ KGeneralSettingsPage( KSettingsDialog * dialog,
+ QWidget * parent,
+ KDirStatApp * mainWin );
+
+ /**
+ * Destructor
+ **/
+ virtual ~KGeneralSettingsPage();
+
+
+ public slots:
+
+ /**
+ * Apply the changes.
+ *
+ * Inherited from @ref KSettingsPage.
+ **/
+ virtual void apply();
+
+ /**
+ * Revert all values to their defaults.
+ *
+ * Inherited from @ref KSettingsPage.
+ **/
+ virtual void revertToDefaults();
+
+ /**
+ * Set up all fields prior to displaying the dialog.
+ *
+ * Inherited from @ref KSettingsPage.
+ **/
+ virtual void setup();
+
+ /**
+ * Check the enabled state of all widgets depending on the value of
+ * other widgets.
+ **/
+ void checkEnabledState();
+
+
+ protected:
+
+ // Data members
+
+ KDirStatApp * _mainWin;
+ KDirTreeView * _treeView;
+
+ QCheckBox * _crossFileSystems;
+ QCheckBox * _enableLocalDirReader;
+
+ QCheckBox * _enableToolBarAnimation;
+ QCheckBox * _enableTreeViewAnimation;
+
+ }; // class KGeneralSettingsPage
+
+
+
+ /**
+ * Settings tab page for treemap settings.
+ **/
+ class KTreemapPage: public KSettingsPage
+ {
+ Q_OBJECT
+
+ public:
+
+ /**
+ * Constructor
+ **/
+ KTreemapPage( KSettingsDialog * dialog,
+ QWidget * parent,
+ KDirStatApp * mainWin );
+
+ /**
+ * Destructor
+ **/
+ virtual ~KTreemapPage();
+
+
+ public slots:
+
+ /**
+ * Apply the changes.
+ *
+ * Inherited from @ref KSettingsPage.
+ **/
+ virtual void apply();
+
+ /**
+ * Revert all values to their defaults.
+ *
+ * Inherited from @ref KSettingsPage.
+ **/
+ virtual void revertToDefaults();
+
+ /**
+ * Set up all fields prior to displaying the dialog.
+ *
+ * Inherited from @ref KSettingsPage.
+ **/
+ virtual void setup();
+
+ /**
+ * Check the enabled state of all widgets depending on the value of
+ * other widgets.
+ **/
+ void checkEnabledState();
+
+
+ protected:
+
+ /**
+ * Returns the main window's current treemap view or 0 if there is
+ * none. Don't cache this value, it changes frequently!
+ **/
+ KTreemapView * treemapView() const { return _mainWin->treemapView(); }
+
+ /**
+ * Convenience method to read a color from 'config'.
+ **/
+ QColor readColorEntry( KConfig * config,
+ const char * entryName,
+ QColor defaultColor );
+
+ // Data members
+
+ KDirStatApp * _mainWin;
+
+
+ // Widgets
+
+ QCheckBox * _squarify;
+ QCheckBox * _doCushionShading;
+ QVGroupBox * _cushionParams;
+ QSlider * _ambientLight;
+ QSpinBox * _ambientLightSB;
+ QSlider * _heightScalePercent;
+ QSpinBox * _heightScalePercentSB;
+ QCheckBox * _ensureContrast;
+ QCheckBox * _forceCushionGrid;
+ KColorButton * _cushionGridColor;
+ QLabel * _cushionGridColorL;
+ QHGroupBox * _plainTileParams;
+ KColorButton * _fileFillColor;
+ KColorButton * _dirFillColor;
+ KColorButton * _outlineColor;
+ KColorButton * _highlightColor;
+ QSpinBox * _minTileSize;
+ QCheckBox * _autoResize;
+
+ }; // class KTreemapPage
+
+} // namespace KDirStat
+
+
+/**
+ * Add a horizontal stretch widget to take all excess space.
+ **/
+void addHStretch( QWidget * parent );
+
+/**
+ * Add a vertical stretch widget to take all excess space.
+ **/
+void addVStretch( QWidget * parent );
+
+
+
+#endif // ifndef KDirStatSettings_h
+
+
+// EOF
diff --git a/kdirstat/kdirstatui.rc b/kdirstat/kdirstatui.rc
new file mode 100644
index 0000000..7234719
--- /dev/null
+++ b/kdirstat/kdirstatui.rc
@@ -0,0 +1,189 @@
+<!-- Emacs: -*-xml-*- [must remain in first line!] -->
+<!-- -->
+<!-- XML GUI description file for KDirStat -->
+<!-- -->
+<!-- Author: Stefan Hundhammer (sh@suse.de) -->
+<!-- Updated: 2004-12-06 -->
+
+
+<!DOCTYPE kpartgui>
+
+<!-- DOCTYPE kpartgui SYSTEM "kpartgui.dtd" would be correct, but -->
+<!-- unfortunately, the DTD isn't installed anywhere, so this only confuses any -->
+<!-- XML/SGML tools like Emacs PSGML mode :-( -->
+
+
+<kpartgui name="kdirstat" version="1.8.2">
+
+
+ <MenuBar>
+
+ <Menu name="file" noMerge="1"> <text>&amp;File</text>
+ <Action name="file_open" />
+ <Action name="file_open_url" />
+ <Action name="file_open_recent" />
+ <Action name="file_refresh_all"/>
+ <Action name="file_refresh_selected"/>
+ <Action name="file_continue_reading_at_mount_point"/>
+ <Action name="file_stop_reading"/>
+ <Separator/>
+ <Action name="file_close"/>
+ <Action name="file_quit"/>
+ </Menu>
+
+ <Menu name="cleanup" noMerge="1"> <text>Clean &amp;Up</text>
+ <TearOffHandle/>
+ <Action name="cleanup_open_with"/>
+ <Separator/>
+ <Action name="cleanup_open_in_konqueror"/>
+ <Action name="cleanup_open_in_terminal"/>
+ <Action name="cleanup_compress_subtree"/>
+ <Action name="cleanup_make_clean"/>
+ <Action name="cleanup_delete_trash"/>
+ <Action name="cleanup_move_to_trash_bin"/>
+ <Action name="cleanup_hard_delete"/>
+ <Separator/>
+ <Action name="cleanup_user_defined_0"/>
+ <Action name="cleanup_user_defined_1"/>
+ <Action name="cleanup_user_defined_2"/>
+ <Action name="cleanup_user_defined_3"/>
+ <Action name="cleanup_user_defined_4"/>
+ <Action name="cleanup_user_defined_5"/>
+ <Action name="cleanup_user_defined_6"/>
+ <Action name="cleanup_user_defined_7"/>
+ <Action name="cleanup_user_defined_8"/>
+ <Action name="cleanup_user_defined_9"/>
+ </Menu>
+
+ <Menu name="treemap" noMerge="1"> <text>&amp;Treemap</text>
+ <TearOffHandle/>
+ <Action name="options_show_treemap"/>
+ <Separator/>
+ <Action name="treemap_zoom_in"/>
+ <Action name="treemap_zoom_out"/>
+ <Action name="treemap_select_parent"/>
+ <Action name="treemap_rebuild"/>
+ <Separator/>
+ <Action name="treemap_help"/>
+ </Menu>
+
+ <Menu name="settings" noMerge="1"> <text>&amp;Settings</text>
+ <Action name="options_show_treemap"/>
+ <Action name="options_show_toolbar"/>
+ <Action name="options_show_statusbar"/>
+ <Action name="options_configure"/>
+ </Menu>
+
+ <Menu name="report" noMerge="1"> <text>&amp;Report</text>
+ <Action name="report_mail_to_owner"/>
+ </Menu>
+
+
+ <Menu name="help" noMerge="1"> <text>&amp;Help</text>
+ <Action name="help_contents"/>
+ <Action name="help_whats_this"/>
+ <Separator/>
+ <Action name="help_report_bug"/>
+ <Action name="help_send_feedback_mail"/>
+ <Separator/>
+ <Action name="help_about_app"/>
+ <Action name="help_about_kde"/>
+ </Menu>
+
+ </MenuBar>
+
+
+ <ToolBar name="mainToolBar" fullWidth="true" noMerge="1"> <text>Main Toolbar</text>
+ <Action name="file_open" group="file_operations" />
+ <Action name="file_refresh_all" group="file_operations" />
+ <Action name="file_stop_reading" group="file_operations" />
+ <Separator/>
+ <Action name="edit_copy" group="edit_operations" />
+ <Action name="report_mail_to_owner" group="report_operations"/>
+ <Separator/>
+ <Action name="cleanup_open_in_konqueror" group="cleanup_operations"/>
+ <Action name="cleanup_open_in_terminal" group="cleanup_operations"/>
+ <Action name="cleanup_compress_subtree" group="cleanup_operations"/>
+ <Action name="cleanup_move_to_trash_bin" group="cleanup_operations"/>
+ <Action name="cleanup_hard_delete" group="cleanup_operations"/>
+ <Separator/>
+ <Action name="treemap_zoom_in" group="treemap_operations"/>
+ <Action name="treemap_zoom_out" group="treemap_operations"/>
+ </ToolBar>
+
+
+
+ <!-- Context menu for the tree view -->
+
+ <Menu name="treeViewContextMenu" noMerge="1">
+ <Action name="edit_copy" />
+ <Action name="report_mail_to_owner"/>
+ <Separator/>
+ <Action name="file_refresh_all"/>
+ <Action name="file_refresh_selected"/>
+ <Action name="file_continue_reading_at_mount_point"/>
+ <Separator/>
+ <Action name="cleanup_open_with"/>
+ <Separator/>
+ <Action name="cleanup_open_in_konqueror"/>
+ <Action name="cleanup_open_in_terminal"/>
+ <Action name="cleanup_compress_subtree"/>
+ <Action name="cleanup_make_clean"/>
+ <Action name="cleanup_delete_trash"/>
+ <Action name="cleanup_move_to_trash_bin"/>
+ <Action name="cleanup_hard_delete"/>
+ <Separator/>
+ <Action name="cleanup_user_defined_0"/>
+ <Action name="cleanup_user_defined_1"/>
+ <Action name="cleanup_user_defined_2"/>
+ <Action name="cleanup_user_defined_3"/>
+ <Action name="cleanup_user_defined_4"/>
+ <Action name="cleanup_user_defined_5"/>
+ <Action name="cleanup_user_defined_6"/>
+ <Action name="cleanup_user_defined_7"/>
+ <Action name="cleanup_user_defined_8"/>
+ <Action name="cleanup_user_defined_9"/>
+ </Menu>
+
+
+ <!-- Context menu for the treemap -->
+
+ <Menu name="treemapContextMenu" noMerge="1">
+ <Action name="treemap_zoom_in" />
+ <Action name="treemap_zoom_out"/>
+ <Action name="treemap_select_parent"/>
+ <Action name="treemap_rebuild"/>
+ <Separator/>
+ <Action name="treemap_help"/>
+ <Separator/>
+ <Action name="file_refresh_all"/>
+ <Separator/>
+ <Action name="cleanup_open_with"/>
+ <Separator/>
+ <Action name="cleanup_open_in_konqueror"/>
+ <Action name="cleanup_open_in_terminal"/>
+ <Action name="cleanup_compress_subtree"/>
+ <Action name="cleanup_make_clean"/>
+ <Action name="cleanup_delete_trash"/>
+ <Action name="cleanup_move_to_trash_bin"/>
+ <Action name="cleanup_hard_delete"/>
+ <Separator/>
+ <Action name="cleanup_user_defined_0"/>
+ <Action name="cleanup_user_defined_1"/>
+ <Action name="cleanup_user_defined_2"/>
+ <Action name="cleanup_user_defined_3"/>
+ <Action name="cleanup_user_defined_4"/>
+ <Action name="cleanup_user_defined_5"/>
+ <Action name="cleanup_user_defined_6"/>
+ <Action name="cleanup_user_defined_7"/>
+ <Action name="cleanup_user_defined_8"/>
+ <Action name="cleanup_user_defined_9"/>
+ </Menu>
+
+
+ <!-- Emacs Customization -->
+ <!-- Local Variables: -->
+ <!-- sgml-indent-step: 4 -->
+ <!-- End: -->
+
+</kpartgui>
diff --git a/kdirstat/kdirtree.cpp b/kdirstat/kdirtree.cpp
new file mode 100644
index 0000000..7bf972a
--- /dev/null
+++ b/kdirstat/kdirtree.cpp
@@ -0,0 +1,1636 @@
+/*
+ * File name: kdirtree.cpp
+ * Summary: Support classes for KDirStat
+ * License: LGPL - See file COPYING.LIB for details.
+ * Author: Stefan Hundhammer <sh@suse.de>
+ *
+ * Updated: 2005-01-07
+ */
+
+
+#include "config.h"
+#include <string.h>
+#include <sys/errno.h>
+#include <qtimer.h>
+#include <kapp.h>
+#include <klocale.h>
+#include <kconfig.h>
+#include "kdirtree.h"
+#include "kdirtreeiterators.h"
+#include "kdirtreeview.h"
+#include "kdirsaver.h"
+#include "kio/job.h"
+#include "kio/netaccess.h"
+
+#define HAVE_STUPID_COMPILER 0
+
+
+using namespace KDirStat;
+
+
+KFileInfo::KFileInfo( KDirTree * tree,
+ KDirInfo * parent,
+ const char * name )
+ : _parent( parent )
+ , _next( 0 )
+ , _tree( tree )
+{
+ _isLocalFile = true;
+ _isSparseFile = false;
+ _name = name ? name : "";
+ _device = 0;
+ _mode = 0;
+ _links = 0;
+ _size = 0;
+ _blocks = 0;
+ _mtime = 0;
+}
+
+
+KFileInfo::KFileInfo( const QString & filenameWithoutPath,
+ struct stat * statInfo,
+ KDirTree * tree,
+ KDirInfo * parent )
+ : _parent( parent )
+ , _next( 0 )
+ , _tree( tree )
+{
+ CHECK_PTR( statInfo );
+
+ _isLocalFile = true;
+ _name = filenameWithoutPath;
+
+ _device = statInfo->st_dev;
+ _mode = statInfo->st_mode;
+ _links = statInfo->st_nlink;
+ _mtime = statInfo->st_mtime;
+
+ if ( isSpecial() )
+ {
+ _size = 0;
+ _blocks = 0;
+ _isSparseFile = false;
+ }
+ else
+ {
+ _size = statInfo->st_size;
+ _blocks = statInfo->st_blocks;
+ _isSparseFile = isFile() && ( allocatedSize() < _size );
+
+ if ( _isSparseFile )
+ {
+ kdDebug() << "Found sparse file: " << this
+ << " Byte size: " << formatSize( byteSize() )
+ << " Allocated: " << formatSize( allocatedSize() )
+ << endl;
+ }
+
+#if 0
+ if ( isFile() && _links > 1 )
+ {
+ kdDebug() << _links << " hard links: " << this << endl;
+ }
+#endif
+ }
+
+#if 0
+#warning Debug mode: Huge sizes
+ _size <<= 10;
+#endif
+}
+
+
+KFileInfo::KFileInfo( const KFileItem * fileItem,
+ KDirTree * tree,
+ KDirInfo * parent )
+ : _parent( parent )
+ , _next( 0 )
+ , _tree( tree )
+{
+ CHECK_PTR( fileItem );
+
+ _isLocalFile = fileItem->isLocalFile();
+ _name = parent ? fileItem->name() : fileItem->url().url();
+ _device = 0;
+ _mode = fileItem->mode();
+ _links = 1;
+
+
+ if ( isSpecial() )
+ {
+ _size = 0;
+ _blocks = 0;
+ _isSparseFile = false;
+ }
+ else
+ {
+ _size = fileItem->size();
+
+ // Since KFileItem does not return any information about allocated disk
+ // blocks, calculate that information artificially so callers don't
+ // need to bother with special cases depending on how this object was
+ // constructed.
+
+ _blocks = _size / blockSize();
+
+ if ( ( _size % blockSize() ) > 0 )
+ _blocks++;
+
+ // There is no way to find out via KFileInfo if this is a sparse file.
+ _isSparseFile = false;
+ }
+
+ _mtime = fileItem->time( KIO::UDS_MODIFICATION_TIME );
+}
+
+
+KFileInfo::~KFileInfo()
+{
+ // NOP
+
+
+ /**
+ * The destructor should also take care about unlinking this object from
+ * its parent's children list, but regrettably that just doesn't work: At
+ * this point (within the destructor) parts of the object are already
+ * destroyed, e.g., the virtual table - virtual methods don't work any
+ * more. Thus, somebody from outside must call deletingChild() just prior
+ * to the actual "delete".
+ *
+ * This sucks, but it's the C++ standard.
+ **/
+}
+
+
+KFileSize
+KFileInfo::allocatedSize() const
+{
+ return blocks() * blockSize();
+}
+
+
+KFileSize
+KFileInfo::size() const
+{
+ KFileSize sz = _isSparseFile ? allocatedSize() : _size;
+
+ if ( _links > 1 )
+ sz /= _links;
+
+ return sz;
+}
+
+
+QString
+KFileInfo::url() const
+{
+ if ( _parent )
+ {
+ QString parentUrl = _parent->url();
+
+ if ( isDotEntry() ) // don't append "/." for dot entries
+ return parentUrl;
+
+ if ( parentUrl == "/" ) // avoid duplicating slashes
+ return parentUrl + _name;
+ else
+ return parentUrl + "/" + _name;
+ }
+ else
+ return _name;
+}
+
+
+QString
+KFileInfo::debugUrl() const
+{
+ return url() + ( isDotEntry() ? "/<Files>" : "" );
+}
+
+
+QString
+KFileInfo::urlPart( int targetLevel ) const
+{
+ int level = treeLevel(); // Cache this - it's expensive!
+
+ if ( level < targetLevel )
+ {
+ kdError() << k_funcinfo << "URL level " << targetLevel
+ << " requested, this is level " << level << endl;
+ return "";
+ }
+
+ const KFileInfo *item = this;
+
+ while ( level > targetLevel )
+ {
+ level--;
+ item = item->parent();
+ }
+
+ return item->name();
+}
+
+
+int
+KFileInfo::treeLevel() const
+{
+ int level = 0;
+ KFileInfo * parent = _parent;
+
+ while ( parent )
+ {
+ level++;
+ parent = parent->parent();
+ }
+
+ return level;
+
+
+ if ( _parent )
+ return _parent->treeLevel() + 1;
+ else
+ return 0;
+}
+
+
+bool
+KFileInfo::hasChildren() const
+{
+ return firstChild() || dotEntry();
+}
+
+
+bool
+KFileInfo::isInSubtree( const KFileInfo *subtree ) const
+{
+ const KFileInfo * ancestor = this;
+
+ while ( ancestor )
+ {
+ if ( ancestor == subtree )
+ return true;
+
+ ancestor = ancestor->parent();
+ }
+
+ return false;
+}
+
+
+KFileInfo *
+KFileInfo::locate( QString url, bool findDotEntries )
+{
+ if ( ! url.startsWith( _name ) )
+ return 0;
+ else // URL starts with this node's name
+ {
+ url.remove( 0, _name.length() ); // Remove leading name of this node
+
+ if ( url.length() == 0 ) // Nothing left?
+ return this; // Hey! That's us!
+
+ if ( url.startsWith( "/" ) ) // If the next thing a path delimiter,
+ url.remove( 0, 1 ); // remove that leading delimiter.
+ else // No path delimiter at the beginning
+ {
+ if ( _name.right(1) != "/" && // and this is not the root directory
+ ! isDotEntry() ) // or a dot entry:
+ return 0; // This can't be any of our children.
+ }
+
+
+ // Search all children
+
+ KFileInfo *child = firstChild();
+
+ while ( child )
+ {
+ KFileInfo *foundChild = child->locate( url, findDotEntries );
+
+ if ( foundChild )
+ return foundChild;
+ else
+ child = child->next();
+ }
+
+
+ // Special case: The dot entry is requested.
+
+ if ( findDotEntries && dotEntry() && url == "<Files>" )
+ return dotEntry();
+
+ // Search the dot entry if there is one - but only if there is no more
+ // path delimiter left in the URL. The dot entry contains files only,
+ // and their names may not contain the path delimiter, nor can they
+ // have children. This check is not strictly necessary, but it may
+ // speed up things a bit if we don't search the non-directory children
+ // if the rest of the URL consists of several pathname components.
+
+ if ( dotEntry() &&
+ url.find ( "/" ) < 0 ) // No (more) "/" in this URL
+ {
+ return dotEntry()->locate( url, findDotEntries );
+ }
+ }
+
+ return 0;
+}
+
+
+
+
+
+
+KDirInfo::KDirInfo( KDirTree * tree,
+ KDirInfo * parent,
+ bool asDotEntry )
+ : KFileInfo( tree, parent )
+{
+ init();
+
+ if ( asDotEntry )
+ {
+ _isDotEntry = true;
+ _dotEntry = 0;
+ _name = ".";
+ }
+ else
+ {
+ _isDotEntry = false;
+ _dotEntry = new KDirInfo( tree, this, true );
+ }
+}
+
+
+KDirInfo::KDirInfo( const QString & filenameWithoutPath,
+ struct stat * statInfo,
+ KDirTree * tree,
+ KDirInfo * parent )
+ : KFileInfo( filenameWithoutPath,
+ statInfo,
+ tree,
+ parent )
+{
+ init();
+ _dotEntry = new KDirInfo( tree, this, true );
+}
+
+
+KDirInfo::KDirInfo( const KFileItem * fileItem,
+ KDirTree * tree,
+ KDirInfo * parent )
+ : KFileInfo( fileItem,
+ tree,
+ parent )
+{
+ init();
+ _dotEntry = new KDirInfo( tree, this, true );
+}
+
+
+void
+KDirInfo::init()
+{
+ _isDotEntry = false;
+ _pendingReadJobs = 0;
+ _dotEntry = 0;
+ _firstChild = 0;
+ _totalSize = _size;
+ _totalBlocks = _blocks;
+ _totalItems = 0;
+ _totalSubDirs = 0;
+ _totalFiles = 0;
+ _latestMtime = _mtime;
+ _isMountPoint = false;
+ _summaryDirty = false;
+ _beingDestroyed = false;
+ _readState = KDirQueued;
+}
+
+
+KDirInfo::~KDirInfo()
+{
+ _beingDestroyed = true;
+ KFileInfo *child = _firstChild;
+
+
+ // Recursively delete all children.
+
+ while ( child )
+ {
+ KFileInfo * nextChild = child->next();
+ delete child;
+ child = nextChild;
+ }
+
+
+ // Delete the dot entry.
+
+ if ( _dotEntry )
+ {
+ delete _dotEntry;
+ }
+}
+
+
+void
+KDirInfo::recalc()
+{
+ // kdDebug() << k_funcinfo << this << endl;
+
+ _totalSize = _size;
+ _totalBlocks = _blocks;
+ _totalItems = 0;
+ _totalSubDirs = 0;
+ _totalFiles = 0;
+ _latestMtime = _mtime;
+
+ KFileInfoIterator it( this, KDotEntryAsSubDir );
+
+ while ( *it )
+ {
+ _totalSize += (*it)->totalSize();
+ _totalBlocks += (*it)->totalBlocks();
+ _totalItems += (*it)->totalItems() + 1;
+ _totalSubDirs += (*it)->totalSubDirs();
+ _totalFiles += (*it)->totalFiles();
+
+ if ( (*it)->isDir() )
+ _totalSubDirs++;
+
+ if ( (*it)->isFile() )
+ _totalFiles++;
+
+ time_t childLatestMtime = (*it)->latestMtime();
+
+ if ( childLatestMtime > _latestMtime )
+ _latestMtime = childLatestMtime;
+
+ ++it;
+ }
+
+ _summaryDirty = false;
+}
+
+
+void
+KDirInfo::setMountPoint( bool isMountPoint )
+{
+ _isMountPoint = isMountPoint;
+}
+
+
+KFileSize
+KDirInfo::totalSize()
+{
+ if ( _summaryDirty )
+ recalc();
+
+ return _totalSize;
+}
+
+
+KFileSize
+KDirInfo::totalBlocks()
+{
+ if ( _summaryDirty )
+ recalc();
+
+ return _totalBlocks;
+}
+
+
+int
+KDirInfo::totalItems()
+{
+ if ( _summaryDirty )
+ recalc();
+
+ return _totalItems;
+}
+
+
+int
+KDirInfo::totalSubDirs()
+{
+ if ( _summaryDirty )
+ recalc();
+
+ return _totalSubDirs;
+}
+
+
+int
+KDirInfo::totalFiles()
+{
+ if ( _summaryDirty )
+ recalc();
+
+ return _totalFiles;
+}
+
+
+time_t
+KDirInfo::latestMtime()
+{
+ if ( _summaryDirty )
+ recalc();
+
+ return _latestMtime;
+}
+
+
+bool
+KDirInfo::isFinished()
+{
+ return ! isBusy();
+}
+
+
+void KDirInfo::setReadState( KDirReadState newReadState )
+{
+ // "aborted" has higher priority than "finished"
+
+ if ( _readState == KDirAborted && newReadState == KDirFinished )
+ return;
+
+ _readState = newReadState;
+}
+
+
+bool
+KDirInfo::isBusy()
+{
+ if ( _pendingReadJobs > 0 && _readState != KDirAborted )
+ return true;
+
+ if ( readState() == KDirReading ||
+ readState() == KDirQueued )
+ return true;
+
+ return false;
+}
+
+
+void
+KDirInfo::insertChild( KFileInfo *newChild )
+{
+ CHECK_PTR( newChild );
+
+ if ( newChild->isDir() || _dotEntry == 0 || _isDotEntry )
+ {
+ /**
+ * Only directories are stored directly in pure directory nodes -
+ * unless something went terribly wrong, e.g. there is no dot entry to use.
+ * If this is a dot entry, store everything it gets directly within it.
+ *
+ * In any of those cases, insert the new child in the children list.
+ *
+ * We don't bother with this list's order - it's explicitly declared to
+ * be unordered, so be warned! We simply insert this new child at the
+ * list head since this operation can be performed in constant time
+ * without the need for any additional lastChild etc. pointers or -
+ * even worse - seeking the correct place for insertion first. This is
+ * none of our business; the corresponding "view" object for this tree
+ * will take care of such niceties.
+ **/
+ newChild->setNext( _firstChild );
+ _firstChild = newChild;
+ newChild->setParent( this ); // make sure the parent pointer is correct
+
+ childAdded( newChild ); // update summaries
+ }
+ else
+ {
+ /*
+ * If the child is not a directory, don't store it directly here - use
+ * this entry's dot entry instead.
+ */
+ _dotEntry->insertChild( newChild );
+ }
+}
+
+
+void
+KDirInfo::childAdded( KFileInfo *newChild )
+{
+ if ( ! _summaryDirty )
+ {
+ _totalSize += newChild->size();
+ _totalBlocks += newChild->blocks();
+ _totalItems++;
+
+ if ( newChild->isDir() )
+ _totalSubDirs++;
+
+ if ( newChild->isFile() )
+ _totalFiles++;
+
+ if ( newChild->mtime() > _latestMtime )
+ _latestMtime = newChild->mtime();
+ }
+ else
+ {
+ // NOP
+
+ /*
+ * Don't bother updating the summary fields if the summary is dirty
+ * (i.e. outdated) anyway: As soon as anybody wants to know some exact
+ * value a complete recalculation of the entire subtree will be
+ * triggered. On the other hand, if nobody wants to know (which is very
+ * likely) we can save this effort.
+ */
+ }
+
+ if ( _parent )
+ _parent->childAdded( newChild );
+}
+
+
+void
+KDirInfo::deletingChild( KFileInfo *deletedChild )
+{
+ /**
+ * When children are deleted, things go downhill: Marking the summary
+ * fields as dirty (i.e. outdated) is the only thing that can be done here.
+ *
+ * The accumulated sizes could be updated (by subtracting this deleted
+ * child's values from them), but the latest mtime definitely has to be
+ * recalculated: The child now being deleted might just be the one with the
+ * latest mtime, and figuring out the second-latest cannot easily be
+ * done. So we merely mark the summary as dirty and wait until a recalc()
+ * will be triggered from outside - which might as well never happen when
+ * nobody wants to know some summary field anyway.
+ **/
+
+ _summaryDirty = true;
+
+ if ( _parent )
+ _parent->deletingChild( deletedChild );
+
+ if ( ! _beingDestroyed && deletedChild->parent() == this )
+ {
+ /**
+ * Unlink the child from the children's list - but only if this doesn't
+ * happen recursively in the destructor of this object: No use
+ * bothering about the validity of the children's list if this will all
+ * be history anyway in a moment.
+ **/
+
+ unlinkChild( deletedChild );
+ }
+}
+
+
+void
+KDirInfo::unlinkChild( KFileInfo *deletedChild )
+{
+ if ( deletedChild->parent() != this )
+ {
+ kdError() << deletedChild << " is not a child of " << this
+ << " - cannot unlink from children list!" << endl;
+ return;
+ }
+
+ if ( deletedChild == _firstChild )
+ {
+ // kdDebug() << "Unlinking first child " << deletedChild << endl;
+ _firstChild = deletedChild->next();
+ return;
+ }
+
+ KFileInfo *child = firstChild();
+
+ while ( child )
+ {
+ if ( child->next() == deletedChild )
+ {
+ // kdDebug() << "Unlinking " << deletedChild << endl;
+ child->setNext( deletedChild->next() );
+
+ return;
+ }
+
+ child = child->next();
+ }
+
+ kdError() << "Couldn't unlink " << deletedChild << " from "
+ << this << " children list" << endl;
+}
+
+
+void
+KDirInfo::readJobAdded()
+{
+ _pendingReadJobs++;
+
+ if ( _parent )
+ _parent->readJobAdded();
+}
+
+
+void
+KDirInfo::readJobFinished()
+{
+ _pendingReadJobs--;
+
+ if ( _parent )
+ _parent->readJobFinished();
+}
+
+
+void
+KDirInfo::readJobAborted()
+{
+ _readState = KDirAborted;
+
+ if ( _parent )
+ _parent->readJobAborted();
+}
+
+
+void
+KDirInfo::finalizeLocal()
+{
+ cleanupDotEntries();
+}
+
+
+KDirReadState
+KDirInfo::readState() const
+{
+ if ( _isDotEntry && _parent )
+ return _parent->readState();
+ else
+ return _readState;
+}
+
+
+void
+KDirInfo::cleanupDotEntries()
+{
+ if ( ! _dotEntry || _isDotEntry )
+ return;
+
+ // Reparent dot entry children if there are no subdirectories on this level
+
+ if ( ! _firstChild )
+ {
+ // kdDebug() << "Removing solo dot entry " << this << " " << endl;
+
+ KFileInfo *child = _dotEntry->firstChild();
+ _firstChild = child; // Move the entire children chain here.
+ _dotEntry->setFirstChild( 0 ); // _dotEntry will be deleted below.
+
+ while ( child )
+ {
+ child->setParent( this );
+ child = child->next();
+ }
+ }
+
+
+ // Delete dot entries without any children
+
+ if ( ! _dotEntry->firstChild() )
+ {
+ // kdDebug() << "Removing empty dot entry " << this << endl;
+ delete _dotEntry;
+ _dotEntry = 0;
+ }
+}
+
+
+
+
+
+
+KDirReadJob::KDirReadJob( KDirTree * tree,
+ KDirInfo * dir )
+ : _tree( tree )
+ , _dir( dir )
+{
+ _dir->readJobAdded();
+}
+
+
+KDirReadJob::~KDirReadJob()
+{
+ _dir->readJobFinished();
+}
+
+
+void
+KDirReadJob::childAdded( KFileInfo *newChild )
+{
+ _tree->childAddedNotify( newChild );
+}
+
+
+void
+KDirReadJob::deletingChild( KFileInfo *deletedChild )
+{
+ _tree->deletingChildNotify( deletedChild );
+}
+
+
+
+
+
+
+KLocalDirReadJob::KLocalDirReadJob( KDirTree * tree,
+ KDirInfo * dir )
+ : KDirReadJob( tree, dir )
+ , _diskDir( 0 )
+{
+}
+
+
+KLocalDirReadJob::~KLocalDirReadJob()
+{
+}
+
+
+void
+KLocalDirReadJob::startReading()
+{
+ struct dirent * entry;
+ struct stat statInfo;
+ QString dirName = _dir->url();
+
+ if ( ( _diskDir = opendir( dirName ) ) )
+ {
+ _tree->sendProgressInfo( dirName );
+ _dir->setReadState( KDirReading );
+
+ while ( ( entry = readdir( _diskDir ) ) )
+ {
+ QString entryName = entry->d_name;
+
+ if ( entryName != "." &&
+ entryName != ".." )
+ {
+ QString fullName = dirName + "/" + entryName;
+
+ if ( lstat( fullName, &statInfo ) == 0 ) // lstat() OK
+ {
+ if ( S_ISDIR( statInfo.st_mode ) ) // directory child?
+ {
+ KDirInfo *subDir = new KDirInfo( entryName, &statInfo, _tree, _dir );
+ _dir->insertChild( subDir );
+ childAdded( subDir );
+
+ if ( subDir->dotEntry() )
+ childAdded( subDir->dotEntry() );
+
+ if ( _dir->device() == subDir->device() ) // normal case
+ {
+ _tree->addJob( new KLocalDirReadJob( _tree, subDir ) );
+ }
+ else // The subdirectory we just found is a mount point.
+ {
+ // kdDebug() << "Found mount point " << subDir << endl;
+ subDir->setMountPoint();
+
+ if ( _tree->crossFileSystems() )
+ {
+ _tree->addJob( new KLocalDirReadJob( _tree, subDir ) );
+ }
+ else
+ {
+ subDir->setReadState( KDirOnRequestOnly );
+ _tree->sendFinalizeLocal( subDir );
+ subDir->finalizeLocal();
+ }
+ }
+ }
+ else // non-directory child
+ {
+ KFileInfo *child = new KFileInfo( entryName, &statInfo, _tree, _dir );
+ _dir->insertChild( child );
+ childAdded( child );
+ }
+ }
+ else // lstat() error
+ {
+ // kdWarning() << "lstat(" << fullName << ") failed: " << strerror( errno ) << endl;
+
+ /*
+ * Not much we can do when lstat() didn't work; let's at
+ * least create an (almost empty) entry as a placeholder.
+ */
+ KDirInfo *child = new KDirInfo( _tree, _dir, entry->d_name );
+ child->setReadState( KDirError );
+ _dir->insertChild( child );
+ childAdded( child );
+ }
+ }
+ }
+
+ closedir( _diskDir );
+ // kdDebug() << "Finished reading " << _dir << endl;
+ _dir->setReadState( KDirFinished );
+ _tree->sendFinalizeLocal( _dir );
+ _dir->finalizeLocal();
+ }
+ else
+ {
+ _dir->setReadState( KDirError );
+ _tree->sendFinalizeLocal( _dir );
+ _dir->finalizeLocal();
+ // kdWarning() << k_funcinfo << "opendir(" << dirName << ") failed" << endl;
+ // opendir() doesn't set 'errno' according to POSIX :-(
+ }
+
+ _tree->jobFinishedNotify( this );
+ // Don't add anything after _tree->jobFinishedNotify()
+ // since this deletes this job!
+}
+
+
+
+KFileInfo *
+KLocalDirReadJob::stat( const KURL & url,
+ KDirTree * tree,
+ KDirInfo * parent )
+{
+ struct stat statInfo;
+
+ if ( lstat( url.path(), &statInfo ) == 0 ) // lstat() OK
+ {
+ QString name = parent ? url.filename() : url.path();
+
+ if ( S_ISDIR( statInfo.st_mode ) ) // directory?
+ {
+ KDirInfo * dir = new KDirInfo( name, &statInfo, tree, parent );
+
+ if ( dir && parent && dir->device() != parent->device() )
+ dir->setMountPoint();
+
+ return dir;
+ }
+ else // no directory
+ return new KFileInfo( name, &statInfo, tree, parent );
+ }
+ else // lstat() failed
+ return 0;
+}
+
+
+
+
+
+
+KAnyDirReadJob::KAnyDirReadJob( KDirTree * tree,
+ KDirInfo * dir )
+ : QObject()
+ , KDirReadJob( tree, dir )
+{
+ _job = 0;
+}
+
+
+KAnyDirReadJob::~KAnyDirReadJob()
+{
+#if 0
+ if ( _job )
+ _job->kill( true ); // quietly
+#endif
+}
+
+
+void
+KAnyDirReadJob::startReading()
+{
+ KURL url( _dir->url() );
+
+ if ( ! url.isValid() )
+ {
+ kdWarning() << k_funcinfo << "URL malformed: " << _dir->url() << endl;
+ }
+
+ _job = KIO::listDir( url,
+ false ); // showProgressInfo
+
+ connect( _job, SIGNAL( entries( KIO::Job *, const KIO::UDSEntryList& ) ),
+ this, SLOT ( entries( KIO::Job *, const KIO::UDSEntryList& ) ) );
+
+ connect( _job, SIGNAL( result ( KIO::Job * ) ),
+ this, SLOT ( finished( KIO::Job * ) ) );
+
+ connect( _job, SIGNAL( canceled( KIO::Job * ) ),
+ this, SLOT ( finished( KIO::Job * ) ) );
+}
+
+
+void
+KAnyDirReadJob::entries ( KIO::Job * job,
+ const KIO::UDSEntryList & entryList )
+{
+ NOT_USED( job );
+ KURL url( _dir->url() ); // Cache this - it's expensive!
+
+ if ( ! url.isValid() )
+ {
+ kdWarning() << k_funcinfo << "URL malformed: " << _dir->url() << endl;
+ }
+
+ KIO::UDSEntryListConstIterator it = entryList.begin();
+
+ while ( it != entryList.end() )
+ {
+ KFileItem entry( *it,
+ url,
+ true, // determineMimeTypeOnDemand
+ true ); // URL is parent directory
+
+ if ( entry.name() != "." &&
+ entry.name() != ".." )
+ {
+ // kdDebug() << "Found " << entry.url().url() << endl;
+
+ if ( entry.isDir() && // Directory child
+ ! entry.isLink() ) // and not a symlink?
+ {
+ KDirInfo *subDir = new KDirInfo( &entry, _tree, _dir );
+ _dir->insertChild( subDir );
+ childAdded( subDir );
+
+ if ( subDir->dotEntry() )
+ childAdded( subDir->dotEntry() );
+
+ _tree->addJob( new KAnyDirReadJob( _tree, subDir ) );
+ }
+ else // non-directory child
+ {
+ KFileInfo *child = new KFileInfo( &entry, _tree, _dir );
+ _dir->insertChild( child );
+ childAdded( child );
+ }
+ }
+
+ ++it;
+ }
+}
+
+
+void
+KAnyDirReadJob::finished( KIO::Job * job )
+{
+ if ( job->error() )
+ _dir->setReadState( KDirError );
+ else
+ _dir->setReadState( KDirFinished );
+
+ _tree->sendFinalizeLocal( _dir );
+ _dir->finalizeLocal();
+ _job = 0; // The job deletes itself after this signal!
+
+ _tree->jobFinishedNotify( this );
+ // Don't add anything after _tree->jobFinishedNotify()
+ // since this deletes this job!
+}
+
+
+
+KFileInfo *
+KAnyDirReadJob::stat( const KURL & url,
+ KDirTree * tree,
+ KDirInfo * parent )
+{
+ KIO::UDSEntry uds_entry;
+
+ if ( KIO::NetAccess::stat( url, uds_entry, qApp->mainWidget() ) ) // remote stat() OK?
+ {
+ KFileItem entry( uds_entry, url,
+ true, // determine MIME type on demand
+ false ); // URL specifies parent directory
+
+ return entry.isDir() ? new KDirInfo ( &entry, tree, parent ) : new KFileInfo( &entry, tree, parent );
+ }
+ else // remote stat() failed
+ return 0;
+
+
+#if HAVE_STUPID_COMPILER
+ /**
+ * This is stupid, but GCC 2.95.3 claims that "control reaches end of
+ * non-void function" without this - so let him have this stupid "return".
+ *
+ * Sigh.
+ **/
+ return 0;
+#endif
+}
+
+
+QString
+KAnyDirReadJob::owner( KURL url )
+{
+ KIO::UDSEntry uds_entry;
+
+ if ( KIO::NetAccess::stat( url, uds_entry, qApp->mainWidget() ) ) // remote stat() OK?
+ {
+ KFileItem entry( uds_entry, url,
+ true, // determine MIME type on demand
+ false ); // URL specifies parent directory
+
+ return entry.user();
+ }
+
+ return QString();
+}
+
+
+
+
+
+
+KDirTree::KDirTree()
+ : QObject()
+{
+ _root = 0;
+ _selection = 0;
+ _isFileProtocol = false;
+ _isBusy = false;
+ _readMethod = KDirReadUnknown;
+ _jobQueue.setAutoDelete( true ); // Delete queued jobs automatically when destroyed
+ readConfig();
+}
+
+
+KDirTree::~KDirTree()
+{
+ selectItem( 0 );
+
+ // Jobs still in the job queue are automatically deleted along with the
+ // queue since autoDelete is set.
+ //
+ // However, the queue needs to be cleared first before the entire tree is
+ // deleted, otherwise the dir pointers in each read job becomes invalid too
+ // early.
+
+ _jobQueue.clear();
+
+ if ( _root )
+ delete _root;
+}
+
+
+void
+KDirTree::readConfig()
+{
+ KConfig * config = kapp->config();
+ config->setGroup( "Directory Reading" );
+
+ _crossFileSystems = config->readBoolEntry( "CrossFileSystems", false );
+ _enableLocalDirReader = config->readBoolEntry( "EnableLocalDirReader", true );
+}
+
+
+void
+KDirTree::startReading( const KURL & url )
+{
+ // kdDebug() << k_funcinfo << " " << url.url() << endl;
+
+#if 0
+ kdDebug() << "url: " << url.url() << endl;
+ kdDebug() << "path: " << url.path() << endl;
+ kdDebug() << "filename: " << url.filename() << endl;
+ kdDebug() << "protocol: " << url.protocol() << endl;
+ kdDebug() << "isValid: " << url.isValid() << endl;
+ kdDebug() << "isMalformed: " << url.isMalformed() << endl;
+ kdDebug() << "isLocalFile: " << url.isLocalFile() << endl;
+#endif
+
+ _isBusy = true;
+ emit startingReading();
+
+ if ( _root )
+ {
+ // Clean up leftover stuff
+
+ selectItem( 0 );
+ emit deletingChild( _root );
+
+ // kdDebug() << "Deleting root prior to reading" << endl;
+ delete _root;
+ _root = 0;
+ emit childDeleted();
+ }
+
+ readConfig();
+ _isFileProtocol = url.isLocalFile();
+
+ if ( _isFileProtocol && _enableLocalDirReader )
+ {
+ // kdDebug() << "Using local directory reader for " << url.url() << endl;
+ _readMethod = KDirReadLocal;
+ _root = KLocalDirReadJob::stat( url, this );
+ }
+ else
+ {
+ // kdDebug() << "Using KIO methods for " << url.url() << endl;
+ KURL cleanUrl( url );
+ cleanUrl.cleanPath(); // Resolve relative paths, get rid of multiple '/'
+ _readMethod = KDirReadKIO;
+ _root = KAnyDirReadJob::stat( cleanUrl, this );
+ }
+
+ if ( _root )
+ {
+ childAddedNotify( _root );
+
+ if ( _root->isDir() )
+ {
+ KDirInfo *dir = (KDirInfo *) _root;
+
+ if ( _readMethod == KDirReadLocal )
+ addJob( new KLocalDirReadJob( this, dir ) );
+ else
+ addJob( new KAnyDirReadJob( this, dir ) );
+ }
+ else
+ {
+ _isBusy = false;
+ emit finished();
+ }
+ }
+ else // stat() failed
+ {
+ // kdWarning() << "stat(" << url.url() << ") failed" << endl;
+ _isBusy = false;
+ emit finished();
+ emit finalizeLocal( 0 );
+ }
+
+ if ( ! _jobQueue.isEmpty() )
+ QTimer::singleShot( 0, this, SLOT( timeSlicedRead() ) );
+}
+
+
+void
+KDirTree::refresh( KFileInfo *subtree )
+{
+ if ( ! _root )
+ return;
+
+ if ( ! subtree || ! subtree->parent() ) // Refresh all (from root)
+ {
+ startReading( fixedUrl( _root->url() ) );
+ }
+ else // Refresh subtree
+ {
+ // Save some values from the old subtree.
+
+ KURL url = subtree->url();
+ KDirInfo * parent = subtree->parent();
+
+
+ // Select nothing if the current selection is to be deleted
+
+ if ( _selection && _selection->isInSubtree( subtree ) )
+ selectItem( 0 );
+
+ // Get rid of the old subtree.
+
+ emit deletingChild( subtree );
+
+ // kdDebug() << "Deleting subtree " << subtree << endl;
+
+ /**
+ * This may sound stupid, but the parent must be told to unlink its
+ * child from the children list. The child cannot simply do this by
+ * itself in its destructor since at this point important parts of the
+ * object may already be destroyed, e.g., the virtual table -
+ * i.e. virtual methods won't work any more.
+ *
+ * I just found that out the hard way by several hours of debugging. ;-}
+ **/
+ parent->deletingChild( subtree );
+ delete subtree;
+ emit childDeleted();
+
+
+ // Create new subtree root.
+
+ subtree = ( _readMethod == KDirReadLocal ) ?
+ KLocalDirReadJob::stat( url, this, parent ) : KAnyDirReadJob::stat( url, this, parent );
+
+ // kdDebug() << "New subtree: " << subtree << endl;
+
+ if ( subtree )
+ {
+ // Insert new subtree root into the tree hierarchy.
+
+ parent->insertChild( subtree );
+ childAddedNotify( subtree );
+
+ if ( subtree->isDir() )
+ {
+ // Prepare reading this subtree's contents.
+
+ KDirInfo *dir = (KDirInfo *) subtree;
+
+ if ( _readMethod == KDirReadLocal )
+ addJob( new KLocalDirReadJob( this, dir ) );
+ else
+ addJob( new KAnyDirReadJob( this, dir ) );
+ }
+ else
+ {
+ _isBusy = false;
+ emit finished();
+ }
+
+
+ // Trigger reading as soon as the event loop continues.
+
+ if ( ! _jobQueue.isEmpty() )
+ QTimer::singleShot( 0, this, SLOT( timeSlicedRead() ) );
+ }
+ }
+}
+
+
+
+void
+KDirTree::timeSlicedRead()
+{
+ if ( ! _jobQueue.isEmpty() )
+ _jobQueue.head()->startReading();
+}
+
+
+
+void
+KDirTree::abortReading()
+{
+ if ( _jobQueue.isEmpty() )
+ return;
+
+ while ( ! _jobQueue.isEmpty() )
+ {
+ _jobQueue.head()->dir()->readJobAborted();
+ _jobQueue.dequeue();
+ }
+
+ _isBusy = false;
+ emit aborted();
+}
+
+
+
+void
+KDirTree::jobFinishedNotify( KDirReadJob *job )
+{
+ // Get rid of the old (finished) job.
+
+ _jobQueue.dequeue();
+ delete job;
+
+
+ // Look for a new job.
+
+ if ( _jobQueue.isEmpty() ) // No new job available - we're done.
+ {
+ _isBusy = false;
+ emit finished();
+ }
+ else // There is a new job
+ {
+ // Set up zero-duration timer for the new job.
+
+ QTimer::singleShot( 0, this, SLOT( timeSlicedRead() ) );
+ }
+}
+
+
+void
+KDirTree::childAddedNotify( KFileInfo *newChild )
+{
+ emit childAdded( newChild );
+
+ if ( newChild->dotEntry() )
+ emit childAdded( newChild->dotEntry() );
+}
+
+
+void
+KDirTree::deletingChildNotify( KFileInfo *deletedChild )
+{
+ emit deletingChild( deletedChild );
+
+ // Only now check for selection and root: Give connected objects
+ // (i.e. views) a chance to change either while handling the signal.
+
+ if ( _selection && _selection->isInSubtree( deletedChild ) )
+ selectItem( 0 );
+
+ if ( deletedChild == _root )
+ _root = 0;
+}
+
+
+void
+KDirTree::childDeletedNotify()
+{
+ emit childDeleted();
+}
+
+
+void
+KDirTree::deleteSubtree( KFileInfo *subtree )
+{
+ // kdDebug() << "Deleting subtree " << subtree << endl;
+ KDirInfo *parent = subtree->parent();
+
+ if ( parent )
+ {
+ // Give the parent of the child to be deleted a chance to unlink the
+ // child from its children list and take care of internal summary
+ // fields
+ parent->deletingChild( subtree );
+ }
+
+ // Send notification to anybody interested (e.g., to attached views)
+ deletingChildNotify( subtree );
+
+ if ( parent )
+ {
+ if ( parent->isDotEntry() && ! parent->hasChildren() )
+ // This was the last child of a dot entry
+ {
+ // Get rid of that now empty and useless dot entry
+
+ if ( parent->parent() )
+ {
+ if ( parent->parent()->isFinished() )
+ {
+ // kdDebug() << "Removing empty dot entry " << parent << endl;
+
+ deletingChildNotify( parent );
+ parent->parent()->setDotEntry( 0 );
+
+ delete parent;
+ }
+ }
+ else // no parent - this should never happen (?)
+ {
+ kdError() << "Internal error: Killing dot entry without parent " << parent << endl;
+
+ // Better leave that dot entry alone - we shouldn't have come
+ // here in the first place. Who knows what will happen if this
+ // thing is deleted now?!
+ //
+ // Intentionally NOT calling:
+ // delete parent;
+ }
+ }
+ }
+
+ delete subtree;
+
+ emit childDeleted();
+}
+
+
+void
+KDirTree::addJob( KDirReadJob * job )
+{
+ CHECK_PTR( job );
+ _jobQueue.enqueue( job );
+}
+
+
+void
+KDirTree::sendProgressInfo( const QString &infoLine )
+{
+ emit progressInfo( infoLine );
+}
+
+
+void
+KDirTree::sendFinalizeLocal( KDirInfo *dir )
+{
+ emit finalizeLocal( dir );
+}
+
+
+void
+KDirTree::selectItem( KFileInfo *newSelection )
+{
+ if ( newSelection == _selection )
+ return;
+
+#if 0
+ if ( newSelection )
+ kdDebug() << k_funcinfo << " selecting " << newSelection << endl;
+ else
+ kdDebug() << k_funcinfo << " selecting nothing" << endl;
+#endif
+
+ _selection = newSelection;
+ emit selectionChanged( _selection );
+}
+
+
+
+
+
+
+KURL
+KDirStat::fixedUrl( const QString & dirtyUrl )
+{
+ KURL url = dirtyUrl;
+
+ if ( ! url.isValid() ) // Maybe it's just a path spec?
+ {
+ url = KURL(); // Start over with an empty, but valid URL
+ url.setPath( dirtyUrl ); // and use just the path part.
+ }
+ else
+ {
+ url.cleanPath(); // Resolve relative paths, get rid of multiple slashes.
+ }
+
+
+ // Strip off the rightmost slash - some kioslaves (e.g. 'tar') can't handle that.
+
+ QString path = url.path();
+
+ if ( path.length() > 1 && path.right(1) == "/" )
+ {
+ path = path.left( path.length()-1 );
+ url.setPath( path );
+ }
+
+ if ( url.isLocalFile() )
+ {
+ // Make a relative path an absolute path
+
+ KDirSaver dir( url.path() );
+ url.setPath( dir.currentDirPath() );
+ }
+
+ return url;
+}
+
+
+
+
+
+
+QString
+KDirStat::formatSize( KFileSize lSize )
+{
+ QString sizeString;
+ double size;
+ QString unit;
+
+ if ( lSize < 1024 )
+ {
+ sizeString.setNum( (long) lSize );
+
+ unit = i18n( "Bytes" );
+ }
+ else
+ {
+ size = lSize / 1024.0; // kB
+
+ if ( size < 1024.0 )
+ {
+ sizeString.sprintf( "%.1f", size );
+ unit = i18n( "kB" );
+ }
+ else
+ {
+ size /= 1024.0; // MB
+
+ if ( size < 1024.0 )
+ {
+ sizeString.sprintf( "%.1f", size );
+ unit = i18n ( "MB" );
+ }
+ else
+ {
+ size /= 1024.0; // GB - we won't go any further...
+
+ sizeString.sprintf( "%.2f", size );
+ unit = i18n ( "GB" );
+ }
+ }
+ }
+
+ if ( ! unit.isEmpty() )
+ {
+ sizeString += " " + unit;
+ }
+
+ return sizeString;
+}
+
+
+
+// EOF
diff --git a/kdirstat/kdirtree.h b/kdirstat/kdirtree.h
new file mode 100644
index 0000000..d4155a9
--- /dev/null
+++ b/kdirstat/kdirtree.h
@@ -0,0 +1,1437 @@
+/*
+ * File name: kdirtree.h
+ * Summary: Support classes for KDirStat
+ * License: LGPL - See file COPYING.LIB for details.
+ * Author: Stefan Hundhammer <sh@suse.de>
+ *
+ * Updated: 2005-01-07
+ */
+
+
+#ifndef KDirTree_h
+#define KDirTree_h
+
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <sys/types.h>
+#include <limits.h>
+#include <dirent.h>
+#include <qptrqueue.h>
+#include <kdebug.h>
+#include <kfileitem.h>
+#include <kio/jobclasses.h>
+
+#ifndef NOT_USED
+# define NOT_USED(PARAM) ( (void) (PARAM) )
+#endif
+
+// Open a new name space since KDE's name space is pretty much cluttered
+// already - all names that would even remotely match are already used up,
+// yet the resprective classes don't quite fit the purposes required here.
+
+namespace KDirStat
+{
+ // With today's hard disks, the 2 GB we could sum up with 'long' (or 4 GB
+ // with 'unsigned long') are definitely not enough. So we have to go for
+ // something larger:
+ typedef long long KFileSize;
+
+ // Taken from Linux <limits.h> (the Alpha definition - 64 Bit long!).
+ // This is how much bytes this program can handle.
+#define KFileSizeMax 9223372036854775807LL
+
+ // Forward declarations
+ class KDirInfo;
+ class KDirTree;
+ class KDirReadJob;
+ class KDirTreeView;
+
+
+ /**
+ * Status of a directory read job.
+ **/
+ typedef enum
+ {
+ KDirQueued, // Waiting in the directory read queue
+ KDirReading, // Reading in progress
+ KDirFinished, // Reading finished and OK
+ KDirOnRequestOnly, // Will be read upon explicit request only (mount points)
+ KDirAborted, // Reading aborted upon user request
+ KDirError // Error while reading
+ } KDirReadState;
+
+
+ /**
+ * Directory read methods.
+ **/
+ typedef enum
+ {
+ KDirReadUnknown, // Unknown (yet)
+ KDirReadLocal, // Use opendir() and lstat()
+ KDirReadKIO // Use KDE 2.x's KIO network transparent methods
+ } KDirReadMethod;
+
+
+
+ /**
+ * The most basic building block of a @ref KDirTree:
+ *
+ * Information about one single directory entry. This is the type of info
+ * typically obtained by stat() / lstat() or similar calls. Most of this
+ * can also be obtained by @ref KIO::KDirListJob, but not all: The device
+ * this file resides on is something none of KIO's many classes will tell
+ * (since of course this only makes sense for local files) - yet this had
+ * been _the_ single most requested feature of KDirStat <1.0: Stay on one
+ * filesystem. To facilitate this, information about the device is
+ * required, thus we'll do lstat() sys calls ourselves for local
+ * files. This is what the classes in this file are all about.
+ *
+ * This class is tuned for size rather than speed: A typical Linux system
+ * easily has 150,000+ file system objects, and at least one entry of this
+ * sort is required for each of them.
+ *
+ * This class provides stubs for children management, yet those stubs all
+ * are default implementations that don't really deal with children.
+ * Derived classes need to take care of that.
+ *
+ * @short Basic file information (like obtained by the lstat() sys call)
+ **/
+ class KFileInfo
+ {
+ public:
+ /**
+ * Default constructor.
+ **/
+ KFileInfo( KDirTree * tree,
+ KDirInfo * parent = 0,
+ const char * name = 0 );
+
+ /**
+ * Constructor from a stat buffer (i.e. based on an lstat() call).
+ **/
+ KFileInfo( const QString & filenameWithoutPath,
+ struct stat * statInfo,
+ KDirTree * tree,
+ KDirInfo * parent = 0 );
+
+ /**
+ * Constructor from a KFileItem, i.e. from a @ref KIO::StatJob
+ **/
+ KFileInfo( const KFileItem * fileItem,
+ KDirTree * tree,
+ KDirInfo * parent = 0 );
+
+ /**
+ * Destructor.
+ *
+ * Don't forget to call @ref KFileInfo::unlinkChild() when deleting
+ * objects of this class!
+ **/
+ virtual ~KFileInfo();
+
+ /**
+ * Returns whether or not this is a local file (protocol "file:").
+ * It might as well be a remote file ("ftp:", "smb:" etc.).
+ **/
+ bool isLocalFile() const { return _isLocalFile; }
+
+ /**
+ * Returns the file or directory name without path, i.e. only the last
+ * path name component (i.e. "printcap" rather than "/etc/printcap").
+ *
+ * If a directory scan doesn't begin at the root directory and this is
+ * the top entry of this directory scan it will also contain the base
+ * path and maybe the protocol (for remote files),
+ * i.e. "/usr/share/man" rather than just "man" if a scan was requested
+ * for "/usr/share/man". Notice, however, that the entry for
+ * "/usr/share/man/man1" will only return "man1" in this example.
+ **/
+ QString name() const { return _name; }
+
+ /**
+ * Returns the full URL of this object with full path and protocol
+ * (unless the protocol is "file:").
+ *
+ * This is a (somewhat) expensive operation since it will recurse up
+ * to the top of the tree.
+ **/
+ QString url() const;
+
+ /**
+ * Very much like @ref KFileInfo::url(), but with "/<Files>" appended
+ * if this is a dot entry. Useful for debugging.
+ * Notice: You can simply use the @ref kdbgstream operator<< to
+ * output exactly this:
+ *
+ * kdDebug() << "Found fileInfo " << info << endl;
+ **/
+ QString debugUrl() const;
+
+ /**
+ * Returns part no. "level" of this object's URL, i.e. traverses up the
+ * tree until this tree level is reached and returns this predecessor's
+ * @ref name() . This is useful for tree searches in symmetrical trees
+ * to find an item's counterpart in the other tree.
+ **/
+ QString urlPart( int level ) const;
+
+ /**
+ * Returns the major and minor device numbers of the device this file
+ * resides on or 0 if this is a remote file.
+ **/
+ dev_t device() const { return _device; }
+
+ /**
+ * The file permissions and object type as returned by lstat().
+ * You might want to use the repective convenience methods instead:
+ * @ref isDir(), @ref isFile(), ...
+ **/
+ mode_t mode() const { return _mode; }
+
+ /**
+ * The number of hard links to this file. Relevant for size summaries
+ * to avoid counting one file several times.
+ **/
+ nlink_t links() const { return _links; }
+
+ /**
+ * The file size in bytes. This does not take unused space in the last
+ * disk block (cluster) into account, yet it is the only size all kinds
+ * of info functions can obtain. This is also what most file system
+ * utilities (like "ls -l") display.
+ **/
+ KFileSize byteSize() const { return _size; }
+
+ /**
+ * The number of bytes actually allocated on the file system. Usually
+ * this will be more than @ref byteSize() since the last few bytes of a
+ * file usually consume an additional cluster on the file system.
+ *
+ * In the case of sparse files, however, this might as well be
+ * considerably less than @ref byteSize() - this means that this file
+ * has "holes", i.e. large portions filled with zeros. This is typical
+ * for large core dumps for example. The only way to create such a file
+ * is to lseek() far ahead of the previous file size and then writing
+ * data. Most file system utilities will however disregard the fact
+ * that files are sparse files and simply allocate the holes as well,
+ * thus greatly increasing the disk space consumption of such a
+ * file. Only some few file system utilities like "cp", "rsync", "tar"
+ * have options to handle this more graciously - but usually only when
+ * specifically requested. See the respective man pages.
+ **/
+ KFileSize allocatedSize() const;
+
+ /**
+ * The file size, taking into account multiple links for plain files or
+ * the true allocated size for sparse files. For plain files with
+ * multiple links this will be size/no_links, for sparse files it is
+ * the number of bytes actually allocated.
+ **/
+ KFileSize size() const;
+
+ /**
+ * The file size in 512 byte blocks.
+ **/
+ KFileSize blocks() const { return _blocks; }
+
+ /**
+ * The size of one single block that @ref blocks() returns.
+ * Notice: This is _not_ the blocksize that lstat() returns!
+ **/
+ KFileSize blockSize() const { return 512L; }
+
+ /**
+ * The modification time of the file (not the inode).
+ **/
+ time_t mtime() const { return _mtime; }
+
+ /**
+ * Returns the total size in bytes of this subtree.
+ * Derived classes that have children should overwrite this.
+ **/
+ virtual KFileSize totalSize() { return size(); }
+
+ /**
+ * Returns the total size in blocks of this subtree.
+ * Derived classes that have children should overwrite this.
+ **/
+ virtual KFileSize totalBlocks() { return _blocks; }
+
+ /**
+ * Returns the total number of children in this subtree, excluding this item.
+ * Derived classes that have children should overwrite this.
+ **/
+ virtual int totalItems() { return 0; }
+
+ /**
+ * Returns the total number of subdirectories in this subtree,
+ * excluding this item. Dot entries and "." or ".." are not counted.
+ * Derived classes that have children should overwrite this.
+ **/
+ virtual int totalSubDirs() { return 0; }
+
+ /**
+ * Returns the total number of plain file children in this subtree,
+ * excluding this item.
+ * Derived classes that have children should overwrite this.
+ **/
+ virtual int totalFiles() { return 0; }
+
+ /**
+ * Returns the latest modification time of this subtree.
+ * Derived classes that have children should overwrite this.
+ **/
+ virtual time_t latestMtime() { return _mtime; }
+
+ /**
+ * Returns whether or not this is a mount point.
+ * Derived classes may want to overwrite this.
+ **/
+ virtual bool isMountPoint() { return false; }
+
+ /**
+ * Sets the mount point state, i.e. whether or not this is a mount
+ * point.
+ *
+ * This default implementation silently ignores the value passed and
+ * does nothing. Derived classes may want to overwrite this.
+ **/
+ virtual void setMountPoint( bool isMountPoint = true )
+ { ((void) isMountPoint); return; }
+
+ /**
+ * Returns true if this subtree is finished reading.
+ *
+ * This default implementation always returns 'true';
+ * derived classes should overwrite this.
+ **/
+ virtual bool isFinished() { return true; }
+
+ /**
+ * Returns true if this subtree is busy, i.e. it is not finished
+ * reading yet.
+ *
+ * This default implementation always returns 'false';
+ * derived classes should overwrite this.
+ **/
+ virtual bool isBusy() { return false; }
+
+ /**
+ * Returns the number of pending read jobs in this subtree. When this
+ * number reaches zero, the entire subtree is done.
+ * Derived classes that have children should overwrite this.
+ **/
+ virtual int pendingReadJobs() { return 0; }
+
+
+ //
+ // Tree management
+ //
+
+ /**
+ * Returns a pointer to the @ref KDirTree this entry belongs to.
+ **/
+ KDirTree * tree() const { return _tree; }
+
+ /**
+ * Returns a pointer to this entry's parent entry or 0 if there is
+ * none.
+ **/
+ KDirInfo * parent() const { return _parent; }
+
+ /**
+ * Set the "parent" pointer.
+ **/
+ void setParent( KDirInfo *newParent ) { _parent = newParent; }
+
+ /**
+ * Returns a pointer to the next entry on the same level
+ * or 0 if there is none.
+ **/
+ KFileInfo * next() const { return _next; }
+
+ /**
+ * Set the "next" pointer.
+ **/
+ void setNext( KFileInfo *newNext ) { _next = newNext; }
+
+ /**
+ * Returns the first child of this item or 0 if there is none.
+ * Use the child's next() method to get the next child.
+ *
+ * This default implementation always returns 0.
+ **/
+ virtual KFileInfo * firstChild() const { return 0; }
+
+ /**
+ * Set this entry's first child.
+ * Use this method only if you know exactly what you are doing.
+ *
+ * This default implementation does nothing.
+ * Derived classes might want to overwrite this.
+ **/
+ virtual void setFirstChild( KFileInfo *newFirstChild )
+ { NOT_USED( newFirstChild ); }
+
+ /**
+ * Returns true if this entry has any children.
+ **/
+ virtual bool hasChildren() const;
+
+ /**
+ * Returns true if this entry is in subtree 'subtree', i.e. if this is
+ * a child or grandchild etc. of 'subtree'.
+ **/
+ bool isInSubtree( const KFileInfo *subtree ) const;
+
+ /**
+ * Locate a child somewhere in this subtree whose URL (i.e. complete
+ * path) matches the URL passed. Returns 0 if there is no such child.
+ *
+ * Notice: This is a very expensive operation since the entire subtree
+ * is searched recursively.
+ *
+ * Derived classes might or might not wish to overwrite this method;
+ * it's only advisable to do so if a derived class comes up with a
+ * different method than brute-force search all children.
+ *
+ * 'findDotEntries' specifies if locating "dot entries" (".../<Files>")
+ * is desired.
+ **/
+ virtual KFileInfo * locate( QString url, bool findDotEntries = false );
+
+ /**
+ * Insert a child into the children list.
+ *
+ * The order of children in this list is absolutely undefined;
+ * don't rely on any implementation-specific order.
+ *
+ * This default implementation does nothing.
+ **/
+ virtual void insertChild( KFileInfo *newChild ) { NOT_USED( newChild ); }
+
+ /**
+ * Return the "Dot Entry" for this node if there is one (or 0
+ * otherwise): This is a pseudo entry that directory nodes use to store
+ * non-directory children separately from directories. This way the end
+ * user can easily tell which summary fields belong to the directory
+ * itself and which are the accumulated values of the entire subtree.
+ *
+ * This default implementation always returns 0.
+ **/
+ virtual KFileInfo *dotEntry() const { return 0; }
+
+ /**
+ * Set a "Dot Entry". This makes sense for directories only.
+ *
+ * This default implementation does nothing.
+ **/
+ virtual void setDotEntry( KFileInfo *newDotEntry ) { NOT_USED( newDotEntry ); }
+
+ /**
+ * Returns true if this is a "Dot Entry".
+ * See @ref dotEntry() for details.
+ *
+ * This default implementation always returns false.
+ **/
+ virtual bool isDotEntry() const { return false; }
+
+ /**
+ * Returns the tree level (depth) of this item.
+ * The topmost level is 0.
+ *
+ * This is a (somewhat) expensive operation since it will recurse up
+ * to the top of the tree.
+ **/
+ int treeLevel() const;
+
+ /**
+ * Notification that a child has been added somewhere in the subtree.
+ *
+ * This default implementation does nothing.
+ **/
+ virtual void childAdded( KFileInfo *newChild ) { NOT_USED( newChild ); }
+
+ /**
+ * Remove a child from the children list.
+ *
+ * IMPORTANT: This MUST be called just prior to deleting an object of
+ * this class. Regrettably, this cannot simply be moved to the
+ * destructor: Important parts of the object might already be destroyed
+ * (e.g., the virtual table - no more virtual methods).
+ *
+ * This default implementation does nothing.
+ * Derived classes that can handle children should overwrite this.
+ **/
+ virtual void unlinkChild( KFileInfo *deletedChild ) { NOT_USED( deletedChild ); }
+
+ /**
+ * Notification that a child is about to be deleted somewhere in the
+ * subtree.
+ **/
+ virtual void deletingChild( KFileInfo *deletedChild ) { NOT_USED( deletedChild ); }
+
+ /**
+ * Get the current state of the directory reading process:
+ *
+ * This default implementation always returns KDirFinished.
+ * Derived classes should overwrite this.
+ **/
+ virtual KDirReadState readState() const { return KDirFinished; }
+
+ /**
+ * Returns true if this is a @ref KDirInfo object.
+ *
+ * Don't confuse this with @ref isDir() which tells whether or not this
+ * is a disk directory! Both should return the same, but you'll never
+ * know - better be safe than sorry!
+ *
+ * This default implementation always returns 'false'. Derived classes
+ * (in particular, those derived from @ref KDirInfo) should overwrite this.
+ **/
+ virtual bool isDirInfo() const { return false; }
+
+ /**
+ * Returns true if this is a sparse file, i.e. if this file has
+ * actually fewer disk blocks allocated than its byte size would call
+ * for.
+ *
+ * This is a cheap operation since it relies on a cached flag that is
+ * calculated in the constructor rather than doing repeated
+ * calculations and comparisons.
+ *
+ * Please not that @ref size() already takes this into account.
+ **/
+ bool isSparseFile() const { return _isSparseFile; }
+
+
+ //
+ // File type / mode convenience methods.
+ // These are simply shortcuts to the respective macros from
+ // <sys/stat.h>.
+ //
+
+ /**
+ * Returns true if this is a directory.
+ **/
+ bool isDir() const { return S_ISDIR( _mode ) ? true : false; }
+
+ /**
+ * Returns true if this is a regular file.
+ **/
+ bool isFile() const { return S_ISREG( _mode ) ? true : false; }
+
+ /**
+ * Returns true if this is a symbolic link.
+ **/
+ bool isSymLink() const { return S_ISLNK( _mode ) ? true : false; }
+
+
+ /**
+ * Returns true if this is a (block or character) device.
+ **/
+ bool isDevice() const { return ( S_ISBLK ( _mode ) ||
+ S_ISCHR ( _mode ) ) ? true : false; }
+
+ /**
+ * Returns true if this is a block device.
+ **/
+ bool isBlockDevice() const { return S_ISBLK ( _mode ) ? true : false; }
+
+ /**
+ * Returns true if this is a block device.
+ **/
+ bool isCharDevice() const { return S_ISCHR ( _mode ) ? true : false; }
+
+ /**
+ * Returns true if this is a "special" file, i.e. a (block or character)
+ * device, a FIFO (named pipe) or a socket.
+ **/
+ bool isSpecial() const { return ( S_ISBLK ( _mode ) ||
+ S_ISCHR ( _mode ) ||
+ S_ISFIFO( _mode ) ||
+ S_ISSOCK( _mode ) ) ? true : false; }
+
+ protected:
+
+ // Data members.
+ //
+ // Keep this short in order to use as little memory as possible -
+ // there will be a _lot_ of entries of this kind!
+
+ QString _name; // the file name (without path!)
+ bool _isLocalFile :1; // flag: local or remote file?
+ bool _isSparseFile :1; // (cache) flag: sparse file (file with "holes")?
+ dev_t _device; // device this object resides on
+ mode_t _mode; // file permissions + object type
+ nlink_t _links; // number of links
+ KFileSize _size; // size in bytes
+ KFileSize _blocks; // 512 bytes blocks
+ time_t _mtime; // modification time
+
+ KDirInfo * _parent; // pointer to the parent entry
+ KFileInfo * _next; // pointer to the next entry
+ KDirTree * _tree; // pointer to the parent tree
+ }; // class KFileInfo
+
+
+ /**
+ * A more specialized version of @ref KFileInfo: This class can actually
+ * manage children. The base class (@ref KFileInfo) has only stubs for the
+ * respective methods to integrate seamlessly with the abstraction of a
+ * file / directory tree; this class fills those stubs with life.
+ *
+ * @short directory item within a @ref KDirTree.
+ **/
+ class KDirInfo: public KFileInfo
+ {
+ public:
+ /**
+ * Default constructor.
+ *
+ * If "asDotEntry" is set, this will be used as the parent's
+ * "dot entry", i.e. the pseudo directory that holds all the parent's
+ * non-directory children. This is the only way to create a "dot
+ * entry"!
+ **/
+ KDirInfo( KDirTree * tree,
+ KDirInfo * parent = 0,
+ bool asDotEntry = false );
+
+ /**
+ * Constructor from a stat buffer (i.e. based on an lstat() call).
+ **/
+ KDirInfo( const QString & filenameWithoutPath,
+ struct stat * statInfo,
+ KDirTree * tree,
+ KDirInfo * parent = 0 );
+
+ /**
+ * Constructor from a KFileItem, i.e. from a @ref KIO::StatJob
+ **/
+ KDirInfo( const KFileItem * fileItem,
+ KDirTree * tree,
+ KDirInfo * parent = 0 );
+
+ /**
+ * Destructor.
+ **/
+ virtual ~KDirInfo();
+
+
+ /**
+ * Returns the total size in bytes of this subtree.
+ *
+ * Reimplemented - inherited from @ref KFileInfo.
+ **/
+ virtual KFileSize totalSize();
+
+ /**
+ * Returns the total size in blocks of this subtree.
+ *
+ * Reimplemented - inherited from @ref KFileInfo.
+ **/
+ virtual KFileSize totalBlocks();
+
+ /**
+ * Returns the total number of children in this subtree, excluding this item.
+ *
+ * Reimplemented - inherited from @ref KFileInfo.
+ **/
+ virtual int totalItems();
+
+ /**
+ * Returns the total number of subdirectories in this subtree,
+ * excluding this item. Dot entries and "." or ".." are not counted.
+ *
+ * Reimplemented - inherited from @ref KFileInfo.
+ **/
+ virtual int totalSubDirs();
+
+ /**
+ * Returns the total number of plain file children in this subtree,
+ * excluding this item.
+ *
+ * Reimplemented - inherited from @ref KFileInfo.
+ **/
+ virtual int totalFiles();
+
+ /**
+ * Returns the latest modification time of this subtree.
+ *
+ * Reimplemented - inherited from @ref KFileInfo.
+ **/
+ virtual time_t latestMtime();
+
+ /**
+ * Returns whether or not this is a mount point.
+ *
+ * This will return 'false' only if this information can be obtained at
+ * all, i.e. if local directory reading methods are used.
+ *
+ * Reimplemented - inherited from @ref KFileInfo.
+ **/
+ virtual bool isMountPoint() { return _isMountPoint; }
+
+ /**
+ * Sets the mount point state, i.e. whether or not this is a mount
+ * point.
+ *
+ * Reimplemented - inherited from @ref KFileInfo.
+ **/
+ virtual void setMountPoint( bool isMountPoint = true );
+
+ /**
+ * Returns true if this subtree is finished reading.
+ *
+ * Reimplemented - inherited from @ref KFileInfo.
+ **/
+ virtual bool isFinished();
+
+ /**
+ * Returns true if this subtree is busy, i.e. it is not finished
+ * reading yet.
+ *
+ * Reimplemented - inherited from @ref KFileInfo.
+ **/
+ virtual bool isBusy();
+
+ /**
+ * Returns the number of pending read jobs in this subtree. When this
+ * number reaches zero, the entire subtree is done.
+ *
+ * Reimplemented - inherited from @ref KFileInfo.
+ **/
+ virtual int pendingReadJobs() { return _pendingReadJobs; }
+
+ /**
+ * Returns the first child of this item or 0 if there is none.
+ * Use the child's next() method to get the next child.
+ **/
+ virtual KFileInfo * firstChild() const { return _firstChild; }
+
+ /**
+ * Set this entry's first child.
+ * Use this method only if you know exactly what you are doing.
+ *
+ * Reimplemented - inherited from @ref KFileInfo.
+ **/
+ virtual void setFirstChild( KFileInfo *newfirstChild )
+ { _firstChild = newfirstChild; }
+
+ /**
+ * Insert a child into the children list.
+ *
+ * The order of children in this list is absolutely undefined;
+ * don't rely on any implementation-specific order.
+ **/
+ virtual void insertChild( KFileInfo *newChild );
+
+ /**
+ * Get the "Dot Entry" for this node if there is one (or 0 otherwise):
+ * This is a pseudo entry that directory nodes use to store
+ * non-directory children separately from directories. This way the end
+ * user can easily tell which summary fields belong to the directory
+ * itself and which are the accumulated values of the entire subtree.
+ **/
+ virtual KFileInfo * dotEntry() const { return _dotEntry; }
+
+ /**
+ * Set a "Dot Entry". This makes sense for directories only.
+ **/
+ virtual void setDotEntry( KFileInfo *newDotEntry ) { _dotEntry = newDotEntry; }
+
+ /**
+ * Returns true if this is a "Dot Entry". See @ref dotEntry() for
+ * details.
+ *
+ * Reimplemented - inherited from @ref KFileInfo.
+ **/
+ virtual bool isDotEntry() const { return _isDotEntry; }
+
+ /**
+ * Notification that a child has been added somewhere in the subtree.
+ *
+ * Reimplemented - inherited from @ref KFileInfo.
+ **/
+ virtual void childAdded( KFileInfo *newChild );
+
+ /**
+ * Remove a child from the children list.
+ *
+ * IMPORTANT: This MUST be called just prior to deleting an object of
+ * this class. Regrettably, this cannot simply be moved to the
+ * destructor: Important parts of the object might already be destroyed
+ * (e.g., the virtual table - no more virtual methods).
+ *
+ * Reimplemented - inherited from @ref KFileInfo.
+ **/
+ virtual void unlinkChild( KFileInfo *deletedChild );
+
+ /**
+ * Notification that a child is about to be deleted somewhere in the
+ * subtree.
+ *
+ * Reimplemented - inherited from @ref KFileInfo.
+ **/
+ virtual void deletingChild( KFileInfo *deletedChild );
+
+ /**
+ * Notification of a new directory read job somewhere in the subtree.
+ **/
+ void readJobAdded();
+
+ /**
+ * Notification of a finished directory read job somewhere in the
+ * subtree.
+ **/
+ void readJobFinished();
+
+ /**
+ * Notification of an aborted directory read job somewhere in the
+ * subtree.
+ **/
+ void readJobAborted();
+
+ /**
+ * Finalize this directory level after reading it is completed.
+ * This does _not_ mean reading reading all subdirectories is completed
+ * as well!
+ *
+ * Clean up unneeded dot entries.
+ **/
+ virtual void finalizeLocal();
+
+ /**
+ * Get the current state of the directory reading process:
+ *
+ * KDirQueued waiting in the directory read queue
+ * KDirReading reading in progress
+ * KDirFinished reading finished and OK
+ * KDirAborted reading aborted upon user request
+ * KDirError error while reading
+ *
+ * Reimplemented - inherited from @ref KFileInfo.
+ **/
+ virtual KDirReadState readState() const;
+
+ /**
+ * Set the state of the directory reading process.
+ * See @ref readState() for details.
+ **/
+ void setReadState( KDirReadState newReadState );
+
+ /**
+ * Returns true if this is a @ref KDirInfo object.
+ *
+ * Don't confuse this with @ref isDir() which tells whether or not this
+ * is a disk directory! Both should return the same, but you'll never
+ * know - better be safe than sorry!
+ *
+ * Reimplemented - inherited from @ref KFileInfo.
+ **/
+ virtual bool isDirInfo() const { return true; }
+
+
+ protected:
+
+ /**
+ * Recursively recalculate the summary fields when they are dirty.
+ *
+ * This is a _very_ expensive operation since the entire subtree may
+ * recursively be traversed.
+ **/
+ void recalc();
+
+ /**
+ * Clean up unneeded / undesired dot entries:
+ * Delete dot entries that don't have any children,
+ * reparent dot entry children to the "real" (parent) directory if
+ * there are not subdirectory siblings at the level of the dot entry.
+ **/
+ void cleanupDotEntries();
+
+
+ bool _isDotEntry; // Flag: is this entry a "dot entry"?
+ bool _isMountPoint; // Flag: is this a mount point?
+ int _pendingReadJobs; // number of open directories in this subtree
+
+ // Children management
+
+ KFileInfo * _firstChild; // pointer to the first child
+ KFileInfo * _dotEntry; // pseudo entry to hold non-dir children
+
+ // Some cached values
+
+ KFileSize _totalSize;
+ KFileSize _totalBlocks;
+ int _totalItems;
+ int _totalSubDirs;
+ int _totalFiles;
+ time_t _latestMtime;
+
+ bool _summaryDirty; // dirty flag for the cached values
+ bool _beingDestroyed;
+ KDirReadState _readState;
+
+
+ private:
+
+ void init();
+
+ }; // class KDirInfo
+
+
+ /**
+ * A directory read job that can be queued. This is mainly to prevent
+ * buffer thrashing because of too many directories opened at the same time
+ * because of simultaneous reads or even system resource consumption
+ * (directory handles in this case).
+ *
+ * Objects of this kind are transient by nature: They live only as long as
+ * the job is queued or executed. When it's done, the data is contained in
+ * the corresponding @ref KDirInfo subtree of the corresponding @ref
+ * KDirTree.
+ *
+ * For each entry automatically a @ref KFileInfo or @ref KDirInfo will be
+ * created and added to the parent @ref KDirInfo. For each directory a new
+ * @ref KDirReadJob will be created and added to the @ref KDirTree 's job
+ * queue.
+ *
+ * Notice: This class contains pure virtuals - you cannot use it
+ * directly. Derive your own class from it or use one of
+ * @ref KLocalDirReadJob or @ref KAnyDirReadJob.
+ *
+ * @short Abstract base class for directory reading.
+ **/
+ class KDirReadJob
+ {
+ public:
+ /**
+ * Constructor.
+ **/
+ KDirReadJob( KDirTree *tree, KDirInfo *dir );
+
+ /**
+ * Destructor.
+ **/
+ virtual ~KDirReadJob();
+
+ /**
+ * Start reading the directory. Prior to this nothing happens.
+ *
+ * Please notice there is no corresponding abortReading() call:
+ * Simply delete the reader if the user requests to abort reading.
+ *
+ * Derived classes need to implement this method.
+ **/
+ virtual void startReading() = 0;
+
+ /**
+ * Returns the corresponding @ref KDirInfo item.
+ **/
+ virtual KDirInfo * dir() { return _dir; }
+
+
+ protected:
+
+ /**
+ * Notification that a new child has been added.
+ *
+ * Derived classes are required to call this whenever a new child is
+ * added so this notification can be passed up to the @ref KDirTree
+ * which in turn emits a corresponding signal.
+ **/
+ void childAdded( KFileInfo *newChild );
+
+ /**
+ * Notification that a child is about to be deleted.
+ *
+ * Derived classes are required to call this just before a child is
+ * deleted so this notification can be passed up to the @ref KDirTree
+ * which in turn emits a corresponding signal.
+ *
+ * Derived classes are not required to handle child deletion at all,
+ * but if they do, calling this method is required.
+ **/
+ void deletingChild( KFileInfo *deletedChild );
+
+
+ KDirTree * _tree;
+ KDirInfo * _dir;
+ };
+
+
+ /**
+ * Impementation of the abstract @ref KDirReadJob class that reads a local
+ * directory.
+ *
+ * This will use lstat() system calls rather than KDE's network transparent
+ * directory services since lstat() unlike the KDE services can obtain
+ * information about the device (i.e. file system) a file or directory
+ * resides on. This is important if you wish to limit directory scans to
+ * one file system - which is most desirable when that one file system runs
+ * out of space.
+ *
+ * @short Directory reader that reads one local directory.
+ **/
+ class KLocalDirReadJob: public KDirReadJob
+ {
+ public:
+ /**
+ * Constructor.
+ **/
+ KLocalDirReadJob( KDirTree * tree, KDirInfo * dir );
+
+ /**
+ * Destructor.
+ **/
+ virtual ~KLocalDirReadJob();
+
+ /**
+ * Start reading the directory. Prior to this nothing happens.
+ *
+ * Inherited and reimplemented from @ref KDirReadJob.
+ **/
+ virtual void startReading();
+
+ /**
+ * Obtain information about the URL specified and create a new @ref
+ * KFileInfo or a @ref KDirInfo (whatever is appropriate) from that
+ * information. Use @ref KFileInfo::isDirInfo() to find out which.
+ * Returns 0 if such information cannot be obtained (i.e. the
+ * appropriate stat() call fails).
+ **/
+ static KFileInfo * stat( const KURL & url,
+ KDirTree * tree,
+ KDirInfo * parent = 0 );
+
+ protected:
+ DIR * _diskDir;
+ };
+
+
+ /**
+ * Generic impementation of the abstract @ref KDirReadJob class, using
+ * KDE's network transparent IO methods.
+ *
+ * This is much more generic than @ref KLocalDirReadJob since it supports
+ * protocols like 'ftp', 'http', 'smb', 'tar' etc., too. Its only drawback
+ * is that is cannot be prevented from crossing file system boundaries -
+ * which makes it pretty useless for figuring out the cause of a 'file
+ * system full' error.
+ *
+ * @short Generic directory reader that reads one directory, remote or local.
+ **/
+ class KAnyDirReadJob: public QObject, public KDirReadJob
+ {
+ Q_OBJECT
+
+ public:
+ /**
+ * Constructor.
+ **/
+ KAnyDirReadJob( KDirTree * tree, KDirInfo * dir );
+
+ /**
+ * Destructor.
+ **/
+ virtual ~KAnyDirReadJob();
+
+ /**
+ * Start reading the directory. Prior to this nothing happens.
+ *
+ * Inherited and reimplemented from @ref KDirReadJob.
+ **/
+ virtual void startReading();
+
+ /**
+ * Obtain information about the URL specified and create a new @ref
+ * KFileInfo or a @ref KDirInfo (whatever is appropriate) from that
+ * information. Use @ref KFileInfo::isDirInfo() to find out which.
+ * Returns 0 if such information cannot be obtained (i.e. the
+ * appropriate stat() call fails).
+ **/
+ static KFileInfo * stat( const KURL & url,
+ KDirTree * tree,
+ KDirInfo * parent = 0 );
+
+ /**
+ * Obtain the owner of the URL specified.
+ *
+ * This is a moderately expensive operation since it involves a network
+ * transparent stat() call.
+ **/
+ static QString owner( KURL url );
+
+
+ protected slots:
+ /**
+ * Receive directory entries from a KIO job.
+ **/
+ void entries( KIO::Job * job,
+ const KIO::UDSEntryList & entryList );
+
+ /**
+ * KIO job is finished.
+ **/
+ void finished( KIO::Job * job );
+
+ protected:
+
+ KIO::ListJob * _job;
+ };
+
+
+
+ /**
+ * This class provides some infrastructure as well as global data for a
+ * directory tree. It acts as the glue that holds things together: The root
+ * item from which to descend into the subtree, the read queue and some
+ * global policies (like whether or not to cross file systems while reading
+ * directories).
+ *
+ * @short Directory tree global data and infrastructure
+ **/
+ class KDirTree: public QObject
+ {
+ Q_OBJECT
+
+ public:
+ /**
+ * Constructor.
+ *
+ * Remember to call @ref startReading() after the constructor and
+ * setting up connections.
+ **/
+ KDirTree();
+
+ /**
+ * Destructor.
+ **/
+ virtual ~KDirTree();
+
+
+ public slots:
+
+ /**
+ * Actually start reading.
+ *
+ * It's not very pretty this is required as an extra method, but this
+ * cannot simply be done in the constructor: We need to give the caller
+ * a chance to set up Qt signal connections, and for this the
+ * constructor must return before any signals are sent, i.e. before
+ * anything is read.
+ **/
+ void startReading( const KURL & url );
+
+ /**
+ * Forcefully stop a running read process.
+ **/
+ void abortReading();
+
+ /**
+ * Refresh a subtree, i.e. read its contents from disk again.
+ *
+ * The old subtree will be deleted and rebuilt from scratch, i.e. all
+ * pointers to elements within this subtree will become invalid (a
+ * @ref subtreeDeleted() signal will be emitted to notify about that
+ * fact).
+ *
+ * When 0 is passed, the entire tree will be refreshed, i.e. from the
+ * root element on.
+ **/
+ void refresh( KFileInfo *subtree = 0 );
+
+ /**
+ * Select some other item in this tree. Triggers the @ref
+ * selectionChanged() signal - even to the sender of this signal,
+ * i.e. take care not to cause endless signal ping-pong!
+ *
+ * Select nothing if '0' is passed.
+ **/
+ void selectItem( KFileInfo *newSelection );
+
+ /**
+ * Delete a subtree.
+ **/
+ void deleteSubtree( KFileInfo *subtree );
+
+
+ public:
+
+ /**
+ * Returns the root item of this tree.
+ *
+ * Currently, there can only be one single root item for each tree.
+ */
+ KFileInfo * root() const { return _root; }
+
+ /**
+ * Locate a child somewhere in the tree whose URL (i.e. complete path)
+ * matches the URL passed. Returns 0 if there is no such child.
+ *
+ * Notice: This is a very expensive operation since the entire tree is
+ * searched recursively.
+ *
+ * 'findDotEntries' specifies if locating "dot entries" (".../<Files>")
+ * is desired.
+ *
+ * This is just a convenience method that maps to
+ * KDirTree::root()->locate( url, findDotEntries )
+ **/
+ KFileInfo * locate( QString url, bool findDotEntries = false )
+ { return _root ? _root->locate( url, findDotEntries ) : 0; }
+
+ /**
+ * Notification of a finished directory read job.
+ * All read jobs are required to call this upon (successful or
+ * unsuccessful) completion.
+ **/
+ void jobFinishedNotify( KDirReadJob *job );
+
+ /**
+ * Add a new directory read job to the queue.
+ **/
+ void addJob( KDirReadJob * job );
+
+ /**
+ * Obtain the directory read method for this tree:
+ * KDirReadLocal use opendir() and lstat()
+ * KDirReadKDirLister use KDE 2.x's KDirLister
+ **/
+ KDirReadMethod readMethod() const { return _readMethod; }
+
+ /**
+ * Should directory scans cross file systems?
+ *
+ * Notice: This can only be avoided with local directories where the
+ * device number a file resides on can be obtained.
+ * Remember, that's what this KDirStat business is all about. ;-)
+ **/
+ bool crossFileSystems() const { return _crossFileSystems; }
+
+ /**
+ * Set or unset the "cross file systems" flag.
+ **/
+ void setCrossFileSystems( bool doCross ) { _crossFileSystems = doCross; }
+
+ /**
+ * Return the tree's current selection.
+ *
+ * Even though the KDirTree by itself doesn't have a visual
+ * representation, it supports the concept of one single selected
+ * item. Views can use this to transparently keep track of this single
+ * selected item, notifying the KDirTree and thus other views with @ref
+ * KDirTree::selectItem() . Attached views should connect to the @ref
+ * selectionChanged() signal to be notified when the selection changes.
+ *
+ * NOTE: This method returns 0 if nothing is selected.
+ **/
+ KFileInfo * selection() const { return _selection; }
+
+ /**
+ * Notification that a child has been added.
+ *
+ * Directory read jobs are required to call this for each child added
+ * so the tree can emit the corresponding @ref childAdded() signal.
+ **/
+ virtual void childAddedNotify( KFileInfo *newChild );
+
+ /**
+ * Notification that a child is about to be deleted.
+ *
+ * Directory read jobs are required to call this for each deleted child
+ * so the tree can emit the corresponding @ref deletingChild() signal.
+ **/
+ virtual void deletingChildNotify( KFileInfo *deletedChild );
+
+ /**
+ * Notification that one or more children have been deleted.
+ *
+ * Directory read jobs are required to call this when one or more
+ * children are deleted so the tree can emit the corresponding @ref
+ * deletingChild() signal. For multiple deletions (e.g. entire
+ * subtrees) this should only happen once at the end.
+ **/
+ virtual void childDeletedNotify();
+
+ /**
+ * Send a @ref progressInfo() signal to keep the user entertained while
+ * directories are being read.
+ **/
+ void sendProgressInfo( const QString &infoLine );
+
+ /**
+ * Send a @ref finalizeLocal() signal to give views a chance to
+ * finalize the display of this directory level - e.g. clean up dot
+ * entries, set the final "expandable" state etc.
+ **/
+ void sendFinalizeLocal( KDirInfo *dir );
+
+ /**
+ * Returns 'true' if this tree uses the 'file:/' protocol (regardless
+ * of local or network transparent directory reader).
+ **/
+ bool isFileProtocol() { return _isFileProtocol; }
+
+ /**
+ * Returns 'true' if directory reading is in progress in this tree.
+ **/
+ bool isBusy() { return _isBusy; }
+
+
+ signals:
+
+ /**
+ * Emitted when a child has been added.
+ **/
+ void childAdded( KFileInfo *newChild );
+
+ /**
+ * Emitted when a child is about to be deleted.
+ **/
+ void deletingChild( KFileInfo *deletedChild );
+
+ /**
+ * Emitted after a child is deleted. If you are interested which child
+ * it was, better use the @ref deletingChild() signal.
+ * @ref childDeleted() is only useful to rebuild a view etc. completely.
+ * If possible, this signal is sent only once for multiple deletions -
+ * e.g., when entire subtrees are deleted.
+ **/
+ void childDeleted();
+
+ /**
+ * Emitted when reading is started.
+ **/
+ void startingReading();
+
+ /**
+ * Emitted when reading this directory tree is finished.
+ **/
+ void finished();
+
+ /**
+ * Emitted when reading this directory tree has been aborted.
+ **/
+ void aborted();
+
+ /**
+ * Emitted when reading a directory is finished.
+ * This does _not_ mean reading all subdirectories is finished, too -
+ * only this directory level is complete!
+ *
+ * WARNING: 'dir' may be 0 if the the tree's root could not be read.
+ *
+ * Use this signal to do similar cleanups like
+ * @ref KDirInfo::finalizeLocal(), e.g. cleaning up unused / undesired
+ * dot entries like in @ref KDirInfo::cleanupDotEntries().
+ **/
+ void finalizeLocal( KDirInfo *dir );
+
+ /**
+ * Emitted when the current selection has changed, i.e. whenever some
+ * attached view triggers the @ref selectItem() slot or when the
+ * current selection is deleted.
+ *
+ * NOTE: 'newSelection' may be 0 if nothing is selected.
+ **/
+ void selectionChanged( KFileInfo *newSelection );
+
+ /**
+ * Single line progress information, emitted when the read status
+ * changes - typically when a new directory is being read. Connect to a
+ * status bar etc. to keep the user entertained.
+ **/
+ void progressInfo( const QString &infoLine );
+
+
+ protected slots:
+
+ /**
+ * Time-sliced work procedure to be performed while the application is
+ * in the main loop: Read some directory entries, but relinquish
+ * control back to the application so it can maintain some
+ * responsiveness. This method uses single-shot timers of minimal
+ * duration to activate itself as soon as there are no more user events
+ * to process. Call this only once directly after inserting a read job
+ * into the job queue.
+ **/
+ void timeSlicedRead();
+
+ /**
+ * Read some parameters from the global @ref KConfig object.
+ **/
+ void readConfig();
+
+
+ protected:
+
+ KFileInfo * _root;
+ KFileInfo * _selection;
+ QPtrQueue<KDirReadJob> _jobQueue;
+ KDirReadMethod _readMethod;
+ bool _crossFileSystems;
+ bool _enableLocalDirReader;
+ bool _isFileProtocol;
+ bool _isBusy;
+ };
+
+
+ //----------------------------------------------------------------------
+ // Static Functions
+ //----------------------------------------------------------------------
+
+ /**
+ * Make a valid, fixed and cleaned URL from a (possibly dirty) URL or maybe
+ * a path.
+ **/
+ KURL fixedUrl( const QString & dirtyUrl );
+
+
+ /**
+ * Format a file / subtree size human readable, i.e. in "GB" / "MB"
+ * etc. rather than huge numbers of digits.
+ *
+ * Note: For kdDebug() etc., operator<< is overwritten to do exactly that:
+ *
+ * kdDebug() << "Size: " << x->totalSize() << endl;
+ **/
+ QString formatSize ( KFileSize lSize );
+
+
+ /**
+ * Print the debugUrl() of a @ref KFileInfo in a debug stream.
+ **/
+ inline kdbgstream & operator<< ( kdbgstream & stream, const KFileInfo * info )
+ {
+ if ( info )
+ stream << info->debugUrl();
+ else
+ stream << "<NULL>";
+
+ return stream;
+ }
+
+
+ /**
+ * Human-readable output of a file size in a debug stream.
+ **/
+ inline kdbgstream & operator<< ( kdbgstream & stream, KFileSize lSize )
+ {
+ stream << formatSize( lSize );
+
+ return stream;
+ }
+
+} // namespace KDirStat
+
+
+#endif // ifndef KDirTree_h
+
+
+// EOF
diff --git a/kdirstat/kdirtreeiterators.cpp b/kdirstat/kdirtreeiterators.cpp
new file mode 100644
index 0000000..6f42798
--- /dev/null
+++ b/kdirstat/kdirtreeiterators.cpp
@@ -0,0 +1,417 @@
+/*
+ * File name: kdirtreeiterators.h
+ * Summary: Support classes for KDirStat - KDirTree iterator classes
+ * License: LGPL - See file COPYING.LIB for details.
+ * Author: Stefan Hundhammer <sh@suse.de>
+ *
+ * Updated: 2003-01-07
+ */
+
+
+#include "kdirtreeiterators.h"
+#include "kdirtree.h"
+
+
+using namespace KDirStat;
+
+
+KFileInfoIterator::KFileInfoIterator( KFileInfo * parent,
+ KDotEntryPolicy dotEntryPolicy )
+{
+ init( parent,
+ dotEntryPolicy,
+ true ); // callNext
+}
+
+
+KFileInfoIterator::KFileInfoIterator( KFileInfo * parent,
+ KDotEntryPolicy dotEntryPolicy,
+ bool callNext )
+{
+ init( parent, dotEntryPolicy, callNext );
+}
+
+
+void
+KFileInfoIterator::init( KFileInfo * parent,
+ KDotEntryPolicy dotEntryPolicy,
+ bool callNext )
+{
+ _parent = parent;
+ _policy = dotEntryPolicy;
+ _current = 0;
+
+ _directChildrenProcessed = false;
+ _dotEntryProcessed = false;
+ _dotEntryChildrenProcessed = false;
+
+ if ( callNext )
+ next();
+}
+
+
+KFileInfoIterator::~KFileInfoIterator()
+{
+ // NOP
+}
+
+
+void KFileInfoIterator::next()
+{
+ if ( ! _directChildrenProcessed )
+ {
+ // Process direct children
+
+ _current = _current ? _current->next() : _parent->firstChild();
+
+ if ( ! _current )
+ {
+ _directChildrenProcessed = true;
+ next();
+ }
+ else
+ {
+ // kdDebug() << k_funcinfo << " direct child " << _current << endl;
+ }
+ }
+ else // _directChildrenProcessed
+ {
+ if ( ! _dotEntryProcessed )
+ {
+ // Process dot entry
+
+ _current = _policy == KDotEntryAsSubDir ? _parent->dotEntry() : 0;
+ _dotEntryProcessed = true;
+
+ if ( ! _current )
+ {
+ next();
+ }
+ else
+ {
+ // kdDebug() << k_funcinfo << " dot entry " << _current << endl;
+ }
+ }
+ else // Dot entry already processed or processing it not desired
+ {
+ if ( ! _dotEntryChildrenProcessed )
+ {
+ if ( _policy == KDotEntryTransparent )
+ {
+ // Process dot entry children
+
+ _current = _current ?
+ _current->next() :
+ ( _parent->dotEntry() ? _parent->dotEntry()->firstChild() : 0 );
+
+ if ( ! _current )
+ {
+ _dotEntryChildrenProcessed = true;
+ }
+ else
+ {
+ // kdDebug() << k_funcinfo << " dot entry child " << _current << endl;
+ }
+ }
+ else // _policy != KDotEntryTransparent
+ {
+ _current = 0;
+ _dotEntryChildrenProcessed = true;
+ }
+ }
+ }
+ }
+}
+
+
+int
+KFileInfoIterator::count()
+{
+ int cnt = 0;
+
+ // Count direct children
+
+ KFileInfo *child = _parent->firstChild();
+
+ while ( child )
+ {
+ cnt++;
+ child = child->next();
+ }
+
+
+ // Handle the dot entry
+
+ switch ( _policy )
+ {
+ case KDotEntryTransparent: // Count the dot entry's children as well.
+ if ( _parent->dotEntry() )
+ {
+ child = _parent->dotEntry()->firstChild();
+
+ while ( child )
+ {
+ cnt++;
+ child = child->next();
+ }
+ }
+ break;
+
+ case KDotEntryAsSubDir: // The dot entry counts as one item.
+ if ( _parent->dotEntry() )
+ cnt++;
+ break;
+
+ case KDotEntryIgnore: // We're done.
+ break;
+ }
+
+ return cnt;
+}
+
+
+
+
+
+
+KFileInfoSortedIterator::KFileInfoSortedIterator( KFileInfo * parent,
+ KDotEntryPolicy dotEntryPolicy,
+ KFileInfoSortOrder sortOrder,
+ bool ascending )
+ : KFileInfoIterator( parent, dotEntryPolicy, false )
+{
+ _sortOrder = sortOrder;
+ _ascending = ascending;
+ _initComplete = false;
+ _childrenList = 0;
+ _current = 0;
+}
+
+
+void
+KFileInfoSortedIterator::delayedInit()
+{
+ _childrenList = new KFileInfoList( _sortOrder, _ascending );
+ CHECK_PTR( _childrenList );
+
+ if ( _sortOrder == KSortByName )
+ {
+ makeDefaultOrderChildrenList();
+ }
+ else
+ {
+ makeChildrenList();
+ }
+
+ _current = _childrenList->first();
+ _initComplete = true;
+}
+
+
+KFileInfoSortedIterator::~KFileInfoSortedIterator()
+{
+ if ( _childrenList )
+ delete _childrenList;
+}
+
+
+void KFileInfoSortedIterator::makeDefaultOrderChildrenList()
+{
+ // Fill children list with direct children
+
+ KFileInfo *child = _parent->firstChild();
+
+ while ( child )
+ {
+ _childrenList->append( child );
+ child = child->next();
+ }
+
+ _childrenList->sort();
+
+ if ( _policy == KDotEntryAsSubDir && _parent->dotEntry() )
+ {
+ // Append dot entry to the children list
+
+ _childrenList->append( _parent->dotEntry() );
+ }
+
+
+ // Append the dot entry's children to the children list
+
+ if ( _policy == KDotEntryTransparent && _parent->dotEntry() )
+ {
+ // Create a temporary list for the dot entry children
+
+ KFileInfoList dotEntryChildrenList( _sortOrder, _ascending );
+ child = _parent->dotEntry()->firstChild();
+
+ while ( child )
+ {
+ dotEntryChildrenList.append( child );
+ child = child->next();
+ }
+
+ dotEntryChildrenList.sort();
+
+
+ // Now append all of this dot entry children list to the children list
+
+ child = dotEntryChildrenList.first();
+
+ while ( child )
+ {
+ _childrenList->append( child );
+ child = dotEntryChildrenList.next();
+ }
+ }
+}
+
+
+void
+KFileInfoSortedIterator::makeChildrenList()
+{
+ KFileInfoIterator it( _parent, _policy );
+
+ while ( *it )
+ {
+ _childrenList->append( *it );
+ ++it;
+ }
+
+ _childrenList->sort();
+}
+
+
+KFileInfo *
+KFileInfoSortedIterator::current()
+{
+ if ( ! _initComplete )
+ delayedInit();
+
+ return _current;
+}
+
+
+void KFileInfoSortedIterator::next()
+{
+ if ( ! _initComplete )
+ delayedInit();
+
+ _current = _childrenList->next();
+}
+
+
+bool
+KFileInfoSortedIterator::finished()
+{
+ if ( ! _initComplete )
+ delayedInit();
+
+ return _current == 0;
+}
+
+
+
+
+
+
+KFileInfoSortedBySizeIterator::KFileInfoSortedBySizeIterator( KFileInfo * parent,
+ KFileSize minSize,
+ KDotEntryPolicy dotEntryPolicy,
+ bool ascending )
+ : KFileInfoSortedIterator( parent, dotEntryPolicy, KSortByTotalSize, ascending )
+ , _minSize( minSize )
+{
+}
+
+
+void
+KFileInfoSortedBySizeIterator::makeChildrenList()
+{
+ KFileInfoIterator it( _parent, _policy );
+
+ while ( *it )
+ {
+ if ( (*it)->totalSize() >= _minSize )
+ _childrenList->append( *it );
+
+ ++it;
+ }
+
+ _childrenList->sort();
+}
+
+
+
+
+
+
+KFileInfoList::KFileInfoList( KFileInfoSortOrder sortOrder, bool ascending )
+ : QPtrList<KFileInfo>()
+{
+ _sortOrder = sortOrder;
+ _ascending = ascending;
+}
+
+
+KFileInfoList::~KFileInfoList()
+{
+ // NOP
+}
+
+
+
+KFileSize
+KFileInfoList::sumTotalSizes()
+{
+ KFileSize sum = 0;
+ KFileInfoListIterator it( *this );
+
+ while ( *it )
+ {
+ sum += (*it)->totalSize();
+ ++it;
+ }
+
+ return sum;
+}
+
+
+
+int
+KFileInfoList::compareItems( QCollection::Item it1, QCollection::Item it2 )
+{
+ if ( it1 == it2 )
+ return 0;
+
+ KFileInfo *file1 = (KFileInfo *) it1;
+ KFileInfo *file2 = (KFileInfo *) it2;
+
+ int result = 0;
+
+ switch ( _sortOrder )
+ {
+ case KUnsorted:
+ return 1;
+
+ case KSortByName:
+ result = QString::compare( file1->name(), file2->name() );
+ break;
+
+ case KSortByTotalSize:
+ result = compare<KFileSize>( file1->totalSize(), file2->totalSize() );
+ break;
+
+ case KSortByLatestMtime:
+ result = compare<time_t>( file1->latestMtime(), file2->latestMtime() );
+ break;
+ }
+
+ return _ascending ? result : -result;
+}
+
+
+
+
+// EOF
diff --git a/kdirstat/kdirtreeiterators.h b/kdirstat/kdirtreeiterators.h
new file mode 100644
index 0000000..c3e2569
--- /dev/null
+++ b/kdirstat/kdirtreeiterators.h
@@ -0,0 +1,386 @@
+/*
+ * File name: kdirtreeiterators.h
+ * Summary: Support classes for KDirStat - KDirTree iterators
+ * License: LGPL - See file COPYING.LIB for details.
+ * Author: Stefan Hundhammer <sh@suse.de>
+ *
+ * Updated: 2003-01-07
+ */
+
+
+#ifndef KDirTreeIterators_h
+#define KDirTreeIterators_h
+
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include "kdirtree.h"
+
+
+namespace KDirStat
+{
+ /**
+ * Policies how to treat a "dot entry" for iterator objects.
+ * See @ref KFileInfoIterator for details.
+ **/
+ typedef enum
+ {
+ KDotEntryTransparent, // Flatten hierarchy - move dot entry children up
+ KDotEntryAsSubDir, // Treat dot entry as ordinary subdirectory
+ KDotEntryIgnore // Ignore dot entry and its children completely
+ } KDotEntryPolicy;
+
+
+ typedef enum
+ {
+ KUnsorted,
+ KSortByName,
+ KSortByTotalSize,
+ KSortByLatestMtime
+ } KFileInfoSortOrder;
+
+
+ // Forward declarations
+ class KFileInfoList;
+
+
+ /**
+ * Iterator class for children of a @ref KFileInfo object. For optimum
+ * performance, this iterator class does NOT return children in any
+ * specific sort order. If you need that, use @ref KFileInfoSortedIterator
+ * instead.
+ *
+ * Sample usage:
+ *
+ * KFileInfoIterator it( node, KDotEntryTransparent );
+ *
+ * while ( *it )
+ * {
+ * kdDebug() << *it << ":\t" << (*it)->totalSize() ) << endl;
+ * ++it;
+ * }
+ *
+ * This will output the URL (path+name) and the total size of each (direct)
+ * subdirectory child and each (direct) file child of 'node'.
+ * Notice: This does not recurse into subdirectories!
+ *
+ * @short (unsorted) iterator for @ref KFileInfo children.
+ **/
+ class KFileInfoIterator
+ {
+ public:
+ /**
+ * Constructor: Initialize an iterator object to iterate over the
+ * children of 'parent' (unsorted!), depending on 'dotEntryPolicy':
+ *
+ * KDotEntryTransparent (default):
+ *
+ * Treat the dot entry as if it wasn't there - pretend to move all its
+ * children up to the real parent. This makes a directory look very
+ * much like the directory on disk, without the dot entry. 'current()'
+ * or 'operator*()' will never return the dot entry, but all of its
+ * children. Subdirectories will be processed before any file children.
+ *
+ * KDotEntryIsSubDir:
+ *
+ * Treat the dot entry just like any other subdirectory. Don't iterate
+ * over its children, too (unlike KDotEntryTransparent above).
+ * 'current()' or 'operator*()' will return the dot entry, but none of
+ * its children (unless, of course, you create an iterator with the dot
+ * entry as the parent).
+ *
+ * KDotEntryIgnore:
+ *
+ * Ignore the dot entry and its children completely. Useful if children
+ * other than subdirectories are not interesting anyway. 'current()'
+ * or 'operator*()' will never return the dot entry nor any of its
+ * children.
+ *
+ **/
+ KFileInfoIterator( KFileInfo * parent,
+ KDotEntryPolicy dotEntryPolicy = KDotEntryTransparent );
+
+ protected:
+ /**
+ * Alternate constructor to be called from derived classes: Those can
+ * choose not to call next() in the constructor.
+ **/
+ KFileInfoIterator ( KFileInfo * parent,
+ KDotEntryPolicy dotEntryPolicy,
+ bool callNext );
+
+ private:
+ /**
+ * Internal initialization called from any constructor.
+ **/
+ void init ( KFileInfo * parent,
+ KDotEntryPolicy dotEntryPolicy,
+ bool callNext );
+
+ public:
+
+ /**
+ * Destructor.
+ **/
+ virtual ~KFileInfoIterator();
+
+ /**
+ * Return the current child object or 0 if there is no more.
+ * Same as @ref operator*() .
+ **/
+ virtual KFileInfo * current() { return _current; }
+
+ /**
+ * Return the current child object or 0 if there is no more.
+ * Same as @ref current().
+ **/
+ KFileInfo * operator*() { return current(); }
+
+ /**
+ * Advance to the next child. Same as @ref operator++().
+ **/
+ virtual void next();
+
+ /**
+ * Advance to the next child. Same as @ref next().
+ **/
+ void operator++() { next(); }
+
+ /**
+ * Returns 'true' if this iterator is finished and 'false' if not.
+ **/
+ virtual bool finished() { return _current == 0; }
+
+ /**
+ * Check whether or not the current child is a directory, i.e. can be
+ * cast to @ref KDirInfo * .
+ **/
+ bool currentIsDir() { return _current && _current->isDirInfo(); }
+
+ /**
+ * Return the current child object cast to @ref KDirInfo * or 0 if
+ * there either is no more or it isn't a directory. Check with @ref
+ * currentIsDir() before using this!
+ **/
+ KDirInfo * currentDir() { return currentIsDir() ? (KDirInfo *) _current : 0; }
+
+ /**
+ * Return the number of items that will be processed.
+ * This is an expensive operation.
+ **/
+ int count();
+
+
+ protected:
+
+ KFileInfo * _parent;
+ KDotEntryPolicy _policy;
+ KFileInfo * _current;
+ bool _directChildrenProcessed;
+ bool _dotEntryProcessed;
+ bool _dotEntryChildrenProcessed;
+
+ }; // class KFileInfoIterator
+
+
+
+ /**
+ * Iterator class for children of a @ref KFileInfo object. This iterator
+ * returns children sorted by name: Subdirectories first, then the dot
+ * entry (if desired - depending on policy), then file children (if
+ * desired). Note: If you don't need the sorting feature, you might want to
+ * use @ref KFileItemIterator instead which has better performance.
+ *
+ * @short sorted iterator for @ref KFileInfo children.
+ **/
+ class KFileInfoSortedIterator: public KFileInfoIterator
+ {
+ public:
+ /**
+ * Constructor. Specify the sorting order with 'sortOrder' and 'ascending'.
+ * See @ref KFileInfoIterator for more details.
+ **/
+ KFileInfoSortedIterator( KFileInfo * parent,
+ KDotEntryPolicy dotEntryPolicy = KDotEntryTransparent,
+ KFileInfoSortOrder sortOrder = KSortByName,
+ bool ascending = true );
+ /**
+ * Destructor.
+ **/
+ virtual ~KFileInfoSortedIterator();
+
+ /**
+ * Return the current child object or 0 if there is no more.
+ *
+ * Inherited from @ref KFileInfoIterator.
+ * Overwritten to overcome some shortcomings of C++:
+ * Virtual methods cannot be used in the constructor.
+ **/
+ virtual KFileInfo * current();
+
+ /**
+ * Advance to the next child. Same as @ref operator++().
+ * Sort by name, sub directories first, then the dot entry (if
+ * desired), then files (if desired).
+ *
+ * Inherited from @ref KFileInfoIterator.
+ **/
+ virtual void next();
+
+ /**
+ * Returns 'true' if this iterator is finished and 'false' if not.
+ *
+ * Inherited from @ref KFileInfoIterator.
+ **/
+ virtual bool finished();
+
+
+ protected:
+
+ /**
+ * Delayed initialization for class parts that rely on availability of
+ * virtual methods. This is a kludge to overcome a major shortcoming of
+ * C++: Virtual methods are not available in the constructor yet.
+ * This is a neverending cause of trouble.
+ **/
+ void delayedInit();
+
+ /**
+ * Make a 'default order' children list:
+ * First all subdirectories sorted by name,
+ * then the dot entry (depending on policy),
+ * then the dot entry's children (depending on policy).
+ **/
+ virtual void makeDefaultOrderChildrenList();
+
+ /**
+ * Make a sorted children list according to the current sort
+ * criteria - unless KSortByName is requested, in which case
+ * makeDefaultOrderChildrenList() above is used.
+ **/
+ virtual void makeChildrenList();
+
+
+ // Data members
+
+ KFileInfoList * _childrenList;
+ KFileInfoSortOrder _sortOrder;
+ bool _ascending;
+ bool _initComplete;
+
+ }; // class KFileInfoSortedIterator
+
+
+
+ /**
+ * Specialized KFileInfo iterator that sorts by (total) size, yet
+ * disregards children below a minimum size. This can considerably improve
+ * performance if the number of children that need to be sorted decreases
+ * dramatically.
+ *
+ * For example, treemaps can only display a limited portion of large
+ * directory trees since the number of available pixels is very
+ * limited. Thus, files (or directories) below a certain size usually don't
+ * get a individual visual representation anyway, so they may as well be
+ * omitted right away - no need for expensive list sorting operations.
+ **/
+ class KFileInfoSortedBySizeIterator: public KFileInfoSortedIterator
+ {
+ public:
+
+ /**
+ * Constructor. Children below 'minSize' will be ignored by this iterator.
+ **/
+ KFileInfoSortedBySizeIterator( KFileInfo * parent,
+ KFileSize minSize = 0,
+ KDotEntryPolicy dotEntryPolicy = KDotEntryTransparent,
+ bool ascending = false );
+
+ /**
+ * Destructor.
+ **/
+ virtual ~KFileInfoSortedBySizeIterator() {};
+
+
+ protected:
+
+ /**
+ * Create the (sorted) children list. Disregard children below minSize.
+ * Reimplemented from KFileInfoSortedIterator.
+ **/
+ virtual void makeChildrenList();
+
+
+ // Data members
+
+ KFileSize _minSize;
+
+ }; // class KFileInfoSortedBySizeIterator
+
+
+
+ /**
+ * Internal helper class for sorting iterators.
+ **/
+ class KFileInfoList: public QPtrList<KFileInfo>
+ {
+ public:
+
+ /**
+ * Constructor.
+ **/
+ KFileInfoList( KFileInfoSortOrder sortOrder = KSortByName,
+ bool ascending = true );
+
+ /**
+ * Destructor.
+ **/
+ virtual ~KFileInfoList();
+
+ /**
+ * Returns the sum of all the total sizes in the list.
+ **/
+ KFileSize sumTotalSizes();
+
+
+ protected:
+ /**
+ * Comparison function. This is why this class is needed at all.
+ **/
+ virtual int compareItems( QCollection::Item it1, QCollection::Item it2 );
+
+ KFileInfoSortOrder _sortOrder;
+ bool _ascending;
+ };
+
+
+ typedef QPtrListIterator<KFileInfo> KFileInfoListIterator;
+
+
+
+ //----------------------------------------------------------------------
+ // Static Functions
+ //----------------------------------------------------------------------
+
+ /**
+ * Generic comparison function as expected by all kinds of sorting etc.
+ * algorithms. Requires operator<() and operator==() to be defined for this
+ * class.
+ **/
+ template<class T>
+ inline int compare( T val1, T val2 )
+ {
+ if ( val1 < val2 ) return -1;
+ else if ( val1 == val2 ) return 0;
+ else return 1;
+ }
+
+} // namespace KDirStat
+
+
+#endif // ifndef KDirTreeIterators_h
+
+
+// EOF
diff --git a/kdirstat/kdirtreeview.cpp b/kdirstat/kdirtreeview.cpp
new file mode 100644
index 0000000..3283efe
--- /dev/null
+++ b/kdirstat/kdirtreeview.cpp
@@ -0,0 +1,1956 @@
+/*
+ * File name: kdirtreeview.cpp
+ * Summary: High level classes for KDirStat
+ * License: LGPL - See file COPYING.LIB for details.
+ * Author: Stefan Hundhammer <sh@suse.de>
+ *
+ * Updated: 2005-01-07
+ */
+
+
+#include <time.h>
+#include <stdlib.h>
+
+#include <qtimer.h>
+#include <qcolor.h>
+#include <qheader.h>
+#include <qpopupmenu.h>
+
+#include <kapp.h>
+#include <klocale.h>
+#include <kglobal.h>
+#include <kglobalsettings.h>
+#include <kicontheme.h>
+#include <kiconloader.h>
+
+#include "kdirtreeview.h"
+#include "kdirtreeiterators.h"
+#include "kpacman.h"
+
+#define SEPARATE_READ_JOBS_COL 0
+#define VERBOSE_PROGRESS_INFO 0
+
+using namespace KDirStat;
+
+
+KDirTreeView::KDirTreeView( 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, AlignRight );
+ setColumnAlignment ( _percentNumCol, AlignRight );
+ setColumnAlignment ( _ownSizeCol, AlignRight );
+ setColumnAlignment ( _totalItemsCol, AlignRight );
+ setColumnAlignment ( _totalFilesCol, AlignRight );
+ setColumnAlignment ( _totalSubDirsCol, AlignRight );
+ setColumnAlignment ( _readJobsCol, AlignRight );
+
+
+ setSorting( _totalSizeCol );
+
+
+#define loadIcon(ICON) KGlobal::iconLoader()->loadIcon( (ICON), KIcon::Small )
+
+ _openDirIcon = loadIcon( "folder_open" );
+ _closedDirIcon = loadIcon( "folder" );
+ _openDotEntryIcon = loadIcon( "folder_orange_open");
+ _closedDotEntryIcon = loadIcon( "folder_orange" );
+ _unreadableDirIcon = loadIcon( "folder_locked" );
+ _mountPointIcon = loadIcon( "hdd_mount" );
+ _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( "stop" );
+ _readyIcon = QPixmap();
+
+#undef loadIcon
+
+ setDefaultFillColors();
+ readConfig();
+ ensureContrast();
+
+
+ connect( kapp, SIGNAL( kdisplayPaletteChanged() ),
+ this, SLOT ( paletteChanged() ) );
+
+ connect( this, SIGNAL( selectionChanged ( QListViewItem * ) ),
+ this, SLOT ( selectItem ( QListViewItem * ) ) );
+
+ connect( this, SIGNAL( rightButtonPressed ( QListViewItem *, const QPoint &, int ) ),
+ this, SLOT ( popupContextMenu ( QListViewItem *, const QPoint &, int ) ) );
+
+ connect( header(), SIGNAL( sizeChange ( int, int, int ) ),
+ this, SLOT ( columnResized( int, int, int ) ) );
+
+ _contextInfo = new QPopupMenu;
+ _idContextInfo = _contextInfo->insertItem ( "dummy" );
+}
+
+
+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
+KDirTreeView::setDebugFunc( int i, const QString & 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 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
+
+ _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, 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 * ) ) );
+
+ // 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 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();
+
+
+ // 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 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
+}
+
+
+#if QT_VERSION < 300
+void
+KDirTreeView::sendProgressInfo()
+{
+ sendProgressInfo( _currentDir );
+}
+#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( QListViewItem *listViewItem )
+{
+ _selection = dynamic_cast<KDirTreeViewItem *>( listViewItem );
+
+ if ( _selection )
+ {
+ // kdDebug() << k_funcinfo << " Selecting item " << _selection << endl;
+ setSelected( _selection, true );
+ }
+ else
+ {
+ // kdDebug() << k_funcinfo << " Clearing selection" << endl;
+ clearSelection();
+ }
+
+
+ emit selectionChanged( _selection );
+ emit selectionChanged( _selection ? _selection->orig() : (KFileInfo *) 0 );
+}
+
+
+void
+KDirTreeView::selectItem( KFileInfo *newSelection )
+{
+ // Short-circuit for the most common case: The signal has been triggered by
+ // this view, and the KDirTree has sent it right back.
+
+ if ( _selection && _selection->orig() == newSelection )
+ return;
+
+ if ( ! newSelection )
+ clearSelection();
+ else
+ {
+ _selection = locate( newSelection,
+ false, // lazy
+ true ); // doClone
+ if ( _selection )
+ {
+ closeAllExcept( _selection );
+ _selection->setOpen( false );
+ ensureItemVisible( _selection );
+ emit selectionChanged( _selection );
+ setSelected( _selection, true );
+ }
+ else
+ kdError() << "Couldn't clone item " << newSelection << endl;
+ }
+}
+
+
+void
+KDirTreeView::clearSelection()
+{
+ // kdDebug() << k_funcinfo << endl;
+ _selection = 0;
+ QListView::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 QColor &
+KDirTreeView::fillColor( int level ) const
+{
+ if ( level < 0 )
+ {
+ level = 0;
+ kdWarning() << k_funcinfo << "Invalid argument: " << level << endl;
+ }
+
+ return _fillColor [ level % _usedFillColors ];
+}
+
+
+const QColor &
+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 QColor & 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++, 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
+KDirTreeView::setTreeBackground( const QColor &color )
+{
+ _treeBackground = color;
+ _percentageBarBackground = _treeBackground.dark( 115 );
+
+ QPalette pal = kapp->palette();
+ pal.setBrush( QColorGroup::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( KGlobalSettings::baseColor() );
+ ensureContrast();
+}
+
+
+void
+KDirTreeView::popupContextMenu( QListViewItem * listViewItem,
+ const QPoint & 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() ) )
+ {
+ 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
+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
+KDirTreeView::popupContextInfo( const QPoint & pos,
+ const QString & info )
+{
+ _contextInfo->changeItem( info, _idContextInfo );
+ _contextInfo->popup( pos );
+}
+
+
+void
+KDirTreeView::readConfig()
+{
+ KConfig *config = kapp->config();
+ KConfigGroupSaver 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
+
+ QColor defaultColor( blue );
+
+ for ( int i=0; i < KDirTreeViewMaxFillColor; i++ )
+ {
+ QString name;
+ name.sprintf( "fillColor_%02d", i );
+ _fillColor [i] = config->readColorEntry( name, &defaultColor );
+ }
+ }
+
+ if ( isVisible() )
+ triggerUpdate();
+}
+
+
+void
+KDirTreeView::saveConfig() const
+{
+ KConfig *config = kapp->config();
+ KConfigGroupSaver saver( config, "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
+KDirTreeView::setSorting( int column, bool increasing )
+{
+ _sortCol = column;
+ QListView::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;
+ }
+
+ QString owner = KAnyDirReadJob::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.
+
+ kapp->invokeMailer( mail );
+ logActivity( 10 );
+}
+
+
+
+
+
+
+KDirTreeViewItem::KDirTreeViewItem( KDirTreeView * view,
+ KFileInfo * orig )
+ : QListViewItem( view )
+{
+ init( view, 0, orig );
+}
+
+
+KDirTreeViewItem::KDirTreeViewItem( KDirTreeView * view,
+ KDirTreeViewItem * parent,
+ KFileInfo * orig )
+ : QListViewItem( 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( "<Files>" ) );
+ QListViewItem::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 );
+ }
+
+ QListViewItem::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 )
+ {
+ 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()
+{
+ 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
+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() ) );
+ }
+
+
+ // 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 );
+ }
+
+ QListViewItem::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
+}
+
+
+QString
+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
+KDirTreeViewItem::compare( QListViewItem * 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 QListViewItem::compare( otherListViewItem, column, ascending );
+}
+
+
+void
+KDirTreeViewItem::paintCell( QPainter * 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 )
+ {
+ QListViewItem::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.
+ */
+ QListViewItem::paintCell( painter,
+ colorGroup,
+ column,
+ width,
+ alignment );
+ }
+}
+
+
+void
+KDirTreeViewItem::paintPercentageBar( float percent,
+ QPainter * 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( 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 )
+{
+ QString sizeText;
+ int count = 0;
+
+ while ( size > 0 )
+ {
+ sizeText = ( ( size % 10 ) + '0' ) + sizeText;
+ size /= 10;
+
+ if ( ++count == 3 && size > 0 )
+ {
+ sizeText = KGlobal::locale()->thousandsSeparator() + sizeText;
+ count = 0;
+ }
+ }
+
+ return sizeText;
+}
+
+
+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(), true ) + " " + // 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
diff --git a/kdirstat/kdirtreeview.h b/kdirstat/kdirtreeview.h
new file mode 100644
index 0000000..baeae6b
--- /dev/null
+++ b/kdirstat/kdirtreeview.h
@@ -0,0 +1,889 @@
+/*
+ * File name: kdirtreeview.h
+ * Summary: High level classes for KDirStat
+ * License: LGPL - See file COPYING.LIB for details.
+ * Author: Stefan Hundhammer <sh@suse.de>
+ *
+ * Updated: 2003-08-26
+ */
+
+
+#ifndef KDirTreeView_h
+#define KDirTreeView_h
+
+
+// Alternative parent class for KDirTreeView.
+//
+// If you change this, don't forget to change the KDirTreeView class
+// declaration also. Unfortunately there this 'define' can't be used -
+// it seems to confuse the 'moc' preprocessor.
+
+#define USE_KLISTVIEW 0
+#define DEBUG_COUNTERS 10
+
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+
+#include <qdatetime.h>
+#include <qlistview.h>
+#include <qpixmap.h>
+#include <klistview.h>
+#include "kdirtree.h"
+
+
+// Forward declarations
+class QWidget;
+class QTimer;
+class QPopupMenu;
+class KPacManAnimation;
+
+
+// Open a new name space since KDE's name space is pretty much cluttered
+// already - all names that would even remotely match are already used up,
+// yet the resprective classes don't quite fit the purposes required here.
+
+namespace KDirStat
+{
+#define KDirTreeViewMaxFillColor 16
+
+
+#if USE_KLISTVIEW
+# define KDirTreeViewParentClass KListView
+#else
+# define KDirTreeViewParentClass QListView
+#endif
+
+ class KDirTreeViewItem;
+
+
+ class KDirTreeView: public QListView
+ // Using
+ // class KDirTreeView: public KDirTreeViewParentClass
+ // or some other 'ifdef' ... construct seems to confuse "moc".
+ {
+ Q_OBJECT
+
+ public:
+ /**
+ * Default constructor.
+ **/
+ KDirTreeView( QWidget * parent = 0 );
+
+ /**
+ * Destructor.
+ **/
+ virtual ~KDirTreeView();
+
+ /**
+ * Locate the counterpart to an original tree item "wanted" somewhere
+ * within this view tree. Returns 0 on failure.
+ * If "lazy" is set, only the open part of the tree is searched.
+ * "doClone" specifies whether or not to (deferred) clone nodes that
+ * are not cloned yet. This is only used if "lazy" is false.
+ **/
+ KDirTreeViewItem * locate( KFileInfo * wanted,
+ bool lazy = true,
+ bool doClone = true );
+
+ /**
+ * Get the first child of this view or 0 if there is none.
+ * Use the child's next() method to get the next child.
+ * Reimplemented from @ref QListView.
+ **/
+ KDirTreeViewItem * firstChild() const
+ { return (KDirTreeViewItem *) KDirTreeViewParentClass::firstChild(); }
+
+ /**
+ * Return the currently selected item or 0, if none is selected.
+ **/
+ KDirTreeViewItem * selection() const { return _selection; }
+
+ /**
+ * Returns the default level until which items are opened by default
+ * (unless they are dot entries).
+ **/
+ int openLevel() const { return _openLevel; }
+
+ /**
+ * Returns true if the view tree is to be cloned lazily, i.e. only
+ * those view tree branches that are really visible are synced with the
+ * original tree.
+ **/
+ bool doLazyClone() const { return _doLazyClone; }
+
+ /**
+ * Enable / disable PacMan animation in this tree view during directory
+ * reading. This is disabled by default since it eats quite some
+ * performance.
+ **/
+ void enablePacManAnimation( bool enable ) { _doPacManAnimation = enable; }
+ /**
+ * Returns true if the PacMan animation is to be used during directory
+ * reading.
+ **/
+ bool doPacManAnimation() const { return _doPacManAnimation; }
+
+ /**
+ * Returns the number of open items in the entire tree.
+ **/
+ int openCount();
+
+ /**
+ * Return the percentage bar fill color for the specified directory
+ * level (0..MaxInt). Wraps around every usedFillColors() colors.
+ **/
+ const QColor & fillColor( int level ) const;
+
+ /**
+ * Very much like @ref fillColor(), but doesn't wrap around at @ref
+ * usedFillColors(), but at KDirTreeViewMaxFillColor.
+ **/
+ const QColor & rawFillColor( int level ) const;
+
+ /**
+ * Set the fill color of percentage bars of the specified directory
+ * level (0..KDirTreeViewMaxFillColor-1).
+ *
+ * Calling repaint() after setting all desired colors is the
+ * caller's responsibility.
+ **/
+ void setFillColor( int level, const QColor &color );
+
+ /**
+ * Set all tree colors to default values.
+ **/
+ void setDefaultFillColors();
+
+ /**
+ * Set the number of used percentage bar fill colors
+ * (1..KDirTreeViewMaxFillColor).
+ **/
+ void setUsedFillColors( int usedFillColors );
+
+ /**
+ * Returns the number of used percentage bar fill colors.
+ **/
+ int usedFillColors() const { return _usedFillColors; }
+
+ /**
+ * Set the tree background color.
+ *
+ * Calling repaint() after setting all desired colors is the
+ * caller's responsibility.
+ **/
+ void setTreeBackground( const QColor &color );
+
+ /**
+ * Returns the tree background color.
+ **/
+ const QColor & treeBackground() const { return _treeBackground; }
+
+ /**
+ * Returns the background color for percentage bars.
+ **/
+ const QColor & percentageBarBackground() const { return _percentageBarBackground; }
+
+ /**
+ * (Try to) ensure good contrast between the tree background and the
+ * percentage bars' 3D edges - prevent ugly 3D effects which will
+ * inevitably be the case for a white background (which unfortunately
+ * is very common): The percentage bars use white and black for 3D
+ * borders - like any other widget. But other widgets normally can
+ * assume their parent widget uses some more neutral color so white and
+ * black will result in at least some minimal contrast.
+ *
+ * This function automagically sets a reasonable default background
+ * color for the tree display: If the current color scheme's document
+ * background color (as used for input fields, lists etc.) is white or
+ * black, use the palette midlight color (the same color as "normal"
+ * widgets like push buttons etc., but brighter). For all other colors
+ * than white, the document background color (the palette base color)
+ * is used.
+ **/
+ void ensureContrast();
+
+ /**
+ * Set the sort column.
+ *
+ * Reimplemented from QListView so we can keep track of the sort column.
+ **/
+ virtual void setSorting( int column, bool increasing = TRUE );
+
+ /**
+ * Returns the internal @ref KDirTree this view works on.
+ * Handle with caution: This might be short-lived information.
+ * The view might choose to create a new tree shortly after returning
+ * this, so don't store this pointer internally.
+ **/
+ KDirTree *tree() { return _tree; }
+
+ int nameCol() const { return _nameCol; }
+ int iconCol() const { return _iconCol; }
+ int percentBarCol() const { return _percentBarCol; }
+ int percentNumCol() const { return _percentNumCol; }
+ int totalSizeCol() const { return _totalSizeCol; }
+ int workingStatusCol() const { return _workingStatusCol; }
+ int ownSizeCol() const { return _ownSizeCol; }
+ int totalItemsCol() const { return _totalItemsCol; }
+ int totalFilesCol() const { return _totalFilesCol; }
+ int totalSubDirsCol() const { return _totalSubDirsCol; }
+ int latestMtimeCol() const { return _latestMtimeCol; }
+ int readJobsCol() const { return _readJobsCol; }
+ int sortCol() const { return _sortCol; }
+
+ QPixmap openDirIcon() const { return _openDirIcon; }
+ QPixmap closedDirIcon() const { return _closedDirIcon; }
+ QPixmap openDotEntryIcon() const { return _openDotEntryIcon; }
+ QPixmap closedDotEntryIcon() const { return _closedDotEntryIcon; }
+ QPixmap unreadableDirIcon() const { return _unreadableDirIcon; }
+ QPixmap mountPointIcon() const { return _mountPointIcon; }
+ QPixmap fileIcon() const { return _fileIcon; }
+ QPixmap symLinkIcon() const { return _symLinkIcon; }
+ QPixmap blockDevIcon() const { return _blockDevIcon; }
+ QPixmap charDevIcon() const { return _charDevIcon; }
+ QPixmap fifoIcon() const { return _fifoIcon; }
+ QPixmap stopIcon() const { return _stopIcon; }
+ QPixmap workingIcon() const { return _workingIcon; }
+ QPixmap readyIcon() const { return _readyIcon; }
+
+
+ /**
+ * Set function name of debug function #i
+ **/
+ void setDebugFunc( int i, const QString & functionName );
+
+ /**
+ * Increase debug counter #i
+ **/
+ void incDebugCount( int i );
+
+
+ public slots:
+
+ /**
+ * Open a directory URL. Assume "file:" protocol unless otherwise specified.
+ **/
+ void openURL( KURL url );
+
+ /**
+ * Refresh (i.e. re-read from disk) the entire tree.
+ **/
+ void refreshAll();
+
+ /**
+ * Refresh (i.e. re-read from disk) the selected subtree.
+ **/
+ void refreshSelected();
+
+ /**
+ * Forcefully stop a running read process.
+ **/
+ void abortReading();
+
+ /**
+ * Clear this view's contents.
+ **/
+ void clear();
+
+ /**
+ * Select a (QListViewItem) item. Triggers selectionChanged() signals.
+ **/
+ void selectItem( QListViewItem *item );
+
+ /**
+ * Select an item. Triggers selectionChanged() signals.
+ * Overloaded for convenience.
+ **/
+ void selectItem( KDirTreeViewItem *item ) { selectItem( (QListViewItem *) item ); }
+
+ /**
+ * Select a KDirTree item. Used for connecting the @ref
+ * KDirTree::selectionChanged() signal.
+ **/
+ void selectItem( KFileInfo *item );
+
+ /**
+ * Clear the current selection. Triggers selectionChanged() signals.
+ **/
+ void clearSelection();
+
+ /**
+ * Close all tree branches except the one specified.
+ **/
+ void closeAllExcept( KDirTreeViewItem *except );
+
+ /**
+ * Send a standardized mail to the owner of the selected branch.
+ * The user will get a mailer window where he can edit that mail all he
+ * likes before deciding to send or discard it.
+ *
+ * The mail includes all currently open branches from the selected
+ * branch on.
+ **/
+ void sendMailToOwner();
+
+ /**
+ * Notification of a change in the KDE palette, i.e. the user selected
+ * and applied different colors in the KDE control center.
+ **/
+ void paletteChanged();
+
+ /**
+ * Read configuration and initialize variables accordingly.
+ * Will be called automatically in the constructor.
+ **/
+ void readConfig();
+
+ /**
+ * Save configuraton.
+ **/
+ void saveConfig() const;
+
+ /**
+ * Emit a @ref userActivity() signal worth 'points' activity points.
+ **/
+ void logActivity( int points );
+
+ /**
+ * Returns the minimum recommended size for this widget.
+ * Reimplemented from QWidget.
+ **/
+ virtual QSize minimumSizeHint() const { return QSize( 0, 0 ); }
+
+
+ protected slots:
+
+ /**
+ * Add a child as a clone of original tree item "newChild" to this view
+ * tree.
+ **/
+ void addChild ( KFileInfo *newChild );
+
+ /**
+ * Delete a cloned child.
+ **/
+ void deleteChild ( KFileInfo *newChild );
+
+ /**
+ * Recursively update the visual representation of the summary fields.
+ * This update is as lazy as possible for optimum performance since it
+ * is called very frequently as a cyclic update.
+ **/
+ void updateSummary();
+
+ /**
+ * Signal end of all read jobs, finalize display and terminate pending
+ * cyclic visual update.
+ **/
+ void slotFinished();
+
+ /**
+ * Signal abortion of all read jobs, finalize display and terminate pending
+ * cyclic visual update.
+ **/
+ void slotAborted();
+
+ /**
+ * Signal end of one read job at this level and finalize display of
+ * this level.
+ **/
+ void finalizeLocal( KDirInfo *dir );
+
+ /**
+ * Display progress information in the status bar. Automatically adds
+ * the elapsed time of a directory scan.
+ **/
+ void sendProgressInfo( const QString & currentDir = "" );
+
+
+#if QT_VERSION < 300
+ /**
+ * "moc" doesnt't seem to handle default arguments well, so this is an
+ * overloaded slot that uses the internally stored current directory.
+ **/
+ void sendProgressInfo();
+#endif
+
+ /**
+ * Set up everything prior to reading: Cyclic update timer, display
+ * busy state, default sorting, stopwatch.
+ **/
+ void prepareReading();
+
+ /**
+ * Change the tree display to "busy" state, i.e. add a column to
+ * display the number of pending read jobs for each level.
+ **/
+ void busyDisplay();
+
+ /**
+ * Change the tree display back to "idle" state, i.e. remove columns
+ * that are useful only while directories are being read, like the
+ * pending read jobs column.
+ **/
+ void idleDisplay();
+
+ /**
+ * Pop up context menu (i.e. emit the contextMenu() signal) or open a
+ * small info popup with exact information, depending on 'column'.
+ **/
+ void popupContextMenu ( QListViewItem * listViewItem,
+ const QPoint & pos,
+ int column );
+
+ /**
+ * Pop up info window with exact byte size.
+ **/
+ void popupContextSizeInfo ( const QPoint & pos,
+ KFileSize size );
+
+ /**
+ * Pop up info window with arbitrary one-line text.
+ **/
+ void popupContextInfo ( const QPoint & pos,
+ const QString & info );
+
+
+ protected slots:
+
+ /**
+ * Notification that a column has just been resized, thus may need
+ * repaining.
+ **/
+ void columnResized( int column, int oldSize, int newSize );
+
+
+ signals:
+
+ /**
+ * Single line progress information, emitted when the read status
+ * changes - typically when a new directory is being read. Connect to a
+ * status bar etc. to keep the user busy.
+ **/
+ void progressInfo( const QString &infoLine );
+
+ /**
+ * Emitted when reading is started.
+ **/
+ void startingReading();
+
+ /**
+ * Emitted when reading this tree is finished.
+ **/
+ void finished();
+
+ /**
+ * Emitted when reading this tree has been aborted.
+ **/
+ void aborted();
+
+ /**
+ * Emitted when the currently selected item changes.
+ * Caution: 'item' may be 0 when the selection is cleared.
+ **/
+ void selectionChanged( KDirTreeViewItem *item );
+
+ /**
+ * Emitted when the currently selected item changes.
+ * Caution: 'item' may be 0 when the selection is cleared.
+ **/
+ void selectionChanged( KFileInfo *item );
+
+ /**
+ * Emitted when a context menu for this item should be opened.
+ * (usually on right click). 'pos' contains the click's mouse
+ * coordinates.
+ *
+ * NOTE:
+ *
+ * This is _not_ the same as @ref QListView::rightButtonClicked():
+ * The context menu may not open on a right click on every column,
+ * usually only in the nameCol().
+ **/
+ void contextMenu( KDirTreeViewItem *item, const QPoint &pos );
+
+ /**
+ * Emitted at user activity. Some interactive actions are assigned an
+ * amount of "activity points" that can be used to judge whether or not
+ * the user is actually using this program or if it's just idly sitting
+ * around on the desktop. This is intended for use together with a @ref
+ * KActivityTracker.
+ **/
+ void userActivity( int points );
+
+
+ protected:
+
+ KDirTree * _tree;
+ QTimer * _updateTimer;
+ QTime _stopWatch;
+ QString _currentDir;
+ KDirTreeViewItem * _selection;
+ QPopupMenu * _contextInfo;
+ int _idContextInfo;
+
+ int _openLevel;
+ bool _doLazyClone;
+ bool _doPacManAnimation;
+ int _updateInterval; // millisec
+ int _usedFillColors;
+ QColor _fillColor [ KDirTreeViewMaxFillColor ];
+ QColor _treeBackground;
+ QColor _percentageBarBackground;
+
+
+ // The various columns in which to display information
+
+ int _nameCol;
+ int _iconCol;
+ int _percentNumCol;
+ int _percentBarCol;
+ int _totalSizeCol;
+ int _workingStatusCol;
+ int _ownSizeCol;
+ int _totalItemsCol;
+ int _totalFilesCol;
+ int _totalSubDirsCol;
+ int _latestMtimeCol;
+ int _readJobsCol;
+ int _sortCol;
+
+ int _debugCount[ DEBUG_COUNTERS ];
+ QString _debugFunc [ DEBUG_COUNTERS ];
+
+
+ // The various icons
+
+ QPixmap _openDirIcon;
+ QPixmap _closedDirIcon;
+ QPixmap _openDotEntryIcon;
+ QPixmap _closedDotEntryIcon;
+ QPixmap _unreadableDirIcon;
+ QPixmap _mountPointIcon;
+ QPixmap _fileIcon;
+ QPixmap _symLinkIcon;
+ QPixmap _blockDevIcon;
+ QPixmap _charDevIcon;
+ QPixmap _fifoIcon;
+ QPixmap _stopIcon;
+ QPixmap _workingIcon;
+ QPixmap _readyIcon;
+ };
+
+
+
+ class KDirTreeViewItem: public QListViewItem
+ {
+ public:
+ /**
+ * Constructor for the root item.
+ **/
+ KDirTreeViewItem ( KDirTreeView * view,
+ KFileInfo * orig );
+
+ /**
+ * Constructor for all other items.
+ **/
+ KDirTreeViewItem ( KDirTreeView * view,
+ KDirTreeViewItem * parent,
+ KFileInfo * orig );
+
+ /**
+ * Destructor.
+ **/
+ virtual ~KDirTreeViewItem();
+
+ /**
+ * Locate the counterpart to an original tree item "wanted" somewhere
+ * within this view tree. Returns 0 on failure.
+ *
+ * When "lazy" is set, only the open part of the tree is searched.
+ * "doClone" specifies whether or not to (deferred) clone nodes that
+ * are not cloned yet. This is only used if "lazy" is false.
+ * "Level" is just a hint for the current tree level for better
+ * performance. It will be calculated automatically if omitted.
+ **/
+ KDirTreeViewItem * locate( KFileInfo * wanted,
+ bool lazy = true,
+ bool doClone = true,
+ int level = -1 );
+
+ /**
+ * Recursively update the visual representation of the summary fields.
+ * This update is as lazy as possible for optimum performance.
+ **/
+ void updateSummary();
+
+ /**
+ * Bring (the top level of) this branch of the view tree in sync with
+ * the original tree. Does _not_ recurse into subdirectories - only
+ * this level of this branch is processed. Called when lazy tree
+ * cloning is in effect and this branch is about to be opened.
+ **/
+ void deferredClone();
+
+
+ /**
+ * Finalize this level - clean up unneeded / undesired dot entries.
+ **/
+ void finalizeLocal();
+
+ /**
+ * Returns the corresponding view.
+ **/
+ KDirTreeView * view() { return _view; }
+
+
+ /**
+ * Returns the parent view item or 0 if this is the root.
+ **/
+ KDirTreeViewItem * parent() { return _parent; }
+
+ /**
+ * Returns the corresponding original item of the "real" (vs. view)
+ * tree where all the important information resides.
+ **/
+ KFileInfo * orig() { return _orig; }
+
+ /**
+ * Returns the first child of this item or 0 if there is none.
+ * Use the child's next() method to get the next child.
+ * Reimplemented from @ref QListViewItem.
+ **/
+ KDirTreeViewItem * firstChild() const
+ { return (KDirTreeViewItem *) QListViewItem::firstChild(); }
+
+ /**
+ * Returns the next sibling of this item or 0 if there is none.
+ * (Kind of) reimplemented from @ref QListViewItem.
+ **/
+ KDirTreeViewItem * next() const
+ { return (KDirTreeViewItem *) QListViewItem::nextSibling(); }
+
+ /**
+ * Comparison function used for sorting the list.
+ *
+ * Using this function is much more efficient than overwriting
+ * QListViewItem::key() which operates on QStrings.
+ *
+ * Returns:
+ * -1 if this < other
+ * 0 if this == other
+ * +1 if this > other
+ *
+ * Reimplemented from QListViewItem
+ **/
+ virtual int compare( QListViewItem * other,
+ int col,
+ bool ascending ) const;
+
+ /**
+ * Perform any necessary pending updates when a branch is opened.
+ * Reimplemented from @ref QListViewItem.
+ **/
+ virtual void setOpen( bool open );
+
+ /**
+ * Notification that a branch in this subtree has been opened or close
+ * somewhere. Don't call this if the state hasn't really changed!
+ **/
+ void openNotify( bool open );
+
+ /**
+ * Recursively open this subtree and all its ancestors up to the root.
+ **/
+ void openSubtree();
+
+ /**
+ * Recursively close all tree branches from here on downwards.
+ **/
+ void closeSubtree();
+
+ /**
+ * Close all tree branches except this one from the root on.
+ **/
+ void closeAllExceptThis();
+
+ /**
+ * Returns the number of open items in this subtree.
+ **/
+ int openCount() const { return _openCount; }
+
+ /**
+ * Recursively return an ASCII representation of all open items from
+ * here on.
+ **/
+ QString asciiDump();
+
+
+ protected:
+
+ /**
+ * Set the appropriate icon depending on this item's type and open /
+ * closed state.
+ **/
+ void setIcon();
+
+ /**
+ * Remove dot entry if it doesn't have any children.
+ * Reparent all of the dot entry's children if there are no
+ * subdirectories on this level.
+ **/
+ void cleanupDotEntries();
+
+ /**
+ * Find this entry's dot entry (clone).
+ *
+ * This doesn't create one if deferred cloning is in effect (which is
+ * not a drawback since cloning directory nodes create a dot entry
+ * clone along with the directory clone).
+ *
+ * Returns 0 if there is no dot entry clone.
+ **/
+ KDirTreeViewItem * findDotEntry() const;
+
+
+ /**
+ * Paint method. Reimplemented from @ref QListViewItem so different
+ * colors can be used - and of course for painting percentage bars.
+ *
+ * Reimplemented from @ref QListViewItem.
+ **/
+ virtual void paintCell ( QPainter * painter,
+ const QColorGroup & colorGroup,
+ int column,
+ int width,
+ int alignment );
+
+ /**
+ * Paint a percentage bar into a @ref QListViewItem cell.
+ * 'width' is the width of the entire cell.
+ * 'indent' is the number of pixels to indent the bar.
+ **/
+ void paintPercentageBar ( float percent,
+ QPainter * painter,
+ int indent,
+ int width,
+ const QColor & fillColor,
+ const QColor & barBackground );
+
+ /**
+ * Generic comparison function.
+ **/
+ template<typename T> inline
+ int compare( T a, T b ) const
+ {
+ if ( a < b ) return -1;
+ if ( a > b ) return 1;
+ return 0;
+ }
+
+ private:
+
+ /**
+ * Initializations common to all constructors.
+ **/
+ void init ( KDirTreeView * view,
+ KDirTreeViewItem * parent,
+ KFileInfo * orig );
+
+ protected:
+
+ // Data members
+
+ KDirTreeView * _view;
+ KDirTreeViewItem * _parent;
+ KFileInfo * _orig;
+ KPacManAnimation * _pacMan;
+ float _percent;
+ int _openCount;
+
+ };
+
+
+ inline kdbgstream & operator<< ( kdbgstream & stream, KDirTreeViewItem * item )
+ {
+ if ( item )
+ {
+ if ( item->orig() )
+ {
+ stream << item->orig()->debugUrl();
+ }
+ else
+ {
+ stream << "<NULL orig()> " << endl;
+ }
+ }
+ else
+ stream << "<NULL>";
+
+ return stream;
+ }
+
+
+
+ //----------------------------------------------------------------------
+ // Static Functions
+ //----------------------------------------------------------------------
+
+ /**
+ * Format a file size with all digits, yet human readable using the current
+ * locale's thousand separator, i.e. 12,345,678 rather than 12345678
+ **/
+ QString formatSizeLong( KFileSize size );
+
+ /**
+ * Format a file size for use within a QListView::key() function:
+ * Right-justify and fill with leading zeroes.
+ **/
+ QString hexKey( KFileSize size );
+
+ /**
+ * Format a millisecond granularity time human readable.
+ * Milliseconds will only be inluded if 'showMilliSeconds' is true.
+ **/
+ QString formatTime ( long millisec,
+ bool showMilliSeconds = false );
+
+ /**
+ * Format counters of any kind.
+ *
+ * Returns an empty string if 'suppressZero' is 'true' and the value of
+ * 'count' is 0.
+ **/
+ QString formatCount( int count, bool suppressZero = false );
+
+ /**
+ * Format percentages.
+ **/
+ QString formatPercent( float percent );
+
+ /**
+ * Format time and date human-readable as "yyyy-mm-dd hh:mm:ss"
+ * - unlike that ctime() crap that is really useless.
+ * See the source for more about why this format.
+ **/
+ QString formatTimeDate( time_t rawTime );
+
+ /**
+ * Format time and date according to the current locale for those who
+ * really must have that brain-dead ctime() format.
+ **/
+ QString localeTimeDate( time_t rawTime );
+
+ /**
+ * Return a color that contrasts to 'contrastColor'.
+ **/
+ QColor contrastingColor ( const QColor &desiredColor,
+ const QColor &contrastColor );
+
+} // namespace KDirStat
+
+
+#endif // ifndef KDirTreeView_h
+
+
+// EOF
diff --git a/kdirstat/kfeedback.cpp b/kdirstat/kfeedback.cpp
new file mode 100644
index 0000000..9730d89
--- /dev/null
+++ b/kdirstat/kfeedback.cpp
@@ -0,0 +1,503 @@
+
+/*
+ * File name: kfeedback.cpp
+ * Summary: User feedback form
+ * License: LGPL - See file COPYING.LIB for details.
+ * Author: Stefan Hundhammer <sh@suse.de>
+ *
+ * Updated: 2004-11-23
+ */
+
+
+#include <qheader.h>
+#include <qlayout.h>
+#include <qlabel.h>
+#include <qmultilineedit.h>
+#include <qhbox.h>
+
+#include <kglobal.h>
+#include <kapp.h>
+#include <klocale.h>
+#include <kdebug.h>
+#include <kaboutdata.h>
+#include <kiconloader.h>
+#include <kurl.h>
+
+#include "kfeedback.h"
+
+
+KFeedbackDialog::KFeedbackDialog( const QString & feedbackMailAddress,
+ const QString & helpTopic )
+ : KDialogBase( Plain, // dialogFace
+ i18n( "Feedback" ), // caption
+ Apply | Cancel
+ | ( helpTopic.isEmpty() ? 0 : (int) Help ), // buttonMask
+ Apply ) // defaultButton
+{
+ QVBoxLayout * layout = new QVBoxLayout( plainPage(), 0, spacingHint() );
+ setButtonApply( KGuiItem( i18n( "&Mail this..." ) ) );
+
+ if ( ! helpTopic.isEmpty() )
+ setHelp( helpTopic );
+
+ _form = new KFeedbackForm( feedbackMailAddress, plainPage() );
+ CHECK_PTR( _form );
+
+ layout->addWidget( _form );
+ checkSendButton();
+
+ connect( this, SIGNAL( applyClicked() ),
+ _form, SLOT ( sendMail() ) );
+
+ connect( _form, SIGNAL( mailSent() ),
+ this, SLOT ( hide() ) );
+
+ connect( _form, SIGNAL( mailSent() ),
+ this, SIGNAL( mailSent() ) );
+
+ connect( _form, SIGNAL( checkComplete() ),
+ this, SLOT ( checkSendButton() ) );
+}
+
+
+KFeedbackDialog::~KFeedbackDialog()
+{
+ // NOP
+}
+
+
+void
+KFeedbackDialog::checkSendButton()
+{
+ enableButtonApply( _form->readyToSend() );
+}
+
+
+
+
+
+KFeedbackForm::KFeedbackForm( const QString & feedbackMailAddress,
+ QWidget * parent )
+ : QVBox( parent )
+ , _feedbackMailAddress( feedbackMailAddress )
+{
+ //
+ // Explanation above the question list
+ //
+
+ QLabel * label = new QLabel( i18n( "<p><b>Please tell us your opinion about this program.</b></p>"
+ "<p>You will be able to review everything in your mailer "
+ "before any mail is sent.<br>"
+ "Nothing will be sent behind your back.</p>"
+ ), this );
+ //
+ // Question list
+ //
+
+ _questionList = new KFeedbackQuestionList( this );
+ CHECK_PTR( _questionList );
+
+ connect( _questionList, SIGNAL( checkComplete() ),
+ this, SLOT ( slotCheckComplete() ) );
+
+
+ //
+ // Explanation below the question list
+ //
+
+ QHBox * hbox = new QHBox( this );
+ CHECK_PTR( hbox );
+
+ QSizePolicy pol( QSizePolicy::Fixed, QSizePolicy::Fixed ); // hor / vert
+
+ label = new QLabel( i18n( "Questions marked with " ), hbox );
+ CHECK_PTR( label );
+ label->setSizePolicy( pol );
+
+ label = new QLabel( hbox );
+ CHECK_PTR( label );
+ label->setPixmap( KGlobal::iconLoader()->loadIcon( "edit", KIcon::Small ) );
+ label->setSizePolicy( pol );
+
+ label = new QLabel( i18n( " must be answered before a mail can be sent.") , hbox );
+ CHECK_PTR( label );
+ label->setSizePolicy( pol );
+
+ new QWidget( hbox ); // Fill any leftover space to the right.
+
+
+ //
+ // Free-text comment field
+ //
+
+ label = new QLabel( "\n" + i18n( "&Additional Comments:" ), this ); CHECK_PTR( label );
+ _comment = new QMultiLineEdit( this ); CHECK_PTR( _comment );
+
+ label->setBuddy( _comment );
+#if (QT_VERSION < 300)
+ _comment->setFixedVisibleLines( 5 );
+#endif
+ _comment->setWordWrap( QMultiLineEdit::FixedColumnWidth );
+ _comment->setWrapColumnOrWidth( 70 );
+}
+
+
+KFeedbackForm::~KFeedbackForm()
+{
+ // NOP
+}
+
+
+void
+KFeedbackForm::sendMail()
+{
+ //
+ // Build mail subject
+ //
+
+ QString subject;
+
+ const KAboutData * aboutData = KGlobal::instance()->aboutData();
+
+ if ( aboutData )
+ subject = aboutData->programName() + "-" + aboutData->version();
+ else
+ subject = kapp->name();
+
+ subject = "[kde-feedback] " + subject + " user feedback";
+
+
+ //
+ // Build mail body
+ //
+
+ QString body = subject + "\n\n"
+ + formatComment()
+ + _questionList->result();
+
+
+ //
+ // Build "mailto:" URL from all this
+ //
+
+ KURL mail;
+ mail.setProtocol( "mailto" );
+ mail.setPath( _feedbackMailAddress );
+ 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.
+
+
+ //
+ // Actually send mail
+ //
+
+ kapp->invokeMailer( mail );
+
+ emit mailSent();
+}
+
+
+void
+KFeedbackForm::slotCheckComplete()
+{
+ emit checkComplete();
+}
+
+
+QString
+KFeedbackForm::formatComment()
+{
+ QString result = _comment->text();
+
+ if ( ! result.isEmpty() )
+ {
+ result = "<comment>\n" + result + "\n</comment>\n\n";
+ }
+
+ return result;
+}
+
+
+bool
+KFeedbackForm::readyToSend()
+{
+ return _questionList->isComplete();
+}
+
+
+
+
+
+
+KFeedbackQuestionList::KFeedbackQuestionList( QWidget *parent )
+ : QListView( parent )
+{
+ addColumn( "" );
+ header()->hide();
+}
+
+
+KFeedbackQuestionList::~KFeedbackQuestionList()
+{
+ // NOP
+}
+
+
+bool
+KFeedbackQuestionList::isComplete()
+{
+ KFeedbackQuestion * question = firstQuestion();
+
+ while ( question )
+ {
+ if ( question->isRequired() && ! question->isAnswered() )
+ return false;
+
+ question = question->nextQuestion();
+ }
+
+ return true;
+}
+
+
+QString
+KFeedbackQuestionList::result()
+{
+ QString res;
+ KFeedbackQuestion * question = firstQuestion();
+
+ while ( question )
+ {
+ res += question->result();
+
+ question = question->nextQuestion();
+ }
+
+ return res;
+}
+
+
+KFeedbackQuestion *
+KFeedbackQuestionList::addQuestion( const QString & text,
+ const QString & id,
+ bool exclusiveAnswer,
+ bool required )
+{
+ KFeedbackQuestion * question = new KFeedbackQuestion( this, text, id,
+ exclusiveAnswer,
+ required );
+ CHECK_PTR( question );
+
+ return question;
+}
+
+
+void
+KFeedbackQuestionList::addYesNoQuestion( const QString & text,
+ const QString & id,
+ bool required )
+{
+
+ KFeedbackQuestion * question = new KFeedbackQuestion( this, text, id,
+ true, // exclusive
+ required );
+ CHECK_PTR( question );
+ question->addAnswer( i18n( "yes" ), "yes" );
+ question->addAnswer( i18n( "no" ), "no" );
+}
+
+
+void
+KFeedbackQuestionList::questionAnswered()
+{
+ emit checkComplete();
+}
+
+void
+KFeedbackQuestionList::questionAdded( KFeedbackQuestion * question)
+{
+ if ( question->isRequired() )
+ emit checkComplete();
+}
+
+
+
+
+
+static int nextNo = 0;
+
+KFeedbackQuestion::KFeedbackQuestion( KFeedbackQuestionList * parent,
+ const QString & text,
+ const QString & id,
+ bool exclusiveAnswer,
+ bool required,
+ bool open )
+ : QCheckListItem( parent, text )
+ , _id( id )
+ , _exclusiveAnswer( exclusiveAnswer )
+ , _required( required )
+{
+ if ( required )
+ {
+ setPixmap( 0, KGlobal::iconLoader()->loadIcon( "edit", KIcon::Small ) );
+ }
+
+ setOpen( open );
+ _no = nextNo++;
+
+ parent->questionAdded( this );
+}
+
+
+void
+KFeedbackQuestion::addAnswer( const QString & text,
+ const QString & id )
+{
+ new KFeedbackAnswer( this, text, id, _exclusiveAnswer );
+}
+
+
+bool
+KFeedbackQuestion::isAnswered()
+{
+ if ( ! _exclusiveAnswer )
+ {
+ /**
+ * If any number of answers is permitted for this question, this
+ * question is always considered to be answered.
+ **/
+
+ return true;
+ }
+
+
+ /**
+ * If this question requires an exclusive answer, exactly one of them
+ * should be checked. We don't need to bother about more than one being
+ * checked here - QListView does that for us.
+ **/
+
+ KFeedbackAnswer *answer = firstAnswer();
+
+ while ( answer )
+ {
+ if ( answer->isChecked() )
+ return true;
+
+ answer = answer->nextAnswer();
+ }
+
+ return false;
+}
+
+
+QString
+KFeedbackQuestion::result()
+{
+ QString res;
+ int answers = 0;
+
+ KFeedbackAnswer *answer = firstAnswer();
+
+ while ( answer )
+ {
+ if ( answer->isChecked() )
+ {
+ res += _id + "=\"" + answer->id() + "\"\n";
+ answers++;
+ }
+
+ answer = answer->nextAnswer();
+ }
+
+ if ( answers > 1 )
+ {
+ res = "\n" + res + "\n";
+ }
+
+ return res;
+}
+
+
+QString
+KFeedbackQuestion::text()
+{
+ return QCheckListItem::text(0);
+}
+
+
+QString
+KFeedbackQuestion::key( int, bool ) const
+{
+ QString no;
+ no.sprintf( "%08d", _no );
+
+ return no;
+}
+
+
+KFeedbackQuestionList *
+KFeedbackQuestion::questionList() const
+{
+ return dynamic_cast<KFeedbackQuestionList *>( listView() );
+}
+
+
+
+
+
+
+
+KFeedbackAnswer::KFeedbackAnswer( KFeedbackQuestion * parent,
+ const QString & text,
+ const QString & id,
+ bool exclusive )
+ : QCheckListItem( parent,
+ text,
+ exclusive
+ ? QCheckListItem::RadioButton
+ : QCheckListItem::CheckBox )
+ , _id( id )
+{
+ _no = nextNo++;
+}
+
+
+QString
+KFeedbackAnswer::text()
+{
+ return QCheckListItem::text(0);
+}
+
+
+QString
+KFeedbackAnswer::key( int, bool ) const
+{
+ QString no;
+ no.sprintf( "%08d", _no );
+
+ return no;
+}
+
+
+void
+KFeedbackAnswer::stateChange( bool newState )
+{
+ if ( newState && question()->isRequired() )
+ {
+ KFeedbackQuestionList * list = question()->questionList();
+
+ if ( list )
+ list->questionAnswered();
+ }
+}
+
+
+
+// EOF
diff --git a/kdirstat/kfeedback.h b/kdirstat/kfeedback.h
new file mode 100644
index 0000000..3d1978b
--- /dev/null
+++ b/kdirstat/kfeedback.h
@@ -0,0 +1,460 @@
+/*
+ * File name: kfeedback.h
+ * Summary: User feedback form and mailing utilities
+ * License: LGPL - See file COPYING.LIB for details.
+ * Author: Stefan Hundhammer <sh@suse.de>
+ *
+ * Updated: 2003-01-07
+ */
+
+
+#ifndef KFeedback_h
+#define KFeedback_h
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <qlistview.h>
+#include <qvbox.h>
+#include <kdialogbase.h>
+
+
+#ifndef NOT_USED
+# define NOT_USED(PARAM) ( (void) (PARAM) )
+#endif
+
+
+class KFeedbackForm;
+class KFeedbackQuestionList;
+class KFeedbackQuestion;
+class KFeedbackAnswer;
+class QMultiLineEdit;
+
+
+/**
+ * Dialog containing a @ref KFeedbackForm and all the infrastructure for
+ * sending a mail etc.
+ **/
+class KFeedbackDialog: public KDialogBase
+{
+ Q_OBJECT
+
+public:
+
+ /**
+ * Constructor.
+ **/
+ KFeedbackDialog( const QString & feedbackMailAddress,
+ const QString & helpTopic = QString::null );
+
+
+ /**
+ * Destructor.
+ **/
+ virtual ~KFeedbackDialog();
+
+
+ /**
+ * Returns the internal @KFeedbackForm
+ **/
+ KFeedbackForm *form() { return _form; }
+
+
+public slots:
+
+ /**
+ * Check if sufficient information is available to send a mail now and
+ * enable / disable the "send mail" button accordingly.
+ **/
+ void checkSendButton();
+
+
+signals:
+ /**
+ * Emitted when the user has sent the feedback mail - i.e. when he clicked
+ * on the "Send mail" button and the mail has successfully been forwarded
+ * to the mailer. He can still choose not to send the mail from within the
+ * mailer, though.
+ **/
+ void mailSent();
+
+
+protected:
+
+ KFeedbackForm * _form;
+};
+
+
+/**
+ * User feedback form:
+ *
+ * User is asked a list of questions, the answers of which will be sent via
+ * mail back to a feedback mail address.
+ **/
+class KFeedbackForm: public QVBox
+{
+ Q_OBJECT
+
+public:
+ /**
+ * Constructor.
+ **/
+ KFeedbackForm( const QString & feedbackMailAddress,
+ QWidget * parent );
+
+ /**
+ * Destructor.
+ **/
+ virtual ~KFeedbackForm();
+
+
+public slots:
+
+ /**
+ * Compose a mail from the user's answers and send it to the feedback mail
+ * address passed to the constructor.
+ *
+ * This method will check with @ref readyToSend() if the mail can be sent
+ * with the questions answered until now and prompt the user to answer more
+ * questions if not.
+ *
+ * Connect the @ref mailSent() signal if you are interested when exactly
+ * all this was successful.
+ **/
+ virtual void sendMail();
+
+
+public:
+
+ /**
+ * Checks if the mail is ready to send, i.e. if all required fields are
+ * filled.
+ **/
+ virtual bool readyToSend();
+
+ /**
+ * Returns the @ref KFeedbackQuestionList .
+ **/
+ KFeedbackQuestionList * questionList() { return _questionList; }
+
+
+signals:
+ /**
+ * Emitted when the user has sent the feedback mail - i.e. when he clicked
+ * on the "Send mail" button and the mail has successfully been forwarded
+ * to the mailer. He can still choose not to send the mail from within the
+ * mailer, though.
+ **/
+ void mailSent();
+
+ /**
+ * Emitted when it is time to check for completeness of all information in
+ * this form: Either when a new question is added or when a question is
+ * answered.
+ **/
+ void checkComplete();
+
+
+protected slots:
+ /**
+ * Check for completeness of this form.
+ **/
+ void slotCheckComplete();
+
+
+protected:
+
+ /**
+ * Format the "personal comments" field for sending mail.
+ **/
+ QString formatComment();
+
+
+ QString _feedbackMailAddress;
+ KFeedbackQuestionList * _questionList;
+ QMultiLineEdit * _comment;
+};
+
+
+
+/**
+ * List of feedback questions presented in a @ref QListView widget.
+ **/
+class KFeedbackQuestionList: public QListView
+{
+ Q_OBJECT
+
+public:
+
+ /**
+ * Constructor.
+ **/
+ KFeedbackQuestionList( QWidget *parent );
+
+ /**
+ * Destructor.
+ **/
+ virtual ~KFeedbackQuestionList();
+
+ /**
+ * Returns whether or not this question list is answered satisfactorily,
+ * i.e. if all questions marked as "required" are answered.
+ **/
+ virtual bool isComplete();
+
+ /**
+ * The result of all answered questions in ASCII.
+ **/
+ QString result();
+
+ /**
+ * Add a yes/no question to the list.
+ *
+ * 'text' is the text the user will see (in his native language).
+ *
+ * 'id' is what will be sent with the feedback mail, thus it should be
+ * unique within the application, yet human readable (preferably English)
+ * and not contain any weird characters that might confuse scripts that are
+ * later used to automatically parse those mails.
+ * Examples: "would_recommend_to_a_friend"
+ *
+ * Set 'required' to 'true' if answering this question is required to
+ * successfully complete this form.
+ *
+ * Returns a pointer to this question so you can add answers.
+ **/
+
+ KFeedbackQuestion * addQuestion( const QString & text,
+ const QString & id,
+ bool exclusiveAnswer = true,
+ bool required = false );
+
+ /**
+ * Add a yes/no question to the list.
+ **/
+ void addYesNoQuestion( const QString & text,
+ const QString & id,
+ bool required = false );
+
+ /**
+ * Returns the first question of that list.
+ * Use @ref KFeedbackQuestion::next() to get the next one.
+ **/
+ KFeedbackQuestion * firstQuestion() const
+ { return (KFeedbackQuestion *) QListView::firstChild(); }
+
+ /**
+ * Notify the list that another question has been answered.
+ * Emits the @ref checkComplete() signal when all required questions are
+ * answered.
+ **/
+ void questionAnswered();
+
+ /**
+ * Notify the list that another question has been added.
+ * Emits the @ref checkComplete() signal when a required question is
+ * added.
+ **/
+ void questionAdded( KFeedbackQuestion * question );
+
+signals:
+ /**
+ * Emitted when all required questions are answered.
+ **/
+ void checkComplete();
+};
+
+
+/**
+ * A user feedback question to be inserted into a @ref KFeedbackQuestionList.
+ **/
+class KFeedbackQuestion: public QCheckListItem
+{
+public:
+
+ /**
+ * Constructor.
+ *
+ * The parent @ref KFeedbackQuestionList assumes ownership of this object,
+ * so don't delete it unless you want to delete it from the question list
+ * as well.
+ *
+ * 'text' is the text the user will see (in his native language).
+ *
+ * 'id' is what will be sent with the feedback mail, thus it should be
+ * unique within the application, yet human readable (preferably English)
+ * and not contain any weird characters that might confuse scripts that are
+ * later used to automatically parse those mails.
+ * Examples: "features_not_liked", "stability"
+ *
+ * Set 'required' to 'true' if answering this question is required to
+ * successfully complete this form.
+ *
+ * Set 'exclusiveAnswer' to 'true' if only one of all answers may be
+ * checked at any one time, to 'false' if multiple answers are allowed.
+ **/
+ KFeedbackQuestion( KFeedbackQuestionList * parent,
+ const QString & text,
+ const QString & id,
+ bool exclusiveAnswer = true,
+ bool required = false,
+ bool open = true );
+
+ /**
+ * Add an answer to this question. Again, 'text' is what the user will see
+ * (translated to his native language), 'id' is what you will get back with
+ * the mail. The answer IDs need only be unique for that question; answers
+ * to other questions may have the same ID.
+ **/
+ void addAnswer( const QString & text,
+ const QString & id );
+
+ /**
+ * Returns if answering this question is required.
+ **/
+ bool isRequired() { return _required; }
+
+ /**
+ * Returns if this question is answered satisfactorily.
+ **/
+ bool isAnswered();
+
+ /**
+ * The result of this question in ASCII, e.g.
+ * recommend="yes"
+ * or
+ * features_i_like="builtin_tetris"
+ * features_i_like="pink_elephant"
+ * features_i_like="studlycapslyfier"
+ **/
+ QString result();
+
+ /**
+ * Return this question's ID.
+ **/
+ QString id() { return _id; }
+
+ /**
+ * Return this question's text.
+ **/
+ QString text();
+
+ /**
+ * Returns whether or not this question requires an exclusive answer.
+ **/
+ bool exclusiveAnswer() { return _exclusiveAnswer; }
+
+
+ /**
+ * Returns the sort key.
+ *
+ * Reimplemented from @ref QListViewItem to maintain insertion order.
+ **/
+ virtual QString key( int column, bool ascending ) const;
+
+ /**
+ * Returns the next question or 0 if there is no more.
+ **/
+ KFeedbackQuestion * nextQuestion() const
+ { return (KFeedbackQuestion *) QListViewItem::nextSibling(); }
+
+ /**
+ * Returns the first possible answer to this question.
+ * Use @ref KFeedbackAnswer::nextAnswer() to get the next one.
+ **/
+ KFeedbackAnswer * firstAnswer() const
+ { return (KFeedbackAnswer *) QListViewItem::firstChild(); }
+
+ /**
+ * Returns the @ref KFeedbackQuestionList this question belongs to or 0 if
+ * the parent is no @ref KFeedbackQuestionList.
+ **/
+ KFeedbackQuestionList * questionList() const;
+
+
+protected:
+
+ QString _id;
+ bool _exclusiveAnswer;
+ bool _required;
+ int _no;
+};
+
+
+class KFeedbackAnswer: public QCheckListItem
+{
+public:
+ /**
+ * Constructor.
+ *
+ * 'exclusive' tells the type of answer: One of many allowed or any number
+ * of many.
+ **/
+ KFeedbackAnswer( KFeedbackQuestion * parent,
+ const QString & text,
+ const QString & id,
+ bool exclusive = true );
+
+ /**
+ * Return this answer's ID.
+ **/
+ QString id() { return _id; }
+
+ /**
+ * Return this answer's text.
+ **/
+ QString text();
+
+ /**
+ * Returns whether or not this is an exclusive answer.
+ **/
+ bool isExclusive() { return _exclusive; }
+
+ /**
+ * Returns whether or not this answer is checked.
+ **/
+ bool isChecked() { return QCheckListItem::isOn(); }
+
+ /**
+ * Returns the next possible answer or 0 if there is no more.
+ **/
+ KFeedbackAnswer * nextAnswer() const
+ { return (KFeedbackAnswer *) QListViewItem::nextSibling(); }
+
+ /**
+ * Returns the question to this answer.
+ **/
+ KFeedbackQuestion * question() const
+ { return (KFeedbackQuestion *) QListViewItem::parent(); }
+
+ /**
+ * Returns the sort key.
+ *
+ * Reimplemented from @ref QListViewItem to maintain insertion order.
+ **/
+ virtual QString key( int column, bool ascending ) const;
+
+
+ /**
+ * On/off change.
+ *
+ * Reimplemented from @ref QCheckListItem to monitor answering required
+ * questions. This method notifies the @ref KFeedbackQuestionList whenever
+ * a required question is being answered.
+ **/
+ virtual void stateChange( bool newState );
+
+protected:
+
+ QString _id;
+ bool _exclusive;
+ int _no;
+};
+
+
+
+#endif // KFeedback_h
+
+
+// EOF
diff --git a/kdirstat/kpacman.cpp b/kdirstat/kpacman.cpp
new file mode 100644
index 0000000..55a61d4
--- /dev/null
+++ b/kdirstat/kpacman.cpp
@@ -0,0 +1,311 @@
+
+/*
+ * File name: kpacman.cpp
+ * Summary: PacMan animation
+ * License: LGPL - See file COPYING.LIB for details.
+ * Author: Stefan Hundhammer <sh@suse.de>
+ *
+ * Updated: 2004-03-29
+ */
+
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <qtimer.h>
+#include <qpixmap.h>
+#include <kdebug.h>
+
+#include "kpacman.h"
+
+
+KPacManAnimation::KPacManAnimation( QWidget * widget,
+ int size,
+ bool randomStart )
+{
+ _widget = widget;
+ _size = size;
+ _randomStart = randomStart;
+ _brush = QBrush( Qt::yellow );
+ _pos = 0;
+ _speed = 4;
+ _interval = 100;
+
+ _minMouth = 10;
+ _maxMouth = 70;
+ _mouthInc = ( _maxMouth - _minMouth ) / 3;
+ _mouth = _minMouth;
+ _pacManRect = QRect( 0, 0, 0, 0 );
+
+ restart();
+}
+
+
+KPacManAnimation::~KPacManAnimation()
+{
+}
+
+
+void
+KPacManAnimation::restart()
+{
+ _justStarted = true;
+
+ if ( _randomStart )
+ {
+ _goingRight = ( rand() > ( RAND_MAX / 2 ) );
+
+ // Initial _pos is set in animate() since the width (upon which it
+ // depends) is still unknown here.
+ }
+ else
+ {
+ _goingRight = true;
+ _pos = 0;
+ }
+
+ // Care for initial display
+ _time = _time.addMSecs( _interval + 1 );
+}
+
+
+void
+KPacManAnimation::animate( QPainter * painter,
+ QRect rect )
+{
+ if ( _time.elapsed() < _interval )
+ return;
+
+ _time.restart();
+
+
+ // Make PacMan fit into height
+
+ int size = _size <= rect.height() ? _size : rect.height();
+
+ if ( rect.width() < size ) // No space to animate in?
+ return; // -> forget it!
+
+
+ if ( _justStarted )
+ {
+ _justStarted = false;
+
+ if ( _pacManRect.width() > 0 )
+ painter->eraseRect( _pacManRect );
+
+ if ( _randomStart )
+ {
+ // Set random initial position
+ // - this depends on the width which is unknown in the constructor.
+
+ _pos = (int) ( (rect.width() - size ) * ( (double) rand() / RAND_MAX) );
+ }
+ }
+ else // ! _justStarted
+ {
+ // Erase last PacMan
+
+ if ( ! _goingRight )
+ _pacManRect.setX( _pacManRect.x() + _pacManRect.width() - _speed );
+
+ _pacManRect.setWidth( _speed );
+ painter->eraseRect( _pacManRect );
+ }
+
+
+ if ( _pos + size > rect.width() ) // Right edge reached?
+ {
+ // Notice: This can also happen when the rectangle is resized - i.e. it
+ // really makes sense to do that right here rather than at the end of
+ // this function!
+
+ // Turn left
+
+ _pos = rect.width() - size;
+ _goingRight = false;
+ _mouth = _minMouth;
+ }
+ else if ( _pos < 0 ) // Left edge reached?
+ {
+ // Turn right
+
+ _pos = 0;
+ _goingRight = true;
+ _mouth = _minMouth;
+ }
+
+
+ // Draw PacMan (double-buffered)
+
+ _pacManRect = QRect( 0, 0, size, size );
+ QPixmap pixmap( size, size );
+ pixmap.fill( painter->backgroundColor() );
+ QPainter p( &pixmap, _widget );
+ p.setBrush( _brush );
+
+ if ( _goingRight )
+ {
+ p.drawPie( _pacManRect,
+ _mouth * 16, // arc (1/16 degrees)
+ ( 360 - 2 * _mouth ) * 16 ); // arc lenght (1/16 degrees)
+ }
+ else
+ {
+ p.drawPie( _pacManRect,
+ ( 180 + _mouth ) * 16, // arc (1/16 degrees)
+ ( 360 - 2 * _mouth ) * 16 ); // arc lenght (1/16 degrees)
+ }
+
+ _pacManRect = QRect( rect.x() + _pos, // x
+ ( rect.height() - size ) / 2, // y
+ size, size ); // width, height
+
+ // Transfer pixmap into widget
+
+#if 0
+ QPoint offset = painter->worldMatrix().map( _pacManRect.topLeft() );
+ // kdDebug() << "bitBlt() to " << offset.x() << ", " << offset.y() << endl;
+ bitBlt( _widget, offset, &pixmap );
+#endif
+
+ painter->drawPixmap( _pacManRect.topLeft(), pixmap );
+
+
+ // Animate mouth for next turn
+
+ _mouth += _mouthInc;
+
+ if ( _mouth >= _maxMouth ) // max open reached
+ {
+ _mouth = _maxMouth;
+ _mouthInc = -_mouthInc; // reverse direction
+ }
+ else if ( _mouth <= _minMouth ) // min open reached
+ {
+ _mouth = _minMouth;
+ _mouthInc = -_mouthInc; // reverse direction
+ }
+
+
+ // Advance position for next turn
+
+ if ( _goingRight )
+ _pos += _speed;
+ else
+ _pos -= _speed;
+}
+
+
+
+
+
+
+KPacMan::KPacMan( QWidget * parent,
+ int pacManSize,
+ bool randomStart,
+ const char * widgetName )
+ : QWidget( parent, widgetName )
+{
+ _pacManSize = pacManSize;
+ _pacMan = new KPacManAnimation( this, _pacManSize, randomStart );
+ _timer = 0;
+ _interval = 100; // millisec
+ _active = false;
+ _painter = new QPainter( this );
+ _margin = 1;
+}
+
+
+KPacMan::~KPacMan()
+{
+ if ( _painter )
+ delete _painter;
+
+ if ( _pacMan )
+ delete _pacMan;
+}
+
+
+void
+KPacMan::start()
+{
+ if ( ! _timer )
+ {
+ _timer = new QTimer( this );
+ }
+
+ _pacMan->restart();
+
+ if ( _timer )
+ {
+ _active = true;
+ _timer->start( _interval );
+ connect( _timer, SIGNAL( timeout() ),
+ this, SLOT ( animate() ) );
+ }
+}
+
+
+void
+KPacMan::stop()
+{
+ _active = false;
+
+ if ( _timer )
+ _timer->stop();
+
+ repaint();
+}
+
+
+void
+KPacMan::animate()
+{
+ repaint( false );
+}
+
+
+void
+KPacMan::setInterval( int intervalMilliSec )
+{
+ _interval = intervalMilliSec;
+ _pacMan->setInterval( _interval );
+
+ if ( _timer )
+ _timer->changeInterval( _interval );
+}
+
+
+void
+KPacMan::paintEvent( QPaintEvent *ev )
+{
+ QWidget::paintEvent( ev );
+
+ if ( _active )
+ {
+ _pacMan->animate( _painter, QRect( _margin, 0, width() - _margin, height() ) );
+ }
+}
+
+
+void
+KPacMan::mouseReleaseEvent ( QMouseEvent *ev )
+{
+ if ( _active )
+ {
+ if ( _pacMan->lastPacMan().contains( ev->pos() ) )
+ stop();
+ }
+}
+
+
+QSize
+KPacMan::sizeHint() const
+{
+ return QSize( 16 * _pacManSize, // width - admittedly somewhat random
+ _pacManSize + 2 * _margin ); // height
+}
+
+
+
+// EOF
diff --git a/kdirstat/kpacman.h b/kdirstat/kpacman.h
new file mode 100644
index 0000000..02d4ce1
--- /dev/null
+++ b/kdirstat/kpacman.h
@@ -0,0 +1,265 @@
+/*
+ * File name: kpacman.h
+ * Summary: PacMan animation inside widgets
+ * License: LGPL - See file COPYING.LIB for details.
+ * Author: Stefan Hundhammer <sh@suse.de>
+ *
+ * Updated: 2004-03-29
+ */
+
+
+#ifndef KPacMan_h
+#define KPacMan_h
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <qwidget.h>
+#include <qpainter.h>
+#include <qcolor.h>
+#include <qdatetime.h>
+
+
+#ifndef NOT_USED
+# define NOT_USED(PARAM) ( (void) (PARAM) )
+#endif
+
+class QTimer;
+
+
+/**
+ * Helper class to display a PacMan animation inside a widget.
+ * Note that this is not a widget itself, it needs to be placed inside a widget
+ * - which fact makes it suitable for use inside non-widget objects such as
+ * @ref QListViewItem.
+ *
+ * If you are looking for a widget that can do all that self-contained, see
+ * @ref KPacMan.
+ *
+ * @short PacMan animation
+ **/
+class KPacManAnimation
+{
+public:
+ /**
+ * Constructor.
+ *
+ * Create a PacMan sprite in 'widget' of 'size' pixels diameter. Start at
+ * a random position and move in random direction if 'randomStart' is true.
+ **/
+ KPacManAnimation( QWidget * widget,
+ int size,
+ bool randomStart );
+
+ /**
+ * Destructor.
+ **/
+ virtual ~KPacManAnimation();
+
+ /**
+ * Animate PacMan inside this rectangle.
+ * Call this frequently enough (e.g. by a timer) to get fluid motion.
+ * Set up the painter prior to calling this; the entire rectangle will be
+ * cleared with the current brush, and PacMan's outline will be drawn with
+ * the current pen.
+ *
+ * PacMan moves from the left side of this rectangle to the right, turning
+ * around when it (he?) reaches the right edge. It (he?) is centered
+ * vertically.
+ *
+ * My, what is the sex of that thing? ;-)
+ **/
+ void animate( QPainter * painter,
+ QRect rect );
+
+ /**
+ * Restart - reset to initial position and direction.
+ **/
+ void restart();
+
+ /**
+ * Return the rectangle where the last PacMan was painted.
+ **/
+ QRect lastPacMan() { return _pacManRect; }
+
+ /**
+ * Set the animation interval in milliseconds.
+ **/
+ void setInterval( int intervalMilliSec ) { _interval = intervalMilliSec; }
+ int interval() const { return _interval; }
+
+ /**
+ * Number of pixels to move for each phase.
+ **/
+ int speed() const { return _speed; }
+ void setSpeed( int speed ) { _speed = speed; }
+
+ /**
+ * Brush to draw PacMan's inside. Bright yellow by default.
+ **/
+ QBrush brush() const { return _brush; }
+ void setBrush( const QBrush & brush ) { _brush = brush; }
+
+ /**
+ * Number of degrees PacMan's mouth opens or closes for each animation.
+ **/
+ int mouthOpenInc() const { return _mouthInc; }
+ void setMouthOpenInc( int deg ) { _mouthInc = deg; }
+
+ /**
+ * Minimum angle in degrees that PacMan's mouth opens.
+ **/
+ int minMouthOpenAngle() const { return _minMouth; }
+ void setMinMouthOpenAngle( int deg ) { _minMouth = deg; }
+
+ /**
+ * Maximum angle in degrees that PacMan's mouth opens.
+ **/
+ int maxMouthOpenAngle() const { return _maxMouth; }
+ void setMaxMouthOpenAngle( int deg ) { _maxMouth = deg; }
+
+protected:
+
+ QWidget * _widget;
+ QBrush _brush;
+ QTime _time;
+ QRect _pacManRect;
+ int _size;
+ bool _randomStart;
+ int _speed;
+
+ int _minMouth;
+ int _maxMouth;
+ int _mouthInc;
+ int _interval; // milliseconds
+
+
+ // Current values
+
+ int _pos;
+ int _mouth;
+ bool _justStarted;
+ bool _goingRight;
+};
+
+
+
+/**
+ * Widget that displays a PacMan animation.
+ *
+ * @short PacMan widget
+ **/
+class KPacMan: public QWidget
+{
+ Q_OBJECT
+
+public:
+
+ /**
+ * Constructor.
+ *
+ * @param pacManSize size of the PacMan sprite
+ * @param randomStart random start position and direction if true
+ **/
+ KPacMan( QWidget * parent = 0,
+ int pacManSize = 16,
+ bool randomStart = false,
+ const char * widgetName = 0 );
+
+ /**
+ * Destructor.
+ **/
+ virtual ~KPacMan();
+
+ /**
+ * Access to the internal @ref PacManAnimation to avoid duplicating all its
+ * methods.
+ **/
+ KPacManAnimation * pacMan() { return _pacMan; }
+
+ /**
+ * Access to the internal @ref QPainter to avoid duplicating all its
+ * methods. Change this painter in order to change the visual appearance of
+ * the PacMan sprite.
+ **/
+ QPainter * painter() { return _painter; }
+
+ /**
+ * Returns the animation interval in milliseconds.
+ **/
+ int interval() const { return _interval; }
+
+ /**
+ * Set the animation interval in milliseconds.
+ **/
+ void setInterval( int intervalMilliSec );
+
+ /**
+ * Return the (left and right) margin.
+ **/
+ int margin() { return _margin; }
+
+ /**
+ * Set the (left and right) margin - a place PacMan never goes.
+ **/
+ void setMargin( int margin ) { _margin = margin; }
+
+ /**
+ * Returns the widget's preferred size.
+ *
+ * Reimplemented from @ref QWidget.
+ **/
+ virtual QSize sizeHint() const;
+
+
+public slots:
+
+ /**
+ * Start the animation.
+ **/
+ void start();
+
+ /**
+ * Stop the animation and clear the widget.
+ **/
+ void stop();
+
+ /**
+ * Do one animation. Triggered by timer.
+ **/
+ void animate();
+
+
+protected:
+
+ /**
+ * Actually do the painting.
+ *
+ * Reimplemented from @ref QWidget.
+ **/
+ virtual void paintEvent( QPaintEvent *ev );
+
+ /**
+ * Stop animation on mouse click.
+ *
+ * Reimplemented from @ref QWidget.
+ **/
+ virtual void mouseReleaseEvent ( QMouseEvent *ev );
+
+
+protected:
+
+ KPacManAnimation * _pacMan;
+ QPainter * _painter;
+ QTimer * _timer;
+ int _interval; // millisec
+ bool _active;
+ int _margin;
+ int _pacManSize;
+};
+
+#endif // KPacMan_h
+
+
+// EOF
diff --git a/kdirstat/kstdcleanup.cpp b/kdirstat/kstdcleanup.cpp
new file mode 100644
index 0000000..8626dfc
--- /dev/null
+++ b/kdirstat/kstdcleanup.cpp
@@ -0,0 +1,150 @@
+/*
+ * File name: kstdcleanup.cpp
+ * Summary: Support classes for KDirStat
+ * License: LGPL - See file COPYING.LIB for details.
+ * Author: Stefan Hundhammer <sh@suse.de>
+ *
+ * Updated: 2004-11-23
+ */
+
+
+#include <klocale.h>
+#include "kcleanup.h"
+#include "kstdcleanup.h"
+
+using namespace KDirStat;
+
+
+KCleanup *
+KStdCleanup::openInKonqueror( KActionCollection *parent )
+{
+ KCleanup *cleanup = new KCleanup( "cleanup_open_in_konqueror",
+ "kfmclient openURL %p",
+ i18n( "Open in &Konqueror" ),
+ parent );
+ CHECK_PTR( cleanup );
+ cleanup->setWorksForDir ( true );
+ cleanup->setWorksForFile ( true );
+ cleanup->setWorksForDotEntry( true );
+ cleanup->setWorksLocalOnly ( false );
+ cleanup->setRefreshPolicy( KCleanup::noRefresh );
+ cleanup->setIcon( "konqueror.png" );
+ cleanup->setShortcut( Qt::CTRL + Qt::Key_K );
+
+ return cleanup;
+}
+
+
+KCleanup *
+KStdCleanup::openInTerminal( KActionCollection *parent )
+{
+ KCleanup *cleanup = new KCleanup( "cleanup_open_in_terminal",
+ "konsole",
+ i18n( "Open in &Terminal" ),
+ parent );
+ CHECK_PTR( cleanup );
+ cleanup->setWorksForDir ( true );
+ cleanup->setWorksForFile ( true );
+ cleanup->setWorksForDotEntry( true );
+ cleanup->setRefreshPolicy( KCleanup::noRefresh );
+ cleanup->setIcon( "konsole.png" );
+ cleanup->setShortcut( Qt::CTRL + Qt::Key_T );
+
+ return cleanup;
+}
+
+
+KCleanup *
+KStdCleanup::compressSubtree( KActionCollection *parent )
+{
+ KCleanup *cleanup = new KCleanup( "cleanup_compress_subtree",
+ "cd ..; tar cjvf %n.tar.bz2 %n && rm -rf %n",
+ i18n( "&Compress" ),
+ parent );
+ CHECK_PTR( cleanup );
+ cleanup->setWorksForDir ( true );
+ cleanup->setWorksForFile ( false );
+ cleanup->setWorksForDotEntry( false );
+ cleanup->setRefreshPolicy( KCleanup::refreshParent );
+ cleanup->setIcon( "ark.png" );
+
+ return cleanup;
+}
+
+
+KCleanup *
+KStdCleanup::makeClean( KActionCollection *parent )
+{
+ KCleanup *cleanup = new KCleanup( "cleanup_make_clean",
+ "make clean",
+ i18n( "&make clean" ),
+ parent );
+ CHECK_PTR( cleanup );
+ cleanup->setWorksForDir ( true );
+ cleanup->setWorksForFile ( false );
+ cleanup->setWorksForDotEntry( true );
+ cleanup->setRefreshPolicy( KCleanup::refreshThis );
+
+ return cleanup;
+}
+
+
+KCleanup *
+KStdCleanup::deleteTrash( KActionCollection *parent )
+{
+ KCleanup *cleanup = new KCleanup( "cleanup_delete_trash",
+ "rm -f *.o *~ *.bak *.auto core",
+ i18n( "Delete T&rash Files" ),
+ parent );
+ CHECK_PTR( cleanup );
+ cleanup->setWorksForDir ( true );
+ cleanup->setWorksForFile ( false );
+ cleanup->setWorksForDotEntry( true );
+ cleanup->setRefreshPolicy( KCleanup::refreshThis );
+ cleanup->setRecurse( true );
+
+ return cleanup;
+}
+
+
+KCleanup *
+KStdCleanup::moveToTrashBin( KActionCollection *parent )
+{
+ KCleanup *cleanup = new KCleanup( "cleanup_move_to_trash_bin",
+ "kfmclient move %p %t",
+ i18n( "Delete (to Trash &Bin)" ),
+ parent );
+ CHECK_PTR( cleanup );
+ cleanup->setWorksForDir ( true );
+ cleanup->setWorksForFile ( true );
+ cleanup->setWorksForDotEntry( false );
+ cleanup->setRefreshPolicy( KCleanup::assumeDeleted );
+ cleanup->setIcon( "edittrash.png" );
+ cleanup->setShortcut( Qt::CTRL + Qt::Key_X );
+
+ return cleanup;
+}
+
+
+KCleanup *
+KStdCleanup::hardDelete( KActionCollection *parent )
+{
+ KCleanup *cleanup = new KCleanup( "cleanup_hard_delete",
+ "rm -rf %p",
+ i18n( "&Delete (no way to undelete!)" ),
+ parent );
+ CHECK_PTR( cleanup );
+ cleanup->setWorksForDir ( true );
+ cleanup->setWorksForFile ( true );
+ cleanup->setWorksForDotEntry( false );
+ cleanup->setAskForConfirmation( true );
+ cleanup->setRefreshPolicy( KCleanup::assumeDeleted );
+ cleanup->setIcon( "editdelete.png" );
+ cleanup->setShortcut( Qt::CTRL + Qt::Key_Delete );
+
+ return cleanup;
+}
+
+
+
+// EOF
diff --git a/kdirstat/kstdcleanup.h b/kdirstat/kstdcleanup.h
new file mode 100644
index 0000000..110cd2b
--- /dev/null
+++ b/kdirstat/kstdcleanup.h
@@ -0,0 +1,65 @@
+/*
+ * File name: kstdcleanup.h
+ * Summary: Support classes for KDirStat
+ * License: LGPL - See file COPYING.LIB for details.
+ * Author: Stefan Hundhammer <sh@suse.de>
+ *
+ * Updated: 2003-01-07
+ */
+
+
+#ifndef KStdCleanup_h
+#define KStdCleanup_h
+
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+// Forward declarations
+class KActionCollection;
+class KDirStat::KCleanup;
+
+
+namespace KDirStat
+{
+ /**
+ * Predefined standard @ref KCleanup actions to be performed on
+ * @ref KDirTree items.
+ *
+ * This class is not meant to be ever instantiated - use the static methods
+ * only.
+ *
+ * For details about what each individual method does, refer to the help
+ * file. Use the old (KDirStat 0.86) help file in case the current help
+ * file isn't available yet.
+ *
+ * @short KDirStat standard cleanup actions
+ **/
+
+ class KStdCleanup
+ {
+ public:
+ static KCleanup *openInKonqueror ( KActionCollection *parent = 0 );
+ static KCleanup *openInTerminal ( KActionCollection *parent = 0 );
+ static KCleanup *compressSubtree ( KActionCollection *parent = 0 );
+ static KCleanup *makeClean ( KActionCollection *parent = 0 );
+ static KCleanup *deleteTrash ( KActionCollection *parent = 0 );
+ static KCleanup *moveToTrashBin ( KActionCollection *parent = 0 );
+ static KCleanup *hardDelete ( KActionCollection *parent = 0 );
+
+ private:
+ /**
+ * Prevent instances of this class - private constructor / destructor.
+ **/
+ KStdCleanup() {}
+ ~KStdCleanup() {}
+ };
+
+} // namespace KDirStat
+
+
+#endif // ifndef KStdCleanup_h
+
+
+// EOF
diff --git a/kdirstat/ktreemaptile.cpp b/kdirstat/ktreemaptile.cpp
new file mode 100644
index 0000000..c55b38c
--- /dev/null
+++ b/kdirstat/ktreemaptile.cpp
@@ -0,0 +1,608 @@
+/*
+ * File name: ktreemaptile.cpp
+ * Summary: High level classes for KDirStat
+ * License: LGPL - See file COPYING.LIB for details.
+ * Author: Stefan Hundhammer <sh@suse.de>
+ *
+ * Updated: 2003-01-30
+ */
+
+
+#include <math.h>
+#include <algorithm>
+
+#include <kapp.h>
+#include <klocale.h>
+#include <kglobal.h>
+#include <qimage.h>
+#include <qpainter.h>
+
+#include "ktreemaptile.h"
+#include "ktreemapview.h"
+#include "kdirtreeiterators.h"
+#include "kdirtreeview.h"
+
+
+using namespace KDirStat;
+using std::max;
+using std::min;
+
+
+KTreemapTile::KTreemapTile( KTreemapView * parentView,
+ KTreemapTile * parentTile,
+ KFileInfo * orig,
+ const QRect & rect,
+ KOrientation orientation )
+ : QCanvasRectangle( rect, parentView->canvas() )
+ , _parentView( parentView )
+ , _parentTile( parentTile )
+ , _orig( orig )
+{
+ init();
+
+ if ( parentTile )
+ _cushionSurface = parentTile->cushionSurface();
+
+ createChildren( rect, orientation );
+}
+
+
+KTreemapTile::KTreemapTile( KTreemapView * parentView,
+ KTreemapTile * parentTile,
+ KFileInfo * orig,
+ const QRect & rect,
+ const KCushionSurface & cushionSurface,
+ KOrientation orientation )
+ : QCanvasRectangle( rect, parentView->canvas() )
+ , _parentView( parentView )
+ , _parentTile( parentTile )
+ , _orig( orig )
+ , _cushionSurface( cushionSurface )
+{
+ init();
+
+ // Intentionally not copying the parent's cushion surface!
+
+ createChildren( rect, orientation );
+}
+
+
+KTreemapTile::~KTreemapTile()
+{
+ // NOP
+}
+
+
+void
+KTreemapTile::init()
+{
+ // Set up height (z coordinate) - one level higher than the parent so this
+ // will be closer to the foreground.
+ //
+ // Note that this must happen before any children are created.
+ // I found that out the hard way. ;-)
+
+ setZ( _parentTile ? ( _parentTile->z() + 1.0 ) : 0.0 );
+
+ setBrush( QColor( 0x60, 0x60, 0x60 ) );
+ setPen( NoPen );
+
+ show(); // QCanvasItems are invisible by default!
+
+ // kdDebug() << "Creating treemap tile for " << orig << " " << rect << " size " << orig->totalSize() << endl;
+}
+
+
+void
+KTreemapTile::createChildren( const QRect & rect,
+ KOrientation orientation )
+{
+ if ( _orig->totalSize() == 0 ) // Prevent division by zero
+ return;
+
+ if ( _parentView->squarify() )
+ createSquarifiedChildren( rect );
+ else
+ createChildrenSimple( rect, orientation );
+}
+
+
+void
+KTreemapTile::createChildrenSimple( const QRect & rect,
+ KOrientation orientation )
+{
+
+ KOrientation dir = orientation;
+ KOrientation childDir = orientation;
+
+ if ( dir == KTreemapAuto )
+ dir = rect.width() > rect.height() ? KTreemapHorizontal : KTreemapVertical;
+
+ if ( orientation == KTreemapHorizontal ) childDir = KTreemapVertical;
+ if ( orientation == KTreemapVertical ) childDir = KTreemapHorizontal;
+
+ int offset = 0;
+ int size = dir == KTreemapHorizontal ? rect.width() : rect.height();
+ int count = 0;
+ double scale = (double) size / (double) _orig->totalSize();
+
+ _cushionSurface.addRidge( childDir, _cushionSurface.height(), rect );
+
+ KFileInfoSortedBySizeIterator it( _orig,
+ (KFileSize) ( _parentView->minTileSize() / scale ),
+ KDotEntryAsSubDir );
+
+ while ( *it )
+ {
+ int childSize = 0;
+
+ childSize = (int) ( scale * (*it)->totalSize() );
+
+ if ( childSize >= _parentView->minTileSize() )
+ {
+ QRect childRect;
+
+ if ( dir == KTreemapHorizontal )
+ childRect = QRect( rect.x() + offset, rect.y(), childSize, rect.height() );
+ else
+ childRect = QRect( rect.x(), rect.y() + offset, rect.width(), childSize );
+
+ KTreemapTile * tile = new KTreemapTile( _parentView, this, *it, childRect, childDir );
+ CHECK_PTR( tile );
+
+ tile->cushionSurface().addRidge( dir,
+ _cushionSurface.height() * _parentView->heightScaleFactor(),
+ childRect );
+
+ offset += childSize;
+ }
+
+ ++count;
+ ++it;
+ }
+}
+
+
+void
+KTreemapTile::createSquarifiedChildren( const QRect & rect )
+{
+ if ( _orig->totalSize() == 0 )
+ {
+ kdError() << k_funcinfo << "Zero totalSize()" << endl;
+ return;
+ }
+
+ double scale = rect.width() * (double) rect.height() / _orig->totalSize();
+ KFileSize minSize = (KFileSize) ( _parentView->minTileSize() / scale );
+
+#if 0
+ if ( _orig->hasChildren() )
+ {
+ _cushionSurface.addRidge( KTreemapHorizontal, _cushionSurface.height(), rect );
+ _cushionSurface.addRidge( KTreemapVertical, _cushionSurface.height(), rect );
+ }
+#endif
+
+ KFileInfoSortedBySizeIterator it( _orig, minSize, KDotEntryAsSubDir );
+ QRect childrenRect = rect;
+
+ while ( *it )
+ {
+ KFileInfoList row = squarify( childrenRect, scale, it );
+ childrenRect = layoutRow( childrenRect, scale, row );
+ }
+}
+
+
+KFileInfoList
+KTreemapTile::squarify( const QRect & rect,
+ double scale,
+ KFileInfoSortedBySizeIterator & it )
+{
+ // kdDebug() << "squarify() " << _orig << " " << rect << endl;
+
+ KFileInfoList row;
+ int length = max( rect.width(), rect.height() );
+
+ if ( length == 0 ) // Sanity check
+ {
+ kdWarning() << k_funcinfo << "Zero length" << endl;
+
+ if ( *it ) // Prevent endless loop in case of error:
+ ++it; // Advance iterator.
+
+ return row;
+ }
+
+
+ bool improvingAspectRatio = true;
+ double lastWorstAspectRatio = -1.0;
+ double sum = 0;
+
+ // This is a bit ugly, but doing all calculations in the 'size' dimension
+ // is more efficient here since that requires only one scaling before
+ // doing all other calculations in the loop.
+ const double scaledLengthSquare = length * (double) length / scale;
+
+ while ( *it && improvingAspectRatio )
+ {
+ sum += (*it)->totalSize();
+
+ if ( ! row.isEmpty() && sum != 0 && (*it)->totalSize() != 0 )
+ {
+ double sumSquare = sum * sum;
+ double worstAspectRatio = max( scaledLengthSquare * row.first()->totalSize() / sumSquare,
+ sumSquare / ( scaledLengthSquare * (*it)->totalSize() ) );
+
+ if ( lastWorstAspectRatio >= 0.0 &&
+ worstAspectRatio > lastWorstAspectRatio )
+ {
+ improvingAspectRatio = false;
+ }
+
+ lastWorstAspectRatio = worstAspectRatio;
+ }
+
+ if ( improvingAspectRatio )
+ {
+ // kdDebug() << "Adding " << *it << " size " << (*it)->totalSize() << endl;
+ row.append( *it );
+ ++it;
+ }
+ else
+ {
+ // kdDebug() << "Getting worse after adding " << *it << " size " << (*it)->totalSize() << endl;
+ }
+ }
+
+ return row;
+}
+
+
+
+QRect
+KTreemapTile::layoutRow( const QRect & rect,
+ double scale,
+ KFileInfoList & row )
+{
+ if ( row.isEmpty() )
+ return rect;
+
+ // Determine the direction in which to subdivide.
+ // We always use the longer side of the rectangle.
+ KOrientation dir = rect.width() > rect.height() ? KTreemapHorizontal : KTreemapVertical;
+
+ // This row's primary length is the longer one.
+ int primary = max( rect.width(), rect.height() );
+
+ // This row's secondary length is determined by the area (the number of
+ // pixels) to be allocated for all of the row's items.
+ KFileSize sum = row.sumTotalSizes();
+ int secondary = (int) ( sum * scale / primary );
+
+ if ( sum == 0 ) // Prevent division by zero.
+ return rect;
+
+ if ( secondary < _parentView->minTileSize() ) // We don't want tiles that small.
+ return rect;
+
+
+ // Set up a cushion surface for this layout row:
+ // Add another ridge perpendicular to the row's direction
+ // that optically groups this row's tiles together.
+
+ KCushionSurface rowCushionSurface = _cushionSurface;
+
+ rowCushionSurface.addRidge( dir == KTreemapHorizontal ? KTreemapVertical : KTreemapHorizontal,
+ _cushionSurface.height() * _parentView->heightScaleFactor(),
+ rect );
+
+ int offset = 0;
+ int remaining = primary;
+ KFileInfoListIterator it( row );
+
+ while ( *it )
+ {
+ int childSize = (int) ( (*it)->totalSize() / (double) sum * primary + 0.5 );
+
+ if ( childSize > remaining ) // Prevent overflow because of accumulated rounding errors
+ childSize = remaining;
+
+ remaining -= childSize;
+
+ if ( childSize >= _parentView->minTileSize() )
+ {
+ QRect childRect;
+
+ if ( dir == KTreemapHorizontal )
+ childRect = QRect( rect.x() + offset, rect.y(), childSize, secondary );
+ else
+ childRect = QRect( rect.x(), rect.y() + offset, secondary, childSize );
+
+ KTreemapTile * tile = new KTreemapTile( _parentView, this, *it, childRect, rowCushionSurface );
+ CHECK_PTR( tile );
+
+ tile->cushionSurface().addRidge( dir,
+ rowCushionSurface.height() * _parentView->heightScaleFactor(),
+ childRect );
+ offset += childSize;
+ }
+
+ ++it;
+ }
+
+
+ // Subtract the layouted area from the rectangle.
+
+ QRect newRect;
+
+ if ( dir == KTreemapHorizontal )
+ newRect = QRect( rect.x(), rect.y() + secondary, rect.width(), rect.height() - secondary );
+ else
+ newRect = QRect( rect.x() + secondary, rect.y(), rect.width() - secondary, rect.height() );
+
+ // kdDebug() << "Left over:" << " " << newRect << " " << _orig << endl;
+
+ return newRect;
+}
+
+
+void
+KTreemapTile::drawShape( QPainter & painter )
+{
+ // kdDebug() << k_funcinfo << endl;
+
+ QSize size = rect().size();
+
+ if ( size.height() < 1 || size.width() < 1 )
+ return;
+
+
+ if ( _parentView->doCushionShading() )
+ {
+ if ( _orig->isDir() || _orig->isDotEntry() )
+ {
+ QCanvasRectangle::drawShape( painter );
+ }
+ else
+ {
+ if ( _cushion.isNull() )
+ _cushion = renderCushion();
+
+ QRect rect = QCanvasRectangle::rect();
+
+ if ( ! _cushion.isNull() )
+ painter.drawPixmap( rect, _cushion );
+
+ if ( _parentView->forceCushionGrid() )
+ {
+ // Draw a clearly visible boundary
+
+ painter.setPen( QPen( _parentView->cushionGridColor(), 1 ) );
+
+ if ( rect.x() > 0 )
+ painter.drawLine( rect.topLeft(), rect.bottomLeft() + QPoint( 0, 1 ) );
+
+ if ( rect.y() > 0 )
+ painter.drawLine( rect.topLeft(), rect.topRight() + QPoint( 1, 0 ) );
+ }
+ }
+ }
+ else // No cushion shading, use plain tiles
+ {
+ painter.setPen( QPen( _parentView->outlineColor(), 1 ) );
+
+ if ( _orig->isDir() || _orig->isDotEntry() )
+ painter.setBrush( _parentView->dirFillColor() );
+ else
+ {
+ painter.setBrush( _parentView->tileColor( _orig ) );
+#if 0
+ painter.setBrush( _parentView->fileFillColor() );
+#endif
+ }
+
+ QCanvasRectangle::drawShape( painter );
+ }
+}
+
+
+QPixmap
+KTreemapTile::renderCushion()
+{
+ QRect rect = QCanvasRectangle::rect();
+
+ if ( rect.width() < 1 || rect.height() < 1 )
+ return QPixmap();
+
+ // kdDebug() << k_funcinfo << endl;
+
+ double nx;
+ double ny;
+ double cosa;
+ int x, y;
+ int red, green, blue;
+
+
+ // Cache some values. They are used for each loop iteration, so let's try
+ // to keep multiple indirect references down.
+
+ int ambientLight = parentView()->ambientLight();
+ double lightX = parentView()->lightX();
+ double lightY = parentView()->lightY();
+ double lightZ = parentView()->lightZ();
+
+ double xx2 = cushionSurface().xx2();
+ double xx1 = cushionSurface().xx1();
+ double yy2 = cushionSurface().yy2();
+ double yy1 = cushionSurface().yy1();
+
+ int x0 = rect.x();
+ int y0 = rect.y();
+
+ QColor color = parentView()->tileColor( _orig );
+ int maxRed = max( 0, color.red() - ambientLight );
+ int maxGreen = max( 0, color.green() - ambientLight );
+ int maxBlue = max( 0, color.blue() - ambientLight );
+
+ QImage image( rect.width(), rect.height(), 32 );
+
+ for ( y = 0; y < rect.height(); y++ )
+ {
+ for ( x = 0; x < rect.width(); x++ )
+ {
+ nx = 2.0 * xx2 * (x+x0) + xx1;
+ ny = 2.0 * yy2 * (y+y0) + yy1;
+ cosa = ( nx * lightX + ny * lightY + lightZ ) / sqrt( nx*nx + ny*ny + 1.0 );
+
+ red = (int) ( maxRed * cosa + 0.5 );
+ green = (int) ( maxGreen * cosa + 0.5 );
+ blue = (int) ( maxBlue * cosa + 0.5 );
+
+ if ( red < 0 ) red = 0;
+ if ( green < 0 ) green = 0;
+ if ( blue < 0 ) blue = 0;
+
+ red += ambientLight;
+ green += ambientLight;
+ blue += ambientLight;
+
+ image.setPixel( x, y, qRgb( red, green, blue) );
+ }
+ }
+
+ if ( _parentView->ensureContrast() )
+ ensureContrast( image );
+
+ return QPixmap( image );
+}
+
+
+void
+KTreemapTile::ensureContrast( QImage & image )
+{
+ if ( image.width() > 5 )
+ {
+ // Check contrast along the right image boundary:
+ //
+ // Compare samples from the outmost boundary to samples a few pixels to
+ // the inside and count identical pixel values. A number of identical
+ // pixels are tolerated, but not too many.
+
+ int x1 = image.width() - 6;
+ int x2 = image.width() - 1;
+ int interval = max( image.height() / 10, 5 );
+ int sameColorCount = 0;
+
+
+ // Take samples
+
+ for ( int y = interval; y < image.height(); y+= interval )
+ {
+ if ( image.pixel( x1, y ) == image.pixel( x2, y ) )
+ sameColorCount++;
+ }
+
+ if ( sameColorCount * 10 > image.height() )
+ {
+ // Add a line at the right boundary
+
+ QRgb val = contrastingColor( image.pixel( x2, image.height() / 2 ) );
+
+ for ( int y = 0; y < image.height(); y++ )
+ image.setPixel( x2, y, val );
+ }
+ }
+
+
+ if ( image.height() > 5 )
+ {
+ // Check contrast along the bottom boundary
+
+ int y1 = image.height() - 6;
+ int y2 = image.height() - 1;
+ int interval = max( image.width() / 10, 5 );
+ int sameColorCount = 0;
+
+ for ( int x = interval; x < image.width(); x += interval )
+ {
+ if ( image.pixel( x, y1 ) == image.pixel( x, y2 ) )
+ sameColorCount++;
+ }
+
+ if ( sameColorCount * 10 > image.height() )
+ {
+ // Add a grey line at the bottom boundary
+
+ QRgb val = contrastingColor( image.pixel( image.width() / 2, y2 ) );
+
+ for ( int x = 0; x < image.width(); x++ )
+ image.setPixel( x, y2, val );
+ }
+ }
+}
+
+
+QRgb
+KTreemapTile::contrastingColor( QRgb col )
+{
+ if ( qGray( col ) < 128 )
+ return qRgb( qRed( col ) * 2, qGreen( col ) * 2, qBlue( col ) * 2 );
+ else
+ return qRgb( qRed( col ) / 2, qGreen( col ) / 2, qBlue( col ) / 2 );
+}
+
+
+
+
+KCushionSurface::KCushionSurface()
+{
+ _xx2 = 0.0;
+ _xx1 = 0.0;
+ _yy2 = 0.0;
+ _yy1 = 0.0;
+ _height = CushionHeight;
+}
+
+
+void
+KCushionSurface::addRidge( KOrientation dim, double height, const QRect & rect )
+{
+ _height = height;
+
+ if ( dim == KTreemapHorizontal )
+ {
+ _xx2 = squareRidge( _xx2, _height, rect.left(), rect.right() );
+ _xx1 = linearRidge( _xx1, _height, rect.left(), rect.right() );
+ }
+ else
+ {
+ _yy2 = squareRidge( _yy2, _height, rect.top(), rect.bottom() );
+ _yy1 = linearRidge( _yy1, _height, rect.top(), rect.bottom() );
+ }
+}
+
+
+double
+KCushionSurface::squareRidge( double squareCoefficient, double height, int x1, int x2 )
+{
+ if ( x2 != x1 ) // Avoid division by zero
+ squareCoefficient -= 4.0 * height / ( x2 - x1 );
+
+ return squareCoefficient;
+}
+
+
+double
+KCushionSurface::linearRidge( double linearCoefficient, double height, int x1, int x2 )
+{
+ if ( x2 != x1 ) // Avoid division by zero
+ linearCoefficient += 4.0 * height * ( x2 + x1 ) / ( x2 - x1 );
+
+ return linearCoefficient;
+}
+
+
+
+
+// EOF
diff --git a/kdirstat/ktreemaptile.h b/kdirstat/ktreemaptile.h
new file mode 100644
index 0000000..93b1ce3
--- /dev/null
+++ b/kdirstat/ktreemaptile.h
@@ -0,0 +1,323 @@
+/*
+ * File name: ktreemaptile.h
+ * Summary: High level classes for KDirStat
+ * License: LGPL - See file COPYING.LIB for details.
+ * Author: Stefan Hundhammer <sh@suse.de>
+ *
+ * Updated: 2003-01-07
+ */
+
+
+#ifndef KTreemapTile_h
+#define KTreemapTile_h
+
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#include <qcanvas.h>
+#include <qrect.h>
+#include "kdirtreeiterators.h"
+
+
+namespace KDirStat
+{
+ class KFileInfo;
+ class KTreemapView;
+
+ enum KOrientation
+ {
+ KTreemapHorizontal,
+ KTreemapVertical,
+ KTreemapAuto
+ };
+
+
+ /**
+ * Helper class for cushioned treemaps: This class holds the polynome
+ * parameters for the cushion surface. The height of each point of such a
+ * surface is defined as:
+ *
+ * z(x, y) = a*x^2 + b*y^2 + c*x + d*y
+ * or
+ * z(x, y) = xx2*x^2 + yy2*y^2 + xx1*x + yy1*y
+ *
+ * to better keep track of which coefficient belongs where.
+ **/
+ class KCushionSurface
+ {
+ public:
+ /**
+ * Constructor. All polynome coefficients are set to 0.
+ **/
+ KCushionSurface();
+
+ /**
+ * Adds a ridge of the specified height in dimension 'dim' within
+ * rectangle 'rect' to this surface. It's real voodo magic.
+ *
+ * Just kidding - read the paper about "cushion treemaps" by Jarke
+ * J. van Wiik and Huub van de Wetering from the TU Eindhoven, NL for
+ * more details.
+ *
+ * If you don't want to get all that involved: The coefficients are
+ * changed in some way.
+ **/
+ void addRidge( KOrientation dim, double height, const QRect & rect );
+
+ /**
+ * Set the cushion's height.
+ **/
+ void setHeight( double newHeight ) { _height = newHeight; }
+
+ /**
+ * Returns the cushion's height.
+ **/
+ double height() const { return _height; }
+
+ /**
+ * Returns the polynomal coefficient of the second order for X direction.
+ **/
+ double xx2() const { return _xx2; }
+
+ /**
+ * Returns the polynomal coefficient of the first order for X direction.
+ **/
+ double xx1() const { return _xx1; }
+
+ /**
+ * Returns the polynomal coefficient of the second order for Y direction.
+ **/
+ double yy2() const { return _yy2; }
+
+ /**
+ * Returns the polynomal coefficient of the first order for Y direction.
+ **/
+ double yy1() const { return _yy1; }
+
+
+ protected:
+
+ /**
+ * Calculate a new square polynomal coefficient for adding a ridge of
+ * specified height between x1 and x2.
+ **/
+ double squareRidge( double squareCoefficient, double height, int x1, int x2 );
+
+ /**
+ * Calculate a new linear polynomal coefficient for adding a ridge of
+ * specified height between x1 and x2.
+ **/
+ double linearRidge( double linearCoefficient, double height, int x1, int x2 );
+
+
+ // Data members
+
+ double _xx2, _xx1;
+ double _yy2, _yy1;
+ double _height;
+
+ }; // class KCushionSurface
+
+
+
+ /**
+ * This is the basic building block of a treemap view: One single tile of a
+ * treemap. If it corresponds to a leaf in the tree, it will be visible as
+ * one tile (one rectangle) of the treemap. If it has children, it will be
+ * subdivided again.
+ *
+ * @short Basic building block of a treemap
+ **/
+ class KTreemapTile: public QCanvasRectangle
+ {
+ public:
+
+ /**
+ * Constructor: Create a treemap tile from 'fileinfo' that fits into a
+ * rectangle 'rect' inside 'parent'.
+ *
+ * 'orientation' is the direction for further subdivision. 'Auto'
+ * selects the wider direction inside 'rect'.
+ **/
+ KTreemapTile( KTreemapView * parentView,
+ KTreemapTile * parentTile,
+ KFileInfo * orig,
+ const QRect & rect,
+ KOrientation orientation = KTreemapAuto );
+
+ protected:
+
+ /**
+ * Alternate constructor: Like the above, but explicitly specify a
+ * cushion surface rather than using the parent's.
+ **/
+ KTreemapTile( KTreemapView * parentView,
+ KTreemapTile * parentTile,
+ KFileInfo * orig,
+ const QRect & rect,
+ const KCushionSurface & cushionSurface,
+ KOrientation orientation = KTreemapAuto );
+
+ public:
+ /**
+ * Destructor.
+ **/
+ virtual ~KTreemapTile();
+
+
+ /**
+ * Returns the original @ref KFileInfo item that corresponds to this
+ * treemap tile.
+ **/
+ KFileInfo * orig() const { return _orig; }
+
+ /**
+ * Returns the parent @ref KTreemapView.
+ **/
+ KTreemapView * parentView() const { return _parentView; }
+
+ /**
+ * Returns the parent @ref KTreemapTile or 0 if there is none.
+ **/
+ KTreemapTile * parentTile() const { return _parentTile; }
+
+ /**
+ * Returns this tile's cushion surface parameters.
+ **/
+ KCushionSurface & cushionSurface() { return _cushionSurface; }
+
+
+ protected:
+
+ /**
+ * Create children (sub-tiles) of this tile.
+ **/
+ void createChildren ( const QRect & rect,
+ KOrientation orientation );
+
+ /**
+ * Create children (sub-tiles) using the simple treemap algorithm:
+ * Alternate between horizontal and vertical subdivision in each
+ * level. Each child will get the entire height or width, respectively,
+ * of the specified rectangle. This algorithm is very fast, but often
+ * results in very thin, elongated tiles.
+ **/
+ void createChildrenSimple( const QRect & rect,
+ KOrientation orientation );
+
+ /**
+ * Create children using the "squarified treemaps" algorithm as
+ * described by Mark Bruls, Kees Huizing, and Jarke J. van Wijk of the
+ * TU Eindhoven, NL.
+ *
+ * This algorithm is not quite so simple and involves more expensive
+ * operations, e.g., sorting the children of each node by size first,
+ * try some variations of the layout and maybe backtrack to the
+ * previous attempt. But it results in tiles that are much more
+ * square-like, i.e. have more reasonable width-to-height ratios. It is
+ * very much less likely to get thin, elongated tiles that are hard to
+ * point at and even harder to compare visually against each other.
+ *
+ * This implementation includes some improvements to that basic
+ * algorithm. For example, children below a certain size are
+ * disregarded completely since they will not get an adequate visual
+ * representation anyway (it would be way too small). They are
+ * summarized in some kind of 'misc stuff' area in the parent treemap
+ * tile - in fact, part of the parent directory's tile can be "seen
+ * through".
+ *
+ * In short, a lot of small children that don't have any useful effect
+ * for the user in finding wasted disk space are omitted from handling
+ * and, most important, don't need to be sorted by size (which has a
+ * cost of O(n*ln(n)) in the best case, so reducing n helps a lot).
+ **/
+ void createSquarifiedChildren( const QRect & rect );
+
+ /**
+ * Squarify as many children as possible: Try to squeeze members
+ * referred to by 'it' into 'rect' until the aspect ratio doesn't get
+ * better any more. Returns a list of children that should be laid out
+ * in 'rect'. Moves 'it' until there is no more improvement or 'it'
+ * runs out of items.
+ *
+ * 'scale' is the scaling factor between file sizes and pixels.
+ **/
+ KFileInfoList squarify( const QRect & rect,
+ double scale,
+ KFileInfoSortedBySizeIterator & it );
+
+ /**
+ * Lay out all members of 'row' within 'rect' along its longer side.
+ * Returns the new rectangle with the layouted area subtracted.
+ **/
+ QRect layoutRow( const QRect & rect,
+ double scale,
+ KFileInfoList & row );
+
+ /**
+ * Draw the tile.
+ *
+ * Reimplemented from QCanvasRectangle.
+ **/
+ virtual void drawShape( QPainter & painter );
+
+ /**
+ * Render a cushion as described in "cushioned treemaps" by Jarke
+ * J. van Wijk and Huub van de Wetering of the TU Eindhoven, NL.
+ **/
+ QPixmap renderCushion();
+
+ /**
+ * Check if the contrast of the specified image is sufficient to
+ * visually distinguish an outline at the right and bottom borders
+ * and add a grey line there, if necessary.
+ **/
+ void ensureContrast( QImage & image );
+
+ /**
+ * Returns a color that gives a reasonable contrast to 'col': Lighter
+ * if 'col' is dark, darker if 'col' is light.
+ **/
+ QRgb contrastingColor( QRgb col );
+
+ private:
+
+ /**
+ * Initialization common to all constructors.
+ **/
+ void init();
+
+
+ protected:
+
+ // Data members
+
+ KTreemapView * _parentView;
+ KTreemapTile * _parentTile;
+ KFileInfo * _orig;
+ KCushionSurface _cushionSurface;
+ QPixmap _cushion;
+
+ }; // class KTreemapTile
+
+} // namespace KDirStat
+
+
+
+inline kdbgstream & operator<< ( kdbgstream & stream, const QRect & rect )
+{
+ stream << "("
+ << rect.width() << "x" << rect.height()
+ << "+" << rect.x() << "+" << rect.y()
+ << ")";
+
+ return stream;
+}
+
+
+#endif // ifndef KTreemapTile_h
+
+
+// EOF
diff --git a/kdirstat/ktreemapview.cpp b/kdirstat/ktreemapview.cpp
new file mode 100644
index 0000000..2a8bf20
--- /dev/null
+++ b/kdirstat/ktreemapview.cpp
@@ -0,0 +1,745 @@
+/*
+ * File name: ktreemapview.cpp
+ * Summary: High level classes for KDirStat
+ * License: LGPL - See file COPYING.LIB for details.
+ * Author: Stefan Hundhammer <sh@suse.de>
+ *
+ * Updated: 2003-10-20
+ */
+
+
+#include <sys/stat.h>
+
+#include <qevent.h>
+#include <qregexp.h>
+
+#include <kapp.h>
+#include <kconfig.h>
+#include <kglobal.h>
+#include <klocale.h>
+
+#include "kdirtree.h"
+#include "ktreemapview.h"
+#include "ktreemaptile.h"
+
+
+using namespace KDirStat;
+
+#define UpdateMinSize 20
+
+
+
+KTreemapView::KTreemapView( KDirTree * tree, QWidget * parent, const QSize & initialSize )
+ : QCanvasView( parent )
+ , _tree( tree )
+ , _rootTile( 0 )
+ , _selectedTile( 0 )
+ , _selectionRect( 0 )
+{
+ // kdDebug() << k_funcinfo << endl;
+
+ readConfig();
+
+ // Default values for light sources taken from Wiik / Wetering's paper
+ // about "cushion treemaps".
+
+ _lightX = 0.09759;
+ _lightY = 0.19518;
+ _lightZ = 0.9759;
+
+ if ( _autoResize )
+ {
+ setHScrollBarMode( AlwaysOff );
+ setVScrollBarMode( AlwaysOff );
+ }
+
+ if ( initialSize.isValid() )
+ resize( initialSize );
+
+ if ( tree && tree->root() )
+ {
+ if ( ! _rootTile )
+ {
+ // The treemap might already be created indirectly by
+ // rebuildTreemap() called from resizeEvent() triggered by resize()
+ // above. If this is so, don't do it again.
+
+ rebuildTreemap( tree->root() );
+ }
+ }
+
+ connect( this, SIGNAL( selectionChanged( KFileInfo * ) ),
+ tree, SLOT ( selectItem ( KFileInfo * ) ) );
+
+ connect( tree, SIGNAL( selectionChanged( KFileInfo * ) ),
+ this, SLOT ( selectTile ( KFileInfo * ) ) );
+
+ connect( tree, SIGNAL( deletingChild ( KFileInfo * ) ),
+ this, SLOT ( deleteNotify ( KFileInfo * ) ) );
+
+ connect( tree, SIGNAL( childDeleted() ),
+ this, SLOT ( rebuildTreemap() ) );
+}
+
+
+KTreemapView::~KTreemapView()
+{
+}
+
+
+void
+KTreemapView::clear()
+{
+ if ( canvas() )
+ deleteAllItems( canvas() );
+
+ _selectedTile = 0;
+ _selectionRect = 0;
+ _rootTile = 0;
+}
+
+
+void
+KTreemapView::deleteAllItems( QCanvas * canvas )
+{
+ if ( ! canvas )
+ return;
+
+ QCanvasItemList all = canvas->allItems();
+
+ for ( QCanvasItemList::Iterator it = all.begin(); it != all.end(); ++it )
+ delete *it;
+}
+
+
+void
+KTreemapView::readConfig()
+{
+ KConfig * config = kapp->config();
+ config->setGroup( "Treemaps" );
+
+ _ambientLight = config->readNumEntry( "AmbientLight" , DefaultAmbientLight );
+
+ _heightScaleFactor = config->readDoubleNumEntry( "HeightScaleFactor" , DefaultHeightScaleFactor );
+ _autoResize = config->readBoolEntry( "AutoResize" , true );
+ _squarify = config->readBoolEntry( "Squarify" , true );
+ _doCushionShading = config->readBoolEntry( "CushionShading" , true );
+ _ensureContrast = config->readBoolEntry( "EnsureContrast" , true );
+ _forceCushionGrid = config->readBoolEntry( "ForceCushionGrid" , false );
+ _minTileSize = config->readNumEntry ( "MinTileSize" , DefaultMinTileSize );
+
+ _highlightColor = readColorEntry( config, "HighlightColor" , red );
+ _cushionGridColor = readColorEntry( config, "CushionGridColor" , QColor( 0x80, 0x80, 0x80 ) );
+ _outlineColor = readColorEntry( config, "OutlineColor" , black );
+ _fileFillColor = readColorEntry( config, "FileFillColor" , QColor( 0xde, 0x8d, 0x53 ) );
+ _dirFillColor = readColorEntry( config, "DirFillColor" , QColor( 0x10, 0x7d, 0xb4 ) );
+
+ if ( _autoResize )
+ {
+ setHScrollBarMode( AlwaysOff );
+ setVScrollBarMode( AlwaysOff );
+ }
+ else
+ {
+ setHScrollBarMode( QScrollView::Auto );
+ setVScrollBarMode( QScrollView::Auto );
+ }
+}
+
+
+QColor
+KTreemapView::readColorEntry( KConfig * config, const char * entryName, QColor defaultColor )
+{
+ return config->readColorEntry( entryName, &defaultColor );
+}
+
+
+KTreemapTile *
+KTreemapView::tileAt( QPoint pos )
+{
+ KTreemapTile * tile = 0;
+
+ QCanvasItemList coll = canvas()->collisions( pos );
+ QCanvasItemList::Iterator it = coll.begin();
+
+ while ( it != coll.end() && tile == 0 )
+ {
+ tile = dynamic_cast<KTreemapTile *> (*it);
+ ++it;
+ }
+
+ return tile;
+}
+
+
+void
+KTreemapView::contentsMousePressEvent( QMouseEvent * event )
+{
+ // kdDebug() << k_funcinfo << endl;
+
+ KTreemapTile * tile = tileAt( event->pos() );
+
+ switch ( event->button() )
+ {
+ case LeftButton:
+ selectTile( tile );
+ emit userActivity( 1 );
+ break;
+
+ case MidButton:
+ // Select clicked tile's parent, if available
+
+ if ( _selectedTile &&
+ _selectedTile->rect().contains( event->pos() ) )
+ {
+ if ( _selectedTile->parentTile() )
+ tile = _selectedTile->parentTile();
+ }
+
+ // Intentionally handling the middle button like the left button if
+ // the user clicked outside the (old) selected tile: Simply select
+ // the clicked tile. This makes using this middle mouse button
+ // intuitive: It can be used very much like the left mouse button,
+ // but it has added functionality. Plus, it cycles back to the
+ // clicked tile if the user has already clicked all the way up the
+ // hierarchy (i.e. the topmost directory is highlighted).
+
+ selectTile( tile );
+ emit userActivity( 1 );
+ break;
+
+ case RightButton:
+
+ if ( tile )
+ {
+ if ( _selectedTile &&
+ _selectedTile->rect().contains( event->pos() ) )
+ {
+ // If a directory (non-leaf tile) is already selected,
+ // don't override this by
+
+ emit contextMenu( _selectedTile, event->globalPos() );
+ }
+ else
+ {
+ selectTile( tile );
+ emit contextMenu( tile, event->globalPos() );
+ }
+
+ emit userActivity( 3 );
+ }
+ break;
+
+ default:
+ // event->button() is an enum, so g++ complains
+ // if there are unhandled cases.
+ break;
+ }
+}
+
+
+void
+KTreemapView::contentsMouseDoubleClickEvent( QMouseEvent * event )
+{
+ // kdDebug() << k_funcinfo << endl;
+
+ KTreemapTile * tile = tileAt( event->pos() );
+
+ switch ( event->button() )
+ {
+ case LeftButton:
+ if ( tile )
+ {
+ selectTile( tile );
+ zoomIn();
+ emit userActivity( 5 );
+ }
+ break;
+
+ case MidButton:
+ zoomOut();
+ emit userActivity( 5 );
+ break;
+
+ case RightButton:
+ // Double-clicking the right mouse button is pretty useless - the
+ // first click opens the context menu: Single clicks are always
+ // delivered first. Even if that would be caught by using timers,
+ // it would still be very awkward to use: Click too slow, and
+ // you'll get the context menu rather than what you really wanted -
+ // then you'd have to get rid of the context menu first.
+ break;
+
+ default:
+ // Prevent compiler complaints about missing enum values in switch
+ break;
+ }
+}
+
+
+void
+KTreemapView::zoomIn()
+{
+ if ( ! _selectedTile || ! _rootTile )
+ return;
+
+ KTreemapTile * newRootTile = _selectedTile;
+
+ while ( newRootTile->parentTile() != _rootTile &&
+ newRootTile->parentTile() ) // This should never happen, but who knows?
+ {
+ newRootTile = newRootTile->parentTile();
+ }
+
+ if ( newRootTile )
+ {
+ KFileInfo * newRoot = newRootTile->orig();
+
+ if ( newRoot->isDir() || newRoot->isDotEntry() )
+ rebuildTreemap( newRoot );
+ }
+}
+
+
+void
+KTreemapView::zoomOut()
+{
+ if ( _rootTile )
+ {
+ KFileInfo * root = _rootTile->orig();
+
+ if ( root->parent() )
+ root = root->parent();
+
+ rebuildTreemap( root );
+ }
+}
+
+
+void
+KTreemapView::selectParent()
+{
+ if ( _selectedTile && _selectedTile->parentTile() )
+ selectTile( _selectedTile->parentTile() );
+}
+
+
+bool
+KTreemapView::canZoomIn() const
+{
+ if ( ! _selectedTile || ! _rootTile )
+ return false;
+
+ if ( _selectedTile == _rootTile )
+ return false;
+
+ KTreemapTile * newRootTile = _selectedTile;
+
+ while ( newRootTile->parentTile() != _rootTile &&
+ newRootTile->parentTile() ) // This should never happen, but who knows?
+ {
+ newRootTile = newRootTile->parentTile();
+ }
+
+ if ( newRootTile )
+ {
+ KFileInfo * newRoot = newRootTile->orig();
+
+ if ( newRoot->isDir() || newRoot->isDotEntry() )
+ return true;
+ }
+
+ return false;
+}
+
+
+bool
+KTreemapView::canZoomOut() const
+{
+ if ( ! _rootTile || ! _tree->root() )
+ return false;
+
+ return _rootTile->orig() != _tree->root();
+}
+
+
+bool
+KTreemapView::canSelectParent() const
+{
+ return _selectedTile && _selectedTile->parentTile();
+}
+
+
+void
+KTreemapView::rebuildTreemap()
+{
+ KFileInfo * root = 0;
+
+ if ( ! _savedRootUrl.isEmpty() )
+ {
+ // kdDebug() << "Restoring old treemap with root " << _savedRootUrl << endl;
+
+ root = _tree->locate( _savedRootUrl, true ); // node, findDotEntries
+ }
+
+ if ( ! root )
+ root = _rootTile ? _rootTile->orig() : _tree->root();
+
+ rebuildTreemap( root, canvas()->size() );
+ _savedRootUrl = "";
+}
+
+
+void
+KTreemapView::rebuildTreemap( KFileInfo * newRoot,
+ const QSize & newSz )
+{
+ // kdDebug() << k_funcinfo << endl;
+
+ QSize newSize = newSz;
+
+ if ( newSz.isEmpty() )
+ newSize = visibleSize();
+
+
+ // Delete all old stuff.
+ clear();
+
+ // Re-create a new canvas
+
+ if ( ! canvas() )
+ {
+ QCanvas * canv = new QCanvas( this );
+ CHECK_PTR( canv );
+ setCanvas( canv );
+ }
+
+ canvas()->resize( newSize.width(), newSize.height() );
+
+ if ( newSize.width() >= UpdateMinSize && newSize.height() >= UpdateMinSize )
+ {
+ // The treemap contents is displayed if larger than a certain minimum
+ // visible size. This is an easy way for the user to avoid
+ // time-consuming delays when deleting a lot of files: Simply make the
+ // treemap (sub-) window very small.
+
+ // Fill the new canvas
+
+ if ( newRoot )
+ {
+ _rootTile = new KTreemapTile( this, // parentView
+ 0, // parentTile
+ newRoot, // orig
+ QRect( QPoint( 0, 0), newSize ),
+ KTreemapAuto );
+ }
+
+
+ // Synchronize selection with the tree
+
+ if ( _tree->selection() )
+ selectTile( _tree->selection() );
+ }
+ else
+ {
+ // kdDebug() << "Too small - suppressing treemap contents" << endl;
+ }
+
+ emit treemapChanged();
+}
+
+
+void
+KTreemapView::deleteNotify( KFileInfo * )
+{
+ if ( _rootTile )
+ {
+ if ( _rootTile->orig() != _tree->root() )
+ {
+ // If the user zoomed the treemap in, save the root's URL so the
+ // current state can be restored upon the next rebuildTreemap()
+ // call (which is triggered by the childDeleted() signal that the
+ // tree emits after deleting is done).
+ //
+ // Intentionally using debugUrl() here rather than just url() so
+ // the correct zoom can be restored even when a dot entry is the
+ // current treemap root.
+
+ _savedRootUrl = _rootTile->orig()->debugUrl();
+ }
+ else
+ {
+ // A shortcut for the most common case: No zoom. Simply use the
+ // tree's root for the next treemap rebuild.
+
+ _savedRootUrl = "";
+ }
+ }
+ else
+ {
+ // Intentionally leaving _savedRootUrl alone: Otherwise multiple
+ // deleteNotify() calls might cause a previously saved _savedRootUrl to
+ // be unnecessarily deleted, thus the treemap couldn't be restored as
+ // it was.
+ }
+
+ clear();
+}
+
+
+void
+KTreemapView::resizeEvent( QResizeEvent * event )
+{
+ QCanvasView::resizeEvent( event );
+
+ if ( _autoResize )
+ {
+ bool tooSmall =
+ event->size().width() < UpdateMinSize ||
+ event->size().height() < UpdateMinSize;
+
+ if ( tooSmall && _rootTile )
+ {
+ // kdDebug() << "Suppressing treemap contents" << endl;
+ rebuildTreemap( _rootTile->orig() );
+ }
+ else if ( ! tooSmall && ! _rootTile )
+ {
+ if ( _tree->root() )
+ {
+ // kdDebug() << "Redisplaying suppressed treemap contents" << endl;
+ rebuildTreemap( _tree->root() );
+ }
+ }
+ else if ( _rootTile )
+ {
+ // kdDebug() << "Auto-resizing treemap" << endl;
+ rebuildTreemap( _rootTile->orig() );
+ }
+ }
+}
+
+
+void
+KTreemapView::selectTile( KTreemapTile * tile )
+{
+ // kdDebug() << k_funcinfo << endl;
+
+ KTreemapTile * oldSelection = _selectedTile;
+ _selectedTile = tile;
+
+
+ // Handle selection (highlight) rectangle
+
+ if ( _selectedTile )
+ {
+ if ( ! _selectionRect )
+ _selectionRect = new KTreemapSelectionRect( canvas(), _highlightColor );
+ }
+
+ if ( _selectionRect )
+ _selectionRect->highlight( _selectedTile );
+
+ canvas()->update();
+
+ if ( oldSelection != _selectedTile )
+ {
+ emit selectionChanged( _selectedTile ? _selectedTile->orig() : 0 );
+ }
+}
+
+
+void
+KTreemapView::selectTile( KFileInfo * node )
+{
+ selectTile( findTile( node ) );
+}
+
+
+
+KTreemapTile *
+KTreemapView::findTile( KFileInfo * node )
+{
+ if ( ! node )
+ return 0;
+
+ QCanvasItemList itemList = canvas()->allItems();
+ QCanvasItemList::Iterator it = itemList.begin();
+
+ while ( it != itemList.end() )
+ {
+ KTreemapTile * tile = dynamic_cast<KTreemapTile *> (*it);
+
+ if ( tile && tile->orig() == node )
+ return tile;
+
+ ++it;
+ }
+
+ return 0;
+}
+
+
+QSize
+KTreemapView::visibleSize()
+{
+ ScrollBarMode oldHMode = hScrollBarMode();
+ ScrollBarMode oldVMode = vScrollBarMode();
+
+ setHScrollBarMode( AlwaysOff );
+ setVScrollBarMode( AlwaysOff );
+
+ QSize size = QSize( QCanvasView::visibleWidth(),
+ QCanvasView::visibleHeight() );
+
+ setHScrollBarMode( oldHMode );
+ setVScrollBarMode( oldVMode );
+
+ return size;
+}
+
+
+QColor
+KTreemapView::tileColor( KFileInfo * file )
+{
+ if ( file )
+ {
+ if ( file->isFile() )
+ {
+ // Find the filename extension: Everything after the first '.'
+ QString ext = file->name().section( '.', 1 );
+
+ while ( ! ext.isEmpty() )
+ {
+ QString lowerExt = ext.lower();
+
+ // Try case sensitive comparisions first
+
+ if ( ext == "~" ) return Qt::red;
+ if ( ext == "bak" ) return Qt::red;
+
+ if ( ext == "c" ) return Qt::blue;
+ if ( ext == "cpp" ) return Qt::blue;
+ if ( ext == "cc" ) return Qt::blue;
+ if ( ext == "h" ) return Qt::blue;
+ if ( ext == "hpp" ) return Qt::blue;
+ if ( ext == "el" ) return Qt::blue;
+
+ if ( ext == "o" ) return QColor( 0xff, 0xa0, 0x00 );
+ if ( ext == "lo" ) return QColor( 0xff, 0xa0, 0x00 );
+ if ( ext == "Po" ) return QColor( 0xff, 0xa0, 0x00 );
+ if ( ext == "al" ) return QColor( 0xff, 0xa0, 0x00 );
+ if ( ext == "moc.cpp" ) return QColor( 0xff, 0xa0, 0x00 );
+ if ( ext == "moc.cc" ) return QColor( 0xff, 0xa0, 0x00 );
+ if ( ext == "elc" ) return QColor( 0xff, 0xa0, 0x00 );
+ if ( ext == "la" ) return QColor( 0xff, 0xa0, 0x00 );
+ if ( ext == "a" ) return QColor( 0xff, 0xa0, 0x00 );
+ if ( ext == "rpm" ) return QColor( 0xff, 0xa0, 0x00 );
+
+ if ( lowerExt == "tar.bz2" ) return Qt::green;
+ if ( lowerExt == "tar.gz" ) return Qt::green;
+ if ( lowerExt == "tgz" ) return Qt::green;
+ if ( lowerExt == "bz2" ) return Qt::green;
+ if ( lowerExt == "bz" ) return Qt::green;
+ if ( lowerExt == "gz" ) return Qt::green;
+
+ if ( lowerExt == "html" ) return Qt::blue;
+ if ( lowerExt == "htm" ) return Qt::blue;
+ if ( lowerExt == "txt" ) return Qt::blue;
+ if ( lowerExt == "doc" ) return Qt::blue;
+
+ if ( lowerExt == "png" ) return Qt::cyan;
+ if ( lowerExt == "jpg" ) return Qt::cyan;
+ if ( lowerExt == "jpeg" ) return Qt::cyan;
+ if ( lowerExt == "gif" ) return Qt::cyan;
+ if ( lowerExt == "tif" ) return Qt::cyan;
+ if ( lowerExt == "tiff" ) return Qt::cyan;
+ if ( lowerExt == "bmp" ) return Qt::cyan;
+ if ( lowerExt == "xpm" ) return Qt::cyan;
+ if ( lowerExt == "tga" ) return Qt::cyan;
+
+ if ( lowerExt == "wav" ) return Qt::yellow;
+ if ( lowerExt == "mp3" ) return Qt::yellow;
+
+ if ( lowerExt == "avi" ) return QColor( 0xa0, 0xff, 0x00 );
+ if ( lowerExt == "mov" ) return QColor( 0xa0, 0xff, 0x00 );
+ if ( lowerExt == "mpg" ) return QColor( 0xa0, 0xff, 0x00 );
+ if ( lowerExt == "mpeg" ) return QColor( 0xa0, 0xff, 0x00 );
+
+ if ( lowerExt == "pdf" ) return Qt::blue;
+ if ( lowerExt == "ps" ) return Qt::cyan;
+
+
+ // Some DOS/Windows types
+
+ if ( lowerExt == "exe" ) return Qt::magenta;
+ if ( lowerExt == "com" ) return Qt::magenta;
+ if ( lowerExt == "dll" ) return QColor( 0xff, 0xa0, 0x00 );
+ if ( lowerExt == "zip" ) return Qt::green;
+ if ( lowerExt == "arj" ) return Qt::green;
+
+
+ // No match so far? Try the next extension. Some files might have
+ // more than one, e.g., "tar.bz2" - if there is no match for
+ // "tar.bz2", there might be one for just "bz2".
+
+ ext = ext.section( '.', 1 );
+ }
+
+ // Shared libs
+ if ( QRegExp( "lib.*\\.so.*" ).exactMatch( file->name() ) )
+ return QColor( 0xff, 0xa0, 0x00 );
+
+ // Very special, but common: Core dumps
+ if ( file->name() == "core" ) return Qt::red;
+
+ // Special case: Executables
+ if ( ( file->mode() & S_IXUSR ) == S_IXUSR ) return Qt::magenta;
+ }
+ else // Directories
+ {
+ // TO DO
+ return Qt::blue;
+ }
+ }
+
+ return Qt::white;
+}
+
+
+
+
+
+
+KTreemapSelectionRect::KTreemapSelectionRect( QCanvas * canvas, const QColor & color )
+ : QCanvasRectangle( canvas )
+{
+ setPen( QPen( color, 2 ) );
+ setZ( 1e10 ); // Higher than everything else
+}
+
+
+
+void
+KTreemapSelectionRect::highlight( KTreemapTile * tile )
+{
+ if ( tile )
+ {
+ QRect tileRect = tile->rect();
+
+ move( tileRect.x(), tileRect.y() );
+ setSize( tileRect.width(), tileRect.height() );
+
+ if ( ! isVisible() )
+ show();
+ }
+ else
+ {
+ if ( isVisible() )
+ hide();
+ }
+}
+
+
+
+// EOF
diff --git a/kdirstat/ktreemapview.h b/kdirstat/ktreemapview.h
new file mode 100644
index 0000000..86edd97
--- /dev/null
+++ b/kdirstat/ktreemapview.h
@@ -0,0 +1,446 @@
+/*
+ * File name: ktreemapview.h
+ * Summary: High level classes for KDirStat
+ * License: LGPL - See file COPYING.LIB for details.
+ * Author: Stefan Hundhammer <sh@suse.de>
+ *
+ * Updated: 2003-02-02
+ */
+
+
+#ifndef KTreemapView_h
+#define KTreemapView_h
+
+#include <qcanvas.h>
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+
+#define MinAmbientLight 0
+#define MaxAmbientLight 200
+#define DefaultAmbientLight 40
+
+#define MinHeightScalePercent 10
+#define MaxHeightScalePercent 200
+#define DefaultHeightScalePercent 100
+#define DefaultHeightScaleFactor ( DefaultHeightScalePercent / 100.0 )
+
+#define DefaultMinTileSize 3
+#define CushionHeight 1.0
+
+
+class QMouseEvent;
+class KConfig;
+
+namespace KDirStat
+{
+ class KTreemapTile;
+ class KTreemapSelectionRect;
+ class KDirTree;
+ class KFileInfo;
+
+ class KTreemapView: public QCanvasView
+ {
+ Q_OBJECT
+
+ public:
+ /**
+ * Constructor.
+ **/
+ KTreemapView( KDirTree * tree,
+ QWidget * parent = 0,
+ const QSize & initialSize = QSize() );
+
+ /**
+ * Destructor.
+ **/
+ virtual ~KTreemapView();
+
+ /**
+ * Returns the (topmost) treemap tile at the specified position
+ * or 0 if there is none.
+ **/
+ KTreemapTile * tileAt( QPoint pos );
+
+ /**
+ * Returns the minimum recommended size for this widget.
+ * Reimplemented from QWidget.
+ **/
+ virtual QSize minimumSizeHint() const { return QSize( 0, 0 ); }
+
+ /**
+ * Returns this treemap view's currently selected treemap tile or 0 if
+ * there is none.
+ **/
+ KTreemapTile * selectedTile() const { return _selectedTile; }
+
+
+ /**
+ * Returns this treemap view's root treemap tile or 0 if there is none.
+ **/
+ KTreemapTile * rootTile() const { return _rootTile; }
+
+ /**
+ * Returns this treemap view's @ref KDirTree.
+ **/
+ KDirTree * tree() const { return _tree; }
+
+ /**
+ * Search the treemap for a tile that corresponds to the specified
+ * KFileInfo node. Returns 0 if there is none.
+ *
+ * Notice: This is an expensive operation since all treemap tiles need
+ * to be searched.
+ **/
+ KTreemapTile * findTile( KFileInfo * node );
+
+ /**
+ * Returns a suitable color for 'file' based on a set of internal rules
+ * (according to filename extension, MIME type or permissions).
+ **/
+ QColor tileColor( KFileInfo * file );
+
+
+ public slots:
+
+ /**
+ * Make a treemap tile this treemap's selected tile.
+ * 'tile' may be 0. In this case, only the previous selection is
+ * deselected.
+ **/
+ void selectTile( KTreemapTile * tile );
+
+ /**
+ * Search the treemap for a tile with the specified KFileInfo node and
+ * select that tile if it is found. If nothing is found or if 'node' is
+ * 0, the previously selected tile is deselected.
+ **/
+ void selectTile( KFileInfo * node );
+
+ /**
+ * Zoom in one level towards the currently selected treemap tile:
+ * The entire treemap will be rebuilt with the near-topmost ancestor of
+ * the selected tile as the new root.
+ **/
+ void zoomIn();
+
+ /**
+ * Zoom out one level: The parent (if there is any) KFileInfo node of
+ * the current treemap root becomes the new root. This usually works
+ * only after zoomIn().
+ **/
+ void zoomOut();
+
+ /**
+ * Select the parent of the currently selected tile (if possible).
+ *
+ * This is very much the same as clicking with the middle mouse button,
+ * but not quite: The middle mouse button cycles back to the tile
+ * clicked at if there is no more parent. This method does not (because
+ * there is no known mouse position).
+ **/
+ void selectParent();
+
+ /**
+ * Completely rebuild the entire treemap from the internal tree's root
+ * on.
+ **/
+ void rebuildTreemap();
+
+ /**
+ * Clear the treemap contents.
+ **/
+ void clear();
+
+ /**
+ * Delete all items of a QCanvas.
+ *
+ * Strangely enough, QCanvas itself does not provide such a function.
+ **/
+ static void deleteAllItems( QCanvas * canvas );
+
+ /**
+ * Notification that a dir tree node has been deleted.
+ **/
+ void deleteNotify( KFileInfo * node );
+
+ /**
+ * Read some parameters from the global @ref KConfig object.
+ **/
+ void readConfig();
+
+ public:
+
+ /**
+ * Rebuild the treemap with 'newRoot' as the new root and the specified
+ * size. If 'newSize' is (0, 0), visibleSize() is used.
+ **/
+ void rebuildTreemap( KFileInfo * newRoot,
+ const QSize & newSize = QSize() );
+
+ /**
+ * Returns the visible size of the viewport presuming no scrollbars are
+ * needed - which makes a lot more sense than fiddling with scrollbars
+ * since treemaps can be scaled to make scrollbars unnecessary.
+ **/
+ QSize visibleSize();
+
+ /**
+ * Returns the visible width of the viewport presuming no scrollbars
+ * are needed.
+ *
+ * This uses visibleSize() which is a somewhat expensive operation, so
+ * if you need both visibleWidth() and visibleHeight(), better call
+ * visibleSize() once and access its width() and height() methods.
+ **/
+ int visibleWidth() { return visibleSize().width(); }
+
+ /**
+ * Returns the visible height of the viewport presuming no scrollbars
+ * are needed.
+ *
+ * This uses visibleSize() which is a somewhat expensive operation, so
+ * if you need both visibleWidth() and visibleHeight(), better call
+ * visibleSize() once and access its width() and height() methods.
+ **/
+ int visibleHeight() { return visibleSize().height(); }
+
+ /**
+ * Returns true if it is possible to zoom in with the currently
+ * selected tile, false if not.
+ **/
+ bool canZoomIn() const;
+
+ /**
+ * Returns true if it is possible to zoom out with the currently
+ * selected tile, false if not.
+ **/
+ bool canZoomOut() const;
+
+ /**
+ * Returns true if it is possible to select the parent of the currently
+ * selected tile, false if not.
+ **/
+ bool canSelectParent() const;
+
+ /**
+ * Returns 'true' if the treemap is automatically resized to fit into
+ * the available space, 'false' if not.
+ **/
+ bool autoResize() const { return _autoResize; }
+
+ /**
+ * Returns 'true' if treemap tiles are to be squarified upon creation,
+ * 'false' if not.
+ **/
+ bool squarify() const { return _squarify; }
+
+ /**
+ * Returns 'true' if cushion shading is to be used, 'false' if not.
+ **/
+ bool doCushionShading() const { return _doCushionShading; }
+
+ /**
+ * Returns 'true' if cushion shaded treemap tiles are to be separated
+ * by a grid, 'false' if not.
+ **/
+ bool forceCushionGrid() const { return _forceCushionGrid; }
+
+ /**
+ * Returns 'true' if tile boundary lines should be drawn for cushion
+ * treemaps, 'false' if not.
+ **/
+ bool ensureContrast() const { return _ensureContrast; }
+
+ /**
+ * Returns the minimum tile size in pixels. No treemap tiles less than
+ * this in width or height are desired.
+ **/
+ int minTileSize() const { return _minTileSize; }
+
+ /**
+ * Returns the cushion grid color.
+ **/
+ const QColor & cushionGridColor() const { return _cushionGridColor; }
+
+ /**
+ * Returns the outline color to use if cushion shading is not used.
+ **/
+ const QColor & outlineColor() const { return _outlineColor; }
+
+ /**
+ * Returns the fill color for non-directory treemap tiles when cushion
+ * shading is not used.
+ **/
+ const QColor & fileFillColor() const { return _fileFillColor; }
+
+ /**
+ * Returns the fill color for directory (or "dotentry") treemap tiles
+ * when cushion shading is not used.
+ **/
+ const QColor & dirFillColor() const { return _dirFillColor; }
+
+ /**
+ * Returns the intensity of ambient light for cushion shading
+ * [0..255]
+ **/
+ int ambientLight() const { return _ambientLight; }
+
+ /**
+ * Returns the X coordinate of a directed light source for cushion
+ * shading.
+ **/
+
+ double lightX() const { return _lightX; }
+
+ /**
+ * Returns the Y coordinate of a directed light source for cushion
+ * shading.
+ **/
+ double lightY() const { return _lightY; }
+
+ /**
+ * Returns the Z coordinate of a directed light source for cushion
+ * shading.
+ **/
+ double lightZ() const { return _lightZ; }
+
+ /**
+ * Returns cushion ridge height degradation factor (0 .. 1.0) for each
+ * level of subdivision.
+ **/
+ double heightScaleFactor() const { return _heightScaleFactor; }
+
+
+ signals:
+
+ /**
+ * Emitted when the currently selected item changes.
+ * Caution: 'item' may be 0 when the selection is cleared.
+ **/
+ void selectionChanged( KFileInfo * item );
+
+ /**
+ * Emitted when the treemap changes, e.g. is rebuilt, zoomed in, or
+ * zoomed out.
+ **/
+ void treemapChanged();
+
+ /**
+ * Emitted when a context menu for this tile should be opened.
+ * (usually on right click). 'pos' contains the click's mouse
+ * coordinates.
+ **/
+ void contextMenu( KTreemapTile * tile, const QPoint & pos );
+
+ /**
+ * Emitted at user activity. Some interactive actions are assigned an
+ * amount of "activity points" that can be used to judge whether or not
+ * the user is actually using this program or if it's just idly sitting
+ * around on the desktop. This is intended for use together with a @ref
+ * KActivityTracker.
+ **/
+ void userActivity( int points );
+
+
+ protected:
+
+ /**
+ * Catch mouse click - emits a selectionChanged() signal.
+ **/
+ virtual void contentsMousePressEvent( QMouseEvent * event );
+
+ /**
+ * Catch mouse double click:
+ * Left button double-click zooms in,
+ * right button double-click zooms out,
+ * middle button double-click rebuilds treemap.
+ **/
+ virtual void contentsMouseDoubleClickEvent( QMouseEvent * event );
+
+ /**
+ * Resize the treemap view. Suppress the treemap contents if the size
+ * falls below a minimum size, redisplay it if it grows above that
+ * minimum size.
+ *
+ * Reimplemented from QFrame.
+ **/
+ virtual void resizeEvent( QResizeEvent * event );
+
+ /**
+ * Convenience method to read a color from 'config'.
+ **/
+ QColor readColorEntry( KConfig * config,
+ const char * entryName,
+ QColor defaultColor );
+
+ // Data members
+
+ KDirTree * _tree;
+ KTreemapTile * _rootTile;
+ KTreemapTile * _selectedTile;
+ KTreemapSelectionRect * _selectionRect;
+ QString _savedRootUrl;
+
+ bool _autoResize;
+ bool _squarify;
+ bool _doCushionShading;
+ bool _forceCushionGrid;
+ bool _ensureContrast;
+ int _minTileSize;
+
+ QColor _highlightColor;
+ QColor _cushionGridColor;
+ QColor _outlineColor;
+ QColor _fileFillColor;
+ QColor _dirFillColor;
+
+ int _ambientLight;
+
+ double _lightX;
+ double _lightY;
+ double _lightZ;
+
+ double _heightScaleFactor;
+
+ }; // class KTreemapView
+
+
+
+ /**
+ * Transparent rectangle to make a treemap tile clearly visible as
+ * "selected". Leaf tiles could do that on their own, but higher-level
+ * tiles (corresponding to directories) are obscured for the most part, so
+ * only a small portion (if any) of their highlighted outline could be
+ * visible. This selection rectangle simply draws a two-pixel red outline
+ * on top (i.e., great z-height) of everything else. The rectangle is
+ * transparent, so the treemap tile contents remain visible.
+ **/
+ class KTreemapSelectionRect: public QCanvasRectangle
+ {
+ public:
+
+ /**
+ * Constructor.
+ **/
+ KTreemapSelectionRect( QCanvas * canvas, const QColor & color );
+
+ /**
+ * Highlight the specified treemap tile: Resize this selection
+ * rectangle to match this tile and move it to this tile's
+ * position. Show the selection rectangle if it is currently
+ * invisible.
+ **/
+ void highlight( KTreemapTile * tile );
+
+ }; // class KTreemapSelectionRect
+
+} // namespace KDirStat
+
+
+#endif // ifndef KTreemapView_h
+
+
+// EOF
diff --git a/kdirstat/lo16-app-kdirstat.png b/kdirstat/lo16-app-kdirstat.png
new file mode 100644
index 0000000..4bd140e
--- /dev/null
+++ b/kdirstat/lo16-app-kdirstat.png
Binary files differ
diff --git a/kdirstat/lo32-app-kdirstat.png b/kdirstat/lo32-app-kdirstat.png
new file mode 100644
index 0000000..d6c8d99
--- /dev/null
+++ b/kdirstat/lo32-app-kdirstat.png
Binary files differ
diff --git a/kdirstat/pics/Makefile.am b/kdirstat/pics/Makefile.am
new file mode 100644
index 0000000..8ae12f6
--- /dev/null
+++ b/kdirstat/pics/Makefile.am
@@ -0,0 +1,2 @@
+iconsdir = $(kde_datadir)/kdirstat/icons
+icons_ICON = AUTO
diff --git a/kdirstat/pics/hi16-action-symlink.png b/kdirstat/pics/hi16-action-symlink.png
new file mode 100644
index 0000000..95ea7d9
--- /dev/null
+++ b/kdirstat/pics/hi16-action-symlink.png
Binary files differ
diff --git a/kdirstat/pics/hi16-action-symlink.xcf b/kdirstat/pics/hi16-action-symlink.xcf
new file mode 100644
index 0000000..c269214
--- /dev/null
+++ b/kdirstat/pics/hi16-action-symlink.xcf
Binary files differ
diff --git a/kdirstat/pics/hi32-action-symlink.png b/kdirstat/pics/hi32-action-symlink.png
new file mode 100644
index 0000000..1f3248a
--- /dev/null
+++ b/kdirstat/pics/hi32-action-symlink.png
Binary files differ
diff --git a/kdirstat/pics/hi48-action-symlink.png b/kdirstat/pics/hi48-action-symlink.png
new file mode 100644
index 0000000..911bff4
--- /dev/null
+++ b/kdirstat/pics/hi48-action-symlink.png
Binary files differ
diff --git a/kdirstat/pics/hi48-action-symlink.xcf b/kdirstat/pics/hi48-action-symlink.xcf
new file mode 100644
index 0000000..e944b60
--- /dev/null
+++ b/kdirstat/pics/hi48-action-symlink.xcf
Binary files differ
diff --git a/kdirstat/pics/lo16-action-symlink.png b/kdirstat/pics/lo16-action-symlink.png
new file mode 100644
index 0000000..ade0d48
--- /dev/null
+++ b/kdirstat/pics/lo16-action-symlink.png
Binary files differ