diff options
author | tpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2010-02-10 01:15:27 +0000 |
---|---|---|
committer | tpearson <tpearson@283d02a7-25f6-0310-bc7c-ecb5cbfe19da> | 2010-02-10 01:15:27 +0000 |
commit | b6e09a3a8ea5f1338d089a29eb6e08f00f03f1aa (patch) | |
tree | 7dee2cbb5c94d3371357f796d42e344e1305ce9c /kdirstat | |
download | kdirstat-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')
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 Binary files differnew file mode 100644 index 0000000..4bd140e --- /dev/null +++ b/kdirstat/hi16-app-kdirstat.png diff --git a/kdirstat/hi32-app-kdirstat.png b/kdirstat/hi32-app-kdirstat.png Binary files differnew file mode 100644 index 0000000..d6c8d99 --- /dev/null +++ b/kdirstat/hi32-app-kdirstat.png 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>&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>&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 &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>&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>&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>&Report</text> + <Action name="report_mail_to_owner"/> + </Menu> + + + <Menu name="help" noMerge="1"> <text>&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 Binary files differnew file mode 100644 index 0000000..4bd140e --- /dev/null +++ b/kdirstat/lo16-app-kdirstat.png diff --git a/kdirstat/lo32-app-kdirstat.png b/kdirstat/lo32-app-kdirstat.png Binary files differnew file mode 100644 index 0000000..d6c8d99 --- /dev/null +++ b/kdirstat/lo32-app-kdirstat.png 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 Binary files differnew file mode 100644 index 0000000..95ea7d9 --- /dev/null +++ b/kdirstat/pics/hi16-action-symlink.png diff --git a/kdirstat/pics/hi16-action-symlink.xcf b/kdirstat/pics/hi16-action-symlink.xcf Binary files differnew file mode 100644 index 0000000..c269214 --- /dev/null +++ b/kdirstat/pics/hi16-action-symlink.xcf diff --git a/kdirstat/pics/hi32-action-symlink.png b/kdirstat/pics/hi32-action-symlink.png Binary files differnew file mode 100644 index 0000000..1f3248a --- /dev/null +++ b/kdirstat/pics/hi32-action-symlink.png diff --git a/kdirstat/pics/hi48-action-symlink.png b/kdirstat/pics/hi48-action-symlink.png Binary files differnew file mode 100644 index 0000000..911bff4 --- /dev/null +++ b/kdirstat/pics/hi48-action-symlink.png diff --git a/kdirstat/pics/hi48-action-symlink.xcf b/kdirstat/pics/hi48-action-symlink.xcf Binary files differnew file mode 100644 index 0000000..e944b60 --- /dev/null +++ b/kdirstat/pics/hi48-action-symlink.xcf diff --git a/kdirstat/pics/lo16-action-symlink.png b/kdirstat/pics/lo16-action-symlink.png Binary files differnew file mode 100644 index 0000000..ade0d48 --- /dev/null +++ b/kdirstat/pics/lo16-action-symlink.png |