summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.am3
-rw-r--r--src/app/Makefile.am8
-rw-r--r--src/app/historyAction.cpp96
-rw-r--r--src/app/historyAction.h64
-rw-r--r--src/app/main.cpp55
-rw-r--r--src/app/mainWindow.cpp280
-rw-r--r--src/app/mainWindow.h83
-rw-r--r--src/define.h17
-rw-r--r--src/part/Config.cpp62
-rw-r--r--src/part/Config.h41
-rw-r--r--src/part/Makefile.am19
-rw-r--r--src/part/debug.h36
-rw-r--r--src/part/dialog.ui574
-rw-r--r--src/part/fileTree.cpp67
-rw-r--r--src/part/fileTree.h238
-rw-r--r--src/part/localLister.cpp333
-rw-r--r--src/part/localLister.h35
-rw-r--r--src/part/part.cpp254
-rw-r--r--src/part/part.h71
-rw-r--r--src/part/progressBox.cpp65
-rw-r--r--src/part/progressBox.h32
-rw-r--r--src/part/radialMap/Makefile.am4
-rw-r--r--src/part/radialMap/builder.cpp141
-rw-r--r--src/part/radialMap/builder.h38
-rw-r--r--src/part/radialMap/labels.cpp327
-rw-r--r--src/part/radialMap/map.cpp442
-rw-r--r--src/part/radialMap/radialMap.h72
-rw-r--r--src/part/radialMap/segmentTip.cpp186
-rw-r--r--src/part/radialMap/segmentTip.h34
-rw-r--r--src/part/radialMap/sincos.h25
-rw-r--r--src/part/radialMap/widget.cpp187
-rw-r--r--src/part/radialMap/widget.h114
-rw-r--r--src/part/radialMap/widgetEvents.cpp275
-rw-r--r--src/part/remoteLister.cpp160
-rw-r--r--src/part/remoteLister.h28
-rw-r--r--src/part/scan.cpp204
-rw-r--r--src/part/scan.h52
-rw-r--r--src/part/settingsDialog.cpp214
-rw-r--r--src/part/settingsDialog.h48
-rw-r--r--src/part/summaryWidget.cpp236
-rw-r--r--src/part/summaryWidget.h25
41 files changed, 5245 insertions, 0 deletions
diff --git a/src/Makefile.am b/src/Makefile.am
new file mode 100644
index 0000000..d47a98a
--- /dev/null
+++ b/src/Makefile.am
@@ -0,0 +1,3 @@
+SUBDIRS = app part
+INCLUDES = $(all_includes)
+METASOURCES = AUTO
diff --git a/src/app/Makefile.am b/src/app/Makefile.am
new file mode 100644
index 0000000..d0ae17d
--- /dev/null
+++ b/src/app/Makefile.am
@@ -0,0 +1,8 @@
+INCLUDES = $(all_includes) -I$(top_srcdir)/src
+METASOURCES = AUTO
+
+# Application
+bin_PROGRAMS = filelight
+filelight_SOURCES = historyAction.cpp mainWindow.cpp main.cpp
+filelight_LDADD = $(LIB_KPARTS) $(LIB_KDEUI)
+filelight_LDFLAGS = $(all_libraries) $(KDE_RPATH)
diff --git a/src/app/historyAction.cpp b/src/app/historyAction.cpp
new file mode 100644
index 0000000..bf715cb
--- /dev/null
+++ b/src/app/historyAction.cpp
@@ -0,0 +1,96 @@
+//Author: Max Howell <[email protected]>, (C) 2003-4
+//Copyright: See COPYING file that comes with this distribution
+
+#include "historyAction.h"
+
+#include <kaccel.h>
+#include <kconfig.h>
+#include <klocale.h>
+
+
+inline
+HistoryAction::HistoryAction( const QString &text, const char *icon, const KShortcut &cut, KActionCollection *ac, const char *name )
+ : KAction( text, icon, cut, 0, 0, ac, name )
+ , m_text( text )
+{
+ // ui files make this false, but we can't rely on UI file as it isn't compiled in :(
+ KAction::setEnabled( false );
+}
+
+void
+HistoryAction::push( const QString &path )
+{
+ if( !path.isEmpty() && m_list.last() != path )
+ {
+ m_list.append( path );
+ setActionMenuTextOnly( this, path );
+ KAction::setEnabled( true );
+ }
+}
+
+QString
+HistoryAction::pop()
+{
+ const QString s = m_list.last();
+ m_list.pop_back();
+ setActionMenuTextOnly( this, m_list.last() );
+ setEnabled();
+ return s;
+}
+
+
+
+HistoryCollection::HistoryCollection( KActionCollection *ac, QObject *parent, const char *name )
+ : QObject( parent, name )
+ , m_b( new HistoryAction( i18n( "Back" ), "back", KStdAccel::back(), ac, "go_back" ) )
+ , m_f( new HistoryAction( i18n( "Forward" ), "forward", KStdAccel::forward(), ac, "go_forward" ) )
+ , m_receiver( 0 )
+{
+ connect( m_b, SIGNAL(activated()), SLOT(pop()) );
+ connect( m_f, SIGNAL(activated()), SLOT(pop()) );
+}
+
+void
+HistoryCollection::push( const KURL &url ) //slot
+{
+ if( !url.isEmpty() )
+ {
+ if( !m_receiver )
+ {
+ m_f->clear();
+ m_receiver = m_b;
+ }
+
+ m_receiver->push( url.path( 1 ) );
+ }
+ m_receiver = 0;
+}
+
+void
+HistoryCollection::pop() //slot
+{
+ KURL url;
+ const QString path = ((HistoryAction*)sender())->pop(); //FIXME here we remove the constness
+ url.setPath( path );
+
+ m_receiver = (sender() == m_b) ? m_f : m_b;
+
+ emit activated( url );
+}
+
+void
+HistoryCollection::save( KConfig *config )
+{
+ config->writePathEntry( "backHistory", m_b->m_list );
+ config->writePathEntry( "forwardHistory", m_f->m_list );
+}
+
+void
+HistoryCollection::restore( KConfig *config )
+{
+ m_b->m_list = config->readPathListEntry( "backHistory" );
+ m_f->m_list = config->readPathListEntry( "forwardHistory" );
+ //TODO texts are not updated - no matter
+}
+
+#include "historyAction.moc"
diff --git a/src/app/historyAction.h b/src/app/historyAction.h
new file mode 100644
index 0000000..773093c
--- /dev/null
+++ b/src/app/historyAction.h
@@ -0,0 +1,64 @@
+//Author: Max Howell <[email protected]>, (C) 2003-4
+//Copyright: See COPYING file that comes with this distribution
+
+#ifndef HISTORYACTION_H
+#define HISTORYACTION_H
+
+#include <kaction.h>
+#include <kurl.h>
+#include <qstringlist.h>
+
+class KConfig;
+
+
+/// defined in mainWindow.cpp
+void setActionMenuTextOnly( KAction *a, QString const &suffix );
+
+
+class HistoryAction : KAction
+{
+ HistoryAction( const QString &text, const char *icon, const KShortcut &cut, KActionCollection *ac, const char *name );
+
+ friend class HistoryCollection;
+
+public:
+ virtual void setEnabled( bool b = true ) { KAction::setEnabled( b ? !m_list.isEmpty() : false ); }
+
+ void clear() { m_list.clear(); KAction::setText( m_text ); }
+
+private:
+ void setText();
+
+ void push( const QString &path );
+ QString pop();
+
+ const QString m_text;
+ QStringList m_list;
+};
+
+
+class HistoryCollection : public QObject
+{
+Q_OBJECT
+
+public:
+ HistoryCollection( KActionCollection *ac, QObject *parent, const char *name );
+
+ void save( KConfig *config );
+ void restore( KConfig *config );
+
+public slots:
+ void push( const KURL& );
+ void stop() { m_receiver = 0; }
+
+signals:
+ void activated( const KURL& );
+
+private slots:
+ void pop();
+
+private:
+ HistoryAction *m_b, *m_f, *m_receiver;
+};
+
+#endif
diff --git a/src/app/main.cpp b/src/app/main.cpp
new file mode 100644
index 0000000..17e6989
--- /dev/null
+++ b/src/app/main.cpp
@@ -0,0 +1,55 @@
+//Author: Max Howell <[email protected]>, (C) 2003-4
+//Copyright: See COPYING file that comes with this distribution
+
+#include "define.h"
+#include <kaboutdata.h>
+#include <kapplication.h>
+#include <kcmdlineargs.h>
+#include <klocale.h>
+#include <kurl.h>
+#include "mainWindow.h"
+
+
+static const KCmdLineOptions options[] =
+{
+ { "+[path]", I18N_NOOP( "Scan 'path'" ), 0 },
+ { 0, 0, 0 }
+};
+
+static KAboutData about(
+ APP_NAME, I18N_NOOP( APP_PRETTYNAME ), APP_VERSION,
+ I18N_NOOP("Graphical disk-usage information"), KAboutData::License_GPL_V2,
+ I18N_NOOP("(C )2006 Max Howell"), 0,
+ "http://www.methylblue.com/filelight/", "[email protected]" );
+
+
+int main( int argc, char *argv[] )
+{
+ using Filelight::MainWindow;
+
+ about.addAuthor( "Max Howell", I18N_NOOP("Author, maintainer"), "[email protected]", "http://www.methylblue.com/" );
+ about.addAuthor( "Mike Diehl", I18N_NOOP("Documentation"), 0, 0 );
+ about.addCredit( "Steffen Gerlach", I18N_NOOP("Inspiration"), 0, "http://www.steffengerlach.de/" );
+ about.addCredit( "André Somers", I18N_NOOP("Internationalization") );
+ about.addCredit( "Stephanie James", I18N_NOOP("Testing") );
+ about.addCredit( "Marcus Camen", I18N_NOOP("Bravery in the face of unreadable code") );
+
+ KCmdLineArgs::init( argc, argv, &about );
+ KCmdLineArgs::addCmdLineOptions( options );
+
+ KApplication app;
+
+ if (!app.isRestored()) {
+ MainWindow *mw = new MainWindow();
+ app.setMainWidget( mw );
+
+ KCmdLineArgs* const args = KCmdLineArgs::parsedArgs();
+ if (args->count() > 0 ) mw->scan( args->url( 0 ));
+ args->clear();
+
+ mw->show();
+ }
+ else RESTORE( MainWindow );
+
+ return app.exec();
+}
diff --git a/src/app/mainWindow.cpp b/src/app/mainWindow.cpp
new file mode 100644
index 0000000..155ece0
--- /dev/null
+++ b/src/app/mainWindow.cpp
@@ -0,0 +1,280 @@
+//Author: Max Howell <[email protected]>, (C) 2003-4
+//Copyright: See COPYING file that comes with this distribution
+
+#include "mainWindow.h"
+#include "part/part.h"
+#include "historyAction.h"
+
+#include <cstdlib> //std::exit()
+#include <kaccel.h> //KStdAccel namespace
+#include <kaction.h>
+#include <kapplication.h> //setupActions()
+#include <kcombobox.h> //locationbar
+#include <kconfig.h>
+#include <kdirselectdialog.h> //slotScanDirectory
+#include <kedittoolbar.h> //for editToolbar dialog
+#include <kkeydialog.h>
+#include <klibloader.h>
+#include <klocale.h>
+#include <kmessagebox.h>
+#include <kshell.h>
+#include <kstatusbar.h>
+#include <ktoolbar.h>
+#include <kurl.h>
+#include <kurlcompletion.h> //locationbar
+#include <qobjectlist.h>
+#include <qpopupmenu.h>
+#include <qtooltip.h>
+
+
+
+namespace Filelight {
+
+MainWindow::MainWindow()
+ : KParts::MainWindow()
+ , m_part( 0 )
+{
+ KLibFactory *factory = KLibLoader::self()->factory( "libfilelight" );
+
+ if (!factory) {
+ KMessageBox::error( this, i18n("KDE could not find the Filelight Part, or the Filelight Part could not be started. Did you make install?") );
+ //exit() seems to not exist inside the std namespace for some users!
+ using namespace std;
+ exit( 1 ); //don't use QApplication::exit() - it causes a crash
+ }
+
+ m_part = (Part *)factory->create( this, "part", "KParts::ReadOnlyPart" );
+
+ setCentralWidget( m_part->widget() );
+ setStandardToolBarMenuEnabled( true );
+ setupActions();
+ createGUI( m_part );
+
+ stateChanged( "scan_failed" ); //bah! doesn't affect the parts' actions, should I add them to the actionCollection here?
+
+ QObjectList *buttons = toolBar()->queryList( "KToolBarButton" );
+ if (buttons->isEmpty())
+ KMessageBox::error( this, i18n("Filelight is not installed properly, consequently its menus and toolbars will appear reduced or even empty") );
+ delete buttons;
+
+ connect( m_part, SIGNAL(started( KIO::Job* )), SLOT(scanStarted()) );
+ connect( m_part, SIGNAL(completed()), SLOT(scanCompleted()) );
+ connect( m_part, SIGNAL(canceled( const QString& )), SLOT(scanFailed()) );
+
+ //TODO test these
+ connect( m_part, SIGNAL(canceled( const QString& )), m_histories, SLOT(stop()) );
+ connect( BrowserExtension::childObject( m_part ), SIGNAL(openURLNotify()), SLOT(urlAboutToChange()) );
+
+ KConfig* const config = KGlobal::config();
+ config->setGroup( "general" );
+ m_combo->setHistoryItems( config->readPathListEntry( "comboHistory" ) );
+ applyMainWindowSettings( config, "window" );
+}
+
+inline void
+MainWindow::setupActions() //singleton function
+{
+ KActionCollection *const ac = actionCollection();
+
+ m_combo = new KHistoryCombo( this, "history_combo" );
+ m_combo->setCompletionObject( new KURLCompletion( KURLCompletion::DirCompletion ) );
+ m_combo->setAutoDeleteCompletionObject( true );
+ m_combo->setSizePolicy( QSizePolicy( QSizePolicy::Expanding, QSizePolicy::Fixed ) );
+ m_combo->setDuplicatesEnabled( false );
+
+ KStdAction::open( this, SLOT(slotScanDirectory()), ac, "scan_directory" );
+ KStdAction::quit( this, SLOT(close()), ac );
+ KStdAction::up( this, SLOT(slotUp()), ac );
+ KStdAction::configureToolbars(this, SLOT(configToolbars()), ac);
+ KStdAction::keyBindings(this, SLOT(configKeys()), ac);
+
+ new KAction( i18n( "Scan &Home Directory" ), "folder_home", CTRL+Key_Home, this, SLOT(slotScanHomeDirectory()), ac, "scan_home" );
+ new KAction( i18n( "Scan &Root Directory" ), "folder_red", 0, this, SLOT(slotScanRootDirectory()), ac, "scan_root" );
+ new KAction( i18n( "Rescan" ), "reload", KStdAccel::reload(), m_part, SLOT(rescan()), ac, "scan_rescan" );
+ new KAction( i18n( "Stop" ), "stop", Qt::Key_Escape, this, SLOT(slotAbortScan()), ac, "scan_stop" );
+ new KAction( i18n( "Clear Location Bar" ), KApplication::reverseLayout() ? "clear_left" : "locationbar_erase", 0, m_combo, SLOT(clearEdit()), ac, "clear_location" );
+ new KAction( i18n( "Go" ), "key_enter", 0, m_combo, SIGNAL(returnPressed()), ac, "go" );
+
+ KWidgetAction *combo = new KWidgetAction( m_combo, i18n( "Location Bar" ), 0, 0, 0, ac, "location_bar" );
+ m_recentScans = new KRecentFilesAction( i18n( "&Recent Scans" ), 0, ac, "scan_recent", 8 );
+ m_histories = new HistoryCollection( ac, this, "history_collection" );
+
+ ac->action( "scan_directory" )->setText( i18n( "&Scan Directory..." ) );
+ m_recentScans->loadEntries( KGlobal::config() );
+ combo->setAutoSized( true ); //FIXME what does this do?
+
+ connect( m_recentScans, SIGNAL(urlSelected( const KURL& )), SLOT(slotScanUrl( const KURL& )) );
+ connect( m_combo, SIGNAL(returnPressed()), SLOT(slotComboScan()) );
+ connect( m_histories, SIGNAL(activated( const KURL& )), SLOT(slotScanUrl( const KURL& )) );
+}
+
+bool
+MainWindow::queryExit()
+{
+ if( !m_part ) //apparently std::exit() still calls this function, and abort() causes a crash..
+ return true;
+
+ KConfig* const config = KGlobal::config();
+
+ saveMainWindowSettings( config, "window" );
+ m_recentScans->saveEntries( config );
+ config->setGroup( "general" );
+ config->writePathEntry( "comboHistory", m_combo->historyItems() );
+ config->sync();
+
+ return true;
+}
+
+inline void
+MainWindow::configToolbars() //slot
+{
+ KEditToolbar dialog( factory(), this );
+ dialog.showButtonApply( false );
+
+ if( dialog.exec() )
+ {
+ createGUI( m_part );
+ applyMainWindowSettings( kapp->config(), "window" );
+ }
+}
+
+inline void
+MainWindow::configKeys() //slot
+{
+ KKeyDialog::configure( actionCollection(), this );
+}
+
+inline void
+MainWindow::slotScanDirectory()
+{
+ slotScanUrl( KDirSelectDialog::selectDirectory( m_part->url().path(), false, this ) );
+}
+
+inline void MainWindow::slotScanHomeDirectory() { slotScanPath( getenv( "HOME" ) ); }
+inline void MainWindow::slotScanRootDirectory() { slotScanPath( "/" ); }
+inline void MainWindow::slotUp() { slotScanUrl( m_part->url().upURL() ); }
+
+inline void
+MainWindow::slotComboScan()
+{
+ const QString path = KShell::tildeExpand(m_combo->lineEdit()->text());
+ if (slotScanPath( path ))
+ m_combo->addToHistory( path );
+}
+
+inline bool
+MainWindow::slotScanPath( const QString &path )
+{
+ return slotScanUrl( KURL::fromPathOrURL( path ) );
+}
+
+bool
+MainWindow::slotScanUrl( const KURL &url )
+{
+ const KURL oldUrl = m_part->url();
+ const bool b = m_part->openURL( url );
+
+ if (b) {
+ m_histories->push( oldUrl );
+ action( "go_back" )->KAction::setEnabled( false ); } //FIXME
+
+ return b;
+}
+
+inline void
+MainWindow::slotAbortScan()
+{
+ if( m_part->closeURL() ) action( "scan_stop" )->setEnabled( false );
+}
+
+inline void
+MainWindow::scanStarted()
+{
+ stateChanged( "scan_started" );
+ m_combo->clearFocus();
+}
+
+inline void
+MainWindow::scanFailed()
+{
+ stateChanged( "scan_failed" );
+ setActionMenuTextOnly( action( "go_up" ), QString::null );
+ m_combo->lineEdit()->clear();
+}
+
+void
+MainWindow::scanCompleted()
+{
+ KAction *goUp = action( "go_up" );
+ const KURL url = m_part->url();
+
+ stateChanged( "scan_complete" );
+
+ m_combo->lineEdit()->setText( m_part->prettyURL() );
+
+ if ( url.path( 1 ) == "/") {
+ goUp->setEnabled( false );
+ setActionMenuTextOnly( goUp, QString() );
+ }
+ else
+ setActionMenuTextOnly( goUp, url.upURL().path( 1 ) );
+
+ m_recentScans->addURL( url ); //FIXME doesn't set the tick
+}
+
+inline void
+MainWindow::urlAboutToChange()
+{
+ //called when part's URL is about to change internally
+ //the part will then create the Map and emit completed()
+
+ m_histories->push( m_part->url() );
+}
+
+
+/**********************************************
+ SESSION MANAGEMENT
+ **********************************************/
+
+void
+MainWindow::saveProperties( KConfig *config ) //virtual
+{
+ m_histories->save( config );
+ config->writeEntry( "currentMap", m_part->url().path() );
+}
+
+void
+MainWindow::readProperties( KConfig *config ) //virtual
+{
+ m_histories->restore( config );
+ slotScanPath( config->readEntry( "currentMap", QString::null ) );
+}
+
+} //namespace Filelight
+
+
+
+/// declared in historyAction.h
+
+void setActionMenuTextOnly( KAction *a, QString const &suffix )
+{
+ QString const menu_text = suffix.isEmpty()
+ ? a->text()
+ : i18n( "&Up: /home/mxcl", "%1: %2" ).arg( a->text(), suffix );
+
+ for (int i = 0; i < a->containerCount(); ++i) {
+ QWidget *w = a->container( i );
+ int const id = a->itemId( i );
+
+ if (w->inherits( "QPopupMenu" ))
+ static_cast<QPopupMenu*>(w)->changeItem( id, menu_text );
+
+ else if (w->inherits( "KToolBar" )) {
+ QWidget *button = static_cast<KToolBar*>(w)->getWidget( id );
+ if (button->inherits( "KToolBarButton" ))
+ QToolTip::add( button, suffix );
+ }
+ }
+}
+
+#include "mainWindow.moc"
diff --git a/src/app/mainWindow.h b/src/app/mainWindow.h
new file mode 100644
index 0000000..87001c8
--- /dev/null
+++ b/src/app/mainWindow.h
@@ -0,0 +1,83 @@
+/***************************************************************************
+ filelight.h - description
+ -------------------
+ begin : Mon May 12 22:38:30 BST 2003
+ copyright : (C) 2003 by Max Howell
+ ***************************************************************************/
+
+/***************************************************************************
+ * *
+ * This program is free software; you can redistribute it and/or modify *
+ * it under the terms of the GNU General Public License as published by *
+ * the Free Software Foundation; either version 2 of the License, or *
+ * (at your option) any later version. *
+ * *
+ ***************************************************************************/
+
+#ifndef FILELIGHT_H
+#define FILELIGHT_H
+
+#include <kparts/mainwindow.h>
+
+class KSqueezedTextLabel;
+class KHistoryCombo;
+class KAction;
+class KRecentFilesAction;
+
+class ScanProgressBox;
+class HistoryCollection;
+
+
+namespace Filelight {
+
+class Part;
+
+class MainWindow : public KParts::MainWindow
+{
+ Q_OBJECT
+
+ public:
+ MainWindow();
+
+ void scan( const KURL &u ) { slotScanUrl( u ); }
+
+ private slots:
+ void slotUp();
+ void slotComboScan();
+ void slotScanDirectory();
+ void slotScanHomeDirectory();
+ void slotScanRootDirectory();
+ bool slotScanUrl( const KURL& );
+ bool slotScanPath( const QString& );
+ void slotAbortScan();
+
+ void configToolbars();
+ void configKeys();
+
+ void scanStarted();
+ void scanFailed();
+ void scanCompleted();
+
+ void urlAboutToChange();
+
+ protected:
+ virtual void saveProperties( KConfig * );
+ virtual void readProperties( KConfig * );
+ virtual bool queryExit();
+
+ private:
+ Filelight::Part *m_part;
+
+ KSqueezedTextLabel *m_status[2];
+ KHistoryCombo *m_combo;
+ HistoryCollection *m_histories;
+ KRecentFilesAction *m_recentScans;
+
+ void setupStatusBar();
+ void setupActions();
+};
+
+} // namespace Filelight
+
+#endif
diff --git a/src/define.h b/src/define.h
new file mode 100644
index 0000000..80571f6
--- /dev/null
+++ b/src/define.h
@@ -0,0 +1,17 @@
+//Author: Max Howell <[email protected]>, (C) 2003-4
+//Copyright: See COPYING file that comes with this distribution
+
+#ifndef DEFINE_H
+#define DEFINE_H
+
+#include "config.h"
+
+#undef VERSION
+#undef PACKAGE
+#undef PRETTYNAME
+
+#define APP_NAME "filelight"
+#define APP_VERSION "1.0"
+#define APP_PRETTYNAME "Filelight"
+
+#endif
diff --git a/src/part/Config.cpp b/src/part/Config.cpp
new file mode 100644
index 0000000..8d2f6b8
--- /dev/null
+++ b/src/part/Config.cpp
@@ -0,0 +1,62 @@
+
+#include "Config.h"
+#include <kconfig.h>
+#include <kglobal.h>
+
+
+bool Config::scanAcrossMounts;
+bool Config::scanRemoteMounts;
+bool Config::scanRemovableMedia;
+bool Config::varyLabelFontSizes;
+bool Config::showSmallFiles;
+uint Config::contrast;
+uint Config::antiAliasFactor;
+uint Config::minFontPitch;
+uint Config::defaultRingDepth;
+Filelight::MapScheme Config::scheme;
+QStringList Config::skipList;
+
+
+inline KConfig&
+Filelight::Config::kconfig()
+{
+ KConfig *config = KGlobal::config();
+ config->setGroup( "filelight_part" );
+ return *config;
+}
+
+void
+Filelight::Config::read()
+{
+ const KConfig &config = kconfig();
+
+ scanAcrossMounts = config.readBoolEntry( "scanAcrossMounts", false );
+ scanRemoteMounts = config.readBoolEntry( "scanRemoteMounts", false );
+ scanRemovableMedia = config.readBoolEntry( "scanRemovableMedia", false );
+ varyLabelFontSizes = config.readBoolEntry( "varyLabelFontSizes", true );
+ showSmallFiles = config.readBoolEntry( "showSmallFiles", false );
+ contrast = config.readNumEntry( "contrast", 75 );
+ antiAliasFactor = config.readNumEntry( "antiAliasFactor", 2 );
+ minFontPitch = config.readNumEntry( "minFontPitch", QFont().pointSize() - 3);
+ scheme = (MapScheme) config.readNumEntry( "scheme", 0 );
+ skipList = config.readPathListEntry( "skipList" );
+
+ defaultRingDepth = 4;
+}
+
+void
+Filelight::Config::write()
+{
+ KConfig &config = kconfig();
+
+ config.writeEntry( "scanAcrossMounts", scanAcrossMounts );
+ config.writeEntry( "scanRemoteMounts", scanRemoteMounts );
+ config.writeEntry( "scanRemovableMedia", scanRemovableMedia );
+ config.writeEntry( "varyLabelFontSizes", varyLabelFontSizes );
+ config.writeEntry( "showSmallFiles", showSmallFiles);
+ config.writeEntry( "contrast", contrast );
+ config.writeEntry( "antiAliasFactor", antiAliasFactor );
+ config.writeEntry( "minFontPitch", minFontPitch );
+ config.writeEntry( "scheme", scheme );
+ config.writePathEntry( "skipList", skipList );
+}
diff --git a/src/part/Config.h b/src/part/Config.h
new file mode 100644
index 0000000..dffaa95
--- /dev/null
+++ b/src/part/Config.h
@@ -0,0 +1,41 @@
+
+#ifndef Config_H
+#define Config_H
+
+#include <qstringlist.h>
+
+class KConfig;
+
+
+namespace Filelight
+{
+ enum MapScheme { Rainbow, HighContrast, KDE, FileDensity, ModTime };
+
+ class Config
+ {
+ static KConfig& kconfig();
+
+ public:
+ static void read();
+ static void write();
+
+ //keep everything positive, avoid using DON'T, NOT or NO
+
+ static bool scanAcrossMounts;
+ static bool scanRemoteMounts;
+ static bool scanRemovableMedia;
+ static bool varyLabelFontSizes;
+ static bool showSmallFiles;
+ static uint contrast;
+ static uint antiAliasFactor;
+ static uint minFontPitch;
+ static uint defaultRingDepth;
+
+ static MapScheme scheme;
+ static QStringList skipList;
+ };
+}
+
+using Filelight::Config;
+
+#endif
diff --git a/src/part/Makefile.am b/src/part/Makefile.am
new file mode 100644
index 0000000..2ff41f8
--- /dev/null
+++ b/src/part/Makefile.am
@@ -0,0 +1,19 @@
+SUBDIRS = radialMap
+INCLUDES = $(all_includes) -I$(top_srcdir)/src
+METASOURCES = AUTO
+
+#Part
+kde_module_LTLIBRARIES = libfilelight.la
+libfilelight_la_LIBADD = ./radialMap/libradialmap.la $(LIB_KFILE) $(LIB_KPARTS) $(LIB_KDEUI) $(LIB_QT)
+libfilelight_la_LDFLAGS = $(all_libraries) $(KDE_PLUGIN)
+libfilelight_la_SOURCES = \
+ dialog.ui \
+ part.cpp \
+ scan.cpp \
+ progressBox.cpp \
+ Config.cpp \
+ settingsDialog.cpp \
+ fileTree.cpp \
+ localLister.cpp \
+ remoteLister.cpp \
+ summaryWidget.cpp
diff --git a/src/part/debug.h b/src/part/debug.h
new file mode 100644
index 0000000..e5e680b
--- /dev/null
+++ b/src/part/debug.h
@@ -0,0 +1,36 @@
+//Author: Max Howell <[email protected]>, (C) 2003-4
+//Copyright: See COPYING file that comes with this distribution
+
+#ifndef DEBUG_H
+#define DEBUG_H
+
+/** Fancy debug header
+ * @author Max Howell
+ *
+ * Define DEBUG_PREFIX as a string before you include this to insert a fancy debug prefix
+ * Debug::debug(), can be used as debug() and is just kdDebug()
+ * use Debug::indent() and Debug::unindent()
+ */
+
+#include <kdebug.h>
+
+#ifdef NDEBUG
+static inline kndbgstream debug() { return kndbgstream(); }
+#else
+static inline kdbgstream debug()
+{
+ return kdbgstream(
+ #ifdef DEBUG_PREFIX
+ "[" DEBUG_PREFIX "] ",
+ #endif
+ 0, 0 );
+}
+#endif
+
+#define error kdError
+#define fatal kdFatal
+#define warning kdWarning
+
+#define DEBUG_ANNOUNCE debug() << ">> " << __PRETTY_FUNCTION__ << endl;
+
+#endif
diff --git a/src/part/dialog.ui b/src/part/dialog.ui
new file mode 100644
index 0000000..2c1d788
--- /dev/null
+++ b/src/part/dialog.ui
@@ -0,0 +1,574 @@
+<!DOCTYPE UI><UI version="3.2" stdsetdef="1">
+<class>Dialog</class>
+<widget class="QDialog">
+ <property name="name">
+ <cstring>Dialog</cstring>
+ </property>
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>415</width>
+ <height>351</height>
+ </rect>
+ </property>
+ <property name="caption">
+ <string>Settings - Filelight</string>
+ </property>
+ <vbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="QTabWidget">
+ <property name="name">
+ <cstring>tabWidget</cstring>
+ </property>
+ <property name="acceptDrops">
+ <bool>false</bool>
+ </property>
+ <widget class="QWidget">
+ <property name="name">
+ <cstring>Widget2</cstring>
+ </property>
+ <attribute name="title">
+ <string>Scannin&amp;g</string>
+ </attribute>
+ <vbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="QLabel">
+ <property name="name">
+ <cstring>textLabel1</cstring>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy>
+ <hsizetype>5</hsizetype>
+ <vsizetype>4</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string>Do &amp;not scan these directories:</string>
+ </property>
+ <property name="textFormat">
+ <enum>PlainText</enum>
+ </property>
+ <property name="buddy" stdset="0">
+ <cstring>m_listBox</cstring>
+ </property>
+ </widget>
+ <widget class="QListBox">
+ <property name="name">
+ <cstring>m_listBox</cstring>
+ </property>
+ <property name="toolTip" stdset="0">
+ <string></string>
+ </property>
+ <property name="whatsThis" stdset="0">
+ <string>Filelight will not scan these directories unless you specifically request them.</string>
+ </property>
+ </widget>
+ <widget class="QLayoutWidget">
+ <property name="name">
+ <cstring>layout4</cstring>
+ </property>
+ <hbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <spacer>
+ <property name="name">
+ <cstring>spacer2</cstring>
+ </property>
+ <property name="orientation">
+ <enum>Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>Expanding</enum>
+ </property>
+ <property name="sizeHint">
+ <size>
+ <width>180</width>
+ <height>21</height>
+ </size>
+ </property>
+ </spacer>
+ <widget class="QPushButton">
+ <property name="name">
+ <cstring>m_removeButton</cstring>
+ </property>
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="text">
+ <string>R&amp;emove</string>
+ </property>
+ <property name="autoDefault">
+ <bool>false</bool>
+ </property>
+ </widget>
+ <widget class="QPushButton">
+ <property name="name">
+ <cstring>m_addButton</cstring>
+ </property>
+ <property name="text">
+ <string>&amp;Add...</string>
+ </property>
+ <property name="autoDefault">
+ <bool>false</bool>
+ </property>
+ <property name="default">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </hbox>
+ </widget>
+ <widget class="Line">
+ <property name="name">
+ <cstring>line1</cstring>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>15</height>
+ </size>
+ </property>
+ <property name="frameShape">
+ <enum>HLine</enum>
+ </property>
+ <property name="frameShadow">
+ <enum>Sunken</enum>
+ </property>
+ <property name="lineWidth">
+ <number>1</number>
+ </property>
+ <property name="midLineWidth">
+ <number>1</number>
+ </property>
+ <property name="orientation">
+ <enum>Horizontal</enum>
+ </property>
+ </widget>
+ <widget class="QLayoutWidget">
+ <property name="name">
+ <cstring>layout7</cstring>
+ </property>
+ <grid>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <spacer row="1" column="0" rowspan="2" colspan="1">
+ <property name="name">
+ <cstring>spacer3</cstring>
+ </property>
+ <property name="orientation">
+ <enum>Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>Fixed</enum>
+ </property>
+ <property name="sizeHint">
+ <size>
+ <width>16</width>
+ <height>50</height>
+ </size>
+ </property>
+ </spacer>
+ <widget class="QCheckBox" row="1" column="1">
+ <property name="name">
+ <cstring>dontScanRemoteMounts</cstring>
+ </property>
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maximumSize">
+ <size>
+ <width>32767</width>
+ <height>32767</height>
+ </size>
+ </property>
+ <property name="acceptDrops">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>Exclude remote files&amp;ystems</string>
+ </property>
+ <property name="toolTip" stdset="0">
+ <string></string>
+ </property>
+ <property name="whatsThis" stdset="0">
+ <string>Prevents scanning of filesystems that are not on this computer, e.g. NFS or Samba mounts.</string>
+ </property>
+ </widget>
+ <widget class="QCheckBox" row="0" column="0" rowspan="1" colspan="2">
+ <property name="name">
+ <cstring>scanAcrossMounts</cstring>
+ </property>
+ <property name="text">
+ <string>Scan across filesystem &amp;boundaries</string>
+ </property>
+ <property name="whatsThis" stdset="0">
+ <string>Allows scans to enter directories that are part of other filesystems. For example, when unchecked, this will usually prevent the contents of &lt;b&gt;/mnt&lt;/b&gt; from being scanned if you scan &lt;b&gt;/&lt;/b&gt;.</string>
+ </property>
+ </widget>
+ <widget class="QCheckBox" row="2" column="1">
+ <property name="name">
+ <cstring>dontScanRemovableMedia</cstring>
+ </property>
+ <property name="enabled">
+ <bool>false</bool>
+ </property>
+ <property name="text">
+ <string>E&amp;xclude removable media</string>
+ </property>
+ <property name="toolTip" stdset="0">
+ <string></string>
+ </property>
+ <property name="whatsThis" stdset="0">
+ <string>Prevents Filelight from scanning removable media (eg. CD-ROMs).</string>
+ </property>
+ </widget>
+ </grid>
+ </widget>
+ </vbox>
+ </widget>
+ <widget class="QWidget">
+ <property name="name">
+ <cstring>Widget3</cstring>
+ </property>
+ <attribute name="title">
+ <string>&amp;Appearance</string>
+ </attribute>
+ <vbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="QGroupBox">
+ <property name="name">
+ <cstring>groupBox1</cstring>
+ </property>
+ <property name="title">
+ <string>Scheme</string>
+ </property>
+ <vbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="QVButtonGroup">
+ <property name="name">
+ <cstring>colourSchemeGroup</cstring>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy>
+ <hsizetype>5</hsizetype>
+ <vsizetype>7</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ <widget class="QLayoutWidget">
+ <property name="name">
+ <cstring>layout10</cstring>
+ </property>
+ <hbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="QLabel">
+ <property name="name">
+ <cstring>textLabel3</cstring>
+ </property>
+ <property name="text">
+ <string>Co&amp;ntrast</string>
+ </property>
+ <property name="textFormat">
+ <enum>PlainText</enum>
+ </property>
+ <property name="buddy" stdset="0">
+ <cstring>contrastSlider</cstring>
+ </property>
+ </widget>
+ <widget class="QSlider">
+ <property name="name">
+ <cstring>contrastSlider</cstring>
+ </property>
+ <property name="minValue">
+ <number>1</number>
+ </property>
+ <property name="maxValue">
+ <number>100</number>
+ </property>
+ <property name="orientation">
+ <enum>Horizontal</enum>
+ </property>
+ <property name="whatsThis" stdset="0">
+ <string>Here you can vary the contrast of the filemap in realtime.</string>
+ </property>
+ </widget>
+ </hbox>
+ </widget>
+ </vbox>
+ </widget>
+ <widget class="QCheckBox">
+ <property name="name">
+ <cstring>useAntialiasing</cstring>
+ </property>
+ <property name="text">
+ <string>&amp;Use anti-aliasing</string>
+ </property>
+ <property name="whatsThis" stdset="0">
+ <string>Anti-aliasing the filemap makes it clearer and prettier, unfortunately it also makes rendering very slow.</string>
+ </property>
+ </widget>
+ <widget class="QLayoutWidget">
+ <property name="name">
+ <cstring>layout10</cstring>
+ </property>
+ <vbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <property name="spacing">
+ <number>0</number>
+ </property>
+ <widget class="QCheckBox">
+ <property name="name">
+ <cstring>varyLabelFontSizes</cstring>
+ </property>
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="text">
+ <string>Var&amp;y label font sizes</string>
+ </property>
+ <property name="whatsThis" stdset="0">
+ <string>The font size of exploded labels can be varied relative to the depth of the directories they represent. This helps you spot the important labels more easily. Set a sensible minimum font size.</string>
+ </property>
+ </widget>
+ <widget class="QLayoutWidget">
+ <property name="name">
+ <cstring>layout9</cstring>
+ </property>
+ <hbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <spacer>
+ <property name="name">
+ <cstring>spacer4</cstring>
+ </property>
+ <property name="orientation">
+ <enum>Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>Fixed</enum>
+ </property>
+ <property name="sizeHint">
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ <widget class="QLabel">
+ <property name="name">
+ <cstring>textLabel2</cstring>
+ </property>
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="text">
+ <string>Minimum font si&amp;ze:</string>
+ </property>
+ <property name="textFormat">
+ <enum>PlainText</enum>
+ </property>
+ <property name="buddy" stdset="0">
+ <cstring>minFontPitch</cstring>
+ </property>
+ <property name="whatsThis" stdset="0">
+ <string>The smallest font size Filelight can use to render labels.</string>
+ </property>
+ </widget>
+ <widget class="KIntSpinBox">
+ <property name="name">
+ <cstring>minFontPitch</cstring>
+ </property>
+ <property name="enabled">
+ <bool>true</bool>
+ </property>
+ <property name="sizePolicy">
+ <sizepolicy>
+ <hsizetype>3</hsizetype>
+ <vsizetype>0</vsizetype>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>80</width>
+ <height>0</height>
+ </size>
+ </property>
+ <property name="maxValue">
+ <number>20</number>
+ </property>
+ <property name="minValue">
+ <number>1</number>
+ </property>
+ <property name="value">
+ <number>9</number>
+ </property>
+ </widget>
+ </hbox>
+ </widget>
+ </vbox>
+ </widget>
+ <widget class="QCheckBox">
+ <property name="name">
+ <cstring>showSmallFiles</cstring>
+ </property>
+ <property name="text">
+ <string>Show small files</string>
+ </property>
+ <property name="whatsThis" stdset="0">
+ <string>Some files are too small to be rendered on the filemap. Selecting this option makes these files visible by merging them all into a single "multi-segment".</string>
+ </property>
+ </widget>
+ </vbox>
+ </widget>
+ </widget>
+ <widget class="QLayoutWidget">
+ <property name="name">
+ <cstring>layout6</cstring>
+ </property>
+ <hbox>
+ <property name="name">
+ <cstring>unnamed</cstring>
+ </property>
+ <widget class="QPushButton">
+ <property name="name">
+ <cstring>m_resetButton</cstring>
+ </property>
+ <property name="text">
+ <string>&amp;Reset</string>
+ </property>
+ <property name="autoDefault">
+ <bool>false</bool>
+ </property>
+ <property name="whatsThis" stdset="0">
+ <string>Reset any changes you have made since you opened this dialog.</string>
+ </property>
+ </widget>
+ <spacer>
+ <property name="name">
+ <cstring>spacer1</cstring>
+ </property>
+ <property name="orientation">
+ <enum>Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>Expanding</enum>
+ </property>
+ <property name="sizeHint">
+ <size>
+ <width>120</width>
+ <height>30</height>
+ </size>
+ </property>
+ </spacer>
+ <widget class="QPushButton">
+ <property name="name">
+ <cstring>m_closeButton</cstring>
+ </property>
+ <property name="text">
+ <string>&amp;Close</string>
+ </property>
+ <property name="default">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </hbox>
+ </widget>
+ </vbox>
+</widget>
+<customwidgets>
+ <customwidget>
+ <class>QVButtonGroup</class>
+ <header location="global">qvbuttongroup.h</header>
+ <sizehint>
+ <width>-1</width>
+ <height>-1</height>
+ </sizehint>
+ <container>0</container>
+ <sizepolicy>
+ <hordata>5</hordata>
+ <verdata>5</verdata>
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ <pixmap>image0</pixmap>
+ </customwidget>
+</customwidgets>
+<images>
+ <image name="image0">
+ <data format="PNG" length="824">89504e470d0a1a0a0000000d4948445200000016000000160806000000c4b46c3b000002ff49444154388db59531681c4714863f992dde820cb370815b50600f54e8ca0ba43970712a8fb838438a3895634813d238a5ab80e314ae4d0a812060a4226017c27221c8a9da6b8c4fe0e00d28b0571cec82043b85611f78c12966efa4bb8bc085f29a6567df7cef9f7fdeccaec571cc2cbaddee47ae21e2385e5b9b815f1ebcfcd8de6a63ad25cb338af7c52741acb5a4a729a3d723a82ec6bd99d267bf3f23fc1c4cab2442d14a915986e792fdfa59569766573049417784f1b12e8267954dab24b78714450a28beaf941f847c2a14e70a0841035a2d45d641eb027213c210c69756320767794684d6508bef0befde1a860796e4c402333542b4256c0f0cdd1e50b97191458be6e0e27d81563a87c643d8fb2d7793d685d696413cc8a6cae46f65f7d79c7c62b87b4f2e15fd0fb0d302be0fefde4a0d557a5f35e90f84e0334014d590f855c9de4ecee17e4eb319d1ff3a00ec02f8c67299f283307c61e7d06fbf1782d082588a33e1cf1705fd81cf773f3601e1f9bec59e2f4b5c7ef5209f0ac95f16630cfd818067c103b586dd274a726229cee0fe8380d191cb4d1267d3d58aa1de7d258ceae5d7d0a78fdd269a86f0c52d414c49bbe3762c9b686de41560d7a72e41c4795a6486a78f95e4c4151481d686efbe7b3398ac58b1a23868b8c474aaa8068c8e714a8dd06c1af2a9e5d1c38c641c909dba6e08237f19b358a7ac5cf3479bc2e41f257e55d2ffc6a73833746f09e186cfa387904f2cbffc90a2aa9886d0e99464d3c5965b512cebd01f1800f67672e2a392fb0f023a3d883a053ffddcc2340dd65ab452b6074dc2cd15c1cbceb863daed413e353cdfcfd97d92333a12da6d0181ec3443cf753ef3cdd092de0e116ff1a02cdc157338ca9d7b8269461cfee1ba2139b9286e1a427f10110f2d561555b076d18a39383d4d99a4c0cd0b787f20747b214962c8266e3cdcf0e97c59126ec2f6edd089f40a92f115e0d1eb11ba238461dd6a15f32b53666de841965bb203575a3cc15a48c64a965fe57105e3635db8fa96dcffc431172b5d715d7103dc3fea7f015f373c8ee3b57f0135105a0fae7717960000000049454e44ae426082</data>
+ </image>
+</images>
+<connections>
+ <connection>
+ <sender>scanAcrossMounts</sender>
+ <signal>toggled(bool)</signal>
+ <receiver>Dialog</receiver>
+ <slot>toggleScanAcrossMounts(bool)</slot>
+ </connection>
+ <connection>
+ <sender>dontScanRemoteMounts</sender>
+ <signal>toggled(bool)</signal>
+ <receiver>Dialog</receiver>
+ <slot>toggleDontScanRemoteMounts(bool)</slot>
+ </connection>
+ <connection>
+ <sender>dontScanRemovableMedia</sender>
+ <signal>toggled(bool)</signal>
+ <receiver>Dialog</receiver>
+ <slot>toggleDontScanRemovableMedia(bool)</slot>
+ </connection>
+</connections>
+<tabstops>
+ <tabstop>tabWidget</tabstop>
+ <tabstop>colourSchemeGroup</tabstop>
+ <tabstop>contrastSlider</tabstop>
+ <tabstop>useAntialiasing</tabstop>
+ <tabstop>varyLabelFontSizes</tabstop>
+ <tabstop>minFontPitch</tabstop>
+ <tabstop>m_resetButton</tabstop>
+ <tabstop>m_closeButton</tabstop>
+ <tabstop>m_listBox</tabstop>
+ <tabstop>m_removeButton</tabstop>
+ <tabstop>m_addButton</tabstop>
+ <tabstop>scanAcrossMounts</tabstop>
+ <tabstop>dontScanRemoteMounts</tabstop>
+ <tabstop>dontScanRemovableMedia</tabstop>
+</tabstops>
+<slots>
+ <slot>toggleDontScanRemovableMedia(bool)</slot>
+ <slot>toggleDontScanRemoteMounts(bool)</slot>
+ <slot>toggleScanAcrossMounts(bool)</slot>
+</slots>
+<layoutdefaults spacing="6" margin="11"/>
+<includehints>
+ <includehint>qvbuttongroup.h</includehint>
+ <includehint>knuminput.h</includehint>
+</includehints>
+</UI>
diff --git a/src/part/fileTree.cpp b/src/part/fileTree.cpp
new file mode 100644
index 0000000..f459d50
--- /dev/null
+++ b/src/part/fileTree.cpp
@@ -0,0 +1,67 @@
+//Author: Max Howell <[email protected]>, (C) 2004
+//Copyright: See COPYING file that comes with this distribution
+
+#include "fileTree.h"
+#include <kglobal.h>
+#include <klocale.h>
+#include <qfile.h>
+
+
+//static definitions
+const uint File::DENOMINATOR[4] = { 1<<0, 1<<10, 1<<20, 1<<30 };
+static const char PREFIX[4] = { 'K', 'M', 'G', 'T' };
+
+
+QString
+File::fullPath( const Directory *root /*= 0*/ ) const
+{
+ QString path;
+
+ if( root == this )
+ root = 0; //prevent returning empty string when there is something we could return
+
+ for( const Directory *d = (Directory*)this; d != root && d; d = d->parent() )
+ path.prepend( d->name() );
+
+ return path;
+}
+
+QString
+File::humanReadableSize( UnitPrefix key /*= mega*/ ) const //FIXME inline
+{
+ return humanReadableSize( m_size, key );
+}
+
+QString
+File::humanReadableSize( uint size, UnitPrefix key /*= mega*/ ) //static
+{
+ if( size == 0 )
+ return "0 B";
+
+ QString s;
+ double prettySize = (double)size / (double)DENOMINATOR[key];
+ const KLocale &locale = *KGlobal::locale();
+
+ if( prettySize >= 0.01 )
+ {
+ //use three significant figures
+ if( prettySize < 1 ) s = locale.formatNumber( prettySize, 2 );
+ else if( prettySize < 100 ) s = locale.formatNumber( prettySize, 1 );
+ else s = locale.formatNumber( prettySize, 0 );
+
+ s += ' ';
+ s += PREFIX[key];
+ s += 'B';
+ }
+
+ if( prettySize < 0.1 )
+ {
+ s += " (";
+ s += locale.formatNumber( size / DENOMINATOR[key - 1], 0 );
+ s += ' ';
+ s += PREFIX[key - 1];
+ s += "B)";
+ }
+
+ return s;
+}
diff --git a/src/part/fileTree.h b/src/part/fileTree.h
new file mode 100644
index 0000000..25ac6fb
--- /dev/null
+++ b/src/part/fileTree.h
@@ -0,0 +1,238 @@
+//Author: Max Howell <[email protected]>, (C) 2004
+//Copyright: See COPYING file that comes with this distribution
+
+#ifndef FILETREE_H
+#define FILETREE_H
+
+#include <qcstring.h> //qstrdup
+#include <qfile.h> //decodeName()
+#include <stdlib.h>
+
+
+//TODO these are pointlessly general purpose now, make them incredibly specific
+
+
+
+typedef unsigned long int FileSize;
+typedef unsigned long int Dirsize; //**** currently unused
+
+template <class T> class Iterator;
+template <class T> class ConstIterator;
+template <class T> class Chain;
+
+template <class T>
+class Link
+{
+public:
+ Link( T* const t ) : prev( this ), next( this ), data( t ) {}
+ Link() : prev( this ), next( this ), data( 0 ) {}
+
+//TODO unlinking is slow and you don't use it very much in this context.
+// ** Perhaps you can make a faster deletion system that doesn't bother tidying up first
+// ** and then you MUST call some kind of detach() function when you remove elements otherwise
+ ~Link() { delete data; unlink(); }
+
+ friend class Iterator<T>;
+ friend class ConstIterator<T>;
+ friend class Chain<T>;
+
+private:
+ void unlink() { prev->next = next; next->prev = prev; prev = next = this; }
+
+ Link<T>* prev;
+ Link<T>* next;
+
+ T* data; //ensure only iterators have access to this
+};
+
+
+template <class T>
+class Iterator
+{
+public:
+ Iterator() : link( 0 ) { } //**** remove this, remove this REMOVE THIS!!! dangerous as your implementation doesn't test for null links, always assumes they can be derefenced
+ Iterator( Link<T> *p ) : link( p ) { }
+
+ bool operator==( const Iterator<T>& it ) const { return link == it.link; }
+ bool operator!=( const Iterator<T>& it ) const { return link != it.link; }
+ bool operator!=( const Link<T> *p ) const { return p != link; }
+
+ //here we have a choice, really I should make two classes one const the other not
+ const T* operator*() const { return link->data; }
+ T* operator*() { return link->data; }
+
+ Iterator<T>& operator++() { link = link->next; return *this; } //**** does it waste time returning in places where we don't use the retval?
+
+ bool isNull() const { return (link == 0); } //REMOVE WITH ABOVE REMOVAL you don't want null iterators to be possible
+
+ void transferTo( Chain<T> &chain )
+ {
+ chain.append( remove() );
+ }
+
+ T* const remove() //remove from list, delete Link, data is returned NOT deleted
+ {
+ T* const d = link->data;
+ Link<T>* const p = link->prev;
+
+ link->data = 0;
+ delete link;
+ link = p; //make iterator point to previous element, YOU must check this points to an element
+
+ return d;
+ }
+
+private:
+ Link<T> *link;
+};
+
+
+template <class T>
+class ConstIterator
+{
+public:
+ ConstIterator( Link<T> *p ) : link( p ) { }
+
+ bool operator==( const Iterator<T>& it ) const { return link == it.link; }
+ bool operator!=( const Iterator<T>& it ) const { return link != it.link; }
+ bool operator!=( const Link<T> *p ) const { return p != link; }
+
+ const T* operator*() const { return link->data; }
+
+ ConstIterator<T>& operator++() { link = link->next; return *this; }
+
+private:
+ const Link<T> *link;
+};
+
+
+template <class T>
+class Chain
+{
+public:
+ virtual ~Chain() { empty(); }
+
+ void append( T* const data )
+ {
+ Link<T>* const link = new Link<T>( data );
+
+ link->prev = head.prev;
+ link->next = &head;
+
+ head.prev->next = link;
+ head.prev = link;
+ }
+
+ void transferTo( Chain &c )
+ {
+ if( isEmpty() ) return;
+
+ Link<T>* const first = head.next;
+ Link<T>* const last = head.prev;
+
+ head.unlink();
+
+ first->prev = c.head.prev;
+ c.head.prev->next = first;
+
+ last->next = &c.head;
+ c.head.prev = last;
+ }
+
+ void empty() { while( head.next != &head ) { delete head.next; } }
+
+ Iterator<T> iterator() const { return Iterator<T>( head.next ); }
+ ConstIterator<T> constIterator() const { return ConstIterator<T>( head.next ); }
+ const Link<T> *end() const { return &head; }
+ bool isEmpty() const { return head.next == &head; }
+
+private:
+ Link<T> head;
+ void operator=( const Chain& );
+};
+
+
+class Directory;
+class QString;
+
+class File
+{
+public:
+ friend class Directory;
+
+ enum UnitPrefix { kilo, mega, giga, tera };
+
+ static const uint DENOMINATOR[4];
+
+public:
+ File( const char *name, FileSize size ) : m_parent( 0 ), m_name( qstrdup( name ) ), m_size( size ) {}
+ virtual ~File() { delete [] m_name; }
+
+ const Directory *parent() const { return m_parent; }
+ const char *name8Bit() const { return m_name; }
+ const FileSize size() const { return m_size; }
+ QString name() const { return QFile::decodeName( m_name ); }
+
+ virtual bool isDirectory() const { return false; }
+
+ QString fullPath( const Directory* = 0 ) const;
+ QString humanReadableSize( UnitPrefix key = mega ) const;
+
+public:
+ static QString humanReadableSize( uint size, UnitPrefix Key = mega );
+
+protected:
+ File( const char *name, FileSize size, Directory *parent ) : m_parent( parent ), m_name( qstrdup( name ) ), m_size( size ) {}
+
+ Directory *m_parent; //0 if this is treeRoot
+ char *m_name;
+ FileSize m_size; //in units of KiB
+
+private:
+ File( const File& );
+ void operator=( const File& );
+};
+
+
+class Directory : public Chain<File>, public File
+{
+public:
+ Directory( const char *name ) : File( name, 0 ), m_children( 0 ) {} //DON'T pass the full path!
+
+ uint children() const { return m_children; }
+ virtual bool isDirectory() const { return true; }
+
+ ///appends a Directory
+ void append( Directory *d, const char *name=0 )
+ {
+ if( name ) {
+ delete [] d->m_name;
+ d->m_name = qstrdup( name ); } //directories that had a fullpath copy just their names this way
+
+ m_children += d->children(); //doesn't include the dir itself
+ d->m_parent = this;
+ append( (File*)d ); //will add 1 to filecount for the dir itself
+ }
+
+ ///appends a File
+ void append( const char *name, FileSize size )
+ {
+ append( new File( name, size, this ) );
+ }
+
+private:
+ void append( File *p )
+ {
+ m_children++;
+ m_size += p->size();
+ Chain<File>::append( p );
+ }
+
+ uint m_children;
+
+private:
+ Directory( const Directory& ); //undefined
+ void operator=( const Directory& ); //undefined
+};
+
+#endif
diff --git a/src/part/localLister.cpp b/src/part/localLister.cpp
new file mode 100644
index 0000000..6bf7945
--- /dev/null
+++ b/src/part/localLister.cpp
@@ -0,0 +1,333 @@
+//Author: Max Howell <[email protected]>, (C) 2003-4
+//Copyright: See COPYING file that comes with this distribution
+
+#include "Config.h"
+#include "debug.h"
+#include <dirent.h>
+#include "fileTree.h"
+#include <fstab.h>
+#include "localLister.h"
+#ifdef HAVE_MNTENT_H
+#include <mntent.h>
+#endif
+#include <qapplication.h> //postEvent()
+#include <qfile.h>
+#include "scan.h"
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+namespace Filelight
+{
+ QStringList LocalLister::s_remoteMounts;
+ QStringList LocalLister::s_localMounts;
+
+ LocalLister::LocalLister( const QString &path, Chain<Directory> *cachedTrees, QObject *parent )
+ : QThread()
+ , m_path( path )
+ , m_trees( cachedTrees )
+ , m_parent( parent )
+ {
+ //add empty directories for any mount points that are in the path
+ //TODO empty directories is not ideal as adds to fileCount incorrectly
+
+ QStringList list( Config::skipList );
+ if( !Config::scanAcrossMounts ) list += s_localMounts;
+ if( !Config::scanRemoteMounts ) list += s_remoteMounts;
+
+ for( QStringList::ConstIterator it = list.constBegin(); it != list.constEnd(); ++it )
+ if( (*it).startsWith( path ) )
+ //prevent scanning of these directories
+ m_trees->append( new Directory( (*it).local8Bit() ) );
+
+ start();
+ }
+
+ void
+ LocalLister::run()
+ {
+ //recursively scan the requested path
+ const QCString path = QFile::encodeName( m_path );
+ Directory *tree = scan( path, path );
+
+ //delete the list of trees useful for this scan,
+ //in a sucessful scan the contents would now be transfered to 'tree'
+ delete m_trees;
+
+ if( ScanManager::s_abort ) //scan was cancelled
+ {
+ debug() << "Scan succesfully aborted\n";
+ delete tree;
+ tree = 0;
+ }
+
+ QCustomEvent *e = new QCustomEvent( 1000 );
+ e->setData( tree );
+ QApplication::postEvent( m_parent, e );
+ }
+
+ // from system.h in GNU coreutils package
+ /* Extract or fake data from a `struct stat'.
+ ST_BLKSIZE: Preferred I/O blocksize for the file, in bytes.
+ ST_NBLOCKS: Number of blocks in the file, including indirect blocks.
+ ST_NBLOCKSIZE: Size of blocks used when calculating ST_NBLOCKS. */
+ #ifndef HAVE_STRUCT_STAT_ST_BLOCKS
+ #define ST_BLKSIZE(statbuf) DEV_BSIZE
+ #if defined _POSIX_SOURCE || !defined BSIZE /* fileblocks.c uses BSIZE. */
+ #define ST_NBLOCKS(statbuf) ((statbuf).st_size / ST_NBLOCKSIZE + ((statbuf).st_size % ST_NBLOCKSIZE != 0))
+ #else /* !_POSIX_SOURCE && BSIZE */
+ #define ST_NBLOCKS(statbuf) (S_ISREG ((statbuf).st_mode) || S_ISDIR ((statbuf).st_mode) ? st_blocks ((statbuf).st_size) : 0)
+ #endif /* !_POSIX_SOURCE && BSIZE */
+ #else /* HAVE_STRUCT_STAT_ST_BLOCKS */
+ /* Some systems, like Sequents, return st_blksize of 0 on pipes.
+ Also, when running `rsh hpux11-system cat any-file', cat would
+ determine that the output stream had an st_blksize of 2147421096.
+ So here we arbitrarily limit the `optimal' block size to 4MB.
+ If anyone knows of a system for which the legitimate value for
+ st_blksize can exceed 4MB, please report it as a bug in this code. */
+ #define ST_BLKSIZE(statbuf) ((0 < (statbuf).st_blksize && (statbuf).st_blksize <= (1 << 22)) /* 4MiB */ ? (statbuf).st_blksize : DEV_BSIZE)
+ #if defined hpux || defined __hpux__ || defined __hpux
+ /* HP-UX counts st_blocks in 1024-byte units.
+ This loses when mixing HP-UX and BSD filesystems with NFS. */
+ #define ST_NBLOCKSIZE 1024
+ #else /* !hpux */
+ #if defined _AIX && defined _I386
+ /* AIX PS/2 counts st_blocks in 4K units. */
+ #define ST_NBLOCKSIZE (4 * 1024)
+ #else /* not AIX PS/2 */
+ #if defined _CRAY
+ #define ST_NBLOCKS(statbuf) (S_ISREG ((statbuf).st_mode) || S_ISDIR ((statbuf).st_mode) ? (statbuf).st_blocks * ST_BLKSIZE(statbuf)/ST_NBLOCKSIZE : 0)
+ #endif /* _CRAY */
+ #endif /* not AIX PS/2 */
+ #endif /* !hpux */
+ #endif /* HAVE_STRUCT_STAT_ST_BLOCKS */
+
+ #ifndef ST_NBLOCKS
+ #define ST_NBLOCKS(statbuf) ((statbuf).st_blocks)
+ #endif
+
+ #ifndef ST_NBLOCKSIZE
+ #define ST_NBLOCKSIZE 512
+ #endif
+
+//some GNU systems don't support big files for some reason
+#ifdef __USE_LARGEFILE64 //see dirent.h
+ #define dirent dirent64
+ #define scandir scandir64
+ #define stat stat64
+ #define statstruct stat64
+ #define lstat lstat64
+ #define readdir readdir64
+#endif
+
+#ifndef NULL
+#define NULL 0
+#endif
+
+
+ #include <errno.h>
+ static void
+ outputError( QCString path )
+ {
+ ///show error message that stat or opendir may give
+
+ #define out( s ) error() << s ": " << path << endl; break
+
+ switch( errno ) {
+ case EACCES:
+ out( "Inadequate access permisions" );
+ case EMFILE:
+ out( "Too many file descriptors in use by Filelight" );
+ case ENFILE:
+ out( "Too many files are currently open in the system" );
+ case ENOENT:
+ out( "A component of the path does not exist, or the path is an empty string" );
+ case ENOMEM:
+ out( "Insufficient memory to complete the operation" );
+ case ENOTDIR:
+ out( "A component of the path is not a directory" );
+ case EBADF:
+ out( "Bad file descriptor" );
+ case EFAULT:
+ out( "Bad address" );
+ case ELOOP: //NOTE shouldn't ever happen
+ out( "Too many symbolic links encountered while traversing the path" );
+ case ENAMETOOLONG:
+ out( "File name too long" );
+ }
+
+ #undef out
+ }
+
+ Directory*
+ LocalLister::scan( const QCString &path, const QCString &dirname )
+ {
+ Directory *cwd = new Directory( dirname );
+ DIR *dir = opendir( path );
+
+ if( !dir ) {
+ outputError( path );
+ return cwd;
+ }
+
+ struct stat statbuf;
+ dirent *ent;
+ while ((ent = readdir( dir )))
+ {
+ if( ScanManager::s_abort )
+ return cwd;
+
+ if( qstrcmp( ent->d_name, "." ) == 0 || qstrcmp( ent->d_name, ".." ) == 0 )
+ continue;
+
+ QCString new_path = path; new_path += ent->d_name;
+
+ //get file information
+ if( lstat( new_path, &statbuf ) == -1 ) {
+ outputError( new_path );
+ continue;
+ }
+
+ if( S_ISLNK( statbuf.st_mode ) ||
+ S_ISCHR( statbuf.st_mode ) ||
+ S_ISBLK( statbuf.st_mode ) ||
+ S_ISFIFO( statbuf.st_mode ) ||
+ S_ISSOCK( statbuf.st_mode ) )
+ {
+ continue;
+ }
+
+ if( S_ISREG( statbuf.st_mode ) ) //file
+ //using units of KiB as 32bit max is 4GiB and 64bit ints are expensive
+ cwd->append( ent->d_name, (ST_NBLOCKS( statbuf ) * ST_NBLOCKSIZE) / 1024 );
+
+ else if( S_ISDIR( statbuf.st_mode ) ) //directory
+ {
+ Directory *d = 0;
+ QCString new_dirname = ent->d_name;
+ new_dirname += '/';
+ new_path += '/';
+
+ //check to see if we've scanned this section already
+
+ for( Iterator<Directory> it = m_trees->iterator(); it != m_trees->end(); ++it )
+ {
+ if( new_path == (*it)->name8Bit() )
+ {
+ debug() << "Tree pre-completed: " << (*it)->name() << "\n";
+ d = it.remove();
+ ScanManager::s_files += d->children();
+ //**** ideally don't have this redundant extra somehow
+ cwd->append( d, new_dirname );
+ }
+ }
+
+ if( !d ) //then scan
+ if ((d = scan( new_path, new_dirname ))) //then scan was successful
+ cwd->append( d );
+ }
+
+ ++ScanManager::s_files;
+ }
+
+ closedir( dir );
+
+ return cwd;
+ }
+
+ bool
+ LocalLister::readMounts()
+ {
+ #define INFO_PARTITIONS "/proc/partitions"
+ #define INFO_MOUNTED_PARTITIONS "/etc/mtab" /* on Linux... */
+
+ //**** SHAMBLES
+ // ** mtab should have priority as mount points don't have to follow fstab
+ // ** no removable media detection
+ // ** no updates if mounts change
+ // ** you want a KDE extension that handles this for you really
+
+ struct fstab *fstab_ent;
+#ifdef HAVE_MNTENT_H
+ struct mntent *mnt_ent;
+#endif
+ QString str;
+
+
+#ifdef HAVE_MNTENT_H
+ FILE *fp;
+ if( setfsent() == 0 || !( fp = setmntent( INFO_MOUNTED_PARTITIONS, "r" ) ) )
+#else
+ if( setfsent() == 0 )
+#endif
+ return false;
+
+ #define FS_NAME fstab_ent->fs_spec // device-name
+ #define FS_FILE fstab_ent->fs_file // mount-point
+ #define FS_TYPE fstab_ent->fs_vfstype // fs-type
+ #define FS_MNTOPS fstab_ent->fs_mntops // mount-options
+
+ QStringList remoteFsTypes;
+ remoteFsTypes << "smbfs" ;
+#ifdef MNTTYPE_NFS
+ remoteFsTypes << MNTTYPE_NFS;
+#else
+ remoteFsTypes << "nfs";
+#endif
+ // What about afs?
+
+ while( (fstab_ent = getfsent()) != NULL )
+ {
+ str = QString( FS_FILE );
+ if( str == "/" ) continue;
+ str += '/';
+
+ if( remoteFsTypes.contains( FS_TYPE ) )
+ s_remoteMounts.append( str ); //**** NO! can't be sure won't have trailing slash, need to do a check first dummy!!
+
+ else
+ s_localMounts.append( str ); //**** NO! can't be sure won't have trailing slash, need to do a check first dummy!!
+
+ kdDebug() << "FSTAB: " << FS_TYPE << "\n";
+ }
+
+ endfsent(); /* close fstab.. */
+
+ #undef FS_NAME
+ #undef FS_FILE
+ #undef FS_TYPE
+ #undef FS_MNTOPS
+
+ #define FS_NAME mnt_ent->mnt_fsname // device-name
+ #define FS_FILE mnt_ent->mnt_dir // mount-point
+ #define FS_TYPE mnt_ent->mnt_type // fs-type
+ #define FS_MNTOPS mnt_ent->mnt_opts // mount-options
+
+ //scan mtab, **** mtab should take priority, but currently it isn't
+
+#ifdef HAVE_MNTENT_H
+ while( ( mnt_ent = getmntent( fp ) ) != NULL )
+ {
+ bool b = false;
+
+ str = QString( FS_FILE );
+ if( str == "/" ) continue;
+ str += "/";
+
+ if( remoteFsTypes.contains( FS_TYPE ) )
+ if( b = !s_remoteMounts.contains( str ) )
+ s_remoteMounts.append( str ); //**** NO! can't be sure won't have trailing slash, need to do a check first dummy!!
+
+ else if( b = !s_localMounts.contains( str ) )
+ s_localMounts.append( str ); //**** NO! can't be sure won't have trailing slash, need to do a check first dummy!!
+
+ if( b ) kdDebug() << "MTAB: " << FS_TYPE << "\n";
+ }
+
+ endmntent( fp ); /* close mtab.. */
+#endif
+
+
+ return true;
+ }
+}
diff --git a/src/part/localLister.h b/src/part/localLister.h
new file mode 100644
index 0000000..5070e14
--- /dev/null
+++ b/src/part/localLister.h
@@ -0,0 +1,35 @@
+//Author: Max Howell <[email protected]>, (C) 2003-4
+//Copyright: See COPYING file that comes with this distribution
+
+#ifndef LOCALLISTER_H
+#define LOCALLISTER_H
+
+#include <qthread.h>
+
+class Directory;
+template<class T> class Chain;
+
+namespace Filelight
+{
+ class LocalLister : public QThread
+ {
+ public:
+ LocalLister( const QString &path, Chain<Directory> *cachedTrees, QObject *parent );
+
+ static bool readMounts();
+
+ private:
+ QString m_path;
+ Chain<Directory> *m_trees;
+ QObject *m_parent;
+
+ private:
+ virtual void run();
+ Directory *scan( const QCString&, const QCString& );
+
+ private:
+ static QStringList s_localMounts, s_remoteMounts; //TODO namespace
+ };
+}
+
+#endif
diff --git a/src/part/part.cpp b/src/part/part.cpp
new file mode 100644
index 0000000..9a4742a
--- /dev/null
+++ b/src/part/part.cpp
@@ -0,0 +1,254 @@
+//Author: Max Howell <[email protected]>, (C) 2003-4
+//Copyright: See COPYING file that comes with this distribution
+
+#include "Config.h"
+#include "debug.h"
+#include "define.h"
+#include "fileTree.h"
+#include "part.h"
+#include "progressBox.h"
+#include "radialMap/widget.h"
+#include "scan.h"
+#include "settingsDialog.h"
+#include "summaryWidget.h"
+
+#include <kaboutdata.h> //::createAboutData()
+#include <kaction.h>
+#include <klocale.h>
+#include <kmessagebox.h> //::start()
+//#include <konq_operations.h>
+#include <kparts/genericfactory.h>
+#include <kstatusbar.h>
+#include <kstdaction.h>
+#include <qfile.h> //encodeName()
+#include <qtimer.h> //postInit() hack
+#include <qvbox.h>
+#include <unistd.h> //access()
+
+
+namespace Filelight {
+
+
+typedef KParts::GenericFactory<Filelight::Part> Factory;
+K_EXPORT_COMPONENT_FACTORY( libfilelight, Filelight::Factory )
+
+
+BrowserExtension::BrowserExtension( Part *parent, const char *name )
+ : KParts::BrowserExtension( parent, name )
+{}
+
+
+Part::Part( QWidget *parentWidget, const char *widgetName, QObject *parent, const char *name, const QStringList& )
+ : ReadOnlyPart( parent, name )
+ , m_ext( new BrowserExtension( this ) )
+ , m_statusbar( new StatusBarExtension( this ) )
+ , m_map( 0 )
+ , m_manager( new ScanManager( this ) )
+ , m_started( false )
+{
+ QPixmap::setDefaultOptimization( QPixmap::BestOptim );
+
+ Config::read();
+
+ setInstance( Factory::instance() );
+ setWidget( new QVBox( parentWidget, widgetName ) );
+ setXMLFile( "filelight_partui.rc" );
+
+ m_map = new RadialMap::Widget( widget() );
+ m_map->hide();
+
+ KStdAction::zoomIn( m_map, SLOT(zoomIn()), actionCollection() );
+ KStdAction::zoomOut( m_map, SLOT(zoomOut()), actionCollection() );
+ KStdAction::preferences( this, SLOT(configFilelight()), actionCollection(), "configure_filelight" )->setText( i18n( "Configure Filelight..." ) );
+
+ connect( m_map, SIGNAL(created( const Directory* )), SIGNAL(completed()) );
+ connect( m_map, SIGNAL(created( const Directory* )), SLOT(mapChanged( const Directory* )) );
+ connect( m_map, SIGNAL(activated( const KURL& )), SLOT(updateURL( const KURL& )) );
+
+ // TODO make better system
+ connect( m_map, SIGNAL(giveMeTreeFor( const KURL& )), SLOT(updateURL( const KURL& )) );
+ connect( m_map, SIGNAL(giveMeTreeFor( const KURL& )), SLOT(openURL( const KURL& )) );
+
+ connect( m_manager, SIGNAL(completed( Directory* )), SLOT(scanCompleted( Directory* )) );
+ connect( m_manager, SIGNAL(aboutToEmptyCache()), m_map, SLOT(invalidate()) );
+
+ QTimer::singleShot( 0, this, SLOT(postInit()) );
+}
+
+void
+Part::postInit()
+{
+ if( m_url.isEmpty() ) //if url is not empty openURL() has been called immediately after ctor, which happens
+ {
+ QWidget *summary = new SummaryWidget( widget(), "summaryWidget" );
+ connect( summary, SIGNAL(activated( const KURL& )), SLOT(openURL( const KURL& )) );
+ summary->show();
+
+ //FIXME KXMLGUI is b0rked, it should allow us to set this
+ //BEFORE createGUI is called but it doesn't
+ stateChanged( "scan_failed" );
+ }
+}
+
+bool
+Part::openURL( const KURL &u )
+{
+ //we don't want to be using the summary screen anymore
+ delete widget()->child( "summaryWidget" );
+ m_map->show();
+
+ //TODO everyone hates dialogs, instead render the text in big fonts on the Map
+ //TODO should have an empty KURL until scan is confirmed successful
+ //TODO probably should set caption to QString::null while map is unusable
+
+ #define KMSG( s ) KMessageBox::information( widget(), s )
+
+ KURL url = u;
+ url.cleanPath( true );
+ const QString path = url.path( 1 );
+ const QCString path8bit = QFile::encodeName( path );
+ const bool isLocal = url.protocol() == "file";
+
+ if( url.isEmpty() )
+ {
+ //do nothing, chances are the user accidently pressed ENTER
+ }
+ else if( !url.isValid() )
+ {
+ KMSG( i18n( "The entered URL cannot be parsed; it is invalid." ) );
+ }
+ else if( path[0] != '/' )
+ {
+ KMSG( i18n( "Filelight only accepts absolute paths, eg. /%1" ).arg( path ) );
+ }
+ else if( isLocal && access( path8bit, F_OK ) != 0 ) //stat( path, &statbuf ) == 0
+ {
+ KMSG( i18n( "Directory not found: %1" ).arg( path ) );
+ }
+ else if( isLocal && access( path8bit, R_OK | X_OK ) != 0 )
+ {
+ KMSG( i18n( "Unable to enter: %1\nYou do not have access rights to this location." ).arg( path ) );
+ }
+ else
+ {
+ if( url == m_url )
+ m_manager->emptyCache(); //same as rescan()
+
+ return start( url );
+ }
+
+ return false;
+}
+
+bool
+Part::closeURL()
+{
+ if( m_manager->abort() )
+ statusBar()->message( i18n( "Aborting Scan..." ) );
+
+ m_url = KURL();
+
+ return true;
+}
+
+void
+Part::updateURL( const KURL &u )
+{
+ //the map has changed internally, update the interface to reflect this
+ emit m_ext->openURLNotify(); //must be done first
+ emit m_ext->setLocationBarURL( u.prettyURL() );
+
+ //do this last, or it breaks Konqi location bar
+ m_url = u;
+}
+
+void
+Part::configFilelight()
+{
+ QWidget *dialog = new SettingsDialog( widget(), "settings_dialog" );
+
+ connect( dialog, SIGNAL(canvasIsDirty( int )), m_map, SLOT(refresh( int )) );
+ connect( dialog, SIGNAL(mapIsInvalid()), m_manager, SLOT(emptyCache()) );
+
+ dialog->show(); //deletes itself
+}
+
+KAboutData*
+Part::createAboutData()
+{
+ return new KAboutData( APP_NAME, I18N_NOOP( APP_PRETTYNAME ), APP_VERSION );
+}
+
+bool
+Part::start( const KURL &url )
+{
+ if( !m_started ) {
+ m_statusbar->addStatusBarItem( new ProgressBox( statusBar(), this ), 0, true );
+ connect( m_map, SIGNAL(mouseHover( const QString& )), statusBar(), SLOT(message( const QString& )) );
+ connect( m_map, SIGNAL(created( const Directory* )), statusBar(), SLOT(clear()) );
+ m_started = true;
+ }
+
+ if( m_manager->start( url ) ) {
+ m_url = url;
+
+ const QString s = i18n( "Scanning: %1" ).arg( prettyURL() );
+ stateChanged( "scan_started" );
+ emit started( 0 ); //as a Part, we have to do this
+ emit setWindowCaption( s );
+ statusBar()->message( s );
+ m_map->invalidate(); //to maintain ui consistency
+
+ return true;
+ }
+
+ return false;
+}
+
+void
+Part::rescan()
+{
+ //FIXME we have to empty the cache because otherwise rescan picks up the old tree..
+ m_manager->emptyCache(); //causes canvas to invalidate
+ start( m_url );
+}
+
+void
+Part::scanCompleted( Directory *tree )
+{
+ if( tree ) {
+ statusBar()->message( i18n( "Scan completed, generating map..." ) );
+
+ m_map->create( tree );
+
+ //do after creating map
+ stateChanged( "scan_complete" );
+ }
+ else {
+ stateChanged( "scan_failed" );
+ emit canceled( i18n( "Scan failed: %1" ).arg( prettyURL() ) );
+ emit setWindowCaption( QString::null );
+
+ statusBar()->clear();
+// QTimer::singleShot( 2000, statusBar(), SLOT(clear()) );
+
+ m_url = KURL();
+ }
+}
+
+void
+Part::mapChanged( const Directory *tree )
+{
+ //IMPORTANT -> m_url has already been set
+
+ emit setWindowCaption( prettyURL() );
+
+ ProgressBox *progress = static_cast<ProgressBox *>(statusBar()->child( "ProgressBox" ));
+
+ if( progress )
+ progress->setText( tree->children() );
+}
+
+} //namespace Filelight
+
+#include "part.moc"
diff --git a/src/part/part.h b/src/part/part.h
new file mode 100644
index 0000000..348b22b
--- /dev/null
+++ b/src/part/part.h
@@ -0,0 +1,71 @@
+// Author: Max Howell <[email protected]>, (C) 2003-4
+// Copyright: See COPYING file that comes with this distribution
+
+#ifndef FILELIGHTPART_H
+#define FILELIGHTPART_H
+
+#include <kparts/browserextension.h>
+#include <kparts/statusbarextension.h>
+#include <kparts/part.h>
+#include <kurl.h>
+
+class KAboutData;
+using KParts::StatusBarExtension;
+namespace RadialMap { class Widget; }
+class Directory;
+
+
+namespace Filelight
+{
+ class Part;
+
+ class BrowserExtension : public KParts::BrowserExtension
+ {
+ public:
+ BrowserExtension( Part*, const char * = 0 );
+ };
+
+
+ class Part : public KParts::ReadOnlyPart
+ {
+ Q_OBJECT
+
+ public:
+ Part( QWidget *, const char *, QObject *, const char *, const QStringList& );
+
+ virtual bool openFile() { return false; } //pure virtual in base class
+ virtual bool closeURL();
+
+ QString prettyURL() const { return m_url.protocol() == "file" ? m_url.path() : m_url.prettyURL(); }
+
+ static KAboutData *createAboutData();
+
+ public slots:
+ virtual bool openURL( const KURL& );
+ void configFilelight();
+ void rescan();
+
+ private slots:
+ void postInit();
+ void scanCompleted( Directory* );
+ void mapChanged( const Directory* );
+
+ private:
+ KStatusBar *statusBar() { return m_statusbar->statusBar(); }
+
+ BrowserExtension *m_ext;
+ StatusBarExtension *m_statusbar;
+ RadialMap::Widget *m_map;
+ class ScanManager *m_manager;
+
+ bool m_started;
+
+ private:
+ bool start( const KURL& );
+
+ private slots:
+ void updateURL( const KURL & );
+ };
+}
+
+#endif
diff --git a/src/part/progressBox.cpp b/src/part/progressBox.cpp
new file mode 100644
index 0000000..5bf205a
--- /dev/null
+++ b/src/part/progressBox.cpp
@@ -0,0 +1,65 @@
+//Author: Max Howell <[email protected]>, (C) 2003-4
+//Copyright: See COPYING file that comes with this distribution
+
+#include <kglobal.h>
+#include <kglobalsettings.h>
+#include <kio/job.h>
+#include <klocale.h>
+
+#include "scan.h"
+#include "progressBox.h"
+
+
+ProgressBox::ProgressBox( QWidget *parent, QObject *part )
+ : QLabel( parent, "ProgressBox" )
+{
+ hide();
+
+ setAlignment( Qt::AlignCenter );
+ setFont( KGlobalSettings::fixedFont() );
+ setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Fixed );
+
+ setText( 999999 );
+ setMinimumWidth( sizeHint().width() );
+
+ connect( &m_timer, SIGNAL(timeout()), SLOT(report()) );
+ connect( part, SIGNAL(started( KIO::Job* )), SLOT(start()) );
+ connect( part, SIGNAL(completed()), SLOT(stop()) );
+ connect( part, SIGNAL(canceled( const QString& )), SLOT(halt()) );
+}
+
+void
+ProgressBox::start() //slot
+{
+ m_timer.start( 50 ); //20 times per second - very smooth
+ report();
+ show();
+}
+
+void
+ProgressBox::report() //slot
+{
+ setText( Filelight::ScanManager::files() );
+}
+
+void
+ProgressBox::stop()
+{
+ m_timer.stop();
+}
+
+void
+ProgressBox::halt()
+{
+ // canceled by stop button
+ m_timer.stop();
+ QTimer::singleShot( 2000, this, SLOT(hide()) );
+}
+
+void
+ProgressBox::setText( int files )
+{
+ QLabel::setText( i18n("%n File", "%n Files", files) );
+}
+
+#include "progressBox.moc"
diff --git a/src/part/progressBox.h b/src/part/progressBox.h
new file mode 100644
index 0000000..17cc2a6
--- /dev/null
+++ b/src/part/progressBox.h
@@ -0,0 +1,32 @@
+//Author: Max Howell <[email protected]>, (C) 2003-4
+//Copyright: See COPYING file that comes with this distribution
+
+#ifndef PROGRESSBOX_H
+#define PROGRESSBOX_H
+
+#include <qlabel.h>
+#include <qtimer.h>
+
+namespace KIO { class Job; }
+
+
+class ProgressBox : public QLabel
+{
+Q_OBJECT
+
+public:
+ ProgressBox( QWidget*, QObject* );
+
+ void setText( int );
+
+public slots:
+ void start();
+ void report();
+ void stop();
+ void halt();
+
+private:
+ QTimer m_timer;
+};
+
+#endif
diff --git a/src/part/radialMap/Makefile.am b/src/part/radialMap/Makefile.am
new file mode 100644
index 0000000..989cbe1
--- /dev/null
+++ b/src/part/radialMap/Makefile.am
@@ -0,0 +1,4 @@
+INCLUDES = -I$(top_srcdir)/src/part $(all_includes)
+METASOURCES = AUTO
+noinst_LTLIBRARIES = libradialmap.la
+libradialmap_la_SOURCES = widget.cpp builder.cpp map.cpp widgetEvents.cpp labels.cpp segmentTip.cpp
diff --git a/src/part/radialMap/builder.cpp b/src/part/radialMap/builder.cpp
new file mode 100644
index 0000000..68dc382
--- /dev/null
+++ b/src/part/radialMap/builder.cpp
@@ -0,0 +1,141 @@
+//Author: Max Howell <[email protected]>, (C) 2003-4
+//Copyright: See COPYING file that comes with this distribution
+
+#include "builder.h"
+#include "Config.h"
+#include "fileTree.h"
+#include <kglobal.h> //locale object
+#include <klocale.h>
+#include "widget.h"
+
+
+//**** REMOVE NEED FOR the +1 with MAX_RING_DEPTH uses
+//**** add some angle bounds checking (possibly in Segment ctor? can I delete in a ctor?)
+//**** this class is a mess
+
+RadialMap::Builder::Builder( RadialMap::Map *m, const Directory* const d, bool fast )
+ : m_map( m )
+ , m_root( d )
+ , m_minSize( static_cast<unsigned int>((d->size() * 3) / (PI * m->height() - m->MAP_2MARGIN )) )
+ , m_depth( &m->m_visibleDepth )
+{
+ m_signature = new Chain<Segment> [*m_depth + 1];
+
+ if( !fast )//|| *m_depth == 0 ) //depth 0 is special case usability-wise //**** WHY?!
+ {
+ //determine depth rather than use old one
+ findVisibleDepth( d ); //sets m_depth
+ }
+
+ m_map->setRingBreadth();
+ setLimits( m_map->m_ringBreadth );
+ build( d );
+
+ m_map->m_signature = m_signature;
+
+ delete [] m_limits;
+}
+
+
+void
+RadialMap::Builder::findVisibleDepth( const Directory* const dir, const unsigned int depth )
+{
+ //**** because I don't use the same minimumSize criteria as in the visual function
+ // this can lead to incorrect visual representation
+ //**** BUT, you can't set those limits until you know m_depth!
+
+ //**** also this function doesn't check to see if anything is actually visible
+ // it just assumes that when it reaches a new level everything in it is visible
+ // automatically. This isn't right especially as there might be no files in the
+ // dir provided to this function!
+
+ static uint stopDepth = 0;
+
+ if( dir == m_root )
+ {
+ stopDepth = *m_depth;
+ *m_depth = 0;
+ }
+
+ if( *m_depth < depth ) *m_depth = depth;
+ if( *m_depth >= stopDepth ) return;
+
+ for( ConstIterator<File> it = dir->constIterator(); it != dir->end(); ++it )
+ if( (*it)->isDirectory() && (*it)->size() > m_minSize )
+ findVisibleDepth( (Directory *)*it, depth + 1 ); //if no files greater than min size the depth is still recorded
+}
+
+void
+RadialMap::Builder::setLimits( const uint &b ) //b = breadth?
+{
+ double size3 = m_root->size() * 3;
+ double pi2B = PI * 2 * b;
+
+ m_limits = new uint [*m_depth + 1]; //FIXME delete!
+
+ for( unsigned int d = 0; d <= *m_depth; ++d )
+ m_limits[d] = (uint)(size3 / (double)(pi2B * (d + 1))); //min is angle that gives 3px outer diameter for that depth
+}
+
+
+//**** segments currently overlap at edges (i.e. end of first is start of next)
+bool
+RadialMap::Builder::build( const Directory* const dir, const unsigned int depth, unsigned int a_start, const unsigned int a_end )
+{
+ //first iteration: dir == m_root
+
+ if( dir->children() == 0 ) //we do fileCount rather than size to avoid chance of divide by zero later
+ return false;
+
+ uint hiddenSize = 0, hiddenFileCount = 0;
+
+ for( ConstIterator<File> it = dir->constIterator(); it != dir->end(); ++it )
+ {
+ if( (*it)->size() > m_limits[depth] )
+ {
+ unsigned int a_len = (unsigned int)(5760 * ((double)(*it)->size() / (double)m_root->size()));
+
+ Segment *s = new Segment( *it, a_start, a_len );
+
+ (m_signature + depth)->append( s );
+
+ if( (*it)->isDirectory() )
+ {
+ if( depth != *m_depth )
+ {
+ //recurse
+ s->m_hasHiddenChildren = build( (Directory*)*it, depth + 1, a_start, a_start + a_len );
+ }
+ else s->m_hasHiddenChildren = true;
+ }
+
+ a_start += a_len; //**** should we add 1?
+
+ } else {
+
+ hiddenSize += (*it)->size();
+
+ if( (*it)->isDirectory() ) //**** considered virtual, but dir wouldn't count itself!
+ hiddenFileCount += static_cast<const Directory*>(*it)->children(); //need to add one to count the dir as well
+
+ ++hiddenFileCount;
+ }
+ }
+
+ if( hiddenFileCount == dir->children() && !Config::showSmallFiles )
+ return true;
+
+ else if( (Config::showSmallFiles && hiddenSize > m_limits[depth]) || (depth == 0 && (hiddenSize > dir->size()/8)) /*|| > size() * 0.75*/ )
+ {
+ //append a segment for unrepresented space - a "fake" segment
+
+ // I dunno how to i18n this
+ const QString s = i18n( "There can't ever be only 1 file", "%1 files, each about %2" )
+ .arg( hiddenFileCount )
+ .arg( File::humanReadableSize( hiddenSize/hiddenFileCount ) );
+
+ (m_signature + depth)->append( new Segment( new File( s.local8Bit(), hiddenSize ), a_start, a_end - a_start, true ) );
+ }
+
+ return false;
+}
diff --git a/src/part/radialMap/builder.h b/src/part/radialMap/builder.h
new file mode 100644
index 0000000..819813a
--- /dev/null
+++ b/src/part/radialMap/builder.h
@@ -0,0 +1,38 @@
+//Author: Max Howell <[email protected]>, (C) 2003-4
+//Copyright: See COPYING file that comes with this distribution
+
+#ifndef BUILDER_H
+#define BUILDER_H
+
+#include "radialMap.h" //Segment, defines
+
+template <class T> class Chain;
+class Directory;
+
+
+namespace RadialMap
+{
+ class Map;
+
+ //temporary class that builds the Map signature
+
+ class Builder
+ {
+ public:
+ Builder( Map*, const Directory* const, bool fast=false );
+
+ private:
+ void findVisibleDepth( const Directory* const dir, const uint=0 );
+ void setLimits( const uint& );
+ bool build( const Directory* const, const uint=0, uint=0, const uint=5760 );
+
+ Map *m_map;
+ const Directory* const m_root;
+ const uint m_minSize;
+ uint *m_depth;
+ Chain<Segment> *m_signature;
+ uint *m_limits;
+ };
+}
+
+#endif
diff --git a/src/part/radialMap/labels.cpp b/src/part/radialMap/labels.cpp
new file mode 100644
index 0000000..73a7ba8
--- /dev/null
+++ b/src/part/radialMap/labels.cpp
@@ -0,0 +1,327 @@
+//Author: Max Howell <[email protected]>, (C) 2003-4
+//Copyright: See COPYING file that comes with this distribution
+
+#include <kstringhandler.h>
+#include <qfont.h>
+#include <qfontmetrics.h>
+#include <qpainter.h>
+#include <qptrlist.h>
+
+#include "Config.h"
+#include "fileTree.h"
+#include "radialMap.h"
+#include "sincos.h"
+#include "widget.h"
+
+
+
+namespace RadialMap
+{
+ struct Label
+ {
+ Label( const RadialMap::Segment *s, int l ) : segment( s ), lvl( l ), a( segment->start() + (segment->length() / 2) ) { }
+
+ bool tooClose( const int &aa ) const { return ( a > aa - LABEL_ANGLE_MARGIN && a < aa + LABEL_ANGLE_MARGIN ); }
+
+ const RadialMap::Segment *segment;
+ const unsigned int lvl;
+ const int a;
+
+ int x1, y1, x2, y2, x3;
+ int tx, ty;
+
+ QString qs;
+ };
+
+ class LabelList : public QPtrList<Label>
+ {
+ protected:
+ int compareItems( QPtrCollection::Item item1, QPtrCollection::Item item2 )
+ {
+ //you add 1440 to work round the fact that later you want the circle split vertically
+ //and as it is you start at 3 o' clock. It's to do with rightPrevY, stops annoying bug
+
+ int a1 = ((Label*)item1)->a + 1440;
+ int a2 = ((Label*)item2)->a + 1440;
+
+ if( a1 == a2 )
+ return 0;
+
+ if( a1 > 5760 ) a1 -= 5760;
+ if( a2 > 5760 ) a2 -= 5760;
+
+ if( a1 > a2 )
+ return 1;
+
+ return -1;
+ }
+ };
+}
+
+
+void
+RadialMap::Widget::paintExplodedLabels( QPainter &paint ) const
+{
+ //we are a friend of RadialMap::Map
+
+ LabelList list; list.setAutoDelete( true );
+ QPtrListIterator<Label> it( list );
+ unsigned int startLevel = 0;
+
+
+ //1. Create list of labels sorted in the order they will be rendered
+
+ if( m_focus && m_focus->file() != m_tree ) //separate behavior for selected vs unselected segments
+ {
+ //don't bother with files
+ if( m_focus->file() && !m_focus->file()->isDirectory() )
+ return;
+
+ //find the range of levels we will be potentially drawing labels for
+ //startLevel is the level above whatever m_focus is in
+ for( const Directory *p = (const Directory*)m_focus->file(); p != m_tree; ++startLevel )
+ p = p->parent();
+
+ //range=2 means 2 levels to draw labels for
+
+ unsigned int a1, a2, minAngle;
+
+ a1 = m_focus->start();
+ a2 = m_focus->end(); //boundry angles
+ minAngle = int(m_focus->length() * LABEL_MIN_ANGLE_FACTOR);
+
+
+ #define segment (*it)
+ #define ring (m_map.m_signature + i)
+
+ //**** Levels should be on a scale starting with 0
+ //**** range is a useless parameter
+ //**** keep a topblock var which is the lowestLevel OR startLevel for identation purposes
+ for( unsigned int i = startLevel; i <= m_map.m_visibleDepth; ++i )
+ for( Iterator<Segment> it = ring->iterator(); it != ring->end(); ++it )
+ if( segment->start() >= a1 && segment->end() <= a2 )
+ if( segment->length() > minAngle )
+ list.inSort( new Label( segment, i ) );
+
+ #undef ring
+ #undef segment
+
+ } else {
+
+ #define ring m_map.m_signature
+
+ for( Iterator<Segment> it = ring->iterator(); it != ring->end(); ++it )
+ if( (*it)->length() > 288 )
+ list.inSort( new Label( (*it), 0 ) );
+
+ #undef ring
+
+ }
+
+ //2. Check to see if any adjacent labels are too close together
+ // if so, remove the least significant labels
+
+ it.toFirst();
+ QPtrListIterator<Label> jt( it );
+ ++jt;
+
+ while( jt ) //**** no need to check _it_ as jt will be NULL if _it_ was too
+ {
+ //this method is fairly efficient
+
+ if( (*it)->tooClose( (*jt)->a ) ) {
+ if( (*it)->lvl > (*jt)->lvl ) {
+ list.remove( *it );
+ it = jt;
+ }
+ else
+ list.remove( *jt );
+ }
+ else
+ ++it;
+
+ jt = it;
+ ++jt;
+ }
+
+ //used in next two steps
+ bool varySizes;
+ //**** should perhaps use doubles
+ int *sizes = new int [ m_map.m_visibleDepth + 1 ]; //**** make sizes an array of floats I think instead (or doubles)
+
+ do
+ {
+ //3. Calculate font sizes
+
+ {
+ //determine current range of levels to draw for
+ uint range = 0;
+
+ for( it.toFirst(); it != 0; ++it )
+ {
+ uint lvl = (*it)->lvl;
+ if( lvl > range )
+ range = lvl;
+
+ //**** better way would just be to assign if nothing is range
+ }
+
+ range -= startLevel; //range 0 means 1 level of labels
+
+ varySizes = Config::varyLabelFontSizes && (range != 0);
+
+ if( varySizes )
+ {
+ //create an array of font sizes for various levels
+ //will exceed normal font pitch automatically if necessary, but not minPitch
+ //**** this needs to be checked lots
+
+ //**** what if this is negative (min size gtr than default size)
+ uint step = (paint.font().pointSize() - Config::minFontPitch) / range;
+ if( step == 0 )
+ step = 1;
+
+ for( uint x = range + startLevel, y = Config::minFontPitch; x >= startLevel; y += step, --x )
+ sizes[x] = y;
+ }
+ }
+
+ //4. determine label co-ordinates
+
+ int x1, y1, x2, y2, x3, tx, ty; //coords
+ double sinra, cosra, ra; //angles
+
+ int cx = m_map.width() / 2 + m_offset.x(); //centre relative to canvas
+ int cy = m_map.height() / 2 + m_offset.y();
+
+ int spacer, preSpacer = int(m_map.m_ringBreadth * 0.5) + m_map.m_innerRadius;
+ int fullStrutLength = ( m_map.width() - m_map.MAP_2MARGIN ) / 2 + LABEL_MAP_SPACER; //full length of a strut from map center
+
+ int prevLeftY = 0;
+ int prevRightY = height();
+
+ bool rightSide;
+
+ QFont font;
+
+ for( it.toFirst(); it != 0; ++it )
+ {
+ //** bear in mind that text is drawn with QPoint param as BOTTOM left corner of text box
+ QString qs = (*it)->segment->file()->name();
+ if( varySizes )
+ font.setPointSize( sizes[(*it)->lvl] );
+ QFontMetrics fm( font );
+ int fmh = fm.height(); //used to ensure label texts don't overlap
+ int fmhD4 = fmh / 4;
+
+ fmh += LABEL_TEXT_VMARGIN;
+
+ rightSide = ( (*it)->a < 1440 || (*it)->a > 4320 );
+
+ ra = M_PI/2880 * (*it)->a; //convert to radians
+ sincos( ra, &sinra, &cosra );
+
+
+ spacer = preSpacer + m_map.m_ringBreadth * (*it)->lvl;
+
+ x1 = cx + (int)(cosra * spacer);
+ y1 = cy - (int)(sinra * spacer);
+ y2 = y1 - (int)(sinra * (fullStrutLength - spacer));
+
+ if( rightSide ) { //righthand side, going upwards
+ if( y2 > prevRightY /*- fmh*/ ) //then it is too low, needs to be drawn higher
+ y2 = prevRightY /*- fmh*/;
+ }
+ else //lefthand side, going downwards
+ if( y2 < prevLeftY/* + fmh*/ ) //then we're too high, need to be drawn lower
+ y2 = prevLeftY /*+ fmh*/;
+
+ x2 = x1 - int(double(y2 - y1) / tan( ra ));
+ ty = y2 + fmhD4;
+
+
+ if( rightSide ) {
+ if( x2 > width() || ty < fmh || x2 < x1 ) {
+ //skip this strut
+ //**** don't duplicate this code
+ list.remove( *it ); //will delete the label and set it to list.current() which _should_ be the next ptr
+ break;
+ }
+
+ prevRightY = ty - fmh - fmhD4; //must be after above's "continue"
+
+ qs = KStringHandler::cPixelSqueeze( qs, fm, width() - x2 );
+
+ x3 = width() - fm.width( qs )
+ - LABEL_HMARGIN //outer margin
+ - LABEL_TEXT_HMARGIN //margin between strut and text
+ //- ((*it)->lvl - startLevel) * LABEL_HMARGIN; //indentation
+ ;
+ if( x3 < x2 ) x3 = x2;
+ tx = x3 + LABEL_TEXT_HMARGIN;
+
+ } else {
+
+ if( x2 < 0 || ty > height() || x2 > x1 )
+ {
+ //skip this strut
+ list.remove( *it ); //will delete the label and set it to list.current() which _should_ be the next ptr
+ break;
+ }
+
+ prevLeftY = ty + fmh - fmhD4;
+
+ qs = KStringHandler::cPixelSqueeze( qs, fm, x2 );
+
+ //**** needs a little tweaking:
+
+ tx = fm.width( qs ) + LABEL_HMARGIN/* + ((*it)->lvl - startLevel) * LABEL_HMARGIN*/;
+ if( tx > x2 ) { //text is too long
+ tx = LABEL_HMARGIN + x2 - tx; //some text will be lost from sight
+ x3 = x2; //no text margin (right side of text here)
+ } else {
+ x3 = tx + LABEL_TEXT_HMARGIN;
+ tx = LABEL_HMARGIN /*+ ((*it)->lvl - startLevel) * LABEL_HMARGIN*/;
+ }
+ }
+
+ (*it)->x1 = x1;
+ (*it)->y1 = y1;
+ (*it)->x2 = x2;
+ (*it)->y2 = y2;
+ (*it)->x3 = x3;
+ (*it)->tx = tx;
+ (*it)->ty = ty;
+ (*it)->qs = qs;
+ }
+
+ //if an element is deleted at this stage, we need to do this whole
+ //iteration again, thus the following loop
+ //**** in rare case that deleted label was last label in top level
+ // and last in labelList too, this will not work as expected (not critical)
+
+ } while( it != 0 );
+
+
+ //5. Render labels
+
+ paint.setPen( QPen( Qt::black, 1 ) );
+
+ for( it.toFirst(); it != 0; ++it )
+ {
+ if( varySizes ) {
+ //**** how much overhead in making new QFont each time?
+ // (implicate sharing remember)
+ QFont font = paint.font();
+ font.setPointSize( sizes[(*it)->lvl] );
+ paint.setFont( font );
+ }
+
+ paint.drawEllipse( (*it)->x1 - 3, (*it)->y1 - 3, 7, 7 ); //**** CPU intensive! better to use a pixmap
+ paint.drawLine( (*it)->x1, (*it)->y1, (*it)->x2, (*it)->y2 );
+ paint.drawLine( (*it)->x2, (*it)->y2, (*it)->x3, (*it)->y2);
+ paint.drawText( (*it)->tx, (*it)->ty, (*it)->qs );
+ }
+
+ delete [] sizes;
+}
diff --git a/src/part/radialMap/map.cpp b/src/part/radialMap/map.cpp
new file mode 100644
index 0000000..8eb8f02
--- /dev/null
+++ b/src/part/radialMap/map.cpp
@@ -0,0 +1,442 @@
+//Author: Max Howell <[email protected]>, (C) 2003-4
+//Copyright: See COPYING file that comes with this distribution
+
+#include <kcursor.h> //make()
+#include <kglobalsettings.h> //kdeColours
+#include <kimageeffect.h> //desaturate()
+#include <qapplication.h> //make()
+#include <qimage.h> //make() & paint()
+#include <qfont.h> //ctor
+#include <qfontmetrics.h> //ctor
+#include <qpainter.h>
+
+#include "builder.h"
+#include "Config.h"
+#include "debug.h"
+#include "fileTree.h"
+#define SINCOS_H_IMPLEMENTATION (1)
+#include "sincos.h"
+#include "widget.h"
+
+#define COLOR_GREY QColor( 0, 0, 140, QColor::Hsv )
+
+
+RadialMap::Map::Map()
+ : m_signature( 0 )
+ , m_ringBreadth( MIN_RING_BREADTH )
+ , m_innerRadius( 0 )
+ , m_visibleDepth( DEFAULT_RING_DEPTH )
+{
+ //FIXME this is all broken. No longer is a maximum depth!
+ const int fmh = QFontMetrics( QFont() ).height();
+ const int fmhD4 = fmh / 4;
+ MAP_2MARGIN = 2 * ( fmh - (fmhD4 - LABEL_MAP_SPACER) ); //margin is dependent on fitting in labels at top and bottom
+}
+
+RadialMap::Map::~Map()
+{
+ delete [] m_signature;
+}
+
+void
+RadialMap::Map::invalidate( const bool desaturateTheImage )
+{
+ DEBUG_ANNOUNCE
+
+ delete [] m_signature;
+ m_signature = 0;
+
+ if( desaturateTheImage )
+ {
+ QImage img = this->convertToImage();
+
+ KImageEffect::desaturate( img, 0.7 );
+ KImageEffect::toGray( img, true );
+
+ this->convertFromImage( img );
+ }
+
+ m_visibleDepth = Config::defaultRingDepth;
+}
+
+void
+RadialMap::Map::make( const Directory *tree, bool refresh )
+{
+ DEBUG_ANNOUNCE
+
+ //**** determineText seems pointless optimisation
+ // but is it good to keep the text consistent?
+ // even if it makes it a lie?
+
+ //slow operation so set the wait cursor
+ QApplication::setOverrideCursor( KCursor::waitCursor() );
+
+ {
+ //build a signature of visible components
+ delete [] m_signature;
+ Builder builder( this, tree, refresh );
+ }
+
+ //colour the segments
+ colorise();
+
+ //determine centerText
+ if( !refresh )
+ {
+ int i;
+ for( i = 2; i > 0; --i )
+ if( tree->size() > File::DENOMINATOR[i] )
+ break;
+
+ m_centerText = tree->humanReadableSize( (File::UnitPrefix)i );
+ }
+
+ //paint the pixmap
+ aaPaint();
+
+ QApplication::restoreOverrideCursor();
+}
+
+void
+RadialMap::Map::setRingBreadth()
+{
+ DEBUG_ANNOUNCE
+
+ //FIXME called too many times on creation
+
+ m_ringBreadth = (height() - MAP_2MARGIN) / (2 * m_visibleDepth + 4);
+
+ if( m_ringBreadth < MIN_RING_BREADTH )
+ m_ringBreadth = MIN_RING_BREADTH;
+
+ else if( m_ringBreadth > MAX_RING_BREADTH )
+ m_ringBreadth = MAX_RING_BREADTH;
+}
+
+bool
+RadialMap::Map::resize( const QRect &rect )
+{
+ DEBUG_ANNOUNCE
+
+ //there's a MAP_2MARGIN border
+
+ #define mw width()
+ #define mh height()
+ #define cw rect.width()
+ #define ch rect.height()
+
+ if( cw < mw || ch < mh || (cw > mw && ch > mh) )
+ {
+ uint size = (( cw < ch ) ? cw : ch) - MAP_2MARGIN;
+
+ //this also causes uneven sizes to always resize when resizing but map is small in that dimension
+ //size -= size % 2; //even sizes mean less staggered non-antialiased resizing
+
+ {
+ const uint minSize = MIN_RING_BREADTH * 2 * (m_visibleDepth + 2);
+ const uint mD2 = MAP_2MARGIN / 2;
+
+ if( size < minSize ) size = minSize;
+
+ //this QRect is used by paint()
+ m_rect.setRect( mD2, mD2, size, size );
+ }
+
+ //resize the pixmap
+ size += MAP_2MARGIN;
+ KPixmap::resize( size, size );
+
+ // for summary widget this is a good optimisation as it happens
+ if (KPixmap::isNull())
+ return false;
+
+ if( m_signature != 0 )
+ {
+ setRingBreadth();
+ paint();
+ }
+ else fill(); //FIXME I don't like having to do this..
+
+ return true;
+ }
+
+ #undef mw
+ #undef mh
+ #undef cw
+ #undef ch
+
+ return false;
+}
+
+void
+RadialMap::Map::colorise()
+{
+ DEBUG_ANNOUNCE
+
+ QColor cp, cb;
+ double darkness = 1;
+ double contrast = (double)Config::contrast / (double)100;
+ int h, s1, s2, v1, v2;
+
+ QColor kdeColour[2] = { KGlobalSettings::inactiveTitleColor(), KGlobalSettings::activeTitleColor() };
+
+ double deltaRed = (double)(kdeColour[0].red() - kdeColour[1].red()) / 2880; //2880 for semicircle
+ double deltaGreen = (double)(kdeColour[0].green() - kdeColour[1].green()) / 2880;
+ double deltaBlue = (double)(kdeColour[0].blue() - kdeColour[1].blue()) / 2880;
+
+ for( uint i = 0; i <= m_visibleDepth; ++i, darkness += 0.04 )
+ {
+ for( Iterator<Segment> it = m_signature[i].iterator(); it != m_signature[i].end(); ++it )
+ {
+ switch( Config::scheme )
+ {
+ case 2000: //HACK for summary view
+
+ if( (*it)->file()->name() == "Used" ) {
+ cb = QApplication::palette().active().color( QColorGroup::Highlight );
+ cb.getHsv( &h, &s1, &v1 );
+
+ if( s1 > 80 )
+ s1 = 80;
+
+ v2 = v1 - int(contrast * v1);
+ s2 = s1 + int(contrast * (255 - s1));
+
+ cb.setHsv( h, s1, v1 );
+ cp.setHsv( h, s2, v2 );
+ }
+ else {
+ cp = Qt::gray;
+ cb = Qt::white;
+ }
+
+ (*it)->setPalette( cp, cb );
+
+ continue;
+
+ case Filelight::KDE:
+ {
+ //gradient will work by figuring out rgb delta values for 360 degrees
+ //then each component is angle*delta
+
+ int a = (*it)->start();
+
+ if( a > 2880 ) a = 2880 - (a - 2880);
+
+ h = (int)(deltaRed * a) + kdeColour[1].red();
+ s1 = (int)(deltaGreen * a) + kdeColour[1].green();
+ v1 = (int)(deltaBlue * a) + kdeColour[1].blue();
+
+ cb.setRgb( h, s1, v1 );
+ cb.getHsv( &h, &s1, &v1 );
+
+ break;
+ }
+
+ case Filelight::HighContrast:
+
+ cp.setHsv( 0, 0, 0 ); //values of h, s and v are irrelevant
+ cb.setHsv( 180, 0, int(255.0 * contrast) );
+ (*it)->setPalette( cp, cb );
+ continue;
+
+ default:
+ h = int((*it)->start() / 16);
+ s1 = 160;
+ v1 = (int)(255.0 / darkness); //****doing this more often than once seems daft!
+ }
+
+ v2 = v1 - int(contrast * v1);
+ s2 = s1 + int(contrast * (255 - s1));
+
+ if( s1 < 80 ) s1 = 80; //can fall too low and makes contrast between the files hard to discern
+
+ if( (*it)->isFake() ) //multi-file
+ {
+ cb.setHsv( h, s2, (v2 < 90) ? 90 : v2 ); //too dark if < 100
+ cp.setHsv( h, 17, v1 );
+ }
+ else if( !(*it)->file()->isDirectory() ) //file
+ {
+ cb.setHsv( h, 17, v1 );
+ cp.setHsv( h, 17, v2 );
+ }
+ else //directory
+ {
+ cb.setHsv( h, s1, v1 ); //v was 225
+ cp.setHsv( h, s2, v2 ); //v was 225 - delta
+ }
+
+ (*it)->setPalette( cp, cb );
+
+ //**** may be better to store KDE colours as H and S and vary V as others
+ //**** perhaps make saturation difference for s2 dependent on contrast too
+ //**** fake segments don't work with highContrast
+ //**** may work better with cp = cb rather than Qt::white
+ //**** you have to ensure the grey of files is sufficient, currently it works only with rainbow (perhaps use contrast there too)
+ //**** change v1,v2 to vp, vb etc.
+ //**** using percentages is not strictly correct as the eye doesn't work like that
+ //**** darkness factor is not done for kde_colour scheme, and also value for files is incorrect really for files in this scheme as it is not set like rainbow one is
+ }
+ }
+}
+
+void
+RadialMap::Map::aaPaint()
+{
+ //paint() is called during continuous processes
+ //aaPaint() is not and is slower so set overidecursor (make sets it too)
+ QApplication::setOverrideCursor( KCursor::waitCursor() );
+ paint( Config::antiAliasFactor );
+ QApplication::restoreOverrideCursor();
+}
+
+void
+RadialMap::Map::paint( unsigned int scaleFactor )
+{
+ DEBUG_ANNOUNCE
+
+ if (scaleFactor == 0) //just in case
+ scaleFactor = 1;
+
+ QPainter paint;
+ QRect rect = m_rect;
+ int step = m_ringBreadth;
+ int excess = -1;
+
+ //scale the pixmap, or do intelligent distribution of excess to prevent nasty resizing
+ if( scaleFactor > 1 )
+ {
+ int x1, y1, x2, y2;
+ rect.coords( &x1, &y1, &x2, &y2 );
+ x1 *= scaleFactor;
+ y1 *= scaleFactor;
+ x2 *= scaleFactor;
+ y2 *= scaleFactor;
+ rect.setCoords( x1, y1, x2, y2 );
+
+ step *= scaleFactor;
+ KPixmap::resize( this->size() * (int)scaleFactor );
+ }
+ else if( m_ringBreadth != MAX_RING_BREADTH && m_ringBreadth != MIN_RING_BREADTH ) {
+ excess = rect.width() % m_ringBreadth;
+ ++step;
+ }
+
+ //**** best option you can think of is to make the circles slightly less perfect,
+ // ** i.e. slightly eliptic when resizing inbetween
+
+ if (KPixmap::isNull())
+ return;
+
+ paint.begin( this );
+
+ fill(); //erase background
+
+ for( int x = m_visibleDepth; x >= 0; --x )
+ {
+ int width = rect.width() / 2;
+ //clever geometric trick to find largest angle that will give biggest arrow head
+ int a_max = int(acos( (double)width / double((width + 5) * scaleFactor) ) * (180*16 / M_PI));
+
+ for( ConstIterator<Segment> it = m_signature[x].constIterator(); it != m_signature[x].end(); ++it )
+ {
+ //draw the pie segments, most of this code is concerned with drawing the little
+ //arrows on the ends of segments when they have hidden files
+
+ paint.setPen( (*it)->pen() );
+
+ if( (*it)->hasHiddenChildren() )
+ {
+ //draw arrow head to indicate undisplayed files/directories
+ QPointArray pts( 3 );
+ QPoint pos, cpos = rect.center();
+ int a[3] = { (*it)->start(), (*it)->length(), 0 };
+
+ a[2] = a[0] + (a[1] / 2); //assign to halfway between
+ if( a[1] > a_max )
+ {
+ a[1] = a_max;
+ a[0] = a[2] - a_max / 2;
+ }
+
+ a[1] += a[0];
+
+ for( int i = 0, radius = width; i < 3; ++i )
+ {
+ double ra = M_PI/(180*16) * a[i], sinra, cosra;
+
+ if( i == 2 )
+ radius += 5 * scaleFactor;
+ sincos( ra, &sinra, &cosra );
+ pos.rx() = cpos.x() + static_cast<int>(cosra * radius);
+ pos.ry() = cpos.y() - static_cast<int>(sinra * radius);
+ pts.setPoint( i, pos );
+ }
+
+ paint.setBrush( (*it)->pen() );
+ paint.drawPolygon( pts );
+ }
+
+ paint.setBrush( (*it)->brush() );
+ paint.drawPie( rect, (*it)->start(), (*it)->length() );
+
+ if( (*it)->hasHiddenChildren() )
+ {
+ //**** code is bloated!
+ paint.save();
+ QPen pen = paint.pen();
+ int width = 2 * scaleFactor;
+ pen.setWidth( width );
+ paint.setPen( pen );
+ QRect rect2 = rect;
+ width /= 2;
+ rect2.addCoords( width, width, -width, -width );
+ paint.drawArc( rect2, (*it)->start(), (*it)->length() );
+ paint.restore();
+ }
+ }
+
+ if( excess >= 0 ) { //excess allows us to resize more smoothly (still crud tho)
+ if( excess < 2 ) //only decrease rect by more if even number of excesses left
+ --step;
+ excess -= 2;
+ }
+
+ rect.addCoords( step, step, -step, -step );
+ }
+
+ // if( excess > 0 ) rect.addCoords( excess, excess, 0, 0 ); //ugly
+
+ paint.setPen( COLOR_GREY );
+ paint.setBrush( Qt::white );
+ paint.drawEllipse( rect );
+
+ if( scaleFactor > 1 )
+ {
+ //have to end in order to smoothscale()
+ paint.end();
+
+ int x1, y1, x2, y2;
+ rect.coords( &x1, &y1, &x2, &y2 );
+ x1 /= scaleFactor;
+ y1 /= scaleFactor;
+ x2 /= scaleFactor;
+ y2 /= scaleFactor;
+ rect.setCoords( x1, y1, x2, y2 );
+
+ QImage img = this->convertToImage();
+ img = img.smoothScale( this->size() / (int)scaleFactor );
+ this->convertFromImage( img );
+
+ paint.begin( this );
+ paint.setPen( COLOR_GREY );
+ paint.setBrush( Qt::white );
+ }
+
+ paint.drawText( rect, Qt::AlignCenter, m_centerText );
+
+ m_innerRadius = rect.width() / 2; //rect.width should be multiple of 2
+
+ paint.end();
+}
diff --git a/src/part/radialMap/radialMap.h b/src/part/radialMap/radialMap.h
new file mode 100644
index 0000000..5023b89
--- /dev/null
+++ b/src/part/radialMap/radialMap.h
@@ -0,0 +1,72 @@
+//Author: Max Howell <[email protected]>, (C) 2003-4
+//Copyright: See COPYING file that comes with this distribution
+
+#ifndef RADIALMAP_H
+#define RADIALMAP_H
+
+#include <qcolor.h>
+
+class File;
+
+
+namespace RadialMap
+{
+ class Segment //all angles are in 16ths of degrees
+ {
+ public:
+ Segment( const File *f, uint s, uint l, bool isFake = false )
+ : m_angleStart( s )
+ , m_angleSegment( l )
+ , m_file( f )
+ , m_hasHiddenChildren( false )
+ , m_fake( isFake ) {}
+ ~Segment();
+
+ uint start() const { return m_angleStart; }
+ uint length() const { return m_angleSegment; }
+ uint end() const { return m_angleStart + m_angleSegment; }
+ const File *file() const { return m_file; }
+ const QColor& pen() const { return m_pen; }
+ const QColor& brush() const { return m_brush; }
+
+ bool isFake() const { return m_fake; }
+ bool hasHiddenChildren() const { return m_hasHiddenChildren; }
+
+ bool intersects( uint a ) const { return ( ( a >= start() ) && ( a < end() ) ); }
+
+ friend class Map;
+ friend class Builder;
+
+ private:
+ void setPalette( const QColor &p, const QColor &b ) { m_pen = p; m_brush = b; }
+
+ const uint m_angleStart, m_angleSegment;
+ const File* const m_file;
+ QColor m_pen, m_brush;
+ bool m_hasHiddenChildren;
+ const bool m_fake;
+ };
+}
+
+
+#ifndef PI
+#define PI 3.141592653589793
+#endif
+#ifndef M_PI
+#define M_PI 3.14159265358979323846264338327
+#endif
+
+#define MIN_RING_BREADTH 20
+#define MAX_RING_BREADTH 60
+#define DEFAULT_RING_DEPTH 4 //first level = 0
+#define MIN_RING_DEPTH 0
+
+#define LABEL_MAP_SPACER 7
+#define LABEL_HMARGIN 10
+#define LABEL_TEXT_HMARGIN 5
+#define LABEL_TEXT_VMARGIN 0
+#define LABEL_ANGLE_MARGIN 32
+#define LABEL_MIN_ANGLE_FACTOR 0.05
+#define LABEL_MAX_CHARS 30
+
+#endif
diff --git a/src/part/radialMap/segmentTip.cpp b/src/part/radialMap/segmentTip.cpp
new file mode 100644
index 0000000..f73e845
--- /dev/null
+++ b/src/part/radialMap/segmentTip.cpp
@@ -0,0 +1,186 @@
+//Author: Max Howell <[email protected]>, (C) 2003-4
+//Copyright: See COPYING file that comes with this distribution
+
+#include "fileTree.h"
+#include "segmentTip.h"
+
+#include <cstdlib>
+#include <kapplication.h> //installing eventFilters
+#include <kglobal.h>
+#include <kglobalsettings.h>
+#include <klocale.h>
+#include <kpixmapeffect.h>
+#include <qpainter.h>
+#include <qtooltip.h> //for its palette
+
+
+
+namespace RadialMap {
+
+
+bool isBackingStoreActive()
+{
+ // # xdpyinfo | grep backing
+ // options: backing-store YES, save-unders YES
+
+ char buffer[4096];
+ FILE *xdpyinfo = popen( "xdpyinfo", "r" );
+ int const N = fread( (void*)buffer, sizeof(char), 4096, xdpyinfo );
+ buffer[ N ] = '\0';
+ pclose( xdpyinfo );
+
+ return QString::fromLocal8Bit( buffer ).contains( "backing-store YES" );
+}
+
+
+SegmentTip::SegmentTip( uint h )
+ : QWidget( 0, 0, WNoAutoErase | WStyle_Customize | WStyle_NoBorder | WStyle_Tool | WStyle_StaysOnTop | WX11BypassWM )
+ , m_cursorHeight( -h )
+ , m_backing_store( isBackingStoreActive() )
+{
+ setBackgroundMode( Qt::NoBackground );
+}
+
+void
+SegmentTip::moveTo( QPoint p, const QWidget &canvas, bool placeAbove )
+{
+ //**** this function is very slow and seems to be visibly influenced by operations like mapFromGlobal() (who knows why!)
+ // ** so any improvements are much desired
+
+ //TODO uints could improve the class
+ p.rx() -= rect().center().x();
+ p.ry() -= (placeAbove ? 8 + height() : m_cursorHeight - 8);
+
+ const QRect screen = KGlobalSettings::desktopGeometry( parentWidget() );
+
+ const int x = p.x();
+ const int y = p.y();
+ const int x2 = x + width();
+ const int y2 = y + height(); //how's it ever gunna get below screen height?! (well you never know I spose)
+ const int sw = screen.width();
+ const int sh = screen.height();
+
+ if( x < 0 ) p.setX( 0 );
+ if( y < 0 ) p.setY( 0 );
+ if( x2 > sw ) p.rx() -= x2 - sw;
+ if( y2 > sh ) p.ry() -= y2 - sh;
+
+
+ //I'm using this QPoint to determine where to offset the bitBlt in m_pixmap
+ QPoint offset = canvas.mapToGlobal( QPoint() ) - p;
+ if( offset.x() < 0 ) offset.setX( 0 );
+ if( offset.y() < 0 ) offset.setY( 0 );
+
+
+ const QRect alphaMaskRect( canvas.mapFromGlobal( p ), size() );
+ const QRect intersection( alphaMaskRect.intersect( canvas.rect() ) );
+
+ m_pixmap.resize( size() ); //move to updateTip once you are sure it can never be null
+ bitBlt( &m_pixmap, offset, &canvas, intersection, Qt::CopyROP );
+
+ QColor const c = QToolTip::palette().color( QPalette::Active, QColorGroup::Background );
+ if (!m_backing_store)
+ m_pixmap.fill( c );
+
+ QPainter paint( &m_pixmap );
+ paint.setPen( Qt::black );
+ paint.setBrush( Qt::NoBrush );
+ paint.drawRect( rect() );
+ paint.end();
+
+ if (m_backing_store)
+ m_pixmap = KPixmapEffect::fade( m_pixmap, 0.6, c );
+
+ paint.begin( &m_pixmap );
+ paint.drawText( rect(), AlignCenter, m_text );
+ paint.end();
+
+ p += screen.topLeft(); //for Xinerama users
+
+ move( x, y );
+ show();
+ update();
+}
+
+void
+SegmentTip::updateTip( const File* const file, const Directory* const root )
+{
+ const QString s1 = file->fullPath( root );
+ QString s2 = file->humanReadableSize();
+ KLocale *loc = KGlobal::locale();
+ const uint MARGIN = 3;
+ const uint pc = 100 * file->size() / root->size();
+ uint maxw = 0;
+ uint h = fontMetrics().height()*2 + 2*MARGIN;
+
+ if( pc > 0 ) s2 += QString( " (%1%)" ).arg( loc->formatNumber( pc, 0 ) );
+
+ m_text = s1;
+ m_text += '\n';
+ m_text += s2;
+
+ if( file->isDirectory() )
+ {
+ double files = static_cast<const Directory*>(file)->children();
+ const uint pc = uint((100 * files) / (double)root->children());
+ QString s3 = i18n( "Files: %1" ).arg( loc->formatNumber( files, 0 ) );
+
+ if( pc > 0 ) s3 += QString( " (%1%)" ).arg( loc->formatNumber( pc, 0 ) );
+
+ maxw = fontMetrics().width( s3 );
+ h += fontMetrics().height();
+ m_text += '\n';
+ m_text += s3;
+ }
+
+ uint
+ w = fontMetrics().width( s1 ); if( w > maxw ) maxw = w;
+ w = fontMetrics().width( s2 ); if( w > maxw ) maxw = w;
+
+ resize( maxw + 2 * MARGIN, h );
+}
+
+bool
+SegmentTip::event( QEvent *e )
+{
+ switch( e->type() )
+ {
+ case QEvent::Show:
+ kapp->installEventFilter( this );
+ break;
+ case QEvent::Hide:
+ kapp->removeEventFilter( this );
+ break;
+ case QEvent::Paint:
+ {
+ //QPainter( this ).drawPixmap( 0, 0, m_pixmap );
+ bitBlt( this, 0, 0, &m_pixmap );
+ return true;
+ }
+ default:
+ ;
+ }
+
+ return false/*QWidget::event( e )*/;
+}
+
+bool
+SegmentTip::eventFilter( QObject*, QEvent *e )
+{
+ switch ( e->type() )
+ {
+ case QEvent::Leave:
+// case QEvent::MouseButtonPress:
+// case QEvent::MouseButtonRelease:
+ case QEvent::KeyPress:
+ case QEvent::KeyRelease:
+ case QEvent::FocusIn:
+ case QEvent::FocusOut:
+ case QEvent::Wheel:
+ hide(); //FALL THROUGH
+ default:
+ return false; //allow this event to passed to target
+ }
+}
+
+} //namespace RadialMap
diff --git a/src/part/radialMap/segmentTip.h b/src/part/radialMap/segmentTip.h
new file mode 100644
index 0000000..8bc479e
--- /dev/null
+++ b/src/part/radialMap/segmentTip.h
@@ -0,0 +1,34 @@
+// Author: Max Howell <[email protected]>, (C) 2004
+// Copyright: See COPYING file that comes with this distribution
+
+#ifndef SEGMENTTIP_H
+#define SEGMENTTIP_H
+
+#include <kpixmap.h>
+#include <qwidget.h>
+
+class File;
+class Directory;
+
+namespace RadialMap
+{
+ class SegmentTip : public QWidget
+ {
+ public:
+ SegmentTip( uint );
+
+ void updateTip( const File*, const Directory* );
+ void moveTo( QPoint, const QWidget&, bool );
+
+ private:
+ virtual bool eventFilter( QObject*, QEvent* );
+ virtual bool event( QEvent* );
+
+ uint m_cursorHeight;
+ KPixmap m_pixmap;
+ QString m_text;
+ bool m_backing_store;
+ };
+}
+
+#endif
diff --git a/src/part/radialMap/sincos.h b/src/part/radialMap/sincos.h
new file mode 100644
index 0000000..b3d8c9f
--- /dev/null
+++ b/src/part/radialMap/sincos.h
@@ -0,0 +1,25 @@
+//Author: Max Howell <[email protected]>, (C) 2003-4
+//Copyright: See COPYING file that comes with this distribution
+
+#ifndef SINCOS_H
+#define SINCOS_H
+
+#include <math.h>
+
+#if !defined(__GLIBC__) || (__GLIBC__ < 2) || (__GLIBC__ == 2 && __GLIBC_MINOR__ < 1)
+
+ void
+ sincos( int angleRadians, double *Sin, double *Cos );
+
+#ifdef SINCOS_H_IMPLEMENTATION
+ void
+ sincos( int angleRadians, double *Sin, double *Cos )
+ {
+ *Sin = sin( angleRadians );
+ *Cos = cos( angleRadians );
+ }
+#endif
+
+#endif
+
+#endif
diff --git a/src/part/radialMap/widget.cpp b/src/part/radialMap/widget.cpp
new file mode 100644
index 0000000..9c82c53
--- /dev/null
+++ b/src/part/radialMap/widget.cpp
@@ -0,0 +1,187 @@
+//Author: Max Howell <[email protected]>, (C) 2003-4
+//Copyright: See COPYING file that comes with this distribution
+
+#include <kcursor.h> //ctor
+#include <klocale.h>
+#include <kurl.h>
+#include <qapplication.h> //sendEvent
+#include <qbitmap.h> //ctor - finding cursor size
+#include <qcursor.h> //slotPostMouseEvent()
+#include <qtimer.h> //member
+
+#include "Config.h"
+#include "debug.h"
+#include "fileTree.h"
+#include "radialMap.h" //constants
+#include "widget.h"
+
+
+
+RadialMap::Widget::Widget( QWidget *parent, const char *name )
+ : QWidget( parent, name, Qt::WNoAutoErase )
+ , m_tree( 0 )
+ , m_focus( 0 )
+ , m_rootSegment( 0 ) //TODO we don't delete it, *shrug*
+{
+ setAcceptDrops( true );
+ setBackgroundColor( Qt::white );
+ const QBitmap *cursor = KCursor::handCursor().bitmap();
+ m_tip = new SegmentTip(cursor ? cursor->height() : 16);
+
+ connect( this, SIGNAL(created( const Directory* )), SLOT(sendFakeMouseEvent()) );
+ connect( this, SIGNAL(created( const Directory* )), SLOT(update()) );
+ connect( &m_timer, SIGNAL(timeout()), SLOT(resizeTimeout()) );
+}
+
+QString
+RadialMap::Widget::path() const
+{
+ return m_tree->fullPath();
+}
+
+KURL
+RadialMap::Widget::url( File const * const file ) const
+{
+ return KURL::fromPathOrURL( file ? file->fullPath() : m_tree->fullPath() );
+}
+
+void
+RadialMap::Widget::invalidate( const bool b )
+{
+ if( isValid() )
+ {
+ //**** have to check that only way to invalidate is this function frankly
+ //**** otherwise you may get bugs..
+
+ //disable mouse tracking
+ setMouseTracking( false );
+
+ //ensure this class won't think we have a map still
+ m_tree = 0;
+ m_focus = 0;
+
+ delete m_rootSegment;
+ m_rootSegment = 0;
+
+ //FIXME move this disablement thing no?
+ // it is confusing in other areas, like the whole createFromCache() thing
+ m_map.invalidate( b ); //b signifies whether the pixmap is made to look disabled or not
+ if( b )
+ update();
+
+ //tell rest of Filelight
+ emit invalidated( url() );
+ }
+}
+
+void
+RadialMap::Widget::create( const Directory *tree )
+{
+ //it is not the responsibility of create() to invalidate first
+ //skip invalidation at your own risk
+
+ //FIXME make it the responsibility of create to invalidate first
+
+ if( tree )
+ {
+ //generate the filemap image
+ m_map.make( tree );
+
+ //this is the inner circle in the center
+ m_rootSegment = new Segment( tree, 0, 16*360 );
+
+ setMouseTracking( true );
+ }
+
+ m_tree = tree;
+
+ //tell rest of Filelight
+ emit created( tree );
+}
+
+void
+RadialMap::Widget::createFromCache( const Directory *tree )
+{
+ //no scan was necessary, use cached tree, however we MUST still emit invalidate
+ invalidate( false );
+ create( tree );
+}
+
+void
+RadialMap::Widget::sendFakeMouseEvent() //slot
+{
+ QMouseEvent me( QEvent::MouseMove, mapFromGlobal( QCursor::pos() ), Qt::NoButton, Qt::NoButton );
+ QApplication::sendEvent( this, &me );
+}
+
+void
+RadialMap::Widget::resizeTimeout() //slot
+{
+ // the segments are about to erased!
+ // this was a horrid bug, and proves the OO programming should be obeyed always!
+ m_focus = 0;
+ if( m_tree )
+ m_map.make( m_tree, true );
+ update();
+}
+
+void
+RadialMap::Widget::refresh( int filth )
+{
+ //TODO consider a more direct connection
+
+ if( !m_map.isNull() )
+ {
+ switch( filth )
+ {
+ case 1:
+ m_map.make( m_tree, true ); //true means refresh only
+ break;
+
+ case 2:
+ m_map.aaPaint();
+ break;
+
+ case 3:
+ m_map.colorise(); //FALL THROUGH!
+ case 4:
+ m_map.paint();
+
+ default:
+ break;
+ }
+
+ update();
+ }
+}
+
+void
+RadialMap::Widget::zoomIn() //slot
+{
+ if( m_map.m_visibleDepth > MIN_RING_DEPTH )
+ {
+ --m_map.m_visibleDepth;
+ m_map.make( m_tree );
+ Config::defaultRingDepth = m_map.m_visibleDepth;
+ update();
+ }
+}
+
+void
+RadialMap::Widget::zoomOut() //slot
+{
+ ++m_map.m_visibleDepth;
+ m_map.make( m_tree );
+ if( m_map.m_visibleDepth > Config::defaultRingDepth )
+ Config::defaultRingDepth = m_map.m_visibleDepth;
+ update();
+}
+
+
+RadialMap::Segment::~Segment()
+{
+ if( isFake() )
+ delete m_file; //created by us in Builder::build()
+}
+
+#include "widget.moc"
diff --git a/src/part/radialMap/widget.h b/src/part/radialMap/widget.h
new file mode 100644
index 0000000..6fdf0e2
--- /dev/null
+++ b/src/part/radialMap/widget.h
@@ -0,0 +1,114 @@
+//Author: Max Howell <[email protected]>, (C) 2004
+//Copyright: See COPYING file that comes with this distribution
+
+#ifndef WIDGET_H
+#define WIDGET_H
+
+#include <kurl.h>
+#include <qtimer.h>
+#include "segmentTip.h"
+
+template <class T> class Chain;
+class Directory;
+class File;
+namespace KIO { class Job; }
+class KURL;
+
+namespace RadialMap
+{
+ class Segment;
+
+ class Map : public KPixmap
+ {
+ public:
+ Map();
+ ~Map();
+
+ void make( const Directory *, bool = false );
+ bool resize( const QRect& );
+
+ bool isNull() const { return ( m_signature == 0 ); }
+ void invalidate( const bool );
+
+ friend class Builder;
+ friend class Widget;
+
+ private:
+ void paint( uint = 1 );
+ void aaPaint();
+ void colorise();
+ void setRingBreadth();
+
+ Chain<Segment> *m_signature;
+
+ QRect m_rect;
+ uint m_ringBreadth; ///ring breadth
+ uint m_innerRadius; ///radius of inner circle
+ uint m_visibleDepth; ///visible level depth of system
+ QString m_centerText;
+
+ uint MAP_2MARGIN;
+ };
+
+ class Widget : public QWidget
+ {
+ Q_OBJECT
+
+ public:
+ Widget( QWidget* = 0, const char* = 0 );
+ ~Widget() { delete m_tip; }
+
+ QString path() const;
+ KURL url( File const * const = 0 ) const;
+
+ bool isValid() const { return m_tree != 0; }
+
+ friend class Label; //FIXME badness
+
+ public slots:
+ void zoomIn();
+ void zoomOut();
+ void create( const Directory* );
+ void invalidate( const bool = true );
+ void refresh( int );
+
+ private slots:
+ void resizeTimeout();
+ void sendFakeMouseEvent();
+ void deleteJobFinished( KIO::Job* );
+ void createFromCache( const Directory* );
+
+ signals:
+ void activated( const KURL& );
+ void invalidated( const KURL& );
+ void created( const Directory* );
+ void mouseHover( const QString& );
+ void giveMeTreeFor( const KURL& );
+
+ protected:
+ virtual void paintEvent( QPaintEvent* );
+ virtual void resizeEvent( QResizeEvent* );
+ virtual void mouseMoveEvent( QMouseEvent* );
+ virtual void mousePressEvent( QMouseEvent* );
+ virtual void dragEnterEvent( QDragEnterEvent* );
+ virtual void dropEvent( QDropEvent* );
+
+ protected:
+ const Segment *segmentAt( QPoint& ) const; //FIXME const reference for a library others can use
+ const Segment *rootSegment() const { return m_rootSegment; } ///never == 0
+ const Segment *focusSegment() const { return m_focus; } ///0 == nothing in focus
+
+ private:
+ void paintExplodedLabels( QPainter& ) const;
+
+ const Directory *m_tree;
+ const Segment *m_focus;
+ QPoint m_offset;
+ QTimer m_timer;
+ Map m_map;
+ SegmentTip *m_tip;
+ Segment *m_rootSegment;
+ };
+}
+
+#endif
diff --git a/src/part/radialMap/widgetEvents.cpp b/src/part/radialMap/widgetEvents.cpp
new file mode 100644
index 0000000..71db1c0
--- /dev/null
+++ b/src/part/radialMap/widgetEvents.cpp
@@ -0,0 +1,275 @@
+//Author: Max Howell <[email protected]>, (C) 2003-4
+//Copyright: See COPYING file that comes with this distribution
+
+#include "fileTree.h"
+#include "radialMap.h" //class Segment
+#include "widget.h"
+
+#include <cmath> //::segmentAt()
+#include <kcursor.h> //::mouseMoveEvent()
+#include <kiconeffect.h> //::mousePressEvent()
+#include <kiconloader.h> //::mousePressEvent()
+#include <kio/job.h> //::mousePressEvent()
+#include <klocale.h>
+#include <kmessagebox.h> //::mousePressEvent()
+#include <kpopupmenu.h> //::mousePressEvent()
+#include <krun.h> //::mousePressEvent()
+#include <kurldrag.h>
+#include <qapplication.h>//QApplication::setOverrideCursor()
+#include <qclipboard.h>
+#include <qpainter.h>
+#include <qtimer.h> //::resizeEvent()
+
+
+
+void
+RadialMap::Widget::resizeEvent( QResizeEvent* )
+{
+ if( m_map.resize( rect() ) )
+ m_timer.start( 500, true ); //will cause signature to rebuild for new size
+
+ //always do these as they need to be initialised on creation
+ m_offset.rx() = (width() - m_map.width()) / 2;
+ m_offset.ry() = (height() - m_map.height()) / 2;
+}
+
+void
+RadialMap::Widget::paintEvent( QPaintEvent* )
+{
+ //bltBit for some Qt setups will bitBlt _after_ the labels are painted. Which buggers things up!
+ //shame as bitBlt is faster, possibly Qt bug? Should report the bug? - seems to be race condition
+ //bitBlt( this, m_offset, &m_map );
+
+ QPainter paint( this );
+
+ paint.drawPixmap( m_offset, m_map );
+
+ //vertical strips
+ if( m_map.width() < width() )
+ {
+ paint.eraseRect( 0, 0, m_offset.x(), height() );
+ paint.eraseRect( m_map.width() + m_offset.x(), 0, m_offset.x() + 1, height() );
+ }
+ //horizontal strips
+ if( m_map.height() < height() )
+ {
+ paint.eraseRect( 0, 0, width(), m_offset.y() );
+ paint.eraseRect( 0, m_map.height() + m_offset.y(), width(), m_offset.y() + 1 );
+ }
+
+ //exploded labels
+ if( !m_map.isNull() && !m_timer.isActive() )
+ paintExplodedLabels( paint );
+}
+
+const RadialMap::Segment*
+RadialMap::Widget::segmentAt( QPoint &e ) const
+{
+ //determine which segment QPoint e is above
+
+ e -= m_offset;
+
+ if( !m_map.m_signature )
+ return 0;
+
+ if( e.x() <= m_map.width() && e.y() <= m_map.height() )
+ {
+ //transform to cartesian coords
+ e.rx() -= m_map.width() / 2; //should be an int
+ e.ry() = m_map.height() / 2 - e.y();
+
+ double length = hypot( e.x(), e.y() );
+
+ if( length >= m_map.m_innerRadius ) //not hovering over inner circle
+ {
+ uint depth = ((int)length - m_map.m_innerRadius) / m_map.m_ringBreadth;
+
+ if( depth <= m_map.m_visibleDepth ) //**** do earlier since you can //** check not outside of range
+ {
+ //vector calculation, reduces to simple trigonometry
+ //cos angle = (aibi + ajbj) / albl
+ //ai = x, bi=1, aj=y, bj=0
+ //cos angle = x / (length)
+
+ uint a = (uint)(acos( (double)e.x() / length ) * 916.736); //916.7324722 = #radians in circle * 16
+
+ //acos only understands 0-180 degrees
+ if( e.y() < 0 ) a = 5760 - a;
+
+ #define ring (m_map.m_signature + depth)
+ for( ConstIterator<Segment> it = ring->constIterator(); it != ring->end(); ++it )
+ if( (*it)->intersects( a ) )
+ return *it;
+ #undef ring
+ }
+ }
+ else return m_rootSegment; //hovering over inner circle
+ }
+
+ return 0;
+}
+
+void
+RadialMap::Widget::mouseMoveEvent( QMouseEvent *e )
+{
+ //set m_focus to what we hover over, update UI if it's a new segment
+
+ Segment const * const oldFocus = m_focus;
+ QPoint p = e->pos();
+
+ m_focus = segmentAt( p ); //NOTE p is passed by non-const reference
+
+ if( m_focus && m_focus->file() != m_tree )
+ {
+ if( m_focus != oldFocus ) //if not same as last time
+ {
+ setCursor( KCursor::handCursor() );
+ m_tip->updateTip( m_focus->file(), m_tree );
+ emit mouseHover( m_focus->file()->fullPath() );
+
+ //repaint required to update labels now before transparency is generated
+ repaint( false );
+ }
+
+ m_tip->moveTo( e->globalPos(), *this, ( p.y() < 0 ) ); //updates tooltip psuedo-tranparent background
+ }
+ else if( oldFocus && oldFocus->file() != m_tree )
+ {
+ unsetCursor();
+ m_tip->hide();
+ update();
+
+ emit mouseHover( QString::null );
+ }
+}
+
+void
+RadialMap::Widget::mousePressEvent( QMouseEvent *e )
+{
+ //m_tip is hidden already by event filter
+ //m_focus is set correctly (I've been strict, I assure you it is correct!)
+
+ enum { Konqueror, Konsole, Center, Open, Copy, Delete };
+
+ if (m_focus && !m_focus->isFake())
+ {
+ const KURL url = Widget::url( m_focus->file() );
+ const bool isDir = m_focus->file()->isDirectory();
+
+ if( e->button() == Qt::RightButton )
+ {
+ KPopupMenu popup;
+ popup.insertTitle( m_focus->file()->fullPath( m_tree ) );
+
+ if (isDir) {
+ popup.insertItem( SmallIconSet( "konqueror" ), i18n( "Open &Konqueror Here" ), Konqueror );
+
+ if( url.protocol() == "file" )
+ popup.insertItem( SmallIconSet( "konsole" ), i18n( "Open &Konsole Here" ), Konsole );
+
+ if (m_focus->file() != m_tree) {
+ popup.insertSeparator();
+ popup.insertItem( SmallIconSet( "viewmag" ), i18n( "&Center Map Here" ), Center );
+ }
+ }
+ else
+ popup.insertItem( SmallIconSet( "fileopen" ), i18n( "&Open" ), Open );
+
+ popup.insertSeparator();
+ popup.insertItem( SmallIconSet( "editcopy" ), i18n( "&Copy to clipboard" ), Copy );
+
+ popup.insertSeparator();
+ popup.insertItem( SmallIconSet( "editdelete" ), i18n( "&Delete" ), Delete );
+
+ switch (popup.exec( e->globalPos(), 1 )) {
+ case Konqueror:
+ //KRun::runCommand will show an error message if there was trouble
+ KRun::runCommand( QString( "kfmclient openURL \"%1\"" ).arg( url.url() ) );
+ break;
+
+ case Konsole:
+ // --workdir only works for local file paths
+ KRun::runCommand( QString( "konsole --workdir \"%1\"" ).arg( url.path() ) );
+ break;
+
+ case Center:
+ case Open:
+ goto section_two;
+
+ case Copy:
+ QApplication::clipboard()->setData( new KURLDrag( KURL::List( url ) ) );
+ break;
+
+ case Delete:
+ {
+ const KURL url = Widget::url( m_focus->file() );
+ const QString message = m_focus->file()->isDirectory()
+ ? i18n( "<qt>The directory at <i>'%1'</i> will be <b>recursively</b> and <b>permanently</b> deleted." )
+ : i18n( "<qt><i>'%1'</i> will be <b>permanently</b> deleted." );
+ const int userIntention = KMessageBox::warningContinueCancel(
+ this, message.arg( url.prettyURL() ),
+ QString::null, KGuiItem( i18n("&Delete"), "editdelete" ) );
+
+ if (userIntention == KMessageBox::Continue) {
+ KIO::Job *job = KIO::del( url );
+ job->setWindow( this );
+ connect( job, SIGNAL(result( KIO::Job* )), SLOT(deleteJobFinished( KIO::Job* )) );
+ QApplication::setOverrideCursor( KCursor::workingCursor() );
+ }
+ }
+
+ default:
+ //ensure m_focus is set for new mouse position
+ sendFakeMouseEvent();
+ }
+ }
+ else { // not right mouse button
+
+ section_two:
+ const QRect rect( e->x() - 20, e->y() - 20, 40, 40 );
+
+ m_tip->hide(); // user expects this
+
+ if (!isDir || e->button() == Qt::MidButton) {
+ KIconEffect::visualActivate( this, rect );
+ new KRun( url, this, true ); //FIXME see above
+ }
+ else if (m_focus->file() != m_tree) { // is left click
+ KIconEffect::visualActivate( this, rect );
+ emit activated( url ); //activate first, this will cause UI to prepare itself
+ createFromCache( (Directory *)m_focus->file() );
+ }
+ else
+ emit giveMeTreeFor( url.upURL() );
+ }
+ }
+}
+
+void
+RadialMap::Widget::deleteJobFinished( KIO::Job *job )
+{
+ QApplication::restoreOverrideCursor();
+ if( !job->error() )
+ invalidate();
+ else
+ job->showErrorDialog( this );
+}
+
+#include "debug.h"
+void
+RadialMap::Widget::dropEvent( QDropEvent *e )
+{
+ DEBUG_ANNOUNCE
+
+ KURL::List urls;
+ if (KURLDrag::decode( e, urls ) && urls.count())
+ emit giveMeTreeFor( urls.first() );
+}
+
+void
+RadialMap::Widget::dragEnterEvent( QDragEnterEvent *e )
+{
+ DEBUG_ANNOUNCE
+
+ e->accept( KURLDrag::canDecode( e ) );
+}
diff --git a/src/part/remoteLister.cpp b/src/part/remoteLister.cpp
new file mode 100644
index 0000000..dcfbbee
--- /dev/null
+++ b/src/part/remoteLister.cpp
@@ -0,0 +1,160 @@
+//Author: Max Howell <[email protected]>, (C) 2003-4
+//Copyright: See COPYING file that comes with this distribution
+
+#include <debug.h>
+#include "fileTree.h"
+#include <qapplication.h>
+#include <qtimer.h>
+#include <qvaluelist.h>
+#include "remoteLister.h"
+#include "scan.h"
+
+namespace Filelight
+{
+ //you need to use a single DirLister
+ //one per directory breaks KIO (seemingly) and also uses un-godly amounts of memory!
+
+ //TODO delete all this stuff!
+
+ struct Store {
+
+ typedef QValueList<Store*> List;
+
+ /// location of the directory
+ const KURL url;
+ /// the directory on which we are operating
+ Directory *directory;
+ /// so we can reference the parent store
+ Store *parent;
+ /// directories in this directory that need to be scanned before we can propagate()
+ List stores;
+
+ Store()
+ : directory( 0 ), parent( 0 ) {}
+ Store( const KURL &u, const QString &name, Store *s )
+ : url( u ), directory( new Directory( name.local8Bit() + '/' ) ), parent( s ) {}
+
+
+ Store*
+ propagate()
+ {
+ /// returns the next store available for scanning
+
+ debug() << "propagate: " << url << endl;
+
+ if( parent ) {
+ parent->directory->append( directory );
+ if( parent->stores.isEmpty() ) {
+ return parent->propagate();
+ }
+ else
+ return parent;
+ }
+
+ //we reached the root, let's get our next directory scanned
+ return this;
+ }
+
+ private:
+ Store( Store& );
+ Store &operator=( const Store& );
+ };
+
+
+ RemoteLister::RemoteLister( const KURL &url, QWidget *parent )
+ : KDirLister( true /*don't fetch mimetypes*/ )
+ , m_root( new Store( url, url.url(), 0 ) )
+ , m_store( m_root )
+ {
+ setAutoUpdate( false ); //don't use KDirWatchers
+ setShowingDotFiles( true ); //stupid KDirLister API function names
+ setMainWindow( parent );
+
+ //use SIGNAL(result(KIO::Job*)) instead and then use Job::error()
+ connect( this, SIGNAL(completed()), SLOT(completed()) );
+ connect( this, SIGNAL(canceled()), SLOT(canceled()) );
+
+ //we do this non-recursively - it is the only way!
+ openURL( url );
+ }
+
+ RemoteLister::~RemoteLister()
+ {
+ Directory *tree = isFinished() ? m_store->directory : 0;
+
+ QCustomEvent *e = new QCustomEvent( 1000 );
+ e->setData( tree );
+ QApplication::postEvent( parent(), e );
+
+ delete m_root;
+ }
+
+ void
+ RemoteLister::completed()
+ {
+ debug() << "completed: " << url().prettyURL() << endl;
+
+ //as usual KDE documentation didn't suggest I needed to do this at all
+ //I had to figure it out myself
+ // -- avoid crash
+ QTimer::singleShot( 0, this, SLOT(_completed()) );
+ }
+
+ void
+ RemoteLister::canceled()
+ {
+ debug() << "canceled: " << url().prettyURL() << endl;
+
+ QTimer::singleShot( 0, this, SLOT(_completed()) );
+ }
+
+ void
+ RemoteLister::_completed()
+ {
+ //m_directory is set to the directory we should operate on
+
+ KFileItemList items = KDirLister::items();
+ for( KFileItemList::ConstIterator it = items.begin(), end = items.end(); it != end; ++it )
+ {
+ if( (*it)->isDir() )
+ m_store->stores += new Store( (*it)->url(), (*it)->name(), m_store );
+ else
+ m_store->directory->append( (*it)->name().local8Bit(), (*it)->size() / 1024 );
+
+ ScanManager::s_files++;
+ }
+
+
+ if( m_store->stores.isEmpty() )
+ //no directories to scan, so we need to append ourselves to the parent directory
+ //propagate() will return the next ancestor that has stores left to be scanned, or root if we are done
+ m_store = m_store->propagate();
+
+ if( !m_store->stores.isEmpty() )
+ {
+ Store::List::Iterator first = m_store->stores.begin();
+ const KURL url( (*first)->url );
+ Store *currentStore = m_store;
+
+ //we should operate with this store next time this function is called
+ m_store = *first;
+
+ //we don't want to handle this store again
+ currentStore->stores.remove( first );
+
+ //this returns _immediately_
+ debug() << "scanning: " << url << endl;
+ openURL( url );
+ }
+ else {
+
+ debug() << "I think we're done\n";
+
+ Q_ASSERT( m_root == m_store );
+
+ delete this;
+ }
+ }
+}
+
+#include "remoteLister.moc"
diff --git a/src/part/remoteLister.h b/src/part/remoteLister.h
new file mode 100644
index 0000000..4736dc1
--- /dev/null
+++ b/src/part/remoteLister.h
@@ -0,0 +1,28 @@
+//Author: Max Howell <[email protected]>, (C) 2003-4
+//Copyright: See COPYING file that comes with this distribution
+
+#ifndef REMOTELISTER_H
+#define REMOTELISTER_H
+
+#include <kdirlister.h>
+
+namespace Filelight
+{
+ class RemoteLister : public KDirLister
+ {
+ Q_OBJECT
+ public:
+ RemoteLister( const KURL &url, QWidget *parent );
+ ~RemoteLister();
+
+ private slots:
+ void completed();
+ void _completed();
+ void canceled();
+
+ private:
+ class Store *m_root, *m_store;
+ };
+}
+
+#endif
diff --git a/src/part/scan.cpp b/src/part/scan.cpp
new file mode 100644
index 0000000..2101624
--- /dev/null
+++ b/src/part/scan.cpp
@@ -0,0 +1,204 @@
+//Author: Max Howell <[email protected]>, (C) 2003-4
+//Copyright: See COPYING file that comes with this distribution
+
+#include "debug.h"
+#include "fileTree.h"
+#include <kcursor.h>
+#include "localLister.h"
+#include <qapplication.h>
+#include "remoteLister.h"
+#include "scan.h"
+
+
+namespace Filelight
+{
+ bool ScanManager::s_abort = false;
+ uint ScanManager::s_files = 0;
+
+ ScanManager::ScanManager( QObject *parent )
+ : QObject( parent )
+ , m_thread( 0 )
+ , m_cache( new Chain<Directory> )
+ {
+ Filelight::LocalLister::readMounts();
+ }
+
+ ScanManager::~ScanManager()
+ {
+ if( m_thread ) {
+ debug() << "Attempting to abort scan operation...\n";
+ s_abort = true;
+ m_thread->wait();
+ }
+
+ delete m_cache;
+
+ //RemoteListers are QObjects and get automatically deleted
+ }
+
+ bool
+ ScanManager::running() const
+ {
+ //FIXME not complete
+ return m_thread && m_thread->running();
+ }
+
+ bool
+ ScanManager::start( const KURL &url )
+ {
+ //url is guarenteed clean and safe
+
+ debug() << "Scan requested for: " << url.prettyURL() << endl;
+
+ if( running() ) {
+ //shouldn't happen, but lets prevent mega-disasters just in case eh?
+ kdWarning() << "Attempted to run 2 scans concurrently!\n";
+ //TODO give user an error
+ return false;
+ }
+
+ s_files = 0;
+ s_abort = false;
+
+ if( url.protocol() == "file" )
+ {
+ const QString path = url.path( 1 );
+
+ Chain<Directory> *trees = new Chain<Directory>;
+
+ /* CHECK CACHE
+ * user wants: /usr/local/
+ * cached: /usr/
+ *
+ * user wants: /usr/
+ * cached: /usr/local/, /usr/include/
+ */
+
+ for( Iterator<Directory> it = m_cache->iterator(); it != m_cache->end(); ++it )
+ {
+ QString cachePath = (*it)->name();
+
+ if( path.startsWith( cachePath ) ) //then whole tree already scanned
+ {
+ //find a pointer to the requested branch
+
+ debug() << "Cache-(a)hit: " << cachePath << endl;
+
+ QStringList split = QStringList::split( '/', path.mid( cachePath.length() ) );
+ Directory *d = *it;
+ Iterator<File> jt;
+
+ while( !split.isEmpty() && d != NULL ) //if NULL we have got lost so abort!!
+ {
+ jt = d->iterator();
+
+ const Link<File> *end = d->end();
+ QString s = split.first(); s += '/';
+
+ for( d = 0; jt != end; ++jt )
+ if( s == (*jt)->name() )
+ {
+ d = (Directory*)*jt;
+ break;
+ }
+
+ split.pop_front();
+ }
+
+ if( d )
+ {
+ delete trees;
+
+ //we found a completed tree, thus no need to scan
+ debug() << "Found cache-handle, generating map..\n";
+
+ //1001 indicates that this should not be cached
+ QCustomEvent *e = new QCustomEvent( 1001 );
+ e->setData( d );
+ QApplication::postEvent( this, e );
+
+ return true;
+ }
+ else
+ {
+ //something went wrong, we couldn't find the directory we were expecting
+ error() << "Didn't find " << path << " in the cache!\n";
+ delete it.remove(); //safest to get rid of it
+ break; //do a full scan
+ }
+ }
+ else if( cachePath.startsWith( path ) ) //then part of the requested tree is already scanned
+ {
+ debug() << "Cache-(b)hit: " << cachePath << endl;
+ it.transferTo( *trees );
+ }
+ }
+
+ m_url.setPath( path ); //FIXME stop switching between paths and KURLs all the time
+ QApplication::setOverrideCursor( KCursor::workingCursor() );
+ //starts listing by itself
+ m_thread = new Filelight::LocalLister( path, trees, this );
+ return true;
+ }
+
+ m_url = url;
+ QApplication::setOverrideCursor( KCursor::workingCursor() );
+ //will start listing straight away
+ QObject *o = new Filelight::RemoteLister( url, (QWidget*)parent() );
+ insertChild( o );
+ o->setName( "remote_lister" );
+ return true;
+ }
+
+ bool
+ ScanManager::abort()
+ {
+ s_abort = true;
+
+ delete child( "remote_lister" );
+
+ return m_thread && m_thread->running();
+ }
+
+ void
+ ScanManager::emptyCache()
+ {
+ s_abort = true;
+
+ if( m_thread && m_thread->running() )
+ m_thread->wait();
+
+ emit aboutToEmptyCache();
+
+ m_cache->empty();
+ }
+
+ void
+ ScanManager::customEvent( QCustomEvent *e )
+ {
+ Directory *tree = (Directory*)e->data();
+
+ if( m_thread ) {
+ m_thread->terminate();
+ m_thread->wait();
+ delete m_thread; //note the lister deletes itself
+ m_thread = 0;
+ }
+
+ emit completed( tree );
+
+ if( tree ) {
+ //we don't cache foreign stuff
+ //we don't recache stuff (thus only type 1000 events)
+ if( e->type() == 1000 && m_url.protocol() == "file" )
+ //TODO sanity check the cache
+ m_cache->append( tree );
+ }
+ else //scan failed
+ m_cache->empty(); //FIXME this is safe but annoying
+
+ QApplication::restoreOverrideCursor();
+ }
+}
+
+#include "scan.moc"
diff --git a/src/part/scan.h b/src/part/scan.h
new file mode 100644
index 0000000..c02b1d2
--- /dev/null
+++ b/src/part/scan.h
@@ -0,0 +1,52 @@
+//Author: Max Howell <[email protected]>, (C) 2003-4
+//Copyright: See COPYING file that comes with this distribution
+
+#ifndef SCAN_H
+#define SCAN_H
+
+#include <kurl.h>
+#include <qobject.h>
+
+class QThread;
+class Directory;
+template<class T> class Chain;
+
+namespace Filelight
+{
+ class ScanManager : public QObject
+ {
+ Q_OBJECT
+
+ friend class LocalLister;
+ friend class RemoteLister;
+
+ public:
+ ScanManager( QObject *parent );
+ virtual ~ScanManager();
+
+ bool start( const KURL& );
+ bool running() const;
+
+ static uint files() { return s_files; }
+
+ public slots:
+ bool abort();
+ void emptyCache();
+
+ signals:
+ void completed( Directory* );
+ void aboutToEmptyCache();
+
+ private:
+ static bool s_abort;
+ static uint s_files;
+
+ KURL m_url;
+ QThread *m_thread;
+ Chain<Directory> *m_cache;
+
+ virtual void customEvent( QCustomEvent* );
+ };
+}
+
+#endif
diff --git a/src/part/settingsDialog.cpp b/src/part/settingsDialog.cpp
new file mode 100644
index 0000000..508904a
--- /dev/null
+++ b/src/part/settingsDialog.cpp
@@ -0,0 +1,214 @@
+//Author: Max Howell <[email protected]>, (C) 2003-4
+//Copyright: See COPYING file that comes with this distribution
+
+#include <qapplication.h> //Getting desktop width
+#include <qcheckbox.h>
+#include <qpushbutton.h>
+#include <qradiobutton.h>
+#include <qslider.h>
+#include <qvbuttongroup.h>
+
+#include <kdirselectdialog.h>
+#include <kiconloader.h>
+#include <klocale.h>
+#include <kmessagebox.h>
+#include <knuminput.h>
+
+#include "settingsDialog.h"
+#include "Config.h"
+
+
+SettingsDialog::SettingsDialog( QWidget *parent, const char *name )
+ : Dialog( parent, name, false ) //3rd param => modal
+{
+ colourSchemeGroup->setFrameShape( QFrame::NoFrame );
+
+ colourSchemeGroup->insert( new QRadioButton( i18n("Rainbow"), colourSchemeGroup ), Filelight::Rainbow );
+ colourSchemeGroup->insert( new QRadioButton( i18n("KDE Colors"), colourSchemeGroup ), Filelight::KDE );
+ colourSchemeGroup->insert( new QRadioButton( i18n("High Contrast"), colourSchemeGroup ), Filelight::HighContrast );
+
+ //read in settings before you make all those nasty connections!
+ reset(); //makes dialog reflect global settings
+
+ connect( &m_timer, SIGNAL(timeout()), SIGNAL(mapIsInvalid()) );
+
+ connect( m_addButton, SIGNAL( clicked() ), SLOT( addDirectory() ) );
+ connect( m_removeButton, SIGNAL( clicked() ), SLOT( removeDirectory() ) );
+ connect( m_resetButton, SIGNAL( clicked() ), SLOT( reset() ) );
+ connect( m_closeButton, SIGNAL( clicked() ), SLOT( close() ) );
+
+ connect( colourSchemeGroup, SIGNAL(clicked( int )), SLOT(changeScheme( int )) );
+ connect( contrastSlider, SIGNAL(valueChanged( int )), SLOT(changeContrast( int )) );
+ connect( contrastSlider, SIGNAL(sliderReleased()), SLOT(slotSliderReleased()) );
+
+ connect( scanAcrossMounts, SIGNAL( toggled( bool ) ), SLOT( startTimer() ) );
+ connect( dontScanRemoteMounts, SIGNAL( toggled( bool ) ), SLOT( startTimer() ) );
+ connect( dontScanRemovableMedia, SIGNAL( toggled( bool ) ), SLOT( startTimer() ) );
+
+ connect( useAntialiasing, SIGNAL( toggled( bool ) ), SLOT( toggleUseAntialiasing( bool ) ) );
+ connect( varyLabelFontSizes, SIGNAL( toggled( bool ) ), SLOT( toggleVaryLabelFontSizes( bool ) ) );
+ connect( showSmallFiles, SIGNAL( toggled( bool ) ), SLOT( toggleShowSmallFiles( bool ) ) );
+
+ connect( minFontPitch, SIGNAL ( valueChanged( int ) ), SLOT( changeMinFontPitch( int ) ) );
+
+ m_addButton->setIconSet( SmallIcon( "fileopen" ) );
+ m_resetButton->setIconSet( SmallIcon( "undo" ) );
+ m_closeButton->setIconSet( SmallIcon( "fileclose" ) );
+}
+
+
+void SettingsDialog::closeEvent( QCloseEvent* )
+{
+ //if an invalidation is pending, force it now!
+ if( m_timer.isActive() ) m_timer.changeInterval( 0 );
+
+ Config::write();
+
+ deleteLater();
+}
+
+
+void SettingsDialog::reset()
+{
+ Config::read();
+
+ //tab 1
+ scanAcrossMounts->setChecked( Config::scanAcrossMounts );
+ dontScanRemoteMounts->setChecked( !Config::scanRemoteMounts );
+ dontScanRemovableMedia->setChecked( !Config::scanRemovableMedia );
+
+ dontScanRemoteMounts->setEnabled( Config::scanAcrossMounts );
+ // dontScanRemovableMedia.setEnabled( Config::scanAcrossMounts );
+
+ m_listBox->clear();
+ m_listBox->insertStringList( Config::skipList );
+ m_listBox->setSelected( 0, true );
+
+ m_removeButton->setEnabled( m_listBox->count() == 0 );
+
+ //tab 2
+ if( colourSchemeGroup->id( colourSchemeGroup->selected() ) != Config::scheme )
+ {
+ colourSchemeGroup->setButton( Config::scheme );
+ //setButton doesn't call a single QButtonGroup signal!
+ //so we need to call this ourselves (and hence the detection above)
+ changeScheme( Config::scheme );
+ }
+ contrastSlider->setValue( Config::contrast );
+
+ useAntialiasing->setChecked( (Config::antiAliasFactor > 1) ? true : false );
+
+ varyLabelFontSizes->setChecked( Config::varyLabelFontSizes );
+ minFontPitch->setEnabled( Config::varyLabelFontSizes );
+ minFontPitch->setValue( Config::minFontPitch );
+ showSmallFiles->setChecked( Config::showSmallFiles );
+}
+
+
+
+void SettingsDialog::toggleScanAcrossMounts( bool b )
+{
+ Config::scanAcrossMounts = b;
+
+ dontScanRemoteMounts->setEnabled( b );
+ //dontScanRemovableMedia.setEnabled( b );
+}
+
+void SettingsDialog::toggleDontScanRemoteMounts( bool b )
+{
+ Config::scanRemoteMounts = !b;
+}
+
+void SettingsDialog::toggleDontScanRemovableMedia( bool b )
+{
+ Config::scanRemovableMedia = !b;
+}
+
+
+
+void SettingsDialog::addDirectory()
+{
+ const KURL url = KDirSelectDialog::selectDirectory( "/", false, this );
+
+ //TODO error handling!
+ //TODO wrong protocol handling!
+
+ if( !url.isEmpty() )
+ {
+ const QString path = url.path( 1 );
+
+ if( !Config::skipList.contains( path ) )
+ {
+ Config::skipList.append( path );
+ m_listBox->insertItem( path );
+ m_removeButton->setEnabled( true );
+ }
+ else KMessageBox::sorry( this, i18n("That directory is already set to be excluded from scans") );
+ }
+}
+
+
+void SettingsDialog::removeDirectory()
+{
+ Config::skipList.remove( m_listBox->currentText() ); //removes all entries that match
+
+ //safest method to ensure consistency
+ m_listBox->clear();
+ m_listBox->insertStringList( Config::skipList );
+
+ m_removeButton->setEnabled( m_listBox->count() == 0 );
+}
+
+
+void SettingsDialog::startTimer()
+{
+ m_timer.start( TIMEOUT, true );
+}
+
+void SettingsDialog::changeScheme( int s )
+{
+ Config::scheme = (Filelight::MapScheme)s;
+ emit canvasIsDirty( 1 );
+}
+void SettingsDialog::changeContrast( int c )
+{
+ Config::contrast = c;
+ emit canvasIsDirty( 3 );
+}
+void SettingsDialog::toggleUseAntialiasing( bool b )
+{
+ Config::antiAliasFactor = b ? 2 : 1;
+ emit canvasIsDirty( 2 );
+}
+void SettingsDialog::toggleVaryLabelFontSizes( bool b )
+{
+ Config::varyLabelFontSizes = b;
+ minFontPitch->setEnabled( b );
+ emit canvasIsDirty( 0 );
+}
+void SettingsDialog::changeMinFontPitch( int p )
+{
+ Config::minFontPitch = p;
+ emit canvasIsDirty( 0 );
+}
+void SettingsDialog::toggleShowSmallFiles( bool b )
+{
+ Config::showSmallFiles = b;
+ emit canvasIsDirty( 1 );
+}
+
+
+void SettingsDialog::slotSliderReleased()
+{
+ emit canvasIsDirty( 2 );
+}
+
+
+void SettingsDialog::reject()
+{
+ //called when escape is pressed
+ reset();
+ QDialog::reject(); //**** doesn't change back scheme so far
+}
+
+#include "settingsDialog.moc"
diff --git a/src/part/settingsDialog.h b/src/part/settingsDialog.h
new file mode 100644
index 0000000..b3ed375
--- /dev/null
+++ b/src/part/settingsDialog.h
@@ -0,0 +1,48 @@
+//Author: Max Howell <[email protected]>, (C) 2003-4
+//Copyright: See COPYING file that comes with this distribution
+
+#ifndef SETTINGSDLG_H
+#define SETTINGSDLG_H
+
+#include "dialog.h" //generated by uic
+#include <qtimer.h>
+
+
+class SettingsDialog : public Dialog
+{
+Q_OBJECT
+
+public:
+ SettingsDialog( QWidget* =0, const char* =0 );
+
+protected:
+ virtual void closeEvent( QCloseEvent * );
+ virtual void reject();
+
+public slots:
+ void addDirectory();
+ void removeDirectory();
+ void toggleScanAcrossMounts( bool );
+ void toggleDontScanRemoteMounts( bool );
+ void toggleDontScanRemovableMedia( bool );
+ void reset();
+ void startTimer();
+ void toggleUseAntialiasing( bool = true );
+ void toggleVaryLabelFontSizes( bool );
+ void changeContrast( int );
+ void changeScheme( int );
+ void changeMinFontPitch( int );
+ void toggleShowSmallFiles( bool );
+ void slotSliderReleased();
+
+signals:
+ void mapIsInvalid();
+ void canvasIsDirty( int );
+
+private:
+ QTimer m_timer;
+
+ static const uint TIMEOUT=1000;
+};
+
+#endif
diff --git a/src/part/summaryWidget.cpp b/src/part/summaryWidget.cpp
new file mode 100644
index 0000000..0e3a140
--- /dev/null
+++ b/src/part/summaryWidget.cpp
@@ -0,0 +1,236 @@
+//Author: Max Howell <[email protected]>, (C) 2004
+//Copyright: See COPYING file that comes with this distribution
+
+#include "Config.h"
+#include "debug.h"
+#include "fileTree.h"
+#include <kcursor.h>
+#include <kiconeffect.h> //MyRadialMap::mousePressEvent()
+#include <kiconloader.h>
+#include <klocale.h>
+#include <qlabel.h>
+#include <qlayout.h>
+#include <qtextstream.h>
+#include <qvbox.h>
+#include "radialMap/radialMap.h"
+#include "radialMap/widget.h"
+#include "summaryWidget.h"
+#include "summaryWidget.moc"
+
+
+static Filelight::MapScheme oldScheme;
+
+
+struct Disk
+{
+ QString device;
+ QString type;
+ QString mount;
+ QString icon;
+
+ int size;
+ int used;
+ int free; //NOTE used+avail != size (clustersize!)
+
+ void guessIconName();
+};
+
+
+struct DiskList : QValueList<Disk>
+{
+ DiskList();
+};
+
+
+class MyRadialMap : public RadialMap::Widget
+{
+public:
+ MyRadialMap( QWidget *parent )
+ : RadialMap::Widget( parent )
+ {}
+
+ virtual void setCursor( const QCursor &c )
+ {
+ if( focusSegment() && focusSegment()->file()->name() == "Used" )
+ RadialMap::Widget::setCursor( c );
+ else
+ unsetCursor();
+ }
+
+ virtual void mousePressEvent( QMouseEvent *e )
+ {
+ const RadialMap::Segment *segment = focusSegment();
+
+ //we will allow right clicks to the center circle
+ if( segment == rootSegment() )
+ RadialMap::Widget::mousePressEvent( e );
+
+ //and clicks to the used segment
+ else if( segment && segment->file()->name() == "Used" ) {
+ const QRect rect( e->x() - 20, e->y() - 20, 40, 40 );
+ KIconEffect::visualActivate( this, rect );
+ emit activated( url() );
+ }
+ }
+};
+
+
+
+SummaryWidget::SummaryWidget( QWidget *parent, const char *name )
+ : QWidget( parent, name )
+{
+ qApp->setOverrideCursor( KCursor::waitCursor() );
+
+ setPaletteBackgroundColor( Qt::white );
+ (new QGridLayout( this, 1, 2 ))->setAutoAdd( true );
+
+ createDiskMaps();
+
+ qApp->restoreOverrideCursor();
+}
+
+SummaryWidget::~SummaryWidget()
+{
+ Config::scheme = oldScheme;
+}
+
+void
+SummaryWidget::createDiskMaps()
+{
+ DiskList disks;
+
+ const QCString free = i18n( "Free" ).local8Bit();
+ const QCString used = i18n( "Used" ).local8Bit();
+
+ KIconLoader loader;
+
+ oldScheme = Config::scheme;
+ Config::scheme = (Filelight::MapScheme)2000;
+
+ for (DiskList::ConstIterator it = disks.begin(), end = disks.end(); it != end; ++it)
+ {
+ Disk const &disk = *it;
+
+ if (disk.free == 0 && disk.used == 0)
+ continue;
+
+ QWidget *box = new QVBox( this );
+ RadialMap::Widget *map = new MyRadialMap( box );
+
+ QString text; QTextOStream( &text )
+ << "<img src='" << loader.iconPath( disk.icon, KIcon::Toolbar ) << "'>"
+ << " &nbsp;" << disk.mount << " "
+ << "<i>(" << disk.device << ")</i>";
+
+ QLabel *label = new QLabel( text, box );
+ label->setAlignment( Qt::AlignCenter );
+ label->setSizePolicy( QSizePolicy::Minimum, QSizePolicy::Maximum );
+
+ box->show(); // will show its children too
+
+ Directory *tree = new Directory( disk.mount.local8Bit() );
+ tree->append( free, disk.free );
+ tree->append( used, disk.used );
+
+ map->create( tree ); //must be done when visible
+
+ connect( map, SIGNAL(activated( const KURL& )), SIGNAL(activated( const KURL& )) );
+ }
+}
+
+
+#if defined(_OS_LINUX_)
+#define DF_ARGS "-kT"
+#else
+#define DF_ARGS "-k"
+#define NO_FS_TYPE
+#endif
+
+
+DiskList::DiskList()
+{
+ //FIXME bug prone
+ setenv( "LANG", "en_US", 1 );
+ setenv( "LC_ALL", "en_US", 1 );
+ setenv( "LC_MESSAGES", "en_US", 1 );
+ setenv( "LC_TYPE", "en_US", 1 );
+ setenv( "LANGUAGE", "en_US", 1 );
+
+ char buffer[4096];
+ FILE *df = popen( "env LC_ALL=POSIX df " DF_ARGS, "r" );
+ int const N = fread( (void*)buffer, sizeof(char), 4096, df );
+ buffer[ N ] = '\0';
+ pclose( df );
+
+ QString output = QString::fromLocal8Bit( buffer );
+ QTextStream t( &output, IO_ReadOnly );
+ QString const BLANK( QChar(' ') );
+
+ while (!t.atEnd()) {
+ QString s = t.readLine();
+ s = s.simplifyWhiteSpace();
+
+ if (s.isEmpty())
+ continue;
+
+ if (s.find( BLANK ) < 0) // devicename was too long, rest in next line
+ if (!t.eof()) { // just appends the next line
+ QString v = t.readLine();
+ s = s.append( v.latin1() );
+ s = s.simplifyWhiteSpace();
+ }
+
+ Disk disk;
+ disk.device = s.left( s.find( BLANK ) );
+ s = s.remove( 0, s.find( BLANK ) + 1 );
+
+ #ifndef NO_FS_TYPE
+ disk.type = s.left( s.find( BLANK ) );
+ s = s.remove( 0, s.find( BLANK ) + 1 );
+ #endif
+
+ int n = s.find( BLANK );
+ disk.size = s.left( n ).toInt();
+ s = s.remove( 0, n + 1 );
+
+ n = s.find( BLANK );
+ disk.used = s.left( n ).toInt();
+ s = s.remove( 0, n + 1 );
+
+ n = s.find( BLANK );
+ disk.free = s.left( n ).toInt();
+ s = s.remove( 0, n + 1 );
+
+ s = s.remove( 0, s.find( BLANK ) + 1 ); // delete the capacity 94%
+ disk.mount = s;
+
+ disk.guessIconName();
+
+ *this += disk;
+ }
+}
+
+
+void
+Disk::guessIconName()
+{
+ if( mount.contains( "cdrom", false ) ) icon = "cdrom";
+ else if( device.contains( "cdrom", false ) ) icon = "cdrom";
+ else if( mount.contains( "writer", false ) ) icon = "cdwriter";
+ else if( device.contains( "writer", false ) ) icon = "cdwriter";
+ else if( mount.contains( "mo", false ) ) icon = "mo";
+ else if( device.contains( "mo", false ) ) icon = "mo";
+ else if( device.contains( "fd", false ) ) {
+ if( device.contains( "360", false ) ) icon = "5floppy";
+ if( device.contains( "1200", false ) ) icon = "5floppy";
+ else
+ icon = "3floppy";
+ }
+ else if( mount.contains( "floppy", false ) ) icon = "3floppy";
+ else if( mount.contains( "zip", false ) ) icon = "zip";
+ else if( type.contains( "nfs", false ) ) icon = "nfs";
+ else
+ icon = "hdd";
+
+ icon += /*mounted() ? */"_mount"/* : "_unmount"*/;
+}
diff --git a/src/part/summaryWidget.h b/src/part/summaryWidget.h
new file mode 100644
index 0000000..7f20153
--- /dev/null
+++ b/src/part/summaryWidget.h
@@ -0,0 +1,25 @@
+//Author: Max Howell <[email protected]>, (C) 2004
+//Copyright: See COPYING file that comes with this distribution
+
+#ifndef FILELIGHTSUMMARY_H
+#define FILELIGHTSUMMARY_H
+
+#include <qwidget.h>
+
+
+class SummaryWidget : public QWidget
+{
+ Q_OBJECT
+
+public:
+ SummaryWidget( QWidget *parent, const char *name );
+ ~SummaryWidget();
+
+signals:
+ void activated( const KURL& );
+
+private:
+ void createDiskMaps();
+};
+
+#endif