blob: 4bbd229cee5267c015214813ac549cb59b51e94d [file] [log] [blame]
DRC2ff39b82011-07-28 08:38:59 +00001//
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
36static 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//
46static 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()
81static 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
89static 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.
98Fl_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.
118Fl_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///
136Fl_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///
153Fl_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///
164Fl_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///
174Fl_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///
196Fl_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)
205const 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///
237int 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.
266void 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///
330Fl_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///
348void 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///
367const 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///
378void 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///
394Fl_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///
412Fl_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///
432Fl_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///
451Fl_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///
472Fl_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///
489Fl_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.
501int 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///
730int 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///
761int 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///
792int 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///
825void 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///
844int 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///
858void 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///
869void 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///
878void 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///
887void 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///
898int 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///
908void 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///
919void 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 */
930void 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//