summaryrefslogtreecommitdiffstats
path: root/konq-plugins/fsview
diff options
context:
space:
mode:
Diffstat (limited to 'konq-plugins/fsview')
-rw-r--r--konq-plugins/fsview/Makefile.am51
-rw-r--r--konq-plugins/fsview/README29
-rw-r--r--konq-plugins/fsview/fsview.cpp540
-rw-r--r--konq-plugins/fsview/fsview.desktop120
-rw-r--r--konq-plugins/fsview/fsview.h136
-rw-r--r--konq-plugins/fsview/fsview_part.cpp413
-rw-r--r--konq-plugins/fsview/fsview_part.desktop58
-rw-r--r--konq-plugins/fsview/fsview_part.h122
-rw-r--r--konq-plugins/fsview/fsview_part.rc15
-rw-r--r--konq-plugins/fsview/hi22-app-fsview.pngbin0 -> 834 bytes
-rw-r--r--konq-plugins/fsview/hi32-app-fsview.pngbin0 -> 902 bytes
-rw-r--r--konq-plugins/fsview/inode.cpp385
-rw-r--r--konq-plugins/fsview/inode.h97
-rw-r--r--konq-plugins/fsview/main.cpp56
-rw-r--r--konq-plugins/fsview/scan.cpp362
-rw-r--r--konq-plugins/fsview/scan.h230
-rw-r--r--konq-plugins/fsview/scantest.cpp56
-rw-r--r--konq-plugins/fsview/treemap.cpp3199
-rw-r--r--konq-plugins/fsview/treemap.h742
19 files changed, 6611 insertions, 0 deletions
diff --git a/konq-plugins/fsview/Makefile.am b/konq-plugins/fsview/Makefile.am
new file mode 100644
index 0000000..a400096
--- /dev/null
+++ b/konq-plugins/fsview/Makefile.am
@@ -0,0 +1,51 @@
+INCLUDES= $(all_includes)
+METASOURCES = AUTO
+
+KDE_ICON = fsview
+
+EXTRA_DIST = main.cpp fsview.cpp fsview.h scan.cpp scan.h scantest.cpp \
+ inode.h inode.cpp \
+ fsview.desktop hi32-app-fsview.png
+
+#xdg_apps_DATA = fsview.desktop
+
+messages: rc.cpp
+ LIST=`find . -name \*.h -o -name \*.cpp`; \
+ if test -n "$$LIST"; then \
+ $(XGETTEXT) $$LIST -o $(podir)/fsview.pot; \
+ fi
+
+# Used both by application and KPart
+
+noinst_LTLIBRARIES = libfsview.la
+libfsview_la_SOURCES = treemap.cpp fsview.cpp scan.cpp inode.cpp
+
+# Application
+
+bin_PROGRAMS = fsview
+
+fsview_LDFLAGS = $(all_libraries) $(KDE_RPATH)
+fsview_SOURCES = main.cpp
+fsview_LDADD = libfsview.la $(LIB_KIO) $(LIB_KDECORE) $(LIB_QT)
+
+check_PROGRAMS = scantest
+
+scantest_LDFLAGS = $(all_libraries) $(KDE_RPATH)
+scantest_SOURCES = scantest.cpp
+scantest_LDADD = libfsview.la $(LIB_KIO) $(LIB_KDECORE) $(LIB_QT)
+
+# The KPart
+
+kde_module_LTLIBRARIES = libfsviewpart.la
+libfsviewpart_la_SOURCES = fsview_part.cpp
+libfsviewpart_la_LDFLAGS = -module $(KDE_PLUGIN) $(all_libraries)
+libfsviewpart_la_LIBADD = libfsview.la $(LIB_KPARTS) $(LIB_KIO) -lkonq
+
+partdesktopdir = $(kde_servicesdir)
+partdesktop_DATA = fsview_part.desktop
+
+rcdir = $(kde_datadir)/fsview
+rc_DATA = fsview_part.rc
+
+appsdir = $(kde_appsdir)/.hidden
+apps_DATA = fsview.desktop
diff --git a/konq-plugins/fsview/README b/konq-plugins/fsview/README
new file mode 100644
index 0000000..faf1c44
--- /dev/null
+++ b/konq-plugins/fsview/README
@@ -0,0 +1,29 @@
+What's this?
+============
+
+Josef Weidendorfer
+Josef.Weidendorfer@gmx.de
+
+FSView is a tool for showing disc utilization in a graphical form, much
+like the UNIX command 'du'. The visualisation type choosen is a treemap.
+Treemaps allow for showing metrics of objects in nested structures, like
+sizes of files and directories on your hard disc, where the the size of
+directories is defined to be the sum of the size of its children.
+Each object is represented by a rectangle which area is proportional to
+its metric. The metric must have the property that the sum of the
+children's metric of some object is equal or smaller than the objects
+metric. This holds true for the file/directory sizes in the use case of
+FSView.
+
+It's provided both as a Konqueror KPart plugin for the mime type
+inode/directory, and a standalone executable.
+
+This was meant as a small test application and usage tutorial for
+the TreeMap widget developed within KCachegrind. As it's quite cool
+and small, it is now provided as a Konqueror addon in KDE.
+
+For a full featured graphical 'du', see KDirStat. It's quite similar
+to FSView, but allows for lot of cleanup actions.
+
+Happy space hunting,
+Josef
diff --git a/konq-plugins/fsview/fsview.cpp b/konq-plugins/fsview/fsview.cpp
new file mode 100644
index 0000000..b0c82d8
--- /dev/null
+++ b/konq-plugins/fsview/fsview.cpp
@@ -0,0 +1,540 @@
+/* This file is part of FSView.
+ Copyright (C) 2002, 2003 Josef Weidendorfer <Josef.Weidendorfer@gmx.de>
+
+ KCachegrind 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, version 2.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+/*
+ * FSView specialisaton of TreeMap classes.
+ */
+
+
+#include <qdir.h>
+#include <qpopupmenu.h>
+#include <qtimer.h>
+
+#include <kapplication.h>
+#include <kconfig.h>
+#include <kdebug.h>
+#include <kglobal.h>
+#include <klocale.h>
+#include <kmessagebox.h>
+#include <kmimetype.h>
+#include <kurl.h>
+
+#include <kio/global.h>
+
+#include "fsview.h"
+
+
+// FSView
+
+QMap<QString, MetricEntry> FSView::_dirMetric;
+
+FSView::FSView(Inode* base, QWidget* parent, const char* name)
+ : TreeMapWidget(base, parent, name)
+{
+ setFieldType(0, i18n("Name"));
+ setFieldType(1, i18n("Size"));
+ setFieldType(2, i18n("File Count"));
+ setFieldType(3, i18n("Directory Count"));
+ setFieldType(4, i18n("Last Modified"));
+ setFieldType(5, i18n("Owner"));
+ setFieldType(6, i18n("Group"));
+ setFieldType(7, i18n("Mime Type"));
+
+ // defaults
+ setVisibleWidth(4, true);
+ setSplitMode(TreeMapItem::Rows);
+ setFieldForced(0, true); // show directory names
+ setFieldForced(1, true); // show directory sizes
+ setSelectionMode(TreeMapWidget::Extended);
+
+ _colorMode = Depth;
+ _pathDepth = 0;
+ _allowRefresh = true;
+
+ _progressPhase = 0;
+ _chunkData1 = 0;
+ _chunkData2 = 0;
+ _chunkData3 = 0;
+ _chunkSize1 = 0;
+ _chunkSize2 = 0;
+ _chunkSize3 = 0;
+ _progressSize = 0;
+ _progress = 0;
+ _dirsFinished = 0;
+ _lastDir = 0;
+
+ _config = new KConfig("fsviewrc");
+
+ // restore TreeMap visualization options of last execution
+ KConfigGroup tmconfig(_config, QCString("TreeMap"));
+ restoreOptions(&tmconfig);
+ QString str = tmconfig.readEntry("ColorMode");
+ if (!str.isEmpty()) setColorMode(str);
+
+ if (_dirMetric.count() == 0) {
+ // restore metric cache
+ KConfigGroup cconfig(_config, QCString("MetricCache"));
+ int ccount = cconfig.readNumEntry("Count", 0);
+ int i, f, d;
+ double s;
+ QString str;
+ for (i=1;i<=ccount;i++) {
+ str = QString("Dir%1").arg(i);
+ if (!cconfig.hasKey(str)) continue;
+ str = cconfig.readPathEntry(str);
+ s = cconfig.readDoubleNumEntry(QString("Size%1").arg(i), 0.0);
+ f = cconfig.readNumEntry(QString("Files%1").arg(i), 0);
+ d = cconfig.readNumEntry(QString("Dirs%1").arg(i), 0);
+ if (s==0.0 || f==0 || d==0) continue;
+ setDirMetric(str, s, f, d);
+ }
+ }
+
+ _sm.setListener(this);
+}
+
+FSView::~FSView()
+{
+ delete _config;
+}
+
+void FSView::stop()
+{
+ _sm.stopScan();
+}
+
+void FSView::setPath(QString p)
+{
+ Inode* b = (Inode*)base();
+ if (!b) return;
+
+ //kdDebug(90100) << "FSView::setPath " << p << endl;
+
+ // stop any previous updating
+ stop();
+
+ QFileInfo fi(p);
+ _path = fi.absFilePath();
+ if (!fi.isDir()) {
+ _path = fi.dirPath(true);
+ }
+ _pathDepth = _path.contains('/');
+
+ KURL u;
+ u.setPath(_path);
+ if (!kapp->authorizeURLAction("list", KURL(), u))
+ {
+ QString msg = KIO::buildErrorString(KIO::ERR_ACCESS_DENIED, u.prettyURL());
+ KMessageBox::queuedMessageBox(this, KMessageBox::Sorry, msg);
+ }
+
+ ScanDir* d = _sm.setTop(_path);
+
+ b->setPeer(d);
+
+ setCaption(QString("%1 - FSView").arg(_path));
+ requestUpdate(b);
+}
+
+KURL::List FSView::selectedUrls()
+{
+ TreeMapItemList s = selection();
+ TreeMapItem* i;
+ KURL::List urls;
+
+ for(i=s.first();i;i=s.next()) {
+ KURL u;
+ u.setPath( ((Inode*)i)->path() );
+ urls.append(u);
+ }
+ return urls;
+}
+
+bool FSView::getDirMetric(const QString& k,
+ double& s, unsigned int& f, unsigned int& d)
+{
+ QMap<QString, MetricEntry>::iterator it;
+
+ it = _dirMetric.find(k);
+ if (it == _dirMetric.end()) return false;
+
+ s = (*it).size;
+ f = (*it).fileCount;
+ d = (*it).dirCount;
+
+ if (0) kdDebug(90100) << "getDirMetric " << k << endl;
+ if (0) kdDebug(90100) << " - got size " << s << ", files " << f << endl;
+
+ return true;
+}
+
+void FSView::setDirMetric(const QString& k,
+ double s, unsigned int f, unsigned int d)
+{
+ if (0) kdDebug(90100) << "setDirMetric '" << k << "': size "
+ << s << ", files " << f << ", dirs " << d << endl;
+ _dirMetric.insert(k, MetricEntry(s, f, d));
+}
+
+void FSView::requestUpdate(Inode* i)
+{
+ if (0) kdDebug(90100) << "FSView::requestUpdate(" << i->path()
+ << ")" << endl;
+
+ ScanDir* peer = i->dirPeer();
+ if (!peer) return;
+
+ peer->clear();
+ i->clear();
+
+ if (!_sm.scanRunning()) {
+ QTimer::singleShot(0, this, SLOT(doUpdate()));
+ QTimer::singleShot(100, this, SLOT(doRedraw()));
+
+ /* start new progress chunk */
+ _progressPhase = 1;
+ _chunkData1 += 3;
+ _chunkData2 = _chunkData1 + 1;
+ _chunkData3 = _chunkData1 + 2;
+ _chunkSize1 = 0;
+ _chunkSize2 = 0;
+ _chunkSize3 = 0;
+ peer->setData(_chunkData1);
+
+ _progressSize = 0;
+ _progress = 0;
+ _dirsFinished = 0;
+ _lastDir = 0;
+ emit started();
+ }
+
+ _sm.startScan(peer);
+}
+
+void FSView::scanFinished(ScanDir* d)
+{
+ /* if finished directory was from last progress chunk, increment */
+ int data = d->data();
+ switch(_progressPhase) {
+ case 1:
+ if (data == _chunkData1) _chunkSize1--;
+ break;
+ case 2:
+ if (data == _chunkData1) _progress++;
+ if (data == _chunkData2) _chunkSize2--;
+ break;
+ case 3:
+ if ((data == _chunkData1) ||
+ (data == _chunkData2)) _progress++;
+ if (data == _chunkData3) _chunkSize3--;
+ break;
+ case 4:
+ if ((data == _chunkData1) ||
+ (data == _chunkData2) ||
+ (data == _chunkData3)) _progress++;
+ break;
+ default:
+ break;
+ }
+
+ _lastDir = d;
+ _dirsFinished++;
+
+ if (0) kdDebug(90100) << "FSFiew::scanFinished: " << d->path()
+ << ", Data " << data
+ << ", Progress " << _progress << "/"
+ << _progressSize << endl;
+}
+
+void FSView::selected(TreeMapItem* i)
+{
+ setPath(((Inode*)i)->path());
+}
+
+void FSView::contextMenu(TreeMapItem* i, const QPoint& p)
+{
+ QPopupMenu popup;
+
+ QPopupMenu* spopup = new QPopupMenu();
+ QPopupMenu* dpopup = new QPopupMenu();
+ QPopupMenu* apopup = new QPopupMenu();
+ QPopupMenu* fpopup = new QPopupMenu();
+
+ // choosing from the selection menu will give a selectionChanged() signal
+ addSelectionItems(spopup, 901, i);
+ popup.insertItem(i18n("Go To"), spopup, 900);
+
+ popup.insertItem(i18n("Go Up"), 2);
+ popup.insertSeparator();
+ popup.insertItem(i18n("Stop Refresh"), 3);
+ popup.setItemEnabled(3, _sm.scanRunning());
+ popup.insertItem(i18n("Refresh"), 5);
+ popup.setItemEnabled(5, !_sm.scanRunning());
+
+ if (i) popup.insertItem(i18n("Refresh '%1'").arg(i->text(0)), 4);
+ popup.insertSeparator();
+ addDepthStopItems(dpopup, 1001, i);
+ popup.insertItem(i18n("Stop at Depth"), dpopup, 1000);
+ addAreaStopItems(apopup, 1101, i);
+ popup.insertItem(i18n("Stop at Area"), apopup, 1100);
+ addFieldStopItems(fpopup, 1201, i);
+ popup.insertItem(i18n("Stop at Name"), fpopup, 1200);
+
+ popup.insertSeparator();
+
+ QPopupMenu* cpopup = new QPopupMenu();
+ addColorItems(cpopup, 1401);
+ popup.insertItem(i18n("Color Mode"), cpopup, 1400);
+ QPopupMenu* vpopup = new QPopupMenu();
+ addVisualizationItems(vpopup, 1301);
+ popup.insertItem(i18n("Visualization"), vpopup, 1300);
+
+ _allowRefresh = false;
+ int r = popup.exec(mapToGlobal(p));
+ _allowRefresh = true;
+
+ if (r==1)
+ selected(i);
+ else if (r==2) {
+ Inode* i = (Inode*) base();
+ if (i) setPath(i->path()+"/..");
+ }
+ else if (r==3)
+ stop();
+ else if (r==4) {
+ //((Inode*)i)->refresh();
+ requestUpdate( (Inode*)i );
+ }
+ else if (r==5) {
+ Inode* i = (Inode*) base();
+ if (i) requestUpdate(i);
+ }
+}
+
+void FSView::saveMetric(KConfigGroup* g)
+{
+ QMap<QString, MetricEntry>::iterator it;
+ int c = 1;
+ for (it=_dirMetric.begin();it!=_dirMetric.end();++it) {
+ g->writePathEntry(QString("Dir%1").arg(c), it.key());
+ g->writeEntry(QString("Size%1").arg(c), (*it).size);
+ g->writeEntry(QString("Files%1").arg(c), (*it).fileCount);
+ g->writeEntry(QString("Dirs%1").arg(c), (*it).dirCount);
+ c++;
+ }
+ g->writeEntry("Count", c-1);
+}
+
+void FSView::setColorMode(FSView::ColorMode cm)
+{
+ if (_colorMode == cm) return;
+
+ _colorMode = cm;
+ redraw();
+}
+
+bool FSView::setColorMode(QString mode)
+{
+ if (mode == "None") setColorMode(None);
+ else if (mode == "Depth") setColorMode(Depth);
+ else if (mode == "Name") setColorMode(Name);
+ else if (mode == "Owner") setColorMode(Owner);
+ else if (mode == "Group") setColorMode(Group);
+ else if (mode == "Mime") setColorMode(Mime);
+ else return false;
+
+ return true;
+}
+
+QString FSView::colorModeString() const
+{
+ QString mode;
+ switch(_colorMode) {
+ case None: mode = "None"; break;
+ case Depth: mode = "Depth"; break;
+ case Name: mode = "Name"; break;
+ case Owner: mode = "Owner"; break;
+ case Group: mode = "Group"; break;
+ case Mime: mode = "Mime"; break;
+ default: mode = "Unknown"; break;
+ }
+ return mode;
+}
+
+void FSView::addColorItems(QPopupMenu* popup, int id)
+{
+ _colorID = id;
+ popup->setCheckable(true);
+
+ connect(popup, SIGNAL(activated(int)),
+ this, SLOT(colorActivated(int)));
+
+ popup->insertItem(i18n("None"), id);
+ popup->insertItem(i18n("Depth"), id+1);
+ popup->insertItem(i18n("Name"), id+2);
+ popup->insertItem(i18n("Owner"), id+3);
+ popup->insertItem(i18n("Group"), id+4);
+ popup->insertItem(i18n("Mime Type"), id+5);
+
+ switch(colorMode()) {
+ case None: popup->setItemChecked(id,true); break;
+ case Depth: popup->setItemChecked(id+1,true); break;
+ case Name: popup->setItemChecked(id+2,true); break;
+ case Owner: popup->setItemChecked(id+3,true); break;
+ case Group: popup->setItemChecked(id+4,true); break;
+ case Mime: popup->setItemChecked(id+5,true); break;
+ default: break;
+ }
+}
+
+void FSView::colorActivated(int id)
+{
+ if (id == _colorID) setColorMode(None);
+ else if (id == _colorID+1) setColorMode(Depth);
+ else if (id == _colorID+2) setColorMode(Name);
+ else if (id == _colorID+3) setColorMode(Owner);
+ else if (id == _colorID+4) setColorMode(Group);
+ else if (id == _colorID+5) setColorMode(Mime);
+}
+
+void FSView::saveFSOptions()
+{
+ KConfigGroup tmconfig(_config, QCString("TreeMap"));
+ saveOptions(&tmconfig);
+ tmconfig.writeEntry("ColorMode", colorModeString());
+
+ KConfigGroup gconfig(_config, QCString("General"));
+ gconfig.writeEntry("Path", _path);
+
+ KConfigGroup cconfig(_config, QCString("MetricCache"));
+ saveMetric(&cconfig);
+}
+
+void FSView::quit()
+{
+ saveFSOptions();
+ KApplication::kApplication()->quit();
+}
+
+void FSView::doRedraw()
+{
+ // we update progress every 1/4 second, and redraw every second
+ static int redrawCounter = 0;
+
+ bool redo = _sm.scanRunning();
+ if (!redo) redrawCounter = 0;
+
+ if ((_progress>0) && (_progressSize>0) && _lastDir) {
+ int percent = _progress * 100 / _progressSize;
+ if (0) kdDebug(90100) << "FSView::progress "
+ << _progress << "/" << _progressSize
+ << "= " << percent << "%, "
+ << _dirsFinished << " dirs read, in "
+ << _lastDir->path() << endl;
+ emit progress(percent, _dirsFinished, _lastDir->path());
+ }
+
+
+ if (_allowRefresh && ((redrawCounter%4)==0)) {
+ if (0) kdDebug(90100) << "doRedraw " << _sm.scanLength() << endl;
+ redraw();
+ }
+ else
+ redo = true;
+
+ if (redo) {
+ QTimer::singleShot(500, this, SLOT(doRedraw()));
+ redrawCounter++;
+ }
+}
+
+
+void FSView::doUpdate()
+{
+ for(int i=0;i<5;i++) {
+ switch(_progressPhase) {
+ case 1:
+ _chunkSize1 += _sm.scan(_chunkData1);
+ if (_chunkSize1 > 100) {
+ _progressPhase = 2;
+
+ /* Go to maximally 33% by scaling with 3 */
+ _progressSize = 3 * _chunkSize1;
+
+ if (1) kdDebug(90100) << "Phase 2: CSize " << _chunkSize1 << endl;
+ }
+ break;
+
+ case 2:
+ /* progress phase 2 */
+ _chunkSize2 += _sm.scan(_chunkData2);
+ /* switch to Phase 3 if we reach 80 % of Phase 2 */
+ if (_progress * 3 > _progressSize * 8/10) {
+ _progressPhase = 3;
+
+ /* Goal: Keep percentage equal from phase 2 to 3 */
+ double percent = (double)_progress / _progressSize;
+ /* We scale by factor 2/3 aferwards */
+ percent = percent * 3/2;
+
+ int todo = _chunkSize2 + (_progressSize/3 - _progress);
+ _progressSize = (int) ((double)todo / (1.0 - percent));
+ _progress = _progressSize - todo;
+
+ /* Go to maximally 66% by scaling with 1.5 */
+ _progressSize = _progressSize *3/2;
+
+ if (1) kdDebug(90100) << "Phase 3: CSize " << _chunkSize2
+ << ", Todo " << todo
+ << ", Progress " << _progress
+ << "/" << _progressSize << endl;
+ }
+ break;
+
+ case 3:
+ /* progress phase 3 */
+ _chunkSize3 += _sm.scan(_chunkData3);
+ /* switch to Phase 4 if we reach 80 % of Phase 3 */
+ if (_progress * 3/2 > _progressSize * 8/10) {
+ _progressPhase = 4;
+
+ /* Goal: Keep percentage equal from phase 2 to 3 */
+ double percent = (double)_progress / _progressSize;
+ int todo = _chunkSize3 + (_progressSize*2/3 - _progress);
+ _progressSize = (int)((double)todo / (1.0 - percent) + .5);
+ _progress = _progressSize - todo;
+
+ if (1) kdDebug(90100) << "Phase 4: CSize " << _chunkSize3
+ << ", Todo " << todo
+ << ", Progress " << _progress
+ << "/" << _progressSize << endl;
+ }
+
+ default:
+ _sm.scan(-1);
+ break;
+ }
+ }
+
+ if (_sm.scanRunning())
+ QTimer::singleShot(0, this, SLOT(doUpdate()));
+ else
+ emit completed(_dirsFinished);
+}
+
+#include "fsview.moc"
diff --git a/konq-plugins/fsview/fsview.desktop b/konq-plugins/fsview/fsview.desktop
new file mode 100644
index 0000000..6f7f163
--- /dev/null
+++ b/konq-plugins/fsview/fsview.desktop
@@ -0,0 +1,120 @@
+# KDE Config File
+[Desktop Entry]
+Type=Application
+Exec=fsview -caption "%c" %i %m %u
+MimeType=inode/directory
+Icon=fsview
+DocPath=konq-plugins/fsview/index.html
+Terminal=false
+Name=File Size Viewer
+Name[bg]=Преглед на файловия размер
+Name[bs]=Preglednik veličine datoteka
+Name[ca]=Visor de mides de fitxers
+Name[cs]=Prohlížeč velikostí souborů
+Name[da]=Filstørrelses fremviser
+Name[de]=Dateigrößenbetrachter
+Name[el]=Προβολέας μεγέθους αρχείων
+Name[eo]=Dosiergrandeca rigardilo
+Name[es]=Visor del tamaño del archivo
+Name[et]=Failisuuruse näitaja
+Name[eu]=Fitxategi neurrien ikustailea
+Name[fa]=مشاهده‌گر اندازۀ پرونده
+Name[fi]=Tiedostokoon näyttäjä
+Name[fr]=Afficheur de taille de fichiers
+Name[fy]=werjefte ôfbyldingsgrutte
+Name[gl]=Visor do Tamaño de Ficheiros
+Name[he]=מציג גדלי קבצים
+Name[hi]=फ़ाइल आकार प्रदर्शक
+Name[hr]=Preglednik veličine datoteka
+Name[hu]=Fájlméret-megtekintő
+Name[is]=Skoða stærð skráa
+Name[it]=Visualizzatore della dimensione dei file
+Name[ja]=ファイルサイズビューア
+Name[ka]=ფაილის ზომის მხილველი
+Name[kk]=Файл көлемін қарау
+Name[km]=កម្មវិធី​មើល​ទំហំ​ឯកសារ
+Name[lt]=Bylų dydžio stebėjimo priemonė
+Name[mk]=Прегледувач на големина на датотеки
+Name[ms]=Pelihat Saiz Fail
+Name[nb]=Filstørrelseviser
+Name[nds]=Dateigrött-Kieker
+Name[ne]=फाइल साइज दर्शक
+Name[nl]=Afbeeldinggrootteweergave
+Name[nn]=Filstorleikvisar
+Name[pa]=ਫਾਇਲ ਅਕਾਰ ਦਰਸ਼ਕ
+Name[pl]=Przeglądarka rozmiaru plików
+Name[pt]=Visualizador do Tamanho de Ficheiros
+Name[pt_BR]=Visão do Tam. do arquivo
+Name[ru]=Размер файла
+Name[sk]=Prehliadač veľkosti súborov
+Name[sl]=Pregledovalnik velikosti datotek
+Name[sr]=Приказивач величине фајла
+Name[sr@Latn]=Prikazivač veličine fajla
+Name[sv]=Filstorleksvisning
+Name[ta]=கோப்பு அளவு காட்சியாளன்
+Name[tg]=Андозаи файл
+Name[tr]=Dosya Boyut Göstericisi
+Name[uk]=Переглядач розміру файлів
+Name[uz]=Fayl hajmini koʻruvchi
+Name[uz@cyrillic]=Файл ҳажмини кўрувчи
+Name[vi]=Bộ xem kích cỡ tập tin
+Name[zh_CN]=文件大小查看器
+Name[zh_TW]=檔案大小檢視器
+Comment=View your filesystem as a TreeMap
+Comment[ar]=عرض لنظام الملفات بنمط شجري
+Comment[bg]=Преглед на файловата система
+Comment[bs]=Prikazuje vaš datotečni sistem kao mapu stabla
+Comment[ca]=Visualitza el vostre sistema de fitxers com a un mapa arbrolat
+Comment[cs]=Zobrazení souborového systému ve stylu stromové mapy
+Comment[cy]=Gweld eich cysawd ffeiliau fel MapCoeden
+Comment[da]=Vis dit filsystem som et trækort
+Comment[de]=Dateisystem als Hierarchiestruktur betrachten
+Comment[el]=Προβολή του συστήματος αρχείων σας σαν δενδρική δομή
+Comment[eo]=Vidu vian dosiersistemon kiel ArbMapo
+Comment[es]=Ver su sistema de archivos en forma árbol
+Comment[et]=Võimalus vaadata failisüsteemi puukujulisena
+Comment[eu]=Ikusi zure fitxategi sistema arbola balitz lez
+Comment[fa]=سیستم پرونده‌تان را به عنوان یک TreeMap مشاهده کنید
+Comment[fi]=Katsele tiedostojärjestelmääsi puukarttana
+Comment[fr]=Afficher votre système de fichiers sous la forme d'une arborescence
+Comment[fy]=Besjoch jo triemsysteem as beamstruktuer
+Comment[gl]=Ver o sistema de ficheiros como unha árbore-mapa
+Comment[he]=תצוגת הקבצים שלך בתור עץ
+Comment[hi]=आपके फ़ाइल-सिस्टम को एक ट्री-मैप में दिखाए
+Comment[hr]=Prikazuje datotečni sustav u obliku stabla
+Comment[hu]=A fájlrendszer áttekintése fastruktúraként
+Comment[is]=Flakka um skráarkerfið í tráarham
+Comment[it]=Visualizza il tuo filesystem come una mappa ad albero
+Comment[ja]=ツリーマップでファイルシステムを表示
+Comment[ka]=ფაილური სისტემის ხის სახით ხილვა
+Comment[kk]=Файлдар жүйесінің бұтақтарын қарау
+Comment[km]=មើល​ប្រព័ន្ធ​ឯកសារ​របស់​អ្នក​​ជា​ផែនទី​មែកធាង
+Comment[lt]=Peržiūrėti bylų sistemą kaip medžio tipo žemėlapį
+Comment[mk]=Прегледајте го Вашиот датотечен систем како мапа на стебло
+Comment[ms]=Lihat sistem fail anda sebagai TreeMap
+Comment[nb]=Se filsystemet som et tre-kart
+Comment[nds]=Dien Dateisysteem as Boomkoort
+Comment[ne]=ट्री मानचित्रका रुपमा तपाईँको फाइल प्रणाली हेर्नुहोस्
+Comment[nl]=Bekijk uw bestandssysteem als boomstructuur
+Comment[nn]=Viser filsystemet som eit trekart
+Comment[pa]=ਆਪਣੇ ਫਾਇਲ ਸਿਸਟਮ ਨੂੰ ਲੜੀ-ਖਾਕੇ ਦੇ ਰੂਪ ਵਿੱਚ ਵੇਖੋ
+Comment[pl]=Przedstawia system plików jako drzewo
+Comment[pt]=Ver o sistema de ficheiros como uma árvore-mapa
+Comment[pt_BR]=Vê seu sistema de arquivos como um Mapa em Árvore
+Comment[ro]=Afişează sistemul de fişiere ca pe o hartă arborescentă
+Comment[ru]=Просмотр дерева папок
+Comment[sk]=Zobrazenie vášho systému súborov ako stromová mapa
+Comment[sl]=Prikaz datotečnega sistema kot drevesa
+Comment[sr]=Прикажите ваш систем фајлова као стаблолику мапу
+Comment[sr@Latn]=Prikažite vaš sistem fajlova kao stabloliku mapu
+Comment[sv]=Visa filsystemet som en trädkarta
+Comment[ta]=உங்கள் கோப்பு அமைப்பை மர வரைபடமாக பார்வையிடு
+Comment[tg]=Аз назар гузаронидани дарахти каталогҳо
+Comment[tr]=Dosya sistemini ağaç şeklinde görüntüle
+Comment[uk]=Перегляд вашої файлової системи як дерева
+Comment[vi]=Xem hệ thống tập tin dạng Sơ Đồ Cây
+Comment[zh_CN]=以树形图查看您的文件系统
+Comment[zh_TW]=將您的作業系統以樹狀圖檢視
+X-DCOP-ServiceType=Multi
+Categories=Qt;KDE;Utility;
+X-KDE-ParentApp=konqueror
diff --git a/konq-plugins/fsview/fsview.h b/konq-plugins/fsview/fsview.h
new file mode 100644
index 0000000..17d926f
--- /dev/null
+++ b/konq-plugins/fsview/fsview.h
@@ -0,0 +1,136 @@
+/* This file is part of FSView.
+ Copyright (C) 2002, 2003 Josef Weidendorfer <Josef.Weidendorfer@gmx.de>
+
+ KCachegrind 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, version 2.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+/*
+ * FSView specialisaton of TreeMap classes.
+ */
+
+#ifndef FSVIEW_H
+#define FSVIEW_H
+
+#include <qmap.h>
+#include <qptrlist.h>
+#include <qfileinfo.h>
+#include <qstring.h>
+
+#include <kmimetype.h>
+
+#include "treemap.h"
+#include "inode.h"
+#include "scan.h"
+
+class KConfig;
+
+/* Cached Metric info config */
+class MetricEntry
+{
+ public:
+ MetricEntry()
+ { size = 0.0; fileCount = 0; dirCount = 0; }
+ MetricEntry(double s, unsigned int f, unsigned int d)
+ { size = s; fileCount = f; dirCount = d; }
+
+ double size;
+ unsigned int fileCount, dirCount;
+};
+
+/**
+ * The root object for the treemap.
+ *
+ * Does context menu handling and
+ * asynchronous file size update
+ */
+class FSView : public TreeMapWidget, public ScanListener
+{
+ Q_OBJECT
+
+public:
+ enum ColorMode { None = 0, Depth, Name, Owner, Group, Mime };
+
+ FSView(Inode*, QWidget* parent=0, const char* name=0);
+ ~FSView();
+
+ KConfig* config() { return _config; }
+
+ void setPath(QString);
+ QString path() { return _path; }
+ int pathDepth() { return _pathDepth; }
+
+ void setColorMode(FSView::ColorMode cm);
+ FSView::ColorMode colorMode() const { return _colorMode; }
+ // returns true if string was recognized
+ bool setColorMode(QString);
+ QString colorModeString() const;
+
+ void requestUpdate(Inode*);
+
+ /* Implementation of listener interface of ScanManager.
+ * Used to calculate progress info */
+ void scanFinished(ScanDir*);
+
+ void stop();
+
+ static bool getDirMetric(const QString&, double&, unsigned int&, unsigned int&);
+ static void setDirMetric(const QString&, double, unsigned int, unsigned int);
+ void saveMetric(KConfigGroup*);
+ void saveFSOptions();
+
+ // for color mode
+ void addColorItems(QPopupMenu*, int);
+
+ KURL::List selectedUrls();
+
+public slots:
+ void selected(TreeMapItem*);
+ void contextMenu(TreeMapItem*, const QPoint &);
+ void quit();
+ void doUpdate();
+ void doRedraw();
+ void colorActivated(int);
+
+ signals:
+ void started();
+ void progress(int percent, int dirs, const QString& lastDir);
+ void completed(int dirs);
+
+ private:
+ KConfig* _config;
+ ScanManager _sm;
+
+ // when a contextMenu is shown, we don't allow async. refreshs
+ bool _allowRefresh;
+ // a cache for directory sizes with long lasting updates
+ static QMap<QString, MetricEntry> _dirMetric;
+
+ // current root path
+ int _pathDepth;
+ QString _path;
+
+ // for progress info
+ int _progressPhase;
+ int _chunkData1, _chunkData2, _chunkData3;
+ int _chunkSize1, _chunkSize2, _chunkSize3;
+ int _progress, _progressSize, _dirsFinished;
+ ScanDir* _lastDir;
+
+ ColorMode _colorMode;
+ int _colorID;
+};
+
+#endif // FSVIEW_H
+
diff --git a/konq-plugins/fsview/fsview_part.cpp b/konq-plugins/fsview/fsview_part.cpp
new file mode 100644
index 0000000..745cb63
--- /dev/null
+++ b/konq-plugins/fsview/fsview_part.cpp
@@ -0,0 +1,413 @@
+/* This file is part of FSView.
+ Copyright (C) 2002, 2003 Josef Weidendorfer <Josef.Weidendorfer@gmx.de>
+
+ KCachegrind 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, version 2.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+/*
+ * The KPart embedding the FSView widget
+ */
+
+#include <qclipboard.h>
+#include <qtimer.h>
+#include <qwhatsthis.h>
+
+#include <kinstance.h>
+#include <kfiledialog.h>
+#include <kfileitem.h>
+#include <kparts/genericfactory.h>
+#include <kapplication.h>
+#include <kaboutdata.h>
+#include <klocale.h>
+#include <kaction.h>
+#include <kpopupmenu.h>
+#include <kglobalsettings.h>
+#include <kprotocolinfo.h>
+#include <kio/job.h>
+#include <kmessagebox.h>
+
+// from kdebase/libkonq...
+#include <konq_operations.h>
+#include <konq_drag.h>
+
+#include "fsview_part.h"
+
+
+
+
+typedef KParts::GenericFactory<FSViewPart> FSViewPartFactory;
+K_EXPORT_COMPONENT_FACTORY( libfsviewpart, FSViewPartFactory )
+
+
+// FSJob, for progress
+
+FSJob::FSJob(FSView* v)
+ : KIO::Job(false)
+{
+ _view = v;
+ QObject::connect(v, SIGNAL(progress(int,int,const QString&)),
+ this, SLOT(progressSlot(int,int,const QString&)));
+}
+
+void FSJob::kill(bool quietly)
+{
+ _view->stop();
+
+ Job::kill(quietly);
+}
+
+void FSJob::progressSlot(int percent, int dirs, const QString& cDir)
+{
+ if (percent<100) {
+ emitPercent(percent, 100);
+ slotInfoMessage(this, i18n("Read 1 folder, in %1",
+ "Read %n folders, in %1",
+ dirs ).arg(cDir));
+ }
+ else
+ slotInfoMessage(this, i18n("1 folder", "%n folders", dirs));
+}
+
+
+// FSViewPart
+
+KAboutData* FSViewPart::createAboutData()
+{
+ KAboutData* aboutData;
+ aboutData = new KAboutData("fsview", I18N_NOOP("FSView"), "0.1.1",
+ I18N_NOOP("Filesystem Utilization Viewer"),
+ KAboutData::License_GPL,
+ I18N_NOOP("(c) 2003-2005, Josef Weidendorfer"));
+ return aboutData;
+}
+
+FSViewPart::FSViewPart(QWidget *parentWidget, const char *widgetName,
+ QObject *parent, const char *name,
+ const QStringList& /* args */)
+ : KParts::ReadOnlyPart(parent, name)
+{
+ // we need an instance
+ setInstance( FSViewPartFactory::instance() );
+
+ _view = new FSView(new Inode(), parentWidget, widgetName);
+ QWhatsThis::add(_view, i18n("<p>This is the FSView plugin, a graphical "
+ "browsing mode showing filesystem utilization "
+ "by using a tree map visualization.</p>"
+ "<p>Note that in this mode, automatic updating "
+ "when filesystem changes are made "
+ "is intentionally <b>not</b> done.</p>"
+ "<p>For details on usage and options available, "
+ "see the online help under "
+ "menu 'Help/FSView Manual'.</p>"));
+
+ _view->show();
+ setWidget(_view);
+
+ _ext = new FSViewBrowserExtension(this);
+ _job = 0;
+
+ _areaMenu = new KActionMenu (i18n("Stop at Area"),
+ actionCollection(), "treemap_areadir");
+ _depthMenu = new KActionMenu (i18n("Stop at Depth"),
+ actionCollection(), "treemap_depthdir");
+ _visMenu = new KActionMenu (i18n("Visualization"),
+ actionCollection(), "treemap_visdir");
+ _colorMenu = new KActionMenu (i18n("Color Mode"),
+ actionCollection(), "treemap_colordir");
+
+ KAction* action;
+ action = new KAction( i18n( "&FSView Manual" ), "fsview",
+ KShortcut(), this, SLOT(showHelp()),
+ actionCollection(), "help_fsview" );
+ action->setToolTip(i18n("Show FSView manual"));
+ action->setWhatsThis(i18n("Opens the help browser with the "
+ "FSView documentation"));
+
+ QObject::connect (_visMenu->popupMenu(), SIGNAL (aboutToShow()),
+ SLOT (slotShowVisMenu()));
+ QObject::connect (_areaMenu->popupMenu(), SIGNAL (aboutToShow()),
+ SLOT (slotShowAreaMenu()));
+ QObject::connect (_depthMenu->popupMenu(), SIGNAL (aboutToShow()),
+ SLOT (slotShowDepthMenu()));
+ QObject::connect (_colorMenu->popupMenu(), SIGNAL (aboutToShow()),
+ SLOT (slotShowColorMenu()));
+
+ slotSettingsChanged(KApplication::SETTINGS_MOUSE);
+ if (kapp)
+ connect( kapp, SIGNAL( settingsChanged(int) ),
+ SLOT( slotSettingsChanged(int) ) );
+
+ QObject::connect(_view,SIGNAL(returnPressed(TreeMapItem*)),
+ _ext,SLOT(selected(TreeMapItem*)));
+ QObject::connect(_view,SIGNAL(selectionChanged()),
+ _ext,SLOT(updateActions()));
+ QObject::connect(_view,
+ SIGNAL(contextMenuRequested(TreeMapItem*,const QPoint&)),
+ _ext,
+ SLOT(contextMenu(TreeMapItem*, const QPoint&)));
+
+ QObject::connect(_view, SIGNAL(started()), this, SLOT(startedSlot()));
+ QObject::connect(_view, SIGNAL(completed(int)),
+ this, SLOT(completedSlot(int)));
+
+ QTimer::singleShot(1, this, SLOT(showInfo()));
+
+ setXMLFile( "fsview_part.rc" );
+}
+
+
+FSViewPart::~FSViewPart()
+{
+ kdDebug(90100) << "FSViewPart Destructor" << endl;
+
+ delete _job;
+ _view->saveFSOptions();
+}
+
+void FSViewPart::slotSettingsChanged(int category)
+{
+ if (category != KApplication::SETTINGS_MOUSE) return;
+
+ QObject::disconnect(_view,SIGNAL(clicked(TreeMapItem*)),
+ _ext,SLOT(selected(TreeMapItem*)));
+ QObject::disconnect(_view,SIGNAL(doubleClicked(TreeMapItem*)),
+ _ext,SLOT(selected(TreeMapItem*)));
+
+ if (KGlobalSettings::singleClick())
+ QObject::connect(_view,SIGNAL(clicked(TreeMapItem*)),
+ _ext,SLOT(selected(TreeMapItem*)));
+ else
+ QObject::connect(_view,SIGNAL(doubleClicked(TreeMapItem*)),
+ _ext,SLOT(selected(TreeMapItem*)));
+}
+
+void FSViewPart::showInfo()
+{
+ QString info;
+ info = i18n("FSView intentionally does not support automatic updates "
+ "when changes are made to files or directories, "
+ "currently visible in FSView, from the outside.\n"
+ "For details, see the 'Help/FSView Manual'.");
+
+ KMessageBox::information( _view, info, QString::null, "ShowFSViewInfo");
+}
+
+void FSViewPart::showHelp()
+{
+ KApplication::startServiceByDesktopName("khelpcenter",
+ QString("help:/konq-plugins/fsview/index.html"));
+}
+
+void FSViewPart::startedSlot()
+{
+ _job = new FSJob(_view);
+ emit started(_job);
+}
+
+void FSViewPart::completedSlot(int dirs)
+{
+ if (_job) {
+ _job->progressSlot(100, dirs, QString::null);
+ delete _job;
+ _job = 0;
+ }
+
+ KConfigGroup cconfig(_view->config(), QCString("MetricCache"));
+ _view->saveMetric(&cconfig);
+
+ emit completed();
+}
+
+void FSViewPart::slotShowVisMenu()
+{
+ _visMenu->popupMenu()->clear();
+ _view->addVisualizationItems(_visMenu->popupMenu(), 1301);
+}
+
+void FSViewPart::slotShowAreaMenu()
+{
+ _areaMenu->popupMenu()->clear();
+ _view->addAreaStopItems(_areaMenu->popupMenu(), 1001, 0);
+}
+
+void FSViewPart::slotShowDepthMenu()
+{
+ _depthMenu->popupMenu()->clear();
+ _view->addDepthStopItems(_depthMenu->popupMenu(), 1501, 0);
+}
+
+void FSViewPart::slotShowColorMenu()
+{
+ _colorMenu->popupMenu()->clear();
+ _view->addColorItems(_colorMenu->popupMenu(), 1401);
+}
+
+bool FSViewPart::openFile() // never called since openURL is reimplemented
+{
+ kdDebug(90100) << "FSViewPart::openFile " << m_file << endl;
+ _view->setPath(m_file);
+
+ return true;
+}
+
+bool FSViewPart::openURL(const KURL &url)
+{
+ kdDebug(90100) << "FSViewPart::openURL " << url.path() << endl;
+
+ if (!url.isValid()) return false;
+ if (!url.isLocalFile()) return false;
+
+ m_url = url;
+ emit setWindowCaption( m_url.prettyURL() );
+
+ _view->setPath(url.path());
+
+ return true;
+}
+
+bool FSViewPart::closeURL()
+{
+ kdDebug(90100) << "FSViewPart::closeURL " << endl;
+
+ _view->stop();
+
+ return true;
+}
+
+// FSViewBrowserExtension
+
+FSViewBrowserExtension::FSViewBrowserExtension(FSViewPart* viewPart,
+ const char *name)
+ :KParts::BrowserExtension(viewPart, name)
+{
+ _view = viewPart->view();
+}
+
+FSViewBrowserExtension::~FSViewBrowserExtension()
+{}
+
+void FSViewBrowserExtension::updateActions()
+{
+ TreeMapItemList s = _view->selection();
+ TreeMapItem* i;
+ int canDel = 0, canCopy = 0;
+ KURL::List urls;
+
+ for(i=s.first();i;i=s.next()) {
+ KURL u;
+ u.setPath( ((Inode*)i)->path() );
+ urls.append(u);
+ canCopy++;
+ if ( KProtocolInfo::supportsDeleting( u ) ) canDel++;
+ }
+ emit enableAction( "copy", canCopy > 0 );
+ emit enableAction( "cut", canDel > 0 );
+ emit enableAction( "trash", canDel > 0);
+ emit enableAction( "del", canDel > 0 );
+ emit enableAction( "editMimeType", ( s.count() == 1 ) );
+
+ emit selectionInfo(urls);
+
+ kdDebug(90100) << "FSViewPart::updateActions, deletable " << canDel << endl;
+}
+
+
+void FSViewBrowserExtension::del()
+{
+ KonqOperations::del(_view, KonqOperations::DEL, _view->selectedUrls());
+ // How to get notified of end of delete operation?
+ // - search for the KonqOperations child of _view (name "KonqOperations")
+ // - connect to destroyed signal
+ KonqOperations* o = (KonqOperations*) _view->child("KonqOperations");
+ if (o) connect(o, SIGNAL(destroyed()), SLOT(refresh()));
+}
+
+void FSViewBrowserExtension::trash()
+{
+ KonqOperations::del(_view, KonqOperations::TRASH, _view->selectedUrls());
+ KonqOperations* o = (KonqOperations*) _view->child("KonqOperations");
+ if (o) connect(o, SIGNAL(destroyed()), SLOT(refresh()));
+}
+
+void FSViewBrowserExtension::copySelection( bool move )
+{
+ KonqDrag *urlData = KonqDrag::newDrag( _view->selectedUrls(), move );
+ QApplication::clipboard()->setData( urlData );
+}
+
+void FSViewBrowserExtension::editMimeType()
+{
+ Inode* i = (Inode*) _view->selection().first();
+ if (i)
+ KonqOperations::editMimeType( i->mimeType()->name() );
+}
+
+
+// refresh treemap at end of KIO jobs
+void FSViewBrowserExtension::refresh()
+{
+ // only need to refresh common ancestor for all selected items
+ TreeMapItemList s = _view->selection();
+ TreeMapItem *i, *commonParent = s.first();
+ if (!commonParent) return;
+ while( (i=s.next()) )
+ commonParent = commonParent->commonParent(i);
+
+ /* if commonParent is a file, update parent directory */
+ if ( !((Inode*)commonParent)->isDir() ) {
+ commonParent = commonParent->parent();
+ if (!commonParent) return;
+ }
+
+ kdDebug(90100) << "FSViewPart::refreshing "
+ << ((Inode*)commonParent)->path() << endl;
+
+ _view->requestUpdate( (Inode*)commonParent );
+}
+
+void FSViewBrowserExtension::selected(TreeMapItem* i)
+{
+ if (!i) return;
+
+ KURL url;
+ url.setPath( ((Inode*)i)->path() );
+ emit openURLRequest(url);
+}
+
+void FSViewBrowserExtension::contextMenu(TreeMapItem* /*item*/,const QPoint& p)
+{
+ TreeMapItemList s = _view->selection();
+ TreeMapItem* i;
+ KFileItemList items;
+ items.setAutoDelete(true);
+
+ for(i=s.first();i;i=s.next()) {
+ KURL u;
+ u.setPath( ((Inode*)i)->path() );
+ QString mimetype = ((Inode*)i)->mimeType()->name();
+ const QFileInfo& info = ((Inode*)i)->fileInfo();
+ mode_t mode =
+ info.isFile() ? S_IFREG :
+ info.isDir() ? S_IFDIR :
+ info.isSymLink() ? S_IFLNK : (mode_t)-1;
+ items.append(new KFileItem(u, mimetype, mode));
+ }
+
+ if (items.count()>0)
+ emit popupMenu(_view->mapToGlobal(p), items);
+}
+
+
+#include "fsview_part.moc"
diff --git a/konq-plugins/fsview/fsview_part.desktop b/konq-plugins/fsview/fsview_part.desktop
new file mode 100644
index 0000000..d282178
--- /dev/null
+++ b/konq-plugins/fsview/fsview_part.desktop
@@ -0,0 +1,58 @@
+[Desktop Entry]
+Name=File Size View
+Name[bg]=Преглед на файловия размер
+Name[bs]=Pogled veličine datoteka
+Name[ca]=Vista de mides de fitxers
+Name[cs]=Prohlížeč velikostí souborů
+Name[da]=Fremvisning af filstørrelse
+Name[de]=Dateigrößen-Ansicht
+Name[el]=Προβολή Μεγέθους αρχείων
+Name[eo]=Dosiergrandeca rigardo
+Name[es]=Vista del tamaño del archivo
+Name[et]=Failisuuruse vaade
+Name[eu]=Fitxategien neurriaren ikuspegia
+Name[fa]=نمای اندازۀ پرونده
+Name[fr]=Afficheur de taille de fichiers
+Name[gl]=Vista de Tamaño de Ficheiros
+Name[he]=מציג גדלי קבצים
+Name[hi]=फ़ाइल आकार दृश्य
+Name[hr]=Prikaz veličine datoteke
+Name[hu]=Fájlméret-nézet
+Name[is]=Skoða skráarstærð
+Name[it]=Visualizza dimensione file
+Name[ja]=ファイルサイズ表示
+Name[ka]=ფაილის ზომის ხილვა
+Name[kk]=Файл көлемдердің қарау
+Name[km]=មើល​ទំហំ​ឯកសារ​
+Name[lt]=Bylų dydžio vaizdas
+Name[mk]=Преглед на големина на датотеки
+Name[ms]=Pandangan Saiz Fail
+Name[nb]=Filstørrelsevisning
+Name[nds]=Dateigrött-Ansicht
+Name[ne]=फाइल साइज दृश्य
+Name[nn]=Filstorleikvisar
+Name[pa]=ਫਾਇਲ ਅਕਾਰ ਵੇਖੋ
+Name[pl]=Widok rozmiaru plików
+Name[pt]=Vista de Tamanho de Ficheiros
+Name[pt_BR]=Visão do Tam. do arquivo
+Name[ru]=Размер файла
+Name[sk]=Veľkosť súborov
+Name[sl]=Pregledovalnik velikosti datotek
+Name[sr]=Приказ величине фајла
+Name[sr@Latn]=Prikaz veličine fajla
+Name[sv]=Filstorleksvisning
+Name[ta]=கோப்பு அளவு காட்சி
+Name[tg]=Андозаи файл
+Name[tr]=Dosya Boyut Görüntüleme
+Name[uk]=Перегляд розміру файлів
+Name[uz]=Fayl hajmini koʻrish
+Name[uz@cyrillic]=Файл ҳажмини кўриш
+Name[vi]=Xem cỡ tập tin
+Name[zh_CN]=文件大小查看
+Name[zh_TW]=檔案大小檢視器
+MimeType=inode/directory
+ServiceTypes=KParts/ReadOnlyPart
+X-KDE-Library=libfsviewpart
+Type=Service
+Icon=fsview
+DocPath=konq-plugins/fsview/index.html
diff --git a/konq-plugins/fsview/fsview_part.h b/konq-plugins/fsview/fsview_part.h
new file mode 100644
index 0000000..4f2c734
--- /dev/null
+++ b/konq-plugins/fsview/fsview_part.h
@@ -0,0 +1,122 @@
+/* This file is part of FSView.
+ Copyright (C) 2002, 2003 Josef Weidendorfer <Josef.Weidendorfer@gmx.de>
+
+ KCachegrind 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, version 2.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+/*
+ * The KPart embedding the FSView widget
+ */
+
+#ifndef FSVIEW_PART_H
+#define FSVIEW_PART_H
+
+#include <kparts/part.h>
+#include <kparts/browserextension.h>
+#include <kio/jobclasses.h>
+
+#include "fsview.h"
+
+class KAboutData;
+class KActionMenu;
+
+class FSViewPart;
+
+class FSViewBrowserExtension : public KParts::BrowserExtension
+{
+ Q_OBJECT
+
+public:
+ FSViewBrowserExtension(FSViewPart *viewPart, const char *name=0L);
+ ~FSViewBrowserExtension();
+
+protected slots:
+ void selected(TreeMapItem*);
+ void contextMenu(TreeMapItem*,const QPoint&);
+
+ void updateActions();
+ void refresh();
+
+ void copy() { copySelection( false ); }
+ void cut() { copySelection( true ); }
+ void trash();
+ void del();
+ void editMimeType();
+
+private:
+ void copySelection( bool move );
+
+ FSView* _view;
+};
+
+class FSJob: public KIO::Job
+{
+ Q_OBJECT
+
+public:
+ FSJob(FSView*);
+
+ virtual void kill( bool quietly = true );
+
+public slots:
+ void progressSlot(int percent, int dirs, const QString& lastDir);
+
+private:
+ FSView* _view;
+};
+
+
+class FSViewPart : public KParts::ReadOnlyPart
+{
+ Q_OBJECT
+ Q_PROPERTY( bool supportsUndo READ supportsUndo )
+public:
+ FSViewPart(QWidget *parentWidget, const char *widgetName,
+ QObject *parent, const char *name, const QStringList &args);
+
+ virtual ~FSViewPart();
+
+ bool supportsUndo() const { return false; }
+
+ static KAboutData* createAboutData();
+ FSView* view() const { return _view; }
+
+public slots:
+ void showInfo();
+ void showHelp();
+ void startedSlot();
+ void completedSlot(int dirs);
+ void slotShowVisMenu();
+ void slotShowAreaMenu();
+ void slotShowDepthMenu();
+ void slotShowColorMenu();
+ void slotSettingsChanged(int);
+
+protected:
+ /**
+ * This must be implemented by each part
+ */
+ virtual bool openFile();
+ virtual bool openURL(const KURL &url);
+ virtual bool closeURL();
+
+private:
+ FSView* _view;
+ FSJob* _job;
+ FSViewBrowserExtension* _ext;
+ KActionMenu *_visMenu, *_areaMenu, *_depthMenu, *_colorMenu;
+};
+
+#endif // FSVIEW_PART_H
diff --git a/konq-plugins/fsview/fsview_part.rc b/konq-plugins/fsview/fsview_part.rc
new file mode 100644
index 0000000..c8df3df
--- /dev/null
+++ b/konq-plugins/fsview/fsview_part.rc
@@ -0,0 +1,15 @@
+<!DOCTYPE kpartplugin>
+<kpartplugin name="FSViewPart" library="libfsviewpart" version = "1">
+<MenuBar>
+ <Menu name="view"><Text>&amp;View</Text>
+ <Separator/>
+ <Action name="treemap_visdir"/>
+ <Action name="treemap_colordir"/>
+ <Action name="treemap_areadir"/>
+ <Action name="treemap_depthdir"/>
+ </Menu>
+ <Menu name="help"><text>&amp;Help</text>
+ <Action name="help_fsview"/>
+ </Menu>
+</MenuBar>
+</kpartplugin>
diff --git a/konq-plugins/fsview/hi22-app-fsview.png b/konq-plugins/fsview/hi22-app-fsview.png
new file mode 100644
index 0000000..efdcdcb
--- /dev/null
+++ b/konq-plugins/fsview/hi22-app-fsview.png
Binary files differ
diff --git a/konq-plugins/fsview/hi32-app-fsview.png b/konq-plugins/fsview/hi32-app-fsview.png
new file mode 100644
index 0000000..6e22f8b
--- /dev/null
+++ b/konq-plugins/fsview/hi32-app-fsview.png
Binary files differ
diff --git a/konq-plugins/fsview/inode.cpp b/konq-plugins/fsview/inode.cpp
new file mode 100644
index 0000000..0411d3a
--- /dev/null
+++ b/konq-plugins/fsview/inode.cpp
@@ -0,0 +1,385 @@
+/* This file is part of FSView.
+ Copyright (C) 2002, 2003 Josef Weidendorfer <Josef.Weidendorfer@gmx.de>
+
+ KCachegrind 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, version 2.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+/*
+ * FSView specialisaton of TreeMapItem class.
+ */
+
+
+#include <kurl.h>
+#include <kmimetype.h>
+#include <kdebug.h>
+#include <kglobal.h>
+#include <klocale.h>
+
+#include "inode.h"
+#include "scan.h"
+#include "fsview.h"
+
+// Inode
+
+Inode::Inode()
+{
+ _dirPeer = 0;
+ _filePeer = 0;
+ init("");
+}
+
+Inode::Inode(ScanDir* d, Inode* parent)
+ : TreeMapItem(parent)
+{
+ QString absPath;
+ if (parent) {
+ absPath = parent->path();
+ if (!absPath.endsWith("/")) absPath += "/";
+ }
+ absPath += d->name();
+
+ _dirPeer = d;
+ _filePeer = 0;
+
+ init(absPath);
+}
+
+Inode::Inode(ScanFile* f, Inode* parent)
+ : TreeMapItem(parent)
+{
+ QString absPath;
+ if (parent)
+ absPath = parent->path() + "/";
+ absPath += f->name();
+
+ _dirPeer = 0;
+ _filePeer = f;
+
+ init(absPath);
+}
+
+Inode::~Inode()
+{
+ if (0) kdDebug(90100) << "~Inode [" << path()
+ << "]" << endl;
+
+ /* reset Listener of old Peer */
+ if (_dirPeer)
+ _dirPeer->setListener(0);
+ if (_filePeer)
+ _filePeer->setListener(0);
+}
+
+void Inode::setPeer(ScanDir* d)
+{
+ /* reset Listener of old Peer */
+ if (_dirPeer)
+ _dirPeer->setListener(0);
+ if (_filePeer)
+ _filePeer->setListener(0);
+
+ _dirPeer = d;
+ _filePeer = 0;
+ init(d->name());
+}
+
+QString Inode::path() const
+{
+ return _info.absFilePath();
+}
+
+void Inode::init(const QString& path)
+{
+ if (0) kdDebug(90100) << "Inode::init [" << path
+ << "]" << endl;
+
+ _info = QFileInfo(path);
+
+ if (!FSView::getDirMetric(path, _sizeEstimation,
+ _fileCountEstimation,
+ _dirCountEstimation)) {
+ _sizeEstimation = 0.0;
+ _fileCountEstimation = 0;
+ _dirCountEstimation = 0;
+ }
+
+ _mimeSet = false;
+ _mimePixmapSet = false;
+ _resortNeeded = false;
+
+ clear();
+
+ /* we want to get notifications about dir changes */
+ if (_dirPeer)
+ _dirPeer->setListener(this);
+ if (_filePeer)
+ _filePeer->setListener(this);
+
+ if (_dirPeer && _dirPeer->scanFinished())
+ scanFinished(_dirPeer);
+}
+
+/* ScanListener interface */
+void Inode::sizeChanged(ScanDir* d)
+{
+ if (0) kdDebug(90100) << "Inode::sizeChanged [" << path() << "] in "
+ << d->name() << ": size " << d->size() << endl;
+
+ _resortNeeded = true;
+}
+
+void Inode::scanFinished(ScanDir* d)
+{
+ if (0) kdDebug(90100) << "Inode::scanFinished [" << path() << "] in "
+ << d->name() << ": size " << d->size() << endl;
+
+ _resortNeeded = true;
+
+ /* no estimation any longer */
+ _sizeEstimation = 0.0;
+ _fileCountEstimation = 0;
+ _dirCountEstimation = 0;
+
+ // cache metrics if "important" (for "/usr" is dd==3)
+ int dd = ((FSView*)widget())->pathDepth() + depth();
+ int files = d->fileCount();
+ int dirs = d->dirCount();
+
+ if ((files < 500) && (dirs < 50)) {
+ if (dd>4 && (files < 50) && (dirs < 5)) return;
+ }
+
+ FSView::setDirMetric(path(), d->size(), files, dirs);
+}
+
+void Inode::destroyed(ScanDir* d)
+{
+ if (_dirPeer == d) _dirPeer = 0;
+
+ // remove children
+ clear();
+}
+
+void Inode::destroyed(ScanFile* f)
+{
+ if (_filePeer == f) _filePeer = 0;
+}
+
+
+
+TreeMapItemList* Inode::children()
+{
+ if (!_dirPeer) return 0;
+
+ if (!_children) {
+ if (!_dirPeer->scanStarted()) return 0;
+
+ _children = new TreeMapItemList;
+ _children->setAutoDelete(true);
+
+ setSorting(-1);
+
+ ScanFileVector& files = _dirPeer->files();
+ if (files.count()>0) {
+ ScanFileVector::iterator it;
+ for( it = files.begin(); it != files.end(); ++it )
+ new Inode( &(*it), this);
+ }
+
+ ScanDirVector& dirs = _dirPeer->dirs();
+ if (dirs.count()>0) {
+ ScanDirVector::iterator it;
+ for( it = dirs.begin(); it != dirs.end(); ++it ) {
+ new Inode( &(*it), this);
+ }
+ }
+
+ setSorting(-2);
+ _resortNeeded = false;
+ }
+
+ if (_resortNeeded) {
+ resort();
+ _resortNeeded = false;
+ }
+
+ return _children;
+}
+
+
+
+double Inode::size() const
+{
+ // sizes of files are always correct
+ if (_filePeer) return _filePeer->size();
+ if (!_dirPeer) return 0;
+
+ double size = _dirPeer->size();
+ return (_sizeEstimation > size) ? _sizeEstimation : size;
+}
+
+double Inode::value() const
+{
+ return size();
+}
+
+unsigned int Inode::fileCount() const
+{
+ unsigned int fileCount = 1;
+
+ if (_dirPeer) fileCount = _dirPeer->fileCount();
+
+ if (_fileCountEstimation > fileCount)
+ fileCount = _fileCountEstimation;
+
+ return fileCount;
+}
+
+unsigned int Inode::dirCount() const
+{
+ unsigned int dirCount = 0;
+
+ if (_dirPeer) dirCount = _dirPeer->dirCount();
+
+ if (_dirCountEstimation > dirCount)
+ dirCount = _dirCountEstimation;
+
+ return dirCount;
+}
+
+
+QColor Inode::backColor() const
+{
+ QString n;
+ int id = 0;
+
+ switch( ((FSView*)widget())->colorMode() ) {
+ case FSView::Depth:
+ {
+ int d = ((FSView*)widget())->pathDepth() + depth();
+ return QColor((100*d)%360, 192,128, QColor::Hsv);
+ }
+
+ case FSView::Name: n = text(0); break;
+ case FSView::Owner: id = _info.ownerId(); break;
+ case FSView::Group: id = _info.groupId(); break;
+ case FSView::Mime: n = text(7); break;
+
+ default:
+ break;
+ }
+
+ if (id>0) n = QString::number(id);
+
+ if (n.isEmpty())
+ return widget()->colorGroup().button();
+
+ const char* str = n.ascii();
+ int h = 0, s = 100;
+ while (*str) {
+ h = (h * 37 + s* (unsigned)*str) % 256;
+ s = (s * 17 + h* (unsigned)*str) % 192;
+ str++;
+ }
+ return QColor(h, 64+s, 192, QColor::Hsv);
+}
+
+KMimeType::Ptr Inode::mimeType() const
+{
+ if (!_mimeSet) {
+ KURL u;
+ u.setPath(path());
+ _mimeType = KMimeType::findByURL( u, 0, true, false );
+
+ _mimeSet = true;
+ }
+ return _mimeType;
+}
+
+QString Inode::text(int i) const
+{
+ if (i==0) {
+ QString name;
+ if (_dirPeer) {
+ name = _dirPeer->name();
+ if (!name.endsWith("/")) name += "/";
+ }
+ else if (_filePeer) name = _filePeer->name();
+
+ return name;
+ }
+ if (i==1) {
+ QString text;
+ double s = size();
+
+ if (s < 1000)
+ text = QString("%1 B").arg((int)(s+.5));
+ else if (s < 10 * 1024)
+ text = QString("%1 kB").arg(KGlobal::locale()->formatNumber(s/1024+.005,2));
+ else if (s < 100 * 1024)
+ text = QString("%1 kB").arg(KGlobal::locale()->formatNumber(s/1024+.05,1));
+ else if (s < 1000 * 1024)
+ text = QString("%1 kB").arg((int)(s/1024+.5));
+ else if (s < 10 * 1024 * 1024)
+ text = QString("%1 MB").arg(KGlobal::locale()->formatNumber(s/1024/1024+.005,2));
+ else if (s < 100 * 1024 * 1024)
+ text = QString("%1 MB").arg(KGlobal::locale()->formatNumber(s/1024/1024+.05,1));
+ else if (s < 1000 * 1024 * 1024)
+ text = QString("%1 MB").arg((int)(s/1024/1024+.5));
+ else
+ text = QString("%1 GB").arg(KGlobal::locale()->formatNumber(s/1024/1024/1024+.005,2));
+
+ if (_sizeEstimation>0) text += "+";
+ return text;
+ }
+
+ if ((i==2) || (i==3)) {
+ /* file/dir count makes no sense for files */
+ if (_filePeer) return QString();
+
+ QString text;
+ unsigned int f = (i==2) ? fileCount() : dirCount();
+
+ if (f>0) {
+ while (f>1000) {
+ text = QString("%1 %2").arg(QString::number(f).right(3)).arg(text);
+ f /= 1000;
+ }
+ text = QString("%1 %2").arg(QString::number(f)).arg(text);
+ if (_fileCountEstimation>0) text += "+";
+ }
+ return text;
+ }
+
+ if (i==4) return _info.lastModified().toString();
+ if (i==5) return _info.owner();
+ if (i==6) return _info.group();
+ if (i==7) return mimeType()->comment();
+ return QString();
+}
+
+QPixmap Inode::pixmap(int i) const
+{
+ if (i!=0) return QPixmap();
+
+ if (!_mimePixmapSet) {
+ KURL u;
+ u.setPath(path());
+ _mimePixmap = mimeType()->pixmap(u, KIcon::Small);
+
+ _mimePixmapSet = true;
+ }
+ return _mimePixmap;
+}
diff --git a/konq-plugins/fsview/inode.h b/konq-plugins/fsview/inode.h
new file mode 100644
index 0000000..8289f6b
--- /dev/null
+++ b/konq-plugins/fsview/inode.h
@@ -0,0 +1,97 @@
+/* This file is part of FSView.
+ Copyright (C) 2002, 2003 Josef Weidendorfer <Josef.Weidendorfer@gmx.de>
+
+ KCachegrind 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, version 2.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+/*
+ * FSView specialisaton of TreeMapItem class.
+ */
+
+#ifndef INODE_H
+#define INODE_H
+
+#include <qmap.h>
+#include <qptrlist.h>
+#include <qfileinfo.h>
+#include <qstring.h>
+
+#include <kmimetype.h>
+
+#include "treemap.h"
+#include "scan.h"
+
+
+/**
+ * A specialized version of a TreeMapItem
+ * for representation of an Directory or File.
+ *
+ * These are dynamically created on drawing.
+ * The real breadth-first scanning of the filesystem
+ * uses ScanDir:scan.
+ */
+class Inode: public TreeMapItem, public ScanListener
+{
+public:
+ Inode();
+ Inode(ScanDir*, Inode*);
+ Inode(ScanFile*, Inode*);
+ ~Inode();
+ void init(const QString&);
+
+ void setPeer(ScanDir*);
+
+ TreeMapItemList* children();
+
+ double value() const;
+ double size() const;
+ unsigned int fileCount() const;
+ unsigned int dirCount() const;
+ QString path() const;
+ QString text(int i) const;
+ QPixmap pixmap(int i) const;
+ QColor backColor() const;
+ KMimeType::Ptr mimeType() const;
+
+ const QFileInfo& fileInfo() const { return _info; }
+ ScanDir* dirPeer() { return _dirPeer; }
+ ScanFile* filePeer() { return _filePeer; }
+ bool isDir() { return (_dirPeer != 0); }
+
+ void sizeChanged(ScanDir*);
+ void scanFinished(ScanDir*);
+ void destroyed(ScanDir*);
+ void destroyed(ScanFile*);
+
+private:
+ void setMetrics(double, unsigned int);
+
+ QFileInfo _info;
+ ScanDir* _dirPeer;
+ ScanFile* _filePeer;
+
+ double _sizeEstimation;
+ unsigned int _fileCountEstimation, _dirCountEstimation;
+
+ bool _resortNeeded;
+
+ // Cached values, calculated lazy.
+ // This means a change even in const methods, thus has to be "mutable"
+ mutable bool _mimeSet, _mimePixmapSet;
+ mutable KMimeType::Ptr _mimeType;
+ mutable QPixmap _mimePixmap;
+};
+
+#endif
diff --git a/konq-plugins/fsview/main.cpp b/konq-plugins/fsview/main.cpp
new file mode 100644
index 0000000..da18dcf
--- /dev/null
+++ b/konq-plugins/fsview/main.cpp
@@ -0,0 +1,56 @@
+/*****************************************************
+ * FSView, a simple TreeMap application
+ *
+ * (C) 2002, Josef Weidendorfer
+ */
+
+#include <kapplication.h>
+#include <kcmdlineargs.h>
+#include <kaboutdata.h>
+#include <klocale.h>
+#include <kglobal.h>
+#include <kconfig.h>
+
+#include "fsview.h"
+
+
+static KCmdLineOptions options[] =
+{
+ { "+[folder]", I18N_NOOP("View filesystem starting from this folder"), 0 },
+ KCmdLineLastOption // End of options.
+};
+
+int main(int argc, char* argv[])
+{
+ // KDE compliant startup
+ KAboutData aboutData("fsview", I18N_NOOP("FSView"), "0.1",
+ I18N_NOOP("Filesystem Viewer"),
+ KAboutData::License_GPL,
+ I18N_NOOP("(c) 2002, Josef Weidendorfer"));
+ KCmdLineArgs::init(argc, argv, &aboutData);
+ KCmdLineArgs::addCmdLineOptions(options);
+ KApplication a;
+
+ KConfigGroup gconfig(KGlobal::config(), QCString("General"));
+ QString path = gconfig.readPathEntry("Path", ".");
+
+ KCmdLineArgs *args = KCmdLineArgs::parsedArgs();
+ if (args->count()>0) path = args->arg(0);
+
+ // TreeMap Widget as toplevel window
+ FSView w(new Inode());
+
+ QObject::connect(&w,SIGNAL(clicked(TreeMapItem*)),
+ &w,SLOT(selected(TreeMapItem*)));
+ QObject::connect(&w,SIGNAL(returnPressed(TreeMapItem*)),
+ &w,SLOT(selected(TreeMapItem*)));
+ QObject::connect(&w,
+ SIGNAL(contextMenuRequested(TreeMapItem*,const QPoint&)),
+ &w,SLOT(contextMenu(TreeMapItem*, const QPoint&)));
+
+ w.setPath(path);
+ w.show();
+
+ a.connect( &a, SIGNAL( lastWindowClosed() ), &w, SLOT( quit() ) );
+ return a.exec();
+}
diff --git a/konq-plugins/fsview/scan.cpp b/konq-plugins/fsview/scan.cpp
new file mode 100644
index 0000000..ed691e3
--- /dev/null
+++ b/konq-plugins/fsview/scan.cpp
@@ -0,0 +1,362 @@
+/* This file is part of FSView.
+ Copyright (C) 2002, 2003 Josef Weidendorfer <Josef.Weidendorfer@gmx.de>
+
+ KCachegrind 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, version 2.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#include <qdir.h>
+#include <qstringlist.h>
+
+#include <kapplication.h>
+#include <kdebug.h>
+
+#include "scan.h"
+#include "inode.h"
+
+
+// ScanManager
+
+ScanManager::ScanManager()
+{
+ _topDir = 0;
+ _listener = 0;
+}
+
+ScanManager::ScanManager(const QString& path)
+{
+ _topDir = 0;
+ _listener = 0;
+ setTop(path);
+}
+
+ScanManager::~ScanManager()
+{
+ stopScan();
+ if (_topDir) delete _topDir;
+}
+
+void ScanManager::setListener(ScanListener* l)
+{
+ _listener = l;
+}
+
+ScanDir* ScanManager::setTop(const QString& path, int data)
+{
+ stopScan();
+ if (_topDir) {
+ delete _topDir;
+ _topDir = 0;
+ }
+ if (!path.isEmpty()) {
+ _topDir = new ScanDir(path, this, 0, data);
+ }
+ return _topDir;
+}
+
+bool ScanManager::scanRunning()
+{
+ if (!_topDir) return false;
+
+ return _topDir->scanRunning();
+}
+
+void ScanManager::startScan(ScanDir* from)
+{
+ if (!_topDir) return;
+ if (!from) from = _topDir;
+
+ if (scanRunning()) stopScan();
+
+ from->clear();
+ if (from->parent())
+ from->parent()->setupChildRescan();
+
+ _list.append(new ScanItem(from->path(),from));
+}
+
+void ScanManager::stopScan()
+{
+ if (!_topDir) return;
+
+ if (0) kdDebug(90100) << "ScanManager::stopScan, scanLength "
+ << _list.count() << endl;
+
+ ScanItem* si;
+ while( (si=_list.take(0))!=0 ) {
+ si->dir->finish();
+ delete si;
+ }
+}
+
+int ScanManager::scan(int data)
+{
+ ScanItem* si = _list.take(0);
+ if (!si) return false;
+
+ int newCount = si->dir->scan(si, _list, data);
+ delete si;
+
+ return newCount;
+}
+
+
+// ScanFile
+
+ScanFile::ScanFile()
+{
+ _size = 0;
+ _listener = 0;
+}
+
+ScanFile::ScanFile(const QString& n, KIO::fileoffset_t s)
+{
+ _name = n;
+ _size = s;
+ _listener = 0;
+}
+
+ScanFile::~ScanFile()
+{
+ if (_listener) _listener->destroyed(this);
+}
+
+// ScanDir
+
+ScanDir::ScanDir()
+{
+ _dirty = true;
+ _dirsFinished = -1; /* scan not started */
+
+ _parent = 0;
+ _manager = 0;
+ _listener = 0;
+ _data = 0;
+}
+
+ScanDir::ScanDir(const QString& n, ScanManager* m,
+ ScanDir* p, int data)
+ : _name(n)
+{
+ _dirty = true;
+ _dirsFinished = -1; /* scan not started */
+
+ _parent = p;
+ _manager = m;
+ _listener = 0;
+ _data = data;
+}
+
+ScanDir::~ScanDir()
+{
+ if (_listener) _listener->destroyed(this);
+}
+
+void ScanDir::setListener(ScanListener* l)
+{
+ _listener = l;
+}
+
+QString ScanDir::path()
+{
+ if (_parent) {
+ QString p = _parent->path();
+ if (!p.endsWith("/")) p += "/";
+ return p + _name;
+ }
+
+ return _name;
+}
+
+void ScanDir::clear()
+{
+ _dirty = true;
+ _dirsFinished = -1; /* scan not started */
+
+ _files.clear();
+ _dirs.clear();
+}
+
+void ScanDir::update()
+{
+ if (!_dirty) return;
+ _dirty = false;
+
+ _fileCount = 0;
+ _dirCount = 0;
+ _size = 0;
+
+ if (_dirsFinished == -1) return;
+
+ if (_files.count()>0) {
+ _fileCount += _files.count();
+ _size = _fileSize;
+ }
+ if (_dirs.count()>0) {
+ _dirCount += _dirs.count();
+ ScanDirVector::iterator it;
+ for( it = _dirs.begin(); it != _dirs.end(); ++it ) {
+ (*it).update();
+ _fileCount += (*it)._fileCount;
+ _dirCount += (*it)._dirCount;
+ _size += (*it)._size;
+ }
+ }
+}
+
+int ScanDir::scan(ScanItem* si, ScanItemList& list, int data)
+{
+ clear();
+ _dirsFinished = 0;
+ _fileSize = 0;
+ _dirty = true;
+
+ KURL u;
+ u.setPath(si->absPath);
+ if (!kapp->authorizeURLAction("list", KURL(), u)) {
+ if (_parent)
+ _parent->subScanFinished();
+
+ return 0;
+ }
+
+ QDir d(si->absPath);
+ QStringList fileList = d.entryList( QDir::Files |
+ QDir::Hidden | QDir::NoSymLinks );
+
+ if (fileList.count()>0) {
+ KDE_struct_stat buff;
+
+ _files.reserve(fileList.count());
+
+ QStringList::Iterator it;
+ for (it = fileList.begin(); it != fileList.end(); ++it ) {
+ KDE_lstat( QFile::encodeName(si->absPath + "/" + (*it)), &buff );
+ _files.append( ScanFile(*it, buff.st_size) );
+ _fileSize += buff.st_size;
+ }
+ }
+
+ QStringList dirList = d.entryList( QDir::Dirs |
+ QDir::Hidden | QDir::NoSymLinks );
+
+ if (dirList.count()>2) {
+ _dirs.reserve(dirList.count()-2);
+
+ QStringList::Iterator it;
+ for (it = dirList.begin(); it != dirList.end(); ++it ) {
+ if ( ((*it) == "..") || ((*it) == ".") ) continue;
+ _dirs.append( ScanDir(*it, _manager, this, data) );
+ list.append( new ScanItem( si->absPath + "/" + (*it),
+ &(_dirs.last()) ));
+ }
+ _dirCount += _dirs.count();
+ }
+
+ callScanStarted();
+ callSizeChanged();
+
+ if (_dirs.count() == 0) {
+ callScanFinished();
+
+ if (_parent)
+ _parent->subScanFinished();
+ }
+
+ return _dirs.count();
+}
+
+void ScanDir::subScanFinished()
+{
+ _dirsFinished++;
+ callSizeChanged();
+
+ if (0) kdDebug(90100) << "ScanDir::subScanFinished [" << path()
+ << "]: " << _dirsFinished << "/" << _dirs.count() << endl;
+
+
+
+ if (_dirsFinished < (int)_dirs.count()) return;
+
+ /* all subdirs read */
+ callScanFinished();
+
+ if (_parent)
+ _parent->subScanFinished();
+}
+
+void ScanDir::finish()
+{
+ if (scanRunning()) {
+ _dirsFinished = (int)_dirs.count();
+ callScanFinished();
+ }
+
+ if (_parent)
+ _parent->finish();
+}
+
+void ScanDir::setupChildRescan()
+{
+ if (_dirs.count() == 0) return;
+
+ _dirsFinished = 0;
+ ScanDirVector::iterator it;
+ for( it = _dirs.begin(); it != _dirs.end(); ++it )
+ if ((*it).scanFinished()) _dirsFinished++;
+
+ if (_parent &&
+ (_dirsFinished < (int)_dirs.count()) )
+ _parent->setupChildRescan();
+
+ callScanStarted();
+}
+
+void ScanDir::callScanStarted()
+{
+ if (0) kdDebug(90100) << "ScanDir:Started [" << path()
+ << "]: size " << size() << ", files " << fileCount() << endl;
+
+ ScanListener* mListener = _manager ? _manager->listener() : 0;
+
+ if (_listener) _listener->scanStarted(this);
+ if (mListener) mListener->scanStarted(this);
+}
+
+void ScanDir::callSizeChanged()
+{
+ if (0) kdDebug(90100) << ". [" << path()
+ << "]: size " << size() << ", files " << fileCount() << endl;
+
+ _dirty = true;
+
+ if (_parent) _parent->callSizeChanged();
+
+ ScanListener* mListener = _manager ? _manager->listener() : 0;
+
+ if (_listener) _listener->sizeChanged(this);
+ if (mListener) mListener->sizeChanged(this);
+}
+
+void ScanDir::callScanFinished()
+{
+ if (0) kdDebug(90100) << "ScanDir:Finished [" << path()
+ << "]: size " << size() << ", files " << fileCount() << endl;
+
+ ScanListener* mListener = _manager ? _manager->listener() : 0;
+
+ if (_listener) _listener->scanFinished(this);
+ if (mListener) mListener->scanFinished(this);
+}
+
diff --git a/konq-plugins/fsview/scan.h b/konq-plugins/fsview/scan.h
new file mode 100644
index 0000000..38b015c
--- /dev/null
+++ b/konq-plugins/fsview/scan.h
@@ -0,0 +1,230 @@
+/* This file is part of FSView.
+ Copyright (C) 2002, 2003 Josef Weidendorfer <Josef.Weidendorfer@gmx.de>
+
+ KCachegrind 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, version 2.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+/*
+ * Classes for breadth-first search in local filesystem
+ */
+
+#ifndef FSDIR_H
+#define FSDIR_H
+
+#include <qptrlist.h>
+#include <qvaluevector.h>
+#include <qfile.h>
+
+/* Use KDE_lstat and KIO::fileoffset_t for 64-bit sizes */
+#include <klargefile.h>
+#include <kio/global.h>
+
+class ScanDir;
+class ScanFile;
+
+class ScanItem
+{
+ public:
+ ScanItem(const QString& p, ScanDir* d)
+ { absPath = p; dir = d; }
+
+ QString absPath;
+ ScanDir* dir;
+};
+
+typedef QPtrList<ScanItem> ScanItemList;
+
+
+/**
+ * Listener for events from directory scanning.
+ *
+ * You can register a listener for the ScanManager to get
+ * all scan events and a listener for every ScanDir for
+ * directory specific scan events.
+ *
+ * sizeChanged is called when a scan of a subdirectory
+ * finished.
+ */
+class ScanListener
+{
+ public:
+ virtual void scanStarted(ScanDir*) {}
+ virtual void sizeChanged(ScanDir*) {}
+ virtual void scanFinished(ScanDir*) {}
+ // destroyed events are not delivered to listeners of ScanManager
+ virtual void destroyed(ScanDir*) {}
+ virtual void destroyed(ScanFile*) {}
+};
+
+
+
+/**
+ * ScanManager
+ *
+ * Start/Stop/Restart Scans. Example:
+ *
+ * ScanManager m("/opt");
+ * m.startScan();
+ * while(m.scan());
+ */
+class ScanManager
+{
+ public:
+ ScanManager();
+ ScanManager(const QString& path);
+ ~ScanManager();
+
+ /** Set the top path for scanning
+ * The ScanDir object created gets attribute data.
+ */
+ ScanDir* setTop(const QString& path, int data = 0);
+ ScanDir* top() { return _topDir; }
+
+ bool scanRunning();
+ unsigned int scanLength() { return _list.count(); }
+
+ /**
+ * Starts the scan. Stop previous scan if running.
+ * For the actual scan to happen, you have to call
+ * scan() peridically.
+ *
+ * If from !=0, restart scan at given position; from must
+ * be from the previous scan of this manager.
+ */
+ void startScan(ScanDir* from = 0);
+
+ /** Stop a current running scan.
+ * Make all directories to finish their scan.
+ */
+ void stopScan();
+
+ /**
+ * Scan first directory from todo list.
+ * Directories added to the todo list are attributed with data.
+ * Returns the number of new subdirectories created for scanning.
+ */
+ int scan(int data);
+
+ /* set listener to get a callbacks from this ScanDir */
+ void setListener(ScanListener*);
+ ScanListener* listener() { return _listener; }
+
+ private:
+ ScanItemList _list;
+ ScanDir* _topDir;
+ ScanListener* _listener;
+};
+
+class ScanFile
+{
+ public:
+ ScanFile();
+ ScanFile(const QString& n, KIO::fileoffset_t s);
+ ~ScanFile();
+
+ const QString& name() { return _name; }
+ KIO::fileoffset_t size() { return _size; }
+
+ /* set listener to get callbacks from this ScanDir */
+ void setListener(ScanListener* l) { _listener = l; }
+ ScanListener* listener() { return _listener; }
+
+ private:
+ QString _name;
+ KIO::fileoffset_t _size;
+ ScanListener* _listener;
+};
+
+typedef QValueVector<ScanFile> ScanFileVector;
+typedef QValueVector<ScanDir> ScanDirVector;
+
+/**
+ * A directory to scan.
+ * You can attribute a directory to scan with a
+ * integer data attribute.
+ */
+class ScanDir
+{
+ public:
+ ScanDir();
+ ScanDir(const QString& n, ScanManager* m,
+ ScanDir* p = 0, int data = 0);
+ ~ScanDir();
+
+ /* Get items of this directory
+ * and append subdirectories to todo list.
+ *
+ * Directories added to the todo list are attributed with data.
+ * Returns the number of new subdirectories created for scanning.
+ */
+ int scan(ScanItem* si, ScanItemList& list, int data);
+
+ /* clear scan objects below */
+ void clear();
+
+ /*
+ * Setup for child rescan
+ */
+ void setupChildRescan();
+
+ /* Absolute path. Warning: Slow, loops to top parent. */
+ QString path();
+
+ /* get integer data attribute */
+ int data() { return _data; }
+ void setData(int d) { _data = d; }
+
+ ScanFileVector& files() { return _files; }
+ ScanDirVector& dirs() { return _dirs; }
+ const QString& name() { return _name; }
+ KIO::fileoffset_t size() { update(); return _size; }
+ unsigned int fileCount() { update(); return _fileCount; }
+ unsigned int dirCount() { update(); return _dirCount; }
+ ScanDir* parent() { return _parent; }
+ bool scanStarted() { return (_dirsFinished >= 0); }
+ bool scanFinished() { return (_dirsFinished == (int)_dirs.count()); }
+ bool scanRunning() { return scanStarted() && !scanFinished(); }
+
+ /* set listener to get a callbacks from this ScanDir */
+ void setListener(ScanListener*);
+ ScanListener* listener() { return _listener; }
+ ScanManager* manager() { return _manager; }
+
+ /* force current scan to be finished */
+ void finish();
+
+ private:
+ void update();
+
+ /* this propagates file count and size to upper dirs */
+ void subScanFinished();
+ void callScanStarted();
+ void callSizeChanged();
+ void callScanFinished();
+
+ ScanFileVector _files;
+ ScanDirVector _dirs;
+
+ QString _name;
+ bool _dirty; /* needs a call to update() */
+ KIO::fileoffset_t _size, _fileSize;
+ unsigned int _fileCount, _dirCount;
+ int _dirsFinished, _data;
+ ScanDir* _parent;
+ ScanListener* _listener;
+ ScanManager* _manager;
+};
+
+#endif
diff --git a/konq-plugins/fsview/scantest.cpp b/konq-plugins/fsview/scantest.cpp
new file mode 100644
index 0000000..e1319a9
--- /dev/null
+++ b/konq-plugins/fsview/scantest.cpp
@@ -0,0 +1,56 @@
+/* This file is part of FSView.
+ Copyright (C) 2002, 2003 Josef Weidendorfer <Josef.Weidendorfer@gmx.de>
+
+ KCachegrind 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, version 2.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+/* Test Directory Scanning. Usually not build. */
+
+#include <stdio.h>
+#include <unistd.h>
+
+#include "scan.h"
+
+class MyListener: public ScanListener
+{
+public:
+ void scanStarted(ScanDir* d)
+ {
+ printf("Started Scan on %s\n", d->name().latin1());
+ };
+
+ void sizeChanged(ScanDir* d)
+ {
+ printf("Change in %s: Dirs %d, Files %d",
+ d->name().latin1(),
+ d->dirCount(), d->fileCount());
+ printf("Size %llu\n", (unsigned long long int)d->size());
+ }
+
+ void scanFinished(ScanDir* d)
+ {
+ printf("Finished Scan on %s\n", d->name().latin1());
+ }
+};
+
+int main(int argc, char* argv[])
+{
+ ScanManager m("/opt");
+ if (argc>1) m.setTop(argv[1]);
+
+ m.setListener(new MyListener());
+ m.startScan();
+ while(m.scan(1));
+}
diff --git a/konq-plugins/fsview/treemap.cpp b/konq-plugins/fsview/treemap.cpp
new file mode 100644
index 0000000..8de5c01
--- /dev/null
+++ b/konq-plugins/fsview/treemap.cpp
@@ -0,0 +1,3199 @@
+/* This file is part of KCachegrind.
+ Copyright (C) 2002, 2003 Josef Weidendorfer <Josef.Weidendorfer@gmx.de>
+
+ KCachegrind 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, version 2.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+/*
+ * A Widget for visualizing hierarchical metrics as areas.
+ * The API is similar to QListView.
+ */
+
+#include <math.h>
+
+#include <qpainter.h>
+#include <qtooltip.h>
+#include <qregexp.h>
+#include <qstyle.h>
+#include <qpopupmenu.h>
+
+#include <klocale.h>
+#include <kconfig.h>
+#include <kdebug.h>
+
+#include "treemap.h"
+
+
+// set this to 1 to enable debug output
+#define DEBUG_DRAWING 0
+#define MAX_FIELD 12
+
+
+//
+// StoredDrawParams
+//
+StoredDrawParams::StoredDrawParams()
+{
+ _selected = false;
+ _current = false;
+ _shaded = true;
+ _rotated = false;
+
+ _backColor = Qt::white;
+
+ // field array has size 0
+}
+
+StoredDrawParams::StoredDrawParams(QColor c,
+ bool selected, bool current)
+{
+ _backColor = c;
+
+ _selected = selected;
+ _current = current;
+ _shaded = true;
+ _rotated = false;
+
+ // field array has size 0
+}
+
+QString StoredDrawParams::text(int f) const
+{
+ if ((f<0) || (f >= (int)_field.size()))
+ return QString::null;
+
+ return _field[f].text;
+}
+
+QPixmap StoredDrawParams::pixmap(int f) const
+{
+ if ((f<0) || (f >= (int)_field.size()))
+ return QPixmap();
+
+ return _field[f].pix;
+}
+
+DrawParams::Position StoredDrawParams::position(int f) const
+{
+ if ((f<0) || (f >= (int)_field.size()))
+ return Default;
+
+ return _field[f].pos;
+}
+
+int StoredDrawParams::maxLines(int f) const
+{
+ if ((f<0) || (f >= (int)_field.size()))
+ return 0;
+
+ return _field[f].maxLines;
+}
+
+const QFont& StoredDrawParams::font() const
+{
+ static QFont* f = 0;
+ if (!f) f = new QFont(QApplication::font());
+
+ return *f;
+}
+
+void StoredDrawParams::ensureField(int f)
+{
+ static Field* def = 0;
+ if (!def) {
+ def = new Field();
+ def->pos = Default;
+ def->maxLines = 0;
+ }
+
+ if (f<0 || f>=MAX_FIELD) return;
+
+ if ((int)_field.size() < f+1) _field.resize(f+1, *def);
+}
+
+
+void StoredDrawParams::setField(int f, const QString& t, QPixmap pm,
+ Position p, int maxLines)
+{
+ if (f<0 || f>=MAX_FIELD) return;
+ ensureField(f);
+
+ _field[f].text = t;
+ _field[f].pix = pm;
+ _field[f].pos = p;
+ _field[f].maxLines = maxLines;
+}
+
+void StoredDrawParams::setText(int f, const QString& t)
+{
+ if (f<0 || f>=MAX_FIELD) return;
+ ensureField(f);
+
+ _field[f].text = t;
+}
+
+void StoredDrawParams::setPixmap(int f, const QPixmap& pm)
+{
+ if (f<0 || f>=MAX_FIELD) return;
+ ensureField(f);
+
+ _field[f].pix = pm;
+}
+
+void StoredDrawParams::setPosition(int f, Position p)
+{
+ if (f<0 || f>=MAX_FIELD) return;
+ ensureField(f);
+
+ _field[f].pos = p;
+}
+
+void StoredDrawParams::setMaxLines(int f, int m)
+{
+ if (f<0 || f>=MAX_FIELD) return;
+ ensureField(f);
+
+ _field[f].maxLines = m;
+}
+
+
+
+//
+// RectDrawing
+//
+
+RectDrawing::RectDrawing(QRect r)
+{
+ _fm = 0;
+ _dp = 0;
+ setRect(r);
+}
+
+
+RectDrawing::~RectDrawing()
+{
+ delete _fm;
+ delete _dp;
+}
+
+DrawParams* RectDrawing::drawParams()
+{
+ if (!_dp)
+ _dp = new StoredDrawParams();
+
+ return _dp;
+}
+
+
+void RectDrawing::setDrawParams(DrawParams* dp)
+{
+ if (_dp) delete _dp;
+ _dp = dp;
+}
+
+void RectDrawing::setRect(QRect r)
+{
+ _rect = r;
+
+ _usedTopLeft = 0;
+ _usedTopCenter = 0;
+ _usedTopRight = 0;
+ _usedBottomLeft = 0;
+ _usedBottomCenter = 0;
+ _usedBottomRight = 0;
+
+ _fontHeight = 0;
+}
+
+QRect RectDrawing::remainingRect(DrawParams* dp)
+{
+ if (!dp) dp = drawParams();
+
+ if ((_usedTopLeft >0) ||
+ (_usedTopCenter >0) ||
+ (_usedTopRight >0)) {
+ if (dp->rotated())
+ _rect.setLeft(_rect.left() + _fontHeight);
+ else
+ _rect.setTop(_rect.top() + _fontHeight);
+ }
+
+ if ((_usedBottomLeft >0) ||
+ (_usedBottomCenter >0) ||
+ (_usedBottomRight >0)) {
+ if (dp->rotated())
+ _rect.setRight(_rect.right() - _fontHeight);
+ else
+ _rect.setBottom(_rect.bottom() - _fontHeight);
+ }
+ return _rect;
+}
+
+
+void RectDrawing::drawBack(QPainter* p, DrawParams* dp)
+{
+ if (!dp) dp = drawParams();
+ if (_rect.width()<=0 || _rect.height()<=0) return;
+
+ QRect r = _rect;
+ QColor normal = dp->backColor();
+ if (dp->selected()) normal = normal.light();
+ bool isCurrent = dp->current();
+
+ // 3D raised/sunken frame effect...
+ QColor high = normal.light();
+ QColor low = normal.dark();
+ p->setPen( isCurrent ? low:high);
+ p->drawLine(r.left(), r.top(), r.right(), r.top());
+ p->drawLine(r.left(), r.top(), r.left(), r.bottom());
+ p->setPen( isCurrent ? high:low);
+ p->drawLine(r.right(), r.top(), r.right(), r.bottom());
+ p->drawLine(r.left(), r.bottom(), r.right(), r.bottom());
+ r.setRect(r.x()+1, r.y()+1, r.width()-2, r.height()-2);
+ if (r.width()<=0 || r.height()<=0) return;
+
+ if (dp->shaded()) {
+ // some shading
+ bool goDark = qGray(normal.rgb())>128;
+ int rBase, gBase, bBase;
+ normal.rgb(&rBase, &gBase, &bBase);
+ p->setBrush(QBrush::NoBrush);
+
+ // shade parameters:
+ int d = 7;
+ float factor = 0.1, forth=0.7, back1 =0.9, toBack2 = .7, back2 = 0.97;
+
+ // coefficient corrections because of rectangle size
+ int s = r.width();
+ if (s > r.height()) s = r.height();
+ if (s<100) {
+ forth -= .3 * (100-s)/100;
+ back1 -= .2 * (100-s)/100;
+ back2 -= .02 * (100-s)/100;
+ }
+
+
+ // maximal color difference
+ int rDiff = goDark ? -rBase/d : (255-rBase)/d;
+ int gDiff = goDark ? -gBase/d : (255-gBase)/d;
+ int bDiff = goDark ? -bBase/d : (255-bBase)/d;
+
+ QColor shadeColor;
+ while (factor<.95) {
+ shadeColor.setRgb((int)(rBase+factor*rDiff+.5),
+ (int)(gBase+factor*gDiff+.5),
+ (int)(bBase+factor*bDiff+.5));
+ p->setPen(shadeColor);
+ p->drawRect(r);
+ r.setRect(r.x()+1, r.y()+1, r.width()-2, r.height()-2);
+ if (r.width()<=0 || r.height()<=0) return;
+ factor = 1.0 - ((1.0 - factor) * forth);
+ }
+
+ // and back (1st half)
+ while (factor>toBack2) {
+ shadeColor.setRgb((int)(rBase+factor*rDiff+.5),
+ (int)(gBase+factor*gDiff+.5),
+ (int)(bBase+factor*bDiff+.5));
+ p->setPen(shadeColor);
+ p->drawRect(r);
+ r.setRect(r.x()+1, r.y()+1, r.width()-2, r.height()-2);
+ if (r.width()<=0 || r.height()<=0) return;
+ factor = 1.0 - ((1.0 - factor) / back1);
+ }
+
+ // and back (2nd half)
+ while ( factor>.01) {
+ shadeColor.setRgb((int)(rBase+factor*rDiff+.5),
+ (int)(gBase+factor*gDiff+.5),
+ (int)(bBase+factor*bDiff+.5));
+ p->setPen(shadeColor);
+ p->drawRect(r);
+ r.setRect(r.x()+1, r.y()+1, r.width()-2, r.height()-2);
+ if (r.width()<=0 || r.height()<=0) return;
+
+ factor = factor * back2;
+ }
+ }
+
+ // fill inside
+ p->setPen(QPen::NoPen);
+ p->setBrush(normal);
+ p->drawRect(r);
+}
+
+
+bool RectDrawing::drawField(QPainter* p, int f, DrawParams* dp)
+{
+ if (!dp) dp = drawParams();
+
+ if (!_fm) {
+ _fm = new QFontMetrics(dp->font());
+ _fontHeight = _fm->height();
+ }
+
+ QRect r = _rect;
+
+ if (0) kdDebug(90100) << "DrawField: Rect " << r.x() << "/" << r.y()
+ << " - " << r.width() << "x" << r.height() << endl;
+
+ int h = _fontHeight;
+ bool rotate = dp->rotated();
+ int width = (rotate ? r.height() : r.width()) -4;
+ int height = (rotate ? r.width() : r.height());
+ int lines = height / h;
+
+ // stop if we have no space available
+ if (lines<1) return false;
+
+ // calculate free space in first line (<unused>)
+ int pos = dp->position(f);
+ if (pos == DrawParams::Default) {
+ switch(f%4) {
+ case 0: pos = DrawParams::TopLeft; break;
+ case 1: pos = DrawParams::TopRight; break;
+ case 2: pos = DrawParams::BottomRight; break;
+ case 3: pos = DrawParams::BottomLeft; break;
+ }
+ }
+
+ int unused = 0;
+ bool isBottom = false;
+ bool isCenter = false;
+ bool isRight = false;
+ int* used = 0;
+ switch(pos) {
+ case DrawParams::TopLeft:
+ used = &_usedTopLeft;
+ if (_usedTopLeft == 0) {
+ if (_usedTopCenter)
+ unused = (width - _usedTopCenter)/2;
+ else
+ unused = width - _usedTopRight;
+ }
+ break;
+
+ case DrawParams::TopCenter:
+ isCenter = true;
+ used = &_usedTopCenter;
+ if (_usedTopCenter == 0) {
+ if (_usedTopLeft > _usedTopRight)
+ unused = width - 2 * _usedTopLeft;
+ else
+ unused = width - 2 * _usedTopRight;
+ }
+ break;
+
+ case DrawParams::TopRight:
+ isRight = true;
+ used = &_usedTopRight;
+ if (_usedTopRight == 0) {
+ if (_usedTopCenter)
+ unused = (width - _usedTopCenter)/2;
+ else
+ unused = width - _usedTopLeft;
+ }
+ break;
+
+ case DrawParams::BottomLeft:
+ isBottom = true;
+ used = &_usedBottomLeft;
+ if (_usedBottomLeft == 0) {
+ if (_usedBottomCenter)
+ unused = (width - _usedBottomCenter)/2;
+ else
+ unused = width - _usedBottomRight;
+ }
+ break;
+
+ case DrawParams::BottomCenter:
+ isCenter = true;
+ isBottom = true;
+ used = &_usedBottomCenter;
+ if (_usedBottomCenter == 0) {
+ if (_usedBottomLeft > _usedBottomRight)
+ unused = width - 2 * _usedBottomLeft;
+ else
+ unused = width - 2 * _usedBottomRight;
+ }
+ break;
+
+ case DrawParams::BottomRight:
+ isRight = true;
+ isBottom = true;
+ used = &_usedBottomRight;
+ if (_usedBottomRight == 0) {
+ if (_usedBottomCenter)
+ unused = (width - _usedBottomCenter)/2;
+ else
+ unused = width - _usedBottomLeft;
+ }
+ break;
+ }
+
+ if (isBottom) {
+ if ((_usedTopLeft >0) ||
+ (_usedTopCenter >0) ||
+ (_usedTopRight >0))
+ lines--;
+ }
+ else if (!isBottom) {
+ if ((_usedBottomLeft >0) ||
+ (_usedBottomCenter >0) ||
+ (_usedBottomRight >0))
+ lines--;
+ }
+ if (lines<1) return false;
+
+
+ int y = isBottom ? height - h : 0;
+
+ if (unused < 0) unused = 0;
+ if (unused == 0) {
+ // no space available in last line at this position
+ y = isBottom ? (y-h) : (y+h);
+ lines--;
+
+ if (lines<1) return false;
+
+ // new line: reset used space
+ if (isBottom)
+ _usedBottomLeft = _usedBottomCenter = _usedBottomRight = 0;
+ else
+ _usedTopLeft = _usedTopCenter = _usedTopRight = 0;
+
+ unused = width;
+ }
+
+ // stop as soon as possible when there's no space for "..."
+ static int dotW = 0;
+ if (!dotW) dotW = _fm->width("...");
+ if (width < dotW) return false;
+
+ // get text and pixmap now, only if we need to, because it is possible
+ // that they are calculated on demand (and this can take some time)
+ QString name = dp->text(f);
+ if (name.isEmpty()) return 0;
+ QPixmap pix = dp->pixmap(f);
+
+ // check if pixmap can be drawn
+ int pixW = pix.width();
+ int pixH = pix.height();
+ int pixY = 0;
+ bool pixDrawn = true;
+ if (pixW>0) {
+ pixW += 2; // X distance from pix
+ if ((width < pixW + dotW) || (height < pixH)) {
+ // don't draw
+ pixW = 0;
+ }
+ else
+ pixDrawn = false;
+ }
+
+ // width of text and pixmap to be drawn
+ int w = pixW + _fm->width(name);
+
+ if (0) kdDebug(90100) << " For '" << name << "': Unused " << unused
+ << ", StrW " << w << ", Width " << width << endl;
+
+ // if we have limited space at 1st line:
+ // use it only if whole name does fit in last line...
+ if ((unused < width) && (w > unused)) {
+ y = isBottom ? (y-h) : (y+h);
+ lines--;
+
+ if (lines<1) return false;
+
+ // new line: reset used space
+ if (isBottom)
+ _usedBottomLeft = _usedBottomCenter = _usedBottomRight = 0;
+ else
+ _usedTopLeft = _usedTopCenter = _usedTopRight = 0;
+ }
+
+ p->save();
+ p->setPen( (qGray(dp->backColor().rgb())>100) ? Qt::black : Qt::white);
+ p->setFont(dp->font());
+ if (rotate) {
+ //p->translate(r.x()+2, r.y()+r.height());
+ p->translate(r.x(), r.y()+r.height()-2);
+ p->rotate(270);
+ }
+ else
+ p->translate(r.x()+2, r.y());
+
+
+ // adjust available lines according to maxLines
+ int max = dp->maxLines(f);
+ if ((max > 0) && (lines>max)) lines = max;
+
+ /* loop over name parts to break up string depending on available width.
+ * every char category change is supposed a possible break,
+ * with the exception Uppercase=>Lowercase.
+ * It's good enough for numbers, Symbols...
+ *
+ * If the text is to be written at the bottom, we start with the
+ * end of the string (so everything is reverted)
+ */
+ QString remaining;
+ int origLines = lines;
+ while (lines>0) {
+
+ if (w>width && lines>1) {
+ int lastBreakPos = name.length(), lastWidth = w;
+ int len = name.length();
+ QChar::Category caOld, ca;
+
+ if (!isBottom) {
+ // start with comparing categories of last 2 chars
+ caOld = name[len-1].category();
+ while (len>2) {
+ len--;
+ ca = name[len-1].category();
+ if (ca != caOld) {
+ // "Aa" has no break between...
+ if (ca == QChar::Letter_Uppercase &&
+ caOld == QChar::Letter_Lowercase) {
+ caOld = ca;
+ continue;
+ }
+ caOld = ca;
+ lastBreakPos = len;
+ w = pixW + _fm->width(name, len);
+ lastWidth = w;
+ if (w <= width) break;
+ }
+ }
+ w = lastWidth;
+ remaining = name.mid(lastBreakPos);
+ // remove space on break point
+ if (name[lastBreakPos-1].category() == QChar::Separator_Space)
+ name = name.left(lastBreakPos-1);
+ else
+ name = name.left(lastBreakPos);
+ }
+ else { // bottom
+ int l = len;
+ caOld = name[l-len].category();
+ while (len>2) {
+ len--;
+ ca = name[l-len].category();
+
+ if (ca != caOld) {
+ // "Aa" has no break between...
+ if (caOld == QChar::Letter_Uppercase &&
+ ca == QChar::Letter_Lowercase) {
+ caOld = ca;
+ continue;
+ }
+ caOld = ca;
+ lastBreakPos = len;
+ w = pixW + _fm->width(name.right(len));
+ lastWidth = w;
+ if (w <= width) break;
+ }
+ }
+ w = lastWidth;
+ remaining = name.left(l-lastBreakPos);
+ // remove space on break point
+ if (name[l-lastBreakPos].category() == QChar::Separator_Space)
+ name = name.right(lastBreakPos-1);
+ else
+ name = name.right(lastBreakPos);
+ }
+ }
+ else
+ remaining = QString::null;
+
+ /* truncate and add ... if needed */
+ if (w>width) {
+ int len = name.length();
+ w += dotW;
+ while (len>2 && (w > width)) {
+ len--;
+ w = pixW + _fm->width(name, len) + dotW;
+ }
+ // stop drawing: we cannot draw 2 chars + "..."
+ if (w>width) break;
+
+ name = name.left(len) + "...";
+ }
+
+ int x = 0;
+ if (isCenter)
+ x = (width - w)/2;
+ else if (isRight)
+ x = width - w;
+
+ if (!pixDrawn) {
+ pixY = y+(h-pixH)/2; // default: center vertically
+ if (pixH > h) pixY = isBottom ? y-(pixH-h) : y;
+
+ p->drawPixmap( x, pixY, pix);
+
+ // for distance to next text
+ pixY = isBottom ? (pixY - h - 2) : (pixY + pixH + 2);
+ pixDrawn = true;
+ }
+
+
+ if (0) kdDebug(90100) << " Drawing '" << name << "' at "
+ << x+pixW << "/" << y << endl;
+
+ p->drawText( x+pixW, y,
+ width - pixW, h,
+ Qt::AlignLeft, name);
+ y = isBottom ? (y-h) : (y+h);
+ lines--;
+
+ if (remaining.isEmpty()) break;
+ name = remaining;
+ w = pixW + _fm->width(name);
+ }
+
+ // make sure the pix stays visible
+ if (pixDrawn && (pixY>0)) {
+ if (isBottom && (pixY<y)) y = pixY;
+ if (!isBottom && (pixY>y)) y = pixY;
+ }
+
+ if (origLines > lines) {
+ // if only 1 line written, don't reset _used* vars
+ if (lines - origLines >1) {
+ if (isBottom)
+ _usedBottomLeft = _usedBottomCenter = _usedBottomRight = 0;
+ else
+ _usedTopLeft = _usedTopCenter = _usedTopRight = 0;
+ }
+
+ // take back one line
+ y = isBottom ? (y+h) : (y-h);
+ if (used) *used = w;
+ }
+
+ // update free space
+ if (!isBottom) {
+ if (rotate)
+ _rect.setRect(r.x()+y, r.y(), r.width()-y, r.height());
+ else
+ _rect.setRect(r.x(), r.y()+y, r.width(), r.height()-y);
+ }
+ else {
+ if (rotate)
+ _rect.setRect(r.x(), r.y(), y+h, r.height());
+ else
+ _rect.setRect(r.x(), r.y(), r.width(), y+h);
+ }
+
+ p->restore();
+
+ return true;
+}
+
+
+
+
+
+
+//
+// TreeMapItemList
+//
+
+int TreeMapItemList::compareItems ( Item item1, Item item2 )
+{
+ bool ascending;
+ int result;
+
+ TreeMapItem* parent = ((TreeMapItem*)item1)->parent();
+ // shouldn't happen
+ if (!parent) return 0;
+
+ int textNo = parent->sorting(&ascending);
+
+ if (textNo < 0) {
+ double diff = ((TreeMapItem*)item1)->value() -
+ ((TreeMapItem*)item2)->value();
+ result = (diff < -.9) ? -1 : (diff > .9) ? 1 : 0;
+ }
+ else
+ result = (((TreeMapItem*)item1)->text(textNo) <
+ ((TreeMapItem*)item2)->text(textNo)) ? -1 : 1;
+
+ return ascending ? result : -result;
+}
+
+
+TreeMapItem* TreeMapItemList::commonParent()
+{
+ TreeMapItem* parent, *item;
+ parent = first();
+ if (parent)
+ while( (item = next()) != 0)
+ parent = parent->commonParent(item);
+
+ return parent;
+}
+
+
+// TreeMapItem
+
+TreeMapItem::TreeMapItem(TreeMapItem* parent, double value)
+{
+ _value = value;
+ _parent = parent;
+
+ _sum = 0;
+ _children = 0;
+ _widget = 0;
+ _index = -1;
+ _depth = -1; // not set
+ _unused_self = 0;
+ _freeRects = 0;
+
+ if (_parent) {
+ // take sorting from parent
+ _sortTextNo = _parent->sorting(&_sortAscending);
+ _parent->addItem(this);
+ }
+ else {
+ _sortAscending = false;
+ _sortTextNo = -1; // default: no sorting
+ }
+}
+
+
+TreeMapItem::TreeMapItem(TreeMapItem* parent, double value,
+ QString text1, QString text2,
+ QString text3, QString text4)
+{
+ _value = value;
+ _parent = parent;
+
+ // this resizes the text vector only if needed
+ if (!text4.isEmpty()) setText(3, text4);
+ if (!text3.isEmpty()) setText(2, text3);
+ if (!text2.isEmpty()) setText(1, text2);
+ setText(0, text1);
+
+ _sum = 0;
+ _children = 0;
+ _widget = 0;
+ _index = -1;
+ _depth = -1; // not set
+ _unused_self = 0;
+ _freeRects = 0;
+
+ if (_parent) _parent->addItem(this);
+}
+
+TreeMapItem::~TreeMapItem()
+{
+ if (_children) delete _children;
+ if (_freeRects) delete _freeRects;
+
+ // finally, notify widget about deletion
+ if (_widget) _widget->deletingItem(this);
+}
+
+void TreeMapItem::setParent(TreeMapItem* p)
+{
+ _parent = p;
+ if (p) _widget = p->_widget;
+}
+
+bool TreeMapItem::isChildOf(TreeMapItem* item)
+{
+ if (!item) return false;
+
+ TreeMapItem* i = this;
+ while (i) {
+ if (item == i) return true;
+ i = i->_parent;
+ }
+ return false;
+}
+
+TreeMapItem* TreeMapItem::commonParent(TreeMapItem* item)
+{
+ while (item && !isChildOf(item)) {
+ item = item->parent();
+ }
+ return item;
+}
+
+void TreeMapItem::redraw()
+{
+ if (_widget)
+ _widget->redraw(this);
+}
+
+void TreeMapItem::clear()
+{
+ if (_children) {
+ // delete selected items below this item from selection
+ if (_widget) _widget->clearSelection(this);
+
+ delete _children;
+ _children = 0;
+ }
+}
+
+
+// invalidates current children and forces redraw
+// this is only usefull when children are created on demand in items()
+void TreeMapItem::refresh()
+{
+ clear();
+ redraw();
+}
+
+
+QStringList TreeMapItem::path(int textNo) const
+{
+ QStringList list(text(textNo));
+
+ TreeMapItem* i = _parent;
+ while (i) {
+ QString text = i->text(textNo);
+ if (!text.isEmpty())
+ list.prepend(i->text(textNo));
+ i = i->_parent;
+ }
+ return list;
+}
+
+int TreeMapItem::depth() const
+{
+ if (_depth>0) return _depth;
+
+ if (_parent)
+ return _parent->depth() + 1;
+ return 1;
+}
+
+
+bool TreeMapItem::initialized()
+{
+ if (!_children) {
+ _children = new TreeMapItemList;
+ _children->setAutoDelete(true);
+ return false;
+ }
+ return true;
+}
+
+void TreeMapItem::addItem(TreeMapItem* i)
+{
+ if (!i) return;
+
+ if (!_children) {
+ _children = new TreeMapItemList;
+ _children->setAutoDelete(true);
+ }
+ i->setParent(this);
+
+ if (sorting(0) == -1)
+ _children->append(i); // preserve insertion order
+ else
+ _children->inSort(i);
+}
+
+
+// default implementations of virtual functions
+
+double TreeMapItem::value() const
+{
+ return _value;
+}
+
+double TreeMapItem::sum() const
+{
+ return _sum;
+}
+
+DrawParams::Position TreeMapItem::position(int f) const
+{
+ Position p = StoredDrawParams::position(f);
+ if (_widget && (p == Default))
+ p = _widget->fieldPosition(f);
+
+ return p;
+}
+
+// use widget font
+const QFont& TreeMapItem::font() const
+{
+ return _widget->currentFont();
+}
+
+
+bool TreeMapItem::isMarked(int) const
+{
+ return false;
+}
+
+
+int TreeMapItem::borderWidth() const
+{
+ if (_widget)
+ return _widget->borderWidth();
+
+ return 2;
+}
+
+int TreeMapItem::sorting(bool* ascending) const
+{
+ if (ascending) *ascending = _sortAscending;
+ return _sortTextNo;
+}
+
+// do *not* set sorting recursively
+void TreeMapItem::setSorting(int textNo, bool ascending)
+{
+ if (_sortTextNo == textNo) {
+ if(_sortAscending == ascending) return;
+ if (textNo == -1) {
+ // when no sorting is done, order change doesn't do anything
+ _sortAscending = ascending;
+ return;
+ }
+ }
+ _sortAscending = ascending;
+ _sortTextNo = textNo;
+
+ if (_children && _sortTextNo != -1) _children->sort();
+}
+
+void TreeMapItem::resort(bool recursive)
+{
+ if (!_children) return;
+
+ if (_sortTextNo != -1) _children->sort();
+
+ if (recursive)
+ for (TreeMapItem* i=_children->first(); i; i=_children->next())
+ i->resort(recursive);
+}
+
+
+TreeMapItem::SplitMode TreeMapItem::splitMode() const
+{
+ if (_widget)
+ return _widget->splitMode();
+
+ return Best;
+}
+
+int TreeMapItem::rtti() const
+{
+ return 0;
+}
+
+TreeMapItemList* TreeMapItem::children()
+{
+ if (!_children) {
+ _children = new TreeMapItemList;
+ _children->setAutoDelete(true);
+ }
+ return _children;
+}
+
+void TreeMapItem::clearItemRect()
+{
+ _rect = QRect();
+ clearFreeRects();
+}
+
+void TreeMapItem::clearFreeRects()
+{
+ if (_freeRects) _freeRects->clear();
+}
+
+void TreeMapItem::addFreeRect(const QRect& r)
+{
+ // don't add invalid rects
+ if ((r.width() < 1) || (r.height() < 1)) return;
+
+ if (!_freeRects) {
+ _freeRects = new QPtrList<QRect>;
+ _freeRects->setAutoDelete(true);
+ }
+
+ if (0) kdDebug(90100) << "addFree(" << path(0).join("/") << ", "
+ << r.x() << "/" << r.y() << "-"
+ << r.width() << "x" << r.height() << ")" << endl;
+
+ QRect* last = _freeRects->last();
+ if (!last) {
+ _freeRects->append(new QRect(r));
+ return;
+ }
+
+ // join rect with last rect if possible
+ // this saves memory and doesn't make the tooltip flicker
+
+ bool replaced = false;
+ if ((last->left() == r.left()) && (last->width() == r.width())) {
+ if ((last->bottom()+1 == r.top()) || (r.bottom()+1 == last->top())) {
+ *last |= r;
+ replaced = true;
+ }
+ }
+ else if ((last->top() == r.top()) && (last->height() == r.height())) {
+ if ((last->right()+1 == r.left()) || (r.right()+1 == last->left())) {
+ *last |= r;
+ replaced = true;
+ }
+ }
+
+ if (!replaced) {
+ _freeRects->append(new QRect(r));
+ return;
+ }
+
+ if (0) kdDebug(90100) << " united with last to ("
+ << last->x() << "/" << last->y() << "-"
+ << last->width() << "x" << last->height() << ")" << endl;
+}
+
+
+// Tooltips for TreeMapWidget
+
+class TreeMapTip: public QToolTip
+{
+public:
+ TreeMapTip( QWidget* p ):QToolTip(p) {}
+
+protected:
+ void maybeTip( const QPoint & );
+};
+
+void TreeMapTip::maybeTip( const QPoint& pos )
+{
+ if ( !parentWidget()->inherits( "TreeMapWidget" ) )
+ return;
+
+ TreeMapWidget* p = (TreeMapWidget*)parentWidget();
+ TreeMapItem* i;
+ i = p->item(pos.x(), pos.y());
+ QPtrList<QRect>* rList = i ? i->freeRects() : 0;
+ if (rList) {
+ QRect* r;
+ for(r=rList->first();r;r=rList->next())
+ if (r->contains(pos))
+ tip(*r, p->tipString(i));
+ }
+}
+
+
+
+// TreeMapWidget
+
+TreeMapWidget::TreeMapWidget(TreeMapItem* base,
+ QWidget* parent, const char* name)
+ : QWidget(parent, name)
+{
+ _base = base;
+ _base->setWidget(this);
+
+ _font = font();
+ _fontHeight = fontMetrics().height();
+
+
+ // default behaviour
+ _selectionMode = Single;
+ _splitMode = TreeMapItem::AlwaysBest;
+ _visibleWidth = 2;
+ _reuseSpace = false;
+ _skipIncorrectBorder = false;
+ _drawSeparators = false;
+ _allowRotation = true;
+ _borderWidth = 2;
+ _shading = true; // beautiful is default!
+ _maxSelectDepth = -1; // unlimited
+ _maxDrawingDepth = -1; // unlimited
+ _minimalArea = -1; // unlimited
+ _markNo = 0;
+
+ // _stopAtText will be unset on resizing (per default)
+ // _textVisible will be true on resizing (per default)
+ // _forceText will be false on resizing (per default)
+
+ // start state: _selection is an empty list
+ _current = 0;
+ _oldCurrent = 0;
+ _pressed = 0;
+ _lastOver = 0;
+ _needsRefresh = _base;
+
+ setBackgroundMode(Qt::NoBackground);
+ setFocusPolicy(QWidget::StrongFocus);
+ _tip = new TreeMapTip(this);
+}
+
+TreeMapWidget::~TreeMapWidget()
+{
+}
+
+const QFont& TreeMapWidget::currentFont() const
+{
+ return _font;
+}
+
+void TreeMapWidget::setSplitMode(TreeMapItem::SplitMode m)
+{
+ if (_splitMode == m) return;
+
+ _splitMode = m;
+ redraw();
+}
+
+TreeMapItem::SplitMode TreeMapWidget::splitMode() const
+{
+ return _splitMode;
+}
+
+bool TreeMapWidget::setSplitMode(QString mode)
+{
+ if (mode == "Bisection") setSplitMode(TreeMapItem::Bisection);
+ else if (mode == "Columns") setSplitMode(TreeMapItem::Columns);
+ else if (mode == "Rows") setSplitMode(TreeMapItem::Rows);
+ else if (mode == "AlwaysBest") setSplitMode(TreeMapItem::AlwaysBest);
+ else if (mode == "Best") setSplitMode(TreeMapItem::Best);
+ else if (mode == "HAlternate") setSplitMode(TreeMapItem::HAlternate);
+ else if (mode == "VAlternate") setSplitMode(TreeMapItem::VAlternate);
+ else if (mode == "Horizontal") setSplitMode(TreeMapItem::Horizontal);
+ else if (mode == "Vertical") setSplitMode(TreeMapItem::Vertical);
+ else return false;
+
+ return true;
+}
+
+QString TreeMapWidget::splitModeString() const
+{
+ QString mode;
+ switch(splitMode()) {
+ case TreeMapItem::Bisection: mode = "Bisection"; break;
+ case TreeMapItem::Columns: mode = "Columns"; break;
+ case TreeMapItem::Rows: mode = "Rows"; break;
+ case TreeMapItem::AlwaysBest: mode = "AlwaysBest"; break;
+ case TreeMapItem::Best: mode = "Best"; break;
+ case TreeMapItem::HAlternate: mode = "HAlternate"; break;
+ case TreeMapItem::VAlternate: mode = "VAlternate"; break;
+ case TreeMapItem::Horizontal: mode = "Horizontal"; break;
+ case TreeMapItem::Vertical: mode = "Vertical"; break;
+ default: mode = "Unknown"; break;
+ }
+ return mode;
+}
+
+
+void TreeMapWidget::setShadingEnabled(bool s)
+{
+ if (_shading == s) return;
+
+ _shading = s;
+ redraw();
+}
+
+void TreeMapWidget::setAllowRotation(bool enable)
+{
+ if (_allowRotation == enable) return;
+
+ _allowRotation = enable;
+ redraw();
+}
+
+void TreeMapWidget::setVisibleWidth(int width, bool reuseSpace)
+{
+ if (_visibleWidth == width && _reuseSpace == reuseSpace) return;
+
+ _visibleWidth = width;
+ _reuseSpace = reuseSpace;
+ redraw();
+}
+
+void TreeMapWidget::setSkipIncorrectBorder(bool enable)
+{
+ if (_skipIncorrectBorder == enable) return;
+
+ _skipIncorrectBorder = enable;
+ redraw();
+}
+
+void TreeMapWidget::setBorderWidth(int w)
+{
+ if (_borderWidth == w) return;
+
+ _borderWidth = w;
+ redraw();
+}
+
+void TreeMapWidget::setMaxDrawingDepth(int d)
+{
+ if (_maxDrawingDepth == d) return;
+
+ _maxDrawingDepth = d;
+ redraw();
+}
+
+QString TreeMapWidget::defaultFieldType(int f) const
+{
+ return i18n("Text %1").arg(f+1);
+}
+
+QString TreeMapWidget::defaultFieldStop(int) const
+{
+ return QString();
+}
+
+bool TreeMapWidget::defaultFieldVisible(int f) const
+{
+ return (f<2);
+}
+
+bool TreeMapWidget::defaultFieldForced(int) const
+{
+ return false;
+}
+
+DrawParams::Position TreeMapWidget::defaultFieldPosition(int f) const
+{
+ switch(f%4) {
+ case 0: return DrawParams::TopLeft;
+ case 1: return DrawParams::TopRight;
+ case 2: return DrawParams::BottomRight;
+ case 3: return DrawParams::BottomLeft;
+ default:break;
+ }
+ return DrawParams::TopLeft;
+}
+
+bool TreeMapWidget::resizeAttr(int size)
+{
+ if (size<0 || size>=MAX_FIELD) return false;
+
+ if (size>(int)_attr.size()) {
+ struct FieldAttr a;
+ int oldSize = _attr.size();
+ _attr.resize(size, a);
+ while (oldSize<size) {
+ _attr[oldSize].type = defaultFieldType(oldSize);
+ _attr[oldSize].stop = defaultFieldStop(oldSize);
+ _attr[oldSize].visible = defaultFieldVisible(oldSize);
+ _attr[oldSize].forced = defaultFieldForced(oldSize);
+ _attr[oldSize].pos = defaultFieldPosition(oldSize);
+ oldSize++;
+ }
+ }
+ return true;
+}
+
+void TreeMapWidget::setFieldType(int f, QString type)
+{
+ if (((int)_attr.size() < f+1) &&
+ (type == defaultFieldType(f))) return;
+ if (resizeAttr(f+1)) _attr[f].type = type;
+
+ // no need to redraw: the type string is not visible in the TreeMap
+}
+
+QString TreeMapWidget::fieldType(int f) const
+{
+ if (f<0 || (int)_attr.size()<f+1) return defaultFieldType(f);
+ return _attr[f].type;
+}
+
+void TreeMapWidget::setFieldStop(int f, QString stop)
+{
+ if (((int)_attr.size() < f+1) &&
+ (stop == defaultFieldStop(f))) return;
+ if (resizeAttr(f+1)) {
+ _attr[f].stop = stop;
+ redraw();
+ }
+}
+
+QString TreeMapWidget::fieldStop(int f) const
+{
+ if (f<0 || (int)_attr.size()<f+1) return defaultFieldStop(f);
+ return _attr[f].stop;
+}
+
+void TreeMapWidget::setFieldVisible(int f, bool enable)
+{
+ if (((int)_attr.size() < f+1) &&
+ (enable == defaultFieldVisible(f))) return;
+
+ if (resizeAttr(f+1)) {
+ _attr[f].visible = enable;
+ redraw();
+ }
+}
+
+bool TreeMapWidget::fieldVisible(int f) const
+{
+ if (f<0 || (int)_attr.size()<f+1)
+ return defaultFieldVisible(f);
+
+ return _attr[f].visible;
+}
+
+void TreeMapWidget::setFieldForced(int f, bool enable)
+{
+ if (((int)_attr.size() < f+1) &&
+ (enable == defaultFieldForced(f))) return;
+
+ if (resizeAttr(f+1)) {
+ _attr[f].forced = enable;
+ if (_attr[f].visible) redraw();
+ }
+}
+
+bool TreeMapWidget::fieldForced(int f) const
+{
+ if (f<0 || (int)_attr.size()<f+1)
+ return defaultFieldForced(f);
+
+ return _attr[f].forced;
+}
+
+void TreeMapWidget::setFieldPosition(int f, TreeMapItem::Position pos)
+{
+ if (((int)_attr.size() < f+1) &&
+ (pos == defaultFieldPosition(f))) return;
+
+ if (resizeAttr(f+1)) {
+ _attr[f].pos = pos;
+ if (_attr[f].visible) redraw();
+ }
+}
+
+DrawParams::Position TreeMapWidget::fieldPosition(int f) const
+{
+ if (f<0 || (int)_attr.size()<f+1)
+ return defaultFieldPosition(f);
+
+ return _attr[f].pos;
+}
+
+void TreeMapWidget::setFieldPosition(int f, QString pos)
+{
+ if (pos == "TopLeft")
+ setFieldPosition(f, DrawParams::TopLeft);
+ else if (pos == "TopCenter")
+ setFieldPosition(f, DrawParams::TopCenter);
+ else if (pos == "TopRight")
+ setFieldPosition(f, DrawParams::TopRight);
+ else if (pos == "BottomLeft")
+ setFieldPosition(f, DrawParams::BottomLeft);
+ else if (pos == "BottomCenter")
+ setFieldPosition(f, DrawParams::BottomCenter);
+ else if (pos == "BottomRight")
+ setFieldPosition(f, DrawParams::BottomRight);
+ else if (pos == "Default")
+ setFieldPosition(f, DrawParams::Default);
+}
+
+QString TreeMapWidget::fieldPositionString(int f) const
+{
+ TreeMapItem::Position pos = fieldPosition(f);
+ if (pos == DrawParams::TopLeft) return QString("TopLeft");
+ if (pos == DrawParams::TopCenter) return QString("TopCenter");
+ if (pos == DrawParams::TopRight) return QString("TopRight");
+ if (pos == DrawParams::BottomLeft) return QString("BottomLeft");
+ if (pos == DrawParams::BottomCenter) return QString("BottomCenter");
+ if (pos == DrawParams::BottomRight) return QString("BottomRight");
+ if (pos == DrawParams::Default) return QString("Default");
+ return QString("unknown");
+}
+
+void TreeMapWidget::setMinimalArea(int area)
+{
+ if (_minimalArea == area) return;
+
+ _minimalArea = area;
+ redraw();
+}
+
+
+void TreeMapWidget::deletingItem(TreeMapItem* i)
+{
+ // remove any references to the item to be deleted
+ while(_selection.findRef(i) > -1)
+ _selection.remove();
+
+ while(_tmpSelection.findRef(i) > -1)
+ _tmpSelection.remove();
+
+ if (_current == i) _current = 0;
+ if (_oldCurrent == i) _oldCurrent = 0;
+ if (_pressed == i) _pressed = 0;
+ if (_lastOver == i) _lastOver = 0;
+
+ // don't redraw a deleted item
+ if (_needsRefresh == i) {
+ // we can savely redraw the parent, as deleting order is
+ // from child to parent; i.e. i->parent() is existing.
+ _needsRefresh = i->parent();
+ }
+}
+
+
+QString TreeMapWidget::tipString(TreeMapItem* i) const
+{
+ QString tip, itemTip;
+
+ while (i) {
+ if (!i->text(0).isEmpty()) {
+ itemTip = i->text(0);
+ if (!i->text(1).isEmpty())
+ itemTip += " (" + i->text(1) + ")";
+
+ if (!tip.isEmpty())
+ tip += "\n";
+
+ tip += itemTip;
+ }
+ i = i->parent();
+ }
+ return tip;
+}
+
+TreeMapItem* TreeMapWidget::item(int x, int y) const
+{
+ TreeMapItem* p = _base;
+ TreeMapItem* i;
+
+ if (!rect().contains(x, y)) return 0;
+ if (DEBUG_DRAWING) kdDebug(90100) << "item(" << x << "," << y << "):" << endl;
+
+ while (1) {
+ TreeMapItemList* list = p->children();
+ if (!list)
+ i = 0;
+ else {
+ int idx=0;
+ for (i=list->first();i;i=list->next(),idx++) {
+
+ if (DEBUG_DRAWING)
+ kdDebug(90100) << " Checking " << i->path(0).join("/") << " ("
+ << i->itemRect().x() << "/" << i->itemRect().y()
+ << "-" << i->itemRect().width()
+ << "x" << i->itemRect().height() << ")" << endl;
+
+ if (i->itemRect().contains(x, y)) {
+
+ if (DEBUG_DRAWING) kdDebug(90100) << " .. Got. Index " << idx << endl;
+
+ p->setIndex(idx);
+ break;
+ }
+ }
+ }
+
+ if (!i) {
+ static TreeMapItem* last = 0;
+ if (p != last) {
+ last = p;
+
+ if (DEBUG_DRAWING)
+ kdDebug(90100) << "item(" << x << "," << y << "): Got "
+ << p->path(0).join("/") << " (Size "
+ << p->itemRect().width() << "x" << p->itemRect().height()
+ << ", Val " << p->value() << ")" << endl;
+ }
+
+ return p;
+ }
+ p = i;
+ }
+ return 0;
+}
+
+TreeMapItem* TreeMapWidget::possibleSelection(TreeMapItem* i) const
+{
+ if (i) {
+ if (_maxSelectDepth>=0) {
+ int depth = i->depth();
+ while(i && depth > _maxSelectDepth) {
+ i = i->parent();
+ depth--;
+ }
+ }
+ }
+ return i;
+}
+
+TreeMapItem* TreeMapWidget::visibleItem(TreeMapItem* i) const
+{
+ if (i) {
+ /* Must have a visible area */
+ while(i && ((i->itemRect().width() <1) ||
+ (i->itemRect().height() <1))) {
+ TreeMapItem* p = i->parent();
+ if (!p) break;
+ int idx = p->children()->findRef(i);
+ idx--;
+ if (idx<0)
+ i = p;
+ else
+ i = p->children()->at(idx);
+ }
+ }
+ return i;
+}
+
+void TreeMapWidget::setSelected(TreeMapItem* item, bool selected)
+{
+ item = possibleSelection(item);
+ setCurrent(item);
+
+ TreeMapItem* changed = setTmpSelected(item, selected);
+ if (!changed) return;
+
+ _selection = _tmpSelection;
+ if (_selectionMode == Single)
+ emit selectionChanged(item);
+ emit selectionChanged();
+ redraw(changed);
+
+ if (0) kdDebug(90100) << (selected ? "S":"Des") << "elected Item "
+ << (item ? item->path(0).join("") : QString("(null)"))
+ << " (depth " << (item ? item->depth() : -1)
+ << ")" << endl;
+}
+
+void TreeMapWidget::setMarked(int markNo, bool redrawWidget)
+{
+ // if there's no marking, return
+ if ((_markNo == 0) && (markNo == 0)) return;
+
+ _markNo = markNo;
+ if (!clearSelection() && redrawWidget) redraw();
+}
+
+/* Returns all items which appear only in one of the given lists */
+TreeMapItemList TreeMapWidget::diff(TreeMapItemList& l1,
+ TreeMapItemList& l2)
+{
+ TreeMapItemList l;
+ TreeMapItemListIterator it1(l1), it2(l2);
+
+ TreeMapItem* item;
+ while ( (item = it1.current()) != 0 ) {
+ ++it1;
+ if (l2.containsRef(item) > 0) continue;
+ l.append(item);
+ }
+ while ( (item = it2.current()) != 0 ) {
+ ++it2;
+ if (l1.containsRef(item) > 0) continue;
+ l.append(item);
+ }
+
+ return l;
+}
+
+/* Only modifies _tmpSelection.
+ * Returns 0 when no change happened, otherwise the TreeMapItem that has
+ * to be redrawn for all changes.
+ */
+TreeMapItem* TreeMapWidget::setTmpSelected(TreeMapItem* item, bool selected)
+{
+ if (!item) return 0;
+ if (_selectionMode == NoSelection) return 0;
+
+ TreeMapItemList old = _tmpSelection;
+
+ if (_selectionMode == Single) {
+ _tmpSelection.clear();
+ if (selected) _tmpSelection.append(item);
+ }
+ else {
+ if (selected) {
+ TreeMapItem* i=_tmpSelection.first();
+ while (i) {
+ if (i->isChildOf(item) || item->isChildOf(i)) {
+ _tmpSelection.remove();
+ i = _tmpSelection.current();
+ }
+ else
+ i = _tmpSelection.next();
+ }
+ _tmpSelection.append(item);
+ }
+ else
+ _tmpSelection.removeRef(item);
+ }
+
+ return diff(old, _tmpSelection).commonParent();
+}
+
+
+bool TreeMapWidget::clearSelection(TreeMapItem* parent)
+{
+ TreeMapItemList old = _selection;
+
+ TreeMapItem* i=_selection.first();
+ while (i) {
+ if (i->isChildOf(parent)) {
+ _selection.remove();
+ i = _selection.current();
+ }
+ else
+ i = _selection.next();
+ }
+
+ TreeMapItem* changed = diff(old, _selection).commonParent();
+ if (changed) {
+ changed->redraw();
+ emit selectionChanged();
+ }
+ return (changed != 0);
+}
+
+bool TreeMapWidget::isSelected(TreeMapItem* i) const
+{
+ return _selection.containsRef(i)>0;
+}
+
+bool TreeMapWidget::isTmpSelected(TreeMapItem* i)
+{
+ return _tmpSelection.containsRef(i)>0;
+}
+
+
+void TreeMapWidget::setCurrent(TreeMapItem* i, bool kbd)
+{
+ TreeMapItem* old = _current;
+ _current = i;
+
+ if (_markNo >0) {
+ // remove mark
+ _markNo = 0;
+
+ if (1) kdDebug(90100) << "setCurrent(" << i->path(0).join("/")
+ << ") - mark removed" << endl;
+
+ // always complete redraw needed to remove mark
+ redraw();
+
+ if (old == _current) return;
+ }
+ else {
+ if (old == _current) return;
+
+ if (old) old->redraw();
+ if (i) i->redraw();
+ }
+
+ //kdDebug(90100) << "Current Item " << (i ? i->path().ascii() : "(null)") << endl;
+
+ emit currentChanged(i, kbd);
+}
+
+void TreeMapWidget::setRangeSelection(TreeMapItem* i1,
+ TreeMapItem* i2, bool selected)
+{
+ i1 = possibleSelection(i1);
+ i2 = possibleSelection(i2);
+ setCurrent(i2);
+
+ TreeMapItem* changed = setTmpRangeSelection(i1, i2, selected);
+ if (!changed) return;
+
+ _selection = _tmpSelection;
+ if (_selectionMode == Single)
+ emit selectionChanged(i2);
+ emit selectionChanged();
+ redraw(changed);
+}
+
+TreeMapItem* TreeMapWidget::setTmpRangeSelection(TreeMapItem* i1,
+ TreeMapItem* i2,
+ bool selected)
+{
+ if ((i1 == 0) && (i2 == 0)) return 0;
+ if ((i1 == 0) || i1->isChildOf(i2)) return setTmpSelected(i2, selected);
+ if ((i2 == 0) || i2->isChildOf(i1)) return setTmpSelected(i1, selected);
+
+ TreeMapItem* changed = setTmpSelected(i1, selected);
+ TreeMapItem* changed2 = setTmpSelected(i2, selected);
+ if (changed2) changed = changed2->commonParent(changed);
+
+ TreeMapItem* commonParent = i1;
+ while (commonParent && !i2->isChildOf(commonParent)) {
+ i1 = commonParent;
+ commonParent = commonParent->parent();
+ }
+ if (!commonParent) return changed;
+ while (i2 && i2->parent() != commonParent)
+ i2 = i2->parent();
+ if (!i2) return changed;
+
+ TreeMapItemList* list = commonParent->children();
+ if (!list) return changed;
+
+ TreeMapItem* i = list->first();
+ bool between = false;
+ while (i) {
+ if (between) {
+ if (i==i1 || i==i2) break;
+ changed2 = setTmpSelected(i, selected);
+ if (changed2) changed = changed2->commonParent(changed);
+ }
+ else if (i==i1 || i==i2)
+ between = true;
+ i = list->next();
+ }
+
+ return changed;
+}
+
+void TreeMapWidget::contextMenuEvent( QContextMenuEvent* e )
+{
+ //kdDebug(90100) << "TreeMapWidget::contextMenuEvent" << endl;
+
+ if ( receivers( SIGNAL(contextMenuRequested(TreeMapItem*, const QPoint &)) ) )
+ e->accept();
+
+ if ( e->reason() == QContextMenuEvent::Keyboard ) {
+ QRect r = (_current) ? _current->itemRect() : _base->itemRect();
+ QPoint p = QPoint(r.left() + r.width()/2, r.top() + r.height()/2);
+ emit contextMenuRequested(_current, p);
+ }
+ else {
+ TreeMapItem* i = item(e->x(), e->y());
+ emit contextMenuRequested(i, e->pos());
+ }
+}
+
+
+void TreeMapWidget::mousePressEvent( QMouseEvent* e )
+{
+ //kdDebug(90100) << "TreeMapWidget::mousePressEvent" << endl;
+
+ _oldCurrent = _current;
+
+ TreeMapItem* i = item(e->x(), e->y());
+
+ _pressed = i;
+
+ _inShiftDrag = e->state() & ShiftButton;
+ _inControlDrag = e->state() & ControlButton;
+ _lastOver = _pressed;
+
+ TreeMapItem* changed = 0;
+ TreeMapItem* item = possibleSelection(_pressed);
+
+ switch(_selectionMode) {
+ case Single:
+ changed = setTmpSelected(item, true);
+ break;
+ case Multi:
+ changed = setTmpSelected(item, !isTmpSelected(item));
+ break;
+ case Extended:
+ if (_inControlDrag)
+ changed = setTmpSelected(item, !isTmpSelected(item));
+ else if (_inShiftDrag) {
+ TreeMapItem* sCurrent = possibleSelection(_current);
+ changed = setTmpRangeSelection(sCurrent, item,
+ !isTmpSelected(item));
+ }
+ else {
+ _selectionMode = Single;
+ changed = setTmpSelected(item, true);
+ _selectionMode = Extended;
+ }
+ break;
+ default:
+ break;
+ }
+
+ // item under mouse always selected on right button press
+ if (e->button() == RightButton) {
+ TreeMapItem* changed2 = setTmpSelected(item, true);
+ if (changed2) changed = changed2->commonParent(changed);
+ }
+
+ setCurrent(_pressed);
+
+ if (changed)
+ redraw(changed);
+
+ if (e->button() == RightButton) {
+
+ // emit selection change
+ if (! (_tmpSelection == _selection)) {
+ _selection = _tmpSelection;
+ if (_selectionMode == Single)
+ emit selectionChanged(_lastOver);
+ emit selectionChanged();
+ }
+ _pressed = 0;
+ _lastOver = 0;
+ emit rightButtonPressed(i, e->pos());
+ }
+}
+
+void TreeMapWidget::mouseMoveEvent( QMouseEvent* e )
+{
+ //kdDebug(90100) << "TreeMapWidget::mouseMoveEvent" << endl;
+
+ if (!_pressed) return;
+ TreeMapItem* over = item(e->x(), e->y());
+ if (_lastOver == over) return;
+
+ setCurrent(over);
+ if (over == 0) {
+ _lastOver = 0;
+ return;
+ }
+
+ TreeMapItem* changed = 0;
+ TreeMapItem* item = possibleSelection(over);
+
+ switch(_selectionMode) {
+ case Single:
+ changed = setTmpSelected(item, true);
+ break;
+ case Multi:
+ changed = setTmpSelected(item, !isTmpSelected(item));
+ break;
+ case Extended:
+ if (_inControlDrag)
+ changed = setTmpSelected(item, !isTmpSelected(item));
+ else {
+ TreeMapItem* sLast = possibleSelection(_lastOver);
+ changed = setTmpRangeSelection(sLast, item, true);
+ }
+ break;
+
+ default:
+ break;
+ }
+
+ _lastOver = over;
+
+ if (changed)
+ redraw(changed);
+}
+
+void TreeMapWidget::mouseReleaseEvent( QMouseEvent* )
+{
+ //kdDebug(90100) << "TreeMapWidget::mouseReleaseEvent" << endl;
+
+ if (!_pressed) return;
+
+ if (!_lastOver) {
+ // take back
+ setCurrent(_oldCurrent);
+ TreeMapItem* changed = diff(_tmpSelection, _selection).commonParent();
+ _tmpSelection = _selection;
+ if (changed)
+ redraw(changed);
+ }
+ else {
+ if (! (_tmpSelection == _selection)) {
+ _selection = _tmpSelection;
+ if (_selectionMode == Single)
+ emit selectionChanged(_lastOver);
+ emit selectionChanged();
+ }
+ if (!_inControlDrag && !_inShiftDrag && (_pressed == _lastOver))
+ emit clicked(_lastOver);
+ }
+
+ _pressed = 0;
+ _lastOver = 0;
+}
+
+
+void TreeMapWidget::mouseDoubleClickEvent( QMouseEvent* e )
+{
+ TreeMapItem* over = item(e->x(), e->y());
+
+ emit doubleClicked(over);
+}
+
+
+/* returns -1 if nothing visible found */
+int nextVisible(TreeMapItem* i)
+{
+ TreeMapItem* p = i->parent();
+ if (!p || p->itemRect().isEmpty()) return -1;
+
+ int idx = p->children()->findRef(i);
+ if (idx<0) return -1;
+
+ while (idx < (int)p->children()->count()-1) {
+ idx++;
+ QRect r = p->children()->at(idx)->itemRect();
+ if (r.width()>1 && r.height()>1)
+ return idx;
+ }
+ return -1;
+}
+
+/* returns -1 if nothing visible found */
+int prevVisible(TreeMapItem* i)
+{
+ TreeMapItem* p = i->parent();
+ if (!p || p->itemRect().isEmpty()) return -1;
+
+ int idx = p->children()->findRef(i);
+ if (idx<0) return -1;
+
+ while (idx > 0) {
+ idx--;
+ QRect r = p->children()->at(idx)->itemRect();
+ if (r.width()>1 && r.height()>1)
+ return idx;
+ }
+ return -1;
+}
+
+
+
+
+void TreeMapWidget::keyPressEvent( QKeyEvent* e )
+{
+ if (e->key() == Key_Escape && _pressed) {
+
+ // take back
+ if (_oldCurrent != _lastOver)
+ setCurrent(_oldCurrent);
+ if (! (_tmpSelection == _selection)) {
+ TreeMapItem* changed = diff(_tmpSelection, _selection).commonParent();
+ _tmpSelection = _selection;
+ if (changed)
+ redraw(changed);
+ }
+ _pressed = 0;
+ _lastOver = 0;
+ }
+
+ if ((e->key() == Key_Space) ||
+ (e->key() == Key_Return)) {
+
+ switch(_selectionMode) {
+ case NoSelection:
+ break;
+ case Single:
+ setSelected(_current, true);
+ break;
+ case Multi:
+ setSelected(_current, !isSelected(_current));
+ break;
+ case Extended:
+ if ((e->state() & ControlButton) || (e->state() & ShiftButton))
+ setSelected(_current, !isSelected(_current));
+ else {
+ _selectionMode = Single;
+ setSelected(_current, true);
+ _selectionMode = Extended;
+ }
+ }
+
+ if (_current && (e->key() == Key_Return))
+ emit returnPressed(_current);
+
+ return;
+ }
+
+ if (!_current) {
+ if (e->key() == Key_Down) {
+ setCurrent(_base, true);
+ }
+ return;
+ }
+
+ TreeMapItem* old = _current, *newItem;
+ TreeMapItem* p = _current->parent();
+
+ bool goBack;
+ if (_current->sorting(&goBack) == -1) {
+ // noSorting
+ goBack = false;
+ }
+
+
+ if ((e->key() == Key_Backspace) ||
+ (e->key() == Key_Up)) {
+ newItem = visibleItem(p);
+ setCurrent(newItem, true);
+ }
+ else if (e->key() == Key_Left) {
+ int newIdx = goBack ? nextVisible(_current) : prevVisible(_current);
+ if (p && newIdx>=0) {
+ p->setIndex(newIdx);
+ setCurrent(p->children()->at(newIdx), true);
+ }
+ }
+ else if (e->key() == Key_Right) {
+ int newIdx = goBack ? prevVisible(_current) : nextVisible(_current);
+ if (p && newIdx>=0) {
+ p->setIndex(newIdx);
+ setCurrent(p->children()->at(newIdx), true);
+ }
+ }
+ else if (e->key() == Key_Down) {
+ if (_current->children() && _current->children()->count()>0) {
+ int newIdx = _current->index();
+ if (newIdx<0)
+ newIdx = goBack ? (_current->children()->count()-1) : 0;
+ if (newIdx>=(int)_current->children()->count())
+ newIdx = _current->children()->count()-1;
+ newItem = visibleItem(_current->children()->at(newIdx));
+ setCurrent(newItem, true);
+ }
+ }
+
+ if (old == _current) return;
+ if (! (e->state() & ControlButton)) return;
+ if (! (e->state() & ShiftButton)) return;
+
+ switch(_selectionMode) {
+ case NoSelection:
+ break;
+ case Single:
+ setSelected(_current, true);
+ break;
+ case Multi:
+ setSelected(_current, !isSelected(_current));
+ break;
+ case Extended:
+ if (e->state() & ControlButton)
+ setSelected(_current, !isSelected(_current));
+ else
+ setSelected(_current, isSelected(old));
+ }
+}
+
+void TreeMapWidget::fontChange( const QFont& )
+{
+ redraw();
+}
+
+
+void TreeMapWidget::resizeEvent( QResizeEvent * )
+{
+ // this automatically redraws (as size is changed)
+ drawTreeMap();
+}
+
+void TreeMapWidget::paintEvent( QPaintEvent * )
+{
+ drawTreeMap();
+}
+
+void TreeMapWidget::showEvent( QShowEvent * )
+{
+ // refresh only if needed
+ drawTreeMap();
+}
+
+// Updates screen from shadow buffer,
+// but redraws before if needed
+void TreeMapWidget::drawTreeMap()
+{
+ // no need to draw if hidden
+ if (!isVisible()) return;
+
+ if (_pixmap.size() != size())
+ _needsRefresh = _base;
+
+ if (_needsRefresh) {
+
+ if (DEBUG_DRAWING)
+ kdDebug(90100) << "Redrawing " << _needsRefresh->path(0).join("/") << endl;
+
+ if (_needsRefresh == _base) {
+ // redraw whole widget
+ _pixmap = QPixmap(size());
+ _pixmap.fill(backgroundColor());
+ }
+ QPainter p(&_pixmap);
+ if (_needsRefresh == _base) {
+ p.setPen(black);
+ p.drawRect(QRect(2, 2, QWidget::width()-4, QWidget::height()-4));
+ _base->setItemRect(QRect(3, 3, QWidget::width()-6, QWidget::height()-6));
+ }
+ else {
+ // only subitem
+ if (!_needsRefresh->itemRect().isValid()) return;
+ }
+
+ // reset cached font object; it could have been changed
+ _font = font();
+ _fontHeight = fontMetrics().height();
+
+ drawItems(&p, _needsRefresh);
+ _needsRefresh = 0;
+ }
+
+ bitBlt( this, 0, 0, &_pixmap, 0, 0,
+ QWidget::width(), QWidget::height(), CopyROP, true);
+
+ if (hasFocus()) {
+ QPainter p(this);
+ style().drawPrimitive( QStyle::PE_FocusRect, &p,
+ QRect(0, 0, QWidget::width(), QWidget::height()),
+ colorGroup() );
+ }
+}
+
+
+
+void TreeMapWidget::redraw(TreeMapItem* i)
+{
+ if (!i) return;
+
+ if (!_needsRefresh)
+ _needsRefresh = i;
+ else {
+ if (!i->isChildOf(_needsRefresh))
+ _needsRefresh = _needsRefresh->commonParent(i);
+ }
+
+ if (isVisible()) {
+ // delayed drawing if we have multiple redraw requests
+ update();
+ }
+}
+
+void TreeMapWidget::drawItem(QPainter* p,
+ TreeMapItem* item)
+{
+ bool isSelected = false;
+ TreeMapItem* i;
+
+ if (_markNo>0) {
+ for(i = item;i;i=i->parent())
+ if (i->isMarked(_markNo)) break;
+
+ isSelected = (i!=0);
+ }
+ else {
+ for (i=_tmpSelection.first();i;i=_tmpSelection.next())
+ if (item->isChildOf(i)) break;
+
+ isSelected = (i!=0);
+ }
+
+ bool isCurrent = _current && item->isChildOf(_current);
+
+ RectDrawing d(item->itemRect());
+ item->setSelected(isSelected);
+ item->setCurrent(isCurrent);
+ item->setShaded(_shading);
+ d.drawBack(p, item);
+}
+
+
+bool TreeMapWidget::horizontal(TreeMapItem* i, const QRect& r)
+{
+ switch(i->splitMode()) {
+ case TreeMapItem::HAlternate:
+ return (i->depth()%2)==1;
+ case TreeMapItem::VAlternate:
+ return (i->depth()%2)==0;
+ case TreeMapItem::Horizontal:
+ return true;
+ case TreeMapItem::Vertical:
+ return false;
+ default:
+ return r.width() > r.height();
+ }
+ return false;
+}
+
+
+/**
+ * Draw TreeMapItems recursive, starting from item
+ */
+void TreeMapWidget::drawItems(QPainter* p,
+ TreeMapItem* item)
+{
+ if (DEBUG_DRAWING)
+ kdDebug(90100) << "+drawItems(" << item->path(0).join("/") << ", "
+ << item->itemRect().x() << "/" << item->itemRect().y()
+ << "-" << item->itemRect().width() << "x"
+ << item->itemRect().height() << "), Val " << item->value()
+ << ", Sum " << item->sum() << endl;
+
+ drawItem(p, item);
+ item->clearFreeRects();
+
+ QRect origRect = item->itemRect();
+ int bw = item->borderWidth();
+ QRect r = QRect(origRect.x()+bw, origRect.y()+bw,
+ origRect.width()-2*bw, origRect.height()-2*bw);
+
+ TreeMapItemList* list = item->children();
+ TreeMapItem* i;
+
+ bool stopDrawing = false;
+
+ // only subdivide if there are children
+ if (!list || list->count()==0)
+ stopDrawing = true;
+
+ // only subdivide if there is enough space
+ if (!stopDrawing && (r.width()<=0 || r.height()<=0))
+ stopDrawing = true;
+
+ // stop drawing if maximum depth is reached
+ if (!stopDrawing &&
+ (_maxDrawingDepth>=0 && item->depth()>=_maxDrawingDepth))
+ stopDrawing = true;
+
+ // stop drawing if stopAtText is reached
+ if (!stopDrawing)
+ for (int no=0;no<(int)_attr.size();no++) {
+ QString stopAt = fieldStop(no);
+ if (!stopAt.isEmpty() && (item->text(no) == stopAt)) {
+ stopDrawing = true;
+ break;
+ }
+ }
+
+ // area size is checked later...
+#if 0
+ // stop drawing if minimal area size is reached
+ if (!stopDrawing &&
+ (_minimalArea > 0) &&
+ (r.width() * r.height() < _minimalArea)) stopDrawing = true;
+#endif
+
+ if (stopDrawing) {
+ if (list) {
+ // invalidate rects
+ for (i=list->first();i;i=list->next())
+ i->clearItemRect();
+ }
+ // tooltip apears on whole item rect
+ item->addFreeRect(item->itemRect());
+
+ // if we have space for text...
+ if ((r.height() < _fontHeight) || (r.width() < _fontHeight)) return;
+
+ RectDrawing d(r);
+ item->setRotated(_allowRotation && (r.height() > r.width()));
+ for (int no=0;no<(int)_attr.size();no++) {
+ if (!fieldVisible(no)) continue;
+ d.drawField(p, no, item);
+ }
+ r = d.remainingRect(item);
+
+ if (DEBUG_DRAWING)
+ kdDebug(90100) << "-drawItems(" << item->path(0).join("/") << ")" << endl;
+ return;
+ }
+
+ double user_sum, child_sum, self;
+
+ // user supplied sum
+ user_sum = item->sum();
+
+ // own sum
+ child_sum = 0;
+ for (i=list->first();i;i=list->next()) {
+ child_sum += i->value();
+ if (DEBUG_DRAWING)
+ kdDebug(90100) << " child: " << i->text(0) << ", value "
+ << i->value() << endl;
+ }
+
+ QRect orig = r;
+
+ // if we have space for text...
+ if ((r.height() >= _fontHeight) && (r.width() >= _fontHeight)) {
+
+ RectDrawing d(r);
+ item->setRotated(_allowRotation && (r.height() > r.width()));
+ for (int no=0;no<(int)_attr.size();no++) {
+ if (!fieldVisible(no)) continue;
+ if (!fieldForced(no)) continue;
+ d.drawField(p, no, item);
+ }
+ r = d.remainingRect(item);
+ }
+
+ if (orig.x() == r.x()) {
+ // Strings on top
+ item->addFreeRect(QRect(orig.x(), orig.y(),
+ orig.width(), orig.height()-r.height()));
+ }
+ else {
+ // Strings on the left
+ item->addFreeRect(QRect(orig.x(), orig.y(),
+ orig.width()-r.width(), orig.height()));
+ }
+
+ if (user_sum == 0) {
+ // user didn't supply any sum
+ user_sum = child_sum;
+ self = 0;
+ }
+ else {
+ self = user_sum - child_sum;
+
+ if (user_sum < child_sum) {
+ //kdDebug(90100) << "TreeMWidget " <<
+ // item->path() << ": User sum " << user_sum << " < Child Items sum " << child_sum << endl;
+
+ // invalid user supplied sum: ignore and use calculate sum
+ user_sum = child_sum;
+ self = 0.0;
+ }
+ else {
+ // Try to put the border waste in self
+ // percent of wasted space on border...
+ float borderArea = origRect.width() * origRect.height();
+ borderArea = (borderArea - r.width()*r.height())/borderArea;
+ unsigned borderValue = (unsigned)(borderArea * user_sum);
+
+ if (borderValue > self) {
+ if (_skipIncorrectBorder) {
+ r = origRect;
+ // should add my self to nested self and set my self =0
+ }
+ else
+ self = 0.0;
+ }
+ else
+ self -= borderValue;
+
+ user_sum = child_sum + self;
+ }
+ }
+
+ bool rotate = (_allowRotation && (r.height() > r.width()));
+ int self_length = (int)( ((rotate) ? r.width() : r.height()) *
+ self / user_sum + .5);
+ if (self_length > 0) {
+ // take space for self cost
+ QRect sr = r;
+ if (rotate) {
+ sr.setWidth( self_length );
+ r.setRect(r.x()+sr.width(), r.y(), r.width()-sr.width(), r.height());
+ }
+ else {
+ sr.setHeight( self_length );
+ r.setRect(r.x(), r.y()+sr.height(), r.width(), r.height()-sr.height());
+ }
+
+ // set selfRect (not occupied by children) for tooltip
+ item->addFreeRect(sr);
+
+ if (0) kdDebug(90100) << "Item " << item->path(0).join("/") << ": SelfR "
+ << sr.x() << "/" << sr.y() << "-" << sr.width()
+ << "/" << sr.height() << ", self " << self << "/"
+ << user_sum << endl;
+
+ if ((sr.height() >= _fontHeight) && (sr.width() >= _fontHeight)) {
+
+ RectDrawing d(sr);
+ item->setRotated(_allowRotation && (r.height() > r.width()));
+ for (int no=0;no<(int)_attr.size();no++) {
+ if (!fieldVisible(no)) continue;
+ if (fieldForced(no)) continue;
+ d.drawField(p, no, item);
+ }
+ }
+
+ user_sum -= self;
+ }
+
+ bool goBack;
+ if (item->sorting(&goBack) == -1) {
+ // noSorting
+ goBack = false;
+ }
+
+ TreeMapItemListIterator it(*list);
+ if (goBack) it.toLast();
+
+ if (item->splitMode() == TreeMapItem::Columns) {
+ int len = list->count();
+ bool drawDetails = true;
+
+ while (len>0 && user_sum>0) {
+ TreeMapItemListIterator first = it;
+ double valSum = 0;
+ int lenLeft = len;
+ int columns = (int)(sqrt((double)len * r.width()/r.height())+.5);
+ if (columns==0) columns = 1; //should never be needed
+
+ while (lenLeft>0 && ((double)valSum*(len-lenLeft) <
+ (double)len*user_sum/columns/columns)) {
+ valSum += it.current()->value();
+ if (goBack) --it; else ++it;
+ lenLeft--;
+ }
+
+ // we always split horizontally
+ int nextPos = (int)((double)r.width() * valSum / user_sum);
+ QRect firstRect = QRect(r.x(), r.y(), nextPos, r.height());
+
+ if (nextPos < _visibleWidth) {
+ if (item->sorting(0) == -1) {
+ // fill current rect with hash pattern
+ drawFill(item, p, firstRect);
+ }
+ else {
+ // fill rest with hash pattern
+ drawFill(item, p, r, first, len, goBack);
+ break;
+ }
+ }
+ else {
+ drawDetails = drawItemArray(p, item, firstRect,
+ valSum, first, len-lenLeft, goBack);
+ }
+ r.setRect(r.x()+nextPos, r.y(), r.width()-nextPos, r.height());
+ user_sum -= valSum;
+ len = lenLeft;
+
+ if (!drawDetails) {
+ if (item->sorting(0) == -1)
+ drawDetails = true;
+ else {
+ drawFill(item, p, r, it, len, goBack);
+ break;
+ }
+ }
+ }
+ }
+ else if (item->splitMode() == TreeMapItem::Rows) {
+ int len = list->count();
+ bool drawDetails = true;
+
+ while (len>0 && user_sum>0) {
+ TreeMapItemListIterator first = it;
+ double valSum = 0;
+ int lenLeft = len;
+ int rows = (int)(sqrt((double)len * r.height()/r.width())+.5);
+ if (rows==0) rows = 1; //should never be needed
+
+ while (lenLeft>0 && ((double)valSum*(len-lenLeft) <
+ (double)len*user_sum/rows/rows)) {
+ valSum += it.current()->value();
+ if (goBack) --it; else ++it;
+ lenLeft--;
+ }
+
+ // we always split horizontally
+ int nextPos = (int)((double)r.height() * valSum / user_sum);
+ QRect firstRect = QRect(r.x(), r.y(), r.width(), nextPos);
+
+ if (nextPos < _visibleWidth) {
+ if (item->sorting(0) == -1) {
+ drawFill(item, p, firstRect);
+ }
+ else {
+ drawFill(item, p, r, first, len, goBack);
+ break;
+ }
+ }
+ else {
+ drawDetails = drawItemArray(p, item, firstRect,
+ valSum, first, len-lenLeft, goBack);
+ }
+ r.setRect(r.x(), r.y()+nextPos, r.width(), r.height()-nextPos);
+ user_sum -= valSum;
+ len = lenLeft;
+
+ if (!drawDetails) {
+ if (item->sorting(0) == -1)
+ drawDetails = true;
+ else {
+ drawFill(item, p, r, it, len, goBack);
+ break;
+ }
+ }
+ }
+ }
+ else
+ drawItemArray(p, item, r, user_sum, it, list->count(), goBack);
+
+ if (DEBUG_DRAWING)
+ kdDebug(90100) << "-drawItems(" << item->path(0).join("/") << ")" << endl;
+}
+
+// fills area with a pattern if to small to draw children
+void TreeMapWidget::drawFill(TreeMapItem* i, QPainter* p, QRect& r)
+{
+ p->setBrush(Qt::Dense4Pattern);
+ p->setPen(Qt::NoPen);
+ p->drawRect(r);
+ i->addFreeRect(r);
+}
+
+// fills area with a pattern if to small to draw children
+void TreeMapWidget::drawFill(TreeMapItem* i, QPainter* p, QRect& r,
+ TreeMapItemListIterator it, int len, bool goBack)
+{
+ if (DEBUG_DRAWING)
+ kdDebug(90100) << " +drawFill(" << r.x() << "/" << r.y()
+ << "-" << r.width() << "x" << r.height()
+ << ", len " << len << ")" << endl;
+
+ p->setBrush(Qt::Dense4Pattern);
+ p->setPen(Qt::NoPen);
+ p->drawRect(r);
+ i->addFreeRect(r);
+
+ // reset rects
+ while (len>0 && it.current()) {
+
+ if (DEBUG_DRAWING)
+ kdDebug(90100) << " Reset Rect " << (*it)->path(0).join("/") << endl;
+
+ (*it)->clearItemRect();
+ if (goBack) --it; else ++it;
+ len--;
+ }
+ if (DEBUG_DRAWING)
+ kdDebug(90100) << " -drawFill(" << r.x() << "/" << r.y()
+ << "-" << r.width() << "x" << r.height()
+ << ", len " << len << ")" << endl;
+}
+
+// returns false if rect gets to small
+bool TreeMapWidget::drawItemArray(QPainter* p, TreeMapItem* item,
+ QRect& r, double user_sum,
+ TreeMapItemListIterator it, int len,
+ bool goBack)
+{
+ if (user_sum == 0) return false;
+
+ static bool b2t = true;
+
+ // stop recursive bisection for small rectangles
+ if (((r.height() < _visibleWidth) &&
+ (r.width() < _visibleWidth)) ||
+ ((_minimalArea > 0) &&
+ (r.width() * r.height() < _minimalArea))) {
+
+ drawFill(item, p, r, it, len, goBack);
+ return false;
+ }
+
+ if (DEBUG_DRAWING)
+ kdDebug(90100) << " +drawItemArray(" << item->path(0).join("/")
+ << ", " << r.x() << "/" << r.y() << "-" << r.width()
+ << "x" << r.height() << ")" << endl;
+
+ if (len>2 && (item->splitMode() == TreeMapItem::Bisection)) {
+
+ TreeMapItemListIterator first = it;
+ double valSum = 0;
+ int lenLeft = len;
+ //while (lenLeft>0 && valSum<user_sum/2) {
+ while (lenLeft>len/2) {
+ valSum += it.current()->value();
+ if (goBack) --it; else ++it;
+ lenLeft--;
+ }
+
+ // draw first half...
+ bool drawOn;
+
+ if (r.width() > r.height()) {
+ int halfPos = (int)((double)r.width() * valSum / user_sum);
+ QRect firstRect = QRect(r.x(), r.y(), halfPos, r.height());
+ drawOn = drawItemArray(p, item, firstRect,
+ valSum, first, len-lenLeft, goBack);
+ r.setRect(r.x()+halfPos, r.y(), r.width()-halfPos, r.height());
+ }
+ else {
+ int halfPos = (int)((double)r.height() * valSum / user_sum);
+ QRect firstRect = QRect(r.x(), r.y(), r.width(), halfPos);
+ drawOn = drawItemArray(p, item, firstRect,
+ valSum, first, len-lenLeft, goBack);
+ r.setRect(r.x(), r.y()+halfPos, r.width(), r.height()-halfPos);
+ }
+
+ // if no sorting, don't stop drawing
+ if (item->sorting(0) == -1) drawOn = true;
+
+ // second half
+ if (drawOn)
+ drawOn = drawItemArray(p, item, r, user_sum - valSum,
+ it, lenLeft, goBack);
+ else {
+ drawFill(item, p, r, it, len, goBack);
+ }
+
+ if (DEBUG_DRAWING)
+ kdDebug(90100) << " -drawItemArray(" << item->path(0).join("/")
+ << ")" << endl;
+
+ return drawOn;
+ }
+
+ bool hor = horizontal(item,r);
+
+ TreeMapItem* i;
+ while (len>0) {
+ i = it.current();
+ if (user_sum <= 0) {
+
+ if (DEBUG_DRAWING)
+ kdDebug(90100) << "drawItemArray: Reset " << i->path(0).join("/") << endl;
+
+ i->clearItemRect();
+ if (goBack) --it; else ++it;
+ len--;
+ continue;
+ }
+
+ // stop drawing for small rectangles
+ if (((r.height() < _visibleWidth) &&
+ (r.width() < _visibleWidth)) ||
+ ((_minimalArea > 0) &&
+ (r.width() * r.height() < _minimalArea))) {
+
+ drawFill(item, p, r, it, len, goBack);
+ if (DEBUG_DRAWING)
+ kdDebug(90100) << " -drawItemArray(" << item->path(0).join("/")
+ << "): Stop" << endl;
+ return false;
+ }
+
+ if (i->splitMode() == TreeMapItem::AlwaysBest)
+ hor = r.width() > r.height();
+
+ int lastPos = hor ? r.width() : r.height();
+ double val = i->value();
+ int nextPos = (user_sum <= 0.0) ? 0: (int)(lastPos * val / user_sum +.5);
+ if (nextPos>lastPos) nextPos = lastPos;
+
+ if ((item->sorting(0) != -1) && (nextPos < _visibleWidth)) {
+ drawFill(item, p, r, it, len, goBack);
+ if (DEBUG_DRAWING)
+ kdDebug(90100) << " -drawItemArray(" << item->path(0).join("/")
+ << "): Stop" << endl;
+ return false;
+ }
+
+ QRect currRect = r;
+
+ if (hor)
+ currRect.setWidth(nextPos);
+ else {
+ if (b2t)
+ currRect.setRect(r.x(), r.bottom()-nextPos+1, r.width(), nextPos);
+ else
+ currRect.setHeight(nextPos);
+ }
+
+ // don't draw very small rectangles:
+ if (nextPos >= _visibleWidth) {
+ i->setItemRect(currRect);
+ drawItems(p, i);
+ }
+ else {
+ i->clearItemRect();
+ drawFill(item, p, currRect);
+ }
+
+ // draw Separator
+ if (_drawSeparators && (nextPos<lastPos)) {
+ p->setPen(black);
+ if (hor) {
+ if (r.top()<=r.bottom())
+ p->drawLine(r.x() + nextPos, r.top(), r.x() + nextPos, r.bottom());
+ }
+ else {
+ if (r.left()<=r.right())
+ p->drawLine(r.left(), r.y() + nextPos, r.right(), r.y() + nextPos);
+ }
+ nextPos++;
+ }
+
+ if (hor)
+ r.setRect(r.x() + nextPos, r.y(), lastPos-nextPos, r.height());
+ else {
+ if (b2t)
+ r.setRect(r.x(), r.y(), r.width(), lastPos-nextPos);
+ else
+ r.setRect(r.x(), r.y() + nextPos, r.width(), lastPos-nextPos);
+ }
+
+ user_sum -= val;
+ if (goBack) --it; else ++it;
+ len--;
+ }
+
+ if (DEBUG_DRAWING)
+ kdDebug(90100) << " -drawItemArray(" << item->path(0).join("/")
+ << "): Continue" << endl;
+
+ return true;
+}
+
+
+/*----------------------------------------------------------------
+ * Popup menus for option setting
+ */
+
+void TreeMapWidget::splitActivated(int id)
+{
+ if (id == _splitID) setSplitMode(TreeMapItem::Bisection);
+ else if (id == _splitID+1) setSplitMode(TreeMapItem::Columns);
+ else if (id == _splitID+2) setSplitMode(TreeMapItem::Rows);
+ else if (id == _splitID+3) setSplitMode(TreeMapItem::AlwaysBest);
+ else if (id == _splitID+4) setSplitMode(TreeMapItem::Best);
+ else if (id == _splitID+5) setSplitMode(TreeMapItem::VAlternate);
+ else if (id == _splitID+6) setSplitMode(TreeMapItem::HAlternate);
+ else if (id == _splitID+7) setSplitMode(TreeMapItem::Horizontal);
+ else if (id == _splitID+8) setSplitMode(TreeMapItem::Vertical);
+}
+
+
+void TreeMapWidget::addSplitDirectionItems(QPopupMenu* popup, int id)
+{
+ _splitID = id;
+ popup->setCheckable(true);
+
+ connect(popup, SIGNAL(activated(int)),
+ this, SLOT(splitActivated(int)));
+
+ popup->insertItem(i18n("Recursive Bisection"), id);
+ popup->insertItem(i18n("Columns"), id+1);
+ popup->insertItem(i18n("Rows"), id+2);
+ popup->insertItem(i18n("Always Best"), id+3);
+ popup->insertItem(i18n("Best"), id+4);
+ popup->insertItem(i18n("Alternate (V)"), id+5);
+ popup->insertItem(i18n("Alternate (H)"), id+6);
+ popup->insertItem(i18n("Horizontal"), id+7);
+ popup->insertItem(i18n("Vertical"), id+8);
+
+ switch(splitMode()) {
+ case TreeMapItem::Bisection: popup->setItemChecked(id,true); break;
+ case TreeMapItem::Columns: popup->setItemChecked(id+1,true); break;
+ case TreeMapItem::Rows: popup->setItemChecked(id+2,true); break;
+ case TreeMapItem::AlwaysBest: popup->setItemChecked(id+3,true); break;
+ case TreeMapItem::Best: popup->setItemChecked(id+4,true); break;
+ case TreeMapItem::VAlternate: popup->setItemChecked(id+5,true); break;
+ case TreeMapItem::HAlternate: popup->setItemChecked(id+6,true); break;
+ case TreeMapItem::Horizontal: popup->setItemChecked(id+7,true); break;
+ case TreeMapItem::Vertical: popup->setItemChecked(id+8,true); break;
+ default: break;
+ }
+}
+
+void TreeMapWidget::visualizationActivated(int id)
+{
+ if (id == _visID+2) setSkipIncorrectBorder(!skipIncorrectBorder());
+ else if (id == _visID+3) setBorderWidth(0);
+ else if (id == _visID+4) setBorderWidth(1);
+ else if (id == _visID+5) setBorderWidth(2);
+ else if (id == _visID+6) setBorderWidth(3);
+ else if (id == _visID+10) setAllowRotation(!allowRotation());
+ else if (id == _visID+11) setShadingEnabled(!isShadingEnabled());
+ else if (id<_visID+19 || id>_visID+100) return;
+
+ id -= 20+_visID;
+ int f = id/10;
+ if ((id%10) == 1) setFieldVisible(f, !fieldVisible(f));
+ else if ((id%10) == 2) setFieldForced(f, !fieldForced(f));
+ else if ((id%10) == 3) setFieldPosition(f, DrawParams::TopLeft);
+ else if ((id%10) == 4) setFieldPosition(f, DrawParams::TopCenter);
+ else if ((id%10) == 5) setFieldPosition(f, DrawParams::TopRight);
+ else if ((id%10) == 6) setFieldPosition(f, DrawParams::BottomLeft);
+ else if ((id%10) == 7) setFieldPosition(f, DrawParams::BottomCenter);
+ else if ((id%10) == 8) setFieldPosition(f, DrawParams::BottomRight);
+}
+
+void TreeMapWidget::addVisualizationItems(QPopupMenu* popup, int id)
+{
+ _visID = id;
+
+ popup->setCheckable(true);
+
+ QPopupMenu* bpopup = new QPopupMenu();
+ bpopup->setCheckable(true);
+
+ connect(popup, SIGNAL(activated(int)),
+ this, SLOT(visualizationActivated(int)));
+ connect(bpopup, SIGNAL(activated(int)),
+ this, SLOT(visualizationActivated(int)));
+
+ QPopupMenu* spopup = new QPopupMenu();
+ addSplitDirectionItems(spopup, id+100);
+ popup->insertItem(i18n("Nesting"), spopup, id);
+
+ popup->insertItem(i18n("Border"), bpopup, id+1);
+ bpopup->insertItem(i18n("Correct Borders Only"), id+2);
+ bpopup->insertSeparator();
+ bpopup->insertItem(i18n("Width %1").arg(0), id+3);
+ bpopup->insertItem(i18n("Width %1").arg(1), id+4);
+ bpopup->insertItem(i18n("Width %1").arg(2), id+5);
+ bpopup->insertItem(i18n("Width %1").arg(3), id+6);
+ bpopup->setItemChecked(id+2, skipIncorrectBorder());
+ bpopup->setItemChecked(id+3, borderWidth()==0);
+ bpopup->setItemChecked(id+4, borderWidth()==1);
+ bpopup->setItemChecked(id+5, borderWidth()==2);
+ bpopup->setItemChecked(id+6, borderWidth()==3);
+
+ popup->insertItem(i18n("Allow Rotation"), id+10);
+ popup->setItemChecked(id+10,allowRotation());
+ popup->insertItem(i18n("Shading"), id+11);
+ popup->setItemChecked(id+11,isShadingEnabled());
+
+ if (_attr.size() ==0) return;
+
+ popup->insertSeparator();
+ int f;
+ QPopupMenu* tpopup;
+ id += 20;
+ for (f=0;f<(int)_attr.size();f++, id+=10) {
+ tpopup = new QPopupMenu();
+ tpopup->setCheckable(true);
+ popup->insertItem(_attr[f].type, tpopup, id);
+ tpopup->insertItem(i18n("Visible"), id+1);
+ tpopup->insertItem(i18n("Take Space From Children"), id+2);
+ tpopup->insertSeparator();
+ tpopup->insertItem(i18n("Top Left"), id+3);
+ tpopup->insertItem(i18n("Top Center"), id+4);
+ tpopup->insertItem(i18n("Top Right"), id+5);
+ tpopup->insertItem(i18n("Bottom Left"), id+6);
+ tpopup->insertItem(i18n("Bottom Center"), id+7);
+ tpopup->insertItem(i18n("Bottom Right"), id+8);
+
+ tpopup->setItemChecked(id+1,_attr[f].visible);
+ tpopup->setItemEnabled(id+2,_attr[f].visible);
+ tpopup->setItemEnabled(id+3,_attr[f].visible);
+ tpopup->setItemEnabled(id+4,_attr[f].visible);
+ tpopup->setItemEnabled(id+5,_attr[f].visible);
+ tpopup->setItemEnabled(id+6,_attr[f].visible);
+ tpopup->setItemEnabled(id+7,_attr[f].visible);
+ tpopup->setItemEnabled(id+8,_attr[f].visible);
+ tpopup->setItemChecked(id+2,_attr[f].forced);
+ tpopup->setItemChecked(id+3,_attr[f].pos == DrawParams::TopLeft);
+ tpopup->setItemChecked(id+4,_attr[f].pos == DrawParams::TopCenter);
+ tpopup->setItemChecked(id+5,_attr[f].pos == DrawParams::TopRight);
+ tpopup->setItemChecked(id+6,_attr[f].pos == DrawParams::BottomLeft);
+ tpopup->setItemChecked(id+7,_attr[f].pos == DrawParams::BottomCenter);
+ tpopup->setItemChecked(id+8,_attr[f].pos == DrawParams::BottomRight);
+
+ connect(tpopup, SIGNAL(activated(int)),
+ this, SLOT(visualizationActivated(int)));
+ }
+}
+
+void TreeMapWidget::selectionActivated(int id)
+{
+ TreeMapItem* i = _menuItem;
+ id -= _selectionID;
+ while (id>0 && i) {
+ i=i->parent();
+ id--;
+ }
+ if (i)
+ setSelected(i, true);
+}
+
+void TreeMapWidget::addSelectionItems(QPopupMenu* popup,
+ int id, TreeMapItem* i)
+{
+ if (!i) return;
+
+ _selectionID = id;
+ _menuItem = i;
+
+ connect(popup, SIGNAL(activated(int)),
+ this, SLOT(selectionActivated(int)));
+
+ while (i) {
+ QString name = i->text(0);
+ if (name.isEmpty()) break;
+ popup->insertItem(i->text(0), id++);
+ i = i->parent();
+ }
+}
+
+void TreeMapWidget::fieldStopActivated(int id)
+{
+ if (id == _fieldStopID) setFieldStop(0, QString::null);
+ else {
+ TreeMapItem* i = _menuItem;
+ id -= _fieldStopID+1;
+ while (id>0 && i) {
+ i=i->parent();
+ id--;
+ }
+ if (i)
+ setFieldStop(0, i->text(0));
+ }
+}
+
+void TreeMapWidget::addFieldStopItems(QPopupMenu* popup,
+ int id, TreeMapItem* i)
+{
+ _fieldStopID = id;
+
+ connect(popup, SIGNAL(activated(int)),
+ this, SLOT(fieldStopActivated(int)));
+
+ popup->insertItem(i18n("No %1 Limit").arg(fieldType(0)), id);
+ popup->setItemChecked(id, fieldStop(0).isEmpty());
+ _menuItem = i;
+ bool foundFieldStop = false;
+ if (i) {
+ popup->insertSeparator();
+
+ while (i) {
+ id++;
+ QString name = i->text(0);
+ if (name.isEmpty()) break;
+ popup->insertItem(i->text(0), id);
+ if (fieldStop(0) == i->text(0)) {
+ popup->setItemChecked(id, true);
+ foundFieldStop = true;
+ }
+ i = i->parent();
+ }
+ }
+
+ if (!foundFieldStop && !fieldStop(0).isEmpty()) {
+ popup->insertSeparator();
+ popup->insertItem(fieldStop(0), id+1);
+ popup->setItemChecked(id+1, true);
+ }
+}
+
+void TreeMapWidget::areaStopActivated(int id)
+{
+ if (id == _areaStopID) setMinimalArea(-1);
+ else if (id == _areaStopID+1) {
+ int area = _menuItem ? (_menuItem->width() * _menuItem->height()) : -1;
+ setMinimalArea(area);
+ }
+ else if (id == _areaStopID+2) setMinimalArea(100);
+ else if (id == _areaStopID+3) setMinimalArea(400);
+ else if (id == _areaStopID+4) setMinimalArea(1000);
+ else if (id == _areaStopID+5) setMinimalArea(minimalArea()*2);
+ else if (id == _areaStopID+6) setMinimalArea(minimalArea()/2);
+}
+
+void TreeMapWidget::addAreaStopItems(QPopupMenu* popup,
+ int id, TreeMapItem* i)
+{
+ _areaStopID = id;
+ _menuItem = i;
+
+ connect(popup, SIGNAL(activated(int)),
+ this, SLOT(areaStopActivated(int)));
+
+ bool foundArea = false;
+
+ popup->insertItem(i18n("No Area Limit"), id);
+ popup->setItemChecked(id, minimalArea() == -1);
+
+ if (i) {
+ int area = i->width() * i->height();
+ popup->insertSeparator();
+ popup->insertItem(i18n("Area of '%1' (%2)")
+ .arg(i->text(0)).arg(area), id+1);
+ if (area == minimalArea()) {
+ popup->setItemChecked(id+1, true);
+ foundArea = true;
+ }
+ }
+
+ popup->insertSeparator();
+ int area = 100, count;
+ for (count=0;count<3;count++) {
+ popup->insertItem(i18n("1 Pixel", "%n Pixels", area), id+2+count);
+ if (area == minimalArea()) {
+ popup->setItemChecked(id+2+count, true);
+ foundArea = true;
+ }
+ area = (area==100) ? 400 : (area==400) ? 1000 : 4000;
+ }
+
+ if (minimalArea()>0) {
+ popup->insertSeparator();
+ if (!foundArea) {
+ popup->insertItem(i18n("1 Pixel", "%n Pixels", minimalArea()), id+10);
+ popup->setItemChecked(id+10, true);
+ }
+
+ popup->insertItem(i18n("Double Area Limit (to %1)")
+ .arg(minimalArea()*2), id+5);
+ popup->insertItem(i18n("Halve Area Limit (to %1)")
+ .arg(minimalArea()/2), id+6);
+ }
+}
+
+
+void TreeMapWidget::depthStopActivated(int id)
+{
+ if (id == _depthStopID) setMaxDrawingDepth(-1);
+ else if (id == _depthStopID+1) {
+ int d = _menuItem ? _menuItem->depth() : -1;
+ setMaxDrawingDepth(d);
+ }
+ else if (id == _depthStopID+2) setMaxDrawingDepth(maxDrawingDepth()-1);
+ else if (id == _depthStopID+3) setMaxDrawingDepth(maxDrawingDepth()+1);
+ else if (id == _depthStopID+4) setMaxDrawingDepth(2);
+ else if (id == _depthStopID+5) setMaxDrawingDepth(4);
+ else if (id == _depthStopID+6) setMaxDrawingDepth(6);
+}
+
+void TreeMapWidget::addDepthStopItems(QPopupMenu* popup,
+ int id, TreeMapItem* i)
+{
+ _depthStopID = id;
+ _menuItem = i;
+
+ connect(popup, SIGNAL(activated(int)),
+ this, SLOT(depthStopActivated(int)));
+
+ bool foundDepth = false;
+
+ popup->insertItem(i18n("No Depth Limit"), id);
+ popup->setItemChecked(id, maxDrawingDepth() == -1);
+
+ if (i) {
+ int d = i->depth();
+ popup->insertSeparator();
+ popup->insertItem(i18n("Depth of '%1' (%2)")
+ .arg(i->text(0)).arg(d), id+1);
+ if (d == maxDrawingDepth()) {
+ popup->setItemChecked(id+1, true);
+ foundDepth = true;
+ }
+ }
+
+ popup->insertSeparator();
+ int depth = 2, count;
+ for (count=0;count<3;count++) {
+ popup->insertItem(i18n("Depth %1").arg(depth), id+4+count);
+ if (depth == maxDrawingDepth()) {
+ popup->setItemChecked(id+4+count, true);
+ foundDepth = true;
+ }
+ depth = (depth==2) ? 4 : 6;
+ }
+
+ if (maxDrawingDepth()>1) {
+ popup->insertSeparator();
+ if (!foundDepth) {
+ popup->insertItem(i18n("Depth %1").arg(maxDrawingDepth()), id+10);
+ popup->setItemChecked(id+10, true);
+ }
+
+ popup->insertItem(i18n("Decrement (to %1)")
+ .arg(maxDrawingDepth()-1), id+2);
+ popup->insertItem(i18n("Increment (to %1)")
+ .arg(maxDrawingDepth()+1), id+3);
+ }
+}
+
+
+
+/*----------------------------------------------------------------
+ * Option saving/restoring
+ */
+
+void TreeMapWidget::saveOptions(KConfigGroup* config, QString prefix)
+{
+ config->writeEntry(prefix+"Nesting", splitModeString());
+ config->writeEntry(prefix+"AllowRotation", allowRotation());
+ config->writeEntry(prefix+"ShadingEnabled", isShadingEnabled());
+ config->writeEntry(prefix+"OnlyCorrectBorder", skipIncorrectBorder());
+ config->writeEntry(prefix+"BorderWidth", borderWidth());
+ config->writeEntry(prefix+"MaxDepth", maxDrawingDepth());
+ config->writeEntry(prefix+"MinimalArea", minimalArea());
+
+ int f, fCount = _attr.size();
+ config->writeEntry(prefix+"FieldCount", fCount);
+ for (f=0;f<fCount;f++) {
+ config->writeEntry(QString(prefix+"FieldVisible%1").arg(f),
+ _attr[f].visible);
+ config->writeEntry(QString(prefix+"FieldForced%1").arg(f),
+ _attr[f].forced);
+ config->writeEntry(QString(prefix+"FieldStop%1").arg(f),
+ _attr[f].stop);
+ config->writeEntry(QString(prefix+"FieldPosition%1").arg(f),
+ fieldPositionString(f));
+ }
+}
+
+
+void TreeMapWidget::restoreOptions(KConfigGroup* config, QString prefix)
+{
+ bool enabled;
+ int num;
+ QString str;
+
+ str = config->readEntry(prefix+"Nesting");
+ if (!str.isEmpty()) setSplitMode(str);
+
+ if (config->hasKey(prefix+"AllowRotation")) {
+ enabled = config->readBoolEntry(prefix+"AllowRotation", true);
+ setAllowRotation(enabled);
+ }
+
+ if (config->hasKey(prefix+"ShadingEnabled")) {
+ enabled = config->readBoolEntry(prefix+"ShadingEnabled", true);
+ setShadingEnabled(enabled);
+ }
+
+ if (config->hasKey(prefix+"OnlyCorrectBorder")) {
+ enabled = config->readBoolEntry(prefix+"OnlyCorrectBorder", false);
+ setSkipIncorrectBorder(enabled);
+ }
+
+ num = config->readNumEntry(prefix+"BorderWidth", -2);
+ if (num!=-2) setBorderWidth(num);
+
+ num = config->readNumEntry(prefix+"MaxDepth", -2);
+ if (num!=-2) setMaxDrawingDepth(num);
+
+ num = config->readNumEntry(prefix+"MinimalArea", -2);
+ if (num!=-2) setMinimalArea(num);
+
+ num = config->readNumEntry(prefix+"FieldCount", -2);
+ if (num<=0 || num>MAX_FIELD) return;
+
+ int f;
+ for (f=0;f<num;f++) {
+ str = QString(prefix+"FieldVisible%1").arg(f);
+ if (config->hasKey(str))
+ setFieldVisible(f, config->readBoolEntry(str));
+
+ str = QString(prefix+"FieldForced%1").arg(f);
+ if (config->hasKey(str))
+ setFieldForced(f, config->readBoolEntry(str));
+
+ str = config->readEntry(QString(prefix+"FieldStop%1").arg(f));
+ setFieldStop(f, str);
+
+ str = config->readEntry(QString(prefix+"FieldPosition%1").arg(f));
+ if (!str.isEmpty()) setFieldPosition(f, str);
+ }
+}
+
+#include "treemap.moc"
diff --git a/konq-plugins/fsview/treemap.h b/konq-plugins/fsview/treemap.h
new file mode 100644
index 0000000..a834d23
--- /dev/null
+++ b/konq-plugins/fsview/treemap.h
@@ -0,0 +1,742 @@
+/* This file is part of KCachegrind.
+ Copyright (C) 2002, 2003 Josef Weidendorfer <Josef.Weidendorfer@gmx.de>
+
+ KCachegrind 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, version 2.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program; see the file COPYING. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+/**
+ * A Widget for visualizing hierarchical metrics as areas.
+ * The API is similar to QListView.
+ *
+ * This file defines the following classes:
+ * DrawParams, RectDrawing, TreeMapItem, TreeMapWidget
+ *
+ * DrawParams/RectDrawing allows reusing of TreeMap drawing
+ * functions in other widgets.
+ */
+
+#ifndef TREEMAP_H
+#define TREEMAP_H
+
+#include <qstring.h>
+#include <qwidget.h>
+#include <qpixmap.h>
+#include <qptrlist.h>
+#include <qvaluevector.h>
+#include <qcolor.h>
+#include <qapplication.h>
+#include <qstringlist.h>
+
+class QPopupMenu;
+class TreeMapTip;
+class TreeMapWidget;
+class TreeMapItem;
+class TreeMapItemList;
+class QString;
+
+class KConfigGroup;
+
+
+/**
+ * Drawing parameters for an object.
+ * A Helper Interface for RectDrawing.
+ */
+class DrawParams
+{
+public:
+ /**
+ * Positions for drawing into a rectangle.
+ *
+ * The specified position assumes no rotation.
+ * If there is more than one text for one position, it is put
+ * nearer to the center of the item.
+ *
+ * Drawing at top positions cuts free space from top,
+ * drawing at bottom positions cuts from bottom.
+ * Default usually gives positions clockwise according to field number.
+ */
+ enum Position { TopLeft, TopCenter, TopRight,
+ BottomLeft, BottomCenter, BottomRight,
+ Default, Unknown};
+
+ // no constructor as this is an abstract class
+ virtual ~DrawParams() {}
+
+ virtual QString text(int) const = 0;
+ virtual QPixmap pixmap(int) const = 0;
+ virtual Position position(int) const = 0;
+ // 0: no limit, negative: leave at least -maxLines() free
+ virtual int maxLines(int) const { return 0; }
+ virtual int fieldCount() const { return 0; }
+
+ virtual QColor backColor() const { return Qt::white; }
+ virtual const QFont& font() const = 0;
+
+ virtual bool selected() const { return false; }
+ virtual bool current() const { return false; }
+ virtual bool shaded() const { return true; }
+ virtual bool rotated() const { return false; }
+};
+
+
+/*
+ * DrawParam with attributes stored
+ */
+class StoredDrawParams: public DrawParams
+{
+public:
+ StoredDrawParams();
+ StoredDrawParams(QColor c,
+ bool selected = false, bool current = false);
+
+ // getters
+ QString text(int) const;
+ QPixmap pixmap(int) const;
+ Position position(int) const;
+ int maxLines(int) const;
+ int fieldCount() const { return _field.size(); }
+
+ QColor backColor() const { return _backColor; }
+ bool selected() const { return _selected; }
+ bool current() const { return _current; }
+ bool shaded() const { return _shaded; }
+ bool rotated() const { return _rotated; }
+
+ const QFont& font() const;
+
+ // attribute setters
+ void setField(int f, const QString& t, QPixmap pm = QPixmap(),
+ Position p = Default, int maxLines = 0);
+ void setText(int f, const QString&);
+ void setPixmap(int f, const QPixmap&);
+ void setPosition(int f, Position);
+ void setMaxLines(int f, int);
+ void setBackColor(const QColor& c) { _backColor = c; }
+ void setSelected(bool b) { _selected = b; }
+ void setCurrent(bool b) { _current = b; }
+ void setShaded(bool b) { _shaded = b; }
+ void setRotated(bool b) { _rotated = b; }
+
+protected:
+ QColor _backColor;
+ bool _selected, _current, _shaded, _rotated;
+
+private:
+ // resize field array if needed to allow to access field <f>
+ void ensureField(int f);
+
+ struct Field {
+ QString text;
+ QPixmap pix;
+ Position pos;
+ int maxLines;
+ };
+
+ QValueVector<Field> _field;
+};
+
+
+/* State for drawing on a rectangle.
+ *
+ * Following drawing functions are provided:
+ * - background drawing with shading and 3D frame
+ * - successive pixmap/text drawing at various positions with wrap-around
+ * optimized for minimal space usage (e.g. if a text is drawn at top right
+ * after text on top left, the same line is used if space allows)
+ *
+ */
+class RectDrawing
+{
+public:
+ RectDrawing(QRect);
+ ~RectDrawing();
+
+ // The default DrawParams object used.
+ DrawParams* drawParams();
+ // we take control over the given object (i.e. delete at destruction)
+ void setDrawParams(DrawParams*);
+
+ // draw on a given QPainter, use this class as info provider per default
+ void drawBack(QPainter*, DrawParams* dp = 0);
+ /* Draw field at position() from pixmap()/text() with maxLines().
+ * Returns true if something was drawn
+ */
+ bool drawField(QPainter*, int f, DrawParams* dp = 0);
+
+ // resets rectangle for free space
+ void setRect(QRect);
+
+ // Returns the rectangle area still free of text/pixmaps after
+ // a number of drawText() calls.
+ QRect remainingRect(DrawParams* dp = 0);
+
+private:
+ int _usedTopLeft, _usedTopCenter, _usedTopRight;
+ int _usedBottomLeft, _usedBottomCenter, _usedBottomRight;
+ QRect _rect;
+
+ // temporary
+ int _fontHeight;
+ QFontMetrics* _fm;
+ DrawParams* _dp;
+};
+
+
+class TreeMapItemList: public QPtrList<TreeMapItem>
+{
+public:
+ TreeMapItem* commonParent();
+protected:
+ int compareItems ( Item item1, Item item2 );
+};
+
+typedef QPtrListIterator<TreeMapItem> TreeMapItemListIterator;
+
+
+/**
+ * Base class of items in TreeMap.
+ *
+ * This class supports an arbitrary number of text() strings
+ * positioned counterclock-wise starting at TopLeft. Each item
+ * has its own static value(), sum() and sorting(). The
+ * splitMode() and borderWidth() is taken from a TreeMapWidget.
+ *
+ * If you want more flexibility, reimplement TreeMapItem and
+ * override the corresponding methods. For dynamic creation of child
+ * items on demand, reimplement children().
+ */
+class TreeMapItem: public StoredDrawParams
+{
+public:
+
+ /**
+ * Split direction for nested areas:
+ * AlwaysBest: Choose split direction for every subitem according to
+ * longest side of rectangle left for drawing
+ * Best: Choose split direction for all subitems of an area
+ * depending on longest side
+ * HAlternate: Horizontal at top; alternate direction on depth step
+ * VAlternate: Vertical at top; alternate direction on depth step
+ * Horizontal: Always horizontal split direction
+ * Vertical: Always vertical split direction
+ */
+ enum SplitMode { Bisection, Columns, Rows,
+ AlwaysBest, Best,
+ HAlternate, VAlternate,
+ Horizontal, Vertical };
+
+ TreeMapItem(TreeMapItem* parent = 0, double value = 1.0 );
+ TreeMapItem(TreeMapItem* parent, double value,
+ QString text1, QString text2 = QString::null,
+ QString text3 = QString::null, QString text4 = QString::null);
+ virtual ~TreeMapItem();
+
+ bool isChildOf(TreeMapItem*);
+
+ TreeMapItem* commonParent(TreeMapItem* item);
+
+ // force a redraw of this item
+ void redraw();
+
+ // delete all children
+ void clear();
+
+ // force new child generation & refresh
+ void refresh();
+
+ // call in a reimplemented items() method to check if already called
+ // after a clear(), this will return false
+ bool initialized();
+
+ /**
+ * Adds an item to a parent.
+ * When no sorting is used, the item is appended (drawn at bottom).
+ * This is only needed if the parent was not already specified in the
+ * construction of the item.
+ */
+ void addItem(TreeMapItem*);
+
+ /**
+ * Returns a list of text strings of specified text number,
+ * from root up to this item.
+ */
+ QStringList path(int) const;
+
+ /**
+ * Depth of this item. This is the distance to root.
+ */
+ int depth() const;
+
+ /**
+ * Parent Item
+ */
+ TreeMapItem* parent() const { return _parent; }
+
+ /**
+ * Temporary rectangle used for drawing this item the last time.
+ * This is internally used to map from a point to an item.
+ */
+ void setItemRect(const QRect& r) { _rect = r; }
+ void clearItemRect();
+ const QRect& itemRect() const { return _rect; }
+ int width() const { return _rect.width(); }
+ int height() const { return _rect.height(); }
+
+ /**
+ * Temporary rectangle list of free space of this item.
+ * Used internally to enable tooltip.
+ */
+ void clearFreeRects();
+ QPtrList<QRect>* freeRects() const { return _freeRects; }
+ void addFreeRect(const QRect& r);
+
+ /**
+ * Temporary child item index of the child that was current() recently.
+ */
+ int index() const { return _index; }
+ void setIndex(int i) { _index = i; }
+
+
+ /**
+ * TreeMap widget this item is put in.
+ */
+ TreeMapWidget* widget() const { return _widget; }
+
+ void setParent(TreeMapItem* p);
+ void setWidget(TreeMapWidget* w) { _widget = w; }
+ void setSum(double s) { _sum = s; }
+ void setValue(double s) { _value = s; }
+
+ virtual double sum() const;
+ virtual double value() const;
+ // replace "Default" position with setting from TreeMapWidget
+ virtual Position position(int) const;
+ virtual const QFont& font() const;
+ virtual bool isMarked(int) const;
+
+ virtual int borderWidth() const;
+
+ /**
+ * Returns the text number after that sorting is done or
+ * -1 for no sorting, -2 for value() sorting (default).
+ * If ascending != 0, a bool value is written at that location
+ * to indicate if sorting should be ascending.
+ */
+ virtual int sorting(bool* ascending) const;
+
+ /**
+ * Set the sorting for child drawing.
+ *
+ * Default is no sorting: <textNo> = -1
+ * For value() sorting, use <textNo> = -2
+ *
+ * For fast sorting, set this to -1 before child insertions and call
+ * again after inserting all children.
+ */
+ void setSorting(int textNo, bool ascending = true);
+
+ /**
+ * Resort according to the already set sorting.
+ *
+ * This has to be done if the sorting base changes (e.g. text or values
+ * change). If this is only true for the children of this item, you can
+ * set the recursive parameter to false.
+ */
+ void resort(bool recursive = true);
+
+ virtual SplitMode splitMode() const;
+ virtual int rtti() const;
+ // not const as this can create children on demand
+ virtual TreeMapItemList* children();
+
+protected:
+ TreeMapItemList* _children;
+ double _sum, _value;
+
+private:
+ TreeMapWidget* _widget;
+ TreeMapItem* _parent;
+
+ int _sortTextNo;
+ bool _sortAscending;
+
+ // temporary layout
+ QRect _rect;
+ QPtrList<QRect>* _freeRects;
+ int _depth;
+
+ // temporary self value (when using level skipping)
+ double _unused_self;
+
+ // index of last active subitem
+ int _index;
+};
+
+
+/**
+ * Class for visualisation of a metric of hierarchically
+ * nested items as 2D areas.
+ */
+class TreeMapWidget: public QWidget
+{
+ Q_OBJECT
+
+public:
+
+ /**
+ * Same as in QListBox/QListView
+ */
+ enum SelectionMode { Single, Multi, Extended, NoSelection };
+
+ TreeMapWidget(TreeMapItem* base, QWidget* parent=0, const char* name=0);
+ ~TreeMapWidget();
+
+ /**
+ * Returns the TreeMapItem filling out the widget space
+ */
+ TreeMapItem* base() const { return _base; }
+
+ /**
+ * Returns a reference to the current widget font.
+ */
+ const QFont& currentFont() const;
+
+ /**
+ * Returns the area item at position x/y, independent from any
+ * maxSelectDepth setting.
+ */
+ TreeMapItem* item(int x, int y) const;
+
+ /**
+ * Returns the nearest item with a visible area; this
+ * can be the given item itself.
+ */
+ TreeMapItem* visibleItem(TreeMapItem*) const;
+
+ /**
+ * Returns the item possible for selection. this returns the
+ * given item itself or a parent thereof,
+ * depending on setting of maxSelectDepth().
+ */
+ TreeMapItem* possibleSelection(TreeMapItem*) const;
+
+ /**
+ * Selects or unselects an item.
+ * In multiselection mode, the constrain that a selected item
+ * has no selected children or parents stays true.
+ */
+ void setSelected(TreeMapItem*, bool selected = true);
+
+ /**
+ * Switches on the marking <markNo>. Marking 0 switches off marking.
+ * This is mutually exclusive to selection, and is automatically
+ * switched off when selection is changed (also by the user).
+ * Marking is visually the same as selection, and is based on
+ * TreeMapItem::isMarked(<markNo>).
+ * This enables to programmatically show multiple selected items
+ * at once even in single selection mode.
+ */
+ void setMarked(int markNo = 1, bool redraw = true);
+
+ /**
+ * Clear selection of all selected items which are children of
+ * parent. When parent == 0, clears whole selection
+ * Returns true if selection changed.
+ */
+ bool clearSelection(TreeMapItem* parent = 0);
+
+ /**
+ * Selects or unselects items in a range.
+ * This is needed internally for Shift-Click in Extented mode.
+ * Range means for a hierarchical widget:
+ * - select/unselect i1 and i2 according selected
+ * - search common parent of i1 and i2, and select/unselect the
+ * range of direct children between but excluding the child
+ * leading to i1 and the child leading to i2.
+ */
+ void setRangeSelection(TreeMapItem* i1,
+ TreeMapItem* i2, bool selected);
+
+ /**
+ * Sets the current item.
+ * The current item is mainly used for keyboard navigation.
+ */
+ void setCurrent(TreeMapItem*, bool kbd=false);
+
+ /**
+ * Set the maximal depth a selected item can have.
+ * If you try to select a item with higher depth, the ancestor holding
+ * this condition is used.
+ *
+ * See also possibleSelection().
+ */
+ void setMaxSelectDepth(int d) { _maxSelectDepth = d; }
+
+
+ void setSelectionMode(SelectionMode m) { _selectionMode = m; }
+
+ /**
+ * for setting/getting global split direction
+ */
+ void setSplitMode(TreeMapItem::SplitMode m);
+ TreeMapItem::SplitMode splitMode() const;
+ // returns true if string was recognized
+ bool setSplitMode(QString);
+ QString splitModeString() const;
+
+
+ /*
+ * Shading of rectangles enabled ?
+ */
+ void setShadingEnabled(bool s);
+ bool isShadingEnabled() const { return _shading; }
+
+
+ /**
+ * Items usually have a size proportional to their value().
+ * With <width>, you can give the minimum width
+ * of the resulting rectangle to still be drawn.
+ * For space not used because of to small items, you can specify
+ * with <reuseSpace> if the background should shine through or
+ * the space will be used to enlarge the next item to be drawn
+ * at this level.
+ */
+ void setVisibleWidth(int width, bool reuseSpace = false);
+
+ /**
+ * If a children value() is almost the parents sum(),
+ * it can happen that the border to be drawn for visibilty of
+ * nesting relations takes to much space, and the
+ * parent/child size relation can not be mapped to a correct
+ * area size relation.
+ *
+ * Either
+ * (1) Ignore the incorrect drawing, or
+ * (2) Skip drawing of the parent level alltogether.
+ */
+ void setSkipIncorrectBorder(bool enable = true);
+ bool skipIncorrectBorder() const { return _skipIncorrectBorder; }
+
+ /**
+ * Maximal nesting depth
+ */
+ void setMaxDrawingDepth(int d);
+ int maxDrawingDepth() const { return _maxDrawingDepth; }
+
+ /**
+ * Minimal area for rectangles to draw
+ */
+ void setMinimalArea(int area);
+ int minimalArea() const { return _minimalArea; }
+
+ /* defaults for text attributes */
+ QString defaultFieldType(int) const;
+ QString defaultFieldStop(int) const;
+ bool defaultFieldVisible(int) const;
+ bool defaultFieldForced(int) const;
+ DrawParams::Position defaultFieldPosition(int) const;
+
+ /**
+ * Set the type name of a field.
+ * This is important for the visualization menu generated
+ * with visualizationMenu()
+ */
+ void setFieldType(int, QString);
+ QString fieldType(int) const;
+
+ /**
+ * Stop drawing at item with name
+ */
+ void setFieldStop(int, QString);
+ QString fieldStop(int) const;
+
+ /**
+ * Should the text with number textNo be visible?
+ * This is only done if remaining space is enough to allow for
+ * proportional size constrains.
+ */
+ void setFieldVisible(int, bool);
+ bool fieldVisible(int) const;
+
+ /**
+ * Should the drawing of the name into the rectangle be forced?
+ * This enables drawing of the name before drawing subitems, and
+ * thus destroys proportional constrains.
+ */
+ void setFieldForced(int, bool);
+ bool fieldForced(int) const;
+
+ /**
+ * Set the field position in the area. See TreeMapItem::Position
+ */
+ void setFieldPosition(int, DrawParams::Position);
+ DrawParams::Position fieldPosition(int) const;
+ void setFieldPosition(int, QString);
+ QString fieldPositionString(int) const;
+
+ /**
+ * Do we allow the texts to be rotated by 90 degrees for better fitting?
+ */
+ void setAllowRotation(bool);
+ bool allowRotation() const { return _allowRotation; }
+
+ void setBorderWidth(int w);
+ int borderWidth() const { return _borderWidth; }
+
+ /**
+ * Save/restore options.
+ */
+ void saveOptions(KConfigGroup*, QString prefix = QString::null);
+ void restoreOptions(KConfigGroup*, QString prefix = QString::null);
+
+ /**
+ * These functions populate given popup menus.
+ * The added items are already connected to handlers.
+ *
+ * The int is the menu id where to start for the items (100 IDs reserved).
+ */
+ void addSplitDirectionItems(QPopupMenu*, int);
+ void addSelectionItems(QPopupMenu*, int, TreeMapItem*);
+ void addFieldStopItems(QPopupMenu*, int, TreeMapItem*);
+ void addAreaStopItems(QPopupMenu*, int, TreeMapItem*);
+ void addDepthStopItems(QPopupMenu*, int, TreeMapItem*);
+ void addVisualizationItems(QPopupMenu*, int);
+
+ TreeMapWidget* widget() { return this; }
+ TreeMapItem* current() const { return _current; }
+ TreeMapItemList selection() const { return _selection; }
+ bool isSelected(TreeMapItem* i) const;
+ int maxSelectDepth() const { return _maxSelectDepth; }
+ SelectionMode selectionMode() const { return _selectionMode; }
+
+ /**
+ * Return tooltip string to show for a item (can be rich text)
+ * Default implementation gives lines with "text0 (text1)" going to root.
+ */
+ virtual QString tipString(TreeMapItem* i) const;
+
+ /**
+ * Redraws an item with all children.
+ * This takes changed values(), sums(), colors() and text() into account.
+ */
+ void redraw(TreeMapItem*);
+ void redraw() { redraw(_base); }
+
+ /**
+ * Resort all TreeMapItems. See TreeMapItem::resort().
+ */
+ void resort() { _base->resort(true); }
+
+ // internal
+ void drawTreeMap();
+
+ // used internally when items are destroyed
+ void deletingItem(TreeMapItem*);
+
+protected slots:
+ void splitActivated(int);
+ void selectionActivated(int);
+ void fieldStopActivated(int);
+ void areaStopActivated(int);
+ void depthStopActivated(int);
+ void visualizationActivated(int);
+
+signals:
+ void selectionChanged();
+ void selectionChanged(TreeMapItem*);
+
+ /**
+ * This signal is emitted if the current item changes.
+ * If the change is done because of keyboard navigation,
+ * the <kbd> is set to true
+ */
+ void currentChanged(TreeMapItem*, bool keyboard);
+ void clicked(TreeMapItem*);
+ void returnPressed(TreeMapItem*);
+ void doubleClicked(TreeMapItem*);
+ void rightButtonPressed(TreeMapItem*, const QPoint &);
+ void contextMenuRequested(TreeMapItem*, const QPoint &);
+
+protected:
+ void mousePressEvent( QMouseEvent * );
+ void contextMenuEvent( QContextMenuEvent * );
+ void mouseReleaseEvent( QMouseEvent * );
+ void mouseMoveEvent( QMouseEvent * );
+ void mouseDoubleClickEvent( QMouseEvent * );
+ void keyPressEvent( QKeyEvent* );
+ void paintEvent( QPaintEvent * );
+ void resizeEvent( QResizeEvent * );
+ void showEvent( QShowEvent * );
+ void fontChange( const QFont& );
+
+private:
+ TreeMapItemList diff(TreeMapItemList&, TreeMapItemList&);
+ // returns true if selection changed
+ TreeMapItem* setTmpSelected(TreeMapItem*, bool selected = true);
+ TreeMapItem* setTmpRangeSelection(TreeMapItem* i1,
+ TreeMapItem* i2, bool selected);
+ bool isTmpSelected(TreeMapItem* i);
+
+ void drawItem(QPainter* p, TreeMapItem*);
+ void drawItems(QPainter* p, TreeMapItem*);
+ bool horizontal(TreeMapItem* i, const QRect& r);
+ void drawFill(TreeMapItem*,QPainter* p, QRect& r);
+ void drawFill(TreeMapItem*,QPainter* p, QRect& r,
+ TreeMapItemListIterator it, int len, bool goBack);
+ bool drawItemArray(QPainter* p, TreeMapItem*, QRect& r, double,
+ TreeMapItemListIterator it, int len, bool);
+ bool resizeAttr(int);
+
+ TreeMapItem* _base;
+ TreeMapItem *_current, *_pressed, *_lastOver, *_oldCurrent;
+ TreeMapTip* _tip;
+ int _maxSelectDepth, _maxDrawingDepth;
+
+ // attributes for field, per textNo
+ struct FieldAttr {
+ QString type, stop;
+ bool visible, forced;
+ DrawParams::Position pos;
+ };
+ QValueVector<FieldAttr> _attr;
+
+ SelectionMode _selectionMode;
+ TreeMapItem::SplitMode _splitMode;
+ int _visibleWidth, _stopArea, _minimalArea, _borderWidth;
+ bool _reuseSpace, _skipIncorrectBorder, _drawSeparators, _shading;
+ bool _allowRotation;
+ TreeMapItem * _needsRefresh;
+ TreeMapItemList _selection;
+ int _markNo;
+
+ // for the context menus: start IDs
+ int _splitID, _selectionID, _visID;
+ int _fieldStopID, _areaStopID, _depthStopID;
+ TreeMapItem* _menuItem;
+
+ // temporary selection while dragging, used for drawing
+ // most of the time, _selection == _tmpSelection
+ TreeMapItemList _tmpSelection;
+ bool _inShiftDrag, _inControlDrag;
+
+ // temporary widget font metrics while drawing
+ QFont _font;
+ int _fontHeight;
+
+ // back buffer pixmap
+ QPixmap _pixmap;
+};
+
+#endif