fltk/src/Fl_Tree.cxx
Greg Ercolano ebd1e106aa Fixes STR#3041, int -> size_t
git-svn-id: file:///fltk/svn/fltk/branches/branch-1.3@10086 ea41ed52-d2ee-0310-a9c1-e6b18d33e121
2014-01-27 02:16:24 +00:00

2848 lines
94 KiB
C++

//
// "$Id$"
//
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <FL/Fl_Tree.H>
#include <FL/Fl_Preferences.H>
//////////////////////
// Fl_Tree.cxx
//////////////////////
//
// Fl_Tree -- This file is part of the Fl_Tree widget for FLTK
// Copyright (C) 2009-2010 by Greg Ercolano.
//
// This library is free software. Distribution and use rights are outlined in
// the file "COPYING" which should have been included with this file. If this
// file is missing or damaged, see the license at:
//
// http://www.fltk.org/COPYING.php
//
// Please report all bugs and problems on the following page:
//
// http://www.fltk.org/str.php
//
// INTERNAL: scroller callback (hor+vert scroll)
static void scroll_cb(Fl_Widget*,void *data) {
((Fl_Tree*)data)->redraw();
}
// INTERNAL: Parse elements from 'path' into an array of null terminated strings
// Handles escape characters, ignores multiple /'s.
// Path="/aa/bb", returns arr[0]="aa", arr[1]="bb", arr[2]=0.
// Caller must call free_path(arr).
//
static char **parse_path(const char *path) {
size_t len = strlen(path);
char *cp = new char[(len+1)], *word = cp, *s = cp; // freed below or in free_path()
char **ap = new char*[(len+1)], **arr = ap; // overallocates arr[]
while (1) {
if (*path =='/' || *path == 0) { // handle path sep or eos
if (word != s) { *s++ = 0; *arr++= word; word = s; }
if ( !*path++) break; else continue; // eos? done, else cont
} else if ( *path == '\\' ) { // handle escape
if ( *(++path) ) { *s++ = *path++; } else continue;
} else { *s++ = *path++; } // handle normal char
}
*arr = 0;
if ( arr == ap ) delete[] cp; // empty arr[]? delete since free_path() can't
return ap;
}
// INTERNAL: Free an array 'arr' returned by parse_path()
static void free_path(char **arr) {
if ( arr ) {
if ( arr[0] ) { delete[] arr[0]; } // deletes cp in parse_path
delete[] arr; // deletes ptr array
}
}
// INTERNAL: Recursively descend 'item's tree hierarchy
// accumulating total child 'count'
//
static int find_total_children(Fl_Tree_Item *item, int count=0) {
count++;
for ( int t=0; t<item->children(); t++ ) {
count = find_total_children(item->child(t), count);
}
return(count);
}
/// Constructor.
Fl_Tree::Fl_Tree(int X, int Y, int W, int H, const char *L) : Fl_Group(X,Y,W,H,L) {
#if FLTK_ABI_VERSION >= 10303
_root = new Fl_Tree_Item(this);
#else
_root = new Fl_Tree_Item(_prefs);
#endif
_root->parent(0); // we are root of tree
_root->label("ROOT");
_item_focus = 0;
_callback_item = 0;
_callback_reason = FL_TREE_REASON_NONE;
_scrollbar_size = 0; // 0: uses Fl::scrollbar_size()
#if FLTK_ABI_VERSION >= 10301
// NEW
_lastselect = 0;
#else /*FLTK_ABI_VERSION*/
// OLD: data initialized static inside handle()
#endif /*FLTK_ABI_VERSION*/
box(FL_DOWN_BOX);
color(FL_BACKGROUND2_COLOR, FL_SELECTION_COLOR);
when(FL_WHEN_CHANGED);
int scrollsize = _scrollbar_size ? _scrollbar_size : Fl::scrollbar_size();
_vscroll = new Fl_Scrollbar(X+W-scrollsize,Y,scrollsize,H);
_vscroll->hide();
_vscroll->type(FL_VERTICAL);
_vscroll->step(1);
_vscroll->callback(scroll_cb, (void*)this);
#if FLTK_ABI_VERSION >= 10303
_hscroll = new Fl_Scrollbar(X,Y+H-scrollsize,W,scrollsize);
_hscroll->hide();
_hscroll->type(FL_HORIZONTAL);
_hscroll->step(1);
_hscroll->callback(scroll_cb, (void*)this);
_tox = _tix = X + Fl::box_dx(box());
_toy = _tiy = Y + Fl::box_dy(box());
_tow = _tiw = W - Fl::box_dw(box());
_toh = _tih = H - Fl::box_dh(box());
_tree_w = -1;
_tree_h = -1;
#endif
end();
}
/// Destructor.
Fl_Tree::~Fl_Tree() {
if ( _root ) { delete _root; _root = 0; }
}
/// Extend the selection between and including \p 'from' and \p 'to'
/// depending on direction \p 'dir', \p 'val', and \p 'visible'.
///
/// Efficient: does not walk entire tree; starts with \p 'from' and stops
/// at \p 'to' while moving in direction \p 'dir'. Dir must be specified though.
#if FLTK_ABI_VERSION >= 10303
///
/// If dir cannot be known in advance, such as during SHIFT-click operations,
/// the method extend_selection(Fl_Tree_Item*,Fl_Tree_Item*,int,bool)
/// should be used.
#endif
///
/// Handles calling redraw() if anything changed.
///
/// \param[in] from Starting item
/// \param[in] to Ending item
/// \param[in] dir Direction to extend selection (FL_Up or FL_Down)
/// \param[in] val 0=deselect, 1=select, 2=toggle
/// \param[in] visible true=affect only open(), visible items,<br>
/// false=affect open or closed items (default)
/// \returns The number of items whose selection states were changed, if any.
/// \version 1.3.3
///
int Fl_Tree::extend_selection_dir(Fl_Tree_Item *from, Fl_Tree_Item *to,
int dir, int val, bool visible ) {
int changed = 0;
for (Fl_Tree_Item *item=from; item; item = next_item(item, dir, visible) ) {
switch (val) {
case 0:
if ( deselect(item, when()) ) ++changed;
break;
case 1:
if ( select(item, when()) ) ++changed;
break;
case 2:
select_toggle(item, when());
++changed; // toggle always involves a change
break;
}
if ( item==to ) break;
}
return(changed);
}
/// Extend a selection between \p 'from' and \p 'to' depending on \p 'visible'.
///
/// Similar to the more efficient
/// extend_selection_dir(Fl_Tree_Item*,Fl_Tree_Item*,int dir,int val,bool vis)
/// method, but direction (up or down) doesn't need to be known.<br>
/// We're less efficient because we search the tree for to/from, then operate
/// on items in between. The more efficient method avoids the "search",
/// but necessitates a direction to be specified to find \p 'to'.<br>
/// Used by SHIFT-click to extend a selection between two items inclusive.<br>
/// Handles calling redraw() if anything changed.
///
/// \param[in] from Starting item
/// \param[in] to Ending item
/// \param[in] val Select or deselect items (0=deselect, 1=select, 2=toggle)
/// \param[in] visible true=affect only open(), visible items,<br>
/// false=affect open or closed items (default)
/// \returns The number of items whose selection states were changed, if any.
#if FLTK_ABI_VERSION >= 10303
/// \version 1.3.3 ABI feature
int Fl_Tree::extend_selection(Fl_Tree_Item *from, Fl_Tree_Item *to,
int val, bool visible) {
#else
/// \notes Made public in 1.3.3 ABI
// Adding overload if not at least one overload breaks ABI, so avoid
// by making a private function until ABI can change..
int Fl_Tree::extend_selection__(Fl_Tree_Item *from, Fl_Tree_Item *to,
int val, bool visible) {
#endif
int changed = 0;
if ( from == to ) {
if ( visible && !from->is_visible() ) return(0); // do nothing
switch (val) {
case 0:
if ( deselect(from, when()) ) ++changed;
break;
case 1:
if ( select(from, when()) ) ++changed;
break;
case 2:
select_toggle(from, when());
++changed; // always changed
break;
}
return(changed);
}
char on = 0;
for ( Fl_Tree_Item *item = first(); item; item = item->next_visible(_prefs) ) {
if ( visible && !item->is_visible() ) continue;
if ( on || (item == from) || (item == to) ) {
switch (val) {
case 0:
if ( deselect(item, when()) ) ++changed;
break;
case 1:
if ( select(item, when()) ) ++changed;
break;
case 2:
select_toggle(item, when());
++changed; // toggle always involves a change
break;
}
if ( (item == from) || (item == to) ) {
on ^= 1;
if ( !on ) break; // done
}
}
}
return(changed);
}
#if FLTK_ABI_VERSION >= 10303
// not needed, above overload handles this
#else
/// Extend a selection between \p 'from' and \p 'to'.
/// Extends selection for items and all children, visible ('open') or not.
/// Walks entire tree from top to bottom looking for \p 'from' and \p 'to'.
/// \version 1.3.0
///
void Fl_Tree::extend_selection(Fl_Tree_Item *from, Fl_Tree_Item *to) {
const int val = 1; // 0=clr, 1=set, 2=toggle
const bool visible = false; // true=only 'open' items, false='open' or 'closed'
extend_selection__(from, to, val, visible); // use private method until we can release it
}
#endif
/// Standard FLTK event handler for this widget.
/// \todo add Fl_Widget_Tracker (see Fl_Browser_.cxx::handle())
int Fl_Tree::handle(int e) {
if (e == FL_NO_EVENT) return(0); // XXX: optimize to prevent slow resizes on large trees!
int ret = 0;
char is_shift = Fl::event_state() & FL_SHIFT ? 1 : 0;
char is_ctrl = Fl::event_state() & FL_CTRL ? 1 : 0;
char is_command = Fl::event_state() & FL_COMMAND ? 1 : 0; // ctrl on win/lin, 'Command' on mac
#if FLTK_ABI_VERSION >= 10301
// NEW: data inside Fl_Tree
#else /*FLTK_ABI_VERSION*/
// OLD:
static Fl_Tree_Item *_lastselect = 0;
#endif /*FLTK_ABI_VERSION*/
// 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?
int updown = is_shift ? FL_Up : FL_Down; // SHIFT-TAB similar to Up, TAB similar to Down
set_item_focus(next_visible_item(0, updown));
break;
}
case FL_Left: // received focus via LEFT or UP?
case FL_Up: { // XK_ISO_Left_Tab
set_item_focus(next_visible_item(0, FL_Up));
break;
}
case FL_Right: // received focus via RIGHT or DOWN?
case FL_Down:
default: {
set_item_focus(next_visible_item(0, FL_Down));
break;
}
}
}
if ( visible_focus() ) redraw(); // draw focus change
return(1);
}
case FL_UNFOCUS: { // FLTK telling us some other widget took focus.
if ( visible_focus() ) redraw(); // draw focus change
return(1);
}
case FL_KEYBOARD: { // keyboard shortcut
// Do shortcuts first or scrollbar will get them...
if ( (Fl::focus() == this) && // tree has focus?
_prefs.selectmode() > FL_TREE_SELECT_NONE ) { // select mode that supports kb events?
if ( !_item_focus ) { // no current focus item?
set_item_focus(first_visible_item()); // use first vis item
if ( Fl::event_key() == FL_Up || // Up or down?
Fl::event_key() == FL_Down ) // ..if so, already did 'motion'
return(1); // ..so just return.
}
if ( _item_focus ) {
int ekey = Fl::event_key();
switch (ekey) {
case FL_Enter: // ENTER: toggle open/close
case FL_KP_Enter: {
open_toggle(_item_focus, when()); // toggle item in focus
return(1); // done, we handled key
}
case ' ': // SPACE: change selection state
switch ( _prefs.selectmode() ) {
case FL_TREE_SELECT_NONE:
break; // ignore, let group have shot at event
case FL_TREE_SELECT_SINGLE:
if ( is_ctrl ) { // CTRL-SPACE: (single mode) toggle
if ( ! _item_focus->is_selected() ) {
select_only(_item_focus, when());
} else {
deselect_all(0, when());
}
} else {
select_only(_item_focus, when()); // SPACE: (single mode) select only
}
_lastselect = _item_focus;
return(1); // done, we handled key
case FL_TREE_SELECT_MULTI:
if ( is_ctrl ) {
select_toggle(_item_focus, when()); // CTRL-SPACE: (multi mode) toggle selection
} else {
select(_item_focus, when()); // SPACE: (multi-mode) select
}
_lastselect = _item_focus;
return(1); // done, we handled key
}
break;
case FL_Right: // RIGHT: open children (if any)
case FL_Left: { // LEFT: close children (if any)
if ( _item_focus ) {
if ( ekey == FL_Right && _item_focus->is_close() ) {
open(_item_focus); // open closed item
ret = 1;
} else if ( ekey == FL_Left && _item_focus->is_open() ) {
close(_item_focus); // close open item
ret = 1;
}
return(1);
}
break;
}
case FL_Up: // UP: next item up, or extend selection up
case FL_Down: { // DOWN: next item down, or extend selection 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?
is_shift && // shift key?
! _item_focus->is_selected() ) { // not already selected?
select(_item_focus, when()); // extend selection..
_lastselect = _item_focus;
}
return(1);
}
break;
}
case 'a':
case 'A': {
if ( is_command ) { // ^A (win/linux), Meta-A (mac)
switch ( _prefs.selectmode() ) {
case FL_TREE_SELECT_NONE:
case FL_TREE_SELECT_SINGLE:
break;
case FL_TREE_SELECT_MULTI:
// Do a 'select all'
select_all();
_lastselect = first_visible_item();
take_focus();
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
// fprintf(stderr, "Fl_Tree::handle(): Event was %s (%d)\n", fl_eventnames[e], e); // DEBUGGING
if ( ! _root ) return(ret);
static int last_my = 0;
switch ( e ) {
case FL_PUSH: { // clicked on tree
last_my = Fl::event_y(); // save for dragging direction..
if (Fl::visible_focus() && handle(FL_FOCUS)) Fl::focus(this);
#if FLTK_ABI_VERSION >= 10303
Fl_Tree_Item *item = _root->find_clicked(_prefs, 0);
#else
Fl_Tree_Item *item = _root->find_clicked(_prefs);
#endif
if ( !item ) { // clicked, but not on an item?
_lastselect = 0;
switch ( _prefs.selectmode() ) {
case FL_TREE_SELECT_NONE:
break;
case FL_TREE_SELECT_SINGLE:
case FL_TREE_SELECT_MULTI:
deselect_all();
break;
}
break;
}
set_item_focus(item); // becomes new focus widget, calls redraw() if needed
ret |= 1; // handled
if ( Fl::event_button() == FL_LEFT_MOUSE ) {
if ( item->event_on_collapse_icon(_prefs) ) { // collapse icon clicked?
open_toggle(item); // toggle open (handles redraw)
} else if ( item->event_on_label(_prefs) && // label clicked?
(!item->widget() || !Fl::event_inside(item->widget())) ) { // not inside widget
switch ( _prefs.selectmode() ) {
case FL_TREE_SELECT_NONE:
break;
case FL_TREE_SELECT_SINGLE:
select_only(item, when()); // select only this item (handles redraw)
_lastselect = item;
break;
case FL_TREE_SELECT_MULTI: {
if ( is_shift ) { // SHIFT+PUSH?
if ( _lastselect ) {
int val = is_ctrl ? 2 : 1;
bool visible = true;
#if FLTK_ABI_VERSION >= 10303
extend_selection(_lastselect, item, val, visible);
#else
extend_selection__(_lastselect, item, val, visible);
#endif
} else {
select(item); // add to selection
}
} else if ( is_ctrl ) { // CTRL+PUSH?
select_toggle(item, when()); // toggle selection state
} else {
select_only(item, when());
}
_lastselect = item;
break;
}
}
}
}
break;
}
case FL_DRAG: {
// Do scrolling first..
// Detect up/down dragging
int my = Fl::event_y();
int dir = (my>last_my) ? FL_Down : FL_Up;
last_my = my;
// Handle autoscrolling
if ( my < y() ) { // Above top?
dir = FL_Up; // ..going up
int p = vposition()-(y()-my); // ..position above us
if ( p < 0 ) p = 0; // ..don't go above 0
vposition(p); // ..scroll to new position
} else if ( my > (y()+h()) ) { // Below bottom?
dir = FL_Down; // ..going down
int p = vposition()+(my-y()-h()); // ..position below us
if ( p > (int)_vscroll->maximum() ) // ..don't go below bottom
p = (int)_vscroll->maximum();
vposition(p); // ..scroll to new position
}
// Now handle the event..
// During drag, only interested in left-mouse operations.
//
if ( Fl::event_button() != FL_LEFT_MOUSE ) break;
#if FLTK_ABI_VERSION >= 10303
Fl_Tree_Item *item = _root->find_clicked(_prefs, 1); // item we're on, vertically
#else
Fl_Tree_Item *item = _root->find_clicked(_prefs); // item we're on, vertically
#endif
if ( !item ) break; // not near item? ignore drag event
ret |= 1; // acknowledge event
set_item_focus(item); // becomes new focus item
if (item==_lastselect) break; // same item as before? avoid reselect
// Handle selection behavior
switch ( _prefs.selectmode() ) {
case FL_TREE_SELECT_NONE:
break; // no selection changes
case FL_TREE_SELECT_SINGLE: {
select_only(item, when()); // select only this item (handles redraw)
break;
}
case FL_TREE_SELECT_MULTI: {
Fl_Tree_Item *from = next_visible_item(_lastselect, dir); // avoid reselecting item
Fl_Tree_Item *to = item;
int val = is_ctrl ? 2 : 1; // toggle_select() or just select()?
bool visible = true;
extend_selection_dir(from, to, dir, val, visible);
break;
}
}
_lastselect = item; // save current item for later
break;
}
case FL_RELEASE:
ret |= 1;
break;
}
return(ret);
}
#if FLTK_ABI_VERSION >= 10303
// nothing
#else
// Redraw timeout callback
// (Only need this hack for old ABI 10302 and older)
//
static void redraw_soon(void *data) {
((Fl_Tree*)data)->redraw();
Fl::remove_timeout(redraw_soon, data);
}
#endif
#if FLTK_ABI_VERSION >= 10303
/// Recalculate widget dimensions and scrollbar visibility,
/// normally managed automatically.
///
/// Low overhead way to update the tree widget's outer/inner dimensions
/// and re-determine scrollbar visibility based on these changes without
/// recalculating the entire size of the tree data.
///
/// Assumes that either the tree's size in _tree_w/_tree_h are correct
/// so that scrollbar visibility can be calculated easily, or are both
/// zero indicating scrollbar visibility can't be calculated yet.
///
/// This method is called when the widget is resize()ed or if the
/// scrollbar's sizes are changed (affects tree widget's inner dimensions
/// tix/y/w/h), and also used by calc_tree().
/// \version 1.3.3 ABI feature
///
void Fl_Tree::calc_dimensions() {
// Calc tree outer xywh
// Area of the tree widget /outside/ scrollbars
//
_tox = x() + Fl::box_dx(box());
_toy = y() + Fl::box_dy(box());
_tow = w() - Fl::box_dw(box());
_toh = h() - Fl::box_dh(box());
// Scrollbar visiblity + positions
// Calc this ONLY if tree_h and tree_w have been calculated.
// Zero values for these indicate calc in progress, but not done yet.
//
if ( _tree_h >= 0 && _tree_w >= 0 ) {
int scrollsize = _scrollbar_size ? _scrollbar_size : Fl::scrollbar_size();
int vshow = _tree_h > _toh ? 1 : 0;
int hshow = _tree_w > _tow ? 1 : 0;
// See if one scroller's appearance affects the other's visibility
if ( hshow && !vshow && (_tree_h > (_toh-scrollsize)) ) vshow = 1;
if ( vshow && !hshow && (_tree_w > (_tow-scrollsize)) ) hshow = 1;
// vertical scrollbar visibility
if ( vshow ) {
_vscroll->show();
_vscroll->resize(_tox+_tow-scrollsize, _toy,
scrollsize, h()-Fl::box_dh(box()) - (hshow ? scrollsize : 0));
} else {
_vscroll->hide();
_vscroll->value(0);
}
// horizontal scrollbar visibility
if ( hshow ) {
_hscroll->show();
_hscroll->resize(_tox, _toy+_toh-scrollsize,
_tow - (vshow ? scrollsize : 0), scrollsize);
} else {
_hscroll->hide();
_hscroll->value(0);
}
// Calculate inner dimensions
// The area the tree occupies inside the scrollbars and margins
//
_tix = _tox;
_tiy = _toy;
_tiw = _tow - (_vscroll->visible() ? _vscroll->w() : 0);
_tih = _toh - (_hscroll->visible() ? _hscroll->h() : 0);
// Scrollbar tab sizes
_vscroll->slider_size(float(_tih) / float(_tree_h));
_vscroll->range(0.0, _tree_h - _tih);
_hscroll->slider_size(float(_tiw) / float(_tree_w));
_hscroll->range(0.0, _tree_w - _tiw);
} else {
// Best we can do without knowing tree_h/tree_w
_tix = _tox;
_tiy = _toy;
_tiw = _tow;
_tih = _toh;
}
}
/// Recalculuates the tree's sizes and scrollbar visibility,
/// normally managed automatically.
///
/// On return:
///
/// - _tree_w will be the overall pixel width of the entire viewable tree
/// - _tree_h will be the overall pixel height ""
/// - scrollbar visibility and pan sizes are updated
/// - internal _tix/_tiy/_tiw/_tih dimensions are updated
///
/// _tree_w/_tree_h include the tree's margins (e.g. marginleft()),
/// whether items are open or closed, label contents and font sizes, etc.
///
/// The tree hierarchy's size is managed separately from the widget's
/// size as an optimization; this way resize() on the widget doesn't
/// involve recalculating the tree's hierarchy needlessly, as widget
/// size has no bearing on the tree hierarchy.
///
/// The tree hierarchy's size only changes when items are added/removed,
/// open/closed, label contents or font sizes changed, margins changed, etc.
///
/// This calculation involves walking the *entire* tree from top to bottom,
/// a potentially a slow calculation if the tree has many items (potentially
/// hundreds of thousands), and should therefore be called sparingly.
///
/// For this reason, recalc_tree() is used as a way to /schedule/
/// calculation when changes affect the tree hierarchy's size.
///
/// Apps may want to call this method directly if the app makes changes
/// to the tree's geometry, then immediately needs to work with the tree's
/// new dimensions before an actual redraw (and recalc) occurs. (This
/// use by an app should only rarely be needed)
///
void Fl_Tree::calc_tree() {
// Set tree width and height to zero, and recalc just _tox/_toy/_tow/_toh for now.
_tree_w = _tree_h = -1;
calc_dimensions();
if ( !_root ) return;
// Walk the tree to determine its width and height.
// We need this to compute scrollbars..
// By the end, 'Y' will be the lowest point on the tree
//
int X = _tix + _prefs.marginleft() + _hscroll->value();
int Y = _tiy + _prefs.margintop() - _vscroll->value();
int W = _tiw;
// Adjust root's X/W if connectors off
if (_prefs.connectorstyle() == FL_TREE_CONNECTOR_NONE) {
X -= _prefs.openicon()->w();
W += _prefs.openicon()->w();
}
int xmax = 0, render = 0, ytop = Y;
fl_font(_prefs.labelfont(), _prefs.labelsize());
_root->draw(X, Y, W, 0, xmax, 1, render); // descend into tree without drawing (render=0)
// Save computed tree width and height
_tree_w = _prefs.marginleft() + xmax - X; // include margin in tree's width
_tree_h = _prefs.margintop() + Y - ytop; // include margin in tree's height
// Calc tree dims again; now that tree_w/tree_h are known, scrollbars are calculated.
calc_dimensions();
}
#endif
void Fl_Tree::resize(int X,int Y,int W, int H) {
fix_scrollbar_order();
Fl_Group::resize(X,Y,W,H);
#if FLTK_ABI_VERSION >= 10303
calc_dimensions();
#endif
init_sizes();
}
#if FLTK_ABI_VERSION >= 10303
/// Standard FLTK draw() method, handles drawing the tree widget.
void Fl_Tree::draw() {
fix_scrollbar_order();
// Has tree recalc been scheduled? If so, do it
if ( _tree_w == -1 ) calc_tree();
else calc_dimensions();
// Let group draw box+label but *NOT* children.
// We handle drawing children ourselves by calling each item's draw()
{
// Draw group's bg + label
if ( damage() & ~FL_DAMAGE_CHILD) { // redraw entire widget?
Fl_Group::draw_box();
Fl_Group::draw_label();
}
if ( ! _root ) return;
// These values are changed during drawing
// By end, 'Y' will be the lowest point on the tree
int X = _tix + _prefs.marginleft() - _hscroll->value();
int Y = _tiy + _prefs.margintop() - _vscroll->value();
int W = _tiw - X + _tix;
// Adjust root's X/W if connectors off
if (_prefs.connectorstyle() == FL_TREE_CONNECTOR_NONE) {
X -= _prefs.openicon()->w();
W += _prefs.openicon()->w();
}
// Draw entire tree, starting with root
fl_push_clip(_tix,_tiy,_tiw,_tih);
{
int xmax = 0;
fl_font(_prefs.labelfont(), _prefs.labelsize());
_root->draw(X, Y, W, // descend into tree here to draw it
(Fl::focus()==this)?_item_focus:0, // show focus item ONLY if Fl_Tree has focus
xmax, 1, 1);
}
fl_pop_clip();
}
// Draw scrollbars last
draw_child(*_vscroll);
draw_child(*_hscroll);
// That little tile between the scrollbars
if ( _vscroll->visible() && _hscroll->visible() ) {
fl_color(_vscroll->color());
fl_rectf(_hscroll->x()+_hscroll->w(),
_vscroll->y()+_vscroll->h(),
_vscroll->w(),
_hscroll->h());
}
}
#else
/// Standard FLTK draw() method, handles drawing the tree widget.
void Fl_Tree::draw() {
int ytoofar = draw_tree();
// See if we're scrolled below bottom of tree
// This can happen if someone just closed a large item.
// If so, change scroller as needed.
//
if ( _vscroll->visible() && ytoofar > 0 ) {
int scrollval = _vscroll->value();
int ch = h() - Fl::box_dh(box());
int range2 = scrollval - ytoofar;
int size2 = ch + range2;
if ( range2 < 0 ) {
_vscroll->value(0);
_vscroll->hide();
} else {
_vscroll->slider_size(float(ch)/float(size2));
_vscroll->range(0.0,range2);
_vscroll->value(range2);
}
Fl::add_timeout(.10, redraw_soon, (void*)this); // use timer to trigger redraw; we can't
}
}
// This method is undocumented, and has been removed in ABI 1.3.3
int Fl_Tree::draw_tree() {
int ret = 0;
fix_scrollbar_order();
// Let group draw box+label but *NOT* children.
// We handle drawing children ourselves by calling each item's draw()
//
int cx = x() + Fl::box_dx(box());
int cy = y() + Fl::box_dy(box());
int cw = w() - Fl::box_dw(box());
int ch = h() - Fl::box_dh(box());
{
// Handle group's bg
if ( damage() & ~FL_DAMAGE_CHILD) { // redraw entire widget?
Fl_Group::draw_box();
Fl_Group::draw_label();
}
if ( ! _root ) return(0);
// These values are changed during drawing
// By end, '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();
// Adjust root's X/W if connectors off
if (_prefs.connectorstyle() == FL_TREE_CONNECTOR_NONE) {
X -= _prefs.openicon()->w();
W += _prefs.openicon()->w();
}
int Ysave = Y;
fl_push_clip(cx,cy,cw,ch);
{
fl_font(_prefs.labelfont(), _prefs.labelsize());
_root->draw(X, Y, W, this,
(Fl::focus()==this)?_item_focus:0, // show focus item ONLY if Fl_Tree has focus
_prefs);
}
fl_pop_clip();
// Show vertical scrollbar?
{
#if FLTK_ABI_VERSION >= 10301
// NEW
int SY = Y + _prefs.marginbottom();
#else /*FLTK_ABI_VERSION*/
// OLD
int SY = Y;
#endif /*FLTK_ABI_VERSION*/
int ydiff = (SY+_prefs.margintop())-Ysave; // ydiff=size of tree
int ytoofar = (cy+ch) - SY; // ytoofar -- if >0, scrolled beyond bottom
if ( ytoofar > 0 ) ydiff += ytoofar;
if ( Ysave<cy || ydiff>ch || int(_vscroll->value())>1 ) {
_vscroll->visible();
int scrollsize = _scrollbar_size ? _scrollbar_size : Fl::scrollbar_size();
int sx = x()+w()-Fl::box_dx(box())-scrollsize;
int sy = y()+Fl::box_dy(box());
int sw = scrollsize;
int sh = h()-Fl::box_dh(box());
_vscroll->show();
_vscroll->resize(sx,sy,sw,sh);
_vscroll->slider_size(float(ch)/float(ydiff));
_vscroll->range(0.0,ydiff-ch);
ret = ytoofar;
} else {
_vscroll->Fl_Slider::value(0);
_vscroll->hide();
ret = 0;
}
}
}
draw_child(*_vscroll); // draw scroll last
return(ret);
}
#endif
/// Print the tree as 'ascii art' to stdout.
/// Used mainly for debugging.
/// \todo should be const
/// \version 1.3.0
///
void Fl_Tree::show_self() {
if ( ! _root ) return;
_root->show_self();
}
/// Set the label for the root item to \p 'new_label'.
///
/// Makes an internally managed copy of 'new_label'.
///
void Fl_Tree::root_label(const char *new_label) {
if ( ! _root ) return;
_root->label(new_label);
}
/// Returns the root item.
Fl_Tree_Item* Fl_Tree::root() {
return(_root);
}
/// Sets the root item to \p 'newitem'.
///
/// If a root item already exists, clear() is first to clear it
/// before replacing it with newitem.
///
#if FLTK_ABI_VERSION >= 10303
/// Use this to install a custom item (derived from Fl_Tree_Item) as the root
/// of the tree. This allows the derived class to implement custom drawing
/// by overriding Fl_Tree_Item::draw_item_content().
///
#endif
/// \version 1.3.3
///
void Fl_Tree::root(Fl_Tree_Item *newitem) {
if ( _root ) clear();
_root = newitem;
}
/// Adds a new item, given a menu style \p 'path'.
/// Any parent nodes that don't already exist are created automatically.
/// Adds the item based on the value of sortorder().
/// If \p 'item' is NULL, a new item is created.
///
/// To specify items or submenus that contain slashes ('/' or '\')
/// use an escape character to protect them, e.g.
/// \code
/// tree->add("/Holidays/Photos/12\\/25\\2010"); // Adds item "12/25/2010"
/// tree->add("/Pathnames/c:\\\\Program Files\\\\MyApp"); // Adds item "c:\Program Files\MyApp"
/// \endcode
/// \param[in] path The path to the item, e.g. "Flintsone/Fred".
/// \param[in] item The new item to be added.
/// If NULL, a new item is created with
/// a name that is the last element in \p 'path'.
/// \returns The new item added, or 0 on error.
/// \version 1.3.3
///
Fl_Tree_Item* Fl_Tree::add(const char *path, Fl_Tree_Item *item) {
// Tree has no root? make one
if ( ! _root ) {
#if FLTK_ABI_VERSION >= 10303
_root = new Fl_Tree_Item(this);
#else
_root = new Fl_Tree_Item(_prefs);
#endif
_root->parent(0);
_root->label("ROOT");
}
// Find parent item via path
char **arr = parse_path(path);
item = _root->add(_prefs, arr, item);
free_path(arr);
return(item);
}
#if FLTK_ABI_VERSION >= 10303
// do nothing here: add(path,item) where item defaults to 0 takes its place
#else
/// Adds a new item given a menu style \p 'path'.
/// Same as calling add(path, NULL);
/// \param[in] path The path to the item to be created, e.g. "Flintsone/Fred".
/// \returns The new item added, or 0 on error.
/// \see add(const char*,Fl_Tree_Item*)
/// \version 1.3.0 release
///
Fl_Tree_Item* Fl_Tree::add(const char *path) {
return add(path, 0);
}
#endif
/// Add a new child item labeled \p 'name' to the specified \p 'parent_item'.
///
/// \param[in] parent_item The parent item the new child item will be added to.
/// Must not be NULL.
/// \param[in] name The label for the new item
/// \returns The new item added.
/// \version 1.3.0 release
///
Fl_Tree_Item* Fl_Tree::add(Fl_Tree_Item *parent_item, const char *name) {
return(parent_item->add(_prefs, name));
}
/// Inserts a new item \p 'name' above the specified Fl_Tree_Item \p 'above'.
/// Example:
/// \code
/// tree->add("Aaa/000"); // "000" is index 0 in Aaa's children
/// tree->add("Aaa/111"); // "111" is index 1 in Aaa's children
/// tree->add("Aaa/222"); // "222" is index 2 in Aaa's children
/// ..
/// // How to use insert_above() to insert a new item above Aaa/222
/// Fl_Tree_Item *item = tree->find_item("Aaa/222"); // get item Aaa/222
/// if (item) tree->insert_above(item, "New item"); // insert new item above it
/// \endcode
///
/// \param[in] above -- the item above which to insert the new item. Must not be NULL.
/// \param[in] name -- the name of the new item
/// \returns The new item added, or 0 if 'above' could not be found.
/// \see insert()
///
Fl_Tree_Item* Fl_Tree::insert_above(Fl_Tree_Item *above, const char *name) {
return(above->insert_above(_prefs, name));
}
/// Insert a new item \p 'name' into \p 'item's children at position \p 'pos'.
///
/// Example:
/// \code
/// tree->add("Aaa/000"); // "000" is index 0 in Aaa's children
/// tree->add("Aaa/111"); // "111" is index 1 in Aaa's children
/// tree->add("Aaa/222"); // "222" is index 2 in Aaa's children
/// ..
/// // How to use insert() to insert a new item between Aaa/111 + Aaa/222
/// Fl_Tree_Item *item = tree->find_item("Aaa"); // get parent item Aaa
/// if (item) tree->insert(item, "New item", 2); // insert as a child of Aaa at index #2
/// \endcode
///
/// \param[in] item The existing item to insert new child into. Must not be NULL.
/// \param[in] name The label for the new item
/// \param[in] pos The position of the new item in the child list
/// \returns The new item added.
/// \see insert_above()
///
Fl_Tree_Item* Fl_Tree::insert(Fl_Tree_Item *item, const char *name, int pos) {
return(item->insert(_prefs, name, pos));
}
/// Remove the specified \p 'item' from the tree.
/// \p item may not be NULL.
/// If it has children, all those are removed too.
/// If item being removed has focus, no item will have focus.
/// \returns 0 if done, -1 if 'item' not found.
///
int Fl_Tree::remove(Fl_Tree_Item *item) {
// Item being removed is focus item? zero focus
if ( item == _item_focus ) _item_focus = 0;
if ( item == _root ) {
clear();
} else {
Fl_Tree_Item *parent = item->parent(); // find item's parent
if ( ! parent ) return(-1);
parent->remove_child(item); // remove child + children
}
return(0);
}
/// Clear the entire tree's children, including the root.
/// The tree will be left completely empty.
///
void Fl_Tree::clear() {
if ( ! _root ) return;
_root->clear_children();
delete _root; _root = 0;
}
/// Clear all the children for \p 'item'.
/// Item may not be NULL.
///
void Fl_Tree::clear_children(Fl_Tree_Item *item) {
if ( item->has_children() ) {
item->clear_children();
redraw(); // redraw only if there were children to clear
}
}
/// Find the item, given a menu style path, e.g. "/Parent/Child/item".
/// There is both a const and non-const version of this method.
/// Const version allows pure const methods to use this method
/// to do lookups without causing compiler errors.
///
/// To specify items or submenus that contain slashes ('/' or '\')
/// use an escape character to protect them, e.g.
///
/// \code
/// tree->add("/Holidays/Photos/12\\/25\\2010"); // Adds item "12/25/2010"
/// tree->add("/Pathnames/c:\\\\Program Files\\\\MyApp"); // Adds item "c:\Program Files\MyApp"
/// \endcode
///
/// \param[in] path -- the tree item's pathname to be found (e.g. "Flintstones/Fred")
/// \returns The item, or NULL if not found.
/// \see item_pathname()
///
const Fl_Tree_Item *Fl_Tree::find_item(const char *path) const {
if ( ! _root ) return(NULL);
char **arr = parse_path(path);
const Fl_Tree_Item *item = _root->find_item(arr);
free_path(arr);
return(item);
}
/// Non-const version of Fl_Tree::find_item(const char *path) const
Fl_Tree_Item *Fl_Tree::find_item(const char *path) {
// "Effective C++, 3rd Ed", p.23. Sola fide, Amen.
return(const_cast<Fl_Tree_Item*>(
static_cast<const Fl_Tree&>(*this).find_item(path)));
}
// 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; \
}
/// Return \p 'pathname' of size \p 'pathnamelen' for the specified \p 'item'.
///
/// If \p 'item' is NULL, root() is used.<br>
/// The tree's root will be included in the pathname of showroot() is on.<br>
/// Menu items or submenus that contain slashes ('/' or '\') in their names
/// will be escaped with a backslash. This is symmetrical with the add()
/// function which uses the same escape pattern to set names.
///
/// \param[out] 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 = (int) strlen(name);
// Add name to end of pathname[]
for ( --len; len>=0; len-- ) {
SAFE_RCAT(name[len]); // rcat name of item
if ( name[len] == '/' || name[len] == '\\' ) {
SAFE_RCAT('\\'); // escape front or back slashes within name
}
}
SAFE_RCAT('/'); // rcat leading slash
item = item->parent(); // move up tree (NULL==root)
}
if ( *(++s) == '/' ) ++s; // leave off leading slash from pathname
if ( s != pathname ) memmove(pathname, s, slen); // Shift down right-aligned string
return(0);
}
#if FLTK_ABI_VERSION >= 10303
/// Find the item that was last clicked on.
/// 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. (The value of the \p 'yonly' flag affects whether
/// both x and y events are checked, or just 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().
///
/// \param[in] yonly -- 0: check both event's X and Y values.
/// -- 1: only check event's Y value, don't care about X.
/// \returns The item clicked, or NULL if no item was under the current event.
/// \version 1.3.0
/// \version 1.3.3 ABI feature: added yonly parameter
///
const Fl_Tree_Item* Fl_Tree::find_clicked(int yonly) const {
if ( ! _root ) return(NULL);
return(_root->find_clicked(_prefs, yonly));
}
/// Non-const version of Fl_Tree::find_clicked(int yonly) const.
Fl_Tree_Item *Fl_Tree::find_clicked(int yonly) {
// "Effective C++, 3rd Ed", p.23. Sola fide, Amen.
return(const_cast<Fl_Tree_Item*>(
static_cast<const Fl_Tree&>(*this).find_clicked(yonly)));
}
#else
/// Find the item that was last clicked on.
/// 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, i.e. 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 NULL if no item was under the current event.
/// \version 1.3.0
///
const Fl_Tree_Item* Fl_Tree::find_clicked() const {
if ( ! _root ) return(NULL);
return(_root->find_clicked(_prefs));
}
/// Non-const version of Fl_Tree::find_clicked() const.
/// \version 1.3.0
Fl_Tree_Item *Fl_Tree::find_clicked() {
// "Effective C++, 3rd Ed", p.23. Sola fide, Amen.
return(const_cast<Fl_Tree_Item*>(
static_cast<const Fl_Tree&>(*this).find_clicked()));
}
#endif
/// 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 in 1.3.3 ABI -- use callback_item() instead.
///
void Fl_Tree::item_clicked(Fl_Tree_Item* item) {
_callback_item = item;
}
/// Return the item that was last clicked.
///
/// Valid only from within the callback().
///
/// \returns The item clicked, or 0 if none.
/// 0 may also be used to indicate several items were clicked/changed.
/// \deprecated in 1.3.3 ABI -- use callback_item() instead.
///
Fl_Tree_Item* Fl_Tree::item_clicked() {
return(_callback_item);
}
/// Returns next open(), visible item above (\p dir==FL_Up)
/// or below (\p dir==FL_Down) the specified \p 'item', or 0 if no more items.
///
/// If \p 'item' is 0, returns first() if \p 'dir' is FL_Up,
/// or last() if \p dir is FL_Down.
///
/// \code
/// // Walk down the tree (forwards)
/// for ( Fl_Tree_Item *i=tree->first_visible_item(); i; i=tree->next_visible_item(i, FL_Down) )
/// printf("Item: %s\n", i->label());
///
/// // Walk up the tree (backwards)
/// for ( Fl_Tree_Item *i=tree->last_visible_item(); i; i=tree->next_visible_item(i, FL_Up) )
/// printf("Item: %s\n", i->label());
/// \endcode
/// \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.
/// \version 1.3.3
///
Fl_Tree_Item *Fl_Tree::next_visible_item(Fl_Tree_Item *item, int dir) {
return next_item(item, dir, true);
}
/// Returns the first item in the tree, or 0 if none.
///
/// Use this to walk the tree in the forward direction, e.g.
/// \code
/// for ( Fl_Tree_Item *item = tree->first(); item; item = tree->next(item) )
/// printf("Item: %s\n", item->label());
/// \endcode
///
/// \returns First item in tree, or 0 if none (tree empty).
/// \see first(), next(), last(), prev()
///
Fl_Tree_Item* Fl_Tree::first() {
return(_root); // first item always root
}
/// Returns the first open(), visible item in the tree, or 0 if none.
/// \deprecated in 1.3.3 ABI -- use first_visible_item() instead.
///
Fl_Tree_Item* Fl_Tree::first_visible() {
return(first_visible_item());
}
/// Returns the first open(), visible item in the tree, or 0 if none.
/// \returns First visible item in tree, or 0 if none.
/// \see first_visible_item(), last_visible_item(), next_visible_item()
/// \version 1.3.3
///
Fl_Tree_Item* Fl_Tree::first_visible_item() {
Fl_Tree_Item *i = showroot() ? first() : next(first());
while ( i ) {
if ( i->visible() ) return(i);
i = next(i);
}
return(0);
}
/// 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 *i = tree->first(); i; i = tree->next(i) )
/// printf("Item: %s\n", i->label());
/// \endcode
///
/// \param[in] item The item to use to find the next item. If NULL, returns 0.
/// \returns Next item in tree, or 0 if at last item.
///
/// \see first(), next(), last(), prev()
///
Fl_Tree_Item *Fl_Tree::next(Fl_Tree_Item *item) {
if ( ! item ) return(0);
return(item->next());
}
/// Return the previous item before \p 'item', or 0 if no more items.
///
/// This can be used to walk the tree in reverse, e.g.
/// \code
/// for ( Fl_Tree_Item *item = tree->first(); item; item = tree->prev(item) )
/// printf("Item: %s\n", item->label());
/// \endcode
///
/// \param[in] item The item to use to find the previous item. If NULL, returns 0.
/// \returns Previous item in tree, or 0 if at first item.
///
/// \see first(), next(), last(), prev()
///
Fl_Tree_Item *Fl_Tree::prev(Fl_Tree_Item *item) {
if ( ! item ) return(0);
return(item->prev());
}
/// Returns the last item in the tree.
///
/// This can be used to walk the tree in reverse, e.g.
///
/// \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 last open(), visible item in the tree.
/// \deprecated in 1.3.3 -- use last_visible_item() instead.
///
Fl_Tree_Item* Fl_Tree::last_visible() {
return(last_visible_item());
}
/// Returns the last open(), visible item in the tree.
/// \returns Last visible item in the tree, or 0 if none.
/// \see first_visible_item(), last_visible_item(), next_visible_item()
/// \version 1.3.3
///
Fl_Tree_Item* Fl_Tree::last_visible_item() {
Fl_Tree_Item *item = last();
while ( item ) {
if ( item->visible() ) {
if ( item == _root && !showroot() ) {
return(0);
} else {
return(item);
}
}
item = prev(item);
}
return(item);
}
/// Returns the first selected item in the tree.
///
/// Use this to walk the tree from top to bottom
/// looking for all the selected items, e.g.
///
/// \code
/// // Walk tree forward, from top to bottom
/// for ( Fl_Tree_Item *i=tree->first_selected_item(); i; i=tree->next_selected_item(i) )
/// printf("Selected item: %s\n", i->label());
/// \endcode
///
/// \returns The first selected item, or 0 if none.
/// \see first_selected_item(), last_selected_item(), next_selected_item()
///
Fl_Tree_Item *Fl_Tree::first_selected_item() {
return(next_selected_item(0));
}
#if FLTK_ABI_VERSION >= 10303
// nothing
#else
/// Returns the next selected item after \p 'item'.
/// If \p item is 0, search starts at the first item (root).
///
/// This is a convenience method; equivalent to next_selected_item(item, FL_Down);
///
/// Use this to walk the tree forward (downward) looking for all the selected items, e.g.
/// \code
/// for ( Fl_Tree_Item *i = tree->first_selected_item(); i; i = tree->next_selected_item(i) )
/// printf("Selected item: %s\n", i->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.
/// \see first_selected_item(), last_selected_item(), next_selected_item()
///
Fl_Tree_Item *Fl_Tree::next_selected_item(Fl_Tree_Item *item) {
return(next_selected_item(item, FL_Down));
}
#endif
/// Returns the last selected item in the tree.
///
/// Use this to walk the tree in reverse from bottom to top
/// looking for all the selected items, e.g.
///
/// \code
/// // Walk tree in reverse, from bottom to top
/// for ( Fl_Tree_Item *i=tree->last_selected_item(); i; i=tree->next_selected_item(i, FL_Up) )
/// printf("Selected item: %s\n", i->label());
/// \endcode
///
/// \returns The last selected item, or 0 if none.
/// \see first_selected_item(), last_selected_item(), next_selected_item()
/// \version 1.3.3
///
Fl_Tree_Item *Fl_Tree::last_selected_item() {
return(next_selected_item(0, FL_Up));
}
/// Returns next item after \p 'item' in direction \p 'dir'
/// depending on \p 'visible'.
///
/// Next item will be above (if dir==FL_Up) or below (if dir==FL_Down).
/// If \p 'visible' is true, only items whose parents are open() will be returned.
/// If \p 'visible' is false, even items whose parents are close()ed will be returned.
///
/// If \p item is 0, the return value will be the result of this truth table:
/// <PRE>
/// visible=true visible=false
/// ------------------- -------------
/// dir=Fl_Up: last_visible_item() last()
/// dir=Fl_Down: first_visible_item() first()
/// </PRE>
///
/// \par Example use:
/// \code
/// // Walk down the tree showing open(), visible items
/// for ( Fl_Tree_Item *i=tree->first_visible_item(); i; i=tree->next_item(i, FL_Down, true) )
/// printf("Item: %s\n", i->label());
///
/// // Walk up the tree showing open(), visible items
/// for ( Fl_Tree_Item *i=tree->last_visible_item(); i; i=tree->next_item(i, FL_Up, true) )
/// printf("Item: %s\n", i->label());
///
/// // Walk down the tree showing all items (open or closed)
/// for ( Fl_Tree_Item *i=tree->first(); i; i=tree->next_item(i, FL_Down, false) )
/// printf("Item: %s\n", i->label());
///
/// // Walk up the tree showing all items (open or closed)
/// for ( Fl_Tree_Item *i=tree->last(); i; i=tree->next_item(i, FL_Up, false) )
/// printf("Item: %s\n", i->label());
/// \endcode
///
/// \param[in] item The item to use to find the next item. If NULL, returns 0.
/// \param[in] dir Can be FL_Up or FL_Down (default=FL_Down or 'next')
/// \param[in] visible true=return only open(), visible items,<br>
/// false=return open or closed items (default)
/// \returns Next item in tree in the direction and visibility specified,
/// or 0 if no more items of specified visibility in that direction.
/// \see first(), last(), next(),<BR>
/// first_visible_item(), last_visible_item(), next_visible_item(),<BR>
/// first_selected_item(), last_selected_item(), next_selected_item()
/// \version 1.3.3
///
Fl_Tree_Item *Fl_Tree::next_item(Fl_Tree_Item *item, int dir, bool visible) {
if ( ! item ) { // no start item?
if ( visible ) {
item = ( dir == FL_Up ) ? last_visible_item() : // wrap to bottom
first_visible_item(); // wrap to top
} else {
item = ( dir == FL_Up ) ? last() : // wrap to bottom
first(); // wrap to top
}
if ( ! item ) return(0);
if ( item->visible_r() ) return(item); // return first/last visible item
}
switch (dir) {
case FL_Up:
if ( visible ) return(item->prev_visible(_prefs));
else return(item->prev());
case FL_Down:
if ( visible ) return(item->next_visible(_prefs));
else return(item->next());
}
return(0); // unknown dir
}
/// Returns the next selected item above or below \p 'item', depending on \p 'dir'.
/// If \p 'item' is 0, search starts at either first() or last(), depending on \p 'dir':
/// first() if \p 'dir' is FL_Down (default), last() if \p 'dir' is FL_Up.
///
/// Use this to walk the tree looking for all the selected items, e.g.
/// \code
/// // Walk down the tree (forwards)
/// for ( Fl_Tree_Item *i=tree->first_selected_item(); i; i=tree->next_selected_item(i, FL_Down) )
/// printf("Item: %s\n", i->label());
///
/// // Walk up the tree (backwards)
/// for ( Fl_Tree_Item *i=tree->last_selected_item(); i; i=tree->next_selected_item(i, FL_Up) )
/// printf("Item: %s\n", i->label());
/// \endcode
///
/// \param[in] item The item above or below which we'll find the next selected item.
/// If NULL, first() is used if FL_Down, last() if FL_Up.
/// (default=NULL)
/// \param[in] dir The direction to go.
/// FL_Up for moving up the tree,
/// FL_Down for down the tree (default)
/// \returns The next selected item, or 0 if there are no more selected items.
/// \see first_selected_item(), last_selected_item(), next_selected_item()
/// \version 1.3.3
///
Fl_Tree_Item *Fl_Tree::next_selected_item(Fl_Tree_Item *item, int dir) {
switch (dir) {
case FL_Down:
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);
case FL_Up:
if ( ! item ) {
if ( ! (item = last()) ) return(0);
if ( item->is_selected() ) return(item);
}
while ( (item = item->prev()) )
if ( item->is_selected() )
return(item);
return(0);
}
return(0);
}
#if FLTK_ABI_VERSION >= 10303 /* reason for this: Fl_Tree_Item_Array::manage_item_destroy() */
/// Returns the currently selected items as an array of \p 'ret_items'.
///
/// Example:
/// \code
/// // Get selected items as an array
/// Fl_Tree_Item_Array items;
/// tree->get_selected_items(items);
/// // Manipulate the returned array
/// for ( int t=0; t<items.total(); t++ ) {
/// Fl_Tree_Item &item = items[t];
/// ..do stuff with each selected item..
/// }
/// \endcode
///
/// \param[out] ret_items The returned array of selected items.
/// \returns The number of items in the returned array.
/// \see first_selected_item(), next_selected_item()
/// \version 1.3.3 ABI feature
///
int Fl_Tree::get_selected_items(Fl_Tree_Item_Array &ret_items) {
ret_items.clear();
for ( Fl_Tree_Item *i=first_selected_item(); i; i=next_selected_item(i) ) {
ret_items.add(i);
}
return ret_items.total();
}
#endif
/// Open the specified \p 'item'.
///
/// This causes the item's children (if any) to be shown.<br>
/// Invokes the callback depending on the value of optional
/// parameter \p 'docallback'.<br>
/// Handles calling redraw() if anything changed.
///
/// 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. Must not be NULL.
/// \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, (default)
/// 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 Fl_Tree::open(Fl_Tree_Item *item, int docallback) {
if ( item->is_open() ) return(0);
item->open(); // handles recalc_tree()
redraw();
if ( docallback ) {
do_callback_for_item(item, FL_TREE_REASON_OPENED);
}
return(1);
}
/// Opens the item specified by \p 'path'.
///
/// This causes the item's children (if any) to be shown.<br>
/// Invokes the callback depending on the value of optional
/// parameter \p 'docallback'.<br>
/// Handles calling redraw() if anything changed.
///
/// Items or submenus that themselves contain slashes ('/' or '\')
/// should be escaped, e.g. open("Holidays/12\\/25\//2010").
///
/// 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 (e.g. "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 (default),
/// callback_reason() will be FL_TREE_REASON_OPENED
/// \returns
/// - 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 Fl_Tree::open(const char *path, int docallback) {
Fl_Tree_Item *item = find_item(path);
if ( ! item ) return(-1);
return(open(item, docallback)); // handles recalc_tree()
}
/// Toggle the open state of \p 'item'.
///
/// Invokes the callback depending on the value of optional
/// parameter \p 'docallback'.<br>
/// Handles calling redraw() if anything changed.
///
/// 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 whose open state is to be toggled. Must not be NULL.
/// \param[in] docallback -- A flag that determines if the callback() is invoked or not:
/// - 0 - callback() is not invoked
/// - 1 - callback() is invoked (default), 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 Fl_Tree::open_toggle(Fl_Tree_Item *item, int docallback) {
if ( item->is_open() ) {
close(item, docallback); // handles recalc_tree()
} else {
open(item, docallback); // handles recalc_tree()
}
}
/// Closes the specified \p 'item'.
///
/// Invokes the callback depending on the value of optional
/// parameter \p 'docallback'.<br>
/// Handles calling redraw() if anything changed.
///
/// 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. Must not be NULL.
/// \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 (default),
/// 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 Fl_Tree::close(Fl_Tree_Item *item, int docallback) {
if ( item->is_close() ) return(0);
item->close(); // handles recalc_tree()
redraw();
if ( docallback ) {
do_callback_for_item(item, FL_TREE_REASON_CLOSED);
}
return(1);
}
/// Closes the item specified by \p 'path'.
///
/// Invokes the callback depending on the value of optional
/// parameter \p 'docallback'.<br>
/// Handles calling redraw() if anything changed.
///
/// Items or submenus that themselves contain slashes ('/' or '\')
/// should be escaped, e.g. close("Holidays/12\\/25\//2010").
///
/// 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 (e.g. "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 (default),
/// callback_reason() will be FL_TREE_REASON_CLOSED
/// \returns
/// - 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 Fl_Tree::close(const char *path, int docallback) {
Fl_Tree_Item *item = find_item(path);
if ( ! item ) return(-1);
return(close(item, docallback)); // handles recalc_tree()
}
/// 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. Must not be NULL.
/// \returns
/// - 1 : item is open
/// - 0 : item is closed
///
int Fl_Tree::is_open(Fl_Tree_Item *item) const {
return(item->is_open()?1:0);
}
/// See if item specified by \p 'path' is open.
///
/// Items or submenus that themselves contain slashes ('/' or '\')
/// should be escaped, e.g. is_open("Holidays/12\\/25\//2010").
///
/// 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 (e.g. "Flintstones/Fred")
/// \returns
/// - 1 - OK: item is open
/// - 0 - OK: item is closed
/// - -1 - ERROR: item was not found
/// \see Fl_Tree_Item::visible_r()
///
int Fl_Tree::is_open(const char *path) const {
const Fl_Tree_Item *item = find_item(path);
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. Must not be NULL.
/// \returns
/// - 1 : item is open
/// - 0 : item is closed
///
int Fl_Tree::is_close(Fl_Tree_Item *item) const {
return(item->is_close());
}
/// See if item specified by \p 'path' is closed.
///
/// Items or submenus that themselves contain slashes ('/' or '\')
/// should be escaped, e.g. is_close("Holidays/12\\/25\//2010").
///
/// \param[in] path -- the tree item's pathname (e.g. "Flintstones/Fred")
/// \returns
/// - 1 - OK: item is closed
/// - 0 - OK: item is open
/// - -1 - ERROR: item was not found
///
int Fl_Tree::is_close(const char *path) const {
const Fl_Tree_Item *item = find_item(path);
if ( ! item ) return(-1);
return(item->is_close()?1:0);
}
/// Select the specified \p 'item'. Use 'deselect()' to de-select it.
///
/// Invokes the callback depending on the value of optional parameter \p docallback.<br>
/// Handles calling redraw() if anything changed.
///
/// 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 selected. Must not be NULL.
/// \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 Fl_Tree::select(Fl_Tree_Item *item, int docallback) {
int alreadySelected = item->is_selected();
if ( !alreadySelected ) {
item->select();
set_changed();
if ( docallback ) {
do_callback_for_item(item, FL_TREE_REASON_SELECTED);
}
redraw();
return(1);
}
#if FLTK_ABI_VERSION >= 10301
// NEW
if ( alreadySelected ) {
if ( (item_reselect_mode() == FL_TREE_SELECTABLE_ALWAYS) && docallback ) {
do_callback_for_item(item, FL_TREE_REASON_RESELECTED);
}
}
#endif /*FLTK_ABI_VERSION*/
return(0);
}
/// Select the item specified by \p 'path'.
///
/// Invokes the callback depending on the value of optional
/// parameter \p 'docallback'.<br>
/// Handles calling redraw() if anything changed.
///
/// Items or submenus that themselves contain slashes ('/' or '\')
/// should be escaped, e.g. select("Holidays/12\\/25\//2010").
///
/// 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 (e.g. "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 (default),
/// callback_reason() will be FL_TREE_REASON_SELECTED
/// \returns
/// - 1 : OK: item's state was changed
/// - 0 : OK: item was already selected, no change was made
/// - -1 : ERROR: item was not found
///
int Fl_Tree::select(const char *path, int docallback) {
Fl_Tree_Item *item = find_item(path);
if ( ! item ) return(-1);
return(select(item, docallback));
}
/// Toggle the select state of the specified \p 'item'.
///
/// Invokes the callback depending on the value of optional
/// parameter \p 'docallback'.<br>
/// Handles calling redraw() if anything changed.
///
/// 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 selected. Must not be NULL.
/// \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 (default), callback_reason() will be
/// either FL_TREE_REASON_SELECTED or FL_TREE_REASON_DESELECTED
///
void Fl_Tree::select_toggle(Fl_Tree_Item *item, int docallback) {
item->select_toggle();
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.
///
/// Invokes the callback depending on the value of optional
/// parameter \p 'docallback'.<br>
/// Handles calling redraw() if anything changed.
///
/// 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 selected. Must not be NULL.
/// \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 (default),
/// 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 Fl_Tree::deselect(Fl_Tree_Item *item, int docallback) {
if ( item->is_selected() ) {
item->deselect();
set_changed();
if ( docallback ) {
do_callback_for_item(item, FL_TREE_REASON_DESELECTED);
}
redraw();
return(1);
}
return(0);
}
/// Deselect an item specified by \p 'path'.
///
/// Invokes the callback depending on the value of optional
/// parameter \p 'docallback'.<br>
/// Handles calling redraw() if anything changed.
///
/// Items or submenus that themselves contain slashes ('/' or '\')
/// should be escaped, e.g. deselect("Holidays/12\\/25\//2010").
///
/// 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 (e.g. "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 (default),
/// callback_reason() will be FL_TREE_REASON_DESELECTED
/// \returns
/// - 1 - OK: item's state was changed
/// - 0 - OK: item was already deselected, no change was made
/// - -1 - ERROR: item was not found
///
int Fl_Tree::deselect(const char *path, int docallback) {
Fl_Tree_Item *item = find_item(path);
if ( ! item ) return(-1);
return(deselect(item, docallback));
}
/// Deselect \p 'item' and all its children.
///
/// If item is NULL, first() is used.<br>
/// Invokes the callback depending on the value of optional
/// parameter \p 'docallback'.<br>
/// Handles calling redraw() if anything changed.
///
/// The callback can use callback_item() and callback_reason() respectively to determine
/// the item changed and the reason the callback was called.
///
/// \param[in] item The item that will be deselected (along with all its children).
/// If NULL, first() is used.
/// \param[in] docallback -- A flag that determines if the callback() is invoked or not:
/// - 0 - the callback() is not invoked
/// - 1 - the callback() is invoked for each item that changed state (default),
/// callback_reason() will be FL_TREE_REASON_DESELECTED
/// \returns Count of how many items were actually changed to the deselected state.
///
int Fl_Tree::deselect_all(Fl_Tree_Item *item, int docallback) {
item = item ? item : first(); // NULL? use first()
if ( ! item ) return(0);
int count = 0;
// Deselect item
if ( item->is_selected() )
if ( deselect(item, docallback) )
++count;
// Deselect its children
for ( int t=0; t<item->children(); t++ ) {
count += deselect_all(item->child(t), docallback); // recurse
}
return(count);
}
/// Select only the specified \p 'item', deselecting all others that might be selected.
///
/// If item is 0, first() is used.<br>
/// Invokes the callback depending on the value of optional
/// parameter \p 'docallback'.<br>
/// Handles calling redraw() if anything changed.
///
/// The callback can use callback_item() and callback_reason() respectively to determine
/// the item changed and the reason the callback was called.
///
/// \param[in] selitem The item to be selected. If NULL, first() is used.
/// \param[in] docallback -- A flag that determines if the callback() is invoked or not:
/// - 0 - the callback() is not invoked
/// - 1 - the callback() is invoked for each item that changed state (default),
/// callback_reason() will be either FL_TREE_REASON_SELECTED or
/// FL_TREE_REASON_DESELECTED
/// \returns The number of items whose selection states were changed, if any.
///
int Fl_Tree::select_only(Fl_Tree_Item *selitem, int docallback) {
selitem = selitem ? selitem : first(); // NULL? use first()
if ( ! selitem ) return(0);
int changed = 0;
// Deselect everything first.
// Prevents callbacks from seeing more than one item selected.
//
for ( Fl_Tree_Item *item = first(); item; item = item->next() ) {
if ( item == selitem ) continue; // don't do anything to selitem yet..
if ( item->is_selected() ) {
deselect(item, docallback);
++changed;
}
}
#if FLTK_ABI_VERSION >= 10301
// Should we 'reselect' item if already selected?
if ( selitem->is_selected() && (item_reselect_mode()==FL_TREE_SELECTABLE_ALWAYS) ) {
// Selection unchanged, so no ++change
select(selitem, docallback); // do callback with reason=reselect
} else if ( !selitem->is_selected() ) {
// Item was not already selected, select and indicate changed
select(selitem, docallback);
++changed;
}
#else
if ( !selitem->is_selected() ) {
// All items deselected, now select the one we want
select(selitem, docallback);
++changed;
}
#endif
return(changed);
}
/// Select \p 'item' and all its children.
///
/// If item is NULL, first() is used.<br>
/// Invokes the callback depending on the value of optional
/// parameter \p 'docallback'.<br>
/// Handles calling redraw() if anything changed.
///
/// The callback can use callback_item() and callback_reason() respectively to determine
/// the item changed and the reason the callback was called.
///
/// \param[in] item The item that will be selected (along with all its children).
/// If NULL, first() is used.
/// \param[in] docallback -- A flag that determines if the callback() is invoked or not:
/// - 0 - the callback() is not invoked
/// - 1 - the callback() is invoked for each item that changed state (default),
/// callback_reason() will be FL_TREE_REASON_SELECTED
/// \returns Count of how many items were actually changed to the selected state.
///
int Fl_Tree::select_all(Fl_Tree_Item *item, int docallback) {
item = item ? item : first(); // NULL? use first()
if ( ! item ) return(0);
int count = 0;
// Select item
if ( !item->is_selected() )
if ( select(item, docallback) )
++count;
// Select its children
for ( int t=0; t<item->children(); t++ ) {
count += select_all(item->child(t), docallback); // recurse
}
return(count);
}
/// Get the item that currently has keyboard focus.
Fl_Tree_Item* Fl_Tree::get_item_focus() const {
return(_item_focus);
}
/// Set the item that currently should have keyboard focus.
///
/// Handles calling redraw() to update the focus box (if it is visible).
///
/// \param[in] item The item that should take focus. If NULL, none will have focus.
///
void Fl_Tree::set_item_focus(Fl_Tree_Item *item) {
if ( _item_focus != item ) { // changed?
_item_focus = item; // update
if ( visible_focus() ) redraw(); // redraw to update focus box
}
}
/// See if the specified \p 'item' is selected.
///
/// \param[in] item -- the item to be tested. Must not be NULL.
///
/// \return
/// - 1 : item selected
/// - 0 : item deselected
///
int Fl_Tree::is_selected(Fl_Tree_Item *item) const {
return(item->is_selected()?1:0);
}
/// See if item specified by \p 'path' is selected.
///
/// Items or submenus that themselves contain slashes ('/' or '\')
/// should be escaped, e.g. is_selected("Holidays/12\\/25\//2010").
///
/// \param[in] path -- the tree item's pathname (e.g. "Flintstones/Fred")
/// \returns
/// - 1 : item selected
/// - 0 : item deselected
/// - -1 : item was not found
///
int Fl_Tree::is_selected(const char *path) {
Fl_Tree_Item *item = find_item(path);
if ( ! item ) return(-1);
return(is_selected(item));
}
/// Get the default label fontsize used for creating new items.
Fl_Fontsize Fl_Tree::item_labelsize() const {
return(_prefs.labelsize());
}
/// Set the default label font size used for creating new items.
/// To change the font size on a per-item basis, use Fl_Tree_Item::labelsize(Fl_Fontsize)
///
void Fl_Tree::item_labelsize(Fl_Fontsize val) {
_prefs.labelsize(val);
}
/// Get the default font face used for creating new items.
Fl_Font Fl_Tree::item_labelfont() const {
return(_prefs.labelfont());
}
/// Set the default font face used for creating new items.
/// To change the font face on a per-item basis, use Fl_Tree_Item::labelfont(Fl_Font)
///
void Fl_Tree::item_labelfont(Fl_Font val) {
_prefs.labelfont(val);
}
/// Get the default label foreground color used for creating new items.
Fl_Color Fl_Tree::item_labelfgcolor(void) const {
return(_prefs.labelfgcolor());
}
/// Set the default label foreground color used for creating new items.
/// To change the foreground color on a per-item basis, use Fl_Tree_Item::labelfgcolor(Fl_Color)
///
void Fl_Tree::item_labelfgcolor(Fl_Color val) {
_prefs.labelfgcolor(val);
}
/// Get the default label background color used for creating new items.
/// If the color is 0xffffffff, it is 'transparent'.
Fl_Color Fl_Tree::item_labelbgcolor(void) const {
return(_prefs.labelbgcolor());
}
/// Set the default label background color used for creating new items.
/// A special case is made for color 0xffffffff (default) which is treated as 'transparent'.
/// To change the background color on a per-item basis, use Fl_Tree_Item::labelbgcolor(Fl_Color)
///
void Fl_Tree::item_labelbgcolor(Fl_Color val) {
_prefs.labelbgcolor(val);
}
/// Get the connector color used for tree connection lines.
Fl_Color Fl_Tree::connectorcolor() const {
return(_prefs.connectorcolor());
}
/// Set the connector color used for tree connection lines.
void Fl_Tree::connectorcolor(Fl_Color val) {
_prefs.connectorcolor(val);
}
/// Get the amount of white space (in pixels) that should appear
/// between the widget's left border and the tree's contents.
///
int Fl_Tree::marginleft() const {
return(_prefs.marginleft());
}
/// Set the amount of white space (in pixels) that should appear
/// between the widget's left border and the left side of the tree's contents.
///
void Fl_Tree::marginleft(int val) {
_prefs.marginleft(val);
redraw();
recalc_tree();
}
/// Get the amount of white space (in pixels) that should appear
/// between the widget's top border and the top of the tree's contents.
///
int Fl_Tree::margintop() const {
return(_prefs.margintop());
}
/// Sets the amount of white space (in pixels) that should appear
/// between the widget's top border and the top of the tree's contents.
///
void Fl_Tree::margintop(int val) {
_prefs.margintop(val);
redraw();
recalc_tree();
}
#if FLTK_ABI_VERSION >= 10301
/// Get the amount of white space (in pixels) that should appear
/// below the last visible item when the vertical scroller is scrolled to the bottom.
///
int Fl_Tree::marginbottom() const {
return(_prefs.marginbottom());
}
/// Sets the amount of white space (in pixels) that should appear
/// below the last visible item when the vertical scroller is scrolled to the bottom.
///
void Fl_Tree::marginbottom(int val) {
_prefs.marginbottom(val);
redraw();
recalc_tree();
}
#endif /*FLTK_ABI_VERSION*/
/// Get the amount of white space (in pixels) that should appear
/// between items in the tree.
///
int Fl_Tree::linespacing() const {
return(_prefs.linespacing());
}
/// Sets the amount of white space (in pixels) that should appear
/// between items in the tree.
///
void Fl_Tree::linespacing(int val) {
_prefs.linespacing(val);
redraw();
recalc_tree();
}
/// Get the amount of white space (in pixels) that should appear
/// below an open child tree's contents.
///
int Fl_Tree::openchild_marginbottom() const {
return(_prefs.openchild_marginbottom());
}
/// Set the amount of white space (in pixels) that should appear
/// below an open child tree's contents.
///
void Fl_Tree::openchild_marginbottom(int val) {
_prefs.openchild_marginbottom(val);
redraw();
recalc_tree();
}
/// Get the amount of white space (in pixels) that should appear
/// to the left of the usericon.
int Fl_Tree::usericonmarginleft() const {
return(_prefs.usericonmarginleft());
}
/// Set the amount of white space (in pixels) that should appear
/// to the left of the usericon.
void Fl_Tree::usericonmarginleft(int val) {
_prefs.usericonmarginleft(val);
redraw();
recalc_tree();
}
/// Get the amount of white space (in pixels) that should appear
/// to the left of the label text.
int Fl_Tree::labelmarginleft() const {
return(_prefs.labelmarginleft());
}
/// Set the amount of white space (in pixels) that should appear
/// to the left of the label text.
void Fl_Tree::labelmarginleft(int val) {
_prefs.labelmarginleft(val);
redraw();
recalc_tree();
}
#if FLTK_ABI_VERSION >= 10301
/// Get the amount of white space (in pixels) that should appear
/// to the left of the child fltk widget (if any).
int Fl_Tree::widgetmarginleft() const {
return(_prefs.widgetmarginleft());
}
/// Set the amount of white space (in pixels) that should appear
/// to the left of the child fltk widget (if any).
void Fl_Tree::widgetmarginleft(int val) {
_prefs.widgetmarginleft(val);
redraw();
recalc_tree();
}
#endif /*FLTK_ABI_VERSION*/
/// Gets the width of the horizontal connection lines (in pixels)
/// that appear to the left of each tree item's label.
///
int Fl_Tree::connectorwidth() const {
return(_prefs.connectorwidth());
}
/// Sets the width of the horizontal connection lines (in pixels)
/// that appear to the left of each tree item's label.
///
void Fl_Tree::connectorwidth(int val) {
_prefs.connectorwidth(val);
redraw();
recalc_tree();
}
/// Returns the Fl_Image being used as the default user icon for all
/// newly created items.
/// Returns zero if no icon has been set, which is the default.
///
Fl_Image* Fl_Tree::usericon() const {
return(_prefs.usericon());
}
/// Sets the Fl_Image to be used as the default user icon for all
/// newly created items.
///
/// If you want to specify user icons on a per-item basis,
/// use Fl_Tree_Item::usericon() instead.
///
/// \param[in] val -- The new image to be used, or
/// zero to disable user icons.
///
void Fl_Tree::usericon(Fl_Image *val) {
_prefs.usericon(val);
redraw();
recalc_tree();
}
/// Returns the icon to be used as the 'open' icon.
/// If none was set, the internal default is returned,
/// a simple '[+]' icon.
///
Fl_Image* Fl_Tree::openicon() const {
return(_prefs.openicon());
}
/// Sets the icon to be used as the 'open' icon.
/// This overrides the built in default '[+]' icon.
///
/// \param[in] val -- The new image, or zero to use the default [+] icon.
///
void Fl_Tree::openicon(Fl_Image *val) {
_prefs.openicon(val);
redraw();
recalc_tree();
}
/// Returns the icon to be used as the 'close' icon.
/// If none was set, the internal default is returned,
/// a simple '[-]' icon.
///
Fl_Image* Fl_Tree::closeicon() const {
return(_prefs.closeicon());
}
/// Sets the icon to be used as the 'close' icon.
/// This overrides the built in default '[-]' icon.
///
/// \param[in] val -- The new image, or zero to use the default [-] icon.
///
void Fl_Tree::closeicon(Fl_Image *val) {
_prefs.closeicon(val);
redraw();
recalc_tree();
}
/// Returns 1 if the collapse icon is enabled, 0 if not.
int Fl_Tree::showcollapse() const {
return(_prefs.showcollapse());
}
/// Set if we should show the collapse icon or not.
/// If collapse icons are disabled, the user will not be able
/// to interactively collapse items in the tree, unless the application
/// provides some other means via open() and close().
///
/// \param[in] val 1: shows collapse icons (default),\n
/// 0: hides collapse icons.
///
void Fl_Tree::showcollapse(int val) {
_prefs.showcollapse(val);
redraw();
recalc_tree();
}
/// Returns 1 if the root item is to be shown, or 0 if not.
int Fl_Tree::showroot() const {
return(_prefs.showroot());
}
/// Set if the root item should be shown or not.
/// \param[in] val 1 -- show the root item (default)\n
/// 0 -- hide the root item.
///
void Fl_Tree::showroot(int val) {
_prefs.showroot(val);
redraw();
recalc_tree();
}
/// Returns the line drawing style for inter-connecting items.
Fl_Tree_Connector Fl_Tree::connectorstyle() const {
return(_prefs.connectorstyle());
}
/// Sets the line drawing style for inter-connecting items.
/// See ::Fl_Tree_Connector for possible values.
///
void Fl_Tree::connectorstyle(Fl_Tree_Connector val) {
_prefs.connectorstyle(val);
redraw();
}
/// Set the default sort order used when items are added to the tree.
/// See ::Fl_Tree_Sort for possible values.
///
Fl_Tree_Sort Fl_Tree::sortorder() const {
return(_prefs.sortorder());
}
/// Gets the sort order used to add items to the tree.
void Fl_Tree::sortorder(Fl_Tree_Sort val) {
_prefs.sortorder(val);
// no redraw().. only affects new add()itions
}
/// Sets the style of box used to draw selected items.
/// This is an fltk ::Fl_Boxtype.
/// The default is influenced by FLTK's current Fl::scheme()
///
Fl_Boxtype Fl_Tree::selectbox() const {
return(_prefs.selectbox());
}
/// Gets the style of box used to draw selected items.
/// This is an fltk ::Fl_Boxtype.
/// The default is influenced by FLTK's current Fl::scheme()
///
void Fl_Tree::selectbox(Fl_Boxtype val) {
_prefs.selectbox(val);
redraw();
}
/// Gets the tree's current selection mode.
Fl_Tree_Select Fl_Tree::selectmode() const {
return(_prefs.selectmode());
}
/// Sets the tree's selection mode.
/// See ::Fl_Tree_Select for possible values.
///
void Fl_Tree::selectmode(Fl_Tree_Select val) {
_prefs.selectmode(val);
}
#if FLTK_ABI_VERSION >= 10301
/// Returns the current item re/selection mode.
/// \version 1.3.1 ABI feature
///
Fl_Tree_Item_Reselect_Mode Fl_Tree::item_reselect_mode() const {
return(_prefs.item_reselect_mode());
}
/// Sets the item re/selection mode
/// See ::Fl_Tree_Item_Reselect_Mode for possible values.
/// \version 1.3.1 ABI feature
///
void Fl_Tree::item_reselect_mode(Fl_Tree_Item_Reselect_Mode mode) {
_prefs.item_reselect_mode(mode);
}
/// Get the 'item draw mode' used for the tree
/// \version 1.3.1 ABI feature
///
Fl_Tree_Item_Draw_Mode Fl_Tree::item_draw_mode() const {
return(_prefs.item_draw_mode());
}
/// Set the 'item draw mode' used for the tree to \p 'mode'.
///
/// This affects how items in the tree are drawn,
/// such as when a widget() is defined.
/// See ::Fl_Tree_Item_Draw_Mode for possible values.
/// \version 1.3.1 ABI feature
///
void Fl_Tree::item_draw_mode(Fl_Tree_Item_Draw_Mode mode) {
_prefs.item_draw_mode(mode);
}
/// Set the 'item draw mode' used for the tree to integer \p 'mode'.
///
/// This affects how items in the tree are drawn,
/// such as when a widget() is defined.
/// See ::Fl_Tree_Item_Draw_Mode for possible values.
/// \version 1.3.1 ABI feature
///
void Fl_Tree::item_draw_mode(int mode) {
_prefs.item_draw_mode(Fl_Tree_Item_Draw_Mode(mode));
}
#endif
/// See if \p 'item' is currently displayed on-screen (visible within the widget).
///
/// This can be used to detect if the item is scrolled off-screen.
/// Checks to see if the item's vertical position is within the top and bottom
/// edges of the display window. This does NOT take into account the hide() / show()
/// or open() / close() status of the item.
///
/// \param[in] item The item to be checked. If NULL, first() is used.
/// \returns 1 if displayed, 0 if scrolled off screen or no items are in tree.
///
int Fl_Tree::displayed(Fl_Tree_Item *item) {
item = item ? item : first();
if (!item) return(0);
return( (item->y() >= y()) && (item->y() <= (y()+h()-item->h())) ? 1 : 0);
}
/// Adjust the vertical scroll bar so that \p 'item' is visible
/// \p 'yoff' pixels from the top of the Fl_Tree widget's display.
///
/// For instance, yoff=0 will position the item at the top.
///
/// If yoff is larger than the vertical scrollbar's limit,
/// the value will be clipped. So if yoff=100, but scrollbar's max
/// is 50, then 50 will be used.
///
/// \param[in] item The item to be shown. If NULL, first() is used.
/// \param[in] yoff The pixel offset from the top for the displayed position.
///
/// \see show_item_top(), show_item_middle(), show_item_bottom()
///
void Fl_Tree::show_item(Fl_Tree_Item *item, int yoff) {
item = item ? item : first();
if (!item) return;
int newval = item->y() - y() - yoff + (int)_vscroll->value();
if ( newval < _vscroll->minimum() ) newval = (int)_vscroll->minimum();
if ( newval > _vscroll->maximum() ) newval = (int)_vscroll->maximum();
_vscroll->value(newval);
redraw();
}
/// Adjust the vertical scroll bar to show \p 'item' at the top
/// of the display IF it is currently off-screen (e.g. show_item_top()).
/// If it is already on-screen, no change is made.
///
/// \param[in] item The item to be shown. If NULL, first() is used.
///
/// \see show_item_top(), show_item_middle(), show_item_bottom()
///
void Fl_Tree::show_item(Fl_Tree_Item *item) {
item = item ? item : first();
if (!item) return;
if ( displayed(item) ) return;
show_item_top(item);
}
/// Adjust the vertical scrollbar so that \p 'item' is at the top of the display.
///
/// \param[in] item The item to be shown. If NULL, first() is used.
///
void Fl_Tree::show_item_top(Fl_Tree_Item *item) {
item = item ? item : first();
if (item) show_item(item, 0);
}
/// Adjust the vertical scrollbar so that \p 'item' is in the middle of the display.
///
/// \param[in] item The item to be shown. If NULL, first() is used.
///
void Fl_Tree::show_item_middle(Fl_Tree_Item *item) {
item = item ? item : first();
#if FLTK_ABI_VERSION >= 10303
if (item) show_item(item, (_tih/2)-(item->h()/2));
#else
if (item) show_item(item, (h()/2)-(item->h()/2));
#endif
}
/// Adjust the vertical scrollbar so that \p 'item' is at the bottom of the display.
///
/// \param[in] item The item to be shown. If NULL, first() is used.
///
void Fl_Tree::show_item_bottom(Fl_Tree_Item *item) {
item = item ? item : first();
#if FLTK_ABI_VERSION >= 10303
if (item) show_item(item, _tih-item->h());
#else
if (item) show_item(item, h()-item->h());
#endif
}
/// Displays \p 'item', scrolling the tree as necessary.
/// \param[in] item The item to be displayed. If NULL, first() is used.
///
void Fl_Tree::display(Fl_Tree_Item *item) {
item = item ? item : first();
if (item) show_item_middle(item);
}
/// 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.
/// \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.
/// \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();
}
/// Returns the horizontal scroll position as a pixel offset.
/// The position returned is how many pixels of the tree are scrolled off the left edge
/// of the screen.
/// \see vposition(), hposition()
/// \note Must be using FLTK ABI 1.3.3 or higher for this to be effective.
///
int Fl_Tree::hposition() const {
#if FLTK_ABI_VERSION >= 10303
return((int)_hscroll->value());
#else
return(0);
#endif
}
/// Sets the horizontal scroll offset to position \p 'pos'.
/// The position is how many pixels of the tree are scrolled off the left edge
/// of the screen.
/// \param[in] pos The vertical position (in pixels) to scroll the browser to.
/// \note Must be using FLTK ABI 1.3.3 or higher for this to be effective.
///
void Fl_Tree::hposition(int pos) {
#if FLTK_ABI_VERSION >= 10303
if (pos < 0) pos = 0;
if (pos > _hscroll->maximum()) pos = (int)_hscroll->maximum();
if (pos == _hscroll->value()) return;
_hscroll->value(pos);
redraw();
#endif
}
/// 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.
/// \todo should be const
///
int Fl_Tree::is_scrollbar(Fl_Widget *w) {
#if FLTK_ABI_VERSION >= 10303
return( (w==_vscroll || w==_hscroll) ? 1 : 0 );
#else
return( (w==_vscroll) ? 1 : 0 );
#endif
}
/// Gets the default size of scrollbars' troughs for this widget
/// 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 Fl_Tree::scrollbar_size() const {
return(_scrollbar_size);
}
/// Sets the pixel size of the scrollbars' troughs to \p 'size'
/// for this widget, 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, and is the default behavior. Normally
/// this is what you want.
///
/// Only use this method if you really need to override just THIS
/// instance of the widget's scrollbar size. (This need 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 Fl_Tree::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());
}
#if FLTK_ABI_VERSION >= 10303
if ( _hscroll->h() != scrollsize ) {
_hscroll->resize(x(), y()+h()-scrollsize, _hscroll->w(), scrollsize);
}
// Changing scrollbar size affects _tiw/_tih + may affect scrollbar visibility
calc_dimensions();
#endif
}
/// See if the vertical scrollbar is currently visible.
/// \returns 1 if scrollbar visible, 0 if not.
///
int Fl_Tree::is_vscroll_visible() const {
return(_vscroll->visible() ? 1 : 0);
}
/// See if the horizontal scrollbar is currently visible.
/// \returns 1 if scrollbar visible, 0 if not.
/// \note Must be using FLTK ABI 1.3.3 or higher for this to be effective.
///
int Fl_Tree::is_hscroll_visible() const {
#if FLTK_ABI_VERSION >= 10303
return(_hscroll->visible() ? 1 : 0);
#else
return 0;
#endif
}
/// Do the callback for the specified \p 'item' using \p 'reason',
/// setting the callback_item() and callback_reason().
///
void Fl_Tree::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());
}
/// Sets the item that was changed for this callback.
/// Used internally to pass the item that invoked the callback.
///
void Fl_Tree::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* Fl_Tree::callback_item() {
return(_callback_item);
}
/// Sets the reason for this callback.
/// Used internally to pass the reason the callback was invoked.
///
void Fl_Tree::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_RESELECTED: ..item was reselected (double-clicked, etc)..
/// case FL_TREE_REASON_DESELECTED: ..item was deselected..
/// }
/// }
/// \endcode
///
/// \see item_reselect_mode() -- enables FL_TREE_REASON_RESELECTED events
///
Fl_Tree_Reason Fl_Tree::callback_reason() const {
return(_callback_reason);
}
/**
* Read a preferences database into the tree widget.
* A preferences database is a hierarchical collection of data which can be
* directly loaded into the tree view for inspection.
* \param[in] prefs the Fl_Preferences database
*/
void Fl_Tree::load(Fl_Preferences &prefs) {
int i, j, n, pn = (int) strlen(prefs.path());
char *p;
const char *path = prefs.path();
if (strcmp(path, ".")==0)
path += 1; // root path is empty
else
path += 2; // child path starts with "./"
n = prefs.groups();
for (i=0; i<n; i++) {
Fl_Preferences prefsChild(prefs, i);
add(prefsChild.path()+2); // children always start with "./"
load(prefsChild);
}
n = prefs.entries();
for (i=0; i<n; i++) {
// We must remove all fwd slashes in the key and value strings. Replace with backslash.
char *key = strdup(prefs.entry(i));
int kn = (int) strlen(key);
for (j=0; j<kn; j++) {
if (key[j]=='/') key[j]='\\';
}
char *val; prefs.get(key, val, "");
int vn = (int) strlen(val);
for (j=0; j<vn; j++) {
if (val[j]=='/') val[j]='\\';
}
if (vn<40) {
size_t sze = pn + strlen(key) + vn;
p = (char*)malloc(sze+5);
sprintf(p, "%s/%s = %s", path, key, val);
} else {
size_t sze = pn + strlen(key) + 40;
p = (char*)malloc(sze+5);
sprintf(p, "%s/%s = %.40s...", path, key, val);
}
add(p[0]=='/'?p+1:p);
free(p);
free(val);
free(key);
}
}
/// Ensure the scrollbars are the last children
void Fl_Tree::fix_scrollbar_order() {
Fl_Widget** a = (Fl_Widget**)array();
if (a[children()-1] != _vscroll) {
int i,j;
#if FLTK_ABI_VERSION >= 10303
for (i = j = 0; j < children(); j++) {
if (a[j] != _vscroll && a[j] != _hscroll ) a[i++] = a[j];
}
a[i++] = _hscroll;
a[i++] = _vscroll;
#else
for (i = j = 0; j < children(); j++) {
if (a[j] != _vscroll) a[i++] = a[j];
}
a[i++] = _vscroll;
#endif
}
}
/// Schedule tree to recalc the entire tree size.
/// \note Must be using FLTK ABI 1.3.3 or higher for this to be effective.
///
void Fl_Tree::recalc_tree() {
#if FLTK_ABI_VERSION >= 10303
_tree_w = _tree_h = -1;
#endif
}
//
// End of "$Id$".
//