KDirStat – a graphical disk usage utility
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ktreemapview.cpp 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745
  1. /*
  2. * File name: ktreemapview.cpp
  3. * Summary: High level classes for KDirStat
  4. * License: LGPL - See file COPYING.LIB for details.
  5. * Author: Stefan Hundhammer <sh@suse.de>
  6. *
  7. * Updated: 2003-10-20
  8. */
  9. #include <sys/stat.h>
  10. #include <tqevent.h>
  11. #include <tqregexp.h>
  12. #include <kapp.h>
  13. #include <tdeconfig.h>
  14. #include <tdeglobal.h>
  15. #include <tdelocale.h>
  16. #include "kdirtree.h"
  17. #include "ktreemapview.h"
  18. #include "ktreemaptile.h"
  19. using namespace KDirStat;
  20. #define UpdateMinSize 20
  21. KTreemapView::KTreemapView( KDirTree * tree, TQWidget * parent, const TQSize & initialSize )
  22. : TQCanvasView( parent )
  23. , _tree( tree )
  24. , _rootTile( 0 )
  25. , _selectedTile( 0 )
  26. , _selectionRect( 0 )
  27. {
  28. // kdDebug() << k_funcinfo << endl;
  29. readConfig();
  30. // Default values for light sources taken from Wiik / Wetering's paper
  31. // about "cushion treemaps".
  32. _lightX = 0.09759;
  33. _lightY = 0.19518;
  34. _lightZ = 0.9759;
  35. if ( _autoResize )
  36. {
  37. setHScrollBarMode( AlwaysOff );
  38. setVScrollBarMode( AlwaysOff );
  39. }
  40. if ( initialSize.isValid() )
  41. resize( initialSize );
  42. if ( tree && tree->root() )
  43. {
  44. if ( ! _rootTile )
  45. {
  46. // The treemap might already be created indirectly by
  47. // rebuildTreemap() called from resizeEvent() triggered by resize()
  48. // above. If this is so, don't do it again.
  49. rebuildTreemap( tree->root() );
  50. }
  51. }
  52. connect( this, TQT_SIGNAL( selectionChanged( KFileInfo * ) ),
  53. tree, TQT_SLOT ( selectItem ( KFileInfo * ) ) );
  54. connect( tree, TQT_SIGNAL( selectionChanged( KFileInfo * ) ),
  55. this, TQT_SLOT ( selectTile ( KFileInfo * ) ) );
  56. connect( tree, TQT_SIGNAL( deletingChild ( KFileInfo * ) ),
  57. this, TQT_SLOT ( deleteNotify ( KFileInfo * ) ) );
  58. connect( tree, TQT_SIGNAL( childDeleted() ),
  59. this, TQT_SLOT ( rebuildTreemap() ) );
  60. }
  61. KTreemapView::~KTreemapView()
  62. {
  63. }
  64. void
  65. KTreemapView::clear()
  66. {
  67. if ( canvas() )
  68. deleteAllItems( canvas() );
  69. _selectedTile = 0;
  70. _selectionRect = 0;
  71. _rootTile = 0;
  72. }
  73. void
  74. KTreemapView::deleteAllItems( TQCanvas * canvas )
  75. {
  76. if ( ! canvas )
  77. return;
  78. TQCanvasItemList all = canvas->allItems();
  79. for ( TQCanvasItemList::Iterator it = all.begin(); it != all.end(); ++it )
  80. delete *it;
  81. }
  82. void
  83. KTreemapView::readConfig()
  84. {
  85. TDEConfig * config = kapp->config();
  86. config->setGroup( "Treemaps" );
  87. _ambientLight = config->readNumEntry( "AmbientLight" , DefaultAmbientLight );
  88. _heightScaleFactor = config->readDoubleNumEntry( "HeightScaleFactor" , DefaultHeightScaleFactor );
  89. _autoResize = config->readBoolEntry( "AutoResize" , true );
  90. _squarify = config->readBoolEntry( "Squarify" , true );
  91. _doCushionShading = config->readBoolEntry( "CushionShading" , true );
  92. _ensureContrast = config->readBoolEntry( "EnsureContrast" , true );
  93. _forceCushionGrid = config->readBoolEntry( "ForceCushionGrid" , false );
  94. _minTileSize = config->readNumEntry ( "MinTileSize" , DefaultMinTileSize );
  95. _highlightColor = readColorEntry( config, "HighlightColor" , red );
  96. _cushionGridColor = readColorEntry( config, "CushionGridColor" , TQColor( 0x80, 0x80, 0x80 ) );
  97. _outlineColor = readColorEntry( config, "OutlineColor" , black );
  98. _fileFillColor = readColorEntry( config, "FileFillColor" , TQColor( 0xde, 0x8d, 0x53 ) );
  99. _dirFillColor = readColorEntry( config, "DirFillColor" , TQColor( 0x10, 0x7d, 0xb4 ) );
  100. if ( _autoResize )
  101. {
  102. setHScrollBarMode( AlwaysOff );
  103. setVScrollBarMode( AlwaysOff );
  104. }
  105. else
  106. {
  107. setHScrollBarMode( TQScrollView::Auto );
  108. setVScrollBarMode( TQScrollView::Auto );
  109. }
  110. }
  111. TQColor
  112. KTreemapView::readColorEntry( TDEConfig * config, const char * entryName, TQColor defaultColor )
  113. {
  114. return config->readColorEntry( entryName, &defaultColor );
  115. }
  116. KTreemapTile *
  117. KTreemapView::tileAt( TQPoint pos )
  118. {
  119. KTreemapTile * tile = 0;
  120. TQCanvasItemList coll = canvas()->collisions( pos );
  121. TQCanvasItemList::Iterator it = coll.begin();
  122. while ( it != coll.end() && tile == 0 )
  123. {
  124. tile = dynamic_cast<KTreemapTile *> (*it);
  125. ++it;
  126. }
  127. return tile;
  128. }
  129. void
  130. KTreemapView::contentsMousePressEvent( TQMouseEvent * event )
  131. {
  132. // kdDebug() << k_funcinfo << endl;
  133. KTreemapTile * tile = tileAt( event->pos() );
  134. switch ( event->button() )
  135. {
  136. case Qt::LeftButton:
  137. selectTile( tile );
  138. emit userActivity( 1 );
  139. break;
  140. case Qt::MidButton:
  141. // Select clicked tile's parent, if available
  142. if ( _selectedTile &&
  143. _selectedTile->rect().contains( event->pos() ) )
  144. {
  145. if ( _selectedTile->parentTile() )
  146. tile = _selectedTile->parentTile();
  147. }
  148. // Intentionally handling the middle button like the left button if
  149. // the user clicked outside the (old) selected tile: Simply select
  150. // the clicked tile. This makes using this middle mouse button
  151. // intuitive: It can be used very much like the left mouse button,
  152. // but it has added functionality. Plus, it cycles back to the
  153. // clicked tile if the user has already clicked all the way up the
  154. // hierarchy (i.e. the topmost directory is highlighted).
  155. selectTile( tile );
  156. emit userActivity( 1 );
  157. break;
  158. case Qt::RightButton:
  159. if ( tile )
  160. {
  161. if ( _selectedTile &&
  162. _selectedTile->rect().contains( event->pos() ) )
  163. {
  164. // If a directory (non-leaf tile) is already selected,
  165. // don't override this by
  166. emit contextMenu( _selectedTile, event->globalPos() );
  167. }
  168. else
  169. {
  170. selectTile( tile );
  171. emit contextMenu( tile, event->globalPos() );
  172. }
  173. emit userActivity( 3 );
  174. }
  175. break;
  176. default:
  177. // event->button() is an enum, so g++ complains
  178. // if there are unhandled cases.
  179. break;
  180. }
  181. }
  182. void
  183. KTreemapView::contentsMouseDoubleClickEvent( TQMouseEvent * event )
  184. {
  185. // kdDebug() << k_funcinfo << endl;
  186. KTreemapTile * tile = tileAt( event->pos() );
  187. switch ( event->button() )
  188. {
  189. case Qt::LeftButton:
  190. if ( tile )
  191. {
  192. selectTile( tile );
  193. zoomIn();
  194. emit userActivity( 5 );
  195. }
  196. break;
  197. case Qt::MidButton:
  198. zoomOut();
  199. emit userActivity( 5 );
  200. break;
  201. case Qt::RightButton:
  202. // Double-clicking the right mouse button is pretty useless - the
  203. // first click opens the context menu: Single clicks are always
  204. // delivered first. Even if that would be caught by using timers,
  205. // it would still be very awkward to use: Click too slow, and
  206. // you'll get the context menu rather than what you really wanted -
  207. // then you'd have to get rid of the context menu first.
  208. break;
  209. default:
  210. // Prevent compiler complaints about missing enum values in switch
  211. break;
  212. }
  213. }
  214. void
  215. KTreemapView::zoomIn()
  216. {
  217. if ( ! _selectedTile || ! _rootTile )
  218. return;
  219. KTreemapTile * newRootTile = _selectedTile;
  220. while ( newRootTile->parentTile() != _rootTile &&
  221. newRootTile->parentTile() ) // This should never happen, but who knows?
  222. {
  223. newRootTile = newRootTile->parentTile();
  224. }
  225. if ( newRootTile )
  226. {
  227. KFileInfo * newRoot = newRootTile->orig();
  228. if ( newRoot->isDir() || newRoot->isDotEntry() )
  229. rebuildTreemap( newRoot );
  230. }
  231. }
  232. void
  233. KTreemapView::zoomOut()
  234. {
  235. if ( _rootTile )
  236. {
  237. KFileInfo * root = _rootTile->orig();
  238. if ( root->parent() )
  239. root = root->parent();
  240. rebuildTreemap( root );
  241. }
  242. }
  243. void
  244. KTreemapView::selectParent()
  245. {
  246. if ( _selectedTile && _selectedTile->parentTile() )
  247. selectTile( _selectedTile->parentTile() );
  248. }
  249. bool
  250. KTreemapView::canZoomIn() const
  251. {
  252. if ( ! _selectedTile || ! _rootTile )
  253. return false;
  254. if ( _selectedTile == _rootTile )
  255. return false;
  256. KTreemapTile * newRootTile = _selectedTile;
  257. while ( newRootTile->parentTile() != _rootTile &&
  258. newRootTile->parentTile() ) // This should never happen, but who knows?
  259. {
  260. newRootTile = newRootTile->parentTile();
  261. }
  262. if ( newRootTile )
  263. {
  264. KFileInfo * newRoot = newRootTile->orig();
  265. if ( newRoot->isDir() || newRoot->isDotEntry() )
  266. return true;
  267. }
  268. return false;
  269. }
  270. bool
  271. KTreemapView::canZoomOut() const
  272. {
  273. if ( ! _rootTile || ! _tree->root() )
  274. return false;
  275. return _rootTile->orig() != _tree->root();
  276. }
  277. bool
  278. KTreemapView::canSelectParent() const
  279. {
  280. return _selectedTile && _selectedTile->parentTile();
  281. }
  282. void
  283. KTreemapView::rebuildTreemap()
  284. {
  285. KFileInfo * root = 0;
  286. if ( ! _savedRootUrl.isEmpty() )
  287. {
  288. // kdDebug() << "Restoring old treemap with root " << _savedRootUrl << endl;
  289. root = _tree->locate( _savedRootUrl, true ); // node, findDotEntries
  290. }
  291. if ( ! root )
  292. root = _rootTile ? _rootTile->orig() : _tree->root();
  293. rebuildTreemap( root, canvas()->size() );
  294. _savedRootUrl = "";
  295. }
  296. void
  297. KTreemapView::rebuildTreemap( KFileInfo * newRoot,
  298. const TQSize & newSz )
  299. {
  300. // kdDebug() << k_funcinfo << endl;
  301. TQSize newSize = newSz;
  302. if ( newSz.isEmpty() )
  303. newSize = visibleSize();
  304. // Delete all old stuff.
  305. clear();
  306. // Re-create a new canvas
  307. if ( ! canvas() )
  308. {
  309. TQCanvas * canv = new TQCanvas( TQT_TQOBJECT(this) );
  310. TQ_CHECK_PTR( canv );
  311. setCanvas( canv );
  312. }
  313. canvas()->resize( newSize.width(), newSize.height() );
  314. if ( newSize.width() >= UpdateMinSize && newSize.height() >= UpdateMinSize )
  315. {
  316. // The treemap contents is displayed if larger than a certain minimum
  317. // visible size. This is an easy way for the user to avoid
  318. // time-consuming delays when deleting a lot of files: Simply make the
  319. // treemap (sub-) window very small.
  320. // Fill the new canvas
  321. if ( newRoot )
  322. {
  323. _rootTile = new KTreemapTile( this, // parentView
  324. 0, // parentTile
  325. newRoot, // orig
  326. TQRect( TQPoint( 0, 0), newSize ),
  327. KTreemapAuto );
  328. }
  329. // Synchronize selection with the tree
  330. if ( _tree->selection() )
  331. selectTile( _tree->selection() );
  332. }
  333. else
  334. {
  335. // kdDebug() << "Too small - suppressing treemap contents" << endl;
  336. }
  337. emit treemapChanged();
  338. }
  339. void
  340. KTreemapView::deleteNotify( KFileInfo * )
  341. {
  342. if ( _rootTile )
  343. {
  344. if ( _rootTile->orig() != _tree->root() )
  345. {
  346. // If the user zoomed the treemap in, save the root's URL so the
  347. // current state can be restored upon the next rebuildTreemap()
  348. // call (which is triggered by the childDeleted() signal that the
  349. // tree emits after deleting is done).
  350. //
  351. // Intentionally using debugUrl() here rather than just url() so
  352. // the correct zoom can be restored even when a dot entry is the
  353. // current treemap root.
  354. _savedRootUrl = _rootTile->orig()->debugUrl();
  355. }
  356. else
  357. {
  358. // A shortcut for the most common case: No zoom. Simply use the
  359. // tree's root for the next treemap rebuild.
  360. _savedRootUrl = "";
  361. }
  362. }
  363. else
  364. {
  365. // Intentionally leaving _savedRootUrl alone: Otherwise multiple
  366. // deleteNotify() calls might cause a previously saved _savedRootUrl to
  367. // be unnecessarily deleted, thus the treemap couldn't be restored as
  368. // it was.
  369. }
  370. clear();
  371. }
  372. void
  373. KTreemapView::resizeEvent( TQResizeEvent * event )
  374. {
  375. TQCanvasView::resizeEvent( event );
  376. if ( _autoResize )
  377. {
  378. bool tooSmall =
  379. event->size().width() < UpdateMinSize ||
  380. event->size().height() < UpdateMinSize;
  381. if ( tooSmall && _rootTile )
  382. {
  383. // kdDebug() << "Suppressing treemap contents" << endl;
  384. rebuildTreemap( _rootTile->orig() );
  385. }
  386. else if ( ! tooSmall && ! _rootTile )
  387. {
  388. if ( _tree->root() )
  389. {
  390. // kdDebug() << "Redisplaying suppressed treemap contents" << endl;
  391. rebuildTreemap( _tree->root() );
  392. }
  393. }
  394. else if ( _rootTile )
  395. {
  396. // kdDebug() << "Auto-resizing treemap" << endl;
  397. rebuildTreemap( _rootTile->orig() );
  398. }
  399. }
  400. }
  401. void
  402. KTreemapView::selectTile( KTreemapTile * tile )
  403. {
  404. // kdDebug() << k_funcinfo << endl;
  405. KTreemapTile * oldSelection = _selectedTile;
  406. _selectedTile = tile;
  407. // Handle selection (highlight) rectangle
  408. if ( _selectedTile )
  409. {
  410. if ( ! _selectionRect )
  411. _selectionRect = new KTreemapSelectionRect( canvas(), _highlightColor );
  412. }
  413. if ( _selectionRect )
  414. _selectionRect->highlight( _selectedTile );
  415. canvas()->update();
  416. if ( oldSelection != _selectedTile )
  417. {
  418. emit selectionChanged( _selectedTile ? _selectedTile->orig() : 0 );
  419. }
  420. }
  421. void
  422. KTreemapView::selectTile( KFileInfo * node )
  423. {
  424. selectTile( findTile( node ) );
  425. }
  426. KTreemapTile *
  427. KTreemapView::findTile( KFileInfo * node )
  428. {
  429. if ( ! node )
  430. return 0;
  431. TQCanvasItemList itemList = canvas()->allItems();
  432. TQCanvasItemList::Iterator it = itemList.begin();
  433. while ( it != itemList.end() )
  434. {
  435. KTreemapTile * tile = dynamic_cast<KTreemapTile *> (*it);
  436. if ( tile && tile->orig() == node )
  437. return tile;
  438. ++it;
  439. }
  440. return 0;
  441. }
  442. TQSize
  443. KTreemapView::visibleSize()
  444. {
  445. ScrollBarMode oldHMode = hScrollBarMode();
  446. ScrollBarMode oldVMode = vScrollBarMode();
  447. setHScrollBarMode( AlwaysOff );
  448. setVScrollBarMode( AlwaysOff );
  449. TQSize size = TQSize( TQCanvasView::visibleWidth(),
  450. TQCanvasView::visibleHeight() );
  451. setHScrollBarMode( oldHMode );
  452. setVScrollBarMode( oldVMode );
  453. return size;
  454. }
  455. TQColor
  456. KTreemapView::tileColor( KFileInfo * file )
  457. {
  458. if ( file )
  459. {
  460. if ( file->isFile() )
  461. {
  462. // Find the filename extension: Everything after the first '.'
  463. TQString ext = file->name().section( '.', 1 );
  464. while ( ! ext.isEmpty() )
  465. {
  466. TQString lowerExt = ext.lower();
  467. // Try case sensitive comparisions first
  468. if ( ext == "~" ) return TQt::red;
  469. if ( ext == "bak" ) return TQt::red;
  470. if ( ext == "c" ) return TQt::blue;
  471. if ( ext == "cpp" ) return TQt::blue;
  472. if ( ext == "cc" ) return TQt::blue;
  473. if ( ext == "h" ) return TQt::blue;
  474. if ( ext == "hpp" ) return TQt::blue;
  475. if ( ext == "el" ) return TQt::blue;
  476. if ( ext == "o" ) return TQColor( 0xff, 0xa0, 0x00 );
  477. if ( ext == "lo" ) return TQColor( 0xff, 0xa0, 0x00 );
  478. if ( ext == "Po" ) return TQColor( 0xff, 0xa0, 0x00 );
  479. if ( ext == "al" ) return TQColor( 0xff, 0xa0, 0x00 );
  480. if ( ext == "moc.cpp" ) return TQColor( 0xff, 0xa0, 0x00 );
  481. if ( ext == "moc.cc" ) return TQColor( 0xff, 0xa0, 0x00 );
  482. if ( ext == "elc" ) return TQColor( 0xff, 0xa0, 0x00 );
  483. if ( ext == "la" ) return TQColor( 0xff, 0xa0, 0x00 );
  484. if ( ext == "a" ) return TQColor( 0xff, 0xa0, 0x00 );
  485. if ( ext == "rpm" ) return TQColor( 0xff, 0xa0, 0x00 );
  486. if ( lowerExt == "tar.bz2" ) return TQt::green;
  487. if ( lowerExt == "tar.gz" ) return TQt::green;
  488. if ( lowerExt == "tgz" ) return TQt::green;
  489. if ( lowerExt == "bz2" ) return TQt::green;
  490. if ( lowerExt == "bz" ) return TQt::green;
  491. if ( lowerExt == "gz" ) return TQt::green;
  492. if ( lowerExt == "html" ) return TQt::blue;
  493. if ( lowerExt == "htm" ) return TQt::blue;
  494. if ( lowerExt == "txt" ) return TQt::blue;
  495. if ( lowerExt == "doc" ) return TQt::blue;
  496. if ( lowerExt == "png" ) return TQt::cyan;
  497. if ( lowerExt == "jpg" ) return TQt::cyan;
  498. if ( lowerExt == "jpeg" ) return TQt::cyan;
  499. if ( lowerExt == "gif" ) return TQt::cyan;
  500. if ( lowerExt == "tif" ) return TQt::cyan;
  501. if ( lowerExt == "tiff" ) return TQt::cyan;
  502. if ( lowerExt == "bmp" ) return TQt::cyan;
  503. if ( lowerExt == "xpm" ) return TQt::cyan;
  504. if ( lowerExt == "tga" ) return TQt::cyan;
  505. if ( lowerExt == "wav" ) return TQt::yellow;
  506. if ( lowerExt == "mp3" ) return TQt::yellow;
  507. if ( lowerExt == "avi" ) return TQColor( 0xa0, 0xff, 0x00 );
  508. if ( lowerExt == "mov" ) return TQColor( 0xa0, 0xff, 0x00 );
  509. if ( lowerExt == "mpg" ) return TQColor( 0xa0, 0xff, 0x00 );
  510. if ( lowerExt == "mpeg" ) return TQColor( 0xa0, 0xff, 0x00 );
  511. if ( lowerExt == "pdf" ) return TQt::blue;
  512. if ( lowerExt == "ps" ) return TQt::cyan;
  513. // Some DOS/Windows types
  514. if ( lowerExt == "exe" ) return TQt::magenta;
  515. if ( lowerExt == "com" ) return TQt::magenta;
  516. if ( lowerExt == "dll" ) return TQColor( 0xff, 0xa0, 0x00 );
  517. if ( lowerExt == "zip" ) return TQt::green;
  518. if ( lowerExt == "arj" ) return TQt::green;
  519. // No match so far? Try the next extension. Some files might have
  520. // more than one, e.g., "tar.bz2" - if there is no match for
  521. // "tar.bz2", there might be one for just "bz2".
  522. ext = ext.section( '.', 1 );
  523. }
  524. // Shared libs
  525. if ( TQRegExp( "lib.*\\.so.*" ).exactMatch( file->name() ) )
  526. return TQColor( 0xff, 0xa0, 0x00 );
  527. // Very special, but common: Core dumps
  528. if ( file->name() == "core" ) return TQt::red;
  529. // Special case: Executables
  530. if ( ( file->mode() & S_IXUSR ) == S_IXUSR ) return TQt::magenta;
  531. }
  532. else // Directories
  533. {
  534. // TO DO
  535. return TQt::blue;
  536. }
  537. }
  538. return TQt::white;
  539. }
  540. KTreemapSelectionRect::KTreemapSelectionRect( TQCanvas * canvas, const TQColor & color )
  541. : TQCanvasRectangle( canvas )
  542. {
  543. setPen( TQPen( color, 2 ) );
  544. setZ( 1e10 ); // Higher than everything else
  545. }
  546. void
  547. KTreemapSelectionRect::highlight( KTreemapTile * tile )
  548. {
  549. if ( tile )
  550. {
  551. TQRect tileRect = tile->rect();
  552. move( tileRect.x(), tileRect.y() );
  553. setSize( tileRect.width(), tileRect.height() );
  554. if ( ! isVisible() )
  555. show();
  556. }
  557. else
  558. {
  559. if ( isVisible() )
  560. hide();
  561. }
  562. }
  563. #include "ktreemapview.moc"
  564. // EOF