source: branches/blt4/builder/scripts/hierlist.tcl @ 2936

Last change on this file since 2936 was 2275, checked in by gah, 13 years ago

Builder fixes for blt4

File size: 19.8 KB
Line 
1# ----------------------------------------------------------------------
2#  COMPONENT: hierlist - hierarchical list of elements
3#
4#  This widget is similar to the BLT hierbox or treeview, but it
5#  allows for embedded windows at each node.
6# ======================================================================
7#  AUTHOR:  Michael McLennan, Purdue University
8#  Copyright (c) 2004-2010  Purdue Research Foundation
9#
10#  See the file "license.terms" for information on usage and
11#  redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES.
12# ======================================================================
13#package require Itk
14package require BLT
15
16option add *Hierlist.width 2i widgetDefault
17option add *Hierlist.height 3i widgetDefault
18option add *Hierlist.indent 20 widgetDefault
19option add *Hierlist.icon {treepl treemn} widgetDefault
20option add *Hierlist.selectBackground cyan widgetDefault
21option add *Hierlist.dropLineColor blue widgetDefault
22option add *Hierlist.font {helvetica -12} widgetDefault
23
24itcl::class Rappture::Hierlist {
25    inherit itk::Widget Rappture::Dragdrop
26
27    itk_option define -indent indent Indent 10
28    itk_option define -padding padding Padding 4
29    itk_option define -icon icon Icon ""
30    itk_option define -font font Font ""
31    itk_option define -title title Title "%type: %id"
32    itk_option define -selectbackground selectBackground Foreground ""
33    itk_option define -droplinecolor dropLineColor Foreground ""
34
35    constructor {args} { # defined below }
36    destructor { # defined below }
37
38    public method tree {option args}
39    public method select {what {how "-notify"}}
40    public method curselection {args}
41    public method toggle {what}
42
43    public method xview {args} { eval $itk_component(area) xview $args }
44    public method yview {args} { eval $itk_component(area) yview $args }
45
46    protected method _redraw {args}
47    protected method _redrawChildren {node indent ypos}
48    protected method _edit {option node field args}
49
50    # define these for drag-n-drop support of items
51    protected method dd_get_source {widget x y}
52    protected method dd_scan_target {x y data}
53    protected method dd_finalize {option args}
54
55    private variable _dispatcher ""  ;# dispatcher for !events
56    private variable _tree ""        ;# BLT tree object for node data
57    private variable _current ""     ;# current selected node
58    private variable _droppos ""     ;# node index for dragdrop target pos
59    private variable _imh            ;# open/close images
60}
61
62itk::usual Hierlist {
63    keep -cursor -font -foreground -background
64}
65
66# ----------------------------------------------------------------------
67# CONSTRUCTOR
68# ----------------------------------------------------------------------
69itcl::body Rappture::Hierlist::constructor {args} {
70    itk_option add hull.width hull.height
71    pack propagate $itk_component(hull) off
72
73    # create a dispatcher for events
74    Rappture::dispatcher _dispatcher
75    $_dispatcher register !redraw
76    $_dispatcher dispatch $this !redraw [itcl::code $this _redraw]
77
78    # create a root node
79    set _tree [blt::tree create]
80    $_tree set 0 terminal yes  ;# so you can't drop at the top level
81
82    itk_component add area {
83        canvas $itk_interior.area -highlightthickness 0
84    } {
85        usual
86        ignore -borderwidth -relief
87        ignore -highlightthickness -highlightbackground -highlightcolor
88        keep -xscrollcommand -yscrollcommand
89    }
90    pack $itk_component(area) -expand yes -fill both
91
92    # this widget exports nodes via drag-n-drop
93    dragdrop source $itk_component(area)
94
95    set _imh(open) [image create picture]
96    set _imh(close) [image create picture]
97
98    eval itk_initialize $args
99}
100
101# ----------------------------------------------------------------------
102# DESTRUCTOR
103# ----------------------------------------------------------------------
104itcl::body Rappture::Hierlist::destructor {} {
105    foreach name [array names _imh] {
106        image delete $_imh($name)
107    }
108    blt::tree destroy $_tree
109}
110
111# ----------------------------------------------------------------------
112# USAGE: tree insert <node> <pos> ?<field1> <val1> <field2> <val2> ...?
113# USAGE: tree delete ?<node> <node> ...?
114# USAGE: tree get <node> <field>
115# USAGE: tree set <node> <field> <value>
116# USAGE: tree path <node> ?<pattern>?
117# USAGE: tree children <node>
118#
119# Clients use this to manipulate items in the tree being displayed
120# within the widget.  Each <node> is an integer node number in the
121# underlying BLT tree object.  The root node is node 0.  Each node
122# has a series of <field>'s with values.  The fields are used for the
123# -title of each node, and the "terminal" field controls whether or
124# not a node can have children added.
125# ----------------------------------------------------------------------
126itcl::body Rappture::Hierlist::tree {option args} {
127    switch -- $option {
128        insert {
129            set node [lindex $args 0]
130            set pos [lindex $args 1]
131            if {$pos == "end"} {set pos [llength [$_tree children $node]]}
132            set args [lrange $args 2 end]
133            set newnode [$_tree insert $node -at $pos -data [eval list open yes terminal yes dragdrop yes $args]]
134
135            # make sure we fix up the layout at some point
136            $_dispatcher event -idle !redraw
137
138            return $newnode
139        }
140        delete {
141            if {$args == "all"} {
142                $_tree delete 0
143            } else {
144                foreach node $args {
145                    $_tree delete $node
146                }
147            }
148
149            # make sure we fix up the layout at some point
150            $_dispatcher event -idle !redraw
151        }
152        get {
153            return [eval $_tree get $args]
154        }
155        set {
156            # changed an option -- may be %type or %id -- update display
157            $_dispatcher event -idle !redraw
158
159            return [eval $_tree set $args]
160        }
161        path {
162            if {[llength $args] < 1 || [llength $args] > 2} {
163                error "wrong # args: should be \"tree path node ?pattern?\""
164            }
165            set node [lindex $args 0]
166            set pattern [lindex $args 1]
167            if {$pattern == ""} { set pattern %id }
168
169            set path ""
170            while {$node > 0} {
171                catch {unset info}
172                foreach {name val} [$_tree get $node] {
173                    set info(%$name) $val
174                    set info(%lc:$name) [string tolower $val]
175                    set info(%uc:$name) [string toupper $val]
176                }
177                set name [string map [array get info] $pattern]
178                set path "$name.$path"
179                set node [$_tree parent $node]
180            }
181            return [string trimright $path .]
182        }
183        children {
184            set node [lindex $args 0]
185            return [$_tree children $node]
186        }
187        default {
188            error "bad option \"$option\": should be insert, delete, get, set, path, children"
189        }
190    }
191}
192
193# ----------------------------------------------------------------------
194# USAGE: select <what> ?-notify|-silent?
195#
196# Called when you click on an item to select it.  Highlights the item
197# and returns information about it for the curselection.  This normally
198# triggers a <<Selection>> event on the widget, but that is suppressed
199# if the -silent option is given.
200# ----------------------------------------------------------------------
201itcl::body Rappture::Hierlist::select {what {how "-notify"}} {
202    set c $itk_component(area)
203    if {$what == "none"} {
204        set what ""
205    }
206    set same [string equal $what $_current]
207
208    if {"" != $_current} {
209        # clear any current selection
210        $c itemconfigure item:$_current-bg -fill ""
211        set _current ""
212    }
213
214    if {"" != $what} {
215        $c itemconfigure item:$what-bg -fill $itk_option(-selectbackground)
216        set _current $what
217    }
218
219    if {!$same && ![string equal $how "-silent"]} {
220        # selection changed? then tell clients
221        event generate $itk_component(hull) <<Selection>>
222    }
223}
224
225# ----------------------------------------------------------------------
226# USAGE: curselection
227# USAGE: curselection -path <string>
228# USAGE: curselection -field <name>
229#
230# Returns information about the current selection.  Returns "" if
231# there is no selection.  If "-field <name>" is specified, then it
232# returns the value of that field for the current selection.  If
233# the "-path <string>" is specified, then it returns the path to
234# the current node, using <string> as a pattern for each node name
235# and filling in any %field items for each node.  With no extra args,
236# it returns the node number from the underlying BLT tree.
237# ----------------------------------------------------------------------
238itcl::body Rappture::Hierlist::curselection {args} {
239    if {"" == $_current} {
240        return ""
241    }
242
243    if {[llength $args] == 0} {
244        return $_current
245
246    } elseif {[llength $args] == 2} {
247        switch -- [lindex $args 0] {
248            -path {
249                set pattern [lindex $args 1]
250                return [tree path $_current $pattern]
251            }
252            -field {
253                return [$_tree get $_current [lindex $args 1]]
254            }
255            default {
256                error "bad option \"[lindex $args 0]\": should be -field, -path"
257            }
258        }
259    } else {
260        error "wrong # args: should be \"curselection ?-path str? ?-field name?\""
261    }
262}
263
264# ----------------------------------------------------------------------
265# USAGE: toggle <what>
266#
267# Called when you click on an item to select it.  Highlights the item
268# and returns information about it for the curselection.
269# ----------------------------------------------------------------------
270itcl::body Rappture::Hierlist::toggle {what} {
271    set flipped [expr {![$_tree get $what open]}]
272    $_tree set $what open $flipped
273    $_dispatcher event -idle !redraw
274}
275
276# ----------------------------------------------------------------------
277# USAGE: _redraw ?<eventArgs>...?
278#
279# Used internally to redraw all items in the hierarchy after the
280# tree has changed somehow.
281# ----------------------------------------------------------------------
282itcl::body Rappture::Hierlist::_redraw {args} {
283    set c $itk_component(area)
284    $c delete all
285
286    set lineht [font metrics $itk_option(-font) -linespace]
287    set ypos [expr {0.5*$lineht + $itk_option(-padding)}]
288    _redrawChildren 0 0 ypos
289
290    foreach {x0 y0 x1 y1} [$c bbox text] break
291    $c configure -scrollregion [list 0 0 [expr {$x1+10}] [expr {$y1+10}]]
292
293    # did a selected node get folded up when the parent closed?
294    if {"" == [$c find withtag selected]} {
295        select none
296    }
297}
298
299# ----------------------------------------------------------------------
300# USAGE: _redrawChildren <node> <indent> <yposVar>
301#
302# Used internally to redraw all items in the hierarchy after the
303# tree has changed somehow.
304# ----------------------------------------------------------------------
305itcl::body Rappture::Hierlist::_redrawChildren {node indent yposVar} {
306    set c $itk_component(area)
307    upvar $yposVar ypos
308
309    set xmid [expr {$indent + $itk_option(-indent)/2}]
310    set xtxt [expr {$indent + $itk_option(-indent)}]
311    set lineht [font metrics $itk_option(-font) -linespace]
312
313    foreach n [$_tree children $node] {
314        # background rectangle for selection highlight
315        set tlist [list item:$n item:$n-bg]
316        if {[string equal $n $_current]} {
317            set bg $itk_option(-selectbackground)
318            lappend tlist selected
319        } else {
320            set bg ""
321        }
322        $c create rect 0 [expr {$ypos-0.5*$lineht-1}] 1000 [expr {$ypos+0.5*$lineht+1}] -outline "" -fill $bg -tags $tlist
323
324        $c bind item:$n <ButtonRelease> [itcl::code $this select $n]
325
326        # +/- button for expanding the hierarchy
327        if {![$_tree get $n terminal]} {
328            if {[$_tree get $n open]} {
329                set imh $_imh(open)
330            } else {
331                set imh $_imh(close)
332            }
333            $c create image $xmid $ypos -anchor c -image $imh -tags plus:$n
334            $c bind plus:$n <ButtonPress> [itcl::code $this toggle $n]
335        }
336
337        # label for this node
338        catch {unset data}
339        array set data [$_tree get $n]
340        set xpos $xtxt
341
342        set subs(%type) ""
343        set subs(%id) ""
344        foreach {name val} [$_tree get $n] {
345            set subs(%$name) $val
346            set subs(%lc:$name) [string tolower $val]
347            set subs(%uc:$name) [string toupper $val]
348        }
349
350        set str [string map [array get subs] $itk_option(-title)]
351        $c create text $xpos $ypos -anchor w -text $str -font $itk_option(-font) -tags [list text item:$n item:$n-text]
352
353        set ypos [expr {$ypos+$lineht+$itk_option(-padding)}]
354
355        # indent and draw all children
356        if {![$_tree get $n terminal] && [$_tree get $n open]} {
357            # if this node has children, draw them here
358            _redrawChildren $n $xtxt ypos
359        }
360    }
361}
362
363# ----------------------------------------------------------------------
364# USAGE: dd_get_source <widget> <x> <y>
365#
366# Looks at the given <widget> and <x>,<y> coordinate to figure out
367# what data value the source is exporting.  Returns a string that
368# identifies the type of the data.  This string is passed along to
369# targets via the dd_scan_target method.  If the target may check
370# the source type and reject the data.
371# ----------------------------------------------------------------------
372itcl::body Rappture::Hierlist::dd_get_source {widget x y} {
373    set data ""
374    foreach id [$itk_component(area) find overlapping $x $y $x $y] {
375        foreach tag [$itk_component(area) gettags $id] {
376            # search for a tag like item:NNN and make sure it's selected
377            if {[regexp {^item:([0-9]+)$} $tag match node]
378                  && $node == $_current} {
379
380                # some nodes have drag-n-drop turned off
381                if {[$_tree get $node dragdrop]} {
382                    set data [list node:$node [$_tree get $node]]
383                }
384                break
385            }
386        }
387    }
388    # return drag-n-drop data, if we found it
389    return $data
390}
391
392# ----------------------------------------------------------------------
393# USAGE: dd_scan_target <x> <y> <data>
394#
395# Looks at the given <x>,<y> coordinate and checks to see if the
396# dragdrop <data> can be accepted at that point.  Returns 1 if so,
397# and 0 if the data is rejected.
398# ----------------------------------------------------------------------
399itcl::body Rappture::Hierlist::dd_scan_target {x y data} {
400    set c $itk_component(area)
401    set _droppos ""  ;# assume no place to drop
402
403    switch -glob -- $data {
404        node:* {
405            # convert from screen coords to canvas coords (for scrollbars)
406            set x [$c canvasx $x]
407            set y [$c canvasy $y]
408
409            # search a little above the hot-spot
410            set y [expr {$y-5}]
411
412            foreach id [$c find overlapping $x $y $x $y] {
413                foreach tag [$c gettags $id] {
414                    if {[regexp {^item:([0-9]+)$} $tag match node]} {
415                        # draw the dropline beneath the item found
416                        if {"" == [$c find withtag dropline]} {
417                            $c create line 0 0 0 0 \
418                                -fill $itk_option(-droplinecolor) -tags dropline
419                        }
420
421                        foreach {x0 y0 x1 y1} [$c bbox item:$node-bg] break
422                        if {![$_tree get $node terminal]} {
423                            # drop on a node with children
424                            if {$y > $y0 + 0.6*($y1-$y0)} {
425                                # drop near the bottom -- insert as first child
426                                set _droppos "$node -at 0"
427                                set d [$_tree depth $node]
428                                set x0 [expr {($d+1)*$itk_option(-indent)}]
429                                $c coords dropline $x0 $y1 $x1 $y1
430                            } else {
431                                # drop on top -- insert as last child
432                                set x0 [expr {$x0+1}]
433                                set x1 [expr {[winfo width $c]-1}]
434                                $c coords dropline $x0 $y0 $x1 $y0 $x1 $y1 $x0 $y1 $x0 $y0
435                                set last [llength [$_tree children $node]]
436                                set _droppos "$node -at $last"
437                            }
438                        } else {
439                            set pnode [$_tree parent $node]
440                            if {![$_tree get $pnode terminal]} {
441                                set pos [expr {[$_tree position $node]+1}]
442                                set _droppos "$pnode -at $pos"
443                                set d [$_tree depth $node]
444                                set x0 [expr {$d*$itk_option(-indent)}]
445                                $c coords dropline $x0 $y1 $x1 $y1
446                            }
447                        }
448
449                        break
450                    }
451                }
452            }
453            if {"" != $_droppos} {
454                return 1
455            }
456            $c coords dropline -1 -1 -1 -1
457            return 0
458        }
459    }
460    return 0
461}
462
463# ----------------------------------------------------------------------
464# USAGE: dd_finalize drop -op start|end -from <w> -to <w> \
465#                           -x <x> -y <y> -data <data>
466# USAGE: dd_finalize cancel
467#
468# Handles the end of a drag and drop operation.  The operation can be
469# completed with a successful drop of data, or cancelled.
470# ----------------------------------------------------------------------
471itcl::body Rappture::Hierlist::dd_finalize {option args} {
472    $itk_component(area) delete dropline
473
474    if {$option == "drop" && "" != $_droppos} {
475        array set params $args
476        switch -glob -- $params(-data) {
477            node:* {
478                if {$params(-op) == "end"
479                      && [string equal $params(-from) $params(-to)]} {
480
481                    regexp {node:([0-9]+)} $params(-data) match node
482                    eval $_tree move $node $_droppos
483                    event generate $itk_component(hull) <<SelectionPath>>
484                } else {
485                    set dlist [list open yes terminal yes dragdrop yes]
486                    eval lappend dlist [lrange $params(-data) 1 end]
487                    eval $_tree insert $_droppos -data [list $dlist]
488                }
489                # make sure the parent is open so we can see this node
490                set parent [lindex $_droppos 0]
491                if {![$_tree get $parent open]} {
492                    $_tree set $parent open 1
493                }
494
495                $_dispatcher event -idle !redraw
496                return 1
497            }
498        }
499        return 0
500    }
501}
502
503# ----------------------------------------------------------------------
504# CONFIGURATION OPTION: -icon
505# ----------------------------------------------------------------------
506itcl::configbody Rappture::Hierlist::icon {
507    set imh0 [lindex $itk_option(-icon) 0]
508    set imh1 [lindex $itk_option(-icon) 1]
509    if {$imh1 == ""} { set imh1 $imh0 }
510
511    set imh0 [Rappture::icon $imh0]
512    if {$imh0 == ""} {
513        $_imh(open) configure -width 1 -height 1
514        $_imh(open) blank
515    } else {
516        $_imh(open) configure -width [image width $imh0] -height [image height $imh0]
517        $_imh(open) copy $imh0
518    }
519
520    set imh1 [Rappture::icon $imh1]
521    if {$imh1 == ""} {
522        $_imh(close) configure -width 1 -height 1
523        $_imh(close) blank
524    } else {
525        $_imh(close) configure -width [image width $imh1] -height [image height $imh1]
526        $_imh(close) copy $imh1
527    }
528}
529
530# ----------------------------------------------------------------------
531# CONFIGURATION OPTION: -selectbackground
532# ----------------------------------------------------------------------
533itcl::configbody Rappture::Hierlist::selectbackground {
534    $itk_component(area) itemconfigure select \
535        -background $itk_option(-selectbackground)
536}
Note: See TracBrowser for help on using the repository browser.