diff options
Diffstat (limited to 'konq-plugins/fsview')
-rw-r--r-- | konq-plugins/fsview/Makefile.am | 51 | ||||
-rw-r--r-- | konq-plugins/fsview/README | 29 | ||||
-rw-r--r-- | konq-plugins/fsview/fsview.cpp | 540 | ||||
-rw-r--r-- | konq-plugins/fsview/fsview.desktop | 120 | ||||
-rw-r--r-- | konq-plugins/fsview/fsview.h | 136 | ||||
-rw-r--r-- | konq-plugins/fsview/fsview_part.cpp | 413 | ||||
-rw-r--r-- | konq-plugins/fsview/fsview_part.desktop | 58 | ||||
-rw-r--r-- | konq-plugins/fsview/fsview_part.h | 122 | ||||
-rw-r--r-- | konq-plugins/fsview/fsview_part.rc | 15 | ||||
-rw-r--r-- | konq-plugins/fsview/hi22-app-fsview.png | bin | 0 -> 834 bytes | |||
-rw-r--r-- | konq-plugins/fsview/hi32-app-fsview.png | bin | 0 -> 902 bytes | |||
-rw-r--r-- | konq-plugins/fsview/inode.cpp | 385 | ||||
-rw-r--r-- | konq-plugins/fsview/inode.h | 97 | ||||
-rw-r--r-- | konq-plugins/fsview/main.cpp | 56 | ||||
-rw-r--r-- | konq-plugins/fsview/scan.cpp | 362 | ||||
-rw-r--r-- | konq-plugins/fsview/scan.h | 230 | ||||
-rw-r--r-- | konq-plugins/fsview/scantest.cpp | 56 | ||||
-rw-r--r-- | konq-plugins/fsview/treemap.cpp | 3199 | ||||
-rw-r--r-- | konq-plugins/fsview/treemap.h | 742 |
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>&View</Text> + <Separator/> + <Action name="treemap_visdir"/> + <Action name="treemap_colordir"/> + <Action name="treemap_areadir"/> + <Action name="treemap_depthdir"/> + </Menu> + <Menu name="help"><text>&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 Binary files differnew file mode 100644 index 0000000..efdcdcb --- /dev/null +++ b/konq-plugins/fsview/hi22-app-fsview.png diff --git a/konq-plugins/fsview/hi32-app-fsview.png b/konq-plugins/fsview/hi32-app-fsview.png Binary files differnew file mode 100644 index 0000000..6e22f8b --- /dev/null +++ b/konq-plugins/fsview/hi32-app-fsview.png 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 |