diff options
| author | Greg Ercolano <erco@seriss.com> | 2010-08-26 13:32:30 +0000 |
|---|---|---|
| committer | Greg Ercolano <erco@seriss.com> | 2010-08-26 13:32:30 +0000 |
| commit | c7ef0f1d17f30f98342117f0635702121ce61d9a (patch) | |
| tree | ee7d0742b9e4454d69aec87f363a9c903c444e9d | |
| parent | 23303736e3cb6a51e72dec08660ca06902925adb (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
| -rw-r--r-- | FL/Fl_Tree.H | 563 | ||||
| -rw-r--r-- | FL/Fl_Tree_Item.H | 26 | ||||
| -rw-r--r-- | FL/Fl_Tree_Prefs.H | 4 | ||||
| -rw-r--r-- | src/Fl_Tree.cxx | 764 | ||||
| -rw-r--r-- | src/Fl_Tree_Item.cxx | 202 | ||||
| -rw-r--r-- | test/tree.fl | 351 | ||||
| -rw-r--r-- | test/unittest_scrollbarsize.cxx | 56 |
7 files changed, 1517 insertions, 449 deletions
diff --git a/FL/Fl_Tree.H b/FL/Fl_Tree.H index be309c81f..5e6865124 100644 --- a/FL/Fl_Tree.H +++ b/FL/Fl_Tree.H @@ -84,68 +84,75 @@ /// tree.end(); /// \endcode /// -/// Items can be added with Fl_Tree::add(), -/// removed with Fl_Tree::remove(), -/// inserted with Fl_Tree::insert_above(), -/// selected/deselected with Fl_Tree::select() and Fl_Tree::deselect(). -/// Items can be swapped with Fl_Tree_Item::swap_children(), sorting control via -/// Fl_Tree::sortorder(). +/// Items can be added with add(), +/// removed with remove(), +/// completely cleared with clear(), +/// inserted with insert() and insert_above(), +/// selected/deselected with select() and deselect(), +/// open/closed with open() and closed(). +/// Children of an item can be swapped around with Fl_Tree_Item::swap_children(), +/// sorting can be controlled when items are add()ed via sortorder(). +/// You can walk the entire tree with first() and next(). +/// You can walk selected items with first_selected_item() and +/// next_selected_item(). +/// Items can be found by their pathname using find_item(const char*), +/// and an item's pathname can be found with item_pathname(). /// -/// The tree can have different selection behaviors controlled by Fl_Tree::selectmode(). +/// The tree can have different selection behaviors controlled by selectmode(). /// -/// FLTK and custom FLTK widgets can be assigned to tree items via Fl_Tree_Item::widget(). +/// FLTK widgets (including custom widgets) can be assigned to tree items via +/// Fl_Tree_Item::widget(). /// -/// Parent nodes can be open/closed with open() and close(), icons can be assigned -/// or redefined with some or all items via +/// Icons for individual items can be changed with /// Fl_Tree_Item::openicon(), /// Fl_Tree_Item::closeicon(), /// Fl_Tree_Item::usericon(). /// -/// Various default preferences can be manipulated via Fl_Tree_Prefs, including -/// colors, margins, connection lines. +/// Various default preferences can be globally manipulated via Fl_Tree_Prefs, +/// including colors, margins, icons, connection lines. +/// +/// The tree's callback() will be invoked when items change state or are open/closed. +/// when() controls when mouse/keyboard events invoke the callback. +/// callback_item() and callback_reason() can be used to determine the cause of the callback. /// /// \image html tree-elements.png /// -/// \todo Needs handling of callbacks when items are procedurally select()ed + +/// \enum Fl_Tree_Reason +/// The reason the callback was invoked. /// +enum Fl_Tree_Reason { + FL_TREE_REASON_NONE=0, ///< unknown reason + FL_TREE_REASON_SELECTED, ///< an item was selected + FL_TREE_REASON_DESELECTED, ///< an item was de-selected + FL_TREE_REASON_OPENED, ///< an item was opened + FL_TREE_REASON_CLOSED ///< an item was closed +}; + class Fl_Tree : public Fl_Group { - Fl_Tree_Item *_root; // can be null! - Fl_Tree_Item *_item_clicked; - Fl_Tree_Prefs _prefs; // all the tree's settings - Fl_Scrollbar *_vscroll; + Fl_Tree_Item *_root; // can be null! + Fl_Tree_Item *_item_focus; // item that has focus box + Fl_Tree_Item *_callback_item; // item invoked during callback (can be NULL) + Fl_Tree_Reason _callback_reason; // reason for the callback + Fl_Tree_Prefs _prefs; // all the tree's settings + int _scrollbar_size; // size of scrollbar trough + +protected: + /// Vertical scrollbar + Fl_Scrollbar *_vscroll; -public: - /// Find the item that was clicked. - /// You probably want to use item_clicked() instead, which is fast. - /// - /// 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 item_clicked(). - /// - /// \returns the item clicked, or 0 if no item was under the current event. - /// - const Fl_Tree_Item *find_clicked() const { - if ( ! _root ) return(0); - return(_root->find_clicked(_prefs)); - } protected: - /// Set the item that was last clicked. - /// Should only be used by subclasses needing to change this value. - /// Normally Fl_Tree manages this value. - /// - void item_clicked(Fl_Tree_Item* val) { - _item_clicked = val; - } - void do_callback_for_item(Fl_Tree_Item* item) { - Fl_Tree_Item *save = _item_clicked; // save previous 'item_clicked' - _item_clicked = item; // set item_clicked to this item while we do callback + void set_item_focus(Fl_Tree_Item *o); + void item_clicked(Fl_Tree_Item* val); + /// Do the callback for the item, setting the item and reason + void do_callback_for_item(Fl_Tree_Item* item, Fl_Tree_Reason reason) { + callback_reason(reason); + callback_item(item); do_callback((Fl_Widget*)this, user_data()); - _item_clicked = save; // restore item_clicked } - + Fl_Tree_Item *next_visible_item(Fl_Tree_Item *start, int dir); + public: Fl_Tree(int X, int Y, int W, int H, const char *L=0); ~Fl_Tree(); @@ -204,7 +211,7 @@ public: void clear_children(Fl_Tree_Item *item) { if ( item->has_children() ) { item->clear_children(); - redraw(); // redraw only if there were children to clear + redraw(); // redraw only if there were children to clear } } @@ -213,52 +220,29 @@ public: //////////////////////// Fl_Tree_Item *find_item(const char *path); const Fl_Tree_Item *find_item(const char *path) const; + int item_pathname(char *pathname, int pathnamelen, const Fl_Tree_Item *item) const; + + const Fl_Tree_Item *find_clicked() const; /// Return the item that was last clicked. /// - /// Valid only from within an Fl_Tree::callback(). + /// Valid only from within the callback(). + /// + /// Deprecated: use callback_item() instead. /// /// \returns the item clicked, or 0 if none. /// 0 may also be used to indicate several items were clicked/changed. /// Fl_Tree_Item *item_clicked() { - return(_item_clicked); - } - /// 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 = item->next() ) { - /// printf("Item: %s\n", item->label()); - /// } - /// \endcode - /// - /// \returns first item in tree, or 0 if none (tree empty). - /// - Fl_Tree_Item *first() { - return(_root); // first item always root - } - /// Returns the last item in the tree. - /// - /// Use this to walk the tree in reverse, eg: - /// - /// \code - /// for ( Fl_Tree_Item *item = tree->last(); item; item = item->prev() ) { - /// printf("Item: %s\n", item->label()); - /// } - /// \endcode - /// - /// \returns last item in the tree, or 0 if none (tree empty). - /// - Fl_Tree_Item *last() { - if ( ! _root ) return(0); - Fl_Tree_Item *item = _root; - while ( item->has_children() ) { - item = item->child(item->children()-1); - } - return(item); - } - + return(_callback_item); + } + Fl_Tree_Item *first(); + Fl_Tree_Item *next(Fl_Tree_Item *item=0); + Fl_Tree_Item *prev(Fl_Tree_Item *item=0); + Fl_Tree_Item *last(); + Fl_Tree_Item *first_selected_item(); + Fl_Tree_Item *next_selected_item(Fl_Tree_Item *item=0); + ////////////////////////// // Item open/close methods ////////////////////////// @@ -266,59 +250,140 @@ public: /// Open the specified 'item'. /// This causes the item's children (if any) to be shown. /// Handles redrawing if anything was actually changed. + /// Invokes the callback depending on the value of optional parameter \p docallback. /// - void open(Fl_Tree_Item *item) { - if ( ! item->is_open() ) { - item->open(); - redraw(); + /// 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 to be opened + /// \param[in] docallback -- A flag that determines if the callback() is invoked or not: + /// - 0 - callback() is not invoked + /// - 1 - callback() is invoked if item changed, + /// callback_reason() will be FL_TREE_REASON_OPENED + /// + /// \returns + /// - 1 -- item was opened + /// - 0 -- item was already open, no change + /// + /// \see open(), close(), is_open(), is_close(), callback_item(), callback_reason() + /// + int open(Fl_Tree_Item *item, int docallback=1) { + if ( item->is_open() ) return(0); + item->open(); + redraw(); + if ( docallback ) { + do_callback_for_item(item, FL_TREE_REASON_OPENED); } + return(1); } /// Opens the item specified by \p path (eg: "Parent/child/item"). /// This causes the item's children (if any) to be shown. /// Handles redrawing if anything was actually 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] path -- the tree item's pathname (eg. "Flintstones/Fred") + /// \param[in] docallback -- A flag that determines if the callback() is invoked or not: + /// - 0 - callback() is not invoked + /// - 1 - callback() is invoked if item changed, + /// callback_reason() will be FL_TREE_REASON_OPENED /// /// \returns - /// - 0 : OK - /// - -1 : item was not found + /// - 1 -- OK: item opened + /// - 0 -- OK: item was already open, no change + /// - -1 -- ERROR: item was not found + /// + /// \see open(), close(), is_open(), is_close(), callback_item(), callback_reason() /// - int open(const char *path) { + int open(const char *path, int docallback=1) { Fl_Tree_Item *item = find_item(path); - if ( item ) { - open(item); - return(0); + if ( ! item ) return(-1); + return(open(item, docallback)); + } + /// Toggle the open state of \p item. + /// Handles redrawing if anything was actually 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 to be opened + /// \param[in] docallback -- A flag that determines if the callback() is invoked or not: + /// - 0 - callback() is not invoked + /// - 1 - callback() is invoked, callback_reason() will be either + /// FL_TREE_REASON_OPENED or FL_TREE_REASON_CLOSED + /// + /// \see open(), close(), is_open(), is_close(), callback_item(), callback_reason() + /// + void open_toggle(Fl_Tree_Item *item, int docallback=1) { + if ( item->is_open() ) { + close(item, docallback); + } else { + open(item, docallback); } - return(-1); } /// Closes the specified \p item. /// Handles redrawing if anything was actually changed. + /// Invokes the callback depending on the value of optional parameter \p docallback. /// - void close(Fl_Tree_Item *item) { - if ( ! item->is_close() ) { - item->close(); - redraw(); + /// 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 to be closed + /// \param[in] docallback -- A flag that determines if the callback() is invoked or not: + /// - 0 - callback() is not invoked + /// - 1 - callback() is invoked if item changed, + /// callback_reason() will be FL_TREE_REASON_CLOSED + /// + /// \returns + /// - 1 -- item was closed + /// - 0 -- item was already closed, no change + /// + /// \see open(), close(), is_open(), is_close(), callback_item(), callback_reason() + /// + int close(Fl_Tree_Item *item, int docallback=1) { + if ( item->is_close() ) return(0); + item->close(); + redraw(); + if ( docallback ) { + do_callback_for_item(item, FL_TREE_REASON_CLOSED); } + return(1); } /// Closes the item specified by \p path, eg: "Parent/child/item". - /// /// Handles redrawing if anything was actually 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] path -- the tree item's pathname (eg. "Flintstones/Fred") + /// \param[in] docallback -- A flag that determines if the callback() is invoked or not: + /// - 0 - callback() is not invoked + /// - 1 - callback() is invoked if item changed, + /// callback_reason() will be FL_TREE_REASON_CLOSED /// /// \returns - /// - 0 -- OK - /// - -1 -- item was not found + /// - 1 -- OK: item closed + /// - 0 -- OK: item was already closed, no change + /// - -1 -- ERROR: item was not found + /// + /// \see open(), close(), is_open(), is_close(), callback_item(), callback_reason() /// - int close(const char *path) { + int close(const char *path, int docallback=1) { Fl_Tree_Item *item = find_item(path); - if ( item ) { - close(item); - return(0); - } - return(-1); + if ( ! item ) return(-1); + return(close(item, docallback)); } /// See if \p item is open. /// /// Items that are 'open' are themselves not necessarily visible; /// one of the item's parents might be closed. /// + /// \param[in] item -- the item to be tested + /// /// \returns /// - 1 : item is open /// - 0 : item is closed @@ -331,17 +396,22 @@ public: /// Items that are 'open' are themselves not necessarily visible; /// one of the item's parents might be closed. /// + /// \param[in] path -- the tree item's pathname (eg. "Flintstones/Fred") + /// /// \returns - /// - 1 : item is open - /// - 0 : item is closed - /// - -1 : item was not found + /// - 1 - OK: item is open + /// - 0 - OK: item is closed + /// - -1 - ERROR: item was not found /// int is_open(const char *path) const { const Fl_Tree_Item *item = find_item(path); - if ( item ) return(item->is_open()?1:0); - return(-1); + if ( ! item ) return(-1); + return(item->is_open()?1:0); } /// See if the specified \p item is closed. + /// + /// \param[in] item -- the item to be tested + /// /// \returns /// - 1 : item is open /// - 0 : item is closed @@ -351,111 +421,154 @@ public: } /// See if item specified by \p path (eg: "Parent/child/item") is closed. /// + /// \param[in] path -- the tree item's pathname (eg. "Flintstones/Fred") + /// /// \returns - /// - 1 : item is closed - /// - 0 : item is open - /// - -1 : item was not found + /// - 1 - OK: item is closed + /// - 0 - OK: item is open + /// - -1 - ERROR: item was not found /// int is_close(const char *path) const { const Fl_Tree_Item *item = find_item(path); - if ( item ) return(item->is_close()?1:0); - return(-1); + if ( ! item ) return(-1); + return(item->is_close()?1:0); } - ///////////////////////// - // Item selection methods - ///////////////////////// - /// Select the specified \p item. Use 'deselect()' to de-select it. /// Handles redrawing if anything was actually 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. - /// - 0 - the callback() is not invoked (default) - /// - 1 - the callback() is invoked if the item changed state, - /// and the callback can use item_clicked() to determine the selected item. + /// The callback can use callback_item() and callback_reason() respectively to determine + /// the item changed and the reason the callback was called. /// - void select(Fl_Tree_Item *item, int docallback=0) { + /// \param[in] item -- the item to be selected + /// \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 if item changed state, + /// callback_reason() will be FL_TREE_REASON_SELECTED + /// + /// \returns + /// - 1 - item's state was changed + /// - 0 - item was already selected, no change was made + /// + int select(Fl_Tree_Item *item, int docallback=1) { if ( ! item->is_selected() ) { item->select(); - if ( docallback == 1 ) do_callback_for_item(item); + set_changed(); + if ( docallback ) { + do_callback_for_item(item, FL_TREE_REASON_SELECTED); + } redraw(); + return(1); } + return(0); } /// Select the item specified by \p path (eg: "Parent/child/item"). /// Handles redrawing if anything was actually 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. - /// - 0 - the callback() is not invoked (default) - /// - 1 - the callback() is invoked if the item changed state, - /// and the callback can use item_clicked() to determine the selected item. + /// The callback can use callback_item() and callback_reason() respectively to determine + /// the item changed and the reason the callback was called. + /// + /// \param[in] path -- the tree item's pathname (eg. "Flintstones/Fred") + /// \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 if item changed state, + /// callback_reason() will be FL_TREE_REASON_SELECTED /// /// \returns - /// - 0 : OK - /// - -1 : item was not found + /// - 1 : OK: item's state was changed + /// - 0 : OK: item was already selected, no change was made + /// - -1 : ERROR: item was not found /// - int select(const char *path, int docallback=0) { + int select(const char *path, int docallback=1) { Fl_Tree_Item *item = find_item(path); - if ( item ) { - select(item); - if ( docallback == 1 ) do_callback_for_item(item); - return(0); - } - return(-1); + if ( ! item ) return(-1); + return(select(item, docallback)); } /// Toggle the select state of the specified \p item. - /// Handles redrawing. + /// Handles redrawing if anything was actually 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. - /// - 0 - the callback() is not invoked (default) - /// - 1 - the callback() is invoked, - /// and the callback can use item_clicked() to determine the selected item. + /// The callback can use callback_item() and callback_reason() respectively to determine + /// the item changed and the reason the callback was called. /// - void select_toggle(Fl_Tree_Item *item, int docallback=0) { + /// \param[in] item -- the item to be selected + /// \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, callback_reason() will be + /// either FL_TREE_REASON_SELECTED or FL_TREE_REASON_DESELECTED + /// + void select_toggle(Fl_Tree_Item *item, int docallback=1) { item->select_toggle(); - if ( docallback == 1 ) do_callback_for_item(item); + set_changed(); + if ( docallback ) { + do_callback_for_item(item, item->is_selected() ? FL_TREE_REASON_SELECTED + : FL_TREE_REASON_DESELECTED); + } redraw(); } /// De-select the specified \p item. /// Handles redrawing if anything was actually 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. - /// - 0 - the callback() is not invoked (default) - /// - 1 - the callback() is invoked if the item changed state, - /// and the callback can use item_clicked() to determine the selected item. + /// The callback can use callback_item() and callback_reason() respectively to determine + /// the item changed and the reason the callback was called. /// - void deselect(Fl_Tree_Item *item, int docallback=0) { + /// \param[in] item -- the item to be selected + /// \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 if item changed state, + /// callback_reason() will be FL_TREE_REASON_DESELECTED + /// + /// \returns + /// - 0 - item was already deselected, no change was made + /// - 1 - item's state was changed + /// + int deselect(Fl_Tree_Item *item, int docallback=1) { if ( item->is_selected() ) { item->deselect(); - if ( docallback == 1 ) do_callback_for_item(item); + set_changed(); + if ( docallback ) { + do_callback_for_item(item, FL_TREE_REASON_DESELECTED); + } redraw(); + return(1); } + return(0); } - /// De-select an item specified by \p path (eg: "Parent/child/item"). + /// Deselect an item specified by \p path (eg: "Parent/child/item"). /// Handles redrawing if anything was actually 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. /// - /// \p docallback is an optional paramemter that can either be 0 or 1. - /// - 0 - the callback() is not invoked (default) - /// - 1 - the callback() is invoked if the item changed state, - /// and the callback can use item_clicked() to determine the selected item. + /// \param[in] path -- the tree item's pathname (eg. "Flintstones/Fred") + /// \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 if item changed state, + /// callback_reason() will be FL_TREE_REASON_DESELECTED /// /// \returns - /// - 0 : OK - /// - -1 : item was not found + /// - 1 - OK: item's state was changed + /// - 0 - OK: item was already deselected, no change was made + /// - -1 - ERROR: item was not found /// - int deselect(const char *path, int docallback=0) { + int deselect(const char *path, int docallback=1) { Fl_Tree_Item *item = find_item(path); - if ( item ) { - deselect(item, docallback); - return(0); - } - return(-1); + if ( ! item ) return(-1); + return(deselect(item, docallback)); } - int deselect_all(Fl_Tree_Item *item=0, int docallback=0); - int select_only(Fl_Tree_Item *selitem, int docallback=0); - int select_all(Fl_Tree_Item *item=0, int docallback=0); + int deselect_all(Fl_Tree_Item *item=0, int docallback=1); + int select_only(Fl_Tree_Item *selitem, int docallback=1); + int select_all(Fl_Tree_Item *item=0, int docallback=1); /// See if the specified \p item is selected. + /// + /// \param[in] item -- the item to be tested + /// /// \return /// - 1 : item selected /// - 0 : item deselected @@ -465,6 +578,8 @@ public: } /// See if item specified by \p path (eg: "Parent/child/item") is selected. /// + /// \param[in] path -- the tree item's pathname (eg. "Flintstones/Fred") + /// /// \returns /// - 1 : item selected /// - 0 : item deselected @@ -472,8 +587,8 @@ public: /// int is_selected(const char *path) { Fl_Tree_Item *item = find_item(path); - if ( item ) return(is_selected(item)); - return(-1); + if ( ! item ) return(-1); + return(is_selected(item)); } /// Print the tree as 'ascii art' to stdout. /// Used mainly for debugging. @@ -688,7 +803,109 @@ public: void selectmode(Fl_Tree_Select val) { _prefs.selectmode(val); } - + void show_item(Fl_Tree_Item *item, int yoff=0); + void show_item_bottom(Fl_Tree_Item *item); + void show_item_middle(Fl_Tree_Item *item); + void show_item_top(Fl_Tree_Item *item); + void display(Fl_Tree_Item *item); + int vposition() const; + void vposition(int ypos); + + /// See if widget \p w is one of the Fl_Tree widget's scrollbars. + /// Use this to skip over the scrollbars when walking the child() array. Example: + /// \code + /// for ( int i=0; i<tree->children(); i++ ) { // walk children + /// Fl_Widget *w= tree->child(i); + /// if ( brow->is_scrollbar(w) ) continue; // skip scrollbars + /// ..do work here.. + /// } + /// \endcode + /// \param[in] w Widget to test + /// \returns 1 if \p w is a scrollbar, 0 if not. + /// + int is_scrollbar(Fl_Widget *w) { + return( ( w == _vscroll ) ? 1 : 0 ); + } + /// Gets the current size of the scrollbars' troughs, in pixels. + /// + /// If this value is zero (default), this widget will use the global + /// Fl::scrollbar_size() value as the scrollbar's width. + /// + /// \returns Scrollbar size in pixels, or 0 if the global Fl::scrollsize() is being used. + /// \see Fl::scrollbar_size(int) + /// + int scrollbar_size() const { + return(_scrollbar_size); + } + /// Sets the pixel size of the scrollbars' troughs to the \p size, in pixels. + /// + /// Normally you should not need this method, and should use the global + /// Fl::scrollbar_size(int) instead to manage the size of ALL + /// your widgets' scrollbars. This ensures your application + /// has a consistent UI, is the default behavior, and is normally + /// what you want. + /// + /// Only use THIS method if you really need to override the global + /// scrollbar size. The need for this should be rare. + /// + /// Setting \p size to the special value of 0 causes the widget to + /// track the global Fl::scrollbar_size(), which is the default. + /// + /// \param[in] size Sets the scrollbar size in pixels.\n + /// If 0 (default), scrollbar size tracks the global Fl::scrollbar_size() + /// \see Fl::scrollbar_size() + /// + void scrollbar_size(int size) { + _scrollbar_size = size; + int scrollsize = _scrollbar_size ? _scrollbar_size : Fl::scrollbar_size(); + if ( _vscroll->w() != scrollsize ) { + _vscroll->resize(x()+w()-scrollsize, h(), scrollsize, _vscroll->h()); + } + } + + /////////////////////// + // callback related + /////////////////////// + + /// Sets the item that was changed for this callback. + /// Used internally to pass the item that invoked the callback. + /// + void callback_item(Fl_Tree_Item* item) { + _callback_item = item; + } + /// Gets the item that caused the callback. + /// The callback() can use this value to see which item changed. + /// + Fl_Tree_Item* callback_item() { + return(_callback_item); + } + /// Sets the reason for this callback. + /// Used internally to pass the reason the callback was invoked. + /// + void callback_reason(Fl_Tree_Reason reason) { + _callback_reason = reason; + } + /// Gets the reason for this callback. + /// + /// The callback() can use this value to see why it was called. Example: + /// \code + /// void MyTreeCallback(Fl_Widget *w, void *userdata) { + /// Fl_Tree *tree = (Fl_Tree*)w; + /// Fl_Tree_Item *item = tree->callback_item(); // the item changed (can be NULL if more than one item was changed!) + /// switch ( tree->callback_reason() ) { // reason callback was invoked + /// case FL_TREE_REASON_OPENED: ..item was opened.. + /// case FL_TREE_REASON_CLOSED: ..item was closed.. + /// case FL_TREE_REASON_SELECTED: ..item was selected.. + /// case FL_TREE_REASON_DESELECTED: ..item was deselected.. + /// } + /// } + /// \endcode + /// + Fl_Tree_Reason callback_reason() const { + return(_callback_reason); + } + + /// Load FLTK preferences void load(class Fl_Preferences&); }; diff --git a/FL/Fl_Tree_Item.H b/FL/Fl_Tree_Item.H index b10b1e838..983c8c0ab 100644 --- a/FL/Fl_Tree_Item.H +++ b/FL/Fl_Tree_Item.H @@ -83,7 +83,11 @@ public: Fl_Tree_Item(const Fl_Tree_Prefs &prefs); // CTOR ~Fl_Tree_Item(); // DTOR Fl_Tree_Item(const Fl_Tree_Item *o); // COPY CTOR - void draw(int X, int &Y, int W, Fl_Widget *tree, const Fl_Tree_Prefs &prefs, int lastchild=1); + int x() const { return(_xywh[0]); } + int y() const { return(_xywh[1]); } + int w() const { return(_xywh[2]); } + int h() const { return(_xywh[3]); } + void draw(int X, int &Y, int W, Fl_Widget *tree, Fl_Tree_Item *itemfocus, const Fl_Tree_Prefs &prefs, int lastchild=1); void show_self(const char *indent = "") const; void label(const char *val); const char *label() const; @@ -163,8 +167,10 @@ public: void clear_children(); void swap_children(int ax, int bx); int swap_children(Fl_Tree_Item *a, Fl_Tree_Item *b); - const Fl_Tree_Item *find_item(char **arr) const; - Fl_Tree_Item *find_item(char **arr); + const Fl_Tree_Item *find_child_item(char **arr) const; // const + Fl_Tree_Item *find_child_item(char **arr); // non-const + const Fl_Tree_Item *find_item(char **arr) const; // const + Fl_Tree_Item *find_item(char **arr); // non-const ////////////////// // Adding items ////////////////// @@ -175,12 +181,16 @@ public: int depth() const; Fl_Tree_Item *prev(); Fl_Tree_Item *next(); + Fl_Tree_Item *next_sibling(); + Fl_Tree_Item *prev_sibling(); + Fl_Tree_Item *next_displayed(Fl_Tree_Prefs &prefs); + Fl_Tree_Item *prev_displayed(Fl_Tree_Prefs &prefs); - /// Return the parent for this item. + /// Return the parent for this item. Returns NULL if we are the root. Fl_Tree_Item *parent() { return(_parent); } - /// Return the const parent for this item. + /// Return the const parent for this item. Returns NULL if we are the root. const Fl_Tree_Item *parent() const { return(_parent); } @@ -293,6 +303,12 @@ public: char is_active() const { return(_active); } + /// See if the item is visible. + int visible() const { + return(_visible ? 1 : 0); + } + int visible_r() const; + /// Set the user icon's image. '0' will disable. void usericon(Fl_Image *val) { _usericon = val; diff --git a/FL/Fl_Tree_Prefs.H b/FL/Fl_Tree_Prefs.H index 05e2eef6e..26b5ef39e 100644 --- a/FL/Fl_Tree_Prefs.H +++ b/FL/Fl_Tree_Prefs.H @@ -69,8 +69,8 @@ enum Fl_Tree_Connector { /// enum Fl_Tree_Select { FL_TREE_SELECT_NONE=0, ///< Nothing selected when items are clicked - FL_TREE_SELECT_SINGLE, ///< Single item selected when item is clicked (default) - FL_TREE_SELECT_MULTI ///< Multiple items can be selected by clicking with + FL_TREE_SELECT_SINGLE=1, ///< Single item selected when item is clicked (default) + FL_TREE_SELECT_MULTI=2 ///< Multiple items can be selected by clicking with ///< SHIFT or CTRL or mouse drags. }; 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$". // diff --git a/test/tree.fl b/test/tree.fl index 42d3c6d80..21382bc61 100644 --- a/test/tree.fl +++ b/test/tree.fl @@ -20,19 +20,37 @@ decl {\#include <FL/Fl_Tree.H>} {public global decl {\#include <FL/fl_ask.H>} {public global } -decl {\#include <FL/Fl_File_Chooser.H>} {selected public global +decl {\#include <FL/fl_message.H>} {public global +} + +decl {\#include <FL/Fl_File_Chooser.H>} {public global } decl {\#include <FL/Fl_Preferences.H>} {public global } +decl {int G_cb_counter = 0;} { + comment {// Global callback event counter} private local +} + +Function {reason_as_name(Fl_Tree_Reason reason)} {open return_type {const char*} +} { + code {switch ( reason ) { + case FL_TREE_REASON_NONE: return("none"); + case FL_TREE_REASON_SELECTED: return("selected"); + case FL_TREE_REASON_DESELECTED: return("deselected"); + case FL_TREE_REASON_OPENED: return("opened"); + case FL_TREE_REASON_CLOSED: return("closed"); + default: return("???"); + }} {} +} + Function {Button_CB(Fl_Widget*w, void*data)} {return_type void } { code {fprintf(stderr, "'%s' button pushed\\n", w->label());} {} } -Function {RebuildTree()} {open -} { +Function {RebuildTree()} {} { code {// REBUILD THE TREE TO MAKE CURRENT "DEFAULT" PREFS TAKE EFFECT tree->clear(); tree->add("Aaa"); @@ -108,6 +126,15 @@ tree->add("Descending/Bbb"); tree->add("Descending/Yyy"); tree->add("Descending/Ccc"); +// Add 500 items in numerical order +tree->sortorder(FL_TREE_SORT_NONE); +for ( int t=0; t<500; t++ ) { + static char s[80]; + sprintf(s, "500 Items/item %04d", t); + tree->add(s); +} +tree->close("500 Items"); // close the 500 items by default + tree->redraw();} {} } @@ -119,13 +146,17 @@ Function {} {open } { Fl_Group tree { user_data 1234 - callback {Fl_Tree_Item *item = tree->item_clicked(); + callback {G_cb_counter++; // Increment callback counter whenever tree callback is invoked + +Fl_Tree_Item *item = tree->callback_item(); if ( item ) { - fprintf(stderr, "TREE CALLBACK: label='%s' userdata=%ld\\n", + fprintf(stderr, "TREE CALLBACK: label='%s' userdata=%ld reason=%s\\n", item->label(), - (long)tree->user_data()); + (long)tree->user_data(), + reason_as_name(tree->callback_reason())); } else { - fprintf(stderr, "TREE CALLBACK: no item (probably multiple items were changed at once)\\n"); + fprintf(stderr, "TREE CALLBACK: reason=%s item=(no item -- probably multiple items were changed at once)\\n", + reason_as_name(tree->callback_reason())); }} open xywh {15 15 550 390} box DOWN_BOX color 55 class Fl_Tree @@ -136,7 +167,7 @@ if ( item ) { callback {int val = (int)margintop_slider->value(); tree->margintop(val); tree->redraw();} - tooltip {Changes the top margin for the tree widget} xywh {190 414 240 16} type Horizontal labelsize 12 align 4 textsize 12 + tooltip {Changes the top margin for the tree widget} xywh {190 414 240 16} type Horizontal labelsize 12 align 4 step 0.01 textsize 12 code0 {o->value(tree->margintop());} code1 {o->range(0.0, 100.0);} code2 {o->step(1.0);} @@ -148,7 +179,7 @@ tree->redraw();} callback {int val = (int)marginleft_slider->value(); tree->marginleft(val); tree->redraw();} - tooltip {Changes the left margin for the tree widget} xywh {190 434 240 16} type Horizontal labelsize 12 align 4 textsize 12 + tooltip {Changes the left margin for the tree widget} xywh {190 434 240 16} type Horizontal labelsize 12 align 4 step 0.01 textsize 12 code0 {o->value(tree->marginleft());} code1 {o->range(0.0, 100.0);} code2 {o->step(1.0);} @@ -160,7 +191,7 @@ tree->redraw();} callback {int val = (int)openchild_marginbottom_slider->value(); tree->openchild_marginbottom(val); tree->redraw();} - tooltip {Changes the vertical space below an open child tree} xywh {190 454 240 16} type Horizontal labelsize 12 align 4 textsize 12 + tooltip {Changes the vertical space below an open child tree} xywh {190 454 240 16} type Horizontal labelsize 12 align 4 step 0.01 textsize 12 code0 {o->value(tree->openchild_marginbottom());} code1 {o->range(0.0, 100.0);} code2 {o->step(1.0);} @@ -173,7 +204,7 @@ tree->redraw();} // DO SELECTED ITEMS int count = 0; -for ( Fl_Tree_Item *item=tree->first(); item; item = item->next() ) { +for ( Fl_Tree_Item *item=tree->first(); item; item = tree->next(item) ) { if ( item->is_selected() ) { item->labelsize(size); count++; @@ -182,14 +213,14 @@ for ( Fl_Tree_Item *item=tree->first(); item; item = item->next() ) { // NO ITEMS SELECTED? DO ALL if ( ! count ) { - for ( Fl_Tree_Item *item=tree->first(); item; item = item->next() ) { + for ( Fl_Tree_Item *item=tree->first(); item; item = tree->next(item) ) { item->labelsize(size); } } tree->redraw();} tooltip {Changes the font size of the selected items -If none selected, all are changed} xywh {190 474 240 16} type Horizontal labelsize 12 align 4 textsize 12 +If none selected, all are changed} xywh {190 474 240 16} type Horizontal labelsize 12 align 4 step 0.01 textsize 12 code0 {o->value(tree->labelsize());} code1 {o->range(5.0, 200.0);} code2 {o->step(1.0);} @@ -199,7 +230,7 @@ If none selected, all are changed} xywh {190 474 240 16} type Horizontal labelsi label {Connector width} user_data tree callback {tree->connectorwidth((int)connectorwidth_slider->value());} - tooltip {Tests Fl_Tree::connectorwidth()} xywh {190 494 240 16} type Horizontal labelsize 12 align 4 textsize 12 + tooltip {Tests Fl_Tree::connectorwidth()} xywh {190 494 240 16} type Horizontal labelsize 12 align 4 step 0.01 textsize 12 code0 {o->value(tree->connectorwidth());} code1 {o->range(1.0, 100.0);} code2 {o->step(1.0);} @@ -256,16 +287,25 @@ if ( usericon_radio->value() ) { if ( ( i = tree->find_item("Bbb/bgb/222") ) != NULL ) i->usericon(0); if ( ( i = tree->find_item("Bbb/bgb/333") ) != NULL ) i->usericon(0); }} - tooltip {Tests Fl_Tree_Item::usericon()} xywh {145 525 130 16} down_box DOWN_BOX labelsize 11 + tooltip {Tests Fl_Tree_Item::usericon()} xywh {90 525 130 16} down_box DOWN_BOX labelsize 11 } Fl_Check_Button showroot_radio { label {Show root?} user_data tree callback {int onoff = showroot_radio->value(); tree->showroot(onoff);} - tooltip {Tests Fl_Tree_Item::usericon()} xywh {145 544 130 16} down_box DOWN_BOX labelsize 11 + tooltip {Tests Fl_Tree_Item::usericon()} xywh {90 542 130 16} down_box DOWN_BOX labelsize 11 code0 {int onoff = tree->showroot(); showroot_radio->value(onoff);} } + Fl_Check_Button visiblefocus_checkbox { + label {Visible focus?} + user_data tree + callback {int onoff = visiblefocus_checkbox->value(); +tree->visible_focus(onoff);} + tooltip {Toggles the tree's visible_focus() +This toggles the visible 'focus box'} xywh {90 559 130 16} down_box DOWN_BOX labelsize 11 + code0 {int onoff = tree->visible_focus(); visiblefocus_checkbox->value(onoff);} + } Fl_Choice collapseicons_chooser { label {Collapse icons} callback {static const char *L_open_xpm[] = { @@ -355,7 +395,7 @@ switch ( collapseicons_chooser->value() ) { tree->showcollapse(0); break; }} open - tooltip {Tests Fl_Tree::openicon() and Fl_Tree::closeicon()} xywh {145 572 110 16} down_box BORDER_BOX labelsize 11 textsize 11 + tooltip {Tests Fl_Tree::openicon() and Fl_Tree::closeicon()} xywh {115 589 110 16} down_box BORDER_BOX labelsize 11 textsize 11 } { MenuItem {} { label Normal @@ -378,7 +418,7 @@ switch ( connectorstyle_chooser->value() ) { case 1: tree->connectorstyle(FL_TREE_CONNECTOR_DOTTED); break; case 2: tree->connectorstyle(FL_TREE_CONNECTOR_SOLID); break; }} open - tooltip {Tests connectorstyle() bit flags} xywh {145 592 110 16} down_box BORDER_BOX labelsize 11 textsize 11 + tooltip {Tests connectorstyle() bit flags} xywh {115 609 110 16} down_box BORDER_BOX labelsize 11 textsize 11 code0 {switch (tree->connectorstyle()) { case FL_TREE_CONNECTOR_NONE: connectorstyle_chooser->value(0); break; case FL_TREE_CONNECTOR_DOTTED: connectorstyle_chooser->value(1); break; case FL_TREE_CONNECTOR_SOLID: connectorstyle_chooser->value(2); break; }} } { MenuItem {} { @@ -408,7 +448,7 @@ switch ( labelcolor_chooser->value() ) { // DO SELECTED ITEMS int count = 0; -for ( Fl_Tree_Item *item=tree->first(); item; item = item->next() ) { + for ( Fl_Tree_Item *item=tree->first(); item; item = tree->next(item) ) { if ( item->is_selected() ) { item->labelcolor(c); count++; @@ -417,14 +457,14 @@ for ( Fl_Tree_Item *item=tree->first(); item; item = item->next() ) { // NO ITEMS SELECTED? DO ALL if ( ! count ) { - for ( Fl_Tree_Item *item=tree->first(); item; item = item->next() ) { + for ( Fl_Tree_Item *item=tree->first(); item; item = tree->next(item) ) { item->labelcolor(c); } } tree->redraw();} open tooltip {Changes the label color for the selected items -If no items selected, all are changed} xywh {145 612 110 16} down_box BORDER_BOX labelsize 11 textsize 11 +If no items selected, all are changed} xywh {115 629 110 16} down_box BORDER_BOX labelsize 11 textsize 11 } { MenuItem {} { label Black @@ -452,7 +492,7 @@ switch ( selectmode_chooser->value() ) { case 2: tree->selectmode(FL_TREE_SELECT_MULTI); break; // Multi default: tree->selectmode(FL_TREE_SELECT_SINGLE); break; // Single }} - tooltip {Sets how Fl_Tree handles mouse selection of tree items} xywh {145 632 110 16} down_box BORDER_BOX labelsize 11 textsize 11 + tooltip {Sets how Fl_Tree handles mouse selection of tree items} xywh {115 649 110 16} down_box BORDER_BOX labelsize 11 textsize 11 code0 {selectmode_chooser->value(1);} code1 {cb_selectmode_chooser(selectmode_chooser, (void*)0);} } { @@ -477,8 +517,8 @@ switch ( whenmode_chooser->value() ) { case 1: tree->when(FL_WHEN_CHANGED); break; case 2: tree->when(FL_WHEN_NEVER); break; default: tree->when(FL_WHEN_RELEASE); break; -}} - tooltip {Sets when() the tree's callback is invoked} xywh {145 652 110 16} down_box BORDER_BOX labelsize 11 textsize 11 +}} open + tooltip {Sets when() the tree's callback is invoked} xywh {115 669 110 16} down_box BORDER_BOX labelsize 11 textsize 11 code0 {whenmode_chooser->value(1);} code1 {cb_whenmode_chooser(whenmode_chooser, (void*)0);} } { @@ -495,68 +535,96 @@ switch ( whenmode_chooser->value() ) { xywh {60 60 36 21} labelsize 11 } } - Fl_Box docallback_box { - xywh {280 521 285 81} box GTK_DOWN_BOX color 47 + Fl_Box showitem_box { + label {show_item() +} + xywh {468 423 60 77} box GTK_DOWN_BOX color 47 labelsize 11 align 1 + } + Fl_Button {} { + label Top + callback {Fl_Tree_Item *item = tree->next_selected_item(); +tree->show_item_top(item);} + tooltip {Scrolls selected item to the top of the display +(only works if scrollbar showing) +To use: +1) open '500 items' +2) select item 0010 +3) Hit Top/Mid/Bot} xywh {478 433 40 16} labelsize 11 + } + Fl_Button {} { + label Mid + callback {Fl_Tree_Item *item = tree->next_selected_item(); +tree->show_item_middle(item);} + tooltip {Scrolls the selected item to the middle of the display +To use: + 1) open '500 items' + 2) select 'item 0010' + 3) Hit Top/Mid/Bot} xywh {478 453 40 16} labelsize 11 + } + Fl_Button {} { + label Bot + callback {Fl_Tree_Item *item = tree->next_selected_item(); +tree->show_item_bottom(item);} + tooltip {Scrolls the selected item to the bottom of the display +To use: + 1) open '500 items' + 2) select 'item 0010' + 3) Hit Top/Mid/Bot} xywh {478 473 40 16} labelsize 11 } - Fl_Check_Button docallback_radio { - label {Invoke callback on select changes?} user_data_type {void*} - tooltip {Invokes the callback when one or more item's state changes.} xywh {310 529 230 16} down_box DOWN_BOX labelsize 11 + Fl_Box docallback_box { + label {Selection State Changes} + xywh {245 527 320 77} box GTK_DOWN_BOX color 47 labelsize 12 align 1 } Fl_Button selectall_button { label {Select All} - callback {int docallbacks = docallback_radio->value() ? 1 : 0; -tree->select_all(0,docallbacks); + callback {tree->select_all(0); tree->redraw();} - tooltip {Selects all items in the tree} xywh {305 551 105 16} labelsize 11 + tooltip {Selects all items in the tree} xywh {260 539 75 16} labelsize 9 } Fl_Button deselectall_button { label {Deselect All} - callback {int docallbacks = docallback_radio->value() ? 1 : 0; -tree->deselect_all(0,docallbacks); + callback {tree->deselect_all(0); tree->redraw();} - tooltip {Deselects all items in the tree} xywh {305 571 105 16} labelsize 11 + tooltip {Deselects all items in the tree} xywh {260 559 75 16} labelsize 9 } - Fl_Light_Button bbbselect_toggle { - label { Select Bbb} + Fl_Light_Button bbbselect2_toggle { + label { Select Bbb+} callback {// Toggle select of just the Bbb item and its immediate children -int docallback = docallback_radio->value() ? 1 : 0; Fl_Tree_Item *bbb = tree->find_item("/Bbb"); if ( !bbb) { fl_alert("FAIL: Couldn't find item '/Bbb'???"); return; } -int onoff = bbbselect_toggle->value(); -if ( onoff ) tree->select_all(bbb, docallback); // select /Bbb and its children -else tree->deselect_all(bbb, docallback); // deselect /Bbb and its children - -tree->redraw(); - -// Toggle select of just the Bbb item and its immediate children -//int docallback = docallback_radio->value() ? 1 : 0; -//int onoff = bbbselect_toggle->value(); -// -//if ( onoff ) tree->select("/Bbb", docallback); -//else tree->deselect("/Bbb", docallback); -// -//tree->redraw();} - tooltip {Toggle selection of the /Bbb item and its children} xywh {430 552 115 15} labelsize 11 +int onoff = bbbselect2_toggle->value(); +if ( onoff ) tree->select_all(bbb); // select /Bbb and its children +else tree->deselect_all(bbb); // deselect /Bbb and its children} + tooltip {Toggle selection of the /Bbb item and its children} xywh {350 560 95 15} selection_color 1 labelsize 9 } Fl_Light_Button bbbchild02select_toggle { label { Toggle child-02} callback {// Toggle select of just the /Bbb/child-02 item -int docallback = docallback_radio->value() ? 1 : 0; int onoff = bbbchild02select_toggle->value(); -if ( onoff ) tree->select("/Bbb/child-02", docallback); -else tree->deselect("/Bbb/child-02", docallback); -tree->redraw();} - tooltip {Toggle the single item /Bbb/child-02} xywh {430 571 115 16} labelsize 11 +if ( onoff ) tree->select("/Bbb/child-02"); +else tree->deselect("/Bbb/child-02");} + tooltip {Toggle the single item /Bbb/child-02} xywh {350 579 95 16} selection_color 1 labelsize 9 + } + Fl_Button loaddb_button { + label {Load Database...} + callback {const char *filename = fl_file_chooser("Select a Preferences style Database", "Preferences(*.prefs)", 0L); +if (filename) { + tree->clear(); + Fl_Preferences prefs(filename, 0L, 0L); + tree->load(prefs); + tree->redraw(); +}} + tooltip {Load the contents of an Fl_Preferences database into the tree view} xywh {380 614 90 16} labelsize 9 } Fl_Light_Button deactivate_toggle { label { Deactivate} callback {int onoff = deactivate_toggle->value() ? 0 : 1; int count = 0; -for (Fl_Tree_Item *item=tree->first(); item; item = item->next()) { +for ( Fl_Tree_Item *item=tree->first(); item; item = tree->next(item) ) { if ( item->is_selected() ) { item->activate(onoff); ++count; @@ -564,14 +632,14 @@ for (Fl_Tree_Item *item=tree->first(); item; item = item->next()) { } if ( count == 0 ) { - for (Fl_Tree_Item *item=tree->first(); item; item = item->next()) { + for ( Fl_Tree_Item *item=tree->first(); item; item = tree->next(item) ) { item->activate(onoff); } } tree->redraw();} tooltip {Toggle the deactivation state of the selected items. -If none are selected, all are set.} xywh {280 633 90 16} labelsize 11 +If none are selected, all are set.} xywh {280 634 90 16} selection_color 1 labelsize 9 } Fl_Light_Button bold_toggle { label { Bold Font} @@ -579,7 +647,7 @@ If none are selected, all are set.} xywh {280 633 90 16} labelsize 11 // DO SELECTED ITEMS int count = 0; -for ( Fl_Tree_Item *item=tree->first(); item; item = item->next() ) { +for ( Fl_Tree_Item *item=tree->first(); item; item = tree->next(item) ) { if ( item->is_selected() ) { item->labelfont(face); count++; @@ -588,14 +656,14 @@ for ( Fl_Tree_Item *item=tree->first(); item; item = item->next() ) { // NO ITEMS SELECTED? DO ALL if ( ! count ) { - for ( Fl_Tree_Item *item=tree->first(); item; item = item->next() ) { + for ( Fl_Tree_Item *item=tree->first(); item; item = tree->next(item) ) { item->labelfont(face); } } tree->redraw();} tooltip {Toggles bold font for selected items -If nothing selected, all are changed} xywh {280 652 90 16} labelsize 11 +If nothing selected, all are changed} xywh {280 654 90 16} selection_color 1 labelsize 9 } Fl_Button insertabove_button { label {Insert Above} @@ -610,15 +678,38 @@ while (item) { } tree->redraw();} - tooltip {Inserts three items above the selected items} xywh {380 632 90 16} labelsize 11 + tooltip {Inserts three items above the selected items} xywh {380 634 90 16} labelsize 9 } Fl_Button rebuildtree_button { label {Rebuild Tree} callback {RebuildTree();} - tooltip {Rebuilds the tree with defaults} xywh {380 652 90 16} labelsize 11 + tooltip {Rebuilds the tree with defaults} xywh {380 654 90 16} labelsize 9 + } + Fl_Button showpathname_button { + label {Show Pathname} + callback {Fl_Tree_Item *item = tree->first_selected_item(); +if ( !item ) { fl_message("No item was selected"); return; } + +char pathname[256]; +switch ( tree->item_pathname(pathname, sizeof(pathname), item) ) { + case 0: fl_message("Pathname for '%s' is: \\"%s\\"", (item->label() ? item->label() : "???"), pathname); break; + case -1: fl_message("item_pathname() returned -1 (NOT FOUND)"); break; + case -2: fl_message("item_pathname() returned -2 (STRING TOO LONG)"); break; +}} selected + tooltip {Show the pathname for the selected item. Tests the Fl_Tree::item_pathname() method.} xywh {380 674 90 16} labelsize 8 + } + Fl_Button showselected_button { + label {Show Selected} + callback {fprintf(stderr, "--- SELECTED ITEMS\\n"); +for ( Fl_Tree_Item *item = tree->first_selected_item(); + item; + item = tree->next_selected_item(item) ) { + fprintf(stderr, "\\t%s\\n", item->label() ? item->label() : "???"); +}} + tooltip {Clears the selected items} xywh {475 614 90 16} labelsize 9 } Fl_Button clearselected_button { - label {Clear Selected} + label {Remove Selected} callback {Fl_Tree_Item *item=tree->first(); while (item) { if ( item->is_selected() ) { @@ -630,34 +721,130 @@ while (item) { } tree->redraw();} - tooltip {Clears the selected items} xywh {475 632 90 16} labelsize 11 + tooltip {Removes the selected items} xywh {475 634 90 16} labelsize 9 } Fl_Button clearall_button { label {Clear All} callback {tree->clear(); tree->redraw();} tooltip {Clears all items -Tests Fl_Tree::clear()} xywh {475 652 90 16} labelsize 11 +Tests Fl_Tree::clear()} xywh {475 654 90 16} labelsize 9 + } + Fl_Button testcallbackflag_button { + label {Test Callback Flag} + callback {Fl_Tree_Item *root = tree->root(); +fprintf(stderr, "--- Checking docallback off\\n"); + +//// "OFF" TEST + +// open/close: Make sure these methods don't trigger cb +G_cb_counter = 0; tree->close(root, 0); if ( G_cb_counter ) fl_alert("FAILED 'OFF' TEST\\n close(item) triggered cb!"); +G_cb_counter = 0; tree->open(root, 0); if ( G_cb_counter ) fl_alert("FAILED 'OFF' TEST\\n open(item) triggered cb!"); +G_cb_counter = 0; tree->open_toggle(root, 0); if ( G_cb_counter ) fl_alert("FAILED 'OFF' TEST\\n open_toggle(item) triggered cb!"); +G_cb_counter = 0; tree->open("ROOT", 0); if ( G_cb_counter ) fl_alert("FAILED 'OFF' TEST\\n open(path) triggered cb!"); +G_cb_counter = 0; tree->close("ROOT", 0); if ( G_cb_counter ) fl_alert("FAILED 'OFF' TEST\\n close(path) triggered cb!"); +tree->open(root,0); // leave root open + +// select/deselect: Make sure these methods don't trigger cb +G_cb_counter = 0; tree->select(root, 0); if ( G_cb_counter ) fl_alert("FAILED 'OFF' TEST\\n select(item) triggered cb!"); +G_cb_counter = 0; tree->deselect(root, 0); if ( G_cb_counter ) fl_alert("FAILED 'OFF' TEST\\n deselect(item) triggered cb!"); +G_cb_counter = 0; tree->select_toggle(root, 0); if ( G_cb_counter ) fl_alert("FAILED 'OFF' TEST\\n select_toggle(item) triggered cb!"); +G_cb_counter = 0; tree->deselect("ROOT", 0); if ( G_cb_counter ) fl_alert("FAILED 'OFF' TEST\\n deselect(path) triggered cb!"); +G_cb_counter = 0; tree->select("ROOT", 0); if ( G_cb_counter ) fl_alert("FAILED 'OFF' TEST\\n select(path) triggered cb!"); +tree->deselect("ROOT"); // leave deselected + +//// "ON" TEST + +// open/close: Make sure these methods don't trigger cb +G_cb_counter = 0; tree->close(root, 1); if ( !G_cb_counter ) fl_alert("FAILED 'ON' TEST\\n close(item) cb wasn't triggered!"); +G_cb_counter = 0; tree->open(root, 1); if ( !G_cb_counter ) fl_alert("FAILED 'ON' TEST\\n open(item) cb wasn't triggered!"); +G_cb_counter = 0; tree->open_toggle(root, 1); if ( !G_cb_counter ) fl_alert("FAILED 'ON' TEST\\n open_toggle(item) cb wasn't triggered!"); +G_cb_counter = 0; tree->open(root, 1); if ( !G_cb_counter ) fl_alert("FAILED 'ON' TEST\\n open(item)[2] cb wasn't triggered!"); +G_cb_counter = 0; tree->close(root, 1); if ( !G_cb_counter ) fl_alert("FAILED 'ON' TEST\\n close(item)[2] cb wasn't triggered!"); +G_cb_counter = 0; tree->open("ROOT", 1); if ( !G_cb_counter ) fl_alert("FAILED 'ON' TEST\\n open(path) cb wasn't triggered!"); +G_cb_counter = 0; tree->close("ROOT", 1); if ( !G_cb_counter ) fl_alert("FAILED 'ON' TEST\\n close(path) cb wasn't triggered!"); +tree->open(root,0); // leave root open + +// select/deselect: Make sure these methods don't trigger cb +G_cb_counter = 0; tree->select(root, 1); if ( !G_cb_counter ) fl_alert("FAILED 'ON' TEST\\n select(item) cb wasn't triggered!"); +G_cb_counter = 0; tree->deselect(root, 1); if ( !G_cb_counter ) fl_alert("FAILED 'ON' TEST\\n deselect(item) cb wasn't triggered!"); +G_cb_counter = 0; tree->select_toggle(root, 1); if ( !G_cb_counter ) fl_alert("FAILED 'ON' TEST\\n select_toggle(item) cb wasn't triggered!"); +G_cb_counter = 0; tree->deselect("ROOT", 1); if ( !G_cb_counter ) fl_alert("FAILED 'ON' TEST\\n deselect(path) cb wasn't triggered!"); +G_cb_counter = 0; tree->select("ROOT", 1); if ( !G_cb_counter ) fl_alert("FAILED 'ON' TEST\\n select(path) cb wasn't triggered!"); +tree->deselect("ROOT"); // leave deselected + +//// "default" TEST (should be same as 'on' + +// open/close: Make sure these methods don't trigger cb +G_cb_counter = 0; tree->close(root); if ( !G_cb_counter ) fl_alert("FAILED 'DEFAULT' TEST: close(item) cb wasn't triggered!"); +G_cb_counter = 0; tree->open(root); if ( !G_cb_counter ) fl_alert("FAILED 'DEFAULT' TEST: open(item) cb wasn't triggered!"); +G_cb_counter = 0; tree->open_toggle(root); if ( !G_cb_counter ) fl_alert("FAILED 'DEFAULT' TEST: open_toggle(item) cb wasn't triggered!"); +G_cb_counter = 0; tree->open("ROOT"); if ( !G_cb_counter ) fl_alert("FAILED 'DEFAULT' TEST: open(path) cb wasn't triggered!"); +G_cb_counter = 0; tree->close("ROOT"); if ( !G_cb_counter ) fl_alert("FAILED 'DEFAULT' TEST: close(path) cb wasn't triggered!"); +tree->open(root,0); // leave root open + +// select/deselect: Make sure these methods don't trigger cb +G_cb_counter = 0; tree->select(root); if ( !G_cb_counter ) fl_alert("FAILED 'DEFAULT' TEST\\n select(item) cb wasn't triggered!"); +G_cb_counter = 0; tree->deselect(root); if ( !G_cb_counter ) fl_alert("FAILED 'DEFAULT' TEST\\n deselect(item) cb wasn't triggered!"); +G_cb_counter = 0; tree->select_toggle(root); if ( !G_cb_counter ) fl_alert("FAILED 'DEFAULT' TEST\\n select_toggle(item) cb wasn't triggered!"); +G_cb_counter = 0; tree->deselect("ROOT"); if ( !G_cb_counter ) fl_alert("FAILED 'DEFAULT' TEST\\n deselect(path) cb wasn't triggered!"); +G_cb_counter = 0; tree->select("ROOT"); if ( !G_cb_counter ) fl_alert("FAILED 'DEFAULT' TEST\\n select(path) cb wasn't triggered!"); +tree->deselect("ROOT"); // leave deselected + +fl_alert("TEST COMPLETED\\n If you didn't see any error dialogs, test PASSED.");} + tooltip {Test the 'docallback' argument can disable callbacks.} xywh {475 674 90 16} labelsize 8 + } + Fl_Light_Button rootselect_toggle { + label {Select ROOT} + callback {// Toggle select of ROOT item and its children +Fl_Tree_Item *item = tree->find_item("/ROOT"); +if ( !item) { + fl_alert("FAIL: Couldn't find item '/ROOT'???"); + return; +} +int onoff = rootselect_toggle->value(); +if ( onoff ) tree->select(item); // select /ROOT and its children +else tree->deselect(item); // deselect /ROOT and its children} + tooltip {Toggle selection of the ROOT item} xywh {460 540 90 15} selection_color 1 labelsize 9 } - Fl_Button loaddb_button { - label {Load Database...} - callback {const char *filename = fl_file_chooser("Select a Preferences style Database", "Preferences(*.prefs)", 0L); -if (filename) { - tree->clear(); - Fl_Preferences prefs(filename, 0L, 0L); - tree->load(prefs); - tree->redraw(); -}} - tooltip {Load the contents of an Fl_Preferences database into the tree view} xywh {380 612 90 16} labelsize 11 + Fl_Light_Button bbbselect_toggle { + label { Select Bbb} + callback {// Toggle select of just the Bbb item (not children) +Fl_Tree_Item *bbb = tree->find_item("/Bbb"); +if ( !bbb) { + fl_alert("FAIL: Couldn't find item '/Bbb'???"); + return; +} +int onoff = bbbselect_toggle->value(); +if ( onoff ) tree->select(bbb); // select /Bbb +else tree->deselect(bbb); // deselect /Bbb} + tooltip {Toggle selection of just the /Bbb item +(Not children)} xywh {350 540 95 15} selection_color 1 labelsize 9 + } + Fl_Light_Button rootselect2_toggle { + label {Select ROOT+} + callback {// Toggle select of ROOT item and its children +Fl_Tree_Item *item = tree->find_item("/ROOT"); +if ( !item) { + fl_alert("FAIL: Couldn't find item '/ROOT'???"); + return; +} +int onoff = rootselect2_toggle->value(); +if ( onoff ) tree->select_all(item); // select /ROOT and its children +else tree->deselect_all(item); // deselect /ROOT and its children} + tooltip {Toggle selection of the ROOT item and all children} xywh {460 560 90 15} selection_color 1 labelsize 9 } } code {// Initialize Tree tree->root_label("ROOT"); -docallback_radio->value(1); // enable docallbacks radio button RebuildTree(); -tree->show_self();} {} +/*tree->show_self();*/} {} code {// FLTK stuff Fl::scheme("gtk+"); window->resizable(window); -window->size_range(window->w(), window->h(), 0, 0);} {} +window->size_range(window->w(), window->h(), 0, 0); + + if ( tree->when() == FL_WHEN_CHANGED ) whenmode_chooser->value(0); +else if ( tree->when() == FL_WHEN_RELEASE ) whenmode_chooser->value(1); +else if ( tree->when() == FL_WHEN_NEVER ) whenmode_chooser->value(2);} {} } diff --git a/test/unittest_scrollbarsize.cxx b/test/unittest_scrollbarsize.cxx index 2c2725e3e..babb82269 100644 --- a/test/unittest_scrollbarsize.cxx +++ b/test/unittest_scrollbarsize.cxx @@ -27,6 +27,7 @@ #include <FL/Fl_Group.H> #include <FL/Fl_Browser.H> +#include <FL/Fl_Tree.H> #include <FL/Fl_Value_Slider.H> // @@ -34,10 +35,12 @@ // class ScrollBarSizeTest : public Fl_Group { Fl_Browser *brow_a, *brow_b, *brow_c; + Fl_Tree *tree_a, *tree_b, *tree_c; Fl_Browser *makebrowser(int X,int Y,int W,int H,const char*L=0) { Fl_Browser *b = new Fl_Browser(X,Y,W,H,L); b->type(FL_MULTI_BROWSER); + b->align(FL_ALIGN_TOP); b->add("Papa"); b->add("Delta"); b->add("Hotel"); b->add("Long entry will show h-bar"); b->add("Charlie"); b->add("Echo"); b->add("Foxtrot"); @@ -59,12 +62,29 @@ class ScrollBarSizeTest : public Fl_Group { b->add("Whisky"); b->add("Zulu"); return(b); } + Fl_Tree *maketree(int X,int Y,int W,int H,const char*L=0) { + Fl_Tree *b = new Fl_Tree(X,Y,W,H,L); + b->type(FL_TREE_SELECT_MULTI); + b->align(FL_ALIGN_TOP); + b->add("Papa"); b->add("Delta"); b->add("Hotel"); + b->add("Long entry will show h-bar"); + b->add("Charlie"); b->add("Echo"); b->add("Foxtrot"); + b->add("Golf"); b->add("Lima"); b->add("Victor"); + b->add("Alpha"); b->add("Xray"); b->add("Yankee"); + b->add("Oscar"); b->add("India"); b->add("Juliet"); + b->add("Kilo"); b->add("Mike"); b->add("Sierra"); + b->add("November"); b->add("Tango"); b->add("Quebec"); + b->add("Bravo"); b->add("Romeo"); b->add("Uniform"); + b->add("Whisky"); b->add("Zulu"); + return(b); + } void slide_cb2(Fl_Value_Slider *in) { const char *label = in->label(); int val = in->value(); //fprintf(stderr, "VAL='%d'\n",val); if ( strcmp(label,"A: Scroll Size") == 0 ) { brow_a->scrollbar_size(val); + tree_a->scrollbar_size(val); } else { Fl::scrollbar_size(val); } @@ -82,10 +102,36 @@ public: // CTOR ScrollBarSizeTest(int X, int Y, int W, int H) : Fl_Group(X,Y,W,H) { begin(); - brow_a = makebrowser(X+ 10,Y+40,100,H-170,"Browser A"); - brow_b = makebrowser(X+120,Y+40,100,H-170,"Browser B"); - brow_c = makebrowser(X+240,Y+40,100,H-170,"Browser C"); - Fl_Value_Slider *slide_glob = new Fl_Value_Slider(X+100,Y+10,100,18,"Global Scroll Size"); + // _____________ _______________ + // |_____________| |_______________| + // --- ----- <-- tgrpy + // brow_a brow_b brow_c | 14 | + // ---------- ---------- ---------- --- | <-- browy + // | | | | | | | | + // | | | | | | |browh | + // | | | | | | | | + // ---------- ---------- ---------- --- tgrph + // | | + // tree_a tree_b tree_c | 20 | + // ---------- ---------- ---------- --- | <-- treey + // | | | | | | | | + // | | | | | | |treeh | + // | | | | | | | | + // ---------- ---------- ---------- --- ------ + // + int tgrpy = Y+30; + int tgrph = H-130; + int browy = tgrpy+14; + int browh = tgrph/2 - 20; + int treey = browy + browh + 20; + int treeh = browh; + brow_a = makebrowser(X+ 10,browy,100,browh,"Browser A"); + brow_b = makebrowser(X+120,browy,100,browh,"Browser B"); + brow_c = makebrowser(X+240,browy,100,browh,"Browser C"); + tree_a = maketree(X+ 10,treey,100,treeh,"Tree A"); + tree_b = maketree(X+120,treey,100,treeh,"Tree B"); + tree_c = maketree(X+240,treey,100,treeh,"Tree C"); + Fl_Value_Slider *slide_glob = new Fl_Value_Slider(X+100,Y,100,18,"Global Scroll Size"); slide_glob->value(16); slide_glob->type(FL_HORIZONTAL); slide_glob->align(FL_ALIGN_LEFT); @@ -93,7 +139,7 @@ public: slide_glob->step(1.0); slide_glob->callback(slide_cb, (void*)this); slide_glob->labelsize(12); - Fl_Value_Slider *slide_browa = new Fl_Value_Slider(X+350,Y+10,100,18,"A: Scroll Size"); + Fl_Value_Slider *slide_browa = new Fl_Value_Slider(X+350,Y,100,18,"A: Scroll Size"); slide_browa->value(16); slide_browa->type(FL_HORIZONTAL); slide_browa->align(FL_ALIGN_LEFT); |
