| 1 | package require Metawidget |
|---|
| 2 | |
|---|
| 3 | # name: Combobox |
|---|
| 4 | # args: args |
|---|
| 5 | # for the first instance defines the image for the arrow button. |
|---|
| 6 | # creates all widgets including the popup list and its toplevel window. |
|---|
| 7 | # the toplevel is withdrawn. defines bindings for keys and mouse clicks. |
|---|
| 8 | # initializes option values by calling the respective option members. |
|---|
| 9 | metawidget create Combobox { |
|---|
| 10 | # initialize the icon and try to find out if KDE is running |
|---|
| 11 | if { ! [ourinfo exists pDown] } { |
|---|
| 12 | our pDown [image create bitmap -data { |
|---|
| 13 | #define _width 8 |
|---|
| 14 | #define _height 4 |
|---|
| 15 | static char _bits[] = { 0xfe, 0x7c, 0x38, 0x10 }; |
|---|
| 16 | }] |
|---|
| 17 | |
|---|
| 18 | # on Linux and KDE, the <FocusOut> binding (see below) doesn't work |
|---|
| 19 | our bKde [expr { $::tcl_platform(os) == "Linux" && ! [catch { exec ps -C kde }] }] |
|---|
| 20 | our wToplevel [winfo toplevel $this] |
|---|
| 21 | } |
|---|
| 22 | |
|---|
| 23 | entry $this.entr -bg white -selectborder 0 -selectback black -selectfore white |
|---|
| 24 | button $this.entr.arrb -padx 0 -pady 0 -image [our pDown] -command "$this _showList" -cursor arrow |
|---|
| 25 | toplevel $this.topl -border 0 |
|---|
| 26 | scrollbar $this.topl.scrv -command "$this.topl.list yview" |
|---|
| 27 | listbox $this.topl.list -bg white -border 0 -yscroll "$this.topl.scrv set" \ |
|---|
| 28 | -selectborder 0 -selectback {dark blue} -selectfore white |
|---|
| 29 | |
|---|
| 30 | # the list is in a window without border. the binding is supposed to let |
|---|
| 31 | # it disappear when some other window is clicked. does not quite work on |
|---|
| 32 | # KDE: when the window to which the combobox belongs is clicked, the focus |
|---|
| 33 | # doesn't get out, and the bind script doesn't get triggered. there's a |
|---|
| 34 | # workaround in _showList and _hideList. |
|---|
| 35 | wm overrideredirect $this.topl 1 |
|---|
| 36 | wm withdraw $this.topl |
|---|
| 37 | bind $this.topl <FocusOut> "$this _hideList" |
|---|
| 38 | |
|---|
| 39 | pack $this.entr -fill x -expand 1 |
|---|
| 40 | place $this.entr.arrb -relx 1 -y 0 -relh 1 -anchor ne |
|---|
| 41 | pack $this.topl.scrv -side right -fill y |
|---|
| 42 | pack $this.topl.list -side left -fill both -expand 1 |
|---|
| 43 | |
|---|
| 44 | bind $this.entr <Up> "$this see -" |
|---|
| 45 | bind $this.entr <Down> "$this see +" |
|---|
| 46 | |
|---|
| 47 | bind $this.topl.list <ButtonRelease-1> "$this _hideList; $this see ?" |
|---|
| 48 | bind $this.topl.list <Key-Return> "$this _hideList; $this see ?" |
|---|
| 49 | bind $this.topl.list <Key-Escape> "$this _hideList" |
|---|
| 50 | |
|---|
| 51 | my iIndex -1 ;# currently displayed |
|---|
| 52 | my iListH 40 ;# required height for list in pixels |
|---|
| 53 | my -command {} ;# evaluated on value change |
|---|
| 54 | |
|---|
| 55 | -lines 5 ;# set box to 5 lines |
|---|
| 56 | } {} -default entr |
|---|
| 57 | |
|---|
| 58 | # name: _showList |
|---|
| 59 | # args: - |
|---|
| 60 | # displays the toplevel with listbox and scrollbar at the appropriate |
|---|
| 61 | # position. the <FocusOut> binding and the <Escape> key lets it |
|---|
| 62 | # disappear by calling _hideList. the toplevel window is positioned |
|---|
| 63 | # either above or below the entry widget, depending on the space |
|---|
| 64 | # left below of it. |
|---|
| 65 | metawidget proc Combobox _showList {} { |
|---|
| 66 | set iX [winfo rootx $this] |
|---|
| 67 | set iY [winfo rooty $this] |
|---|
| 68 | set iW [winfo width $this] |
|---|
| 69 | set iH [winfo height $this] |
|---|
| 70 | |
|---|
| 71 | # show listbox above the entry, if there's not enough space below |
|---|
| 72 | if { [expr [winfo screenheight $this] - ( $iY + $iH )] >= [my iListH] } { |
|---|
| 73 | incr iY $iH |
|---|
| 74 | } else { |
|---|
| 75 | incr iY -[my iListH] |
|---|
| 76 | } |
|---|
| 77 | |
|---|
| 78 | # position window, then show it |
|---|
| 79 | wm geometry $this.topl ${iW}x[my iListH]+$iX+$iY |
|---|
| 80 | wm deiconify $this.topl |
|---|
| 81 | |
|---|
| 82 | $this.topl.list selection clear 0 end |
|---|
| 83 | $this.topl.list selection set [my iIndex] |
|---|
| 84 | |
|---|
| 85 | raise $this.topl |
|---|
| 86 | focus $this.topl.list |
|---|
| 87 | |
|---|
| 88 | # workaround for KDE: the listbox doesn't disappear when the application |
|---|
| 89 | # window is clicked outside of a widget that can receive the focus. it |
|---|
| 90 | # would even stay on the screen when the app. window is moved. to prevent |
|---|
| 91 | # this, a <Configure> binding helps. we need to save any previous script |
|---|
| 92 | # for this binding, though. |
|---|
| 93 | if { [our bKde] } { |
|---|
| 94 | our sConfScript [bind [our wToplevel] <Configure>] |
|---|
| 95 | bind [our wToplevel] <Configure> "$this _hideList" |
|---|
| 96 | } |
|---|
| 97 | } |
|---|
| 98 | |
|---|
| 99 | # name: _hideList |
|---|
| 100 | # args: - |
|---|
| 101 | # releases the grab. lets the listbox toplevel disappear. |
|---|
| 102 | metawidget proc Combobox _hideList {} { |
|---|
| 103 | wm withdraw $this.topl |
|---|
| 104 | focus $this.entr |
|---|
| 105 | |
|---|
| 106 | # restore the original <Configure> bind script again! |
|---|
| 107 | if { [our bKde] } { |
|---|
| 108 | bind [our wToplevel] <Configure> [our sConfScript] |
|---|
| 109 | } |
|---|
| 110 | } |
|---|
| 111 | |
|---|
| 112 | # name: -lines |
|---|
| 113 | # args: iLines: number of listbox lines |
|---|
| 114 | # set the number of lines for the listbox. converts it into pixels by |
|---|
| 115 | # multiplying it with the font height. stores pixel height in iListH. |
|---|
| 116 | metawidget proc Combobox -lines { iLines } { |
|---|
| 117 | $this.topl.list configure -height $iLines |
|---|
| 118 | my -lines $iLines |
|---|
| 119 | |
|---|
| 120 | set iFontH [font metrics [$this.topl.list cget -font] -linespace] |
|---|
| 121 | my iListH [expr (1+$iFontH)*[my -lines]+2] |
|---|
| 122 | } |
|---|
| 123 | |
|---|
| 124 | # name: -entries |
|---|
| 125 | # args: lEntries: list of entries to set for the Combobox |
|---|
| 126 | # repopulates the list with the given values in lEntries. |
|---|
| 127 | metawidget proc Combobox -entries { args } { |
|---|
| 128 | if { ! [llength $args] } { |
|---|
| 129 | $this.topl.list get 0 end |
|---|
| 130 | } else { |
|---|
| 131 | $this.topl.list delete 0 end |
|---|
| 132 | eval $this.topl.list insert 0 [lindex $args 0] |
|---|
| 133 | } |
|---|
| 134 | } |
|---|
| 135 | |
|---|
| 136 | # name: -state |
|---|
| 137 | # args: sState: state to change the Combobox to |
|---|
| 138 | # applies normal and disabled state simply to the entry widget and the button. |
|---|
| 139 | # for restricted state, the two bindings prevent input to the entry field. |
|---|
| 140 | # only values from the list can be set with cursor up/down. |
|---|
| 141 | metawidget proc Combobox -state { sState } { |
|---|
| 142 | my -state [mkw.complete $sState {normal disabled restricted}] |
|---|
| 143 | |
|---|
| 144 | switch [my -state] { |
|---|
| 145 | normal - disabled { |
|---|
| 146 | bind $this.entr <Key> {} |
|---|
| 147 | bind $this.entr <KeyRelease> {} |
|---|
| 148 | $this.entr config -state $sState |
|---|
| 149 | $this.entr.arrb config -state $sState |
|---|
| 150 | } |
|---|
| 151 | restricted { |
|---|
| 152 | bind $this.entr <Key> "$this.entr config -state disabled" |
|---|
| 153 | bind $this.entr <KeyRelease> "$this.entr config -state normal" |
|---|
| 154 | $this.entr config -state normal |
|---|
| 155 | $this.entr.arrb config -state normal |
|---|
| 156 | see 0 |
|---|
| 157 | } |
|---|
| 158 | } |
|---|
| 159 | } |
|---|
| 160 | |
|---|
| 161 | # name: see |
|---|
| 162 | # args: iIndex: index of the value list to display |
|---|
| 163 | # if index is not given, returns the index of the currently displayed |
|---|
| 164 | # value, or -1 if the value is not part of the value list. |
|---|
| 165 | # if index is + or -, the next resp. previous value in the value list is |
|---|
| 166 | # set. for index ?, the listbox' currently selected element is taken. |
|---|
| 167 | # if index is an integer, the corresponding element from the value list |
|---|
| 168 | # is displayed. any defined command is evaluated, if the value in |
|---|
| 169 | # the entry field has changed. |
|---|
| 170 | metawidget proc Combobox see { {iIndex {}} } { |
|---|
| 171 | if { $iIndex == {} } { |
|---|
| 172 | return [lsearch -exact [$this.topl.list get 0 end] [$this.entr get]] |
|---|
| 173 | } |
|---|
| 174 | |
|---|
| 175 | set iLen [llength [$this.topl.list get 0 end]] |
|---|
| 176 | set iOldIndex [my iIndex] |
|---|
| 177 | |
|---|
| 178 | if { $iIndex == "?" } { |
|---|
| 179 | my iIndex [$this.topl.list curselection] |
|---|
| 180 | } elseif { $iIndex == "+" && [my iIndex] < [expr $iLen-1] } { |
|---|
| 181 | my iIndex [expr [my iIndex]+1] |
|---|
| 182 | } elseif { $iIndex == "-" && [my iIndex] > 0 } { |
|---|
| 183 | my iIndex [expr [my iIndex]-1] |
|---|
| 184 | } elseif { $iIndex >= 0 && $iIndex < $iLen } { |
|---|
| 185 | my iIndex $iIndex |
|---|
| 186 | } |
|---|
| 187 | |
|---|
| 188 | if { $iOldIndex != [my iIndex] } { |
|---|
| 189 | $this.entr delete 0 end |
|---|
| 190 | $this.entr insert 0 [$this.topl.list get [my iIndex]] |
|---|
| 191 | eval [my -command] |
|---|
| 192 | } |
|---|
| 193 | } |
|---|
| 194 | |
|---|
| 195 | metawidget command Combobox _showList _showList |
|---|
| 196 | metawidget command Combobox _hideList _hideList |
|---|
| 197 | |
|---|
| 198 | metawidget command Combobox see see |
|---|
| 199 | |
|---|
| 200 | metawidget option Combobox -lines -lines |
|---|
| 201 | metawidget option Combobox -entries -entries -entries |
|---|
| 202 | metawidget option Combobox -state -state |
|---|
| 203 | metawidget option Combobox -command |
|---|
| 204 | |
|---|
| 205 | proc test {} { |
|---|
| 206 | pack [frame .upfr -border 0] -fill both |
|---|
| 207 | pack [label .upfr.text -text "Enter values and press Set!"] -anchor w |
|---|
| 208 | pack [entry .upfr.vals] -side left -fill x -expand 1 |
|---|
| 209 | pack [button .upfr.setb -pady 0 -text Set! -command {.cbox config -entries [.upfr.vals get]} ] -side right |
|---|
| 210 | |
|---|
| 211 | .upfr.vals insert end {red green blue yellow cyan magenta} |
|---|
| 212 | |
|---|
| 213 | pack [combobox .cbox -command {.outp config -text [.cbox get]}] -fill x -pady 10 |
|---|
| 214 | |
|---|
| 215 | pack [label .outp -border 1 -relief sunken] -fill x |
|---|
| 216 | pack [radiobutton .mod1 -text "Normal" -value 1 -command {.cbox config -state normal}] -fill x -side left |
|---|
| 217 | pack [radiobutton .mod2 -text "Restricted" -value 2 -command {.cbox config -state restricted}] -fill x -side left |
|---|
| 218 | pack [radiobutton .mod3 -text "Disabled" -value 3 -command {.cbox config -state disabled}] -fill x -side left |
|---|
| 219 | } |
|---|
| 220 | |
|---|
| 221 | #test |
|---|
| 222 | |
|---|
| 223 | |
|---|