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.

977 lines
33KB

  1. //
  2. // "$Id: Fl_Tree.cxx 8632 2011-05-04 02:59:50Z greg.ercolano $"
  3. //
  4. #include <stdio.h>
  5. #include <stdlib.h>
  6. #include <string.h>
  7. #include <FL/Fl_Tree.H>
  8. #include <FL/Fl_Preferences.H>
  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-2010 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. // Handles escape characters.
  37. // Path="/aa/bb"
  38. // Return: arr[0]="aa", arr[1]="bb", arr[2]=0
  39. // Caller must call free_path(arr).
  40. //
  41. static char **parse_path(const char *path) {
  42. while ( *path == '/' ) path++; // skip leading '/'
  43. // First pass: identify, null terminate, and count separators
  44. int seps = 1; // separator count (1: first item)
  45. int arrsize = 1; // array size (1: first item)
  46. char *save = strdup(path); // make copy we can modify
  47. char *sin = save, *sout = save;
  48. while ( *sin ) {
  49. if ( *sin == '\\' ) { // handle escape character
  50. *sout++ = *++sin;
  51. if ( *sin ) ++sin;
  52. } else if ( *sin == '/' ) { // handle submenu
  53. *sout++ = 0;
  54. sin++;
  55. seps++;
  56. arrsize++;
  57. } else { // all other chars
  58. *sout++ = *sin++;
  59. }
  60. }
  61. *sout = 0;
  62. arrsize++; // (room for terminating NULL)
  63. // Second pass: create array, save nonblank elements
  64. char **arr = (char**)malloc(sizeof(char*) * arrsize);
  65. int t = 0;
  66. sin = save;
  67. while ( seps-- > 0 ) {
  68. if ( *sin ) { arr[t++] = sin; } // skips empty fields, e.g. '//'
  69. sin += (strlen(sin) + 1);
  70. }
  71. arr[t] = 0;
  72. return(arr);
  73. }
  74. // INTERNAL: Free the array returned by parse_path()
  75. static void free_path(char **arr) {
  76. if ( arr ) {
  77. if ( arr[0] ) { free((void*)arr[0]); }
  78. free((void*)arr);
  79. }
  80. }
  81. // INTERNAL: Recursively descend tree hierarchy, accumulating total child count
  82. static int find_total_children(Fl_Tree_Item *item, int count=0) {
  83. count++;
  84. for ( int t=0; t<item->children(); t++ ) {
  85. count = find_total_children(item->child(t), count);
  86. }
  87. return(count);
  88. }
  89. /// Constructor.
  90. Fl_Tree::Fl_Tree(int X, int Y, int W, int H, const char *L) : Fl_Group(X,Y,W,H,L) {
  91. _root = new Fl_Tree_Item(_prefs);
  92. _root->parent(0); // we are root of tree
  93. _root->label("ROOT");
  94. _item_focus = 0;
  95. _callback_item = 0;
  96. _callback_reason = FL_TREE_REASON_NONE;
  97. _scrollbar_size = 0; // 0: uses Fl::scrollbar_size()
  98. box(FL_DOWN_BOX);
  99. color(FL_BACKGROUND2_COLOR, FL_SELECTION_COLOR);
  100. when(FL_WHEN_CHANGED);
  101. _vscroll = new Fl_Scrollbar(0,0,0,0); // will be resized by draw()
  102. _vscroll->hide();
  103. _vscroll->type(FL_VERTICAL);
  104. _vscroll->step(1);
  105. _vscroll->callback(scroll_cb, (void*)this);
  106. end();
  107. }
  108. /// Destructor.
  109. Fl_Tree::~Fl_Tree() {
  110. if ( _root ) { delete _root; _root = 0; }
  111. }
  112. /// Adds a new item, given a 'menu style' path, eg: "/Parent/Child/item".
  113. /// Any parent nodes that don't already exist are created automatically.
  114. /// Adds the item based on the value of sortorder().
  115. ///
  116. /// To specify items or submenus that contain slashes ('/' or '\')
  117. /// use an escape character to protect them, e.g.
  118. ///
  119. /// \code
  120. /// tree->add("/Holidays/Photos/12\\/25\\2010"); // Adds item "12/25/2010"
  121. /// tree->add("/Pathnames/c:\\\\Program Files\\\\MyApp"); // Adds item "c:\Program Files\MyApp"
  122. /// \endcode
  123. ///
  124. /// \returns the child item created, or 0 on error.
  125. ///
  126. Fl_Tree_Item* Fl_Tree::add(const char *path) {
  127. if ( ! _root ) { // Create root if none
  128. _root = new Fl_Tree_Item(_prefs);
  129. _root->parent(0);
  130. _root->label("ROOT");
  131. }
  132. char **arr = parse_path(path);
  133. Fl_Tree_Item *item = _root->add(_prefs, arr);
  134. free_path(arr);
  135. return(item);
  136. }
  137. /// Inserts a new item above the specified Fl_Tree_Item, with the label set to 'name'.
  138. /// \param[in] above -- the item above which to insert the new item. Must not be NULL.
  139. /// \param[in] name -- the name of the new item
  140. /// \returns the item that was added, or 0 if 'above' could not be found.
  141. ///
  142. Fl_Tree_Item* Fl_Tree::insert_above(Fl_Tree_Item *above, const char *name) {
  143. return(above->insert_above(_prefs, name));
  144. }
  145. /// Insert a new item into a tree-item's children at a specified position.
  146. ///
  147. /// \param[in] item The existing item to insert new child into. Must not be NULL.
  148. /// \param[in] name The label for the new item
  149. /// \param[in] pos The position of the new item in the child list
  150. /// \returns the item that was added.
  151. ///
  152. Fl_Tree_Item* Fl_Tree::insert(Fl_Tree_Item *item, const char *name, int pos) {
  153. return(item->insert(_prefs, name, pos));
  154. }
  155. /// Add a new child to a tree-item.
  156. ///
  157. /// \param[in] item The existing item to add new child to. Must not be NULL.
  158. /// \param[in] name The label for the new item
  159. /// \returns the item that was added.
  160. ///
  161. Fl_Tree_Item* Fl_Tree::add(Fl_Tree_Item *item, const char *name) {
  162. return(item->add(_prefs, name));
  163. }
  164. /// Find the item, given a menu style path, eg: "/Parent/Child/item".
  165. /// There is both a const and non-const version of this method.
  166. /// Const version allows pure const methods to use this method
  167. /// to do lookups without causing compiler errors.
  168. ///
  169. /// To specify items or submenus that contain slashes ('/' or '\')
  170. /// use an escape character to protect them, e.g.
  171. ///
  172. /// \code
  173. /// tree->add("/Holidays/Photos/12\\/25\\2010"); // Adds item "12/25/2010"
  174. /// tree->add("/Pathnames/c:\\\\Program Files\\\\MyApp"); // Adds item "c:\Program Files\MyApp"
  175. /// \endcode
  176. ///
  177. /// \param[in] path -- the tree item's pathname to be found (e.g. "Flintstones/Fred")
  178. /// \returns the item, or NULL if not found.
  179. ///
  180. /// \see item_pathname()
  181. ///
  182. Fl_Tree_Item *Fl_Tree::find_item(const char *path) {
  183. if ( ! _root ) return(NULL);
  184. char **arr = parse_path(path);
  185. Fl_Tree_Item *item = _root->find_item(arr);
  186. free_path(arr);
  187. return(item);
  188. }
  189. /// A const version of Fl_Tree::find_item(const char *path)
  190. const Fl_Tree_Item *Fl_Tree::find_item(const char *path) const {
  191. if ( ! _root ) return(NULL);
  192. char **arr = parse_path(path);
  193. const Fl_Tree_Item *item = _root->find_item(arr);
  194. free_path(arr);
  195. return(item);
  196. }
  197. // Handle safe 'reverse string concatenation'.
  198. // In the following we build the pathname from right-to-left,
  199. // since we start at the child and work our way up to the root.
  200. //
  201. #define SAFE_RCAT(c) { \
  202. slen += 1; if ( slen >= pathnamelen ) { pathname[0] = '\0'; return(-2); } \
  203. *s-- = c; \
  204. }
  205. /// Find the pathname for the specified \p item.
  206. /// If \p item is NULL, root() is used.
  207. /// The tree's root will be included in the pathname of showroot() is on.
  208. /// Menu items or submenus that contain slashes ('/' or '\') in their names
  209. /// will be escaped with a backslash. This is symmetrical with the add()
  210. /// function which uses the same escape pattern to set names.
  211. /// \param[in] pathname The string to use to return the pathname
  212. /// \param[in] pathnamelen The maximum length of the string (including NULL). Must not be zero.
  213. /// \param[in] item The item whose pathname is to be returned.
  214. /// \returns
  215. /// - 0 : OK (\p pathname returns the item's pathname)
  216. /// - -1 : item not found (pathname="")
  217. /// - -2 : pathname not large enough (pathname="")
  218. /// \see find_item()
  219. ///
  220. int Fl_Tree::item_pathname(char *pathname, int pathnamelen, const Fl_Tree_Item *item) const {
  221. pathname[0] = '\0';
  222. item = item ? item : _root;
  223. if ( !item ) return(-1);
  224. // Build pathname starting at end
  225. char *s = (pathname+pathnamelen-1);
  226. int slen = 0; // length of string compiled so far (including NULL)
  227. SAFE_RCAT('\0');
  228. while ( item ) {
  229. if ( item->is_root() && showroot() == 0 ) break; // don't include root in path if showroot() off
  230. // Find name of current item
  231. const char *name = item->label() ? item->label() : "???"; // name for this item
  232. int len = strlen(name);
  233. // Add name to end of pathname[]
  234. for ( --len; len>=0; len-- ) {
  235. SAFE_RCAT(name[len]); // rcat name of item
  236. if ( name[len] == '/' || name[len] == '\\' ) {
  237. SAFE_RCAT('\\'); // escape front or back slashes within name
  238. }
  239. }
  240. SAFE_RCAT('/'); // rcat leading slash
  241. item = item->parent(); // move up tree (NULL==root)
  242. }
  243. if ( *(++s) == '/' ) ++s; // leave off leading slash from pathname
  244. if ( s != pathname ) memmove(pathname, s, slen); // Shift down right-aligned string
  245. return(0);
  246. }
  247. /// Standard FLTK draw() method, handles draws the tree widget.
  248. void Fl_Tree::draw() {
  249. // Let group draw box+label but *NOT* children.
  250. // We handle drawing children ourselves by calling each item's draw()
  251. //
  252. // Handle group's bg
  253. Fl_Group::draw_box();
  254. Fl_Group::draw_label();
  255. // Handle tree
  256. if ( ! _root ) return;
  257. int cx = x() + Fl::box_dx(box());
  258. int cy = y() + Fl::box_dy(box());
  259. int cw = w() - Fl::box_dw(box());
  260. int ch = h() - Fl::box_dh(box());
  261. // These values are changed during drawing
  262. // 'Y' will be the lowest point on the tree
  263. int X = cx + _prefs.marginleft();
  264. int Y = cy + _prefs.margintop() - (_vscroll->visible() ? _vscroll->value() : 0);
  265. int W = cw - _prefs.marginleft(); // - _prefs.marginright();
  266. int Ysave = Y;
  267. fl_push_clip(cx,cy,cw,ch);
  268. {
  269. fl_font(_prefs.labelfont(), _prefs.labelsize());
  270. _root->draw(X, Y, W, this,
  271. (Fl::focus()==this)?_item_focus:0, // show focus item ONLY if Fl_Tree has focus
  272. _prefs);
  273. }
  274. fl_pop_clip();
  275. // Show vertical scrollbar?
  276. int ydiff = (Y+_prefs.margintop())-Ysave; // ydiff=size of tree
  277. int ytoofar = (cy+ch) - Y; // ytoofar -- scrolled beyond bottom (e.g. stow)
  278. //printf("ydiff=%d ch=%d Ysave=%d ytoofar=%d value=%d\n",
  279. //int(ydiff),int(ch),int(Ysave),int(ytoofar), int(_vscroll->value()));
  280. if ( ytoofar > 0 ) ydiff += ytoofar;
  281. if ( Ysave<cy || ydiff > ch || int(_vscroll->value()) > 1 ) {
  282. _vscroll->visible();
  283. int scrollsize = _scrollbar_size ? _scrollbar_size : Fl::scrollbar_size();
  284. int sx = x()+w()-Fl::box_dx(box())-scrollsize;
  285. int sy = y()+Fl::box_dy(box());
  286. int sw = scrollsize;
  287. int sh = h()-Fl::box_dh(box());
  288. _vscroll->show();
  289. _vscroll->range(0.0,ydiff-ch);
  290. _vscroll->resize(sx,sy,sw,sh);
  291. _vscroll->slider_size(float(ch)/float(ydiff));
  292. } else {
  293. _vscroll->Fl_Slider::value(0);
  294. _vscroll->hide();
  295. }
  296. fl_push_clip(cx,cy,cw,ch);
  297. Fl_Group::draw_children(); // draws any FLTK children set via Fl_Tree::widget()
  298. fl_pop_clip();
  299. }
  300. /// Returns next visible item above (dir==Fl_Up) or below (dir==Fl_Down) the specified \p item.
  301. /// If \p item is 0, returns first() if \p dir is Fl_Up, or last() if \p dir is FL_Down.
  302. ///
  303. /// \param[in] item The item above/below which we'll find the next visible item
  304. /// \param[in] dir The direction to search. Can be FL_Up or FL_Down.
  305. /// \returns The item found, or 0 if there's no visible items above/below the specified \p item.
  306. ///
  307. Fl_Tree_Item *Fl_Tree::next_visible_item(Fl_Tree_Item *item, int dir) {
  308. if ( ! item ) { // no start item?
  309. item = ( dir == FL_Up ) ? last() : first(); // start at top or bottom
  310. if ( ! item ) return(0);
  311. if ( item->visible_r() ) return(item); // return first/last visible item
  312. }
  313. switch ( dir ) {
  314. case FL_Up: return(item->prev_displayed(_prefs));
  315. case FL_Down: return(item->next_displayed(_prefs));
  316. default: return(item->next_displayed(_prefs));
  317. }
  318. }
  319. /// Set the item that currently should have keyboard focus.
  320. /// Handles calling redraw() to update the focus box (if it is visible).
  321. ///
  322. /// \param[in] item The item that should take focus. If NULL, none will have focus.
  323. ///
  324. void Fl_Tree::set_item_focus(Fl_Tree_Item *item) {
  325. if ( _item_focus != item ) { // changed?
  326. _item_focus = item; // update
  327. if ( visible_focus() ) redraw(); // redraw to update focus box
  328. }
  329. }
  330. /// Find the item that was clicked.
  331. /// You should use callback_item() instead, which is fast,
  332. /// and is meant to be used within a callback to determine the item clicked.
  333. ///
  334. /// This method walks the entire tree looking for the first item that is
  335. /// under the mouse (ie. at Fl::event_x()/Fl:event_y().
  336. ///
  337. /// Use this method /only/ if you've subclassed Fl_Tree, and are receiving
  338. /// events before Fl_Tree has been able to process and update callback_item().
  339. ///
  340. /// \returns the item clicked, or 0 if no item was under the current event.
  341. ///
  342. const Fl_Tree_Item* Fl_Tree::find_clicked() const {
  343. if ( ! _root ) return(NULL);
  344. return(_root->find_clicked(_prefs));
  345. }
  346. /// Set the item that was last clicked.
  347. /// Should only be used by subclasses needing to change this value.
  348. /// Normally Fl_Tree manages this value.
  349. ///
  350. /// Deprecated: use callback_item() instead.
  351. ///
  352. void Fl_Tree::item_clicked(Fl_Tree_Item* val) {
  353. _callback_item = val;
  354. }
  355. /// Returns the first item in the tree.
  356. ///
  357. /// Use this to walk the tree in the forward direction, eg:
  358. /// \code
  359. /// for ( Fl_Tree_Item *item = tree->first(); item; item = tree->next(item) ) {
  360. /// printf("Item: %s\n", item->label());
  361. /// }
  362. /// \endcode
  363. ///
  364. /// \returns first item in tree, or 0 if none (tree empty).
  365. /// \see first(),next(),last(),prev()
  366. ///
  367. Fl_Tree_Item* Fl_Tree::first() {
  368. return(_root); // first item always root
  369. }
  370. /// Return the next item after \p item, or 0 if no more items.
  371. ///
  372. /// Use this code to walk the entire tree:
  373. /// \code
  374. /// for ( Fl_Tree_Item *item = tree->first(); item; item = tree->next(item) ) {
  375. /// printf("Item: %s\n", item->label());
  376. /// }
  377. /// \endcode
  378. ///
  379. /// \param[in] item The item to use to find the next item. If NULL, returns 0.
  380. /// \returns Next item in tree, or 0 if at last item.
  381. ///
  382. /// \see first(),next(),last(),prev()
  383. ///
  384. Fl_Tree_Item *Fl_Tree::next(Fl_Tree_Item *item) {
  385. if ( ! item ) return(0);
  386. return(item->next());
  387. }
  388. /// Return the previous item before \p item, or 0 if no more items.
  389. ///
  390. /// This can be used to walk the tree in reverse, eg:
  391. ///
  392. /// \code
  393. /// for ( Fl_Tree_Item *item = tree->first(); item; item = tree->prev(item) ) {
  394. /// printf("Item: %s\n", item->label());
  395. /// }
  396. /// \endcode
  397. ///
  398. /// \param[in] item The item to use to find the previous item. If NULL, returns 0.
  399. /// \returns Previous item in tree, or 0 if at first item.
  400. ///
  401. /// \see first(),next(),last(),prev()
  402. ///
  403. Fl_Tree_Item *Fl_Tree::prev(Fl_Tree_Item *item) {
  404. if ( ! item ) return(0);
  405. return(item->prev());
  406. }
  407. /// Returns the last item in the tree.
  408. ///
  409. /// This can be used to walk the tree in reverse, eg:
  410. ///
  411. /// \code
  412. /// for ( Fl_Tree_Item *item = tree->last(); item; item = tree->prev() ) {
  413. /// printf("Item: %s\n", item->label());
  414. /// }
  415. /// \endcode
  416. ///
  417. /// \returns last item in the tree, or 0 if none (tree empty).
  418. ///
  419. /// \see first(),next(),last(),prev()
  420. ///
  421. Fl_Tree_Item* Fl_Tree::last() {
  422. if ( ! _root ) return(0);
  423. Fl_Tree_Item *item = _root;
  424. while ( item->has_children() ) {
  425. item = item->child(item->children()-1);
  426. }
  427. return(item);
  428. }
  429. /// Returns the first selected item in the tree.
  430. ///
  431. /// Use this to walk the tree looking for all the selected items, eg:
  432. ///
  433. /// \code
  434. /// for ( Fl_Tree_Item *item = tree->first_selected_item(); item; item = tree->next_selected_item(item) ) {
  435. /// printf("Item: %s\n", item->label());
  436. /// }
  437. /// \endcode
  438. ///
  439. /// \returns The next selected item, or 0 if there are no more selected items.
  440. ///
  441. Fl_Tree_Item *Fl_Tree::first_selected_item() {
  442. return(next_selected_item(0));
  443. }
  444. /// Returns the next selected item after \p item.
  445. /// If \p item is 0, search starts at the first item (root).
  446. ///
  447. /// Use this to walk the tree looking for all the selected items, eg:
  448. /// \code
  449. /// for ( Fl_Tree_Item *item = tree->first_selected_item(); item; item = tree->next_selected_item(item) ) {
  450. /// printf("Item: %s\n", item->label());
  451. /// }
  452. /// \endcode
  453. ///
  454. /// \param[in] item The item to use to find the next selected item. If NULL, first() is used.
  455. /// \returns The next selected item, or 0 if there are no more selected items.
  456. ///
  457. Fl_Tree_Item *Fl_Tree::next_selected_item(Fl_Tree_Item *item) {
  458. if ( ! item ) {
  459. if ( ! (item = first()) ) return(0);
  460. if ( item->is_selected() ) return(item);
  461. }
  462. while ( (item = item->next()) )
  463. if ( item->is_selected() )
  464. return(item);
  465. return(0);
  466. }
  467. /// Standard FLTK event handler for this widget.
  468. int Fl_Tree::handle(int e) {
  469. int ret = 0;
  470. // Developer note: Fl_Browser_::handle() used for reference here..
  471. // #include <FL/names.h> // for event debugging
  472. // fprintf(stderr, "DEBUG: %s (%d)\n", fl_eventnames[e], e);
  473. if (e == FL_ENTER || e == FL_LEAVE) return(1);
  474. switch (e) {
  475. case FL_FOCUS: {
  476. // FLTK tests if we want focus.
  477. // If a nav key was used to give us focus, and we've got no saved
  478. // focus widget, determine which item gets focus depending on nav key.
  479. //
  480. if ( ! _item_focus ) { // no focus established yet?
  481. switch (Fl::event_key()) { // determine if focus was navigated..
  482. case FL_Tab: { // received focus via TAB?
  483. if ( Fl::event_state(FL_SHIFT) ) { // SHIFT-TAB similar to FL_Up
  484. set_item_focus(next_visible_item(0, FL_Up));
  485. } else { // TAB similar to FL_Down
  486. set_item_focus(next_visible_item(0, FL_Down));
  487. }
  488. break;
  489. }
  490. case FL_Left: // received focus via LEFT or UP?
  491. case FL_Up: { // XK_ISO_Left_Tab
  492. set_item_focus(next_visible_item(0, FL_Up));
  493. break;
  494. }
  495. case FL_Right: // received focus via RIGHT or DOWN?
  496. case FL_Down:
  497. default: {
  498. set_item_focus(next_visible_item(0, FL_Down));
  499. break;
  500. }
  501. }
  502. }
  503. if ( visible_focus() ) redraw(); // draw focus change
  504. return(1);
  505. }
  506. case FL_UNFOCUS: { // FLTK telling us some other widget took focus.
  507. if ( visible_focus() ) redraw(); // draw focus change
  508. return(1);
  509. }
  510. case FL_KEYBOARD: { // keyboard shortcut
  511. // Do shortcuts first or scrollbar will get them...
  512. if (_prefs.selectmode() > FL_TREE_SELECT_NONE ) {
  513. if ( !_item_focus ) {
  514. set_item_focus(first());
  515. }
  516. if ( _item_focus ) {
  517. int ekey = Fl::event_key();
  518. switch (ekey) {
  519. case FL_Enter: // ENTER: selects current item only
  520. case FL_KP_Enter:
  521. if ( when() & ~FL_WHEN_ENTER_KEY) {
  522. select_only(_item_focus);
  523. show_item(_item_focus); // STR #2426
  524. return(1);
  525. }
  526. break;
  527. case ' ': // toggle selection state
  528. switch ( _prefs.selectmode() ) {
  529. case FL_TREE_SELECT_NONE:
  530. break;
  531. case FL_TREE_SELECT_SINGLE:
  532. if ( ! _item_focus->is_selected() ) // not selected?
  533. select_only(_item_focus); // select only this
  534. else
  535. deselect_all(); // select nothing
  536. break;
  537. case FL_TREE_SELECT_MULTI:
  538. select_toggle(_item_focus);
  539. break;
  540. }
  541. break;
  542. case FL_Right: // open children (if any)
  543. case FL_Left: { // close children (if any)
  544. if ( _item_focus ) {
  545. if ( ekey == FL_Right && _item_focus->is_close() ) {
  546. // Open closed item
  547. open(_item_focus);
  548. redraw();
  549. ret = 1;
  550. } else if ( ekey == FL_Left && _item_focus->is_open() ) {
  551. // Close open item
  552. close(_item_focus);
  553. redraw();
  554. ret = 1;
  555. }
  556. return(1);
  557. }
  558. break;
  559. }
  560. case FL_Up: // next item up
  561. case FL_Down: { // next item down
  562. set_item_focus(next_visible_item(_item_focus, ekey)); // next item up|dn
  563. if ( _item_focus ) { // item in focus?
  564. // Autoscroll
  565. int itemtop = _item_focus->y();
  566. int itembot = _item_focus->y()+_item_focus->h();
  567. if ( itemtop < y() ) { show_item_top(_item_focus); }
  568. if ( itembot > y()+h() ) { show_item_bottom(_item_focus); }
  569. // Extend selection
  570. if ( _prefs.selectmode() == FL_TREE_SELECT_MULTI && // multiselect on?
  571. (Fl::event_state() & FL_SHIFT) && // shift key?
  572. ! _item_focus->is_selected() ) { // not already selected?
  573. select(_item_focus); // extend selection..
  574. }
  575. return(1);
  576. }
  577. break;
  578. }
  579. }
  580. }
  581. }
  582. break;
  583. }
  584. }
  585. // Let Fl_Group take a shot at handling the event
  586. if (Fl_Group::handle(e)) {
  587. return(1); // handled? don't continue below
  588. }
  589. // Handle events the child FLTK widgets didn't need
  590. static Fl_Tree_Item *lastselect = 0;
  591. // fprintf(stderr, "ERCODEBUG: Fl_Tree::handle(): Event was %s (%d)\n", fl_eventnames[e], e); // DEBUGGING
  592. if ( ! _root ) return(ret);
  593. switch ( e ) {
  594. case FL_PUSH: { // clicked on a tree item?
  595. if (Fl::visible_focus() && handle(FL_FOCUS)) {
  596. Fl::focus(this);
  597. }
  598. lastselect = 0;
  599. Fl_Tree_Item *o = _root->find_clicked(_prefs);
  600. if ( ! o ) break;
  601. set_item_focus(o); // becomes new focus widget
  602. redraw();
  603. ret |= 1; // handled
  604. if ( Fl::event_button() == FL_LEFT_MOUSE ) {
  605. if ( o->event_on_collapse_icon(_prefs) ) { // collapse icon clicked?
  606. open_toggle(o);
  607. } else if ( o->event_on_label(_prefs) && // label clicked?
  608. (!o->widget() || !Fl::event_inside(o->widget())) && // not inside widget
  609. (!_vscroll->visible() || !Fl::event_inside(_vscroll)) ) { // not on scroller
  610. switch ( _prefs.selectmode() ) {
  611. case FL_TREE_SELECT_NONE:
  612. break;
  613. case FL_TREE_SELECT_SINGLE:
  614. select_only(o);
  615. break;
  616. case FL_TREE_SELECT_MULTI: {
  617. if ( Fl::event_state() & FL_SHIFT ) { // SHIFT+PUSH?
  618. select(o); // add to selection
  619. } else if ( Fl::event_state() & FL_CTRL ) { // CTRL+PUSH?
  620. select_toggle(o); // toggle selection state
  621. lastselect = o; // save toggled item (prevent oscillation)
  622. } else {
  623. select_only(o);
  624. }
  625. break;
  626. }
  627. }
  628. }
  629. }
  630. break;
  631. }
  632. case FL_DRAG: {
  633. // do the scrolling first:
  634. int my = Fl::event_y();
  635. if ( my < y() ) { // above top?
  636. int p = vposition()-(y()-my);
  637. if ( p < 0 ) p = 0;
  638. vposition(p);
  639. } else if ( my > (y()+h()) ) { // below bottom?
  640. int p = vposition()+(my-y()-h());
  641. if ( p > (int)_vscroll->maximum() ) p = (int)_vscroll->maximum();
  642. vposition(p);
  643. }
  644. if ( Fl::event_button() != FL_LEFT_MOUSE ) break;
  645. Fl_Tree_Item *o = _root->find_clicked(_prefs);
  646. if ( ! o ) break;
  647. set_item_focus(o); // becomes new focus widget
  648. redraw();
  649. ret |= 1;
  650. // Item's label clicked?
  651. if ( o->event_on_label(_prefs) &&
  652. (!o->widget() || !Fl::event_inside(o->widget())) &&
  653. (!_vscroll->visible() || !Fl::event_inside(_vscroll)) ) {
  654. // Handle selection behavior
  655. switch ( _prefs.selectmode() ) {
  656. case FL_TREE_SELECT_NONE: break; // no selection changes
  657. case FL_TREE_SELECT_SINGLE:
  658. select_only(o);
  659. break;
  660. case FL_TREE_SELECT_MULTI:
  661. if ( Fl::event_state() & FL_CTRL && // CTRL-DRAG: toggle?
  662. lastselect != o ) { // not already toggled from last microdrag?
  663. select_toggle(o); // toggle selection
  664. lastselect = o; // save we toggled it (prevents oscillation)
  665. } else {
  666. select(o); // select this
  667. }
  668. break;
  669. }
  670. }
  671. break;
  672. }
  673. }
  674. return(ret);
  675. }
  676. /// Deselect \p item and all its children.
  677. /// If item is NULL, first() is used.
  678. /// Handles calling redraw() if anything was changed.
  679. /// Invokes the callback depending on the value of optional parameter \p docallback.
  680. ///
  681. /// The callback can use callback_item() and callback_reason() respectively to determine
  682. /// the item changed and the reason the callback was called.
  683. ///
  684. /// \param[in] item The item that will be deselected (along with all its children).
  685. /// If NULL, first() is used.
  686. /// \param[in] docallback -- A flag that determines if the callback() is invoked or not:
  687. /// - 0 - the callback() is not invoked
  688. /// - 1 - the callback() is invoked for each item that changed state,
  689. /// callback_reason() will be FL_TREE_REASON_DESELECTED
  690. ///
  691. /// \returns count of how many items were actually changed to the deselected state.
  692. ///
  693. int Fl_Tree::deselect_all(Fl_Tree_Item *item, int docallback) {
  694. item = item ? item : first(); // NULL? use first()
  695. if ( ! item ) return(0);
  696. int count = 0;
  697. // Deselect item
  698. if ( item->is_selected() )
  699. if ( deselect(item, docallback) )
  700. ++count;
  701. // Deselect its children
  702. for ( int t=0; t<item->children(); t++ ) {
  703. count += deselect_all(item->child(t), docallback); // recurse
  704. }
  705. return(count);
  706. }
  707. /// Select \p item and all its children.
  708. /// If item is NULL, first() is used.
  709. /// Handles calling redraw() if anything was changed.
  710. /// Invokes the callback depending on the value of optional parameter \p docallback.
  711. ///
  712. /// The callback can use callback_item() and callback_reason() respectively to determine
  713. /// the item changed and the reason the callback was called.
  714. ///
  715. /// \param[in] item The item that will be selected (along with all its children).
  716. /// If NULL, first() is used.
  717. /// \param[in] docallback -- A flag that determines if the callback() is invoked or not:
  718. /// - 0 - the callback() is not invoked
  719. /// - 1 - the callback() is invoked for each item that changed state,
  720. /// callback_reason() will be FL_TREE_REASON_SELECTED
  721. /// \returns count of how many items were actually changed to the selected state.
  722. ///
  723. int Fl_Tree::select_all(Fl_Tree_Item *item, int docallback) {
  724. item = item ? item : first(); // NULL? use first()
  725. if ( ! item ) return(0);
  726. int count = 0;
  727. // Select item
  728. if ( !item->is_selected() )
  729. if ( select(item, docallback) )
  730. ++count;
  731. // Select its children
  732. for ( int t=0; t<item->children(); t++ ) {
  733. count += select_all(item->child(t), docallback); // recurse
  734. }
  735. return(count);
  736. }
  737. /// Select only the specified \p item, deselecting all others that might be selected.
  738. /// If item is 0, first() is used.
  739. /// Handles calling redraw() if anything was changed.
  740. /// Invokes the callback depending on the value of optional parameter \p docallback.
  741. ///
  742. /// The callback can use callback_item() and callback_reason() respectively to determine
  743. /// the item changed and the reason the callback was called.
  744. ///
  745. /// \param[in] selitem The item to be selected. If NULL, first() is used.
  746. /// \param[in] docallback -- A flag that determines if the callback() is invoked or not:
  747. /// - 0 - the callback() is not invoked
  748. /// - 1 - the callback() is invoked for each item that changed state,
  749. /// callback_reason() will be either FL_TREE_REASON_SELECTED or
  750. /// FL_TREE_REASON_DESELECTED
  751. /// \returns the number of items whose selection states were changed, if any.
  752. ///
  753. int Fl_Tree::select_only(Fl_Tree_Item *selitem, int docallback) {
  754. selitem = selitem ? selitem : first(); // NULL? use first()
  755. if ( ! selitem ) return(0);
  756. int changed = 0;
  757. for ( Fl_Tree_Item *item = first(); item; item = item->next() ) {
  758. if ( item == selitem ) {
  759. if ( item->is_selected() ) continue; // don't count if already selected
  760. select(item, docallback);
  761. ++changed;
  762. } else {
  763. if ( item->is_selected() ) {
  764. deselect(item, docallback);
  765. ++changed;
  766. }
  767. }
  768. }
  769. return(changed);
  770. }
  771. /// Adjust the vertical scroll bar so that \p item is visible
  772. /// \p yoff pixels from the top of the Fl_Tree widget's display.
  773. ///
  774. /// For instance, yoff=0 will position the item at the top.
  775. ///
  776. /// If yoff is larger than the vertical scrollbar's limit,
  777. /// the value will be clipped. So if yoff=100, but scrollbar's max
  778. /// is 50, then 50 will be used.
  779. ///
  780. /// \param[in] item The item to be shown. If NULL, first() is used.
  781. /// \param[in] yoff The pixel offset from the top for the displayed position.
  782. ///
  783. /// \see show_item_top(), show_item_middle(), show_item_bottom()
  784. ///
  785. void Fl_Tree::show_item(Fl_Tree_Item *item, int yoff) {
  786. item = item ? item : first();
  787. if (!item) return;
  788. int newval = item->y() - y() - yoff + (int)_vscroll->value();
  789. if ( newval < _vscroll->minimum() ) newval = (int)_vscroll->minimum();
  790. if ( newval > _vscroll->maximum() ) newval = (int)_vscroll->maximum();
  791. _vscroll->value(newval);
  792. redraw();
  793. }
  794. /// See if \p item is currently displayed on-screen (visible within the widget).
  795. /// This can be used to detect if the item is scrolled off-screen.
  796. /// Checks to see if the item's vertical position is within the top and bottom
  797. /// edges of the display window. This does NOT take into account the hide()/show()
  798. /// or open()/close() status of the item.
  799. ///
  800. /// \param[in] item The item to be checked. If NULL, first() is used.
  801. /// \returns 1 if displayed, 0 if scrolled off screen or no items are in tree.
  802. ///
  803. int Fl_Tree::displayed(Fl_Tree_Item *item) {
  804. item = item ? item : first();
  805. if (!item) return(0);
  806. return( (item->y() >= y()) && (item->y() <= (y()+h()-item->h())) ? 1 : 0);
  807. }
  808. /// Adjust the vertical scroll bar to show \p item at the top
  809. /// of the display IF it is currently off-screen (e.g. show_item_top()).
  810. /// If it is already on-screen, no change is made.
  811. ///
  812. /// \param[in] item The item to be shown. If NULL, first() is used.
  813. ///
  814. /// \see show_item_top(), show_item_middle(), show_item_bottom()
  815. ///
  816. void Fl_Tree::show_item(Fl_Tree_Item *item) {
  817. item = item ? item : first();
  818. if (!item) return;
  819. if ( displayed(item) ) return;
  820. show_item_top(item);
  821. }
  822. /// Adjust the vertical scrollbar so that \p item is at the top of the display.
  823. ///
  824. /// \param[in] item The item to be shown. If NULL, first() is used.
  825. ///
  826. void Fl_Tree::show_item_top(Fl_Tree_Item *item) {
  827. item = item ? item : first();
  828. if (item) show_item(item, 0);
  829. }
  830. /// Adjust the vertical scrollbar so that \p item is in the middle of the display.
  831. ///
  832. /// \param[in] item The item to be shown. If NULL, first() is used.
  833. ///
  834. void Fl_Tree::show_item_middle(Fl_Tree_Item *item) {
  835. item = item ? item : first();
  836. if (item) show_item(item, (h()/2)-(item->h()/2));
  837. }
  838. /// Adjust the vertical scrollbar so that \p item is at the bottom of the display.
  839. ///
  840. /// \param[in] item The item to be shown. If NULL, first() is used.
  841. ///
  842. void Fl_Tree::show_item_bottom(Fl_Tree_Item *item) {
  843. item = item ? item : first();
  844. if (item) show_item(item, h()-item->h());
  845. }
  846. /// Returns the vertical scroll position as a pixel offset.
  847. /// The position returned is how many pixels of the tree are scrolled off the top edge
  848. /// of the screen. Example: A position of '3' indicates the top 3 pixels of
  849. /// the tree are scrolled off the top edge of the screen.
  850. /// \see vposition(), hposition()
  851. ///
  852. int Fl_Tree::vposition() const {
  853. return((int)_vscroll->value());
  854. }
  855. /// Sets the vertical scroll offset to position \p pos.
  856. /// The position is how many pixels of the tree are scrolled off the top edge
  857. /// of the screen. Example: A position of '3' scrolls the top three pixels of
  858. /// the tree off the top edge of the screen.
  859. /// \param[in] pos The vertical position (in pixels) to scroll the browser to.
  860. ///
  861. void Fl_Tree::vposition(int pos) {
  862. if (pos < 0) pos = 0;
  863. if (pos > _vscroll->maximum()) pos = (int)_vscroll->maximum();
  864. if (pos == _vscroll->value()) return;
  865. _vscroll->value(pos);
  866. redraw();
  867. }
  868. /// Displays \p item, scrolling the tree as necessary.
  869. /// \param[in] item The item to be displayed. If NULL, first() is used.
  870. ///
  871. void Fl_Tree::display(Fl_Tree_Item *item) {
  872. item = item ? item : first();
  873. if (item) show_item_middle(item);
  874. }
  875. /**
  876. * Read a preferences database into the tree widget.
  877. * A preferences database is a hierarchical collection of data which can be
  878. * directly loaded into the tree view for inspection.
  879. * \param[in] prefs the Fl_Preferences database
  880. */
  881. void Fl_Tree::load(Fl_Preferences &prefs)
  882. {
  883. int i, j, n, pn = strlen(prefs.path());
  884. char *p;
  885. const char *path = prefs.path();
  886. if (strcmp(path, ".")==0)
  887. path += 1; // root path is empty
  888. else
  889. path += 2; // child path starts with "./"
  890. n = prefs.groups();
  891. for (i=0; i<n; i++) {
  892. Fl_Preferences prefsChild(prefs, i);
  893. add(prefsChild.path()+2); // children always start with "./"
  894. load(prefsChild);
  895. }
  896. n = prefs.entries();
  897. for (i=0; i<n; i++) {
  898. // We must remove all fwd slashes in the key and value strings. Replace with backslash.
  899. char *key = strdup(prefs.entry(i));
  900. int kn = strlen(key);
  901. for (j=0; j<kn; j++) {
  902. if (key[j]=='/') key[j]='\\';
  903. }
  904. char *val; prefs.get(key, val, "");
  905. int vn = strlen(val);
  906. for (j=0; j<vn; j++) {
  907. if (val[j]=='/') val[j]='\\';
  908. }
  909. if (vn<40) {
  910. int sze = pn + strlen(key) + vn;
  911. p = (char*)malloc(sze+5);
  912. sprintf(p, "%s/%s = %s", path, key, val);
  913. } else {
  914. int sze = pn + strlen(key) + 40;
  915. p = (char*)malloc(sze+5);
  916. sprintf(p, "%s/%s = %.40s...", path, key, val);
  917. }
  918. add(p[0]=='/'?p+1:p);
  919. free(p);
  920. free(val);
  921. free(key);
  922. }
  923. }
  924. //
  925. // End of "$Id: Fl_Tree.cxx 8632 2011-05-04 02:59:50Z greg.ercolano $".
  926. //