1 |
578 |
markom |
# Copyright (c) 1998, Bryan Oakley
|
2 |
|
|
# All Rights Reservered
|
3 |
|
|
#
|
4 |
|
|
# Bryan Oakley
|
5 |
|
|
# oakley@channelpoint.com
|
6 |
|
|
#
|
7 |
|
|
# combobox v1.05 August 17, 1998
|
8 |
|
|
# a dropdown combobox widget
|
9 |
|
|
#
|
10 |
|
|
# this code is freely distributable without restriction, but is
|
11 |
|
|
# provided as-is with no waranty expressed or implied.
|
12 |
|
|
#
|
13 |
|
|
# Standard Options:
|
14 |
|
|
#
|
15 |
|
|
# -background -borderwidth -font -foreground -highlightthickness
|
16 |
|
|
# -highlightbackground -relief -state -textvariable
|
17 |
|
|
# -selectbackground -selectborderwidth -selectforeground
|
18 |
|
|
# -cursor
|
19 |
|
|
#
|
20 |
|
|
# Custom Options:
|
21 |
|
|
# -command a command to run whenever the value is changed.
|
22 |
|
|
# This command will be called with two values
|
23 |
|
|
# appended to it -- the name of the widget and the
|
24 |
|
|
# new value. It is run at the global scope.
|
25 |
|
|
# -editable if true, user can type into edit box; false, she can't
|
26 |
|
|
# -height specifies height of dropdown list, in lines
|
27 |
|
|
# -image image for the button to pop down the list...
|
28 |
|
|
# -maxheight specifies maximum height of dropdown list, in lines
|
29 |
|
|
# -value duh
|
30 |
|
|
# -width treated just like the -width option to entry widgets
|
31 |
|
|
#
|
32 |
|
|
#
|
33 |
|
|
# widget commands:
|
34 |
|
|
#
|
35 |
|
|
# (see source... there's a bunch; duplicates of most of the entry
|
36 |
|
|
# widget commands, plus commands to manipulate the listbox and a couple
|
37 |
|
|
# unique to the combobox as a whole)
|
38 |
|
|
#
|
39 |
|
|
# to create a combobox:
|
40 |
|
|
#
|
41 |
|
|
# namespace import combobox::combobox
|
42 |
|
|
# combobox .foo ?options?
|
43 |
|
|
#
|
44 |
|
|
#
|
45 |
|
|
# thanks to the following people who provided beta test support or
|
46 |
|
|
# patches to the code:
|
47 |
|
|
#
|
48 |
|
|
# Martin M. Hunt (hunt@cygnus.com)
|
49 |
|
|
|
50 |
|
|
package require Tk 8.0
|
51 |
|
|
package provide combobox 1.05
|
52 |
|
|
|
53 |
|
|
namespace eval ::combobox {
|
54 |
|
|
global tcl_platform
|
55 |
|
|
# this is the public interface
|
56 |
|
|
namespace export combobox
|
57 |
|
|
|
58 |
|
|
if {$tcl_platform(platform) != "windows"} {
|
59 |
|
|
set sbtest ". "
|
60 |
|
|
radiobutton $sbtest
|
61 |
|
|
set disabledfg [$sbtest cget -disabledforeground]
|
62 |
|
|
set enabledfg [$sbtest cget -fg]
|
63 |
|
|
} else {
|
64 |
|
|
set disabledfg SystemDisabledText
|
65 |
|
|
set enabledfg SystemWindowText
|
66 |
|
|
}
|
67 |
|
|
|
68 |
|
|
# the image used for the button...
|
69 |
|
|
image create bitmap ::combobox::bimage -data {
|
70 |
|
|
#define down_arrow_width 15
|
71 |
|
|
#define down_arrow_height 15
|
72 |
|
|
static char down_arrow_bits[] = {
|
73 |
|
|
0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0xf8,0x8f,0xf0,0x87,0xe0,
|
74 |
|
|
0x83,0xc0,0x81,0x80,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80,0x00,0x80
|
75 |
|
|
};
|
76 |
|
|
}
|
77 |
|
|
}
|
78 |
|
|
|
79 |
|
|
# this is the command that gets exported, and creates a new
|
80 |
|
|
# combobox widget. It works like other widget commands in that
|
81 |
|
|
# it takes as its first argument a widget path, and any remaining
|
82 |
|
|
# arguments are option/value pairs for the widget
|
83 |
|
|
proc ::combobox::combobox {w args} {
|
84 |
|
|
|
85 |
|
|
# build it...
|
86 |
|
|
eval build $w $args
|
87 |
|
|
|
88 |
|
|
# set some bindings...
|
89 |
|
|
setBindings $w
|
90 |
|
|
|
91 |
|
|
# and we are done!
|
92 |
|
|
return $w
|
93 |
|
|
}
|
94 |
|
|
|
95 |
|
|
# builds the combobox...
|
96 |
|
|
proc ::combobox::build {w args } {
|
97 |
|
|
global tcl_platform
|
98 |
|
|
if {[winfo exists $w]} {
|
99 |
|
|
error "window name \"$w\" already exists"
|
100 |
|
|
}
|
101 |
|
|
|
102 |
|
|
# create the namespace...
|
103 |
|
|
namespace eval ::combobox::$w {
|
104 |
|
|
|
105 |
|
|
variable widgets
|
106 |
|
|
variable options
|
107 |
|
|
variable oldValue
|
108 |
|
|
variable ignoreTrace
|
109 |
|
|
variable this
|
110 |
|
|
|
111 |
|
|
array set widgets {}
|
112 |
|
|
array set options {}
|
113 |
|
|
|
114 |
|
|
set oldValue {}
|
115 |
|
|
set ignoreTrace 0
|
116 |
|
|
}
|
117 |
|
|
|
118 |
|
|
# import the widgets and options arrays into this proc
|
119 |
|
|
upvar ::combobox::${w}::widgets widgets
|
120 |
|
|
upvar ::combobox::${w}::options options
|
121 |
|
|
|
122 |
|
|
# ok, everything we create should exist in the namespace
|
123 |
|
|
# we create for this widget. This is to hide all the internal
|
124 |
|
|
# foo from prying eyes. If they really want to get at the
|
125 |
|
|
# internals, they know where they can find it...
|
126 |
|
|
|
127 |
|
|
# see... I'm pretending to be a Java programmer here...
|
128 |
|
|
set this $w
|
129 |
|
|
namespace eval ::combobox::$w "set this $this"
|
130 |
|
|
|
131 |
|
|
# the basic, always-visible parts of the combobox. We do these
|
132 |
|
|
# here, because we want to query some of them for their default
|
133 |
|
|
# values, which we want to juggle to other widgets. I suppose
|
134 |
|
|
# I could use the options database, but I choose not to...
|
135 |
|
|
set widgets(this) [frame $this -class Combobox -takefocus 0]
|
136 |
|
|
set widgets(entry) [entry $this.entry -takefocus {}]
|
137 |
|
|
set widgets(button) [label $this.button -takefocus 0]
|
138 |
|
|
|
139 |
|
|
# we will later rename the frame's widget proc to be our
|
140 |
|
|
# own custom widget proc. We need to keep track of this
|
141 |
|
|
# new name, so we'll store it here...
|
142 |
|
|
set widgets(frame) .$this
|
143 |
|
|
|
144 |
|
|
pack $widgets(button) -side right -fill y -expand n
|
145 |
|
|
pack $widgets(entry) -side left -fill both -expand y
|
146 |
|
|
|
147 |
|
|
# we need these to be defined, regardless if the user defined
|
148 |
|
|
# them for us or not...
|
149 |
|
|
array set options [list \
|
150 |
|
|
-height 0 \
|
151 |
|
|
-maxheight 10 \
|
152 |
|
|
-command {} \
|
153 |
|
|
-image {} \
|
154 |
|
|
-textvariable {} \
|
155 |
|
|
-editable 1 \
|
156 |
|
|
-state normal
|
157 |
|
|
]
|
158 |
|
|
# now, steal some attributes from the entry widget...
|
159 |
|
|
foreach option [list -background -foreground -relief \
|
160 |
|
|
-borderwidth -highlightthickness -highlightbackground \
|
161 |
|
|
-font -width -selectbackground -selectborderwidth \
|
162 |
|
|
-selectforeground] {
|
163 |
|
|
set options($option) [$widgets(entry) cget $option]
|
164 |
|
|
}
|
165 |
|
|
|
166 |
|
|
# I should probably do this in a catch, but for now it's
|
167 |
|
|
# good enough... What it does, obviously, is put all of
|
168 |
|
|
# the option/values pairs into an array. Make them easier
|
169 |
|
|
# to handle later on...
|
170 |
|
|
array set options $args
|
171 |
|
|
|
172 |
|
|
# now, the dropdown list... the same renaming nonsense
|
173 |
|
|
# must go on here as well...
|
174 |
|
|
set widgets(popup) [toplevel $this.top]
|
175 |
|
|
set widgets(listbox) [listbox $this.top.list]
|
176 |
|
|
set widgets(vsb) [scrollbar $this.top.vsb]
|
177 |
|
|
|
178 |
|
|
pack $widgets(listbox) -side left -fill both -expand y
|
179 |
|
|
|
180 |
|
|
# fine tune the widgets based on the options (and a few
|
181 |
|
|
# arbitrary values...)
|
182 |
|
|
|
183 |
|
|
# NB: we are going to use the frame to handle the relief
|
184 |
|
|
# of the widget as a whole, so the entry widget will be
|
185 |
|
|
# flat.
|
186 |
|
|
$widgets(vsb) configure \
|
187 |
|
|
-command "$widgets(listbox) yview" \
|
188 |
|
|
-highlightthickness 0
|
189 |
|
|
|
190 |
|
|
set width [expr [winfo reqwidth $widgets(vsb)] - 2]
|
191 |
|
|
$widgets(button) configure \
|
192 |
|
|
-highlightthickness 0 \
|
193 |
|
|
-borderwidth 1 \
|
194 |
|
|
-relief raised \
|
195 |
|
|
-width $width
|
196 |
|
|
|
197 |
|
|
$widgets(entry) configure \
|
198 |
|
|
-borderwidth 0 \
|
199 |
|
|
-relief flat \
|
200 |
|
|
-highlightthickness 0
|
201 |
|
|
|
202 |
|
|
$widgets(popup) configure \
|
203 |
|
|
-borderwidth 1 \
|
204 |
|
|
-relief sunken
|
205 |
|
|
$widgets(listbox) configure \
|
206 |
|
|
-selectmode browse \
|
207 |
|
|
-background [$widgets(entry) cget -bg] \
|
208 |
|
|
-yscrollcommand "$widgets(vsb) set" \
|
209 |
|
|
-borderwidth 0
|
210 |
|
|
|
211 |
|
|
#Windows look'n'feel: black boarder around listbox
|
212 |
|
|
if {$tcl_platform(platform)=="windows"} {
|
213 |
|
|
$widgets(listbox) configure -highlightbackground black
|
214 |
|
|
}
|
215 |
|
|
|
216 |
|
|
|
217 |
|
|
# do some window management foo.
|
218 |
|
|
wm overrideredirect $widgets(popup) 1
|
219 |
|
|
wm transient $widgets(popup) [winfo toplevel $this]
|
220 |
|
|
wm group $widgets(popup) [winfo parent $this]
|
221 |
|
|
wm resizable $widgets(popup) 0 0
|
222 |
|
|
wm withdraw $widgets(popup)
|
223 |
|
|
|
224 |
|
|
# this moves the original frame widget proc into our
|
225 |
|
|
# namespace and gives it a handy name
|
226 |
|
|
rename ::$this $widgets(frame)
|
227 |
|
|
|
228 |
|
|
# now, create our widget proc. Obviously (?) it goes in
|
229 |
|
|
# the global namespace
|
230 |
|
|
|
231 |
|
|
proc ::$this {command args} \
|
232 |
|
|
"eval ::combobox::widgetProc $this \$command \$args"
|
233 |
|
|
# namespace export $this
|
234 |
|
|
# uplevel \#0 namespace import ::combobox::${this}::$this
|
235 |
|
|
|
236 |
|
|
# ok, the thing exists... let's do a bit more configuration:
|
237 |
|
|
foreach opt [array names options] {
|
238 |
|
|
::combobox::configure $widgets(this) set $opt $options($opt)
|
239 |
|
|
}
|
240 |
|
|
}
|
241 |
|
|
|
242 |
|
|
# here's where we do most of the binding foo. I think there's probably
|
243 |
|
|
# a few bindings I ought to add that I just haven't thought about...
|
244 |
|
|
proc ::combobox::setBindings {w} {
|
245 |
|
|
namespace eval ::combobox::$w {
|
246 |
|
|
variable widgets
|
247 |
|
|
variable options
|
248 |
|
|
|
249 |
|
|
# make sure we clean up after ourselves...
|
250 |
|
|
bind $widgets(this) <Destroy> [list ::combobox::destroyHandler $this]
|
251 |
|
|
|
252 |
|
|
# this closes the listbox if we get hidden
|
253 |
|
|
bind $widgets(this) <Unmap> "$widgets(this) close"
|
254 |
|
|
|
255 |
|
|
# this helps (but doesn't fully solve) focus issues.
|
256 |
|
|
bind $widgets(this) <FocusIn> [list focus $widgets(entry)]
|
257 |
|
|
|
258 |
|
|
# this makes our "button" (which is actually a label)
|
259 |
|
|
# do the right thing
|
260 |
|
|
bind $widgets(button) <ButtonPress-1> [list $widgets(this) toggle]
|
261 |
|
|
|
262 |
|
|
# this lets the autoscan of the listbox work, even if they
|
263 |
|
|
# move the cursor over the entry widget.
|
264 |
|
|
bind $widgets(entry) <B1-Enter> "break"
|
265 |
|
|
bind $widgets(entry) <FocusIn> \
|
266 |
|
|
[list ::combobox::entryFocus $widgets(this) "<FocusIn>"]
|
267 |
|
|
bind $widgets(entry) <FocusOut> \
|
268 |
|
|
[list ::combobox::entryFocus $widgets(this) "<FocusOut>"]
|
269 |
|
|
|
270 |
|
|
# this will (hopefully) close (and lose the grab on) the
|
271 |
|
|
# listbox if the user clicks anywhere outside of it. Note
|
272 |
|
|
# that on Windows, you can click on some other app and
|
273 |
|
|
# the listbox will still be there, because tcl won't see
|
274 |
|
|
# that button click
|
275 |
|
|
bind $widgets(this) <Any-ButtonPress> [list $widgets(this) close]
|
276 |
|
|
bind $widgets(this) <Any-ButtonRelease> [list $widgets(this) close]
|
277 |
|
|
|
278 |
|
|
bind $widgets(listbox) <ButtonRelease-1> \
|
279 |
|
|
"::combobox::select $widgets(this) \[$widgets(listbox) nearest %y\]; break"
|
280 |
|
|
|
281 |
|
|
bind $widgets(listbox) <Any-Motion> {
|
282 |
|
|
%W selection clear 0 end
|
283 |
|
|
%W activate @%x,%y
|
284 |
|
|
%W selection anchor @%x,%y
|
285 |
|
|
%W selection set @%x,%y @%x,%y
|
286 |
|
|
# need to do a yview if the cursor goes off the top
|
287 |
|
|
# or bottom of the window... (or do we?)
|
288 |
|
|
}
|
289 |
|
|
|
290 |
|
|
# these events need to be passed from the entry
|
291 |
|
|
# widget to the listbox, or need some sort of special
|
292 |
|
|
# handling....
|
293 |
|
|
foreach event [list <Up> <Down> <Tab> <Return> <Escape> \
|
294 |
|
|
<Next> <Prior> <Double-1> <1> <Any-KeyPress> \
|
295 |
|
|
<FocusIn> <FocusOut>] {
|
296 |
|
|
bind $widgets(entry) $event \
|
297 |
|
|
"::combobox::handleEvent $widgets(this) $event"
|
298 |
|
|
}
|
299 |
|
|
|
300 |
|
|
}
|
301 |
|
|
}
|
302 |
|
|
|
303 |
|
|
# this proc handles events from the entry widget that we want handled
|
304 |
|
|
# specially (typically, to allow navigation of the list even though
|
305 |
|
|
# the focus is in the entry widget)
|
306 |
|
|
proc ::combobox::handleEvent {w event} {
|
307 |
|
|
upvar ::combobox::${w}::widgets widgets
|
308 |
|
|
upvar ::combobox::${w}::options options
|
309 |
|
|
upvar ::combobox::${w}::oldValue oldValue
|
310 |
|
|
|
311 |
|
|
# for all of these events, if we have a special action we'll
|
312 |
|
|
# do that and do a "return -code break" to keep additional
|
313 |
|
|
# bindings from firing. Otherwise we'll let the event fall
|
314 |
|
|
# on through.
|
315 |
|
|
switch $event {
|
316 |
|
|
"<Any-KeyPress>" {
|
317 |
|
|
set editable [::combobox::getBoolean $options(-editable)]
|
318 |
|
|
# if the widget is editable, clear the selection.
|
319 |
|
|
# this makes it more obvious what will happen if the
|
320 |
|
|
# user presses <Return> (and helps our code know what
|
321 |
|
|
# to do if the user presses return)
|
322 |
|
|
if {$editable} {
|
323 |
|
|
$widgets(listbox) see 0
|
324 |
|
|
$widgets(listbox) selection clear 0 end
|
325 |
|
|
$widgets(listbox) selection anchor 0
|
326 |
|
|
$widgets(listbox) activate 0
|
327 |
|
|
}
|
328 |
|
|
}
|
329 |
|
|
|
330 |
|
|
"<FocusIn>" {
|
331 |
|
|
set oldValue [$widgets(entry) get]
|
332 |
|
|
}
|
333 |
|
|
|
334 |
|
|
"<FocusOut>" {
|
335 |
|
|
$widgets(entry) delete 0 end
|
336 |
|
|
$widgets(entry) insert 0 $oldValue
|
337 |
|
|
}
|
338 |
|
|
|
339 |
|
|
"<1>" {
|
340 |
|
|
set editable [::combobox::getBoolean $options(-editable)]
|
341 |
|
|
if {!$editable} {
|
342 |
|
|
if {[winfo ismapped $widgets(popup)]} {
|
343 |
|
|
$widgets(this) close
|
344 |
|
|
return -code break;
|
345 |
|
|
|
346 |
|
|
} else {
|
347 |
|
|
if {$options(-state) != "disabled"} {
|
348 |
|
|
$widgets(this) open
|
349 |
|
|
return -code break;
|
350 |
|
|
}
|
351 |
|
|
}
|
352 |
|
|
}
|
353 |
|
|
}
|
354 |
|
|
|
355 |
|
|
"<Double-1>" {
|
356 |
|
|
if {$options(-state) != "disabled"} {
|
357 |
|
|
$widgets(this) toggle
|
358 |
|
|
return -code break;
|
359 |
|
|
}
|
360 |
|
|
}
|
361 |
|
|
"<Tab>" {
|
362 |
|
|
if {[winfo ismapped $widgets(popup)]} {
|
363 |
|
|
::combobox::find $widgets(this)
|
364 |
|
|
return -code break;
|
365 |
|
|
}
|
366 |
|
|
}
|
367 |
|
|
"<Escape>" {
|
368 |
|
|
$widgets(entry) delete 0 end
|
369 |
|
|
$widgets(entry) insert 0 $oldValue
|
370 |
|
|
if {[winfo ismapped $widgets(popup)]} {
|
371 |
|
|
$widgets(this) close
|
372 |
|
|
return -code break;
|
373 |
|
|
}
|
374 |
|
|
}
|
375 |
|
|
|
376 |
|
|
"<Return>" {
|
377 |
|
|
set editable [::combobox::getBoolean $options(-editable)]
|
378 |
|
|
if {$editable} {
|
379 |
|
|
# if there is something in the list that is selected,
|
380 |
|
|
# we'll pick it. Otherwise, use whats in the
|
381 |
|
|
# entry widget...
|
382 |
|
|
set index [$widgets(listbox) curselection]
|
383 |
|
|
if {[winfo ismapped $widgets(popup)] && \
|
384 |
|
|
[llength $index] > 0} {
|
385 |
|
|
|
386 |
|
|
::combobox::select $widgets(this) \
|
387 |
|
|
[$widgets(listbox) curselection]
|
388 |
|
|
return -code break;
|
389 |
|
|
|
390 |
|
|
} else {
|
391 |
|
|
::combobox::setValue $widgets(this) [$widgets(this) get]
|
392 |
|
|
$widgets(this) close
|
393 |
|
|
return -code break;
|
394 |
|
|
}
|
395 |
|
|
}
|
396 |
|
|
|
397 |
|
|
if {[winfo ismapped $widgets(popup)]} {
|
398 |
|
|
::combobox::select $widgets(this) \
|
399 |
|
|
[$widgets(listbox) curselection]
|
400 |
|
|
return -code break;
|
401 |
|
|
}
|
402 |
|
|
|
403 |
|
|
}
|
404 |
|
|
|
405 |
|
|
"<Next>" {
|
406 |
|
|
$widgets(listbox) yview scroll 1 pages
|
407 |
|
|
set index [$widgets(listbox) index @0,0]
|
408 |
|
|
$widgets(listbox) see $index
|
409 |
|
|
$widgets(listbox) activate $index
|
410 |
|
|
$widgets(listbox) selection clear 0 end
|
411 |
|
|
$widgets(listbox) selection anchor $index
|
412 |
|
|
$widgets(listbox) selection set $index
|
413 |
|
|
|
414 |
|
|
}
|
415 |
|
|
|
416 |
|
|
"<Prior>" {
|
417 |
|
|
$widgets(listbox) yview scroll -1 pages
|
418 |
|
|
set index [$widgets(listbox) index @0,0]
|
419 |
|
|
$widgets(listbox) activate $index
|
420 |
|
|
$widgets(listbox) see $index
|
421 |
|
|
$widgets(listbox) selection clear 0 end
|
422 |
|
|
$widgets(listbox) selection anchor $index
|
423 |
|
|
$widgets(listbox) selection set $index
|
424 |
|
|
}
|
425 |
|
|
|
426 |
|
|
"<Down>" {
|
427 |
|
|
if {![winfo ismapped $widgets(popup)]} {
|
428 |
|
|
if {$options(-state) != "disabled"} {
|
429 |
|
|
$widgets(this) open
|
430 |
|
|
return -code break;
|
431 |
|
|
}
|
432 |
|
|
} else {
|
433 |
|
|
tkListboxUpDown $widgets(listbox) 1
|
434 |
|
|
return -code break;
|
435 |
|
|
}
|
436 |
|
|
}
|
437 |
|
|
"<Up>" {
|
438 |
|
|
if {![winfo ismapped $widgets(popup)]} {
|
439 |
|
|
if {$options(-state) != "disabled"} {
|
440 |
|
|
$widgets(this) open
|
441 |
|
|
return -code break;
|
442 |
|
|
}
|
443 |
|
|
} else {
|
444 |
|
|
tkListboxUpDown $widgets(listbox) -1
|
445 |
|
|
return -code break;
|
446 |
|
|
}
|
447 |
|
|
}
|
448 |
|
|
}
|
449 |
|
|
}
|
450 |
|
|
|
451 |
|
|
# this cleans up the mess that is left behind when the widget goes away
|
452 |
|
|
proc ::combobox::destroyHandler {w} {
|
453 |
|
|
|
454 |
|
|
# kill any trace or after we may have started...
|
455 |
|
|
namespace eval ::combobox::$w {
|
456 |
|
|
variable options
|
457 |
|
|
variable widgets
|
458 |
|
|
|
459 |
|
|
if {[string length $options(-textvariable)]} {
|
460 |
|
|
trace vdelete $options(-textvariable) w \
|
461 |
|
|
[list ::combobox::vTrace $widgets(this)]
|
462 |
|
|
}
|
463 |
|
|
|
464 |
|
|
# CYGNUS LOCAL - kill any after command that may be registered.
|
465 |
|
|
if {[info exists widgets(after)]} {
|
466 |
|
|
after cancel $widgets(after)
|
467 |
|
|
unset widgets(after)
|
468 |
|
|
}
|
469 |
|
|
}
|
470 |
|
|
|
471 |
|
|
# catch {rename ::combobox::${w}::$w {}}
|
472 |
|
|
# kill the namespace
|
473 |
|
|
catch {namespace delete ::combobox::$w}
|
474 |
|
|
}
|
475 |
|
|
|
476 |
|
|
# finds something in the listbox that matches the pattern in the
|
477 |
|
|
# entry widget
|
478 |
|
|
#
|
479 |
|
|
# I'm not convinced this is working the way it ought to. It works,
|
480 |
|
|
# but is the behavior what is expected? I've also got a gut feeling
|
481 |
|
|
# that there's a better way to do this, but I'm too lazy to figure
|
482 |
|
|
# it out...
|
483 |
|
|
proc ::combobox::find {w {exact 0}} {
|
484 |
|
|
upvar ::combobox::${w}::widgets widgets
|
485 |
|
|
upvar ::combobox::${w}::options options
|
486 |
|
|
|
487 |
|
|
## *sigh* this logic is rather gross and convoluted. Surely
|
488 |
|
|
## there is a more simple, straight-forward way to implement
|
489 |
|
|
## all this. As the saying goes, I lack the time to make it
|
490 |
|
|
## shorter...
|
491 |
|
|
|
492 |
|
|
# use what is already in the entry widget as a pattern
|
493 |
|
|
set pattern [$widgets(entry) get]
|
494 |
|
|
|
495 |
|
|
if {[string length $pattern] == 0} {
|
496 |
|
|
# clear the current selection
|
497 |
|
|
$widgets(listbox) see 0
|
498 |
|
|
$widgets(listbox) selection clear 0 end
|
499 |
|
|
$widgets(listbox) selection anchor 0
|
500 |
|
|
$widgets(listbox) activate 0
|
501 |
|
|
return
|
502 |
|
|
}
|
503 |
|
|
|
504 |
|
|
# we're going to be searching this list...
|
505 |
|
|
set list [$widgets(listbox) get 0 end]
|
506 |
|
|
|
507 |
|
|
# if we are doing an exact match, try to find,
|
508 |
|
|
# well, an exact match
|
509 |
|
|
if {$exact} {
|
510 |
|
|
set exactMatch [lsearch -exact $list $pattern]
|
511 |
|
|
}
|
512 |
|
|
|
513 |
|
|
# search for it. We'll try to be clever and not only
|
514 |
|
|
# search for a match for what they typed, but a match for
|
515 |
|
|
# something close to what they typed. We'll keep removing one
|
516 |
|
|
# character at a time from the pattern until we find a match
|
517 |
|
|
# of some sort.
|
518 |
|
|
set index -1
|
519 |
|
|
while {$index == -1 && [string length $pattern]} {
|
520 |
|
|
set index [lsearch -glob $list "$pattern*"]
|
521 |
|
|
if {$index == -1} {
|
522 |
|
|
regsub {.$} $pattern {} pattern
|
523 |
|
|
}
|
524 |
|
|
}
|
525 |
|
|
|
526 |
|
|
# this is the item that most closely matches...
|
527 |
|
|
set thisItem [lindex $list $index]
|
528 |
|
|
|
529 |
|
|
# did we find a match? If so, do some additional munging...
|
530 |
|
|
if {$index != -1} {
|
531 |
|
|
|
532 |
|
|
# we need to find the part of the first item that is
|
533 |
|
|
# unique wrt the second... I know there's probably a
|
534 |
|
|
# simpler way to do this...
|
535 |
|
|
|
536 |
|
|
set nextIndex [expr $index + 1]
|
537 |
|
|
set nextItem [lindex $list $nextIndex]
|
538 |
|
|
|
539 |
|
|
# we don't really need to do much if the next
|
540 |
|
|
# item doesn't match our pattern...
|
541 |
|
|
if {[string match $pattern* $nextItem]} {
|
542 |
|
|
# ok, the next item matches our pattern, too
|
543 |
|
|
# now the trick is to find the first character
|
544 |
|
|
# where they *don't* match...
|
545 |
|
|
set marker [string length $pattern]
|
546 |
|
|
while {$marker <= [string length $pattern]} {
|
547 |
|
|
set a [string index $thisItem $marker]
|
548 |
|
|
set b [string index $nextItem $marker]
|
549 |
|
|
if {[string compare $a $b] == 0} {
|
550 |
|
|
append pattern $a
|
551 |
|
|
incr marker
|
552 |
|
|
} else {
|
553 |
|
|
break
|
554 |
|
|
}
|
555 |
|
|
}
|
556 |
|
|
} else {
|
557 |
|
|
set marker [string length $pattern]
|
558 |
|
|
}
|
559 |
|
|
|
560 |
|
|
} else {
|
561 |
|
|
set marker end
|
562 |
|
|
set index 0
|
563 |
|
|
}
|
564 |
|
|
|
565 |
|
|
# ok, we know the pattern and what part is unique;
|
566 |
|
|
# update the entry widget and listbox appropriately
|
567 |
|
|
if {$exact && $exactMatch == -1} {
|
568 |
|
|
$widgets(listbox) selection clear 0 end
|
569 |
|
|
$widgets(listbox) see $index
|
570 |
|
|
} else {
|
571 |
|
|
$widgets(entry) delete 0 end
|
572 |
|
|
$widgets(entry) insert end $thisItem
|
573 |
|
|
$widgets(entry) selection clear
|
574 |
|
|
$widgets(entry) selection range $marker end
|
575 |
|
|
$widgets(listbox) activate $index
|
576 |
|
|
$widgets(listbox) selection clear 0 end
|
577 |
|
|
$widgets(listbox) selection anchor $index
|
578 |
|
|
$widgets(listbox) selection set $index
|
579 |
|
|
$widgets(listbox) see $index
|
580 |
|
|
}
|
581 |
|
|
}
|
582 |
|
|
|
583 |
|
|
# selects an item from the list and sets the value of the combobox
|
584 |
|
|
# to that value
|
585 |
|
|
proc ::combobox::select {w index} {
|
586 |
|
|
upvar ::combobox::${w}::widgets widgets
|
587 |
|
|
upvar ::combobox::${w}::options options
|
588 |
|
|
|
589 |
|
|
catch {
|
590 |
|
|
set data [$widgets(listbox) get [lindex $index 0]]
|
591 |
|
|
::combobox::setValue $widgets(this) $data
|
592 |
|
|
}
|
593 |
|
|
|
594 |
|
|
$widgets(this) close
|
595 |
|
|
}
|
596 |
|
|
|
597 |
|
|
# computes the geometry of the popup list based on the size of the
|
598 |
|
|
# combobox. Compute size of popup by requested size of listbox
|
599 |
|
|
# plus twice the bordersize of the popup.
|
600 |
|
|
proc ::combobox::computeGeometry {w} {
|
601 |
|
|
upvar ::combobox::${w}::widgets widgets
|
602 |
|
|
upvar ::combobox::${w}::options options
|
603 |
|
|
|
604 |
|
|
if {$options(-height) == 0 && $options(-maxheight) != "0"} {
|
605 |
|
|
# if this is the case, count the items and see if
|
606 |
|
|
# it exceeds our maxheight. If so, set the listbox
|
607 |
|
|
# size to maxheight...
|
608 |
|
|
set nitems [$widgets(listbox) size]
|
609 |
|
|
if {$nitems > $options(-maxheight)} {
|
610 |
|
|
# tweak the height of the listbox
|
611 |
|
|
$widgets(listbox) configure -height $options(-maxheight)
|
612 |
|
|
} else {
|
613 |
|
|
# un-tweak the height of the listbox
|
614 |
|
|
$widgets(listbox) configure -height 0
|
615 |
|
|
}
|
616 |
|
|
update idletasks
|
617 |
|
|
}
|
618 |
|
|
set bd [$widgets(popup) cget -borderwidth]
|
619 |
|
|
set height [expr [winfo reqheight $widgets(listbox)] + $bd + $bd]
|
620 |
|
|
#set height [winfo reqheight $widgets(popup)]
|
621 |
|
|
|
622 |
|
|
set width [winfo reqwidth $widgets(this)]
|
623 |
|
|
|
624 |
|
|
# Compute size of listbox, allowing larger entries to expand
|
625 |
|
|
# the listbox, clipped by the screen
|
626 |
|
|
set x [winfo rootx $widgets(this)]
|
627 |
|
|
set sw [winfo screenwidth $widgets(this)]
|
628 |
|
|
if {$width > $sw - $x} {
|
629 |
|
|
# The listbox will run off the side of the screen, so clip it
|
630 |
|
|
# (and keep a 10 pixel margin).
|
631 |
|
|
set width [expr {$sw - $x - 10}]
|
632 |
|
|
}
|
633 |
|
|
set size [format "%dx%d" $width $height]
|
634 |
|
|
set y [expr {[winfo rooty $widgets(this)]+[winfo reqheight $widgets(this)] + 1}]
|
635 |
|
|
if {[expr $y + $height] >= [winfo screenheight .]} {
|
636 |
|
|
set y [expr [winfo rooty $widgets(this)] - $height]
|
637 |
|
|
}
|
638 |
|
|
set location "+[winfo rootx $widgets(this)]+$y"
|
639 |
|
|
set geometry "=${size}${location}"
|
640 |
|
|
return $geometry
|
641 |
|
|
}
|
642 |
|
|
|
643 |
|
|
# perform an internal widget command, then mung any error results
|
644 |
|
|
# to look like it came from our megawidget. A lot of work just to
|
645 |
|
|
# give the illusion that our megawidget is an atomic widget
|
646 |
|
|
proc ::combobox::doInternalWidgetCommand {w subwidget command args} {
|
647 |
|
|
upvar ::combobox::${w}::widgets widgets
|
648 |
|
|
upvar ::combobox::${w}::options options
|
649 |
|
|
|
650 |
|
|
set subcommand $command
|
651 |
|
|
set command [concat $widgets($subwidget) $command $args]
|
652 |
|
|
|
653 |
|
|
if {[catch $command result]} {
|
654 |
|
|
# replace the subwidget name with the megawidget name
|
655 |
|
|
regsub $widgets($subwidget) $result $widgets($w) result
|
656 |
|
|
|
657 |
|
|
# replace specific instances of the subwidget command
|
658 |
|
|
# with out megawidget command
|
659 |
|
|
switch $subwidget,$subcommand {
|
660 |
|
|
listbox,index {regsub "index" $result "list index" result}
|
661 |
|
|
listbox,insert {regsub "insert" $result "list insert" result}
|
662 |
|
|
listbox,delete {regsub "delete" $result "list delete" result}
|
663 |
|
|
listbox,get {regsub "get" $result "list get" result}
|
664 |
|
|
listbox,size {regsub "size" $result "list size" result}
|
665 |
|
|
listbox,curselection {regsub "curselection" $result "list curselection" result}
|
666 |
|
|
}
|
667 |
|
|
error $result
|
668 |
|
|
|
669 |
|
|
} else {
|
670 |
|
|
return $result
|
671 |
|
|
}
|
672 |
|
|
}
|
673 |
|
|
|
674 |
|
|
|
675 |
|
|
# this is the widget proc that gets called when you do something like
|
676 |
|
|
# ".checkbox configure ..."
|
677 |
|
|
proc ::combobox::widgetProc {w command args} {
|
678 |
|
|
upvar ::combobox::${w}::widgets widgets
|
679 |
|
|
upvar ::combobox::${w}::options options
|
680 |
|
|
|
681 |
|
|
# this is just shorthand notation...
|
682 |
|
|
set doWidgetCommand \
|
683 |
|
|
[list ::combobox::doInternalWidgetCommand $widgets(this)]
|
684 |
|
|
|
685 |
|
|
if {$command == "list"} {
|
686 |
|
|
# ok, the next argument is a list command; we'll
|
687 |
|
|
# rip it from args and append it to command to
|
688 |
|
|
# create a unique internal command
|
689 |
|
|
#
|
690 |
|
|
# NB: because of the sloppy way we are doing this,
|
691 |
|
|
# we'll also let the user enter our secret command
|
692 |
|
|
# directly (eg: listinsert, listdelete), but we
|
693 |
|
|
# won't document that fact
|
694 |
|
|
set command "list[lindex $args 0]"
|
695 |
|
|
set args [lrange $args 1 end]
|
696 |
|
|
}
|
697 |
|
|
|
698 |
|
|
# many of these commands are just synonyms for specific
|
699 |
|
|
# commands in one of the subwidgets. We'll get them out
|
700 |
|
|
# of the way first, then do the custom commands.
|
701 |
|
|
switch $command {
|
702 |
|
|
bbox {eval $doWidgetCommand entry bbox $args}
|
703 |
|
|
delete {eval $doWidgetCommand entry delete $args}
|
704 |
|
|
get {eval $doWidgetCommand entry get $args}
|
705 |
|
|
icursor {eval $doWidgetCommand entry icursor $args}
|
706 |
|
|
index {eval $doWidgetCommand entry index $args}
|
707 |
|
|
insert {eval $doWidgetCommand entry insert $args}
|
708 |
|
|
listinsert {
|
709 |
|
|
eval $doWidgetCommand listbox insert $args
|
710 |
|
|
# pack the scrollbar if the number of items exceeds
|
711 |
|
|
# the maximum
|
712 |
|
|
if {$options(-height) == 0 && $options(-maxheight) != 0
|
713 |
|
|
&& ([$widgets(listbox) size] > $options(-maxheight))} {
|
714 |
|
|
pack $widgets(vsb) -before $widgets(listbox) -side right \
|
715 |
|
|
-fill y -expand n
|
716 |
|
|
}
|
717 |
|
|
}
|
718 |
|
|
listdelete {
|
719 |
|
|
eval $doWidgetCommand listbox delete $args
|
720 |
|
|
# unpack the scrollbar if the number of items
|
721 |
|
|
# decreases under the maximum
|
722 |
|
|
if {$options(-height) == 0 && $options(-maxheight) != 0
|
723 |
|
|
&& ([$widgets(listbox) size] <= $options(-maxheight))} {
|
724 |
|
|
pack forget $widgets(vsb)
|
725 |
|
|
}
|
726 |
|
|
}
|
727 |
|
|
listget {eval $doWidgetCommand listbox get $args}
|
728 |
|
|
listindex {eval $doWidgetCommand listbox index $args}
|
729 |
|
|
listsize {eval $doWidgetCommand listbox size $args}
|
730 |
|
|
listcurselection {eval $doWidgetCommand listbox curselection $args}
|
731 |
|
|
|
732 |
|
|
scan {eval $doWidgetCommand entry scan $args}
|
733 |
|
|
selection {eval $doWidgetCommand entry selection $args}
|
734 |
|
|
xview {eval $doWidgetCommand entry xview $args}
|
735 |
|
|
|
736 |
|
|
entryset {
|
737 |
|
|
# update the entry field without invoking the command
|
738 |
|
|
::combobox::setValue $widgets(this) [lindex $args 0] 0
|
739 |
|
|
}
|
740 |
|
|
|
741 |
|
|
toggle {
|
742 |
|
|
# ignore this command if the widget is disabled...
|
743 |
|
|
if {$options(-state) == "disabled"} return
|
744 |
|
|
|
745 |
|
|
# pops down the list if it is not, hides it
|
746 |
|
|
# if it is...
|
747 |
|
|
if {[winfo ismapped $widgets(popup)]} {
|
748 |
|
|
$widgets(this) close
|
749 |
|
|
} else {
|
750 |
|
|
$widgets(this) open
|
751 |
|
|
}
|
752 |
|
|
}
|
753 |
|
|
|
754 |
|
|
open {
|
755 |
|
|
# if we are disabled, we won't allow this to happen
|
756 |
|
|
if {$options(-state) == "disabled"} {
|
757 |
|
|
return 0
|
758 |
|
|
}
|
759 |
|
|
|
760 |
|
|
# compute the geometry of the window to pop up, and set
|
761 |
|
|
# it, and force the window manager to take notice
|
762 |
|
|
# (even if it is not presently visible).
|
763 |
|
|
#
|
764 |
|
|
# this isn't strictly necessary if the window is already
|
765 |
|
|
# mapped, but we'll go ahead and set the geometry here
|
766 |
|
|
# since its harmless and *may* actually reset the geometry
|
767 |
|
|
# to something better in some weird case.
|
768 |
|
|
set geometry [::combobox::computeGeometry $widgets(this)]
|
769 |
|
|
wm geometry $widgets(popup) $geometry
|
770 |
|
|
update idletasks
|
771 |
|
|
|
772 |
|
|
# if we are already open, there's nothing else to do
|
773 |
|
|
if {[winfo ismapped $widgets(popup)]} {
|
774 |
|
|
return 0
|
775 |
|
|
}
|
776 |
|
|
|
777 |
|
|
# ok, tweak the visual appearance of things and
|
778 |
|
|
# make the list pop up
|
779 |
|
|
$widgets(button) configure -relief sunken
|
780 |
|
|
wm deiconify $widgets(popup)
|
781 |
|
|
raise $widgets(popup) [winfo parent $widgets(this)]
|
782 |
|
|
focus -force $widgets(entry)
|
783 |
|
|
|
784 |
|
|
# select something by default, but only if its an
|
785 |
|
|
# exact match...
|
786 |
|
|
::combobox::find $widgets(this) 1
|
787 |
|
|
|
788 |
|
|
# *gasp* do a global grab!!! Mom always told not to
|
789 |
|
|
# do things like this... :-)
|
790 |
|
|
grab -global $widgets(this)
|
791 |
|
|
|
792 |
|
|
# fake the listbox into thinking it has focus
|
793 |
|
|
event generate $widgets(listbox) <B1-Enter>
|
794 |
|
|
|
795 |
|
|
return 1
|
796 |
|
|
}
|
797 |
|
|
|
798 |
|
|
close {
|
799 |
|
|
# if we are already closed, don't do anything...
|
800 |
|
|
if {![winfo ismapped $widgets(popup)]} {
|
801 |
|
|
return 0
|
802 |
|
|
}
|
803 |
|
|
# hides the listbox
|
804 |
|
|
grab release $widgets(this)
|
805 |
|
|
$widgets(button) configure -relief raised
|
806 |
|
|
wm withdraw $widgets(popup)
|
807 |
|
|
|
808 |
|
|
# select the data in the entry widget. Not sure
|
809 |
|
|
# why, other than observation seems to suggest that's
|
810 |
|
|
# what windows widgets do.
|
811 |
|
|
set editable [::combobox::getBoolean $options(-editable)]
|
812 |
|
|
if {$editable} {
|
813 |
|
|
$widgets(entry) selection range 0 end
|
814 |
|
|
$widgets(button) configure -relief raised
|
815 |
|
|
}
|
816 |
|
|
|
817 |
|
|
# magic tcl stuff (see tk.tcl in the distribution
|
818 |
|
|
# lib directory)
|
819 |
|
|
tkCancelRepeat
|
820 |
|
|
|
821 |
|
|
return 1
|
822 |
|
|
}
|
823 |
|
|
|
824 |
|
|
cget {
|
825 |
|
|
# tries to mimic the standard "cget" command
|
826 |
|
|
if {[llength $args] != 1} {
|
827 |
|
|
error "wrong # args: should be \"$widgets(this) cget option\""
|
828 |
|
|
}
|
829 |
|
|
set option [lindex $args 0]
|
830 |
|
|
return [::combobox::configure $widgets(this) cget $option]
|
831 |
|
|
}
|
832 |
|
|
|
833 |
|
|
configure {
|
834 |
|
|
# trys to mimic the standard "configure" command
|
835 |
|
|
if {[llength $args] == 0} {
|
836 |
|
|
# this isn't the same format as "real" widgets,
|
837 |
|
|
# but for now its good enough
|
838 |
|
|
foreach item [lsort [array names options]] {
|
839 |
|
|
lappend result [list $item $options($item)]
|
840 |
|
|
}
|
841 |
|
|
return $result
|
842 |
|
|
|
843 |
|
|
} elseif {[llength $args] == 1} {
|
844 |
|
|
# they are requesting configure information...
|
845 |
|
|
set option [lindex $args 0]
|
846 |
|
|
return [::combobox::configure $widgets(this) get $option]
|
847 |
|
|
} else {
|
848 |
|
|
array set tmpopt $args
|
849 |
|
|
foreach opt [array names tmpopt] {
|
850 |
|
|
::combobox::configure $widgets(this) set $opt $tmpopt($opt)
|
851 |
|
|
}
|
852 |
|
|
}
|
853 |
|
|
}
|
854 |
|
|
default {
|
855 |
|
|
error "bad option \"$command\""
|
856 |
|
|
}
|
857 |
|
|
}
|
858 |
|
|
}
|
859 |
|
|
|
860 |
|
|
# handles all of the configure and cget foo
|
861 |
|
|
proc ::combobox::configure {w action {option ""} {newValue ""}} {
|
862 |
|
|
upvar ::combobox::${w}::widgets widgets
|
863 |
|
|
upvar ::combobox::${w}::options options
|
864 |
|
|
set namespace "::combobox::${w}"
|
865 |
|
|
|
866 |
|
|
if {$action == "get"} {
|
867 |
|
|
# this really ought to do more than just get the value,
|
868 |
|
|
# but for the time being I don't fully support the configure
|
869 |
|
|
# command in all its glory...
|
870 |
|
|
if {$option == "-value"} {
|
871 |
|
|
return [list "-value" [$widgets(entry) get]]
|
872 |
|
|
} else {
|
873 |
|
|
return [list $option $options($option)]
|
874 |
|
|
}
|
875 |
|
|
|
876 |
|
|
} elseif {$action == "cget"} {
|
877 |
|
|
if {$option == "-value"} {
|
878 |
|
|
return [$widgets(entry) get]
|
879 |
|
|
} else {
|
880 |
|
|
return $options($option)
|
881 |
|
|
}
|
882 |
|
|
|
883 |
|
|
} else {
|
884 |
|
|
|
885 |
|
|
if {[info exists options($option)]} {
|
886 |
|
|
set oldValue $options($option)
|
887 |
|
|
set options($option) $newValue
|
888 |
|
|
} else {
|
889 |
|
|
set oldValue ""
|
890 |
|
|
set options($option) $newValue
|
891 |
|
|
}
|
892 |
|
|
|
893 |
|
|
# some (actually, most) options require us to
|
894 |
|
|
# do something, like change the attributes of
|
895 |
|
|
# a widget or two. Here's where we do that...
|
896 |
|
|
switch -- $option {
|
897 |
|
|
-background {
|
898 |
|
|
$widgets(frame) configure -background $newValue
|
899 |
|
|
$widgets(entry) configure -background $newValue
|
900 |
|
|
$widgets(listbox) configure -background $newValue
|
901 |
|
|
$widgets(vsb) configure -background $newValue
|
902 |
|
|
$widgets(vsb) configure -troughcolor $newValue
|
903 |
|
|
}
|
904 |
|
|
|
905 |
|
|
-borderwidth {
|
906 |
|
|
$widgets(frame) configure -borderwidth $newValue
|
907 |
|
|
}
|
908 |
|
|
|
909 |
|
|
-command {
|
910 |
|
|
# nothing else to do...
|
911 |
|
|
}
|
912 |
|
|
|
913 |
|
|
-cursor {
|
914 |
|
|
$widgets(frame) configure -cursor $newValue
|
915 |
|
|
$widgets(entry) configure -cursor $newValue
|
916 |
|
|
$widgets(listbox) configure -cursor $newValue
|
917 |
|
|
}
|
918 |
|
|
|
919 |
|
|
-editable {
|
920 |
|
|
if {$newValue} {
|
921 |
|
|
# it's editable...
|
922 |
|
|
$widgets(entry) configure -state normal
|
923 |
|
|
$widgets(entry) configure -bg white
|
924 |
|
|
} else {
|
925 |
|
|
global tcl_platform
|
926 |
|
|
|
927 |
|
|
$widgets(entry) configure -state disabled
|
928 |
|
|
$widgets(entry) configure -bg white
|
929 |
|
|
}
|
930 |
|
|
}
|
931 |
|
|
|
932 |
|
|
-font {
|
933 |
|
|
$widgets(entry) configure -font $newValue
|
934 |
|
|
$widgets(listbox) configure -font $newValue
|
935 |
|
|
}
|
936 |
|
|
|
937 |
|
|
-foreground {
|
938 |
|
|
$widgets(entry) configure -foreground $newValue
|
939 |
|
|
$widgets(button) configure -foreground $newValue
|
940 |
|
|
$widgets(listbox) configure -foreground $newValue
|
941 |
|
|
}
|
942 |
|
|
|
943 |
|
|
-height {
|
944 |
|
|
$widgets(listbox) configure -height $newValue
|
945 |
|
|
}
|
946 |
|
|
|
947 |
|
|
-highlightbackground {
|
948 |
|
|
$widgets(frame) configure -highlightbackground $newValue
|
949 |
|
|
}
|
950 |
|
|
|
951 |
|
|
-highlightthickness {
|
952 |
|
|
$widgets(frame) configure -highlightthickness $newValue
|
953 |
|
|
}
|
954 |
|
|
|
955 |
|
|
-image {
|
956 |
|
|
if {[string length $newValue] > 0} {
|
957 |
|
|
$widgets(button) configure -image $newValue
|
958 |
|
|
} else {
|
959 |
|
|
$widgets(button) configure -image ::combobox::bimage
|
960 |
|
|
}
|
961 |
|
|
}
|
962 |
|
|
|
963 |
|
|
-maxheight {
|
964 |
|
|
# computeGeometry may dork with the actual height
|
965 |
|
|
# of the listbox, so let's undork it
|
966 |
|
|
$widgets(listbox) configure -height $options(-height)
|
967 |
|
|
}
|
968 |
|
|
|
969 |
|
|
-relief {
|
970 |
|
|
$widgets(frame) configure -relief $newValue
|
971 |
|
|
}
|
972 |
|
|
|
973 |
|
|
-selectbackground {
|
974 |
|
|
$widgets(entry) configure -selectbackground $newValue
|
975 |
|
|
$widgets(listbox) configure -selectbackground $newValue
|
976 |
|
|
}
|
977 |
|
|
|
978 |
|
|
-selectborderwidth {
|
979 |
|
|
$widgets(entry) configure -selectborderwidth $newValue
|
980 |
|
|
$widgets(listbox) configure -selectborderwidth $newValue
|
981 |
|
|
}
|
982 |
|
|
|
983 |
|
|
-selectforeground {
|
984 |
|
|
$widgets(entry) configure -selectforeground $newValue
|
985 |
|
|
$widgets(listbox) configure -selectforeground $newValue
|
986 |
|
|
}
|
987 |
|
|
|
988 |
|
|
-state {
|
989 |
|
|
if {$newValue == "normal"} {
|
990 |
|
|
# it's enabled
|
991 |
|
|
set editable [::combobox::getBoolean \
|
992 |
|
|
$options(-editable)]
|
993 |
|
|
if {$editable} {
|
994 |
|
|
$widgets(entry) configure -state normal -takefocus 1
|
995 |
|
|
}
|
996 |
|
|
$widgets(entry) configure -fg $::combobox::enabledfg
|
997 |
|
|
} else {
|
998 |
|
|
# it's disabled
|
999 |
|
|
$widgets(entry) configure -state disabled -takefocus 0\
|
1000 |
|
|
-fg $::combobox::disabledfg
|
1001 |
|
|
}
|
1002 |
|
|
}
|
1003 |
|
|
|
1004 |
|
|
-textvariable {
|
1005 |
|
|
# destroy our trace on the old value, if any
|
1006 |
|
|
if {[string length $oldValue] > 0} {
|
1007 |
|
|
trace vdelete $oldValue w \
|
1008 |
|
|
[list ::combobox::vTrace $widgets(this)]
|
1009 |
|
|
}
|
1010 |
|
|
# set up a trace on the new value, if any. Also, set
|
1011 |
|
|
# the value of the widget to the current value of
|
1012 |
|
|
# the variable
|
1013 |
|
|
|
1014 |
|
|
set variable ::$newValue
|
1015 |
|
|
if {[string length $newValue] > 0} {
|
1016 |
|
|
if {[info exists $variable]} {
|
1017 |
|
|
::combobox::setValue $widgets(this) [set $variable]
|
1018 |
|
|
}
|
1019 |
|
|
trace variable $variable w \
|
1020 |
|
|
[list ::combobox::vTrace $widgets(this)]
|
1021 |
|
|
}
|
1022 |
|
|
}
|
1023 |
|
|
|
1024 |
|
|
-value {
|
1025 |
|
|
::combobox::setValue $widgets(this) $newValue
|
1026 |
|
|
}
|
1027 |
|
|
|
1028 |
|
|
-width {
|
1029 |
|
|
$widgets(entry) configure -width $newValue
|
1030 |
|
|
$widgets(listbox) configure -width $newValue
|
1031 |
|
|
}
|
1032 |
|
|
|
1033 |
|
|
default {
|
1034 |
|
|
error "unknown option \"$option\""
|
1035 |
|
|
}
|
1036 |
|
|
}
|
1037 |
|
|
}
|
1038 |
|
|
}
|
1039 |
|
|
|
1040 |
|
|
# this proc is called whenever the user changes the value of
|
1041 |
|
|
# the -textvariable associated with a widget
|
1042 |
|
|
proc ::combobox::vTrace {w args} {
|
1043 |
|
|
upvar ::combobox::${w}::widgets widgets
|
1044 |
|
|
upvar ::combobox::${w}::options options
|
1045 |
|
|
upvar ::combobox::${w}::ignoreTrace ignoreTrace
|
1046 |
|
|
|
1047 |
|
|
if {[info exists ignoreTrace]} return
|
1048 |
|
|
::combobox::setValue $widgets(this) [set ::$options(-textvariable)]
|
1049 |
|
|
}
|
1050 |
|
|
|
1051 |
|
|
# sets the value of the combobox and calls the -command, if defined
|
1052 |
|
|
proc ::combobox::setValue {w newValue {call 1}} {
|
1053 |
|
|
upvar ::combobox::${w}::widgets widgets
|
1054 |
|
|
upvar ::combobox::${w}::options options
|
1055 |
|
|
upvar ::combobox::${w}::ignoreTrace ignoreTrace
|
1056 |
|
|
upvar ::combobox::${w}::oldValue oldValue
|
1057 |
|
|
|
1058 |
|
|
set editable [::combobox::getBoolean $options(-editable)]
|
1059 |
|
|
|
1060 |
|
|
# update the widget, no matter what. This might cause a few
|
1061 |
|
|
# false triggers on a trace of the associated textvariable,
|
1062 |
|
|
# but that's a chance we'll have to take.
|
1063 |
|
|
$widgets(entry) configure -state normal
|
1064 |
|
|
$widgets(entry) delete 0 end
|
1065 |
|
|
$widgets(entry) insert 0 $newValue
|
1066 |
|
|
if {!$editable || $options(-state) != "normal"} {
|
1067 |
|
|
$widgets(entry) configure -state disabled
|
1068 |
|
|
}
|
1069 |
|
|
|
1070 |
|
|
# set the associated textvariable
|
1071 |
|
|
if {[string length $options(-textvariable)] > 0} {
|
1072 |
|
|
set ignoreTrace 1 ;# so we don't get in a recursive loop
|
1073 |
|
|
uplevel \#0 [list set $options(-textvariable) $newValue]
|
1074 |
|
|
unset ignoreTrace
|
1075 |
|
|
}
|
1076 |
|
|
|
1077 |
|
|
# Call the -command, if it exists.
|
1078 |
|
|
# We could optionally check to see if oldValue == newValue
|
1079 |
|
|
# first, but sometimes we want to execute the command even
|
1080 |
|
|
# if the value didn't change...
|
1081 |
|
|
# CYGNUS LOCAL
|
1082 |
|
|
# Call it after idle, so the menu gets unposted BEFORE
|
1083 |
|
|
# the command gets run... Make sure to clean up the afters
|
1084 |
|
|
# so you don't try to access a dead widget...
|
1085 |
|
|
|
1086 |
|
|
if {$call && [string length $options(-command)] > 0} {
|
1087 |
|
|
if {[info exists widgets(after)]} {
|
1088 |
|
|
after cancel $widgets(after)
|
1089 |
|
|
}
|
1090 |
|
|
set widgets(after) [after idle $options(-command) \
|
1091 |
|
|
[list $widgets(this) $newValue]\;\
|
1092 |
|
|
unset ::combobox::${w}::widgets(after)]
|
1093 |
|
|
}
|
1094 |
|
|
set oldValue $newValue
|
1095 |
|
|
}
|
1096 |
|
|
|
1097 |
|
|
# returns the value of a (presumably) boolean string (ie: it should
|
1098 |
|
|
# do the right thing if the string is "yes", "no", "true", 1, etc
|
1099 |
|
|
proc ::combobox::getBoolean {value {errorValue 1}} {
|
1100 |
|
|
if {[catch {expr {([string trim $value])?1:0}} res]} {
|
1101 |
|
|
return $errorValue
|
1102 |
|
|
} else {
|
1103 |
|
|
return $res
|
1104 |
|
|
}
|
1105 |
|
|
}
|
1106 |
|
|
|
1107 |
|
|
# computes the combobox widget name based on the name of one of
|
1108 |
|
|
# it's children widgets.. Not presently used, but might come in
|
1109 |
|
|
# handy...
|
1110 |
|
|
proc ::combobox::widgetName {w} {
|
1111 |
|
|
while {$w != "."} {
|
1112 |
|
|
if {[winfo class $w] == "Combobox"} {
|
1113 |
|
|
return $w
|
1114 |
|
|
}
|
1115 |
|
|
set w [winfo parent $w]
|
1116 |
|
|
}
|
1117 |
|
|
error "internal error: $w is not a child of a combobox"
|
1118 |
|
|
}
|