355 lines
11 KiB
C++
355 lines
11 KiB
C++
|
#include <stdio.h>
|
||
|
#include <stdlib.h>
|
||
|
#include <string.h>
|
||
|
|
||
|
#include <FL/Fl_Tree.H>
|
||
|
|
||
|
#define SCROLL_W 15
|
||
|
|
||
|
//////////////////////
|
||
|
// Fl_Tree.cxx
|
||
|
//////////////////////
|
||
|
//
|
||
|
// Fl_Tree -- This file is part of the Fl_Tree widget for FLTK
|
||
|
// Copyright (C) 2009 by Greg Ercolano.
|
||
|
//
|
||
|
// This library is free software; you can redistribute it and/or
|
||
|
// modify it under the terms of the GNU Library General Public
|
||
|
// License as published by the Free Software Foundation; either
|
||
|
// version 2 of the License, or (at your option) any later version.
|
||
|
//
|
||
|
// This library is distributed in the hope that it will be useful,
|
||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||
|
// Library General Public License for more details.
|
||
|
//
|
||
|
// You should have received a copy of the GNU Library General Public
|
||
|
// License along with this library; if not, write to the Free Software
|
||
|
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
|
||
|
// USA.
|
||
|
//
|
||
|
|
||
|
// INTERNAL: scroller callback
|
||
|
static void scroll_cb(Fl_Widget*,void *data) {
|
||
|
((Fl_Tree*)data)->redraw();
|
||
|
}
|
||
|
|
||
|
// INTERNAL: Parse elements from path into an array of null terminated strings
|
||
|
// Path="/aa/bb"
|
||
|
// Return: arr[0]="aa", arr[1]="bb", arr[2]=0
|
||
|
// Caller must: free(arr[0]); free(arr);
|
||
|
//
|
||
|
static char **parse_path(const char *path) {
|
||
|
while ( *path == '/' ) path++; // skip leading '/'
|
||
|
// First pass: identify, null terminate, and count separators
|
||
|
int seps = 1; // separator count (1: first item)
|
||
|
int arrsize = 1; // array size (1: first item)
|
||
|
char *save = strdup(path); // make copy we can modify
|
||
|
char *s = save;
|
||
|
while ( ( s = strchr(s, '/') ) ) {
|
||
|
while ( *s == '/' ) { *s++ = 0; seps++; }
|
||
|
if ( *s ) { arrsize++; }
|
||
|
}
|
||
|
arrsize++; // (room for terminating NULL)
|
||
|
// Second pass: create array, save nonblank elements
|
||
|
char **arr = (char**)malloc(sizeof(char*) * arrsize);
|
||
|
int t = 0;
|
||
|
s = save;
|
||
|
while ( seps-- > 0 ) {
|
||
|
if ( *s ) { arr[t++] = s; } // skips empty fields, eg. '//'
|
||
|
s += (strlen(s) + 1);
|
||
|
}
|
||
|
arr[t] = 0;
|
||
|
return(arr);
|
||
|
}
|
||
|
|
||
|
// INTERNAL: Recursively descend tree hierarchy, accumulating total child count
|
||
|
static int find_total_children(Fl_Tree_Item *item, int count=0) {
|
||
|
count++;
|
||
|
for ( int t=0; t<item->children(); t++ ) {
|
||
|
count = find_total_children(item->child(t), count);
|
||
|
}
|
||
|
return(count);
|
||
|
}
|
||
|
|
||
|
/// Constructor.
|
||
|
Fl_Tree::Fl_Tree(int X, int Y, int W, int H, const char *L) : Fl_Group(X,Y,W,H,L) {
|
||
|
_root = new Fl_Tree_Item(_prefs);
|
||
|
_root->parent(0); // we are root of tree
|
||
|
_root->label("ROOT");
|
||
|
_item_clicked = 0;
|
||
|
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->hide();
|
||
|
_vscroll->type(FL_VERTICAL);
|
||
|
_vscroll->step(1);
|
||
|
_vscroll->callback(scroll_cb, (void*)this);
|
||
|
end();
|
||
|
}
|
||
|
|
||
|
/// Destructor.
|
||
|
Fl_Tree::~Fl_Tree() {
|
||
|
if ( _root ) { delete _root; _root = 0; }
|
||
|
}
|
||
|
|
||
|
/// Adds a new item, given a 'menu style' path, eg: "/Parent/Child/item".
|
||
|
/// Any parent nodes that don't already exist are created automatically.
|
||
|
/// Adds the item based on the value of sortorder().
|
||
|
/// \returns the child item created, or 0 on error.
|
||
|
///
|
||
|
Fl_Tree_Item* Fl_Tree::add(const char *path) {
|
||
|
if ( ! _root ) { // Create root if none
|
||
|
_root = new Fl_Tree_Item(_prefs);
|
||
|
_root->parent(0);
|
||
|
_root->label("ROOT");
|
||
|
}
|
||
|
char **arr = parse_path(path);
|
||
|
Fl_Tree_Item *item = _root->add(_prefs, arr);
|
||
|
free((void*)arr[0]);
|
||
|
free((void*)arr);
|
||
|
return(item);
|
||
|
}
|
||
|
|
||
|
/// Inserts a new item above the specified Fl_Tree_Item, with the label set to 'name'.
|
||
|
/// \returns the item that was added, or 0 if 'above' could not be found.
|
||
|
///
|
||
|
Fl_Tree_Item* Fl_Tree::insert_above(Fl_Tree_Item *above, const char *name) {
|
||
|
return(above->insert_above(_prefs, name));
|
||
|
}
|
||
|
|
||
|
/// Find the item, given a menu style path, eg: "/Parent/Child/item".
|
||
|
///
|
||
|
/// There is both a const and non-const version of this method.
|
||
|
/// Const version allows pure const methods to use this method
|
||
|
/// to do lookups without causing compiler errors.
|
||
|
/// \returns the item, or 0 if not found.
|
||
|
///
|
||
|
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);
|
||
|
return(item);
|
||
|
}
|
||
|
|
||
|
/// A const version of Fl_Tree::find_item(const char *path)
|
||
|
const Fl_Tree_Item *Fl_Tree::find_item(const char *path) const {
|
||
|
if ( ! _root ) return(0);
|
||
|
char **arr = parse_path(path);
|
||
|
const Fl_Tree_Item *item = _root->find_item(arr);
|
||
|
free((void*)arr[0]);
|
||
|
free((void*)arr);
|
||
|
return(item);
|
||
|
}
|
||
|
|
||
|
/// 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()
|
||
|
//
|
||
|
Fl_Group::draw_box();
|
||
|
Fl_Group::draw_label();
|
||
|
if ( ! _root ) return;
|
||
|
int cx = x() + Fl::box_dx(box());
|
||
|
int cy = y() + Fl::box_dy(box());
|
||
|
int cw = w() - Fl::box_dw(box());
|
||
|
int ch = h() - Fl::box_dh(box());
|
||
|
// These values are changed during drawing
|
||
|
// 'Y' will be the lowest point on the tree
|
||
|
int X = cx + _prefs.marginleft();
|
||
|
int Y = cy + _prefs.margintop() - (_vscroll->visible() ? _vscroll->value() : 0);
|
||
|
int W = cw - _prefs.marginleft(); // - _prefs.marginright();
|
||
|
int Ysave = Y;
|
||
|
fl_push_clip(cx,cy,cw,ch);
|
||
|
{
|
||
|
fl_font(_prefs.labelfont(), _prefs.labelsize());
|
||
|
_root->draw(X, Y, W, this, _prefs);
|
||
|
}
|
||
|
fl_pop_clip();
|
||
|
|
||
|
// Show vertical scrollbar?
|
||
|
int ydiff = (Y+_prefs.margintop())-Ysave; // ydiff=size of tree
|
||
|
int ytoofar = (cy+ch) - Y; // ytoofar -- scrolled beyond bottom (eg. stow)
|
||
|
|
||
|
//printf("ydiff=%d ch=%d Ysave=%d ytoofar=%d value=%d\n",
|
||
|
//int(ydiff),int(ch),int(Ysave),int(ytoofar), int(_vscroll->value()));
|
||
|
|
||
|
if ( ytoofar > 0 ) ydiff += ytoofar;
|
||
|
if ( Ysave<cy || ydiff > ch || int(_vscroll->value()) > 1 ) {
|
||
|
_vscroll->visible();
|
||
|
int sx = x()+w()-Fl::box_dx(box())-SCROLL_W;
|
||
|
int sy = y()+Fl::box_dy(box());
|
||
|
int sw = SCROLL_W;
|
||
|
int sh = h()-Fl::box_dh(box());
|
||
|
_vscroll->show();
|
||
|
_vscroll->range(0.0,ydiff-ch);
|
||
|
_vscroll->resize(sx,sy,sw,sh);
|
||
|
_vscroll->slider_size(float(ch)/float(ydiff));
|
||
|
} else {
|
||
|
_vscroll->Fl_Slider::value(0);
|
||
|
_vscroll->hide();
|
||
|
}
|
||
|
fl_push_clip(cx,cy,cw,ch);
|
||
|
Fl_Group::draw_children(); // draws any FLTK children set via Fl_Tree::widget()
|
||
|
fl_pop_clip();
|
||
|
}
|
||
|
|
||
|
/// Standard FLTK event handler for this widget.
|
||
|
int Fl_Tree::handle(int e) {
|
||
|
static Fl_Tree_Item *lastselect = 0;
|
||
|
int changed = 0;
|
||
|
int ret = Fl_Group::handle(e);
|
||
|
if ( ! _root ) return(ret);
|
||
|
switch ( e ) {
|
||
|
case FL_PUSH: {
|
||
|
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
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
case FL_DRAG: {
|
||
|
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)
|
||
|
redraw(); // make change(s) visible
|
||
|
}
|
||
|
} else {
|
||
|
changed = 1; // changed
|
||
|
o->select(); // select this
|
||
|
redraw(); // make change(s) visible
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
if ( changed ) {
|
||
|
redraw(); // make change(s) visible
|
||
|
if ( when() & FL_WHEN_CHANGED ) {
|
||
|
set_changed();
|
||
|
do_callback((Fl_Widget*)this, user_data()); // item callback
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
case FL_RELEASE: {
|
||
|
if ( Fl::event_button() == FL_LEFT_MOUSE ) {
|
||
|
ret |= 1;
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
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".
|
||
|
///
|
||
|
int Fl_Tree::deselect_all(Fl_Tree_Item *item) {
|
||
|
item = item ? item : root(); // NULL? use root()
|
||
|
int count = item->deselect_all();
|
||
|
if ( count ) redraw(); // anything changed? cause redraw
|
||
|
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.
|
||
|
///
|
||
|
int Fl_Tree::select_only(Fl_Tree_Item *selitem) {
|
||
|
selitem = selitem ? selitem : root(); // NULL? use root()
|
||
|
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();
|
||
|
++changed;
|
||
|
} else {
|
||
|
if ( item->is_selected() ) {
|
||
|
item->deselect();
|
||
|
++changed;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
if ( changed ) redraw(); // anything changed? redraw
|
||
|
return(changed);
|
||
|
}
|