|
- //
- // "$Id: Fl_Tree.cxx 8632 2011-05-04 02:59:50Z greg.ercolano $"
- //
-
- #include <stdio.h>
- #include <stdlib.h>
- #include <string.h>
-
- #include <FL/Fl_Tree.H>
- #include <FL/Fl_Preferences.H>
-
- //////////////////////
- // Fl_Tree.cxx
- //////////////////////
- //
- // Fl_Tree -- This file is part of the Fl_Tree widget for FLTK
- // Copyright (C) 2009-2010 by Greg Ercolano.
- //
- // This library is free software; you can redistribute it and/or
- // modify it under the terms of the GNU Library General Public
- // License as published by the Free Software Foundation; either
- // version 2 of the License, or (at your option) any later version.
- //
- // This library is distributed in the hope that it will be useful,
- // but WITHOUT ANY WARRANTY; without even the implied warranty of
- // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- // Library General Public License for more details.
- //
- // You should have received a copy of the GNU Library General Public
- // License along with this library; if not, write to the Free Software
- // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
- // USA.
- //
-
- // INTERNAL: scroller callback
- static void scroll_cb(Fl_Widget*,void *data) {
- ((Fl_Tree*)data)->redraw();
- }
-
- // INTERNAL: Parse elements from path into an array of null terminated strings
- // Handles escape characters.
- // Path="/aa/bb"
- // Return: arr[0]="aa", arr[1]="bb", arr[2]=0
- // Caller must call free_path(arr).
- //
- static char **parse_path(const char *path) {
- while ( *path == '/' ) path++; // skip leading '/'
- // First pass: identify, null terminate, and count separators
- int seps = 1; // separator count (1: first item)
- int arrsize = 1; // array size (1: first item)
- char *save = strdup(path); // make copy we can modify
- char *sin = save, *sout = save;
- while ( *sin ) {
- if ( *sin == '\\' ) { // handle escape character
- *sout++ = *++sin;
- if ( *sin ) ++sin;
- } else if ( *sin == '/' ) { // handle submenu
- *sout++ = 0;
- sin++;
- seps++;
- arrsize++;
- } else { // all other chars
- *sout++ = *sin++;
- }
- }
- *sout = 0;
- arrsize++; // (room for terminating NULL)
- // Second pass: create array, save nonblank elements
- char **arr = (char**)malloc(sizeof(char*) * arrsize);
- int t = 0;
- sin = save;
- while ( seps-- > 0 ) {
- if ( *sin ) { arr[t++] = sin; } // skips empty fields, e.g. '//'
- sin += (strlen(sin) + 1);
- }
- arr[t] = 0;
- return(arr);
- }
-
- // INTERNAL: Free the array returned by parse_path()
- static void free_path(char **arr) {
- if ( arr ) {
- if ( arr[0] ) { free((void*)arr[0]); }
- free((void*)arr);
- }
- }
-
- // INTERNAL: Recursively descend tree hierarchy, accumulating total child count
- static int find_total_children(Fl_Tree_Item *item, int count=0) {
- count++;
- for ( int t=0; t<item->children(); t++ ) {
- count = find_total_children(item->child(t), count);
- }
- return(count);
- }
-
- /// Constructor.
- Fl_Tree::Fl_Tree(int X, int Y, int W, int H, const char *L) : Fl_Group(X,Y,W,H,L) {
- _root = new Fl_Tree_Item(_prefs);
- _root->parent(0); // we are root of tree
- _root->label("ROOT");
- _item_focus = 0;
- _callback_item = 0;
- _callback_reason = FL_TREE_REASON_NONE;
- _scrollbar_size = 0; // 0: uses Fl::scrollbar_size()
- box(FL_DOWN_BOX);
- color(FL_BACKGROUND2_COLOR, FL_SELECTION_COLOR);
- when(FL_WHEN_CHANGED);
- _vscroll = new Fl_Scrollbar(0,0,0,0); // will be resized by draw()
- _vscroll->hide();
- _vscroll->type(FL_VERTICAL);
- _vscroll->step(1);
- _vscroll->callback(scroll_cb, (void*)this);
- end();
- }
-
- /// Destructor.
- Fl_Tree::~Fl_Tree() {
- if ( _root ) { delete _root; _root = 0; }
- }
-
- /// Adds a new item, given a 'menu style' path, eg: "/Parent/Child/item".
- /// Any parent nodes that don't already exist are created automatically.
- /// Adds the item based on the value of sortorder().
- ///
- /// To specify items or submenus that contain slashes ('/' or '\')
- /// use an escape character to protect them, e.g.
- ///
- /// \code
- /// tree->add("/Holidays/Photos/12\\/25\\2010"); // Adds item "12/25/2010"
- /// tree->add("/Pathnames/c:\\\\Program Files\\\\MyApp"); // Adds item "c:\Program Files\MyApp"
- /// \endcode
- ///
- /// \returns the child item created, or 0 on error.
- ///
- Fl_Tree_Item* Fl_Tree::add(const char *path) {
- if ( ! _root ) { // Create root if none
- _root = new Fl_Tree_Item(_prefs);
- _root->parent(0);
- _root->label("ROOT");
- }
- char **arr = parse_path(path);
- Fl_Tree_Item *item = _root->add(_prefs, arr);
- free_path(arr);
- return(item);
- }
-
- /// Inserts a new item above the specified Fl_Tree_Item, with the label set to 'name'.
- /// \param[in] above -- the item above which to insert the new item. Must not be NULL.
- /// \param[in] name -- the name of the new item
- /// \returns the item that was added, or 0 if 'above' could not be found.
- ///
- Fl_Tree_Item* Fl_Tree::insert_above(Fl_Tree_Item *above, const char *name) {
- return(above->insert_above(_prefs, name));
- }
-
- /// Insert a new item into a tree-item's children at a specified position.
- ///
- /// \param[in] item The existing item to insert new child into. Must not be NULL.
- /// \param[in] name The label for the new item
- /// \param[in] pos The position of the new item in the child list
- /// \returns the item that was added.
- ///
- Fl_Tree_Item* Fl_Tree::insert(Fl_Tree_Item *item, const char *name, int pos) {
- return(item->insert(_prefs, name, pos));
- }
-
- /// Add a new child to a tree-item.
- ///
- /// \param[in] item The existing item to add new child to. Must not be NULL.
- /// \param[in] name The label for the new item
- /// \returns the item that was added.
- ///
- Fl_Tree_Item* Fl_Tree::add(Fl_Tree_Item *item, const char *name) {
- return(item->add(_prefs, name));
- }
-
- /// Find the item, given a menu style path, eg: "/Parent/Child/item".
- /// There is both a const and non-const version of this method.
- /// Const version allows pure const methods to use this method
- /// to do lookups without causing compiler errors.
- ///
- /// To specify items or submenus that contain slashes ('/' or '\')
- /// use an escape character to protect them, e.g.
- ///
- /// \code
- /// tree->add("/Holidays/Photos/12\\/25\\2010"); // Adds item "12/25/2010"
- /// tree->add("/Pathnames/c:\\\\Program Files\\\\MyApp"); // Adds item "c:\Program Files\MyApp"
- /// \endcode
- ///
- /// \param[in] path -- the tree item's pathname to be found (e.g. "Flintstones/Fred")
- /// \returns the item, or NULL if not found.
- ///
- /// \see item_pathname()
- ///
- Fl_Tree_Item *Fl_Tree::find_item(const char *path) {
- if ( ! _root ) return(NULL);
- char **arr = parse_path(path);
- Fl_Tree_Item *item = _root->find_item(arr);
- free_path(arr);
- return(item);
- }
-
- /// A const version of Fl_Tree::find_item(const char *path)
- const Fl_Tree_Item *Fl_Tree::find_item(const char *path) const {
- if ( ! _root ) return(NULL);
- char **arr = parse_path(path);
- const Fl_Tree_Item *item = _root->find_item(arr);
- free_path(arr);
- return(item);
- }
-
- // Handle safe 'reverse string concatenation'.
- // In the following we build the pathname from right-to-left,
- // since we start at the child and work our way up to the root.
- //
- #define SAFE_RCAT(c) { \
- slen += 1; if ( slen >= pathnamelen ) { pathname[0] = '\0'; return(-2); } \
- *s-- = c; \
- }
-
- /// Find the pathname for the specified \p item.
- /// If \p item is NULL, root() is used.
- /// The tree's root will be included in the pathname of showroot() is on.
- /// Menu items or submenus that contain slashes ('/' or '\') in their names
- /// will be escaped with a backslash. This is symmetrical with the add()
- /// function which uses the same escape pattern to set names.
- /// \param[in] pathname The string to use to return the pathname
- /// \param[in] pathnamelen The maximum length of the string (including NULL). Must not be zero.
- /// \param[in] item The item whose pathname is to be returned.
- /// \returns
- /// - 0 : OK (\p pathname returns the item's pathname)
- /// - -1 : item not found (pathname="")
- /// - -2 : pathname not large enough (pathname="")
- /// \see find_item()
- ///
- int Fl_Tree::item_pathname(char *pathname, int pathnamelen, const Fl_Tree_Item *item) const {
- pathname[0] = '\0';
- item = item ? item : _root;
- if ( !item ) return(-1);
- // Build pathname starting at end
- char *s = (pathname+pathnamelen-1);
- int slen = 0; // length of string compiled so far (including NULL)
- SAFE_RCAT('\0');
- while ( item ) {
- if ( item->is_root() && showroot() == 0 ) break; // don't include root in path if showroot() off
- // Find name of current item
- const char *name = item->label() ? item->label() : "???"; // name for this item
- int len = strlen(name);
- // Add name to end of pathname[]
- for ( --len; len>=0; len-- ) {
- SAFE_RCAT(name[len]); // rcat name of item
- if ( name[len] == '/' || name[len] == '\\' ) {
- SAFE_RCAT('\\'); // escape front or back slashes within name
- }
- }
- SAFE_RCAT('/'); // rcat leading slash
- item = item->parent(); // move up tree (NULL==root)
- }
- if ( *(++s) == '/' ) ++s; // leave off leading slash from pathname
- if ( s != pathname ) memmove(pathname, s, slen); // Shift down right-aligned string
- return(0);
- }
-
- /// Standard FLTK draw() method, handles draws the tree widget.
- void Fl_Tree::draw() {
- // Let group draw box+label but *NOT* children.
- // We handle drawing children ourselves by calling each item's draw()
- //
- // Handle group's bg
- Fl_Group::draw_box();
- Fl_Group::draw_label();
- // Handle tree
- if ( ! _root ) return;
- int cx = x() + Fl::box_dx(box());
- int cy = y() + Fl::box_dy(box());
- int cw = w() - Fl::box_dw(box());
- int ch = h() - Fl::box_dh(box());
- // These values are changed during drawing
- // 'Y' will be the lowest point on the tree
- int X = cx + _prefs.marginleft();
- int Y = cy + _prefs.margintop() - (_vscroll->visible() ? _vscroll->value() : 0);
- int W = cw - _prefs.marginleft(); // - _prefs.marginright();
- int Ysave = Y;
- fl_push_clip(cx,cy,cw,ch);
- {
- fl_font(_prefs.labelfont(), _prefs.labelsize());
- _root->draw(X, Y, W, this,
- (Fl::focus()==this)?_item_focus:0, // show focus item ONLY if Fl_Tree has focus
- _prefs);
- }
- fl_pop_clip();
-
- // Show vertical scrollbar?
- int ydiff = (Y+_prefs.margintop())-Ysave; // ydiff=size of tree
- int ytoofar = (cy+ch) - Y; // ytoofar -- scrolled beyond bottom (e.g. stow)
-
- //printf("ydiff=%d ch=%d Ysave=%d ytoofar=%d value=%d\n",
- //int(ydiff),int(ch),int(Ysave),int(ytoofar), int(_vscroll->value()));
-
- if ( ytoofar > 0 ) ydiff += ytoofar;
- if ( Ysave<cy || ydiff > ch || int(_vscroll->value()) > 1 ) {
- _vscroll->visible();
-
- int scrollsize = _scrollbar_size ? _scrollbar_size : Fl::scrollbar_size();
- int sx = x()+w()-Fl::box_dx(box())-scrollsize;
- int sy = y()+Fl::box_dy(box());
- int sw = scrollsize;
- int sh = h()-Fl::box_dh(box());
- _vscroll->show();
- _vscroll->range(0.0,ydiff-ch);
- _vscroll->resize(sx,sy,sw,sh);
- _vscroll->slider_size(float(ch)/float(ydiff));
- } else {
- _vscroll->Fl_Slider::value(0);
- _vscroll->hide();
- }
- fl_push_clip(cx,cy,cw,ch);
- Fl_Group::draw_children(); // draws any FLTK children set via Fl_Tree::widget()
- fl_pop_clip();
- }
-
- /// Returns next visible item above (dir==Fl_Up) or below (dir==Fl_Down) the specified \p item.
- /// If \p item is 0, returns first() if \p dir is Fl_Up, or last() if \p dir is FL_Down.
- ///
- /// \param[in] item The item above/below which we'll find the next visible item
- /// \param[in] dir The direction to search. Can be FL_Up or FL_Down.
- /// \returns The item found, or 0 if there's no visible items above/below the specified \p item.
- ///
- Fl_Tree_Item *Fl_Tree::next_visible_item(Fl_Tree_Item *item, int dir) {
- if ( ! item ) { // no start item?
- item = ( dir == FL_Up ) ? last() : first(); // start at top or bottom
- if ( ! item ) return(0);
- if ( item->visible_r() ) return(item); // return first/last visible item
- }
- switch ( dir ) {
- case FL_Up: return(item->prev_displayed(_prefs));
- case FL_Down: return(item->next_displayed(_prefs));
- default: return(item->next_displayed(_prefs));
- }
- }
-
- /// Set the item that currently should have keyboard focus.
- /// Handles calling redraw() to update the focus box (if it is visible).
- ///
- /// \param[in] item The item that should take focus. If NULL, none will have focus.
- ///
- void Fl_Tree::set_item_focus(Fl_Tree_Item *item) {
- if ( _item_focus != item ) { // changed?
- _item_focus = item; // update
- if ( visible_focus() ) redraw(); // redraw to update focus box
- }
- }
-
- /// Find the item that was clicked.
- /// You should use callback_item() instead, which is fast,
- /// and is meant to be used within a callback to determine the item clicked.
- ///
- /// This method walks the entire tree looking for the first item that is
- /// under the mouse (ie. at Fl::event_x()/Fl:event_y().
- ///
- /// Use this method /only/ if you've subclassed Fl_Tree, and are receiving
- /// events before Fl_Tree has been able to process and update callback_item().
- ///
- /// \returns the item clicked, or 0 if no item was under the current event.
- ///
- const Fl_Tree_Item* Fl_Tree::find_clicked() const {
- if ( ! _root ) return(NULL);
- return(_root->find_clicked(_prefs));
- }
-
- /// Set the item that was last clicked.
- /// Should only be used by subclasses needing to change this value.
- /// Normally Fl_Tree manages this value.
- ///
- /// Deprecated: use callback_item() instead.
- ///
- void Fl_Tree::item_clicked(Fl_Tree_Item* val) {
- _callback_item = val;
- }
-
- /// Returns the first item in the tree.
- ///
- /// Use this to walk the tree in the forward direction, eg:
- /// \code
- /// for ( Fl_Tree_Item *item = tree->first(); item; item = tree->next(item) ) {
- /// printf("Item: %s\n", item->label());
- /// }
- /// \endcode
- ///
- /// \returns first item in tree, or 0 if none (tree empty).
- /// \see first(),next(),last(),prev()
- ///
- Fl_Tree_Item* Fl_Tree::first() {
- return(_root); // first item always root
- }
-
- /// Return the next item after \p item, or 0 if no more items.
- ///
- /// Use this code to walk the entire tree:
- /// \code
- /// for ( Fl_Tree_Item *item = tree->first(); item; item = tree->next(item) ) {
- /// printf("Item: %s\n", item->label());
- /// }
- /// \endcode
- ///
- /// \param[in] item The item to use to find the next item. If NULL, returns 0.
- /// \returns Next item in tree, or 0 if at last item.
- ///
- /// \see first(),next(),last(),prev()
- ///
- Fl_Tree_Item *Fl_Tree::next(Fl_Tree_Item *item) {
- if ( ! item ) return(0);
- return(item->next());
- }
-
- /// Return the previous item before \p item, or 0 if no more items.
- ///
- /// This can be used to walk the tree in reverse, eg:
- ///
- /// \code
- /// for ( Fl_Tree_Item *item = tree->first(); item; item = tree->prev(item) ) {
- /// printf("Item: %s\n", item->label());
- /// }
- /// \endcode
- ///
- /// \param[in] item The item to use to find the previous item. If NULL, returns 0.
- /// \returns Previous item in tree, or 0 if at first item.
- ///
- /// \see first(),next(),last(),prev()
- ///
- Fl_Tree_Item *Fl_Tree::prev(Fl_Tree_Item *item) {
- if ( ! item ) return(0);
- return(item->prev());
- }
-
- /// Returns the last item in the tree.
- ///
- /// This can be used to walk the tree in reverse, eg:
- ///
- /// \code
- /// for ( Fl_Tree_Item *item = tree->last(); item; item = tree->prev() ) {
- /// printf("Item: %s\n", item->label());
- /// }
- /// \endcode
- ///
- /// \returns last item in the tree, or 0 if none (tree empty).
- ///
- /// \see first(),next(),last(),prev()
- ///
- Fl_Tree_Item* Fl_Tree::last() {
- if ( ! _root ) return(0);
- Fl_Tree_Item *item = _root;
- while ( item->has_children() ) {
- item = item->child(item->children()-1);
- }
- return(item);
- }
-
- /// Returns the first selected item in the tree.
- ///
- /// Use this to walk the tree looking for all the selected items, eg:
- ///
- /// \code
- /// for ( Fl_Tree_Item *item = tree->first_selected_item(); item; item = tree->next_selected_item(item) ) {
- /// printf("Item: %s\n", item->label());
- /// }
- /// \endcode
- ///
- /// \returns The next selected item, or 0 if there are no more selected items.
- ///
- Fl_Tree_Item *Fl_Tree::first_selected_item() {
- return(next_selected_item(0));
- }
-
- /// Returns the next selected item after \p item.
- /// If \p item is 0, search starts at the first item (root).
- ///
- /// Use this to walk the tree looking for all the selected items, eg:
- /// \code
- /// for ( Fl_Tree_Item *item = tree->first_selected_item(); item; item = tree->next_selected_item(item) ) {
- /// printf("Item: %s\n", item->label());
- /// }
- /// \endcode
- ///
- /// \param[in] item The item to use to find the next selected item. If NULL, first() is used.
- /// \returns The next selected item, or 0 if there are no more selected items.
- ///
- Fl_Tree_Item *Fl_Tree::next_selected_item(Fl_Tree_Item *item) {
- if ( ! item ) {
- if ( ! (item = first()) ) return(0);
- if ( item->is_selected() ) return(item);
- }
- while ( (item = item->next()) )
- if ( item->is_selected() )
- return(item);
- return(0);
- }
-
- /// Standard FLTK event handler for this widget.
- int Fl_Tree::handle(int e) {
- int ret = 0;
- // Developer note: Fl_Browser_::handle() used for reference here..
- // #include <FL/names.h> // for event debugging
- // fprintf(stderr, "DEBUG: %s (%d)\n", fl_eventnames[e], e);
- if (e == FL_ENTER || e == FL_LEAVE) return(1);
- switch (e) {
- case FL_FOCUS: {
- // FLTK tests if we want focus.
- // If a nav key was used to give us focus, and we've got no saved
- // focus widget, determine which item gets focus depending on nav key.
- //
- if ( ! _item_focus ) { // no focus established yet?
- switch (Fl::event_key()) { // determine if focus was navigated..
- case FL_Tab: { // received focus via TAB?
- if ( Fl::event_state(FL_SHIFT) ) { // SHIFT-TAB similar to FL_Up
- set_item_focus(next_visible_item(0, FL_Up));
- } else { // TAB similar to FL_Down
- set_item_focus(next_visible_item(0, FL_Down));
- }
- break;
- }
- case FL_Left: // received focus via LEFT or UP?
- case FL_Up: { // XK_ISO_Left_Tab
- set_item_focus(next_visible_item(0, FL_Up));
- break;
- }
- case FL_Right: // received focus via RIGHT or DOWN?
- case FL_Down:
- default: {
- set_item_focus(next_visible_item(0, FL_Down));
- break;
- }
- }
- }
- if ( visible_focus() ) redraw(); // draw focus change
- return(1);
- }
- case FL_UNFOCUS: { // FLTK telling us some other widget took focus.
- if ( visible_focus() ) redraw(); // draw focus change
- return(1);
- }
- case FL_KEYBOARD: { // keyboard shortcut
- // Do shortcuts first or scrollbar will get them...
- if (_prefs.selectmode() > FL_TREE_SELECT_NONE ) {
- if ( !_item_focus ) {
- set_item_focus(first());
- }
- if ( _item_focus ) {
- int ekey = Fl::event_key();
- switch (ekey) {
- case FL_Enter: // ENTER: selects current item only
- case FL_KP_Enter:
- if ( when() & ~FL_WHEN_ENTER_KEY) {
- select_only(_item_focus);
- show_item(_item_focus); // STR #2426
- return(1);
- }
- break;
- case ' ': // toggle selection state
- switch ( _prefs.selectmode() ) {
- case FL_TREE_SELECT_NONE:
- break;
- case FL_TREE_SELECT_SINGLE:
- if ( ! _item_focus->is_selected() ) // not selected?
- select_only(_item_focus); // select only this
- else
- deselect_all(); // select nothing
- break;
- case FL_TREE_SELECT_MULTI:
- select_toggle(_item_focus);
- break;
- }
- break;
- case FL_Right: // open children (if any)
- case FL_Left: { // close children (if any)
- if ( _item_focus ) {
- if ( ekey == FL_Right && _item_focus->is_close() ) {
- // Open closed item
- open(_item_focus);
- redraw();
- ret = 1;
- } else if ( ekey == FL_Left && _item_focus->is_open() ) {
- // Close open item
- close(_item_focus);
- redraw();
- ret = 1;
- }
- return(1);
- }
- break;
- }
- case FL_Up: // next item up
- case FL_Down: { // next item down
- set_item_focus(next_visible_item(_item_focus, ekey)); // next item up|dn
- if ( _item_focus ) { // item in focus?
- // Autoscroll
- int itemtop = _item_focus->y();
- int itembot = _item_focus->y()+_item_focus->h();
- if ( itemtop < y() ) { show_item_top(_item_focus); }
- if ( itembot > y()+h() ) { show_item_bottom(_item_focus); }
- // Extend selection
- if ( _prefs.selectmode() == FL_TREE_SELECT_MULTI && // multiselect on?
- (Fl::event_state() & FL_SHIFT) && // shift key?
- ! _item_focus->is_selected() ) { // not already selected?
- select(_item_focus); // extend selection..
- }
- return(1);
- }
- break;
- }
- }
- }
- }
- break;
- }
- }
-
- // Let Fl_Group take a shot at handling the event
- if (Fl_Group::handle(e)) {
- return(1); // handled? don't continue below
- }
-
- // Handle events the child FLTK widgets didn't need
-
- static Fl_Tree_Item *lastselect = 0;
- // fprintf(stderr, "ERCODEBUG: Fl_Tree::handle(): Event was %s (%d)\n", fl_eventnames[e], e); // DEBUGGING
- if ( ! _root ) return(ret);
- switch ( e ) {
- case FL_PUSH: { // clicked on a tree item?
- if (Fl::visible_focus() && handle(FL_FOCUS)) {
- Fl::focus(this);
- }
- lastselect = 0;
- Fl_Tree_Item *o = _root->find_clicked(_prefs);
- if ( ! o ) break;
- set_item_focus(o); // becomes new focus widget
- redraw();
- ret |= 1; // handled
- if ( Fl::event_button() == FL_LEFT_MOUSE ) {
- if ( o->event_on_collapse_icon(_prefs) ) { // collapse icon clicked?
- open_toggle(o);
- } else if ( o->event_on_label(_prefs) && // label clicked?
- (!o->widget() || !Fl::event_inside(o->widget())) && // not inside widget
- (!_vscroll->visible() || !Fl::event_inside(_vscroll)) ) { // not on scroller
- switch ( _prefs.selectmode() ) {
- case FL_TREE_SELECT_NONE:
- break;
- case FL_TREE_SELECT_SINGLE:
- select_only(o);
- break;
- case FL_TREE_SELECT_MULTI: {
- if ( Fl::event_state() & FL_SHIFT ) { // SHIFT+PUSH?
- select(o); // add to selection
- } else if ( Fl::event_state() & FL_CTRL ) { // CTRL+PUSH?
- select_toggle(o); // toggle selection state
- lastselect = o; // save toggled item (prevent oscillation)
- } else {
- select_only(o);
- }
- break;
- }
- }
- }
- }
- break;
- }
- case FL_DRAG: {
- // do the scrolling first:
- int my = Fl::event_y();
- if ( my < y() ) { // above top?
- int p = vposition()-(y()-my);
- if ( p < 0 ) p = 0;
- vposition(p);
- } else if ( my > (y()+h()) ) { // below bottom?
- int p = vposition()+(my-y()-h());
- if ( p > (int)_vscroll->maximum() ) p = (int)_vscroll->maximum();
- vposition(p);
- }
- if ( Fl::event_button() != FL_LEFT_MOUSE ) break;
- Fl_Tree_Item *o = _root->find_clicked(_prefs);
- if ( ! o ) break;
- set_item_focus(o); // becomes new focus widget
- redraw();
- ret |= 1;
- // Item's label clicked?
- if ( o->event_on_label(_prefs) &&
- (!o->widget() || !Fl::event_inside(o->widget())) &&
- (!_vscroll->visible() || !Fl::event_inside(_vscroll)) ) {
- // Handle selection behavior
- switch ( _prefs.selectmode() ) {
- case FL_TREE_SELECT_NONE: break; // no selection changes
- case FL_TREE_SELECT_SINGLE:
- select_only(o);
- break;
- case FL_TREE_SELECT_MULTI:
- if ( Fl::event_state() & FL_CTRL && // CTRL-DRAG: toggle?
- lastselect != o ) { // not already toggled from last microdrag?
- select_toggle(o); // toggle selection
- lastselect = o; // save we toggled it (prevents oscillation)
- } else {
- select(o); // select this
- }
- break;
- }
- }
- break;
- }
- }
- return(ret);
- }
-
- /// Deselect \p item and all its children.
- /// If item is NULL, first() is used.
- /// Handles calling redraw() if anything was changed.
- /// Invokes the callback depending on the value of optional parameter \p docallback.
- ///
- /// The callback can use callback_item() and callback_reason() respectively to determine
- /// the item changed and the reason the callback was called.
- ///
- /// \param[in] item The item that will be deselected (along with all its children).
- /// If NULL, first() is used.
- /// \param[in] docallback -- A flag that determines if the callback() is invoked or not:
- /// - 0 - the callback() is not invoked
- /// - 1 - the callback() is invoked for each item that changed state,
- /// callback_reason() will be FL_TREE_REASON_DESELECTED
- ///
- /// \returns count of how many items were actually changed to the deselected state.
- ///
- int Fl_Tree::deselect_all(Fl_Tree_Item *item, int docallback) {
- item = item ? item : first(); // NULL? use first()
- if ( ! item ) return(0);
- int count = 0;
- // Deselect item
- if ( item->is_selected() )
- if ( deselect(item, docallback) )
- ++count;
- // Deselect its children
- for ( int t=0; t<item->children(); t++ ) {
- count += deselect_all(item->child(t), docallback); // recurse
- }
- return(count);
- }
-
- /// Select \p item and all its children.
- /// If item is NULL, first() is used.
- /// Handles calling redraw() if anything was changed.
- /// Invokes the callback depending on the value of optional parameter \p docallback.
- ///
- /// The callback can use callback_item() and callback_reason() respectively to determine
- /// the item changed and the reason the callback was called.
- ///
- /// \param[in] item The item that will be selected (along with all its children).
- /// If NULL, first() is used.
- /// \param[in] docallback -- A flag that determines if the callback() is invoked or not:
- /// - 0 - the callback() is not invoked
- /// - 1 - the callback() is invoked for each item that changed state,
- /// callback_reason() will be FL_TREE_REASON_SELECTED
- /// \returns count of how many items were actually changed to the selected state.
- ///
- int Fl_Tree::select_all(Fl_Tree_Item *item, int docallback) {
- item = item ? item : first(); // NULL? use first()
- if ( ! item ) return(0);
- int count = 0;
- // Select item
- if ( !item->is_selected() )
- if ( select(item, docallback) )
- ++count;
- // Select its children
- for ( int t=0; t<item->children(); t++ ) {
- count += select_all(item->child(t), docallback); // recurse
- }
- return(count);
- }
-
- /// Select only the specified \p item, deselecting all others that might be selected.
- /// If item is 0, first() is used.
- /// Handles calling redraw() if anything was changed.
- /// Invokes the callback depending on the value of optional parameter \p docallback.
- ///
- /// The callback can use callback_item() and callback_reason() respectively to determine
- /// the item changed and the reason the callback was called.
- ///
- /// \param[in] selitem The item to be selected. If NULL, first() is used.
- /// \param[in] docallback -- A flag that determines if the callback() is invoked or not:
- /// - 0 - the callback() is not invoked
- /// - 1 - the callback() is invoked for each item that changed state,
- /// callback_reason() will be either FL_TREE_REASON_SELECTED or
- /// FL_TREE_REASON_DESELECTED
- /// \returns the number of items whose selection states were changed, if any.
- ///
- int Fl_Tree::select_only(Fl_Tree_Item *selitem, int docallback) {
- selitem = selitem ? selitem : first(); // NULL? use first()
- if ( ! selitem ) return(0);
- int changed = 0;
- for ( Fl_Tree_Item *item = first(); item; item = item->next() ) {
- if ( item == selitem ) {
- if ( item->is_selected() ) continue; // don't count if already selected
- select(item, docallback);
- ++changed;
- } else {
- if ( item->is_selected() ) {
- deselect(item, docallback);
- ++changed;
- }
- }
- }
- return(changed);
- }
-
- /// Adjust the vertical scroll bar so that \p item is visible
- /// \p yoff pixels from the top of the Fl_Tree widget's display.
- ///
- /// For instance, yoff=0 will position the item at the top.
- ///
- /// If yoff is larger than the vertical scrollbar's limit,
- /// the value will be clipped. So if yoff=100, but scrollbar's max
- /// is 50, then 50 will be used.
- ///
- /// \param[in] item The item to be shown. If NULL, first() is used.
- /// \param[in] yoff The pixel offset from the top for the displayed position.
- ///
- /// \see show_item_top(), show_item_middle(), show_item_bottom()
- ///
- void Fl_Tree::show_item(Fl_Tree_Item *item, int yoff) {
- item = item ? item : first();
- if (!item) return;
- int newval = item->y() - y() - yoff + (int)_vscroll->value();
- if ( newval < _vscroll->minimum() ) newval = (int)_vscroll->minimum();
- if ( newval > _vscroll->maximum() ) newval = (int)_vscroll->maximum();
- _vscroll->value(newval);
- redraw();
- }
-
- /// See if \p item is currently displayed on-screen (visible within the widget).
- /// This can be used to detect if the item is scrolled off-screen.
- /// Checks to see if the item's vertical position is within the top and bottom
- /// edges of the display window. This does NOT take into account the hide()/show()
- /// or open()/close() status of the item.
- ///
- /// \param[in] item The item to be checked. If NULL, first() is used.
- /// \returns 1 if displayed, 0 if scrolled off screen or no items are in tree.
- ///
- int Fl_Tree::displayed(Fl_Tree_Item *item) {
- item = item ? item : first();
- if (!item) return(0);
- return( (item->y() >= y()) && (item->y() <= (y()+h()-item->h())) ? 1 : 0);
- }
-
- /// Adjust the vertical scroll bar to show \p item at the top
- /// of the display IF it is currently off-screen (e.g. show_item_top()).
- /// If it is already on-screen, no change is made.
- ///
- /// \param[in] item The item to be shown. If NULL, first() is used.
- ///
- /// \see show_item_top(), show_item_middle(), show_item_bottom()
- ///
- void Fl_Tree::show_item(Fl_Tree_Item *item) {
- item = item ? item : first();
- if (!item) return;
- if ( displayed(item) ) return;
- show_item_top(item);
- }
-
- /// Adjust the vertical scrollbar so that \p item is at the top of the display.
- ///
- /// \param[in] item The item to be shown. If NULL, first() is used.
- ///
- void Fl_Tree::show_item_top(Fl_Tree_Item *item) {
- item = item ? item : first();
- if (item) show_item(item, 0);
- }
-
- /// Adjust the vertical scrollbar so that \p item is in the middle of the display.
- ///
- /// \param[in] item The item to be shown. If NULL, first() is used.
- ///
- void Fl_Tree::show_item_middle(Fl_Tree_Item *item) {
- item = item ? item : first();
- if (item) show_item(item, (h()/2)-(item->h()/2));
- }
-
- /// Adjust the vertical scrollbar so that \p item is at the bottom of the display.
- ///
- /// \param[in] item The item to be shown. If NULL, first() is used.
- ///
- void Fl_Tree::show_item_bottom(Fl_Tree_Item *item) {
- item = item ? item : first();
- if (item) show_item(item, h()-item->h());
- }
-
- /// Returns the vertical scroll position as a pixel offset.
- /// The position returned is how many pixels of the tree are scrolled off the top edge
- /// of the screen. Example: A position of '3' indicates the top 3 pixels of
- /// the tree are scrolled off the top edge of the screen.
- /// \see vposition(), hposition()
- ///
- int Fl_Tree::vposition() const {
- return((int)_vscroll->value());
- }
-
- /// Sets the vertical scroll offset to position \p pos.
- /// The position is how many pixels of the tree are scrolled off the top edge
- /// of the screen. Example: A position of '3' scrolls the top three pixels of
- /// the tree off the top edge of the screen.
- /// \param[in] pos The vertical position (in pixels) to scroll the browser to.
- ///
- void Fl_Tree::vposition(int pos) {
- if (pos < 0) pos = 0;
- if (pos > _vscroll->maximum()) pos = (int)_vscroll->maximum();
- if (pos == _vscroll->value()) return;
- _vscroll->value(pos);
- redraw();
- }
-
- /// Displays \p item, scrolling the tree as necessary.
- /// \param[in] item The item to be displayed. If NULL, first() is used.
- ///
- void Fl_Tree::display(Fl_Tree_Item *item) {
- item = item ? item : first();
- if (item) show_item_middle(item);
- }
-
- /**
- * Read a preferences database into the tree widget.
- * A preferences database is a hierarchical collection of data which can be
- * directly loaded into the tree view for inspection.
- * \param[in] prefs the Fl_Preferences database
- */
- void Fl_Tree::load(Fl_Preferences &prefs)
- {
- int i, j, n, pn = strlen(prefs.path());
- char *p;
- const char *path = prefs.path();
- if (strcmp(path, ".")==0)
- path += 1; // root path is empty
- else
- path += 2; // child path starts with "./"
- n = prefs.groups();
- for (i=0; i<n; i++) {
- Fl_Preferences prefsChild(prefs, i);
- add(prefsChild.path()+2); // children always start with "./"
- load(prefsChild);
- }
- n = prefs.entries();
- for (i=0; i<n; i++) {
- // We must remove all fwd slashes in the key and value strings. Replace with backslash.
- char *key = strdup(prefs.entry(i));
- int kn = strlen(key);
- for (j=0; j<kn; j++) {
- if (key[j]=='/') key[j]='\\';
- }
- char *val; prefs.get(key, val, "");
- int vn = strlen(val);
- for (j=0; j<vn; j++) {
- if (val[j]=='/') val[j]='\\';
- }
- if (vn<40) {
- int sze = pn + strlen(key) + vn;
- p = (char*)malloc(sze+5);
- sprintf(p, "%s/%s = %s", path, key, val);
- } else {
- int sze = pn + strlen(key) + 40;
- p = (char*)malloc(sze+5);
- sprintf(p, "%s/%s = %.40s...", path, key, val);
- }
- add(p[0]=='/'?p+1:p);
- free(p);
- free(val);
- free(key);
- }
- }
-
- //
- // End of "$Id: Fl_Tree.cxx 8632 2011-05-04 02:59:50Z greg.ercolano $".
- //
|