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

Last change on this file since 6372 was 6372, checked in by dkearney, 8 years ago

adding multichoice widget from the multichoice branch

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