| 1 |
2 |
olivier.girard |
# Copyright (c) 1998-2003, Bryan Oakley
|
| 2 |
|
|
# All Rights Reservered
|
| 3 |
|
|
#
|
| 4 |
|
|
# Bryan Oakley
|
| 5 |
|
|
# oakley@bardo.clearlight.com
|
| 6 |
|
|
#
|
| 7 |
|
|
# combobox v2.3 August 16, 2003
|
| 8 |
|
|
#
|
| 9 |
|
|
# a combobox / dropdown listbox (pick your favorite name) widget
|
| 10 |
|
|
# written in pure tcl
|
| 11 |
|
|
#
|
| 12 |
|
|
# this code is freely distributable without restriction, but is
|
| 13 |
|
|
# provided as-is with no warranty expressed or implied.
|
| 14 |
|
|
#
|
| 15 |
|
|
# thanks to the following people who provided beta test support or
|
| 16 |
|
|
# patches to the code (in no particular order):
|
| 17 |
|
|
#
|
| 18 |
|
|
# Scott Beasley Alexandre Ferrieux Todd Helfter
|
| 19 |
|
|
# Matt Gushee Laurent Duperval John Jackson
|
| 20 |
|
|
# Fred Rapp Christopher Nelson
|
| 21 |
|
|
# Eric Galluzzo Jean-Francois Moine Oliver Bienert
|
| 22 |
|
|
#
|
| 23 |
|
|
# A special thanks to Martin M. Hunt who provided several good ideas,
|
| 24 |
|
|
# and always with a patch to implement them. Jean-Francois Moine,
|
| 25 |
|
|
# Todd Helfter and John Jackson were also kind enough to send in some
|
| 26 |
|
|
# code patches.
|
| 27 |
|
|
#
|
| 28 |
|
|
# ... and many others over the years.
|
| 29 |
|
|
|
| 30 |
|
|
package require Tk 8.0
|
| 31 |
|
|
package provide combobox 2.3
|
| 32 |
|
|
|
| 33 |
|
|
namespace eval ::combobox {
|
| 34 |
|
|
|
| 35 |
|
|
# this is the public interface
|
| 36 |
|
|
namespace export combobox
|
| 37 |
|
|
|
| 38 |
|
|
# these contain references to available options
|
| 39 |
|
|
variable widgetOptions
|
| 40 |
|
|
|
| 41 |
|
|
# these contain references to available commands and subcommands
|
| 42 |
|
|
variable widgetCommands
|
| 43 |
|
|
variable scanCommands
|
| 44 |
|
|
variable listCommands
|
| 45 |
|
|
}
|
| 46 |
|
|
|
| 47 |
|
|
# ::combobox::combobox --
|
| 48 |
|
|
#
|
| 49 |
|
|
# This is the command that gets exported. It creates a new
|
| 50 |
|
|
# combobox widget.
|
| 51 |
|
|
#
|
| 52 |
|
|
# Arguments:
|
| 53 |
|
|
#
|
| 54 |
|
|
# w path of new widget to create
|
| 55 |
|
|
# args additional option/value pairs (eg: -background white, etc.)
|
| 56 |
|
|
#
|
| 57 |
|
|
# Results:
|
| 58 |
|
|
#
|
| 59 |
|
|
# It creates the widget and sets up all of the default bindings
|
| 60 |
|
|
#
|
| 61 |
|
|
# Returns:
|
| 62 |
|
|
#
|
| 63 |
|
|
# The name of the newly create widget
|
| 64 |
|
|
|
| 65 |
|
|
proc ::combobox::combobox {w args} {
|
| 66 |
|
|
variable widgetOptions
|
| 67 |
|
|
variable widgetCommands
|
| 68 |
|
|
variable scanCommands
|
| 69 |
|
|
variable listCommands
|
| 70 |
|
|
|
| 71 |
|
|
# perform a one time initialization
|
| 72 |
|
|
if {![info exists widgetOptions]} {
|
| 73 |
|
|
Init
|
| 74 |
|
|
}
|
| 75 |
|
|
|
| 76 |
|
|
# build it...
|
| 77 |
|
|
eval Build $w $args
|
| 78 |
|
|
|
| 79 |
|
|
# set some bindings...
|
| 80 |
|
|
SetBindings $w
|
| 81 |
|
|
|
| 82 |
|
|
# and we are done!
|
| 83 |
|
|
return $w
|
| 84 |
|
|
}
|
| 85 |
|
|
|
| 86 |
|
|
|
| 87 |
|
|
# ::combobox::Init --
|
| 88 |
|
|
#
|
| 89 |
|
|
# Initialize the namespace variables. This should only be called
|
| 90 |
|
|
# once, immediately prior to creating the first instance of the
|
| 91 |
|
|
# widget
|
| 92 |
|
|
#
|
| 93 |
|
|
# Arguments:
|
| 94 |
|
|
#
|
| 95 |
|
|
# none
|
| 96 |
|
|
#
|
| 97 |
|
|
# Results:
|
| 98 |
|
|
#
|
| 99 |
|
|
# All state variables are set to their default values; all of
|
| 100 |
|
|
# the option database entries will exist.
|
| 101 |
|
|
#
|
| 102 |
|
|
# Returns:
|
| 103 |
|
|
#
|
| 104 |
|
|
# empty string
|
| 105 |
|
|
|
| 106 |
|
|
proc ::combobox::Init {} {
|
| 107 |
|
|
variable widgetOptions
|
| 108 |
|
|
variable widgetCommands
|
| 109 |
|
|
variable scanCommands
|
| 110 |
|
|
variable listCommands
|
| 111 |
|
|
variable defaultEntryCursor
|
| 112 |
|
|
|
| 113 |
|
|
array set widgetOptions [list \
|
| 114 |
|
|
-background {background Background} \
|
| 115 |
|
|
-bd -borderwidth \
|
| 116 |
|
|
-bg -background \
|
| 117 |
|
|
-borderwidth {borderWidth BorderWidth} \
|
| 118 |
|
|
-buttonbackground {buttonBackground Background} \
|
| 119 |
|
|
-command {command Command} \
|
| 120 |
|
|
-commandstate {commandState State} \
|
| 121 |
|
|
-cursor {cursor Cursor} \
|
| 122 |
|
|
-disabledbackground {disabledBackground DisabledBackground} \
|
| 123 |
|
|
-disabledforeground {disabledForeground DisabledForeground} \
|
| 124 |
|
|
-dropdownwidth {dropdownWidth DropdownWidth} \
|
| 125 |
|
|
-editable {editable Editable} \
|
| 126 |
|
|
-elementborderwidth {elementBorderWidth BorderWidth} \
|
| 127 |
|
|
-fg -foreground \
|
| 128 |
|
|
-font {font Font} \
|
| 129 |
|
|
-foreground {foreground Foreground} \
|
| 130 |
|
|
-height {height Height} \
|
| 131 |
|
|
-highlightbackground {highlightBackground HighlightBackground} \
|
| 132 |
|
|
-highlightcolor {highlightColor HighlightColor} \
|
| 133 |
|
|
-highlightthickness {highlightThickness HighlightThickness} \
|
| 134 |
|
|
-image {image Image} \
|
| 135 |
|
|
-listvar {listVariable Variable} \
|
| 136 |
|
|
-maxheight {maxHeight Height} \
|
| 137 |
|
|
-opencommand {opencommand Command} \
|
| 138 |
|
|
-relief {relief Relief} \
|
| 139 |
|
|
-selectbackground {selectBackground Foreground} \
|
| 140 |
|
|
-selectborderwidth {selectBorderWidth BorderWidth} \
|
| 141 |
|
|
-selectforeground {selectForeground Background} \
|
| 142 |
|
|
-state {state State} \
|
| 143 |
|
|
-takefocus {takeFocus TakeFocus} \
|
| 144 |
|
|
-textvariable {textVariable Variable} \
|
| 145 |
|
|
-value {value Value} \
|
| 146 |
|
|
-width {width Width} \
|
| 147 |
|
|
-xscrollcommand {xScrollCommand ScrollCommand} \
|
| 148 |
|
|
]
|
| 149 |
|
|
|
| 150 |
|
|
|
| 151 |
|
|
set widgetCommands [list \
|
| 152 |
|
|
bbox cget configure curselection \
|
| 153 |
|
|
delete get icursor index \
|
| 154 |
|
|
insert list scan selection \
|
| 155 |
|
|
xview select toggle open \
|
| 156 |
|
|
close subwidget \
|
| 157 |
|
|
]
|
| 158 |
|
|
|
| 159 |
|
|
set listCommands [list \
|
| 160 |
|
|
delete get \
|
| 161 |
|
|
index insert size \
|
| 162 |
|
|
]
|
| 163 |
|
|
|
| 164 |
|
|
set scanCommands [list mark dragto]
|
| 165 |
|
|
|
| 166 |
|
|
# why check for the Tk package? This lets us be sourced into
|
| 167 |
|
|
# an interpreter that doesn't have Tk loaded, such as the slave
|
| 168 |
|
|
# interpreter used by pkg_mkIndex. In theory it should have no
|
| 169 |
|
|
# side effects when run
|
| 170 |
|
|
if {[lsearch -exact [package names] "Tk"] != -1} {
|
| 171 |
|
|
|
| 172 |
|
|
##################################################################
|
| 173 |
|
|
#- this initializes the option database. Kinda gross, but it works
|
| 174 |
|
|
#- (I think).
|
| 175 |
|
|
##################################################################
|
| 176 |
|
|
|
| 177 |
|
|
# the image used for the button...
|
| 178 |
|
|
if {$::tcl_platform(platform) == "windows"} {
|
| 179 |
|
|
image create bitmap ::combobox::bimage -data {
|
| 180 |
|
|
#define down_arrow_width 12
|
| 181 |
|
|
#define down_arrow_height 12
|
| 182 |
|
|
static char down_arrow_bits[] = {
|
| 183 |
|
|
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
|
| 184 |
|
|
0xfc,0xf1,0xf8,0xf0,0x70,0xf0,0x20,0xf0,
|
| 185 |
|
|
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00;
|
| 186 |
|
|
}
|
| 187 |
|
|
}
|
| 188 |
|
|
} else {
|
| 189 |
|
|
image create bitmap ::combobox::bimage -data {
|
| 190 |
|
|
#define down_arrow_width 15
|
| 191 |
|
|
#define down_arrow_height 15
|
| 192 |
|
|
static char down_arrow_bits[] = {
|
| 193 |
|
|
0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,
|
| 194 |
|
|
0x00,0x80,0xf8,0x8f,0xf0,0x87,0xe0,0x83,
|
| 195 |
|
|
0xc0,0x81,0x80,0x80,0x00,0x80,0x00,0x80,
|
| 196 |
|
|
0x00,0x80,0x00,0x80,0x00,0x80
|
| 197 |
|
|
}
|
| 198 |
|
|
}
|
| 199 |
|
|
}
|
| 200 |
|
|
|
| 201 |
|
|
# compute a widget name we can use to create a temporary widget
|
| 202 |
|
|
set tmpWidget ".__tmp__"
|
| 203 |
|
|
set count 0
|
| 204 |
|
|
while {[winfo exists $tmpWidget] == 1} {
|
| 205 |
|
|
set tmpWidget ".__tmp__$count"
|
| 206 |
|
|
incr count
|
| 207 |
|
|
}
|
| 208 |
|
|
|
| 209 |
|
|
# get the scrollbar width. Because we try to be clever and draw our
|
| 210 |
|
|
# own button instead of using a tk widget, we need to know what size
|
| 211 |
|
|
# button to create. This little hack tells us the width of a scroll
|
| 212 |
|
|
# bar.
|
| 213 |
|
|
#
|
| 214 |
|
|
# NB: we need to be sure and pick a window that doesn't already
|
| 215 |
|
|
# exist...
|
| 216 |
|
|
scrollbar $tmpWidget
|
| 217 |
|
|
set sb_width [winfo reqwidth $tmpWidget]
|
| 218 |
|
|
set bbg [$tmpWidget cget -background]
|
| 219 |
|
|
destroy $tmpWidget
|
| 220 |
|
|
|
| 221 |
|
|
# steal options from the entry widget
|
| 222 |
|
|
# we want darn near all options, so we'll go ahead and do
|
| 223 |
|
|
# them all. No harm done in adding the one or two that we
|
| 224 |
|
|
# don't use.
|
| 225 |
|
|
entry $tmpWidget
|
| 226 |
|
|
foreach foo [$tmpWidget configure] {
|
| 227 |
|
|
# the cursor option is special, so we'll save it in
|
| 228 |
|
|
# a special way
|
| 229 |
|
|
if {[lindex $foo 0] == "-cursor"} {
|
| 230 |
|
|
set defaultEntryCursor [lindex $foo 4]
|
| 231 |
|
|
}
|
| 232 |
|
|
if {[llength $foo] == 5} {
|
| 233 |
|
|
set option [lindex $foo 1]
|
| 234 |
|
|
set value [lindex $foo 4]
|
| 235 |
|
|
option add *Combobox.$option $value widgetDefault
|
| 236 |
|
|
|
| 237 |
|
|
# these options also apply to the dropdown listbox
|
| 238 |
|
|
if {[string compare $option "foreground"] == 0 \
|
| 239 |
|
|
|| [string compare $option "background"] == 0 \
|
| 240 |
|
|
|| [string compare $option "font"] == 0} {
|
| 241 |
|
|
option add *Combobox*ComboboxListbox.$option $value \
|
| 242 |
|
|
widgetDefault
|
| 243 |
|
|
}
|
| 244 |
|
|
}
|
| 245 |
|
|
}
|
| 246 |
|
|
destroy $tmpWidget
|
| 247 |
|
|
|
| 248 |
|
|
# these are unique to us...
|
| 249 |
|
|
option add *Combobox.elementBorderWidth 1 widgetDefault
|
| 250 |
|
|
option add *Combobox.buttonBackground $bbg widgetDefault
|
| 251 |
|
|
option add *Combobox.dropdownWidth {} widgetDefault
|
| 252 |
|
|
option add *Combobox.openCommand {} widgetDefault
|
| 253 |
|
|
option add *Combobox.cursor {} widgetDefault
|
| 254 |
|
|
option add *Combobox.commandState normal widgetDefault
|
| 255 |
|
|
option add *Combobox.editable 1 widgetDefault
|
| 256 |
|
|
option add *Combobox.maxHeight 10 widgetDefault
|
| 257 |
|
|
option add *Combobox.height 0
|
| 258 |
|
|
}
|
| 259 |
|
|
|
| 260 |
|
|
# set class bindings
|
| 261 |
|
|
SetClassBindings
|
| 262 |
|
|
}
|
| 263 |
|
|
|
| 264 |
|
|
# ::combobox::SetClassBindings --
|
| 265 |
|
|
#
|
| 266 |
|
|
# Sets up the default bindings for the widget class
|
| 267 |
|
|
#
|
| 268 |
|
|
# this proc exists since it's The Right Thing To Do, but
|
| 269 |
|
|
# I haven't had the time to figure out how to do all the
|
| 270 |
|
|
# binding stuff on a class level. The main problem is that
|
| 271 |
|
|
# the entry widget must have focus for the insertion cursor
|
| 272 |
|
|
# to be visible. So, I either have to have the entry widget
|
| 273 |
|
|
# have the Combobox bindtag, or do some fancy juggling of
|
| 274 |
|
|
# events or some such. What a pain.
|
| 275 |
|
|
#
|
| 276 |
|
|
# Arguments:
|
| 277 |
|
|
#
|
| 278 |
|
|
# none
|
| 279 |
|
|
#
|
| 280 |
|
|
# Returns:
|
| 281 |
|
|
#
|
| 282 |
|
|
# empty string
|
| 283 |
|
|
|
| 284 |
|
|
proc ::combobox::SetClassBindings {} {
|
| 285 |
|
|
|
| 286 |
|
|
# make sure we clean up after ourselves...
|
| 287 |
|
|
bind Combobox <Destroy> [list ::combobox::DestroyHandler %W]
|
| 288 |
|
|
|
| 289 |
|
|
# this will (hopefully) close (and lose the grab on) the
|
| 290 |
|
|
# listbox if the user clicks anywhere outside of it. Note
|
| 291 |
|
|
# that on Windows, you can click on some other app and
|
| 292 |
|
|
# the listbox will still be there, because tcl won't see
|
| 293 |
|
|
# that button click
|
| 294 |
|
|
set this {[::combobox::convert %W -W]}
|
| 295 |
|
|
bind Combobox <Any-ButtonPress> "$this close"
|
| 296 |
|
|
bind Combobox <Any-ButtonRelease> "$this close"
|
| 297 |
|
|
|
| 298 |
|
|
# this helps (but doesn't fully solve) focus issues. The general
|
| 299 |
|
|
# idea is, whenever the frame gets focus it gets passed on to
|
| 300 |
|
|
# the entry widget
|
| 301 |
|
|
bind Combobox <FocusIn> {::combobox::tkTabToWindow \
|
| 302 |
|
|
[::combobox::convert %W -W].entry}
|
| 303 |
|
|
|
| 304 |
|
|
# this closes the listbox if we get hidden
|
| 305 |
|
|
bind Combobox <Unmap> {[::combobox::convert %W -W] close}
|
| 306 |
|
|
|
| 307 |
|
|
return ""
|
| 308 |
|
|
}
|
| 309 |
|
|
|
| 310 |
|
|
# ::combobox::SetBindings --
|
| 311 |
|
|
#
|
| 312 |
|
|
# here's where we do most of the binding foo. I think there's probably
|
| 313 |
|
|
# a few bindings I ought to add that I just haven't thought
|
| 314 |
|
|
# about...
|
| 315 |
|
|
#
|
| 316 |
|
|
# I'm not convinced these are the proper bindings. Ideally all
|
| 317 |
|
|
# bindings should be on "Combobox", but because of my juggling of
|
| 318 |
|
|
# bindtags I'm not convinced thats what I want to do. But, it all
|
| 319 |
|
|
# seems to work, its just not as robust as it could be.
|
| 320 |
|
|
#
|
| 321 |
|
|
# Arguments:
|
| 322 |
|
|
#
|
| 323 |
|
|
# w widget pathname
|
| 324 |
|
|
#
|
| 325 |
|
|
# Returns:
|
| 326 |
|
|
#
|
| 327 |
|
|
# empty string
|
| 328 |
|
|
|
| 329 |
|
|
proc ::combobox::SetBindings {w} {
|
| 330 |
|
|
upvar ::combobox::${w}::widgets widgets
|
| 331 |
|
|
upvar ::combobox::${w}::options options
|
| 332 |
|
|
|
| 333 |
|
|
# juggle the bindtags. The basic idea here is to associate the
|
| 334 |
|
|
# widget name with the entry widget, so if a user does a bind
|
| 335 |
|
|
# on the combobox it will get handled properly since it is
|
| 336 |
|
|
# the entry widget that has keyboard focus.
|
| 337 |
|
|
bindtags $widgets(entry) \
|
| 338 |
|
|
[concat $widgets(this) [bindtags $widgets(entry)]]
|
| 339 |
|
|
|
| 340 |
|
|
bindtags $widgets(button) \
|
| 341 |
|
|
[concat $widgets(this) [bindtags $widgets(button)]]
|
| 342 |
|
|
|
| 343 |
|
|
# override the default bindings for tab and shift-tab. The
|
| 344 |
|
|
# focus procs take a widget as their only parameter and we
|
| 345 |
|
|
# want to make sure the right window gets used (for shift-
|
| 346 |
|
|
# tab we want it to appear as if the event was generated
|
| 347 |
|
|
# on the frame rather than the entry.
|
| 348 |
|
|
bind $widgets(entry) <Tab> \
|
| 349 |
|
|
"::combobox::tkTabToWindow \[tk_focusNext $widgets(entry)\]; break"
|
| 350 |
|
|
bind $widgets(entry) <Shift-Tab> \
|
| 351 |
|
|
"::combobox::tkTabToWindow \[tk_focusPrev $widgets(this)\]; break"
|
| 352 |
|
|
|
| 353 |
|
|
# this makes our "button" (which is actually a label)
|
| 354 |
|
|
# do the right thing
|
| 355 |
|
|
bind $widgets(button) <ButtonPress-1> [list $widgets(this) toggle]
|
| 356 |
|
|
|
| 357 |
|
|
# this lets the autoscan of the listbox work, even if they
|
| 358 |
|
|
# move the cursor over the entry widget.
|
| 359 |
|
|
bind $widgets(entry) <B1-Enter> "break"
|
| 360 |
|
|
|
| 361 |
|
|
bind $widgets(listbox) <ButtonRelease-1> \
|
| 362 |
|
|
"::combobox::Select [list $widgets(this)] \
|
| 363 |
|
|
\[$widgets(listbox) nearest %y\]; break"
|
| 364 |
|
|
|
| 365 |
|
|
bind $widgets(vsb) <ButtonPress-1> {continue}
|
| 366 |
|
|
bind $widgets(vsb) <ButtonRelease-1> {continue}
|
| 367 |
|
|
|
| 368 |
|
|
bind $widgets(listbox) <Any-Motion> {
|
| 369 |
|
|
%W selection clear 0 end
|
| 370 |
|
|
%W activate @%x,%y
|
| 371 |
|
|
%W selection anchor @%x,%y
|
| 372 |
|
|
%W selection set @%x,%y @%x,%y
|
| 373 |
|
|
# need to do a yview if the cursor goes off the top
|
| 374 |
|
|
# or bottom of the window... (or do we?)
|
| 375 |
|
|
}
|
| 376 |
|
|
|
| 377 |
|
|
# these events need to be passed from the entry widget
|
| 378 |
|
|
# to the listbox, or otherwise need some sort of special
|
| 379 |
|
|
# handling.
|
| 380 |
|
|
foreach event [list <Up> <Down> <Tab> <Return> <Escape> \
|
| 381 |
|
|
<Next> <Prior> <Double-1> <1> <Any-KeyPress> \
|
| 382 |
|
|
<FocusIn> <FocusOut>] {
|
| 383 |
|
|
bind $widgets(entry) $event \
|
| 384 |
|
|
[list ::combobox::HandleEvent $widgets(this) $event]
|
| 385 |
|
|
}
|
| 386 |
|
|
|
| 387 |
|
|
# like the other events, <MouseWheel> needs to be passed from
|
| 388 |
|
|
# the entry widget to the listbox. However, in this case we
|
| 389 |
|
|
# need to add an additional parameter
|
| 390 |
|
|
catch {
|
| 391 |
|
|
bind $widgets(entry) <MouseWheel> \
|
| 392 |
|
|
[list ::combobox::HandleEvent $widgets(this) <MouseWheel> %D]
|
| 393 |
|
|
}
|
| 394 |
|
|
}
|
| 395 |
|
|
|
| 396 |
|
|
# ::combobox::Build --
|
| 397 |
|
|
#
|
| 398 |
|
|
# This does all of the work necessary to create the basic
|
| 399 |
|
|
# combobox.
|
| 400 |
|
|
#
|
| 401 |
|
|
# Arguments:
|
| 402 |
|
|
#
|
| 403 |
|
|
# w widget name
|
| 404 |
|
|
# args additional option/value pairs
|
| 405 |
|
|
#
|
| 406 |
|
|
# Results:
|
| 407 |
|
|
#
|
| 408 |
|
|
# Creates a new widget with the given name. Also creates a new
|
| 409 |
|
|
# namespace patterened after the widget name, as a child namespace
|
| 410 |
|
|
# to ::combobox
|
| 411 |
|
|
#
|
| 412 |
|
|
# Returns:
|
| 413 |
|
|
#
|
| 414 |
|
|
# the name of the widget
|
| 415 |
|
|
|
| 416 |
|
|
proc ::combobox::Build {w args } {
|
| 417 |
|
|
variable widgetOptions
|
| 418 |
|
|
|
| 419 |
|
|
if {[winfo exists $w]} {
|
| 420 |
|
|
error "window name \"$w\" already exists"
|
| 421 |
|
|
}
|
| 422 |
|
|
|
| 423 |
|
|
# create the namespace for this instance, and define a few
|
| 424 |
|
|
# variables
|
| 425 |
|
|
namespace eval ::combobox::$w {
|
| 426 |
|
|
|
| 427 |
|
|
variable ignoreTrace 0
|
| 428 |
|
|
variable oldFocus {}
|
| 429 |
|
|
variable oldGrab {}
|
| 430 |
|
|
variable oldValue {}
|
| 431 |
|
|
variable options
|
| 432 |
|
|
variable this
|
| 433 |
|
|
variable widgets
|
| 434 |
|
|
|
| 435 |
|
|
set widgets(foo) foo ;# coerce into an array
|
| 436 |
|
|
set options(foo) foo ;# coerce into an array
|
| 437 |
|
|
|
| 438 |
|
|
unset widgets(foo)
|
| 439 |
|
|
unset options(foo)
|
| 440 |
|
|
}
|
| 441 |
|
|
|
| 442 |
|
|
# import the widgets and options arrays into this proc so
|
| 443 |
|
|
# we don't have to use fully qualified names, which is a
|
| 444 |
|
|
# pain.
|
| 445 |
|
|
upvar ::combobox::${w}::widgets widgets
|
| 446 |
|
|
upvar ::combobox::${w}::options options
|
| 447 |
|
|
|
| 448 |
|
|
# this is our widget -- a frame of class Combobox. Naturally,
|
| 449 |
|
|
# it will contain other widgets. We create it here because
|
| 450 |
|
|
# we need it in order to set some default options.
|
| 451 |
|
|
set widgets(this) [frame $w -class Combobox -takefocus 0]
|
| 452 |
|
|
set widgets(entry) [entry $w.entry -takefocus 1]
|
| 453 |
|
|
set widgets(button) [label $w.button -takefocus 0]
|
| 454 |
|
|
|
| 455 |
|
|
# this defines all of the default options. We get the
|
| 456 |
|
|
# values from the option database. Note that if an array
|
| 457 |
|
|
# value is a list of length one it is an alias to another
|
| 458 |
|
|
# option, so we just ignore it
|
| 459 |
|
|
foreach name [array names widgetOptions] {
|
| 460 |
|
|
if {[llength $widgetOptions($name)] == 1} continue
|
| 461 |
|
|
|
| 462 |
|
|
set optName [lindex $widgetOptions($name) 0]
|
| 463 |
|
|
set optClass [lindex $widgetOptions($name) 1]
|
| 464 |
|
|
|
| 465 |
|
|
set value [option get $w $optName $optClass]
|
| 466 |
|
|
set options($name) $value
|
| 467 |
|
|
}
|
| 468 |
|
|
|
| 469 |
|
|
# a couple options aren't available in earlier versions of
|
| 470 |
|
|
# tcl, so we'll set them to sane values. For that matter, if
|
| 471 |
|
|
# they exist but are empty, set them to sane values.
|
| 472 |
|
|
if {[string length $options(-disabledforeground)] == 0} {
|
| 473 |
|
|
set options(-disabledforeground) $options(-foreground)
|
| 474 |
|
|
}
|
| 475 |
|
|
if {[string length $options(-disabledbackground)] == 0} {
|
| 476 |
|
|
set options(-disabledbackground) $options(-background)
|
| 477 |
|
|
}
|
| 478 |
|
|
|
| 479 |
|
|
# if -value is set to null, we'll remove it from our
|
| 480 |
|
|
# local array. The assumption is, if the user sets it from
|
| 481 |
|
|
# the option database, they will set it to something other
|
| 482 |
|
|
# than null (since it's impossible to determine the difference
|
| 483 |
|
|
# between a null value and no value at all).
|
| 484 |
|
|
if {[info exists options(-value)] \
|
| 485 |
|
|
&& [string length $options(-value)] == 0} {
|
| 486 |
|
|
unset options(-value)
|
| 487 |
|
|
}
|
| 488 |
|
|
|
| 489 |
|
|
# we will later rename the frame's widget proc to be our
|
| 490 |
|
|
# own custom widget proc. We need to keep track of this
|
| 491 |
|
|
# new name, so we'll define and store it here...
|
| 492 |
|
|
set widgets(frame) ::combobox::${w}::$w
|
| 493 |
|
|
|
| 494 |
|
|
# gotta do this sooner or later. Might as well do it now
|
| 495 |
|
|
pack $widgets(button) -side right -fill y -expand no
|
| 496 |
|
|
pack $widgets(entry) -side left -fill both -expand yes
|
| 497 |
|
|
|
| 498 |
|
|
# I should probably do this in a catch, but for now it's
|
| 499 |
|
|
# good enough... What it does, obviously, is put all of
|
| 500 |
|
|
# the option/values pairs into an array. Make them easier
|
| 501 |
|
|
# to handle later on...
|
| 502 |
|
|
array set options $args
|
| 503 |
|
|
|
| 504 |
|
|
# now, the dropdown list... the same renaming nonsense
|
| 505 |
|
|
# must go on here as well...
|
| 506 |
|
|
set widgets(dropdown) [toplevel $w.top]
|
| 507 |
|
|
set widgets(listbox) [listbox $w.top.list]
|
| 508 |
|
|
set widgets(vsb) [scrollbar $w.top.vsb]
|
| 509 |
|
|
|
| 510 |
|
|
pack $widgets(listbox) -side left -fill both -expand y
|
| 511 |
|
|
|
| 512 |
|
|
# fine tune the widgets based on the options (and a few
|
| 513 |
|
|
# arbitrary values...)
|
| 514 |
|
|
|
| 515 |
|
|
# NB: we are going to use the frame to handle the relief
|
| 516 |
|
|
# of the widget as a whole, so the entry widget will be
|
| 517 |
|
|
# flat. This makes the button which drops down the list
|
| 518 |
|
|
# to appear "inside" the entry widget.
|
| 519 |
|
|
|
| 520 |
|
|
$widgets(vsb) configure \
|
| 521 |
|
|
-borderwidth 1 \
|
| 522 |
|
|
-command "$widgets(listbox) yview" \
|
| 523 |
|
|
-highlightthickness 0
|
| 524 |
|
|
|
| 525 |
|
|
$widgets(button) configure \
|
| 526 |
|
|
-background $options(-buttonbackground) \
|
| 527 |
|
|
-highlightthickness 0 \
|
| 528 |
|
|
-borderwidth $options(-elementborderwidth) \
|
| 529 |
|
|
-relief raised \
|
| 530 |
|
|
-width [expr {[winfo reqwidth $widgets(vsb)] - 2}]
|
| 531 |
|
|
|
| 532 |
|
|
$widgets(entry) configure \
|
| 533 |
|
|
-borderwidth 0 \
|
| 534 |
|
|
-relief flat \
|
| 535 |
|
|
-highlightthickness 0
|
| 536 |
|
|
|
| 537 |
|
|
$widgets(dropdown) configure \
|
| 538 |
|
|
-borderwidth $options(-elementborderwidth) \
|
| 539 |
|
|
-relief sunken
|
| 540 |
|
|
|
| 541 |
|
|
$widgets(listbox) configure \
|
| 542 |
|
|
-selectmode browse \
|
| 543 |
|
|
-background [$widgets(entry) cget -bg] \
|
| 544 |
|
|
-yscrollcommand "$widgets(vsb) set" \
|
| 545 |
|
|
-exportselection false \
|
| 546 |
|
|
-borderwidth 0
|
| 547 |
|
|
|
| 548 |
|
|
|
| 549 |
|
|
# trace variable ::combobox::${w}::entryTextVariable w \
|
| 550 |
|
|
# [list ::combobox::EntryTrace $w]
|
| 551 |
|
|
|
| 552 |
|
|
# do some window management foo on the dropdown window
|
| 553 |
|
|
wm overrideredirect $widgets(dropdown) 1
|
| 554 |
|
|
wm transient $widgets(dropdown) [winfo toplevel $w]
|
| 555 |
|
|
wm group $widgets(dropdown) [winfo parent $w]
|
| 556 |
|
|
wm resizable $widgets(dropdown) 0 0
|
| 557 |
|
|
wm withdraw $widgets(dropdown)
|
| 558 |
|
|
|
| 559 |
|
|
# this moves the original frame widget proc into our
|
| 560 |
|
|
# namespace and gives it a handy name
|
| 561 |
|
|
rename ::$w $widgets(frame)
|
| 562 |
|
|
|
| 563 |
|
|
# now, create our widget proc. Obviously (?) it goes in
|
| 564 |
|
|
# the global namespace. All combobox widgets will actually
|
| 565 |
|
|
# share the same widget proc to cut down on the amount of
|
| 566 |
|
|
# bloat.
|
| 567 |
|
|
proc ::$w {command args} \
|
| 568 |
|
|
"eval ::combobox::WidgetProc $w \$command \$args"
|
| 569 |
|
|
|
| 570 |
|
|
|
| 571 |
|
|
# ok, the thing exists... let's do a bit more configuration.
|
| 572 |
|
|
if {[catch "::combobox::Configure [list $widgets(this)] [array get options]" error]} {
|
| 573 |
|
|
catch {destroy $w}
|
| 574 |
|
|
error "internal error: $error"
|
| 575 |
|
|
}
|
| 576 |
|
|
|
| 577 |
|
|
return ""
|
| 578 |
|
|
|
| 579 |
|
|
}
|
| 580 |
|
|
|
| 581 |
|
|
# ::combobox::HandleEvent --
|
| 582 |
|
|
#
|
| 583 |
|
|
# this proc handles events from the entry widget that we want
|
| 584 |
|
|
# handled specially (typically, to allow navigation of the list
|
| 585 |
|
|
# even though the focus is in the entry widget)
|
| 586 |
|
|
#
|
| 587 |
|
|
# Arguments:
|
| 588 |
|
|
#
|
| 589 |
|
|
# w widget pathname
|
| 590 |
|
|
# event a string representing the event (not necessarily an
|
| 591 |
|
|
# actual event)
|
| 592 |
|
|
# args additional arguments required by particular events
|
| 593 |
|
|
|
| 594 |
|
|
proc ::combobox::HandleEvent {w event args} {
|
| 595 |
|
|
upvar ::combobox::${w}::widgets widgets
|
| 596 |
|
|
upvar ::combobox::${w}::options options
|
| 597 |
|
|
upvar ::combobox::${w}::oldValue oldValue
|
| 598 |
|
|
|
| 599 |
|
|
# for all of these events, if we have a special action we'll
|
| 600 |
|
|
# do that and do a "return -code break" to keep additional
|
| 601 |
|
|
# bindings from firing. Otherwise we'll let the event fall
|
| 602 |
|
|
# on through.
|
| 603 |
|
|
switch $event {
|
| 604 |
|
|
|
| 605 |
|
|
"<MouseWheel>" {
|
| 606 |
|
|
if {[winfo ismapped $widgets(dropdown)]} {
|
| 607 |
|
|
set D [lindex $args 0]
|
| 608 |
|
|
# the '120' number in the following expression has
|
| 609 |
|
|
# it's genesis in the tk bind manpage, which suggests
|
| 610 |
|
|
# that the smallest value of %D for mousewheel events
|
| 611 |
|
|
# will be 120. The intent is to scroll one line at a time.
|
| 612 |
|
|
$widgets(listbox) yview scroll [expr {-($D/120)}] units
|
| 613 |
|
|
}
|
| 614 |
|
|
}
|
| 615 |
|
|
|
| 616 |
|
|
"<Any-KeyPress>" {
|
| 617 |
|
|
# if the widget is editable, clear the selection.
|
| 618 |
|
|
# this makes it more obvious what will happen if the
|
| 619 |
|
|
# user presses <Return> (and helps our code know what
|
| 620 |
|
|
# to do if the user presses return)
|
| 621 |
|
|
if {$options(-editable)} {
|
| 622 |
|
|
$widgets(listbox) see 0
|
| 623 |
|
|
$widgets(listbox) selection clear 0 end
|
| 624 |
|
|
$widgets(listbox) selection anchor 0
|
| 625 |
|
|
$widgets(listbox) activate 0
|
| 626 |
|
|
}
|
| 627 |
|
|
}
|
| 628 |
|
|
|
| 629 |
|
|
"<FocusIn>" {
|
| 630 |
|
|
set oldValue [$widgets(entry) get]
|
| 631 |
|
|
}
|
| 632 |
|
|
|
| 633 |
|
|
"<FocusOut>" {
|
| 634 |
|
|
if {![winfo ismapped $widgets(dropdown)]} {
|
| 635 |
|
|
# did the value change?
|
| 636 |
|
|
set newValue [$widgets(entry) get]
|
| 637 |
|
|
if {$oldValue != $newValue} {
|
| 638 |
|
|
CallCommand $widgets(this) $newValue
|
| 639 |
|
|
}
|
| 640 |
|
|
}
|
| 641 |
|
|
}
|
| 642 |
|
|
|
| 643 |
|
|
"<1>" {
|
| 644 |
|
|
set editable [::combobox::GetBoolean $options(-editable)]
|
| 645 |
|
|
if {!$editable} {
|
| 646 |
|
|
if {[winfo ismapped $widgets(dropdown)]} {
|
| 647 |
|
|
$widgets(this) close
|
| 648 |
|
|
return -code break;
|
| 649 |
|
|
|
| 650 |
|
|
} else {
|
| 651 |
|
|
if {$options(-state) != "disabled"} {
|
| 652 |
|
|
$widgets(this) open
|
| 653 |
|
|
return -code break;
|
| 654 |
|
|
}
|
| 655 |
|
|
}
|
| 656 |
|
|
}
|
| 657 |
|
|
}
|
| 658 |
|
|
|
| 659 |
|
|
"<Double-1>" {
|
| 660 |
|
|
if {$options(-state) != "disabled"} {
|
| 661 |
|
|
$widgets(this) toggle
|
| 662 |
|
|
return -code break;
|
| 663 |
|
|
}
|
| 664 |
|
|
}
|
| 665 |
|
|
|
| 666 |
|
|
"<Tab>" {
|
| 667 |
|
|
if {[winfo ismapped $widgets(dropdown)]} {
|
| 668 |
|
|
::combobox::Find $widgets(this) 0
|
| 669 |
|
|
return -code break;
|
| 670 |
|
|
} else {
|
| 671 |
|
|
::combobox::SetValue $widgets(this) [$widgets(this) get]
|
| 672 |
|
|
}
|
| 673 |
|
|
}
|
| 674 |
|
|
|
| 675 |
|
|
"<Escape>" {
|
| 676 |
|
|
# $widgets(entry) delete 0 end
|
| 677 |
|
|
# $widgets(entry) insert 0 $oldValue
|
| 678 |
|
|
if {[winfo ismapped $widgets(dropdown)]} {
|
| 679 |
|
|
$widgets(this) close
|
| 680 |
|
|
return -code break;
|
| 681 |
|
|
}
|
| 682 |
|
|
}
|
| 683 |
|
|
|
| 684 |
|
|
"<Return>" {
|
| 685 |
|
|
# did the value change?
|
| 686 |
|
|
set newValue [$widgets(entry) get]
|
| 687 |
|
|
if {$oldValue != $newValue} {
|
| 688 |
|
|
CallCommand $widgets(this) $newValue
|
| 689 |
|
|
}
|
| 690 |
|
|
|
| 691 |
|
|
if {[winfo ismapped $widgets(dropdown)]} {
|
| 692 |
|
|
::combobox::Select $widgets(this) \
|
| 693 |
|
|
[$widgets(listbox) curselection]
|
| 694 |
|
|
return -code break;
|
| 695 |
|
|
}
|
| 696 |
|
|
|
| 697 |
|
|
}
|
| 698 |
|
|
|
| 699 |
|
|
"<Next>" {
|
| 700 |
|
|
$widgets(listbox) yview scroll 1 pages
|
| 701 |
|
|
set index [$widgets(listbox) index @0,0]
|
| 702 |
|
|
$widgets(listbox) see $index
|
| 703 |
|
|
$widgets(listbox) activate $index
|
| 704 |
|
|
$widgets(listbox) selection clear 0 end
|
| 705 |
|
|
$widgets(listbox) selection anchor $index
|
| 706 |
|
|
$widgets(listbox) selection set $index
|
| 707 |
|
|
|
| 708 |
|
|
}
|
| 709 |
|
|
|
| 710 |
|
|
"<Prior>" {
|
| 711 |
|
|
$widgets(listbox) yview scroll -1 pages
|
| 712 |
|
|
set index [$widgets(listbox) index @0,0]
|
| 713 |
|
|
$widgets(listbox) activate $index
|
| 714 |
|
|
$widgets(listbox) see $index
|
| 715 |
|
|
$widgets(listbox) selection clear 0 end
|
| 716 |
|
|
$widgets(listbox) selection anchor $index
|
| 717 |
|
|
$widgets(listbox) selection set $index
|
| 718 |
|
|
}
|
| 719 |
|
|
|
| 720 |
|
|
"<Down>" {
|
| 721 |
|
|
if {[winfo ismapped $widgets(dropdown)]} {
|
| 722 |
|
|
::combobox::tkListboxUpDown $widgets(listbox) 1
|
| 723 |
|
|
return -code break;
|
| 724 |
|
|
|
| 725 |
|
|
} else {
|
| 726 |
|
|
if {$options(-state) != "disabled"} {
|
| 727 |
|
|
$widgets(this) open
|
| 728 |
|
|
return -code break;
|
| 729 |
|
|
}
|
| 730 |
|
|
}
|
| 731 |
|
|
}
|
| 732 |
|
|
"<Up>" {
|
| 733 |
|
|
if {[winfo ismapped $widgets(dropdown)]} {
|
| 734 |
|
|
::combobox::tkListboxUpDown $widgets(listbox) -1
|
| 735 |
|
|
return -code break;
|
| 736 |
|
|
|
| 737 |
|
|
} else {
|
| 738 |
|
|
if {$options(-state) != "disabled"} {
|
| 739 |
|
|
$widgets(this) open
|
| 740 |
|
|
return -code break;
|
| 741 |
|
|
}
|
| 742 |
|
|
}
|
| 743 |
|
|
}
|
| 744 |
|
|
}
|
| 745 |
|
|
|
| 746 |
|
|
return ""
|
| 747 |
|
|
}
|
| 748 |
|
|
|
| 749 |
|
|
# ::combobox::DestroyHandler {w} --
|
| 750 |
|
|
#
|
| 751 |
|
|
# Cleans up after a combobox widget is destroyed
|
| 752 |
|
|
#
|
| 753 |
|
|
# Arguments:
|
| 754 |
|
|
#
|
| 755 |
|
|
# w widget pathname
|
| 756 |
|
|
#
|
| 757 |
|
|
# Results:
|
| 758 |
|
|
#
|
| 759 |
|
|
# The namespace that was created for the widget is deleted,
|
| 760 |
|
|
# and the widget proc is removed.
|
| 761 |
|
|
|
| 762 |
|
|
proc ::combobox::DestroyHandler {w} {
|
| 763 |
|
|
|
| 764 |
|
|
catch {
|
| 765 |
|
|
# if the widget actually being destroyed is of class Combobox,
|
| 766 |
|
|
# remove the namespace and associated proc.
|
| 767 |
|
|
if {[string compare [winfo class $w] "Combobox"] == 0} {
|
| 768 |
|
|
# delete the namespace and the proc which represents
|
| 769 |
|
|
# our widget
|
| 770 |
|
|
namespace delete ::combobox::$w
|
| 771 |
|
|
rename $w {}
|
| 772 |
|
|
}
|
| 773 |
|
|
}
|
| 774 |
|
|
return ""
|
| 775 |
|
|
}
|
| 776 |
|
|
|
| 777 |
|
|
# ::combobox::Find
|
| 778 |
|
|
#
|
| 779 |
|
|
# finds something in the listbox that matches the pattern in the
|
| 780 |
|
|
# entry widget and selects it
|
| 781 |
|
|
#
|
| 782 |
|
|
# N.B. I'm not convinced this is working the way it ought to. It
|
| 783 |
|
|
# works, but is the behavior what is expected? I've also got a gut
|
| 784 |
|
|
# feeling that there's a better way to do this, but I'm too lazy to
|
| 785 |
|
|
# figure it out...
|
| 786 |
|
|
#
|
| 787 |
|
|
# Arguments:
|
| 788 |
|
|
#
|
| 789 |
|
|
# w widget pathname
|
| 790 |
|
|
# exact boolean; if true an exact match is desired
|
| 791 |
|
|
#
|
| 792 |
|
|
# Returns:
|
| 793 |
|
|
#
|
| 794 |
|
|
# Empty string
|
| 795 |
|
|
|
| 796 |
|
|
proc ::combobox::Find {w {exact 0}} {
|
| 797 |
|
|
upvar ::combobox::${w}::widgets widgets
|
| 798 |
|
|
upvar ::combobox::${w}::options options
|
| 799 |
|
|
|
| 800 |
|
|
## *sigh* this logic is rather gross and convoluted. Surely
|
| 801 |
|
|
## there is a more simple, straight-forward way to implement
|
| 802 |
|
|
## all this. As the saying goes, I lack the time to make it
|
| 803 |
|
|
## shorter...
|
| 804 |
|
|
|
| 805 |
|
|
# use what is already in the entry widget as a pattern
|
| 806 |
|
|
set pattern [$widgets(entry) get]
|
| 807 |
|
|
|
| 808 |
|
|
if {[string length $pattern] == 0} {
|
| 809 |
|
|
# clear the current selection
|
| 810 |
|
|
$widgets(listbox) see 0
|
| 811 |
|
|
$widgets(listbox) selection clear 0 end
|
| 812 |
|
|
$widgets(listbox) selection anchor 0
|
| 813 |
|
|
$widgets(listbox) activate 0
|
| 814 |
|
|
return
|
| 815 |
|
|
}
|
| 816 |
|
|
|
| 817 |
|
|
# we're going to be searching this list...
|
| 818 |
|
|
set list [$widgets(listbox) get 0 end]
|
| 819 |
|
|
|
| 820 |
|
|
# if we are doing an exact match, try to find,
|
| 821 |
|
|
# well, an exact match
|
| 822 |
|
|
set exactMatch -1
|
| 823 |
|
|
if {$exact} {
|
| 824 |
|
|
set exactMatch [lsearch -exact $list $pattern]
|
| 825 |
|
|
}
|
| 826 |
|
|
|
| 827 |
|
|
# search for it. We'll try to be clever and not only
|
| 828 |
|
|
# search for a match for what they typed, but a match for
|
| 829 |
|
|
# something close to what they typed. We'll keep removing one
|
| 830 |
|
|
# character at a time from the pattern until we find a match
|
| 831 |
|
|
# of some sort.
|
| 832 |
|
|
set index -1
|
| 833 |
|
|
while {$index == -1 && [string length $pattern]} {
|
| 834 |
|
|
set index [lsearch -glob $list "$pattern*"]
|
| 835 |
|
|
if {$index == -1} {
|
| 836 |
|
|
regsub {.$} $pattern {} pattern
|
| 837 |
|
|
}
|
| 838 |
|
|
}
|
| 839 |
|
|
|
| 840 |
|
|
# this is the item that most closely matches...
|
| 841 |
|
|
set thisItem [lindex $list $index]
|
| 842 |
|
|
|
| 843 |
|
|
# did we find a match? If so, do some additional munging...
|
| 844 |
|
|
if {$index != -1} {
|
| 845 |
|
|
|
| 846 |
|
|
# we need to find the part of the first item that is
|
| 847 |
|
|
# unique WRT the second... I know there's probably a
|
| 848 |
|
|
# simpler way to do this...
|
| 849 |
|
|
|
| 850 |
|
|
set nextIndex [expr {$index + 1}]
|
| 851 |
|
|
set nextItem [lindex $list $nextIndex]
|
| 852 |
|
|
|
| 853 |
|
|
# we don't really need to do much if the next
|
| 854 |
|
|
# item doesn't match our pattern...
|
| 855 |
|
|
if {[string match $pattern* $nextItem]} {
|
| 856 |
|
|
# ok, the next item matches our pattern, too
|
| 857 |
|
|
# now the trick is to find the first character
|
| 858 |
|
|
# where they *don't* match...
|
| 859 |
|
|
set marker [string length $pattern]
|
| 860 |
|
|
while {$marker <= [string length $pattern]} {
|
| 861 |
|
|
set a [string index $thisItem $marker]
|
| 862 |
|
|
set b [string index $nextItem $marker]
|
| 863 |
|
|
if {[string compare $a $b] == 0} {
|
| 864 |
|
|
append pattern $a
|
| 865 |
|
|
incr marker
|
| 866 |
|
|
} else {
|
| 867 |
|
|
break
|
| 868 |
|
|
}
|
| 869 |
|
|
}
|
| 870 |
|
|
} else {
|
| 871 |
|
|
set marker [string length $pattern]
|
| 872 |
|
|
}
|
| 873 |
|
|
|
| 874 |
|
|
} else {
|
| 875 |
|
|
set marker end
|
| 876 |
|
|
set index 0
|
| 877 |
|
|
}
|
| 878 |
|
|
|
| 879 |
|
|
# ok, we know the pattern and what part is unique;
|
| 880 |
|
|
# update the entry widget and listbox appropriately
|
| 881 |
|
|
if {$exact && $exactMatch == -1} {
|
| 882 |
|
|
# this means we didn't find an exact match
|
| 883 |
|
|
$widgets(listbox) selection clear 0 end
|
| 884 |
|
|
$widgets(listbox) see $index
|
| 885 |
|
|
|
| 886 |
|
|
} elseif {!$exact} {
|
| 887 |
|
|
# this means we found something, but it isn't an exact
|
| 888 |
|
|
# match. If we find something that *is* an exact match we
|
| 889 |
|
|
# don't need to do the following, since it would merely
|
| 890 |
|
|
# be replacing the data in the entry widget with itself
|
| 891 |
|
|
set oldstate [$widgets(entry) cget -state]
|
| 892 |
|
|
$widgets(entry) configure -state normal
|
| 893 |
|
|
$widgets(entry) delete 0 end
|
| 894 |
|
|
$widgets(entry) insert end $thisItem
|
| 895 |
|
|
$widgets(entry) selection clear
|
| 896 |
|
|
$widgets(entry) selection range $marker end
|
| 897 |
|
|
$widgets(listbox) activate $index
|
| 898 |
|
|
$widgets(listbox) selection clear 0 end
|
| 899 |
|
|
$widgets(listbox) selection anchor $index
|
| 900 |
|
|
$widgets(listbox) selection set $index
|
| 901 |
|
|
$widgets(listbox) see $index
|
| 902 |
|
|
$widgets(entry) configure -state $oldstate
|
| 903 |
|
|
}
|
| 904 |
|
|
}
|
| 905 |
|
|
|
| 906 |
|
|
# ::combobox::Select --
|
| 907 |
|
|
#
|
| 908 |
|
|
# selects an item from the list and sets the value of the combobox
|
| 909 |
|
|
# to that value
|
| 910 |
|
|
#
|
| 911 |
|
|
# Arguments:
|
| 912 |
|
|
#
|
| 913 |
|
|
# w widget pathname
|
| 914 |
|
|
# index listbox index of item to be selected
|
| 915 |
|
|
#
|
| 916 |
|
|
# Returns:
|
| 917 |
|
|
#
|
| 918 |
|
|
# empty string
|
| 919 |
|
|
|
| 920 |
|
|
proc ::combobox::Select {w index} {
|
| 921 |
|
|
upvar ::combobox::${w}::widgets widgets
|
| 922 |
|
|
upvar ::combobox::${w}::options options
|
| 923 |
|
|
|
| 924 |
|
|
# the catch is because I'm sloppy -- presumably, the only time
|
| 925 |
|
|
# an error will be caught is if there is no selection.
|
| 926 |
|
|
if {![catch {set data [$widgets(listbox) get [lindex $index 0]]}]} {
|
| 927 |
|
|
::combobox::SetValue $widgets(this) $data
|
| 928 |
|
|
|
| 929 |
|
|
$widgets(listbox) selection clear 0 end
|
| 930 |
|
|
$widgets(listbox) selection anchor $index
|
| 931 |
|
|
$widgets(listbox) selection set $index
|
| 932 |
|
|
|
| 933 |
|
|
}
|
| 934 |
|
|
$widgets(entry) selection range 0 end
|
| 935 |
|
|
$widgets(entry) icursor end
|
| 936 |
|
|
|
| 937 |
|
|
$widgets(this) close
|
| 938 |
|
|
|
| 939 |
|
|
return ""
|
| 940 |
|
|
}
|
| 941 |
|
|
|
| 942 |
|
|
# ::combobox::HandleScrollbar --
|
| 943 |
|
|
#
|
| 944 |
|
|
# causes the scrollbar of the dropdown list to appear or disappear
|
| 945 |
|
|
# based on the contents of the dropdown listbox
|
| 946 |
|
|
#
|
| 947 |
|
|
# Arguments:
|
| 948 |
|
|
#
|
| 949 |
|
|
# w widget pathname
|
| 950 |
|
|
# action the action to perform on the scrollbar
|
| 951 |
|
|
#
|
| 952 |
|
|
# Returns:
|
| 953 |
|
|
#
|
| 954 |
|
|
# an empty string
|
| 955 |
|
|
|
| 956 |
|
|
proc ::combobox::HandleScrollbar {w {action "unknown"}} {
|
| 957 |
|
|
upvar ::combobox::${w}::widgets widgets
|
| 958 |
|
|
upvar ::combobox::${w}::options options
|
| 959 |
|
|
|
| 960 |
|
|
if {$options(-height) == 0} {
|
| 961 |
|
|
set hlimit $options(-maxheight)
|
| 962 |
|
|
} else {
|
| 963 |
|
|
set hlimit $options(-height)
|
| 964 |
|
|
}
|
| 965 |
|
|
|
| 966 |
|
|
switch $action {
|
| 967 |
|
|
"grow" {
|
| 968 |
|
|
if {$hlimit > 0 && [$widgets(listbox) size] > $hlimit} {
|
| 969 |
|
|
pack forget $widgets(listbox)
|
| 970 |
|
|
pack $widgets(vsb) -side right -fill y -expand n
|
| 971 |
|
|
pack $widgets(listbox) -side left -fill both -expand y
|
| 972 |
|
|
}
|
| 973 |
|
|
}
|
| 974 |
|
|
|
| 975 |
|
|
"shrink" {
|
| 976 |
|
|
if {$hlimit > 0 && [$widgets(listbox) size] <= $hlimit} {
|
| 977 |
|
|
pack forget $widgets(vsb)
|
| 978 |
|
|
}
|
| 979 |
|
|
}
|
| 980 |
|
|
|
| 981 |
|
|
"crop" {
|
| 982 |
|
|
# this means the window was cropped and we definitely
|
| 983 |
|
|
# need a scrollbar no matter what the user wants
|
| 984 |
|
|
pack forget $widgets(listbox)
|
| 985 |
|
|
pack $widgets(vsb) -side right -fill y -expand n
|
| 986 |
|
|
pack $widgets(listbox) -side left -fill both -expand y
|
| 987 |
|
|
}
|
| 988 |
|
|
|
| 989 |
|
|
default {
|
| 990 |
|
|
if {$hlimit > 0 && [$widgets(listbox) size] > $hlimit} {
|
| 991 |
|
|
pack forget $widgets(listbox)
|
| 992 |
|
|
pack $widgets(vsb) -side right -fill y -expand n
|
| 993 |
|
|
pack $widgets(listbox) -side left -fill both -expand y
|
| 994 |
|
|
} else {
|
| 995 |
|
|
pack forget $widgets(vsb)
|
| 996 |
|
|
}
|
| 997 |
|
|
}
|
| 998 |
|
|
}
|
| 999 |
|
|
|
| 1000 |
|
|
return ""
|
| 1001 |
|
|
}
|
| 1002 |
|
|
|
| 1003 |
|
|
# ::combobox::ComputeGeometry --
|
| 1004 |
|
|
#
|
| 1005 |
|
|
# computes the geometry of the dropdown list based on the size of the
|
| 1006 |
|
|
# combobox...
|
| 1007 |
|
|
#
|
| 1008 |
|
|
# Arguments:
|
| 1009 |
|
|
#
|
| 1010 |
|
|
# w widget pathname
|
| 1011 |
|
|
#
|
| 1012 |
|
|
# Returns:
|
| 1013 |
|
|
#
|
| 1014 |
|
|
# the desired geometry of the listbox
|
| 1015 |
|
|
|
| 1016 |
|
|
proc ::combobox::ComputeGeometry {w} {
|
| 1017 |
|
|
upvar ::combobox::${w}::widgets widgets
|
| 1018 |
|
|
upvar ::combobox::${w}::options options
|
| 1019 |
|
|
|
| 1020 |
|
|
if {$options(-height) == 0 && $options(-maxheight) != "0"} {
|
| 1021 |
|
|
# if this is the case, count the items and see if
|
| 1022 |
|
|
# it exceeds our maxheight. If so, set the listbox
|
| 1023 |
|
|
# size to maxheight...
|
| 1024 |
|
|
set nitems [$widgets(listbox) size]
|
| 1025 |
|
|
if {$nitems > $options(-maxheight)} {
|
| 1026 |
|
|
# tweak the height of the listbox
|
| 1027 |
|
|
$widgets(listbox) configure -height $options(-maxheight)
|
| 1028 |
|
|
} else {
|
| 1029 |
|
|
# un-tweak the height of the listbox
|
| 1030 |
|
|
$widgets(listbox) configure -height 0
|
| 1031 |
|
|
}
|
| 1032 |
|
|
update idletasks
|
| 1033 |
|
|
}
|
| 1034 |
|
|
|
| 1035 |
|
|
# compute height and width of the dropdown list
|
| 1036 |
|
|
set bd [$widgets(dropdown) cget -borderwidth]
|
| 1037 |
|
|
set height [expr {[winfo reqheight $widgets(dropdown)] + $bd + $bd}]
|
| 1038 |
|
|
if {[string length $options(-dropdownwidth)] == 0 ||
|
| 1039 |
|
|
$options(-dropdownwidth) == 0} {
|
| 1040 |
|
|
set width [winfo width $widgets(this)]
|
| 1041 |
|
|
} else {
|
| 1042 |
|
|
set m [font measure [$widgets(listbox) cget -font] "m"]
|
| 1043 |
|
|
set width [expr {$options(-dropdownwidth) * $m}]
|
| 1044 |
|
|
}
|
| 1045 |
|
|
|
| 1046 |
|
|
# figure out where to place it on the screen, trying to take into
|
| 1047 |
|
|
# account we may be running under some virtual window manager
|
| 1048 |
|
|
set screenWidth [winfo screenwidth $widgets(this)]
|
| 1049 |
|
|
set screenHeight [winfo screenheight $widgets(this)]
|
| 1050 |
|
|
set rootx [winfo rootx $widgets(this)]
|
| 1051 |
|
|
set rooty [winfo rooty $widgets(this)]
|
| 1052 |
|
|
set vrootx [winfo vrootx $widgets(this)]
|
| 1053 |
|
|
set vrooty [winfo vrooty $widgets(this)]
|
| 1054 |
|
|
|
| 1055 |
|
|
# the x coordinate is simply the rootx of our widget, adjusted for
|
| 1056 |
|
|
# the virtual window. We won't worry about whether the window will
|
| 1057 |
|
|
# be offscreen to the left or right -- we want the illusion that it
|
| 1058 |
|
|
# is part of the entry widget, so if part of the entry widget is off-
|
| 1059 |
|
|
# screen, so will the list. If you want to change the behavior,
|
| 1060 |
|
|
# simply change the if statement... (and be sure to update this
|
| 1061 |
|
|
# comment!)
|
| 1062 |
|
|
set x [expr {$rootx + $vrootx}]
|
| 1063 |
|
|
if {0} {
|
| 1064 |
|
|
set rightEdge [expr {$x + $width}]
|
| 1065 |
|
|
if {$rightEdge > $screenWidth} {
|
| 1066 |
|
|
set x [expr {$screenWidth - $width}]
|
| 1067 |
|
|
}
|
| 1068 |
|
|
if {$x < 0} {set x 0}
|
| 1069 |
|
|
}
|
| 1070 |
|
|
|
| 1071 |
|
|
# the y coordinate is the rooty plus vrooty offset plus
|
| 1072 |
|
|
# the height of the static part of the widget plus 1 for a
|
| 1073 |
|
|
# tiny bit of visual separation...
|
| 1074 |
|
|
set y [expr {$rooty + $vrooty + [winfo reqheight $widgets(this)] + 1}]
|
| 1075 |
|
|
set bottomEdge [expr {$y + $height}]
|
| 1076 |
|
|
|
| 1077 |
|
|
if {$bottomEdge >= $screenHeight} {
|
| 1078 |
|
|
# ok. Fine. Pop it up above the entry widget isntead of
|
| 1079 |
|
|
# below.
|
| 1080 |
|
|
set y [expr {($rooty - $height - 1) + $vrooty}]
|
| 1081 |
|
|
|
| 1082 |
|
|
if {$y < 0} {
|
| 1083 |
|
|
# this means it extends beyond our screen. How annoying.
|
| 1084 |
|
|
# Now we'll try to be real clever and either pop it up or
|
| 1085 |
|
|
# down, depending on which way gives us the biggest list.
|
| 1086 |
|
|
# then, we'll trim the list to fit and force the use of
|
| 1087 |
|
|
# a scrollbar
|
| 1088 |
|
|
|
| 1089 |
|
|
# (sadly, for windows users this measurement doesn't
|
| 1090 |
|
|
# take into consideration the height of the taskbar,
|
| 1091 |
|
|
# but don't blame me -- there isn't any way to detect
|
| 1092 |
|
|
# it or figure out its dimensions. The same probably
|
| 1093 |
|
|
# applies to any window manager with some magic windows
|
| 1094 |
|
|
# glued to the top or bottom of the screen)
|
| 1095 |
|
|
|
| 1096 |
|
|
if {$rooty > [expr {$screenHeight / 2}]} {
|
| 1097 |
|
|
# we are in the lower half of the screen --
|
| 1098 |
|
|
# pop it up. Y is zero; that parts easy. The height
|
| 1099 |
|
|
# is simply the y coordinate of our widget, minus
|
| 1100 |
|
|
# a pixel for some visual separation. The y coordinate
|
| 1101 |
|
|
# will be the topof the screen.
|
| 1102 |
|
|
set y 1
|
| 1103 |
|
|
set height [expr {$rooty - 1 - $y}]
|
| 1104 |
|
|
|
| 1105 |
|
|
} else {
|
| 1106 |
|
|
# we are in the upper half of the screen --
|
| 1107 |
|
|
# pop it down
|
| 1108 |
|
|
set y [expr {$rooty + $vrooty + \
|
| 1109 |
|
|
[winfo reqheight $widgets(this)] + 1}]
|
| 1110 |
|
|
set height [expr {$screenHeight - $y}]
|
| 1111 |
|
|
|
| 1112 |
|
|
}
|
| 1113 |
|
|
|
| 1114 |
|
|
# force a scrollbar
|
| 1115 |
|
|
HandleScrollbar $widgets(this) crop
|
| 1116 |
|
|
}
|
| 1117 |
|
|
}
|
| 1118 |
|
|
|
| 1119 |
|
|
if {$y < 0} {
|
| 1120 |
|
|
# hmmm. Bummer.
|
| 1121 |
|
|
set y 0
|
| 1122 |
|
|
set height $screenheight
|
| 1123 |
|
|
}
|
| 1124 |
|
|
|
| 1125 |
|
|
set geometry [format "=%dx%d+%d+%d" $width $height $x $y]
|
| 1126 |
|
|
|
| 1127 |
|
|
return $geometry
|
| 1128 |
|
|
}
|
| 1129 |
|
|
|
| 1130 |
|
|
# ::combobox::DoInternalWidgetCommand --
|
| 1131 |
|
|
#
|
| 1132 |
|
|
# perform an internal widget command, then mung any error results
|
| 1133 |
|
|
# to look like it came from our megawidget. A lot of work just to
|
| 1134 |
|
|
# give the illusion that our megawidget is an atomic widget
|
| 1135 |
|
|
#
|
| 1136 |
|
|
# Arguments:
|
| 1137 |
|
|
#
|
| 1138 |
|
|
# w widget pathname
|
| 1139 |
|
|
# subwidget pathname of the subwidget
|
| 1140 |
|
|
# command subwidget command to be executed
|
| 1141 |
|
|
# args arguments to the command
|
| 1142 |
|
|
#
|
| 1143 |
|
|
# Returns:
|
| 1144 |
|
|
#
|
| 1145 |
|
|
# The result of the subwidget command, or an error
|
| 1146 |
|
|
|
| 1147 |
|
|
proc ::combobox::DoInternalWidgetCommand {w subwidget command args} {
|
| 1148 |
|
|
upvar ::combobox::${w}::widgets widgets
|
| 1149 |
|
|
upvar ::combobox::${w}::options options
|
| 1150 |
|
|
|
| 1151 |
|
|
set subcommand $command
|
| 1152 |
|
|
set command [concat $widgets($subwidget) $command $args]
|
| 1153 |
|
|
if {[catch $command result]} {
|
| 1154 |
|
|
# replace the subwidget name with the megawidget name
|
| 1155 |
|
|
regsub $widgets($subwidget) $result $widgets(this) result
|
| 1156 |
|
|
|
| 1157 |
|
|
# replace specific instances of the subwidget command
|
| 1158 |
|
|
# with our megawidget command
|
| 1159 |
|
|
switch $subwidget,$subcommand {
|
| 1160 |
|
|
listbox,index {regsub "index" $result "list index" result}
|
| 1161 |
|
|
listbox,insert {regsub "insert" $result "list insert" result}
|
| 1162 |
|
|
listbox,delete {regsub "delete" $result "list delete" result}
|
| 1163 |
|
|
listbox,get {regsub "get" $result "list get" result}
|
| 1164 |
|
|
listbox,size {regsub "size" $result "list size" result}
|
| 1165 |
|
|
}
|
| 1166 |
|
|
error $result
|
| 1167 |
|
|
|
| 1168 |
|
|
} else {
|
| 1169 |
|
|
return $result
|
| 1170 |
|
|
}
|
| 1171 |
|
|
}
|
| 1172 |
|
|
|
| 1173 |
|
|
|
| 1174 |
|
|
# ::combobox::WidgetProc --
|
| 1175 |
|
|
#
|
| 1176 |
|
|
# This gets uses as the widgetproc for an combobox widget.
|
| 1177 |
|
|
# Notice where the widget is created and you'll see that the
|
| 1178 |
|
|
# actual widget proc merely evals this proc with all of the
|
| 1179 |
|
|
# arguments intact.
|
| 1180 |
|
|
#
|
| 1181 |
|
|
# Note that some widget commands are defined "inline" (ie:
|
| 1182 |
|
|
# within this proc), and some do most of their work in
|
| 1183 |
|
|
# separate procs. This is merely because sometimes it was
|
| 1184 |
|
|
# easier to do it one way or the other.
|
| 1185 |
|
|
#
|
| 1186 |
|
|
# Arguments:
|
| 1187 |
|
|
#
|
| 1188 |
|
|
# w widget pathname
|
| 1189 |
|
|
# command widget subcommand
|
| 1190 |
|
|
# args additional arguments; varies with the subcommand
|
| 1191 |
|
|
#
|
| 1192 |
|
|
# Results:
|
| 1193 |
|
|
#
|
| 1194 |
|
|
# Performs the requested widget command
|
| 1195 |
|
|
|
| 1196 |
|
|
proc ::combobox::WidgetProc {w command args} {
|
| 1197 |
|
|
upvar ::combobox::${w}::widgets widgets
|
| 1198 |
|
|
upvar ::combobox::${w}::options options
|
| 1199 |
|
|
upvar ::combobox::${w}::oldFocus oldFocus
|
| 1200 |
|
|
upvar ::combobox::${w}::oldFocus oldGrab
|
| 1201 |
|
|
|
| 1202 |
|
|
set command [::combobox::Canonize $w command $command]
|
| 1203 |
|
|
|
| 1204 |
|
|
# this is just shorthand notation...
|
| 1205 |
|
|
set doWidgetCommand \
|
| 1206 |
|
|
[list ::combobox::DoInternalWidgetCommand $widgets(this)]
|
| 1207 |
|
|
|
| 1208 |
|
|
if {$command == "list"} {
|
| 1209 |
|
|
# ok, the next argument is a list command; we'll
|
| 1210 |
|
|
# rip it from args and append it to command to
|
| 1211 |
|
|
# create a unique internal command
|
| 1212 |
|
|
#
|
| 1213 |
|
|
# NB: because of the sloppy way we are doing this,
|
| 1214 |
|
|
# we'll also let the user enter our secret command
|
| 1215 |
|
|
# directly (eg: listinsert, listdelete), but we
|
| 1216 |
|
|
# won't document that fact
|
| 1217 |
|
|
set command "list-[lindex $args 0]"
|
| 1218 |
|
|
set args [lrange $args 1 end]
|
| 1219 |
|
|
}
|
| 1220 |
|
|
|
| 1221 |
|
|
set result ""
|
| 1222 |
|
|
|
| 1223 |
|
|
# many of these commands are just synonyms for specific
|
| 1224 |
|
|
# commands in one of the subwidgets. We'll get them out
|
| 1225 |
|
|
# of the way first, then do the custom commands.
|
| 1226 |
|
|
switch $command {
|
| 1227 |
|
|
bbox -
|
| 1228 |
|
|
delete -
|
| 1229 |
|
|
get -
|
| 1230 |
|
|
icursor -
|
| 1231 |
|
|
index -
|
| 1232 |
|
|
insert -
|
| 1233 |
|
|
scan -
|
| 1234 |
|
|
selection -
|
| 1235 |
|
|
xview {
|
| 1236 |
|
|
set result [eval $doWidgetCommand entry $command $args]
|
| 1237 |
|
|
}
|
| 1238 |
|
|
list-get {set result [eval $doWidgetCommand listbox get $args]}
|
| 1239 |
|
|
list-index {set result [eval $doWidgetCommand listbox index $args]}
|
| 1240 |
|
|
list-size {set result [eval $doWidgetCommand listbox size $args]}
|
| 1241 |
|
|
|
| 1242 |
|
|
select {
|
| 1243 |
|
|
if {[llength $args] == 1} {
|
| 1244 |
|
|
set index [lindex $args 0]
|
| 1245 |
|
|
set result [Select $widgets(this) $index]
|
| 1246 |
|
|
} else {
|
| 1247 |
|
|
error "usage: $w select index"
|
| 1248 |
|
|
}
|
| 1249 |
|
|
}
|
| 1250 |
|
|
|
| 1251 |
|
|
subwidget {
|
| 1252 |
|
|
set knownWidgets [list button entry listbox dropdown vsb]
|
| 1253 |
|
|
if {[llength $args] == 0} {
|
| 1254 |
|
|
return $knownWidgets
|
| 1255 |
|
|
}
|
| 1256 |
|
|
|
| 1257 |
|
|
set name [lindex $args 0]
|
| 1258 |
|
|
if {[lsearch $knownWidgets $name] != -1} {
|
| 1259 |
|
|
set result $widgets($name)
|
| 1260 |
|
|
} else {
|
| 1261 |
|
|
error "unknown subwidget $name"
|
| 1262 |
|
|
}
|
| 1263 |
|
|
}
|
| 1264 |
|
|
|
| 1265 |
|
|
curselection {
|
| 1266 |
|
|
set result [eval $doWidgetCommand listbox curselection]
|
| 1267 |
|
|
}
|
| 1268 |
|
|
|
| 1269 |
|
|
list-insert {
|
| 1270 |
|
|
eval $doWidgetCommand listbox insert $args
|
| 1271 |
|
|
set result [HandleScrollbar $w "grow"]
|
| 1272 |
|
|
}
|
| 1273 |
|
|
|
| 1274 |
|
|
list-delete {
|
| 1275 |
|
|
eval $doWidgetCommand listbox delete $args
|
| 1276 |
|
|
set result [HandleScrollbar $w "shrink"]
|
| 1277 |
|
|
}
|
| 1278 |
|
|
|
| 1279 |
|
|
toggle {
|
| 1280 |
|
|
# ignore this command if the widget is disabled...
|
| 1281 |
|
|
if {$options(-state) == "disabled"} return
|
| 1282 |
|
|
|
| 1283 |
|
|
# pops down the list if it is not, hides it
|
| 1284 |
|
|
# if it is...
|
| 1285 |
|
|
if {[winfo ismapped $widgets(dropdown)]} {
|
| 1286 |
|
|
set result [$widgets(this) close]
|
| 1287 |
|
|
} else {
|
| 1288 |
|
|
set result [$widgets(this) open]
|
| 1289 |
|
|
}
|
| 1290 |
|
|
}
|
| 1291 |
|
|
|
| 1292 |
|
|
open {
|
| 1293 |
|
|
|
| 1294 |
|
|
# if this is an editable combobox, the focus should
|
| 1295 |
|
|
# be set to the entry widget
|
| 1296 |
|
|
if {$options(-editable)} {
|
| 1297 |
|
|
focus $widgets(entry)
|
| 1298 |
|
|
$widgets(entry) select range 0 end
|
| 1299 |
|
|
$widgets(entry) icursor end
|
| 1300 |
|
|
}
|
| 1301 |
|
|
|
| 1302 |
|
|
# if we are disabled, we won't allow this to happen
|
| 1303 |
|
|
if {$options(-state) == "disabled"} {
|
| 1304 |
|
|
return 0
|
| 1305 |
|
|
}
|
| 1306 |
|
|
|
| 1307 |
|
|
# if there is a -opencommand, execute it now
|
| 1308 |
|
|
if {[string length $options(-opencommand)] > 0} {
|
| 1309 |
|
|
# hmmm... should I do a catch, or just let the normal
|
| 1310 |
|
|
# error handling handle any errors? For now, the latter...
|
| 1311 |
|
|
uplevel \#0 $options(-opencommand)
|
| 1312 |
|
|
}
|
| 1313 |
|
|
|
| 1314 |
|
|
# compute the geometry of the window to pop up, and set
|
| 1315 |
|
|
# it, and force the window manager to take notice
|
| 1316 |
|
|
# (even if it is not presently visible).
|
| 1317 |
|
|
#
|
| 1318 |
|
|
# this isn't strictly necessary if the window is already
|
| 1319 |
|
|
# mapped, but we'll go ahead and set the geometry here
|
| 1320 |
|
|
# since its harmless and *may* actually reset the geometry
|
| 1321 |
|
|
# to something better in some weird case.
|
| 1322 |
|
|
set geometry [::combobox::ComputeGeometry $widgets(this)]
|
| 1323 |
|
|
wm geometry $widgets(dropdown) $geometry
|
| 1324 |
|
|
update idletasks
|
| 1325 |
|
|
|
| 1326 |
|
|
# if we are already open, there's nothing else to do
|
| 1327 |
|
|
if {[winfo ismapped $widgets(dropdown)]} {
|
| 1328 |
|
|
return 0
|
| 1329 |
|
|
}
|
| 1330 |
|
|
|
| 1331 |
|
|
# save the widget that currently has the focus; we'll restore
|
| 1332 |
|
|
# the focus there when we're done
|
| 1333 |
|
|
set oldFocus [focus]
|
| 1334 |
|
|
|
| 1335 |
|
|
# ok, tweak the visual appearance of things and
|
| 1336 |
|
|
# make the list pop up
|
| 1337 |
|
|
$widgets(button) configure -relief sunken
|
| 1338 |
|
|
wm deiconify $widgets(dropdown)
|
| 1339 |
|
|
update idletasks
|
| 1340 |
|
|
raise $widgets(dropdown)
|
| 1341 |
|
|
|
| 1342 |
|
|
# force focus to the entry widget so we can handle keypress
|
| 1343 |
|
|
# events for traversal
|
| 1344 |
|
|
focus -force $widgets(entry)
|
| 1345 |
|
|
|
| 1346 |
|
|
# select something by default, but only if its an
|
| 1347 |
|
|
# exact match...
|
| 1348 |
|
|
::combobox::Find $widgets(this) 1
|
| 1349 |
|
|
|
| 1350 |
|
|
# save the current grab state for the display containing
|
| 1351 |
|
|
# this widget. We'll restore it when we close the dropdown
|
| 1352 |
|
|
# list
|
| 1353 |
|
|
set status "none"
|
| 1354 |
|
|
set grab [grab current $widgets(this)]
|
| 1355 |
|
|
if {$grab != ""} {set status [grab status $grab]}
|
| 1356 |
|
|
set oldGrab [list $grab $status]
|
| 1357 |
|
|
unset grab status
|
| 1358 |
|
|
|
| 1359 |
|
|
# *gasp* do a global grab!!! Mom always told me not to
|
| 1360 |
|
|
# do things like this, but sometimes a man's gotta do
|
| 1361 |
|
|
# what a man's gotta do.
|
| 1362 |
|
|
grab -global $widgets(this)
|
| 1363 |
|
|
|
| 1364 |
|
|
# fake the listbox into thinking it has focus. This is
|
| 1365 |
|
|
# necessary to get scanning initialized properly in the
|
| 1366 |
|
|
# listbox.
|
| 1367 |
|
|
event generate $widgets(listbox) <B1-Enter>
|
| 1368 |
|
|
|
| 1369 |
|
|
return 1
|
| 1370 |
|
|
}
|
| 1371 |
|
|
|
| 1372 |
|
|
close {
|
| 1373 |
|
|
# if we are already closed, don't do anything...
|
| 1374 |
|
|
if {![winfo ismapped $widgets(dropdown)]} {
|
| 1375 |
|
|
return 0
|
| 1376 |
|
|
}
|
| 1377 |
|
|
|
| 1378 |
|
|
# restore the focus and grab, but ignore any errors...
|
| 1379 |
|
|
# we're going to be paranoid and release the grab before
|
| 1380 |
|
|
# trying to set any other grab because we really really
|
| 1381 |
|
|
# really want to make sure the grab is released.
|
| 1382 |
|
|
catch {focus $oldFocus} result
|
| 1383 |
|
|
catch {grab release $widgets(this)}
|
| 1384 |
|
|
catch {
|
| 1385 |
|
|
set status [lindex $oldGrab 1]
|
| 1386 |
|
|
if {$status == "global"} {
|
| 1387 |
|
|
grab -global [lindex $oldGrab 0]
|
| 1388 |
|
|
} elseif {$status == "local"} {
|
| 1389 |
|
|
grab [lindex $oldGrab 0]
|
| 1390 |
|
|
}
|
| 1391 |
|
|
unset status
|
| 1392 |
|
|
}
|
| 1393 |
|
|
|
| 1394 |
|
|
# hides the listbox
|
| 1395 |
|
|
$widgets(button) configure -relief raised
|
| 1396 |
|
|
wm withdraw $widgets(dropdown)
|
| 1397 |
|
|
|
| 1398 |
|
|
# select the data in the entry widget. Not sure
|
| 1399 |
|
|
# why, other than observation seems to suggest that's
|
| 1400 |
|
|
# what windows widgets do.
|
| 1401 |
|
|
set editable [::combobox::GetBoolean $options(-editable)]
|
| 1402 |
|
|
if {$editable} {
|
| 1403 |
|
|
$widgets(entry) selection range 0 end
|
| 1404 |
|
|
$widgets(button) configure -relief raised
|
| 1405 |
|
|
}
|
| 1406 |
|
|
|
| 1407 |
|
|
|
| 1408 |
|
|
# magic tcl stuff (see tk.tcl in the distribution
|
| 1409 |
|
|
# lib directory)
|
| 1410 |
|
|
::combobox::tkCancelRepeat
|
| 1411 |
|
|
|
| 1412 |
|
|
return 1
|
| 1413 |
|
|
}
|
| 1414 |
|
|
|
| 1415 |
|
|
cget {
|
| 1416 |
|
|
if {[llength $args] != 1} {
|
| 1417 |
|
|
error "wrong # args: should be $w cget option"
|
| 1418 |
|
|
}
|
| 1419 |
|
|
set opt [::combobox::Canonize $w option [lindex $args 0]]
|
| 1420 |
|
|
|
| 1421 |
|
|
if {$opt == "-value"} {
|
| 1422 |
|
|
set result [$widgets(entry) get]
|
| 1423 |
|
|
} else {
|
| 1424 |
|
|
set result $options($opt)
|
| 1425 |
|
|
}
|
| 1426 |
|
|
}
|
| 1427 |
|
|
|
| 1428 |
|
|
configure {
|
| 1429 |
|
|
set result [eval ::combobox::Configure {$w} $args]
|
| 1430 |
|
|
}
|
| 1431 |
|
|
|
| 1432 |
|
|
default {
|
| 1433 |
|
|
error "bad option \"$command\""
|
| 1434 |
|
|
}
|
| 1435 |
|
|
}
|
| 1436 |
|
|
|
| 1437 |
|
|
return $result
|
| 1438 |
|
|
}
|
| 1439 |
|
|
|
| 1440 |
|
|
# ::combobox::Configure --
|
| 1441 |
|
|
#
|
| 1442 |
|
|
# Implements the "configure" widget subcommand
|
| 1443 |
|
|
#
|
| 1444 |
|
|
# Arguments:
|
| 1445 |
|
|
#
|
| 1446 |
|
|
# w widget pathname
|
| 1447 |
|
|
# args zero or more option/value pairs (or a single option)
|
| 1448 |
|
|
#
|
| 1449 |
|
|
# Results:
|
| 1450 |
|
|
#
|
| 1451 |
|
|
# Performs typcial "configure" type requests on the widget
|
| 1452 |
|
|
|
| 1453 |
|
|
proc ::combobox::Configure {w args} {
|
| 1454 |
|
|
variable widgetOptions
|
| 1455 |
|
|
variable defaultEntryCursor
|
| 1456 |
|
|
|
| 1457 |
|
|
upvar ::combobox::${w}::widgets widgets
|
| 1458 |
|
|
upvar ::combobox::${w}::options options
|
| 1459 |
|
|
|
| 1460 |
|
|
if {[llength $args] == 0} {
|
| 1461 |
|
|
# hmmm. User must be wanting all configuration information
|
| 1462 |
|
|
# note that if the value of an array element is of length
|
| 1463 |
|
|
# one it is an alias, which needs to be handled slightly
|
| 1464 |
|
|
# differently
|
| 1465 |
|
|
set results {}
|
| 1466 |
|
|
foreach opt [lsort [array names widgetOptions]] {
|
| 1467 |
|
|
if {[llength $widgetOptions($opt)] == 1} {
|
| 1468 |
|
|
set alias $widgetOptions($opt)
|
| 1469 |
|
|
set optName $widgetOptions($alias)
|
| 1470 |
|
|
lappend results [list $opt $optName]
|
| 1471 |
|
|
} else {
|
| 1472 |
|
|
set optName [lindex $widgetOptions($opt) 0]
|
| 1473 |
|
|
set optClass [lindex $widgetOptions($opt) 1]
|
| 1474 |
|
|
set default [option get $w $optName $optClass]
|
| 1475 |
|
|
if {[info exists options($opt)]} {
|
| 1476 |
|
|
lappend results [list $opt $optName $optClass \
|
| 1477 |
|
|
$default $options($opt)]
|
| 1478 |
|
|
} else {
|
| 1479 |
|
|
lappend results [list $opt $optName $optClass \
|
| 1480 |
|
|
$default ""]
|
| 1481 |
|
|
}
|
| 1482 |
|
|
}
|
| 1483 |
|
|
}
|
| 1484 |
|
|
|
| 1485 |
|
|
return $results
|
| 1486 |
|
|
}
|
| 1487 |
|
|
|
| 1488 |
|
|
# one argument means we are looking for configuration
|
| 1489 |
|
|
# information on a single option
|
| 1490 |
|
|
if {[llength $args] == 1} {
|
| 1491 |
|
|
set opt [::combobox::Canonize $w option [lindex $args 0]]
|
| 1492 |
|
|
|
| 1493 |
|
|
set optName [lindex $widgetOptions($opt) 0]
|
| 1494 |
|
|
set optClass [lindex $widgetOptions($opt) 1]
|
| 1495 |
|
|
set default [option get $w $optName $optClass]
|
| 1496 |
|
|
set results [list $opt $optName $optClass \
|
| 1497 |
|
|
$default $options($opt)]
|
| 1498 |
|
|
return $results
|
| 1499 |
|
|
}
|
| 1500 |
|
|
|
| 1501 |
|
|
# if we have an odd number of values, bail.
|
| 1502 |
|
|
if {[expr {[llength $args]%2}] == 1} {
|
| 1503 |
|
|
# hmmm. An odd number of elements in args
|
| 1504 |
|
|
error "value for \"[lindex $args end]\" missing"
|
| 1505 |
|
|
}
|
| 1506 |
|
|
|
| 1507 |
|
|
# Great. An even number of options. Let's make sure they
|
| 1508 |
|
|
# are all valid before we do anything. Note that Canonize
|
| 1509 |
|
|
# will generate an error if it finds a bogus option; otherwise
|
| 1510 |
|
|
# it returns the canonical option name
|
| 1511 |
|
|
foreach {name value} $args {
|
| 1512 |
|
|
set name [::combobox::Canonize $w option $name]
|
| 1513 |
|
|
set opts($name) $value
|
| 1514 |
|
|
}
|
| 1515 |
|
|
|
| 1516 |
|
|
# process all of the configuration options
|
| 1517 |
|
|
# some (actually, most) options require us to
|
| 1518 |
|
|
# do something, like change the attributes of
|
| 1519 |
|
|
# a widget or two. Here's where we do that...
|
| 1520 |
|
|
#
|
| 1521 |
|
|
# note that the handling of disabledforeground and
|
| 1522 |
|
|
# disabledbackground is a little wonky. First, we have
|
| 1523 |
|
|
# to deal with backwards compatibility (ie: tk 8.3 and below
|
| 1524 |
|
|
# didn't have such options for the entry widget), and
|
| 1525 |
|
|
# we have to deal with the fact we might want to disable
|
| 1526 |
|
|
# the entry widget but use the normal foreground/background
|
| 1527 |
|
|
# for when the combobox is not disabled, but not editable either.
|
| 1528 |
|
|
|
| 1529 |
|
|
set updateVisual 0
|
| 1530 |
|
|
foreach option [array names opts] {
|
| 1531 |
|
|
set newValue $opts($option)
|
| 1532 |
|
|
if {[info exists options($option)]} {
|
| 1533 |
|
|
set oldValue $options($option)
|
| 1534 |
|
|
}
|
| 1535 |
|
|
|
| 1536 |
|
|
switch -- $option {
|
| 1537 |
|
|
-buttonbackground {
|
| 1538 |
|
|
$widgets(button) configure -background $newValue
|
| 1539 |
|
|
}
|
| 1540 |
|
|
-background {
|
| 1541 |
|
|
set updateVisual 1
|
| 1542 |
|
|
set options($option) $newValue
|
| 1543 |
|
|
}
|
| 1544 |
|
|
|
| 1545 |
|
|
-borderwidth {
|
| 1546 |
|
|
$widgets(frame) configure -borderwidth $newValue
|
| 1547 |
|
|
set options($option) $newValue
|
| 1548 |
|
|
}
|
| 1549 |
|
|
|
| 1550 |
|
|
-command {
|
| 1551 |
|
|
# nothing else to do...
|
| 1552 |
|
|
set options($option) $newValue
|
| 1553 |
|
|
}
|
| 1554 |
|
|
|
| 1555 |
|
|
-commandstate {
|
| 1556 |
|
|
# do some value checking...
|
| 1557 |
|
|
if {$newValue != "normal" && $newValue != "disabled"} {
|
| 1558 |
|
|
set options($option) $oldValue
|
| 1559 |
|
|
set message "bad state value \"$newValue\";"
|
| 1560 |
|
|
append message " must be normal or disabled"
|
| 1561 |
|
|
error $message
|
| 1562 |
|
|
}
|
| 1563 |
|
|
set options($option) $newValue
|
| 1564 |
|
|
}
|
| 1565 |
|
|
|
| 1566 |
|
|
-cursor {
|
| 1567 |
|
|
$widgets(frame) configure -cursor $newValue
|
| 1568 |
|
|
$widgets(entry) configure -cursor $newValue
|
| 1569 |
|
|
$widgets(listbox) configure -cursor $newValue
|
| 1570 |
|
|
set options($option) $newValue
|
| 1571 |
|
|
}
|
| 1572 |
|
|
|
| 1573 |
|
|
-disabledforeground {
|
| 1574 |
|
|
set updateVisual 1
|
| 1575 |
|
|
set options($option) $newValue
|
| 1576 |
|
|
}
|
| 1577 |
|
|
|
| 1578 |
|
|
-disabledbackground {
|
| 1579 |
|
|
set updateVisual 1
|
| 1580 |
|
|
set options($option) $newValue
|
| 1581 |
|
|
}
|
| 1582 |
|
|
|
| 1583 |
|
|
-dropdownwidth {
|
| 1584 |
|
|
set options($option) $newValue
|
| 1585 |
|
|
}
|
| 1586 |
|
|
|
| 1587 |
|
|
-editable {
|
| 1588 |
|
|
set updateVisual 1
|
| 1589 |
|
|
if {$newValue} {
|
| 1590 |
|
|
# it's editable...
|
| 1591 |
|
|
$widgets(entry) configure \
|
| 1592 |
|
|
-state normal \
|
| 1593 |
|
|
-cursor $defaultEntryCursor
|
| 1594 |
|
|
} else {
|
| 1595 |
|
|
$widgets(entry) configure \
|
| 1596 |
|
|
-state disabled \
|
| 1597 |
|
|
-cursor $options(-cursor)
|
| 1598 |
|
|
}
|
| 1599 |
|
|
set options($option) $newValue
|
| 1600 |
|
|
}
|
| 1601 |
|
|
|
| 1602 |
|
|
-elementborderwidth {
|
| 1603 |
|
|
$widgets(button) configure -borderwidth $newValue
|
| 1604 |
|
|
$widgets(vsb) configure -borderwidth $newValue
|
| 1605 |
|
|
$widgets(dropdown) configure -borderwidth $newValue
|
| 1606 |
|
|
set options($option) $newValue
|
| 1607 |
|
|
}
|
| 1608 |
|
|
|
| 1609 |
|
|
-font {
|
| 1610 |
|
|
$widgets(entry) configure -font $newValue
|
| 1611 |
|
|
$widgets(listbox) configure -font $newValue
|
| 1612 |
|
|
set options($option) $newValue
|
| 1613 |
|
|
}
|
| 1614 |
|
|
|
| 1615 |
|
|
-foreground {
|
| 1616 |
|
|
set updateVisual 1
|
| 1617 |
|
|
set options($option) $newValue
|
| 1618 |
|
|
}
|
| 1619 |
|
|
|
| 1620 |
|
|
-height {
|
| 1621 |
|
|
$widgets(listbox) configure -height $newValue
|
| 1622 |
|
|
HandleScrollbar $w
|
| 1623 |
|
|
set options($option) $newValue
|
| 1624 |
|
|
}
|
| 1625 |
|
|
|
| 1626 |
|
|
-highlightbackground {
|
| 1627 |
|
|
$widgets(frame) configure -highlightbackground $newValue
|
| 1628 |
|
|
set options($option) $newValue
|
| 1629 |
|
|
}
|
| 1630 |
|
|
|
| 1631 |
|
|
-highlightcolor {
|
| 1632 |
|
|
$widgets(frame) configure -highlightcolor $newValue
|
| 1633 |
|
|
set options($option) $newValue
|
| 1634 |
|
|
}
|
| 1635 |
|
|
|
| 1636 |
|
|
-highlightthickness {
|
| 1637 |
|
|
$widgets(frame) configure -highlightthickness $newValue
|
| 1638 |
|
|
set options($option) $newValue
|
| 1639 |
|
|
}
|
| 1640 |
|
|
|
| 1641 |
|
|
-image {
|
| 1642 |
|
|
if {[string length $newValue] > 0} {
|
| 1643 |
|
|
puts "old button width: [$widgets(button) cget -width]"
|
| 1644 |
|
|
$widgets(button) configure \
|
| 1645 |
|
|
-image $newValue \
|
| 1646 |
|
|
-width [expr {[image width $newValue] + 2}]
|
| 1647 |
|
|
puts "new button width: [$widgets(button) cget -width]"
|
| 1648 |
|
|
|
| 1649 |
|
|
} else {
|
| 1650 |
|
|
$widgets(button) configure -image ::combobox::bimage
|
| 1651 |
|
|
}
|
| 1652 |
|
|
set options($option) $newValue
|
| 1653 |
|
|
}
|
| 1654 |
|
|
|
| 1655 |
|
|
-listvar {
|
| 1656 |
|
|
if {[catch {$widgets(listbox) cget -listvar}]} {
|
| 1657 |
|
|
return -code error \
|
| 1658 |
|
|
"-listvar not supported with this version of tk"
|
| 1659 |
|
|
}
|
| 1660 |
|
|
$widgets(listbox) configure -listvar $newValue
|
| 1661 |
|
|
set options($option) $newValue
|
| 1662 |
|
|
}
|
| 1663 |
|
|
|
| 1664 |
|
|
-maxheight {
|
| 1665 |
|
|
# ComputeGeometry may dork with the actual height
|
| 1666 |
|
|
# of the listbox, so let's undork it
|
| 1667 |
|
|
$widgets(listbox) configure -height $options(-height)
|
| 1668 |
|
|
HandleScrollbar $w
|
| 1669 |
|
|
set options($option) $newValue
|
| 1670 |
|
|
}
|
| 1671 |
|
|
|
| 1672 |
|
|
-opencommand {
|
| 1673 |
|
|
# nothing else to do...
|
| 1674 |
|
|
set options($option) $newValue
|
| 1675 |
|
|
}
|
| 1676 |
|
|
|
| 1677 |
|
|
-relief {
|
| 1678 |
|
|
$widgets(frame) configure -relief $newValue
|
| 1679 |
|
|
set options($option) $newValue
|
| 1680 |
|
|
}
|
| 1681 |
|
|
|
| 1682 |
|
|
-selectbackground {
|
| 1683 |
|
|
$widgets(entry) configure -selectbackground $newValue
|
| 1684 |
|
|
$widgets(listbox) configure -selectbackground $newValue
|
| 1685 |
|
|
set options($option) $newValue
|
| 1686 |
|
|
}
|
| 1687 |
|
|
|
| 1688 |
|
|
-selectborderwidth {
|
| 1689 |
|
|
$widgets(entry) configure -selectborderwidth $newValue
|
| 1690 |
|
|
$widgets(listbox) configure -selectborderwidth $newValue
|
| 1691 |
|
|
set options($option) $newValue
|
| 1692 |
|
|
}
|
| 1693 |
|
|
|
| 1694 |
|
|
-selectforeground {
|
| 1695 |
|
|
$widgets(entry) configure -selectforeground $newValue
|
| 1696 |
|
|
$widgets(listbox) configure -selectforeground $newValue
|
| 1697 |
|
|
set options($option) $newValue
|
| 1698 |
|
|
}
|
| 1699 |
|
|
|
| 1700 |
|
|
-state {
|
| 1701 |
|
|
if {$newValue == "normal"} {
|
| 1702 |
|
|
set updateVisual 1
|
| 1703 |
|
|
# it's enabled
|
| 1704 |
|
|
|
| 1705 |
|
|
set editable [::combobox::GetBoolean \
|
| 1706 |
|
|
$options(-editable)]
|
| 1707 |
|
|
if {$editable} {
|
| 1708 |
|
|
$widgets(entry) configure -state normal
|
| 1709 |
|
|
$widgets(entry) configure -takefocus 1
|
| 1710 |
|
|
}
|
| 1711 |
|
|
|
| 1712 |
|
|
# note that $widgets(button) is actually a label,
|
| 1713 |
|
|
# not a button. And being able to disable labels
|
| 1714 |
|
|
# wasn't possible until tk 8.3. (makes me wonder
|
| 1715 |
|
|
# why I chose to use a label, but that answer is
|
| 1716 |
|
|
# lost to antiquity)
|
| 1717 |
|
|
if {[info patchlevel] >= 8.3} {
|
| 1718 |
|
|
$widgets(button) configure -state normal
|
| 1719 |
|
|
}
|
| 1720 |
|
|
|
| 1721 |
|
|
} elseif {$newValue == "disabled"} {
|
| 1722 |
|
|
set updateVisual 1
|
| 1723 |
|
|
# it's disabled
|
| 1724 |
|
|
$widgets(entry) configure -state disabled
|
| 1725 |
|
|
$widgets(entry) configure -takefocus 0
|
| 1726 |
|
|
# note that $widgets(button) is actually a label,
|
| 1727 |
|
|
# not a button. And being able to disable labels
|
| 1728 |
|
|
# wasn't possible until tk 8.3. (makes me wonder
|
| 1729 |
|
|
# why I chose to use a label, but that answer is
|
| 1730 |
|
|
# lost to antiquity)
|
| 1731 |
|
|
if {$::tcl_version >= 8.3} {
|
| 1732 |
|
|
$widgets(button) configure -state disabled
|
| 1733 |
|
|
}
|
| 1734 |
|
|
|
| 1735 |
|
|
} else {
|
| 1736 |
|
|
set options($option) $oldValue
|
| 1737 |
|
|
set message "bad state value \"$newValue\";"
|
| 1738 |
|
|
append message " must be normal or disabled"
|
| 1739 |
|
|
error $message
|
| 1740 |
|
|
}
|
| 1741 |
|
|
|
| 1742 |
|
|
set options($option) $newValue
|
| 1743 |
|
|
}
|
| 1744 |
|
|
|
| 1745 |
|
|
-takefocus {
|
| 1746 |
|
|
$widgets(entry) configure -takefocus $newValue
|
| 1747 |
|
|
set options($option) $newValue
|
| 1748 |
|
|
}
|
| 1749 |
|
|
|
| 1750 |
|
|
-textvariable {
|
| 1751 |
|
|
$widgets(entry) configure -textvariable $newValue
|
| 1752 |
|
|
set options($option) $newValue
|
| 1753 |
|
|
}
|
| 1754 |
|
|
|
| 1755 |
|
|
-value {
|
| 1756 |
|
|
::combobox::SetValue $widgets(this) $newValue
|
| 1757 |
|
|
set options($option) $newValue
|
| 1758 |
|
|
}
|
| 1759 |
|
|
|
| 1760 |
|
|
-width {
|
| 1761 |
|
|
$widgets(entry) configure -width $newValue
|
| 1762 |
|
|
$widgets(listbox) configure -width $newValue
|
| 1763 |
|
|
set options($option) $newValue
|
| 1764 |
|
|
}
|
| 1765 |
|
|
|
| 1766 |
|
|
-xscrollcommand {
|
| 1767 |
|
|
$widgets(entry) configure -xscrollcommand $newValue
|
| 1768 |
|
|
set options($option) $newValue
|
| 1769 |
|
|
}
|
| 1770 |
|
|
}
|
| 1771 |
|
|
|
| 1772 |
|
|
if {$updateVisual} {UpdateVisualAttributes $w}
|
| 1773 |
|
|
}
|
| 1774 |
|
|
}
|
| 1775 |
|
|
|
| 1776 |
|
|
# ::combobox::UpdateVisualAttributes --
|
| 1777 |
|
|
#
|
| 1778 |
|
|
# sets the visual attributes (foreground, background mostly)
|
| 1779 |
|
|
# based on the current state of the widget (normal/disabled,
|
| 1780 |
|
|
# editable/non-editable)
|
| 1781 |
|
|
#
|
| 1782 |
|
|
# why a proc for such a simple thing? Well, in addition to the
|
| 1783 |
|
|
# various states of the widget, we also have to consider the
|
| 1784 |
|
|
# version of tk being used -- versions from 8.4 and beyond have
|
| 1785 |
|
|
# the notion of disabled foreground/background options for various
|
| 1786 |
|
|
# widgets. All of the permutations can get nasty, so we encapsulate
|
| 1787 |
|
|
# it all in one spot.
|
| 1788 |
|
|
#
|
| 1789 |
|
|
# note also that we don't handle all visual attributes here; just
|
| 1790 |
|
|
# the ones that depend on the state of the widget. The rest are
|
| 1791 |
|
|
# handled on a case by case basis
|
| 1792 |
|
|
#
|
| 1793 |
|
|
# Arguments:
|
| 1794 |
|
|
# w widget pathname
|
| 1795 |
|
|
#
|
| 1796 |
|
|
# Returns:
|
| 1797 |
|
|
# empty string
|
| 1798 |
|
|
|
| 1799 |
|
|
proc ::combobox::UpdateVisualAttributes {w} {
|
| 1800 |
|
|
|
| 1801 |
|
|
upvar ::combobox::${w}::widgets widgets
|
| 1802 |
|
|
upvar ::combobox::${w}::options options
|
| 1803 |
|
|
|
| 1804 |
|
|
if {$options(-state) == "normal"} {
|
| 1805 |
|
|
|
| 1806 |
|
|
set foreground $options(-foreground)
|
| 1807 |
|
|
set background $options(-background)
|
| 1808 |
|
|
|
| 1809 |
|
|
} elseif {$options(-state) == "disabled"} {
|
| 1810 |
|
|
|
| 1811 |
|
|
set foreground $options(-disabledforeground)
|
| 1812 |
|
|
set background $options(-disabledbackground)
|
| 1813 |
|
|
}
|
| 1814 |
|
|
|
| 1815 |
|
|
$widgets(entry) configure -foreground $foreground -background $background
|
| 1816 |
|
|
$widgets(listbox) configure -foreground $foreground -background $background
|
| 1817 |
|
|
$widgets(button) configure -foreground $foreground
|
| 1818 |
|
|
$widgets(vsb) configure -background $background -troughcolor $background
|
| 1819 |
|
|
$widgets(frame) configure -background $background
|
| 1820 |
|
|
|
| 1821 |
|
|
# we need to set the disabled colors in case our widget is disabled.
|
| 1822 |
|
|
# We could actually check for disabled-ness, but we also need to
|
| 1823 |
|
|
# check whether we're enabled but not editable, in which case the
|
| 1824 |
|
|
# entry widget is disabled but we still want the enabled colors. It's
|
| 1825 |
|
|
# easier just to set everything and be done with it.
|
| 1826 |
|
|
|
| 1827 |
|
|
if {$::tcl_version >= 8.4} {
|
| 1828 |
|
|
$widgets(entry) configure \
|
| 1829 |
|
|
-disabledforeground $foreground \
|
| 1830 |
|
|
-disabledbackground $background
|
| 1831 |
|
|
$widgets(button) configure -disabledforeground $foreground
|
| 1832 |
|
|
$widgets(listbox) configure -disabledforeground $foreground
|
| 1833 |
|
|
}
|
| 1834 |
|
|
}
|
| 1835 |
|
|
|
| 1836 |
|
|
# ::combobox::SetValue --
|
| 1837 |
|
|
#
|
| 1838 |
|
|
# sets the value of the combobox and calls the -command,
|
| 1839 |
|
|
# if defined
|
| 1840 |
|
|
#
|
| 1841 |
|
|
# Arguments:
|
| 1842 |
|
|
#
|
| 1843 |
|
|
# w widget pathname
|
| 1844 |
|
|
# newValue the new value of the combobox
|
| 1845 |
|
|
#
|
| 1846 |
|
|
# Returns
|
| 1847 |
|
|
#
|
| 1848 |
|
|
# Empty string
|
| 1849 |
|
|
|
| 1850 |
|
|
proc ::combobox::SetValue {w newValue} {
|
| 1851 |
|
|
|
| 1852 |
|
|
upvar ::combobox::${w}::widgets widgets
|
| 1853 |
|
|
upvar ::combobox::${w}::options options
|
| 1854 |
|
|
upvar ::combobox::${w}::ignoreTrace ignoreTrace
|
| 1855 |
|
|
upvar ::combobox::${w}::oldValue oldValue
|
| 1856 |
|
|
|
| 1857 |
|
|
if {[info exists options(-textvariable)] \
|
| 1858 |
|
|
&& [string length $options(-textvariable)] > 0} {
|
| 1859 |
|
|
set variable ::$options(-textvariable)
|
| 1860 |
|
|
set $variable $newValue
|
| 1861 |
|
|
} else {
|
| 1862 |
|
|
set oldstate [$widgets(entry) cget -state]
|
| 1863 |
|
|
$widgets(entry) configure -state normal
|
| 1864 |
|
|
$widgets(entry) delete 0 end
|
| 1865 |
|
|
$widgets(entry) insert 0 $newValue
|
| 1866 |
|
|
$widgets(entry) configure -state $oldstate
|
| 1867 |
|
|
}
|
| 1868 |
|
|
|
| 1869 |
|
|
# set our internal textvariable; this will cause any public
|
| 1870 |
|
|
# textvariable (ie: defined by the user) to be updated as
|
| 1871 |
|
|
# well
|
| 1872 |
|
|
# set ::combobox::${w}::entryTextVariable $newValue
|
| 1873 |
|
|
|
| 1874 |
|
|
# redefine our concept of the "old value". Do it before running
|
| 1875 |
|
|
# any associated command so we can be sure it happens even
|
| 1876 |
|
|
# if the command somehow fails.
|
| 1877 |
|
|
set oldValue $newValue
|
| 1878 |
|
|
|
| 1879 |
|
|
|
| 1880 |
|
|
# call the associated command. The proc will handle whether or
|
| 1881 |
|
|
# not to actually call it, and with what args
|
| 1882 |
|
|
CallCommand $w $newValue
|
| 1883 |
|
|
|
| 1884 |
|
|
return ""
|
| 1885 |
|
|
}
|
| 1886 |
|
|
|
| 1887 |
|
|
# ::combobox::CallCommand --
|
| 1888 |
|
|
#
|
| 1889 |
|
|
# calls the associated command, if any, appending the new
|
| 1890 |
|
|
# value to the command to be called.
|
| 1891 |
|
|
#
|
| 1892 |
|
|
# Arguments:
|
| 1893 |
|
|
#
|
| 1894 |
|
|
# w widget pathname
|
| 1895 |
|
|
# newValue the new value of the combobox
|
| 1896 |
|
|
#
|
| 1897 |
|
|
# Returns
|
| 1898 |
|
|
#
|
| 1899 |
|
|
# empty string
|
| 1900 |
|
|
|
| 1901 |
|
|
proc ::combobox::CallCommand {w newValue} {
|
| 1902 |
|
|
upvar ::combobox::${w}::widgets widgets
|
| 1903 |
|
|
upvar ::combobox::${w}::options options
|
| 1904 |
|
|
|
| 1905 |
|
|
# call the associated command, if defined and -commandstate is
|
| 1906 |
|
|
# set to "normal"
|
| 1907 |
|
|
if {$options(-commandstate) == "normal" && \
|
| 1908 |
|
|
[string length $options(-command)] > 0} {
|
| 1909 |
|
|
set args [list $widgets(this) $newValue]
|
| 1910 |
|
|
uplevel \#0 $options(-command) $args
|
| 1911 |
|
|
}
|
| 1912 |
|
|
}
|
| 1913 |
|
|
|
| 1914 |
|
|
|
| 1915 |
|
|
# ::combobox::GetBoolean --
|
| 1916 |
|
|
#
|
| 1917 |
|
|
# returns the value of a (presumably) boolean string (ie: it should
|
| 1918 |
|
|
# do the right thing if the string is "yes", "no", "true", 1, etc
|
| 1919 |
|
|
#
|
| 1920 |
|
|
# Arguments:
|
| 1921 |
|
|
#
|
| 1922 |
|
|
# value value to be converted
|
| 1923 |
|
|
# errorValue a default value to be returned in case of an error
|
| 1924 |
|
|
#
|
| 1925 |
|
|
# Returns:
|
| 1926 |
|
|
#
|
| 1927 |
|
|
# a 1 or zero, or the value of errorValue if the string isn't
|
| 1928 |
|
|
# a proper boolean value
|
| 1929 |
|
|
|
| 1930 |
|
|
proc ::combobox::GetBoolean {value {errorValue 1}} {
|
| 1931 |
|
|
if {[catch {expr {([string trim $value])?1:0}} res]} {
|
| 1932 |
|
|
return $errorValue
|
| 1933 |
|
|
} else {
|
| 1934 |
|
|
return $res
|
| 1935 |
|
|
}
|
| 1936 |
|
|
}
|
| 1937 |
|
|
|
| 1938 |
|
|
# ::combobox::convert --
|
| 1939 |
|
|
#
|
| 1940 |
|
|
# public routine to convert %x, %y and %W binding substitutions.
|
| 1941 |
|
|
# Given an x, y and or %W value relative to a given widget, this
|
| 1942 |
|
|
# routine will convert the values to be relative to the combobox
|
| 1943 |
|
|
# widget. For example, it could be used in a binding like this:
|
| 1944 |
|
|
#
|
| 1945 |
|
|
# bind .combobox <blah> {doSomething [::combobox::convert %W -x %x]}
|
| 1946 |
|
|
#
|
| 1947 |
|
|
# Note that this procedure is *not* exported, but is intended for
|
| 1948 |
|
|
# public use. It is not exported because the name could easily
|
| 1949 |
|
|
# clash with existing commands.
|
| 1950 |
|
|
#
|
| 1951 |
|
|
# Arguments:
|
| 1952 |
|
|
#
|
| 1953 |
|
|
# w a widget path; typically the actual result of a %W
|
| 1954 |
|
|
# substitution in a binding. It should be either a
|
| 1955 |
|
|
# combobox widget or one of its subwidgets
|
| 1956 |
|
|
#
|
| 1957 |
|
|
# args should one or more of the following arguments or
|
| 1958 |
|
|
# pairs of arguments:
|
| 1959 |
|
|
#
|
| 1960 |
|
|
# -x <x> will convert the value <x>; typically <x> will
|
| 1961 |
|
|
# be the result of a %x substitution
|
| 1962 |
|
|
# -y <y> will convert the value <y>; typically <y> will
|
| 1963 |
|
|
# be the result of a %y substitution
|
| 1964 |
|
|
# -W (or -w) will return the name of the combobox widget
|
| 1965 |
|
|
# which is the parent of $w
|
| 1966 |
|
|
#
|
| 1967 |
|
|
# Returns:
|
| 1968 |
|
|
#
|
| 1969 |
|
|
# a list of the requested values. For example, a single -w will
|
| 1970 |
|
|
# result in a list of one items, the name of the combobox widget.
|
| 1971 |
|
|
# Supplying "-x 10 -y 20 -W" (in any order) will return a list of
|
| 1972 |
|
|
# three values: the converted x and y values, and the name of
|
| 1973 |
|
|
# the combobox widget.
|
| 1974 |
|
|
|
| 1975 |
|
|
proc ::combobox::convert {w args} {
|
| 1976 |
|
|
set result {}
|
| 1977 |
|
|
if {![winfo exists $w]} {
|
| 1978 |
|
|
error "window \"$w\" doesn't exist"
|
| 1979 |
|
|
}
|
| 1980 |
|
|
|
| 1981 |
|
|
while {[llength $args] > 0} {
|
| 1982 |
|
|
set option [lindex $args 0]
|
| 1983 |
|
|
set args [lrange $args 1 end]
|
| 1984 |
|
|
|
| 1985 |
|
|
switch -exact -- $option {
|
| 1986 |
|
|
-x {
|
| 1987 |
|
|
set value [lindex $args 0]
|
| 1988 |
|
|
set args [lrange $args 1 end]
|
| 1989 |
|
|
set win $w
|
| 1990 |
|
|
while {[winfo class $win] != "Combobox"} {
|
| 1991 |
|
|
incr value [winfo x $win]
|
| 1992 |
|
|
set win [winfo parent $win]
|
| 1993 |
|
|
if {$win == "."} break
|
| 1994 |
|
|
}
|
| 1995 |
|
|
lappend result $value
|
| 1996 |
|
|
}
|
| 1997 |
|
|
|
| 1998 |
|
|
-y {
|
| 1999 |
|
|
set value [lindex $args 0]
|
| 2000 |
|
|
set args [lrange $args 1 end]
|
| 2001 |
|
|
set win $w
|
| 2002 |
|
|
while {[winfo class $win] != "Combobox"} {
|
| 2003 |
|
|
incr value [winfo y $win]
|
| 2004 |
|
|
set win [winfo parent $win]
|
| 2005 |
|
|
if {$win == "."} break
|
| 2006 |
|
|
}
|
| 2007 |
|
|
lappend result $value
|
| 2008 |
|
|
}
|
| 2009 |
|
|
|
| 2010 |
|
|
-w -
|
| 2011 |
|
|
-W {
|
| 2012 |
|
|
set win $w
|
| 2013 |
|
|
while {[winfo class $win] != "Combobox"} {
|
| 2014 |
|
|
set win [winfo parent $win]
|
| 2015 |
|
|
if {$win == "."} break;
|
| 2016 |
|
|
}
|
| 2017 |
|
|
lappend result $win
|
| 2018 |
|
|
}
|
| 2019 |
|
|
}
|
| 2020 |
|
|
}
|
| 2021 |
|
|
return $result
|
| 2022 |
|
|
}
|
| 2023 |
|
|
|
| 2024 |
|
|
# ::combobox::Canonize --
|
| 2025 |
|
|
#
|
| 2026 |
|
|
# takes a (possibly abbreviated) option or command name and either
|
| 2027 |
|
|
# returns the canonical name or an error
|
| 2028 |
|
|
#
|
| 2029 |
|
|
# Arguments:
|
| 2030 |
|
|
#
|
| 2031 |
|
|
# w widget pathname
|
| 2032 |
|
|
# object type of object to canonize; must be one of "command",
|
| 2033 |
|
|
# "option", "scan command" or "list command"
|
| 2034 |
|
|
# opt the option (or command) to be canonized
|
| 2035 |
|
|
#
|
| 2036 |
|
|
# Returns:
|
| 2037 |
|
|
#
|
| 2038 |
|
|
# Returns either the canonical form of an option or command,
|
| 2039 |
|
|
# or raises an error if the option or command is unknown or
|
| 2040 |
|
|
# ambiguous.
|
| 2041 |
|
|
|
| 2042 |
|
|
proc ::combobox::Canonize {w object opt} {
|
| 2043 |
|
|
variable widgetOptions
|
| 2044 |
|
|
variable columnOptions
|
| 2045 |
|
|
variable widgetCommands
|
| 2046 |
|
|
variable listCommands
|
| 2047 |
|
|
variable scanCommands
|
| 2048 |
|
|
|
| 2049 |
|
|
switch $object {
|
| 2050 |
|
|
command {
|
| 2051 |
|
|
if {[lsearch -exact $widgetCommands $opt] >= 0} {
|
| 2052 |
|
|
return $opt
|
| 2053 |
|
|
}
|
| 2054 |
|
|
|
| 2055 |
|
|
# command names aren't stored in an array, and there
|
| 2056 |
|
|
# isn't a way to get all the matches in a list, so
|
| 2057 |
|
|
# we'll stuff the commands in a temporary array so
|
| 2058 |
|
|
# we can use [array names]
|
| 2059 |
|
|
set list $widgetCommands
|
| 2060 |
|
|
foreach element $list {
|
| 2061 |
|
|
set tmp($element) ""
|
| 2062 |
|
|
}
|
| 2063 |
|
|
set matches [array names tmp ${opt}*]
|
| 2064 |
|
|
}
|
| 2065 |
|
|
|
| 2066 |
|
|
{list command} {
|
| 2067 |
|
|
if {[lsearch -exact $listCommands $opt] >= 0} {
|
| 2068 |
|
|
return $opt
|
| 2069 |
|
|
}
|
| 2070 |
|
|
|
| 2071 |
|
|
# command names aren't stored in an array, and there
|
| 2072 |
|
|
# isn't a way to get all the matches in a list, so
|
| 2073 |
|
|
# we'll stuff the commands in a temporary array so
|
| 2074 |
|
|
# we can use [array names]
|
| 2075 |
|
|
set list $listCommands
|
| 2076 |
|
|
foreach element $list {
|
| 2077 |
|
|
set tmp($element) ""
|
| 2078 |
|
|
}
|
| 2079 |
|
|
set matches [array names tmp ${opt}*]
|
| 2080 |
|
|
}
|
| 2081 |
|
|
|
| 2082 |
|
|
{scan command} {
|
| 2083 |
|
|
if {[lsearch -exact $scanCommands $opt] >= 0} {
|
| 2084 |
|
|
return $opt
|
| 2085 |
|
|
}
|
| 2086 |
|
|
|
| 2087 |
|
|
# command names aren't stored in an array, and there
|
| 2088 |
|
|
# isn't a way to get all the matches in a list, so
|
| 2089 |
|
|
# we'll stuff the commands in a temporary array so
|
| 2090 |
|
|
# we can use [array names]
|
| 2091 |
|
|
set list $scanCommands
|
| 2092 |
|
|
foreach element $list {
|
| 2093 |
|
|
set tmp($element) ""
|
| 2094 |
|
|
}
|
| 2095 |
|
|
set matches [array names tmp ${opt}*]
|
| 2096 |
|
|
}
|
| 2097 |
|
|
|
| 2098 |
|
|
option {
|
| 2099 |
|
|
if {[info exists widgetOptions($opt)] \
|
| 2100 |
|
|
&& [llength $widgetOptions($opt)] == 2} {
|
| 2101 |
|
|
return $opt
|
| 2102 |
|
|
}
|
| 2103 |
|
|
set list [array names widgetOptions]
|
| 2104 |
|
|
set matches [array names widgetOptions ${opt}*]
|
| 2105 |
|
|
}
|
| 2106 |
|
|
|
| 2107 |
|
|
}
|
| 2108 |
|
|
|
| 2109 |
|
|
if {[llength $matches] == 0} {
|
| 2110 |
|
|
set choices [HumanizeList $list]
|
| 2111 |
|
|
error "unknown $object \"$opt\"; must be one of $choices"
|
| 2112 |
|
|
|
| 2113 |
|
|
} elseif {[llength $matches] == 1} {
|
| 2114 |
|
|
set opt [lindex $matches 0]
|
| 2115 |
|
|
|
| 2116 |
|
|
# deal with option aliases
|
| 2117 |
|
|
switch $object {
|
| 2118 |
|
|
option {
|
| 2119 |
|
|
set opt [lindex $matches 0]
|
| 2120 |
|
|
if {[llength $widgetOptions($opt)] == 1} {
|
| 2121 |
|
|
set opt $widgetOptions($opt)
|
| 2122 |
|
|
}
|
| 2123 |
|
|
}
|
| 2124 |
|
|
}
|
| 2125 |
|
|
|
| 2126 |
|
|
return $opt
|
| 2127 |
|
|
|
| 2128 |
|
|
} else {
|
| 2129 |
|
|
set choices [HumanizeList $list]
|
| 2130 |
|
|
error "ambiguous $object \"$opt\"; must be one of $choices"
|
| 2131 |
|
|
}
|
| 2132 |
|
|
}
|
| 2133 |
|
|
|
| 2134 |
|
|
# ::combobox::HumanizeList --
|
| 2135 |
|
|
#
|
| 2136 |
|
|
# Returns a human-readable form of a list by separating items
|
| 2137 |
|
|
# by columns, but separating the last two elements with "or"
|
| 2138 |
|
|
# (eg: foo, bar or baz)
|
| 2139 |
|
|
#
|
| 2140 |
|
|
# Arguments:
|
| 2141 |
|
|
#
|
| 2142 |
|
|
# list a valid tcl list
|
| 2143 |
|
|
#
|
| 2144 |
|
|
# Results:
|
| 2145 |
|
|
#
|
| 2146 |
|
|
# A string which as all of the elements joined with ", " or
|
| 2147 |
|
|
# the word " or "
|
| 2148 |
|
|
|
| 2149 |
|
|
proc ::combobox::HumanizeList {list} {
|
| 2150 |
|
|
|
| 2151 |
|
|
if {[llength $list] == 1} {
|
| 2152 |
|
|
return [lindex $list 0]
|
| 2153 |
|
|
} else {
|
| 2154 |
|
|
set list [lsort $list]
|
| 2155 |
|
|
set secondToLast [expr {[llength $list] -2}]
|
| 2156 |
|
|
set most [lrange $list 0 $secondToLast]
|
| 2157 |
|
|
set last [lindex $list end]
|
| 2158 |
|
|
|
| 2159 |
|
|
return "[join $most {, }] or $last"
|
| 2160 |
|
|
}
|
| 2161 |
|
|
}
|
| 2162 |
|
|
|
| 2163 |
|
|
# This is some backwards-compatibility code to handle TIP 44
|
| 2164 |
|
|
# (http://purl.org/tcl/tip/44.html). For all private tk commands
|
| 2165 |
|
|
# used by this widget, we'll make duplicates of the procs in the
|
| 2166 |
|
|
# combobox namespace.
|
| 2167 |
|
|
#
|
| 2168 |
|
|
# I'm not entirely convinced this is the right thing to do. I probably
|
| 2169 |
|
|
# shouldn't even be using the private commands. Then again, maybe the
|
| 2170 |
|
|
# private commands really should be public. Oh well; it works so it
|
| 2171 |
|
|
# must be OK...
|
| 2172 |
|
|
foreach command {TabToWindow CancelRepeat ListboxUpDown} {
|
| 2173 |
|
|
if {[llength [info commands ::combobox::tk$command]] == 1} break;
|
| 2174 |
|
|
|
| 2175 |
|
|
set tmp [info commands tk$command]
|
| 2176 |
|
|
set proc ::combobox::tk$command
|
| 2177 |
|
|
if {[llength [info commands tk$command]] == 1} {
|
| 2178 |
|
|
set command [namespace which [lindex $tmp 0]]
|
| 2179 |
|
|
proc $proc {args} "uplevel $command \$args"
|
| 2180 |
|
|
} else {
|
| 2181 |
|
|
if {[llength [info commands ::tk::$command]] == 1} {
|
| 2182 |
|
|
proc $proc {args} "uplevel ::tk::$command \$args"
|
| 2183 |
|
|
}
|
| 2184 |
|
|
}
|
| 2185 |
|
|
}
|
| 2186 |
|
|
|
| 2187 |
|
|
# end of combobox.tcl
|
| 2188 |
|
|
|