PageRenderTime 25ms CodeModel.GetById 12ms RepoModel.GetById 1ms app.codeStats 0ms

/fltk/src/Fl_Tree.cxx

http://luafltk.googlecode.com/
C++ | 431 lines | 290 code | 26 blank | 115 comment | 77 complexity | c662eb981b5b4f3233aba6ac25034d1f MD5 | raw file
Possible License(s): LGPL-2.0, LGPL-3.0, 0BSD
  1. //
  2. // "$Id: Fl_Tree.cxx 7604 2010-05-12 04:59:52Z greg.ercolano $"
  3. //
  4. #include <stdio.h>
  5. #include <stdlib.h>
  6. #include <string.h>
  7. #include <FL/Fl_Tree.H>
  8. #define SCROLL_W 15
  9. //////////////////////
  10. // Fl_Tree.cxx
  11. //////////////////////
  12. //
  13. // Fl_Tree -- This file is part of the Fl_Tree widget for FLTK
  14. // Copyright (C) 2009 by Greg Ercolano.
  15. //
  16. // This library is free software; you can redistribute it and/or
  17. // modify it under the terms of the GNU Library General Public
  18. // License as published by the Free Software Foundation; either
  19. // version 2 of the License, or (at your option) any later version.
  20. //
  21. // This library is distributed in the hope that it will be useful,
  22. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  23. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  24. // Library General Public License for more details.
  25. //
  26. // You should have received a copy of the GNU Library General Public
  27. // License along with this library; if not, write to the Free Software
  28. // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
  29. // USA.
  30. //
  31. // INTERNAL: scroller callback
  32. static void scroll_cb(Fl_Widget*,void *data) {
  33. ((Fl_Tree*)data)->redraw();
  34. }
  35. // INTERNAL: Parse elements from path into an array of null terminated strings
  36. // Path="/aa/bb"
  37. // Return: arr[0]="aa", arr[1]="bb", arr[2]=0
  38. // Caller must: free(arr[0]); free(arr);
  39. //
  40. static char **parse_path(const char *path) {
  41. while ( *path == '/' ) path++; // skip leading '/'
  42. // First pass: identify, null terminate, and count separators
  43. int seps = 1; // separator count (1: first item)
  44. int arrsize = 1; // array size (1: first item)
  45. char *save = strdup(path); // make copy we can modify
  46. char *s = save;
  47. while ( ( s = strchr(s, '/') ) ) {
  48. while ( *s == '/' ) { *s++ = 0; seps++; }
  49. if ( *s ) { arrsize++; }
  50. }
  51. arrsize++; // (room for terminating NULL)
  52. // Second pass: create array, save nonblank elements
  53. char **arr = (char**)malloc(sizeof(char*) * arrsize);
  54. int t = 0;
  55. s = save;
  56. while ( seps-- > 0 ) {
  57. if ( *s ) { arr[t++] = s; } // skips empty fields, eg. '//'
  58. s += (strlen(s) + 1);
  59. }
  60. arr[t] = 0;
  61. return(arr);
  62. }
  63. // INTERNAL: Recursively descend tree hierarchy, accumulating total child count
  64. static int find_total_children(Fl_Tree_Item *item, int count=0) {
  65. count++;
  66. for ( int t=0; t<item->children(); t++ ) {
  67. count = find_total_children(item->child(t), count);
  68. }
  69. return(count);
  70. }
  71. /// Constructor.
  72. Fl_Tree::Fl_Tree(int X, int Y, int W, int H, const char *L) : Fl_Group(X,Y,W,H,L) {
  73. _root = new Fl_Tree_Item(_prefs);
  74. _root->parent(0); // we are root of tree
  75. _root->label("ROOT");
  76. _item_clicked = 0;
  77. box(FL_DOWN_BOX);
  78. color(FL_WHITE);
  79. when(FL_WHEN_CHANGED);
  80. _vscroll = new Fl_Scrollbar(0,0,0,0); // will be resized by draw()
  81. _vscroll->hide();
  82. _vscroll->type(FL_VERTICAL);
  83. _vscroll->step(1);
  84. _vscroll->callback(scroll_cb, (void*)this);
  85. end();
  86. }
  87. /// Destructor.
  88. Fl_Tree::~Fl_Tree() {
  89. if ( _root ) { delete _root; _root = 0; }
  90. }
  91. /// Adds a new item, given a 'menu style' path, eg: "/Parent/Child/item".
  92. /// Any parent nodes that don't already exist are created automatically.
  93. /// Adds the item based on the value of sortorder().
  94. /// \returns the child item created, or 0 on error.
  95. ///
  96. Fl_Tree_Item* Fl_Tree::add(const char *path) {
  97. if ( ! _root ) { // Create root if none
  98. _root = new Fl_Tree_Item(_prefs);
  99. _root->parent(0);
  100. _root->label("ROOT");
  101. }
  102. char **arr = parse_path(path);
  103. Fl_Tree_Item *item = _root->add(_prefs, arr);
  104. free((void*)arr[0]);
  105. free((void*)arr);
  106. return(item);
  107. }
  108. /// Inserts a new item above the specified Fl_Tree_Item, with the label set to 'name'.
  109. /// \returns the item that was added, or 0 if 'above' could not be found.
  110. ///
  111. Fl_Tree_Item* Fl_Tree::insert_above(Fl_Tree_Item *above, const char *name) {
  112. return(above->insert_above(_prefs, name));
  113. }
  114. /// Insert a new item into a tree-item's children at a specified position.
  115. /// \returns the item that was added.
  116. Fl_Tree_Item* Fl_Tree::insert(Fl_Tree_Item *item, const char *name, int pos) {
  117. return(item->insert(_prefs, name, pos));
  118. }
  119. /// Add a new child to a tree-item.
  120. /// \returns the item that was added.
  121. Fl_Tree_Item* Fl_Tree::add(Fl_Tree_Item *item, const char *name) {
  122. return(item->add(_prefs, name));
  123. }
  124. /// Find the item, given a menu style path, eg: "/Parent/Child/item".
  125. ///
  126. /// There is both a const and non-const version of this method.
  127. /// Const version allows pure const methods to use this method
  128. /// to do lookups without causing compiler errors.
  129. /// \returns the item, or 0 if not found.
  130. ///
  131. Fl_Tree_Item *Fl_Tree::find_item(const char *path) {
  132. if ( ! _root ) return(0);
  133. char **arr = parse_path(path);
  134. Fl_Tree_Item *item = _root->find_item(arr);
  135. free((void*)arr[0]);
  136. free((void*)arr);
  137. return(item);
  138. }
  139. /// A const version of Fl_Tree::find_item(const char *path)
  140. const Fl_Tree_Item *Fl_Tree::find_item(const char *path) const {
  141. if ( ! _root ) return(0);
  142. char **arr = parse_path(path);
  143. const Fl_Tree_Item *item = _root->find_item(arr);
  144. free((void*)arr[0]);
  145. free((void*)arr);
  146. return(item);
  147. }
  148. /// Standard FLTK draw() method, handles draws the tree widget.
  149. void Fl_Tree::draw() {
  150. // Let group draw box+label but *NOT* children.
  151. // We handle drawing children ourselves by calling each item's draw()
  152. //
  153. Fl_Group::draw_box();
  154. Fl_Group::draw_label();
  155. if ( ! _root ) return;
  156. int cx = x() + Fl::box_dx(box());
  157. int cy = y() + Fl::box_dy(box());
  158. int cw = w() - Fl::box_dw(box());
  159. int ch = h() - Fl::box_dh(box());
  160. // These values are changed during drawing
  161. // 'Y' will be the lowest point on the tree
  162. int X = cx + _prefs.marginleft();
  163. int Y = cy + _prefs.margintop() - (_vscroll->visible() ? _vscroll->value() : 0);
  164. int W = cw - _prefs.marginleft(); // - _prefs.marginright();
  165. int Ysave = Y;
  166. fl_push_clip(cx,cy,cw,ch);
  167. {
  168. fl_font(_prefs.labelfont(), _prefs.labelsize());
  169. _root->draw(X, Y, W, this, _prefs);
  170. }
  171. fl_pop_clip();
  172. // Show vertical scrollbar?
  173. int ydiff = (Y+_prefs.margintop())-Ysave; // ydiff=size of tree
  174. int ytoofar = (cy+ch) - Y; // ytoofar -- scrolled beyond bottom (eg. stow)
  175. //printf("ydiff=%d ch=%d Ysave=%d ytoofar=%d value=%d\n",
  176. //int(ydiff),int(ch),int(Ysave),int(ytoofar), int(_vscroll->value()));
  177. if ( ytoofar > 0 ) ydiff += ytoofar;
  178. if ( Ysave<cy || ydiff > ch || int(_vscroll->value()) > 1 ) {
  179. _vscroll->visible();
  180. int sx = x()+w()-Fl::box_dx(box())-SCROLL_W;
  181. int sy = y()+Fl::box_dy(box());
  182. int sw = SCROLL_W;
  183. int sh = h()-Fl::box_dh(box());
  184. _vscroll->show();
  185. _vscroll->range(0.0,ydiff-ch);
  186. _vscroll->resize(sx,sy,sw,sh);
  187. _vscroll->slider_size(float(ch)/float(ydiff));
  188. } else {
  189. _vscroll->Fl_Slider::value(0);
  190. _vscroll->hide();
  191. }
  192. fl_push_clip(cx,cy,cw,ch);
  193. Fl_Group::draw_children(); // draws any FLTK children set via Fl_Tree::widget()
  194. fl_pop_clip();
  195. }
  196. /// Standard FLTK event handler for this widget.
  197. int Fl_Tree::handle(int e) {
  198. static Fl_Tree_Item *lastselect = 0;
  199. int changed = 0;
  200. int ret = Fl_Group::handle(e);
  201. if ( ! _root ) return(ret);
  202. switch ( e ) {
  203. case FL_PUSH: {
  204. lastselect = 0;
  205. item_clicked(0); // assume no item was clicked
  206. Fl_Tree_Item *o = _root->find_clicked(_prefs);
  207. if ( o ) {
  208. ret |= 1; // handled
  209. if ( Fl::event_button() == FL_LEFT_MOUSE ) {
  210. // Was collapse icon clicked?
  211. if ( o->event_on_collapse_icon(_prefs) ) {
  212. o->open_toggle();
  213. redraw();
  214. }
  215. // Item's label clicked?
  216. else if ( o->event_on_label(_prefs) &&
  217. (!o->widget() || !Fl::event_inside(o->widget())) &&
  218. callback() &&
  219. (!_vscroll->visible() || !Fl::event_inside(_vscroll)) ) {
  220. item_clicked(o); // save item clicked
  221. // Handle selection behavior
  222. switch ( _prefs.selectmode() ) {
  223. case FL_TREE_SELECT_NONE: { // no selection changes
  224. break;
  225. }
  226. case FL_TREE_SELECT_SINGLE: {
  227. changed = select_only(o);
  228. break;
  229. }
  230. case FL_TREE_SELECT_MULTI: {
  231. int state = Fl::event_state();
  232. if ( state & FL_SHIFT ) {
  233. if ( ! o->is_selected() ) {
  234. o->select(); // add to selection
  235. changed = 1; // changed
  236. }
  237. } else if ( state & FL_CTRL ) {
  238. changed = 1; // changed
  239. o->select_toggle(); // toggle selection state
  240. lastselect = o; // save we toggled it (prevents oscillation)
  241. } else {
  242. changed = select_only(o);
  243. }
  244. break;
  245. }
  246. }
  247. if ( changed ) {
  248. redraw(); // make change(s) visible
  249. if ( when() & FL_WHEN_CHANGED ) {
  250. set_changed();
  251. do_callback((Fl_Widget*)this, user_data()); // item callback
  252. }
  253. }
  254. }
  255. }
  256. }
  257. break;
  258. }
  259. case FL_DRAG: {
  260. if ( Fl::event_button() != FL_LEFT_MOUSE ) break;
  261. Fl_Tree_Item *o = _root->find_clicked(_prefs);
  262. if ( o ) {
  263. ret |= 1; // handled
  264. // Item's label clicked?
  265. if ( o->event_on_label(_prefs) &&
  266. (!o->widget() || !Fl::event_inside(o->widget())) &&
  267. callback() &&
  268. (!_vscroll->visible() || !Fl::event_inside(_vscroll)) ) {
  269. item_clicked(o); // save item clicked
  270. // Handle selection behavior
  271. switch ( _prefs.selectmode() ) {
  272. case FL_TREE_SELECT_NONE: { // no selection changes
  273. break;
  274. }
  275. case FL_TREE_SELECT_SINGLE: {
  276. changed = select_only(o);
  277. break;
  278. }
  279. case FL_TREE_SELECT_MULTI: {
  280. int state = Fl::event_state();
  281. if ( state & FL_CTRL ) {
  282. if ( lastselect != o ) {// not already toggled from last microdrag?
  283. changed = 1; // changed
  284. o->select_toggle(); // toggle selection
  285. lastselect = o; // save we toggled it (prevents oscillation)
  286. }
  287. } else {
  288. if ( ! o->is_selected() ) {
  289. changed = 1; // changed
  290. o->select(); // select this
  291. }
  292. }
  293. break;
  294. }
  295. }
  296. if ( changed ) {
  297. redraw(); // make change(s) visible
  298. if ( when() & FL_WHEN_CHANGED ) {
  299. set_changed();
  300. do_callback((Fl_Widget*)this, user_data()); // item callback
  301. }
  302. }
  303. }
  304. }
  305. break;
  306. }
  307. case FL_RELEASE: {
  308. if ( Fl::event_button() == FL_LEFT_MOUSE ) {
  309. ret |= 1;
  310. if ( when() & FL_WHEN_RELEASE || ( this->changed() && (when() & FL_WHEN_CHANGED)) ) {
  311. do_callback((Fl_Widget*)this, user_data()); // item callback
  312. }
  313. }
  314. break;
  315. }
  316. }
  317. return(ret);
  318. }
  319. /// Deselect item and all its children.
  320. /// If item is NULL, root() is used.
  321. /// Handles calling redraw() if anything was changed.
  322. /// Returns count of how many items were in the 'selected' state,
  323. /// ie. how many items were "changed".
  324. ///
  325. /// \p docallback is an optional paramemter that can either be 0 or 1:
  326. ///
  327. /// - 0 - the callback() is not invoked (default)
  328. /// - 1 - the callback() is invoked once if \b any items changed state,
  329. /// and item_clicked() will be NULL (since many items could have been changed).
  330. //
  331. /// \todo deselect_all()'s docallback should support '2' (invoke callback for each item changed)
  332. ///
  333. int Fl_Tree::deselect_all(Fl_Tree_Item *item, int docallback) {
  334. item = item ? item : root(); // NULL? use root()
  335. int count = item->deselect_all();
  336. if ( count ) {
  337. redraw(); // anything changed? cause redraw
  338. if ( docallback == 1 )
  339. do_callback_for_item(0);
  340. }
  341. return(count);
  342. }
  343. /// Select item and all its children.
  344. /// If item is NULL, root() is used.
  345. /// Handles calling redraw() if anything was changed.
  346. /// Returns count of how many items were in the 'deselected' state,
  347. /// ie. how many items were "changed".
  348. ///
  349. /// \p docallback is an optional paramemter that can either be 0 or 1:
  350. ///
  351. /// - 0 - the callback() is not invoked (default)
  352. /// - 1 - the callback() is invoked once if \b any items changed state,
  353. /// and item_clicked() will be NULL (since many items could have been changed).
  354. ///
  355. /// \todo select_all()'s docallback should support '2' (invoke callback for each item changed)
  356. ///
  357. int Fl_Tree::select_all(Fl_Tree_Item *item, int docallback) {
  358. item = item ? item : root(); // NULL? use root()
  359. int count = item->select_all();
  360. if ( count ) {
  361. redraw(); // anything changed? cause redraw
  362. if (docallback == 1)
  363. do_callback_for_item(0);
  364. }
  365. return(count);
  366. }
  367. /// Select only this item.
  368. /// If item is NULL, root() is used.
  369. /// Handles calling redraw() if anything was changed.
  370. /// Returns how many items were changed, if any.
  371. ///
  372. /// \p docallback is an optional paramemter that can either be 0, 1 or 2:
  373. ///
  374. /// - 0 - the callback() is not invoked (default)
  375. /// - 1 - the callback() is invoked once if \b any items changed state,
  376. /// and item_clicked() will be NULL (since many items could have been changed).
  377. /// - 2 - the callback() is invoked once for \b each item that changed state,
  378. /// and the callback() can use item_clicked() to determine the item changed.
  379. ///
  380. int Fl_Tree::select_only(Fl_Tree_Item *selitem, int docallback) {
  381. selitem = selitem ? selitem : root(); // NULL? use root()
  382. int changed = 0;
  383. for ( Fl_Tree_Item *item = first(); item; item = item->next() ) {
  384. if ( item == selitem ) {
  385. if ( item->is_selected() ) continue; // don't count if already selected
  386. item->select();
  387. ++changed;
  388. if ( docallback == 2 ) do_callback_for_item(item);
  389. } else {
  390. if ( item->is_selected() ) {
  391. item->deselect();
  392. ++changed;
  393. if ( docallback == 2 ) do_callback_for_item(item);
  394. }
  395. }
  396. }
  397. if ( changed ) {
  398. redraw(); // anything changed? redraw
  399. if ( docallback == 1 ) do_callback_for_item(0);
  400. }
  401. return(changed);
  402. }
  403. //
  404. // End of "$Id: Fl_Tree.cxx 7604 2010-05-12 04:59:52Z greg.ercolano $".
  405. //