source: trunk/gui/scripts/dropdownchecks.tcl @ 5157

Last change on this file since 5157 was 3330, checked in by gah, 11 years ago

merge (by hand) with Rappture1.2 branch

File size: 13.9 KB
Line 
1# -*- mode: tcl; indent-tabs-mode: nil -*-
2# ----------------------------------------------------------------------
3#  COMPONENT: dropdownchecks - drop-down list of checkbox items
4#
5#  This is the drop-down for the Combochecks widget, which maintains
6#  a list of options.
7#
8# ======================================================================
9#  AUTHOR:  Michael McLennan, Purdue University
10#  Copyright (c) 2004-2012  HUBzero Foundation, LLC
11#
12#  See the file "license.terms" for information on usage and
13#  redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES.
14# ======================================================================
15package require Itk
16
17option add *Dropdownchecks.textBackground white widgetDefault
18option add *Dropdownchecks.foreground black widgetDefault
19option add *Dropdownchecks.outline black widgetDefault
20option add *Dropdownchecks.borderwidth 1 widgetDefault
21option add *Dropdownchecks.relief flat widgetDefault
22option add *Dropdownchecks.font \
23    -*-helvetica-medium-r-normal-*-12-* widgetDefault
24
25itcl::class Rappture::Dropdownchecks {
26    inherit Rappture::Dropdown
27
28    itk_option define -font font Font ""
29    itk_option define -foreground foreground Foreground ""
30
31    constructor {args} { # defined below }
32
33    public method insert {pos args}
34    public method delete {first {last ""}}
35    public method index {args}
36    public method get {args}
37    public method size {}
38    public method state {value {newval ""}}
39    public method reset {}
40
41    private variable _dispatcher "" ;# dispatcher for !events
42    private variable _values ""     ;# values for all elems in list
43    private variable _labels ""     ;# labels for each of the _values
44    private variable _states        ;# maps list item => on/off state
45    private variable _layout        ;# layout parameters for drawing
46
47    protected method _redraw {args}
48    protected method _layout {args}
49    protected method _adjust {{widget ""}}
50    protected method _react {x y}
51}
52
53itk::usual Dropdownchecks {
54    keep -background -outline -cursor -font
55}
56
57# ----------------------------------------------------------------------
58# CONSTRUCTOR
59# ----------------------------------------------------------------------
60itcl::body Rappture::Dropdownchecks::constructor {args} {
61    Rappture::dispatcher _dispatcher
62    $_dispatcher register !redraw
63    $_dispatcher dispatch $this !redraw [itcl::code $this _redraw]
64    $_dispatcher register !layout
65    $_dispatcher dispatch $this !layout [itcl::code $this _layout]
66
67    itk_component add scroller {
68        Rappture::Scroller $itk_interior.sc \
69            -xscrollmode off -yscrollmode auto
70    }
71    pack $itk_component(scroller) -expand yes -fill both
72
73    itk_component add list {
74        canvas $itk_component(scroller).list \
75            -highlightthickness 0 -width 3i -height 1.5i
76    } {
77        usual
78        rename -background -textbackground textBackground Background
79        ignore -highlightthickness -highlightbackground -highlightcolor
80        keep -relief
81    }
82    $itk_component(scroller) contents $itk_component(list)
83
84    # add bindings so the listbox can react to selections
85    bind RapptureDropdownchecks-$this <ButtonRelease-1> \
86        [itcl::code $this _react %x %y]
87    bind RapptureDropdownchecks-$this <KeyPress-Escape> \
88        [itcl::code $this unpost]
89
90    set btags [bindtags $itk_component(list)]
91    set i [lsearch $btags [winfo class $itk_component(list)]]
92    set btags [lreplace $btags $i $i RapptureDropdownchecks-$this]
93    bindtags $itk_component(list) $btags
94
95    eval itk_initialize $args
96
97    $_dispatcher event -idle !layout
98}
99
100# ----------------------------------------------------------------------
101# USAGE: insert <pos> ?<value1> <label1> <value2> <label2> ...?
102#
103# Inserts one or more values into this drop-down list.  Each value
104# has a keyword (computer-friendly value) and a label (human-friendly
105# value).  The labels appear in the listbox.  If the label is "--",
106# then the value is used as the label.
107# ----------------------------------------------------------------------
108itcl::body Rappture::Dropdownchecks::insert {pos args} {
109    if {"end" == $pos} {
110        set pos [llength $_values]
111    } elseif {![string is integer -strict $pos]} {
112        error "bad index \"$pos\": should be integer or \"end\""
113    }
114
115    if {[llength $args] == 1} {
116        set args [lindex $args 0]
117    }
118    if {[llength $args] % 2 != 0} {
119        error "wrong # args: should be \"insert pos ?value label ...?\""
120    }
121
122    foreach {val label} $args {
123        if {$label == "--"} {
124            set label $val
125        }
126        set _values [linsert $_values $pos $val]
127        set _labels [linsert $_labels $pos $label]
128        set _states($val) 0
129        incr pos
130    }
131    $_dispatcher event -idle !redraw
132}
133
134# ----------------------------------------------------------------------
135# USAGE: delete <first> ?<last>?
136#
137# Deletes one or more values from this drop-down list.  The indices
138# <first> and <last> should be integers from 0 or the keyword "end".
139# All values in the specified range are deleted.
140# ----------------------------------------------------------------------
141itcl::body Rappture::Dropdownchecks::delete {first {last ""}} {
142    if {$last == ""} {
143        set last $first
144    }
145    if {![regexp {^[0-9]+|end$} $first]} {
146        error "bad index \"$first\": should be integer or \"end\""
147    }
148    if {![regexp {^[0-9]+|end$} $last]} {
149        error "bad index \"$last\": should be integer or \"end\""
150    }
151
152    foreach val [lrange $_values $first $last] {
153        unset _states($val)
154    }
155    set _values [lreplace $_values $first $last]
156    set _labels [lreplace $_labels $first $last]
157
158    $_dispatcher event -idle !redraw
159}
160
161# ----------------------------------------------------------------------
162# USAGE: index ?-value|-label? <value>
163#
164# Returns the integer index for the position of the specified <value>
165# in the list.  Returns -1 if the value is not recognized.
166# ----------------------------------------------------------------------
167itcl::body Rappture::Dropdownchecks::index {args} {
168    set format -value
169    set first [lindex $args 0]
170    if {$first == "-value" || $first == "-label"} {
171        set format $first
172        set args [lrange $args 1 end]
173    } elseif {[llength $args] > 1} {
174        error "bad option \"$first\": should be -value or -label"
175    }
176    if {[llength $args] != 1} {
177        error "wrong # args: should be \"index ?-value? ?-label? string\""
178    }
179    set value [lindex $args 0]
180
181    switch -- $format {
182        -value { return [lsearch -exact $_values $value] }
183        -label { return [lsearch -exact $_labels $value] }
184    }
185    return -1
186}
187
188# ----------------------------------------------------------------------
189# USAGE: get ?-value|-label|-both? ?<index>?
190#
191# Queries one or more values from the drop-down list.  With no args,
192# it returns a list of all values and labels in the list.  If the
193# index is specified, then it returns the value or label (or both)
194# for the specified index.
195# ----------------------------------------------------------------------
196itcl::body Rappture::Dropdownchecks::get {args} {
197    set format -both
198    set first [lindex $args 0]
199    if {[string index $first 0] == "-"} {
200        set choices {-value -label -both}
201        if {[lsearch $choices $first] < 0} {
202            error "bad option \"$first\": should be [join [lsort $choices] {, }]"
203        }
204        set format $first
205        set args [lrange $args 1 end]
206    }
207
208    # return the whole list or just a single value
209    if {[llength $args] > 1} {
210        error "wrong # args: should be \"get ?-value|-label|-both? ?index?\""
211    }
212    if {[llength $args] == 0} {
213        set vlist $_values
214        set llist $_labels
215        set op lappend
216    } else {
217        set i [lindex $args 0]
218        set vlist [list [lindex $_values $i]]
219        set llist [list [lindex $_labels $i]]
220        set op set
221    }
222
223    # scan through and build up the return list
224    set rlist ""
225    foreach v $vlist l $llist {
226        switch -- $format {
227            -value { $op rlist $v }
228            -label { $op rlist $l }
229            -both  { lappend rlist $v $l }
230        }
231    }
232    return $rlist
233}
234
235# ----------------------------------------------------------------------
236# USAGE: size
237#
238# Returns the number of choices in this list.
239# ----------------------------------------------------------------------
240itcl::body Rappture::Dropdownchecks::size {} {
241    return [llength $_values]
242}
243
244# ----------------------------------------------------------------------
245# USAGE: state <value> ?on|off?
246#
247# Used to query or set the state for the underlying values of the
248# listbox.  The <value> is the value associated with a label option.
249# With no other arg, it returns the current state for that value.
250# Otherwise, it sets the state for that value.
251# ----------------------------------------------------------------------
252itcl::body Rappture::Dropdownchecks::state {val {newval ""}} {
253    if {$newval eq ""} {
254        if {[info exists _states($val)]} {
255            return $_states($val)
256        }
257        return ""
258    }
259
260    if {[info exists _states($val)]} {
261        if {$newval} {
262            set img [Rappture::icon cbon]
263        } else {
264            set img [Rappture::icon cboff]
265        }
266        $itk_component(list) itemconfigure box-$val -image $img
267        set _states($val) $newval
268    } else {
269        error "bad value \"$val\": should be one of [join $_values {, }]"
270    }
271}
272
273# ----------------------------------------------------------------------
274# USAGE: reset
275#
276# Resets the state of all checkboxes back to 0 (unchecked).
277# ----------------------------------------------------------------------
278itcl::body Rappture::Dropdownchecks::reset {} {
279    foreach val $_values {
280        set _states($val) 0
281    }
282}
283
284# ----------------------------------------------------------------------
285# USAGE: _layout ?<eventData>?
286#
287# Used internally to recompute layout parameters whenever the font
288# changes.
289# ----------------------------------------------------------------------
290itcl::body Rappture::Dropdownchecks::_layout {args} {
291    # figure out the sizes of the checkboxes
292    set wmax 0
293    set hmax 0
294    foreach icon {cbon cboff} {
295        set img [Rappture::icon $icon]
296        set w [image width $img]
297        if {$w > $wmax} {set wmax $w}
298        set h [image height $img]
299        if {$h > $hmax} {set hmax $h}
300    }
301    incr wmax 2
302
303    set fnt $itk_option(-font)
304    set lineh [expr {[font metrics $fnt -linespace]+2}]
305
306    if {$hmax > $lineh} {
307        set lineh $hmax
308    }
309
310    set _layout(boxwidth) $wmax
311    set _layout(lineh) $lineh
312}
313
314# ----------------------------------------------------------------------
315# USAGE: _redraw ?<eventData>?
316#
317# Used internally to redraw the items in the list whenever new items
318# are added or removed.
319# ----------------------------------------------------------------------
320itcl::body Rappture::Dropdownchecks::_redraw {args} {
321    set c $itk_component(list)
322    $c delete all
323
324    set fg $itk_option(-foreground)
325
326    if {[$_dispatcher ispending !layout]} {
327        $_dispatcher event -now !layout
328    }
329
330    set y 0
331    set _layout(ypos) ""
332    for {set i 0} {$i < [llength $_values]} {incr i} {
333        set x0 2
334        set val [lindex $_values $i]
335        set label [lindex $_labels $i]
336
337        if {$_states($val)} {
338            set img [Rappture::icon cbon]
339        } else {
340            set img [Rappture::icon cboff]
341        }
342
343        set ymid [expr {$y + $_layout(lineh)/2}]
344        $c create image $x0 $ymid -anchor w -image $img -tags box-$val
345        incr x0 $_layout(lineh)
346        $c create text $x0 $ymid -anchor w -text $label -fill $fg
347
348        lappend _layout(ypos) [incr y $_layout(lineh)]
349    }
350
351    # fix the overall size for scrolling
352    foreach {x0 y0 x1 y1} [$c bbox all] break
353    $c configure -scrollregion [list 0 0 $x1 $y1]
354}
355
356# ----------------------------------------------------------------------
357# USAGE: _adjust ?<widget>?
358#
359# This method is invoked each time the dropdown is posted to adjust
360# its size and contents.  If the <widget> is specified, it is the
361# controlling widget.
362# ----------------------------------------------------------------------
363itcl::body Rappture::Dropdownchecks::_adjust {{widget ""}} {
364    chain $widget
365
366    set c $itk_component(list)
367    set lineh $_layout(lineh)
368
369    foreach {x0 y0 x1 y1} [$c bbox all] break
370    set maxw [expr {$x1+2}]
371
372    set maxh [expr {10*$lineh + 2*[$c cget -borderwidth] + 2}]
373
374    if {$widget != ""} {
375        if {$maxw < [winfo width $widget]} { set maxw [winfo width $widget] }
376    }
377    $c configure -width $maxw -height $maxh
378
379    if {$widget != ""} {
380        set y [expr {[winfo rooty $widget]+[winfo height $widget]}]
381        set screenh [winfo screenheight $widget]
382        set lines [expr {round(floor(double($screenh-$y)/$lineh))}]
383        if {[llength $_labels] < $lines} {
384            set lines [llength $_labels]
385        }
386        set maxh [expr {[lindex $_layout(ypos) [expr {$lines-1}]]+2}]
387        $c configure -height $maxh
388    }
389
390    focus $c
391}
392
393# ----------------------------------------------------------------------
394# USAGE: _react <x> <y>
395#
396# Invoked automatically when the user has selected something from
397# the listbox.  Unposts the drop-down and generates an event letting
398# the client know that the selection has changed.
399# ----------------------------------------------------------------------
400itcl::body Rappture::Dropdownchecks::_react {x y} {
401    if {$x >= 0 && $x <= [winfo width $itk_component(hull)]
402          && $y >= 0 && $y <= [winfo height $itk_component(hull)]} {
403
404        set x [$itk_component(list) canvasx $x]
405        set y [$itk_component(list) canvasy $y]
406
407        for {set i 0} {$i < [llength $_values]} {incr i} {
408            if {$y < [lindex $_layout(ypos) $i]} {
409                set val [lindex $_values $i]
410                if {$_states($val)} {
411                    state $val 0
412                } else {
413                    state $val 1
414                }
415                break
416            }
417        }
418        event generate $itk_component(hull) <<DropdownchecksSelect>>
419    } else {
420        unpost
421    }
422}
Note: See TracBrowser for help on using the repository browser.