OpenCores
URL https://opencores.org/ocsvn/or1k/or1k/trunk

Subversion Repositories or1k

[/] [or1k/] [trunk/] [insight/] [libgui/] [library/] [combobox.tcl] - Blame information for rev 1765

Details | Compare with Previous | View Log

Line No. Rev Author Line
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
}

powered by: WebSVN 2.1.0

© copyright 1999-2024 OpenCores.org, equivalent to Oliscience, all rights reserved. OpenCores®, registered trademark.