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