summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGreg Ercolano <erco@seriss.com>2010-08-26 13:32:30 +0000
committerGreg Ercolano <erco@seriss.com>2010-08-26 13:32:30 +0000
commitc7ef0f1d17f30f98342117f0635702121ce61d9a (patch)
treeee7d0742b9e4454d69aec87f363a9c903c444e9d /src
parent23303736e3cb6a51e72dec08660ca06902925adb (diff)
Fl_Tree related mods
* open() / close() now can invoke the callback(). New method callback_reason() lets one determine the cause. (Used FLU's tree for reference on this) * new Fl_Tree methods: > item_pathname() > show_item(), show_top(), show_middle(), show_bottom(). > next_selected_item() -- loop through the selected items. > callback_item() -- the item that caused the callback > callback_reason() -- reason the callback was invoked FL_TREE_REASON_SELECTED -- item was selected FL_TREE_REASON_DESELECTED -- item was de-selected FL_TREE_REASON_OPENED -- item was opened FL_TREE_REASON_CLOSED -- item was closed > position() > display() * new Fl_Tree_Item methods: > find_child_item() -- searches children for a path > find_item() -- searches self and children for a path > next(item) -- loop forward through tree > prev(item) -- loop backward through tree > first_selected_item() > next_selected_item() > x(), y(), w(), h() * deprecated: > item_clicked(). Use callback_item() instead * the 'docallback' optional integer argument for all methods is now back to 0 or 1 only. (Other values became unnecessary when above new callback() behavior was defined) * test/tree has new "Test Callback Flags" button to test the 'docallback' flags for eg. open/close/select/deselect to make sure no bugs creep in. * INTERNAL: added free_path() to free special path array created by parse_path(). * Various docs strengthened: * How to use first()/next() and last()/prev() to walk tree > made sure more method's options use \param[in] > Added more \see references * Moved several implementations from .H -> .cxx * Added autoscroll to keyboard nav and mouse drags * test/unittests: added Fl_Tree to scrollsize test TODO: o Horiz scroll bar (see Johannes Schock's email re. additions he sent) o Need to allow keyboard nav to move focus to child FLTK widgets o Fix fast-selections so that no gaps are left behind. (Select all items from the last selected item to the current) o Investigate non-default values of when() causing odd behavior. (See the tree demo's when() pulldown..) * tree demo modified to include top/mid/bot buttons that test the above. * Keyboard navigation added: Up/Down -- move focus Left/Right -- closes/opens tree item in focus Spacebar -- toggle selection state of item in focus Enter -- selects the item in focus, deselecting all others Tab/Shift-Tab -- change widget focus * All Fl_Tree select() and deselect() methods now return a value that indicates if the item's state was changed. * Fixed focus box drawing (focus box resides more precisely within item's box) git-svn-id: file:///fltk/svn/fltk/branches/branch-1.3@7691 ea41ed52-d2ee-0310-a9c1-e6b18d33e121
Diffstat (limited to 'src')
-rw-r--r--src/Fl_Tree.cxx764
-rw-r--r--src/Fl_Tree_Item.cxx202
2 files changed, 784 insertions, 182 deletions
diff --git a/src/Fl_Tree.cxx b/src/Fl_Tree.cxx
index 54b56ee63..3d1143246 100644
--- a/src/Fl_Tree.cxx
+++ b/src/Fl_Tree.cxx
@@ -9,8 +9,6 @@
#include <FL/Fl_Tree.H>
#include <FL/Fl_Preferences.H>
-#define SCROLL_W 15
-
//////////////////////
// Fl_Tree.cxx
//////////////////////
@@ -42,12 +40,12 @@ static void scroll_cb(Fl_Widget*,void *data) {
// INTERNAL: Parse elements from path into an array of null terminated strings
// Path="/aa/bb"
// Return: arr[0]="aa", arr[1]="bb", arr[2]=0
-// Caller must: free(arr[0]); free(arr);
+// 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 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 *s = save;
@@ -61,13 +59,21 @@ static char **parse_path(const char *path) {
int t = 0;
s = save;
while ( seps-- > 0 ) {
- if ( *s ) { arr[t++] = s; } // skips empty fields, eg. '//'
+ if ( *s ) { arr[t++] = s; } // skips empty fields, eg. '//'
s += (strlen(s) + 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++;
@@ -82,11 +88,14 @@ 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_clicked = 0;
+ _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_WHITE);
when(FL_WHEN_CHANGED);
- _vscroll = new Fl_Scrollbar(0,0,0,0); // will be resized by draw()
+ _vscroll = new Fl_Scrollbar(0,0,0,0); // will be resized by draw()
_vscroll->hide();
_vscroll->type(FL_VERTICAL);
_vscroll->step(1);
@@ -112,8 +121,7 @@ Fl_Tree_Item* Fl_Tree::add(const char *path) {
}
char **arr = parse_path(path);
Fl_Tree_Item *item = _root->add(_prefs, arr);
- free((void*)arr[0]);
- free((void*)arr);
+ free_path(arr);
return(item);
}
@@ -125,12 +133,21 @@ Fl_Tree_Item* Fl_Tree::insert_above(Fl_Tree_Item *above, const char *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
+/// \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
+/// \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));
@@ -141,14 +158,17 @@ Fl_Tree_Item* Fl_Tree::add(Fl_Tree_Item *item, const char *name) {
/// 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.
+///
+/// \param[in] path -- the tree item's pathname to be found (eg. "Flintstones/Fred")
+///
/// \returns the item, or 0 if not found.
+/// \see item_pathname()
///
Fl_Tree_Item *Fl_Tree::find_item(const char *path) {
if ( ! _root ) return(0);
char **arr = parse_path(path);
Fl_Tree_Item *item = _root->find_item(arr);
- free((void*)arr[0]);
- free((void*)arr);
+ free_path(arr);
return(item);
}
@@ -157,18 +177,63 @@ const Fl_Tree_Item *Fl_Tree::find_item(const char *path) const {
if ( ! _root ) return(0);
char **arr = parse_path(path);
const Fl_Tree_Item *item = _root->find_item(arr);
- free((void*)arr[0]);
- free((void*)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.
+/// \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
+ 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());
@@ -178,12 +243,14 @@ void Fl_Tree::draw() {
// '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 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, _prefs);
+ _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();
@@ -197,9 +264,11 @@ void Fl_Tree::draw() {
if ( ytoofar > 0 ) ydiff += ytoofar;
if ( Ysave<cy || ydiff > ch || int(_vscroll->value()) > 1 ) {
_vscroll->visible();
- int sx = x()+w()-Fl::box_dx(box())-SCROLL_W;
+
+ 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 = SCROLL_W;
+ int sw = scrollsize;
int sh = h()-Fl::box_dh(box());
_vscroll->show();
_vscroll->range(0.0,ydiff-ch);
@@ -214,125 +283,391 @@ void Fl_Tree::draw() {
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 currently in focus. Handles calling redraw()
+/// as needed to update the focus box.
+///
+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(0);
+ 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() ) {
+/// 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 NULL
+///
+/// \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 NULL
+///
+/// \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:
+ case 0xfe20: { // 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);
+ 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;
- int changed = 0;
- int ret = Fl_Group::handle(e);
+ // 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: {
+ case FL_PUSH: { // clicked on a tree item?
+ if (Fl::visible_focus() && handle(FL_FOCUS)) {
+ Fl::focus(this);
+ }
lastselect = 0;
- item_clicked(0); // assume no item was clicked
Fl_Tree_Item *o = _root->find_clicked(_prefs);
- if ( o ) {
- ret |= 1; // handled
- if ( Fl::event_button() == FL_LEFT_MOUSE ) {
- // Was collapse icon clicked?
- if ( o->event_on_collapse_icon(_prefs) ) {
- o->open_toggle();
- redraw();
- }
- // Item's label clicked?
- else if ( o->event_on_label(_prefs) &&
- (!o->widget() || !Fl::event_inside(o->widget())) &&
- callback() &&
- (!_vscroll->visible() || !Fl::event_inside(_vscroll)) ) {
- item_clicked(o); // save item clicked
-
- // Handle selection behavior
- switch ( _prefs.selectmode() ) {
- case FL_TREE_SELECT_NONE: { // no selection changes
- break;
- }
- case FL_TREE_SELECT_SINGLE: {
- changed = select_only(o);
- break;
- }
- case FL_TREE_SELECT_MULTI: {
- int state = Fl::event_state();
- if ( state & FL_SHIFT ) {
- if ( ! o->is_selected() ) {
- o->select(); // add to selection
- changed = 1; // changed
- }
- } else if ( state & FL_CTRL ) {
- changed = 1; // changed
- o->select_toggle(); // toggle selection state
- lastselect = o; // save we toggled it (prevents oscillation)
- } else {
- changed = select_only(o);
- }
- break;
- }
- }
-
- if ( changed ) {
- redraw(); // make change(s) visible
- if ( when() & FL_WHEN_CHANGED ) {
- set_changed();
- do_callback((Fl_Widget*)this, user_data()); // item callback
- }
- }
- }
- }
+ 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 ) {
- ret |= 1; // handled
- // Item's label clicked?
- if ( o->event_on_label(_prefs) &&
- (!o->widget() || !Fl::event_inside(o->widget())) &&
- callback() &&
- (!_vscroll->visible() || !Fl::event_inside(_vscroll)) ) {
- item_clicked(o); // save item clicked
- // Handle selection behavior
- switch ( _prefs.selectmode() ) {
- case FL_TREE_SELECT_NONE: { // no selection changes
- break;
- }
- case FL_TREE_SELECT_SINGLE: {
- changed = select_only(o);
- break;
- }
- case FL_TREE_SELECT_MULTI: {
- int state = Fl::event_state();
- if ( state & FL_CTRL ) {
- if ( lastselect != o ) {// not already toggled from last microdrag?
- changed = 1; // changed
- o->select_toggle(); // toggle selection
- lastselect = o; // save we toggled it (prevents oscillation)
- }
- } else {
- if ( ! o->is_selected() ) {
- changed = 1; // changed
- o->select(); // select this
- }
- }
- break;
- }
- }
- if ( changed ) {
- redraw(); // make change(s) visible
- if ( when() & FL_WHEN_CHANGED ) {
- set_changed();
- do_callback((Fl_Widget*)this, user_data()); // item callback
- }
- }
- }
- }
- break;
- }
- case FL_RELEASE: {
- if ( Fl::event_button() == FL_LEFT_MOUSE ) {
- ret |= 1;
- if ( when() & FL_WHEN_RELEASE || ( this->changed() && (when() & FL_WHEN_CHANGED)) ) {
- do_callback((Fl_Widget*)this, user_data()); // item callback
- }
+ 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;
}
@@ -340,93 +675,172 @@ int Fl_Tree::handle(int e) {
return(ret);
}
-/// Deselect item and all its children.
-/// If item is NULL, root() is used.
-/// Handles calling redraw() if anything was changed.
-/// Returns count of how many items were in the 'selected' state,
-/// ie. how many items were "changed".
+/// 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.
///
-/// \p docallback is an optional paramemter that can either be 0 or 1:
+/// The callback can use callback_item() and callback_reason() respectively to determine
+/// the item changed and the reason the callback was called.
///
-/// - 0 - the callback() is not invoked (default)
-/// - 1 - the callback() is invoked once if \b any items changed state,
-/// and item_clicked() will be NULL (since many items could have been changed).
-//
-/// \todo deselect_all()'s docallback should support '2' (invoke callback for each item changed)
+/// \param[in] item The item that will be deselected (along with all its children)
+/// \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 : root(); // NULL? use root()
- int count = item->deselect_all();
- if ( count ) {
- redraw(); // anything changed? cause redraw
- if ( docallback == 1 )
- do_callback_for_item(0);
+ item = item ? item : first(); // NULL? use first()
+ if ( ! item ) return(0);
+ int count = 0;
+ for ( ; item; item = next(item) ) {
+ if ( item->is_selected() )
+ if ( deselect(item, docallback) )
+ ++count;
}
return(count);
}
-/// Select item and all its children.
-/// If item is NULL, root() is used.
-/// Handles calling redraw() if anything was changed.
-/// Returns count of how many items were in the 'deselected' state,
-/// ie. how many items were "changed".
+/// 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.
///
-/// \p docallback is an optional paramemter that can either be 0 or 1:
+/// The callback can use callback_item() and callback_reason() respectively to determine
+/// the item changed and the reason the callback was called.
///
-/// - 0 - the callback() is not invoked (default)
-/// - 1 - the callback() is invoked once if \b any items changed state,
-/// and item_clicked() will be NULL (since many items could have been changed).
+/// \param[in] item The item that will be selected (along with all its children).
+/// If NULL, first() is assumed.
+/// \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
///
-/// \todo select_all()'s docallback should support '2' (invoke callback for each item changed)
+/// \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 : root(); // NULL? use root()
- int count = item->select_all();
- if ( count ) {
- redraw(); // anything changed? cause redraw
- if (docallback == 1)
- do_callback_for_item(0);
+ item = item ? item : first(); // NULL? use first()
+ if ( ! item ) return(0);
+ int count = 0;
+ for ( ; item; item = next(item) ) {
+ if ( !item->is_selected() )
+ if ( select(item, docallback) )
+ ++count;
}
return(count);
}
-/// Select only this item.
-/// If item is NULL, root() is used.
-/// Handles calling redraw() if anything was changed.
-/// Returns how many items were changed, if any.
+/// 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.
///
-/// \p docallback is an optional paramemter that can either be 0, 1 or 2:
+/// The callback can use callback_item() and callback_reason() respectively to determine
+/// the item changed and the reason the callback was called.
///
-/// - 0 - the callback() is not invoked (default)
-/// - 1 - the callback() is invoked once if \b any items changed state,
-/// and item_clicked() will be NULL (since many items could have been changed).
-/// - 2 - the callback() is invoked once for \b each item that changed state,
-/// and the callback() can use item_clicked() to determine the item changed.
+/// \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 : root(); // NULL? use root()
+ 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
- item->select();
+ select(item, docallback);
++changed;
- if ( docallback == 2 ) do_callback_for_item(item);
} else {
if ( item->is_selected() ) {
- item->deselect();
+ deselect(item, docallback);
++changed;
- if ( docallback == 2 ) do_callback_for_item(item);
}
}
}
- if ( changed ) {
- redraw(); // anything changed? redraw
- if ( docallback == 1 ) do_callback_for_item(0);
- }
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.
+///
+/// \see show_item_top(), show_item_middle(), show_item_bottom()
+///
+void Fl_Tree::show_item(Fl_Tree_Item *item, int yoff) {
+ 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();
+}
+
+/// Adjust the vertical scrollbar so that \p item is at the top of the display.
+void Fl_Tree::show_item_top(Fl_Tree_Item *item) {
+ item = item ? item : first();
+ if ( ! item ) return;
+ show_item(item, 0);
+}
+
+/// Adjust the vertical scrollbar so that \p item is in the middle of the display.
+void Fl_Tree::show_item_middle(Fl_Tree_Item *item) {
+ item = item ? item : first();
+ if ( ! item ) return;
+ show_item(item, h()/2 - item->h()/2);
+}
+
+/// Adjust the vertical scrollbar so that \p item is at the bottom of the display.
+void Fl_Tree::show_item_bottom(Fl_Tree_Item *item) {
+ item = item ? item : first();
+ if ( ! item ) return;
+ 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.
+///
+void Fl_Tree::display(Fl_Tree_Item *item) {
+ if ( ! item ) return;
+ show_item_middle(item);
+}
+
/**
* Read a preferences database into the tree widget.
* A preferences database is a hierarchical collection of data which can be
diff --git a/src/Fl_Tree_Item.cxx b/src/Fl_Tree_Item.cxx
index 24f33cd2c..80123793f 100644
--- a/src/Fl_Tree_Item.cxx
+++ b/src/Fl_Tree_Item.cxx
@@ -165,12 +165,12 @@ int Fl_Tree_Item::find_child(const char *name) {
return(-1);
}
-/// Find item by descending array of names.
+/// Find child item by descending array of names. Does not include self in search.
/// Only Fl_Tree should need this method.
///
/// \returns item, or 0 if not found
///
-const Fl_Tree_Item *Fl_Tree_Item::find_item(char **arr) const {
+const Fl_Tree_Item *Fl_Tree_Item::find_child_item(char **arr) const {
for ( int t=0; t<children(); t++ ) {
if ( child(t)->label() ) {
if ( strcmp(child(t)->label(), *arr) == 0 ) { // match?
@@ -185,12 +185,12 @@ const Fl_Tree_Item *Fl_Tree_Item::find_item(char **arr) const {
return(0);
}
-/// Find item by by descending array of names.
-/// Only Fl_Tree should need this method.
+/// Find child item by descending array of names. Does not include self in search.
+/// Only Fl_Tree should need this method. Use Fl_Tree::find_item() instead.
///
/// \returns item, or 0 if not found
///
-Fl_Tree_Item *Fl_Tree_Item::find_item(char **arr) {
+Fl_Tree_Item *Fl_Tree_Item::find_child_item(char **arr) {
for ( int t=0; t<children(); t++ ) {
if ( child(t)->label() ) {
if ( strcmp(child(t)->label(), *arr) == 0 ) { // match?
@@ -205,6 +205,40 @@ Fl_Tree_Item *Fl_Tree_Item::find_item(char **arr) {
return(0);
}
+/// Find item by descending array of \p names. Includes self in search.
+/// Only Fl_Tree should need this method. Use Fl_Tree::find_item() instead.
+///
+/// \returns item, or 0 if not found
+///
+const Fl_Tree_Item *Fl_Tree_Item::find_item(char **names) const {
+ if ( label() && strcmp(label(), *names) == 0 ) { // match self?
+ if ( *(names+1) == 0 ) { // end of names,
+ return(this); // found ourself.
+ }
+ }
+ if ( children() ) { // check children..
+ return(find_child_item(names));
+ }
+ return(0);
+}
+
+/// Find item by descending array of \p names. Includes self in search.
+/// Only Fl_Tree should need this method.
+///
+/// \returns item, or 0 if not found
+///
+Fl_Tree_Item *Fl_Tree_Item::find_item(char **names) {
+ if ( label() && strcmp(label(), *names) == 0 ) { // match self?
+ if ( *(names+1) == 0 ) { // end of names,
+ return(this); // found ourself.
+ }
+ }
+ if ( children() ) { // check children..
+ return(find_child_item(names));
+ }
+ return(0);
+}
+
/// Find the index number for the specified 'item'
/// in the current item's list of children.
///
@@ -473,8 +507,49 @@ Fl_Tree_Item *Fl_Tree_Item::find_clicked(const Fl_Tree_Prefs &prefs) {
return(0);
}
+static void draw_item_focus(Fl_Boxtype B, Fl_Color C, int X, int Y, int W, int H) {
+ if (!Fl::visible_focus()) return;
+ switch (B) {
+ case FL_DOWN_BOX:
+ case FL_DOWN_FRAME:
+ case FL_THIN_DOWN_BOX:
+ case FL_THIN_DOWN_FRAME:
+ X ++;
+ Y ++;
+ default:
+ break;
+ }
+ fl_color(fl_contrast(FL_BLACK, C));
+
+#if defined(USE_X11) || defined(__APPLE_QUARTZ__)
+ fl_line_style(FL_DOT);
+ fl_rect(X + Fl::box_dx(B), Y + Fl::box_dy(B),
+ W - Fl::box_dw(B) - 1, H - Fl::box_dh(B) - 1);
+ fl_line_style(FL_SOLID);
+#else
+ // Some platforms don't implement dotted line style, so draw
+ // every other pixel around the focus area...
+ //
+ // Also, QuickDraw (MacOS) does not support line styles specifically,
+ // and the hack we use in fl_line_style() will not draw horizontal lines
+ // on odd-numbered rows...
+ int i, xx, yy;
+
+ X += Fl::box_dx(B);
+ Y += Fl::box_dy(B);
+ W -= Fl::box_dw(B) + 2;
+ H -= Fl::box_dh(B) + 2;
+
+ for (xx = 0, i = 1; xx < W; xx ++, i ++) if (i & 1) fl_point(X + xx, Y);
+ for (yy = 0; yy < H; yy ++, i ++) if (i & 1) fl_point(X + W, Y + yy);
+ for (xx = W; xx > 0; xx --, i ++) if (i & 1) fl_point(X + xx, Y + H);
+ for (yy = H; yy > 0; yy --, i ++) if (i & 1) fl_point(X, Y + yy);
+#endif
+}
+
/// Draw this item and its children.
-void Fl_Tree_Item::draw(int X, int &Y, int W, Fl_Widget *tree,
+void Fl_Tree_Item::draw(int X, int &Y, int W, Fl_Widget *tree,
+ Fl_Tree_Item *itemfocus,
const Fl_Tree_Prefs &prefs, int lastchild) {
if ( ! _visible ) return;
fl_font(_labelfont, _labelsize);
@@ -598,6 +673,10 @@ void Fl_Tree_Item::draw(int X, int &Y, int W, Fl_Widget *tree,
fl_draw(_label, X+useroff, Y+H-fl_descent()-1);
}
}
+ if ( this == itemfocus && Fl::visible_focus() && Fl::focus() == tree) {
+ // Draw focus box around this item
+ draw_item_focus(FL_NO_BOX,bg,bx+1,by+1,bw-1,bh-1);
+ }
Y += H;
} // end drawthis
// Draw children
@@ -608,7 +687,7 @@ void Fl_Tree_Item::draw(int X, int &Y, int W, Fl_Widget *tree,
int child_y_start = Y;
for ( int t=0; t<children(); t++ ) {
int lastchild = ((t+1)==children()) ? 1 : 0;
- _children[t]->draw(child_x, Y, child_w, tree, prefs, lastchild);
+ _children[t]->draw(child_x, Y, child_w, tree, itemfocus, prefs, lastchild);
}
if ( has_children() && is_open() ) {
Y += prefs.openchild_marginbottom(); // offset below open child tree
@@ -736,6 +815,115 @@ Fl_Tree_Item *Fl_Tree_Item::prev() {
return(p);
}
+/// Return this item's next sibling.
+///
+/// Moves to the next item below us at the same level (sibling).
+/// Use this to move down the tree without moving deeper into the tree,
+/// effectively skipping over this item's children/descendents.
+///
+/// \returns item's next sibling, or 0 if none.
+///
+Fl_Tree_Item *Fl_Tree_Item::next_sibling() {
+ if ( !parent() ) return(0); // No parent (root)? We have no siblings
+ int index = parent()->find_child(this); // find our position in parent's child() array
+ if ( index == -1 ) return(0); // parent doesn't know us? weird
+ if ( (index+1) < parent()->children() ) // is there a next child?
+ return(parent()->child(index+1)); // return next child if there's one below us
+ return(0); // no siblings below us
+}
+
+/// Return this item's previous sibling.
+///
+/// Moves to the previous item above us at the same level (sibling).
+/// Use this to move up the tree without moving deeper into the tree.
+///
+/// \returns This item's previous sibling, or 0 if none.
+///
+Fl_Tree_Item *Fl_Tree_Item::prev_sibling() {
+ if ( !parent() ) return(0); // No parent (root)? We have no siblings
+ int index = parent()->find_child(this); // find next position up in parent's child() array
+ if ( index == -1 ) return(0); // parent doesn't know us? weird
+ if ( index > 0 ) return(parent()->child(index-1)); // return previous child if there's one above us
+ return(0); // no siblings above us
+}
+
+/// Return the next visible item. (If this item has children and is closed, children are skipped)
+///
+/// This method can be used to walk the tree forward, skipping items
+/// that are not currently visible to the user.
+///
+/// \returns the next visible item below us, or 0 if there's no more items.
+///
+Fl_Tree_Item *Fl_Tree_Item::next_displayed(Fl_Tree_Prefs &prefs) {
+ Fl_Tree_Item *c = this;
+ while ( c ) {
+ if ( c->is_root() && !prefs.showroot() ) { // on root and can't show it?
+ c = c->next(); // skip ahead, try again
+ continue;
+ }
+ if ( c->has_children() && c->is_close() ) { // item has children and: invisible or closed?
+ // Skip children, take next sibling. If none, try parent's sibling, repeat
+ while ( c ) {
+ Fl_Tree_Item *sib = c->next_sibling(); // get sibling
+ if ( sib ) { c = sib; break; } // Found? let outer loop test it
+ c = c->parent(); // No sibling? move up tree, try parent's sibling
+ }
+ } else { // has children and isn't closed, or no children
+ c = c->next(); // use normal 'next'
+ }
+ if ( !c ) return(0); // no more? done
+ // Check all parents to be sure none are closed.
+ // If closed, move up to that level and repeat until sure none are closed.
+ Fl_Tree_Item *p = c->parent();
+ while (1) {
+ if ( !p || p->is_root() ) return(c); // hit top? then we're displayed, return c
+ if ( p->is_close() ) c = p; // found closed parent? make it current
+ p = p->parent(); // continue up tree
+ }
+ if ( c && c->visible() ) return(c); // item visible? return it
+ }
+ return(0); // hit end: no more items
+}
+
+/// Return the previous visible item. (If this item above us has children and is closed, its children are skipped)
+///
+/// This method can be used to walk the tree backward,
+/// skipping items that are not currently visible to the user.
+///
+/// \returns the previous visible item above us, or 0 if there's no more items.
+///
+Fl_Tree_Item *Fl_Tree_Item::prev_displayed(Fl_Tree_Prefs &prefs) {
+ Fl_Tree_Item *c = this;
+ while ( c ) {
+ c = c->prev(); // previous item
+ if ( !c ) break; // no more items? done
+ if ( c->is_root() ) // root
+ return((prefs.showroot()&&c->visible()) ? c : 0); // return root if visible
+ if ( !c->visible() ) continue; // item not visible? skip
+ // Check all parents to be sure none are closed.
+ // If closed, move up to that level and repeat until sure none are closed.
+ Fl_Tree_Item *p = c->parent();
+ while (1) {
+ if ( !p || p->is_root() ) return(c); // hit top? then we're displayed, return c
+ if ( p->is_close() ) c = p; // found closed parent? make it current
+ p = p->parent(); // continue up tree
+ }
+ }
+ return(0); // hit end: no more items
+}
+
+/// Returns if item and all its parents are visible.
+/// Also takes into consideration if any parent is close()ed.
+/// \returns
+/// 1 -- item and its parents are visible/open()
+/// 0 -- item (or parents) invisible or close()ed.
+///
+int Fl_Tree_Item::visible_r() const {
+ for (const Fl_Tree_Item *p=this; p; p=p->parent()) // move up through parents
+ if (!p->visible() || p->is_close()) return(0); // any parent not visible or closed?
+ return(1);
+}
+
//
// End of "$Id$".
//