home *** CD-ROM | disk | FTP | other *** search
/ PCNET 2006 September - Disc 1 / PCNET_CD_2006_09.iso / linux / puppy-barebones-2.01r2.iso / pup_201.sfs / usr / lib / combobox-2.3 / combobox.tcl < prev    next >
Encoding:
Text File  |  2003-08-18  |  62.9 KB  |  2,189 lines

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