# # SelectBox # ---------------------------------------------------------------------- # Implements a selection box widget using primitive widgets as the # building blocks. A selection box widget displays a list of items # and allows the user to scroll through the list and select single # or multiple items. This class is derived from ListBox, and so # it inherits the basic listbox display behavior. # # PUBLIC ATTRIBUTES: # # -mode .......... single/multi selection # -action ........ callback invoked whenever entry is selected/unselected # # -list .......... list of items to be displayed # -width ......... width of displayed list in characters or "expand" # -height ........ height of displayed list in lines or "expand" # # METHODS: # # config ....... used to change public attributes # get .......... returns "all" or "selected" list # select ....... select/unselect entries programmatically # # X11 OPTION DATABASE ATTRIBUTES # # selectBackground ... background color for selected entries # selectForeground ... foreground color for selected entries # # listBackground ..... background color for entries # listForeground ..... foreground color for entries # # ...and the rest of the usual widget attributes # # ---------------------------------------------------------------------- # AUTHOR: Michael J. McLennan Phone: (610)712-2842 # AT&T Bell Laboratories E-mail: michael.mclennan@att.com # # RCS: SelectBox.tcl,v 1.3 1994/04/08 13:39:21 mmc Exp # ---------------------------------------------------------------------- # Copyright (c) 1993 AT&T Bell Laboratories # ====================================================================== # Permission to use, copy, modify, and distribute this software and its # documentation for any purpose and without fee is hereby granted, # provided that the above copyright notice appear in all copies and that # both that the copyright notice and warranty disclaimer appear in # supporting documentation, and that the names of AT&T Bell Laboratories # any of their entities not be used in advertising or publicity # pertaining to distribution of the software without specific, written # prior permission. # # AT&T disclaims all warranties with regard to this software, including # all implied warranties of merchantability and fitness. In no event # shall AT&T be liable for any special, indirect or consequential # damages or any damages whatsoever resulting from loss of use, data or # profits, whether in an action of contract, negligence or other # tortuous action, arising out of or in connection with the use or # performance of this software. # ====================================================================== itcl_class SelectBox { inherit ListBox # ------------------------------------------------------------------ # CONSTRUCTOR - create new selectbox # ------------------------------------------------------------------ constructor {config} { ListBox::constructor set normalbg [option get $this listBackground SelectBox] if {$normalbg == ""} {set normalbg white} set normalfg [option get $this listForeground SelectBox] if {$normalfg == ""} {set normalfg black} $this.list config -bg $normalbg -fg $normalfg set selectfg [option get $this selectForeground SelectBox] set selectbg [option get $this selectBackground SelectBox] switch [tk colormodel $this] { monochrome { if {$selectbg == ""} {set selectbg black} if {$selectfg == ""} {set selectfg white} } color { if {$selectbg == ""} {set selectbg LightSteelBlue} if {$selectfg == ""} {set selectfg black} } } set focusbg [option get $this focusBackground SelectBox] if {$focusbg == ""} {set focusbg $normalfg} set focusfg [option get $this focusForeground SelectBox] if {$focusfg == ""} {set focusfg $normalbg} bind $this.list <1> "$this _clickStart \[%W index @%x,%y\]" bind $this.list { } bind $this.list { } bind $this.list { } bind $this.list { } bind $this.list { } _resetArray active config -mode $mode # # Explicitly handle config's that may have been ignored earlier # foreach attr $config { config -$attr [set $attr] } } # ------------------------------------------------------------------ # METHOD: config - used to change public attributes # ------------------------------------------------------------------ method config {config} {} # ------------------------------------------------------------------ # DESTRUCTOR - destroy window containing widget # ------------------------------------------------------------------ # Nothing to do--ListBox destructor will clean up destructor { } # ------------------------------------------------------------------ # METHOD: get - returns "all" or "selected" list # ------------------------------------------------------------------ method get {{what all}} { switch $what { all { return $list } selected { set selns {} foreach tag [array names active] { lappend selns $active($tag) } return [lsort $selns] } default { error "invalid arg \"$what\": should be all or selected" } } } # ------------------------------------------------------------------ # METHOD: select - public access for highlighting entries # USAGE: select all # select reset # select entry label state # ------------------------------------------------------------------ method select {how args} { switch $how { reset { foreach tag [array names active] { _deactivate $tag } } all { foreach item $list { _activate $tags($item) } } entry { if {[llength $args] != 2} { error "improper usage: should be \"select entry label state\"" } set label [lindex $args 0] set state [lindex $args 1] if {[info exists tags($label)]} { set tag $tags($label) switch $state { on { if {$mode == "single"} { foreach old [array names active] { if {$old != $tag} { _deactivate $old } } } _activate $tag } off { _deactivate $tag } default { error "improper state: should be \"on\" or \"off\"" } } } else { error "entry not found in SelectBox $this: $label" } } default { error "wrong # args: should be \"select type ?args?\"" } } } # ------------------------------------------------------------------ # METHOD: _hilite - temporary highlight when pointer is over entry # ------------------------------------------------------------------ method _hilite {tag state} { switch $state { on { $this.list tag config $tag \ -background $focusbg -foreground $focusfg } off { if {[info exists active($tag)]} { $this.list tag config $tag -relief raised \ -background $selectbg -foreground $selectfg } else { $this.list tag config $tag -relief flat \ -background $normalbg -foreground $normalfg } } } } # ------------------------------------------------------------------ # METHOD: _activate - add item to "active" list # ------------------------------------------------------------------ method _activate {tag} { if {$tag != ""} { if {![info exists active($tag)]} { set active($tag) $items($tag) } $this.list tag config $tag -relief raised \ -background $selectbg -foreground $selectfg if {$action != ""} { eval $action [list $items($tag)] on } } } # ------------------------------------------------------------------ # METHOD: _deactivate - remove item from "active" list # ------------------------------------------------------------------ method _deactivate {tag} { if {$tag != ""} { $this.list tag config $tag -relief flat \ -background $normalbg -foreground $normalfg if {[info exists active($tag)]} { unset active($tag) if {$action != ""} { eval $action [list $items($tag)] off } } } } # ------------------------------------------------------------------ # METHOD: _clickStart - invoked for button-press selection # ------------------------------------------------------------------ method _clickStart {current} { $this.list mark set selstart current $this.list mark set sellast current set tag [$this.list tag names $current] if {$mode == "single"} { foreach old [array names active] { if {$old != $tag} { _deactivate $old } } } else { set snapshot [array names active] } if {[info exists active($tag)]} { set sweep unselect _deactivate $tag } else { set sweep select _activate $tag } } # ------------------------------------------------------------------ # METHOD: _clickSweep - invoked for button-press movement # ------------------------------------------------------------------ method _clickSweep {current} { set start [$this.list index selstart] set dist [expr abs($current-$start)] set last [expr abs([$this.list index sellast]-$start)] set op $sweep if {$dist < $last} { set op restore } set cline [lindex [split $current "."] 0] set lline [lindex [split [$this.list index sellast] "."] 0] if {$lline < $cline} { set inc 1 } else { set inc -1 } for {set line $lline} {$line != [expr $cline+$inc]} {incr line $inc} { set tag [$this.list tag names $line.0] switch $op { select { _activate $tag } unselect { _deactivate $tag } restore { if {[lsearch $snapshot $tag] >= 0} { _activate $tag } else { _deactivate $tag } } } } _hilite $current on $this.list mark set sellast $current } # ------------------------------------------------------------------ # METHOD: _resetArray - clear array to empty state # ------------------------------------------------------------------ method _resetArray {name} { catch "unset $name" set ${name}(0) "make-this-an-array" unset ${name}(0) } # # PUBLIC DATA # public list {} { if {[winfo exists $this]} { foreach tag [array names active] { if {$action != ""} { eval $action [list $active($tag)] off } } config -ListBox::list $list _resetArray active foreach item [array names tags] { set tag $tags($item) $this.list tag bind $tag "$this _hilite $tag on" $this.list tag bind $tag "$this _hilite $tag off" } } } public mode multi { if {[winfo exists $this]} { set skip 1 if {$mode == "single"} { foreach tag [array names active] { if {!$skip} { _deactivate $tag } set skip 0 } bind $this.list { } } else { bind $this.list \ "$this _clickSweep \[%W index @%x,%y\]" } } } public action {} # # PROTECTED DATA # active ......... array of tags for selected entries # snapshot ....... snapshot of "active" list used during click-drag # sweep .......... select/unselect type of click-drag sweep # # selectbg ....... background color for selected entries # selectfg ....... foreground color for selected entries # focusbg ........ background color when pointer is over entry # focusfg ........ foreground color when pointer is over entry # protected active protected snapshot {} protected sweep {} protected selectbg {} protected selectfg {} protected focusbg {} protected focusfg {} }