| 1 | # Copyright (c) 1999, Bryan Oakley |
|---|
| 2 | # All Rights Reservered |
|---|
| 3 | # |
|---|
| 4 | # Bryan Oakley |
|---|
| 5 | # oakley@channelpoint.com |
|---|
| 6 | # |
|---|
| 7 | # mclistbox v1.02 March 30, 1999 |
|---|
| 8 | # |
|---|
| 9 | # a multicolumn listbox written in pure tcl |
|---|
| 10 | # |
|---|
| 11 | # this code is freely distributable without restriction, but is |
|---|
| 12 | # provided as-is with no waranty expressed or implied. |
|---|
| 13 | # |
|---|
| 14 | # basic usage: |
|---|
| 15 | # |
|---|
| 16 | # mclistbox::mclistbox .listbox |
|---|
| 17 | # .listbox column add col1 -label "Column 1" |
|---|
| 18 | # .listbox column add col2 -label "Column 2" |
|---|
| 19 | # .listbox insert end [list "some stuff" "some more stuff"] |
|---|
| 20 | # .listbox insert end [list "a second row of stuff" "blah blah blah"] |
|---|
| 21 | # |
|---|
| 22 | # see the documentation for more, uh, documentation. |
|---|
| 23 | # |
|---|
| 24 | # Something to think about: implement a "-optimize" option, with two |
|---|
| 25 | # values: speed and memory. If set to speed, keep a copy of the data |
|---|
| 26 | # in our hidden listbox so retrieval of data doesn't require us to |
|---|
| 27 | # do all the getting and splitting and so forth. If set to "memory", |
|---|
| 28 | # bag saving a duplicate copy of the data, which means data retrieval |
|---|
| 29 | # will be slower, but memory requirements will be reduced. |
|---|
| 30 | |
|---|
| 31 | package require Tk 8.0 |
|---|
| 32 | package provide mclistbox 1.02 |
|---|
| 33 | |
|---|
| 34 | namespace eval ::mclistbox { |
|---|
| 35 | |
|---|
| 36 | # this is the public interface |
|---|
| 37 | namespace export mclistbox |
|---|
| 38 | |
|---|
| 39 | # these contain references to available options |
|---|
| 40 | variable widgetOptions |
|---|
| 41 | variable columnOptions |
|---|
| 42 | |
|---|
| 43 | # these contain references to available commands and subcommands |
|---|
| 44 | variable widgetCommands |
|---|
| 45 | variable columnCommands |
|---|
| 46 | variable labelCommands |
|---|
| 47 | } |
|---|
| 48 | |
|---|
| 49 | # ::mclistbox::Init -- |
|---|
| 50 | # |
|---|
| 51 | # Initialize the global (well, namespace) variables. This should |
|---|
| 52 | # only be called once, immediately prior to creating the first |
|---|
| 53 | # instance of the widget |
|---|
| 54 | # |
|---|
| 55 | # Results: |
|---|
| 56 | # |
|---|
| 57 | # All state variables are set to their default values; all of |
|---|
| 58 | # the option database entries will exist. |
|---|
| 59 | # |
|---|
| 60 | # Returns: |
|---|
| 61 | # |
|---|
| 62 | # empty string |
|---|
| 63 | |
|---|
| 64 | proc ::mclistbox::Init {} { |
|---|
| 65 | variable widgetOptions |
|---|
| 66 | variable columnOptions |
|---|
| 67 | variable widgetCommands |
|---|
| 68 | variable columnCommands |
|---|
| 69 | variable labelCommands |
|---|
| 70 | |
|---|
| 71 | # here we match up command line options with option database names |
|---|
| 72 | # and classes. As it turns out, this is a handy reference of all of the |
|---|
| 73 | # available options. Note that if an item has a value with only one |
|---|
| 74 | # item (like -bd, for example) it is a synonym and the value is the |
|---|
| 75 | # actual item. |
|---|
| 76 | |
|---|
| 77 | array set widgetOptions [list \ |
|---|
| 78 | -background {background Background} \ |
|---|
| 79 | -bd -borderwidth \ |
|---|
| 80 | -bg -background \ |
|---|
| 81 | -borderwidth {borderWidth BorderWidth} \ |
|---|
| 82 | -columnbd -columnborderwidth \ |
|---|
| 83 | -columnborderwidth {columnBorderWidth BorderWidth} \ |
|---|
| 84 | -columnrelief {columnRelief Relief} \ |
|---|
| 85 | -cursor {cursor Cursor} \ |
|---|
| 86 | -exportselection {exportSelection ExportSelection} \ |
|---|
| 87 | -fg -foreground \ |
|---|
| 88 | -fillcolumn {fillColumn FillColumn} \ |
|---|
| 89 | -font {font Font} \ |
|---|
| 90 | -foreground {foreground Foreground} \ |
|---|
| 91 | -height {height Height} \ |
|---|
| 92 | -highlightbackground {highlightBackground HighlightBackground} \ |
|---|
| 93 | -highlightcolor {highlightColor HighlightColor} \ |
|---|
| 94 | -highlightthickness {highlightThickness HighlightThickness} \ |
|---|
| 95 | -labelanchor {labelAnchor Anchor} \ |
|---|
| 96 | -labelbackground {labelBackground Background} \ |
|---|
| 97 | -labelbd -labelborderwidth \ |
|---|
| 98 | -labelbg -labelbackground \ |
|---|
| 99 | -labelborderwidth {labelBorderWidth BorderWidth} \ |
|---|
| 100 | -labelfg -labelforeground \ |
|---|
| 101 | -labelfont {labelFont Font} \ |
|---|
| 102 | -labelforeground {labelForeground Foreground} \ |
|---|
| 103 | -labelheight {labelHeight Height} \ |
|---|
| 104 | -labelimage {labelImage Image} \ |
|---|
| 105 | -labelrelief {labelRelief Relief} \ |
|---|
| 106 | -labels {labels Labels} \ |
|---|
| 107 | -relief {relief Relief} \ |
|---|
| 108 | -resizablecolumns {resizableColumns ResizableColumns} \ |
|---|
| 109 | -selectbackground {selectBackground Foreground} \ |
|---|
| 110 | -selectborderwidth {selectBorderWidth BorderWidth} \ |
|---|
| 111 | -selectcommand {selectCommand Command} \ |
|---|
| 112 | -selectforeground {selectForeground Background} \ |
|---|
| 113 | -selectmode {selectMode SelectMode} \ |
|---|
| 114 | -setgrid {setGrid SetGrid} \ |
|---|
| 115 | -takefocus {takeFocus TakeFocus} \ |
|---|
| 116 | -width {width Width} \ |
|---|
| 117 | -xscrollcommand {xScrollCommand ScrollCommand} \ |
|---|
| 118 | -yscrollcommand {yScrollCommand ScrollCommand} \ |
|---|
| 119 | ] |
|---|
| 120 | |
|---|
| 121 | # and likewise for column-specific stuff. |
|---|
| 122 | array set columnOptions [list \ |
|---|
| 123 | -background {background Background} \ |
|---|
| 124 | -bitmap {bitmap Bitmap} \ |
|---|
| 125 | -font {font Font} \ |
|---|
| 126 | -foreground {foreground Foreground} \ |
|---|
| 127 | -image {image Image} \ |
|---|
| 128 | -label {label Label} \ |
|---|
| 129 | -position {position Position} \ |
|---|
| 130 | -resizable {resizable Resizable} \ |
|---|
| 131 | -visible {visible Visible} \ |
|---|
| 132 | -width {width Width} \ |
|---|
| 133 | ] |
|---|
| 134 | |
|---|
| 135 | # this defines the valid widget commands. It's important to |
|---|
| 136 | # list them here; we use this list to validate commands and |
|---|
| 137 | # expand abbreviations. |
|---|
| 138 | set widgetCommands [list \ |
|---|
| 139 | activate bbox cget column configure \ |
|---|
| 140 | curselection delete get index insert \ |
|---|
| 141 | label nearest scan see selection \ |
|---|
| 142 | size xview yview |
|---|
| 143 | ] |
|---|
| 144 | |
|---|
| 145 | set columnCommands [list add cget configure delete names nearest] |
|---|
| 146 | set labelCommands [list bind] |
|---|
| 147 | |
|---|
| 148 | ###################################################################### |
|---|
| 149 | #- this initializes the option database. Kinda gross, but it works |
|---|
| 150 | #- (I think). |
|---|
| 151 | ###################################################################### |
|---|
| 152 | |
|---|
| 153 | set packages [package names] |
|---|
| 154 | |
|---|
| 155 | # why check for the Tk package? This lets us be sourced into |
|---|
| 156 | # an interpreter that doesn't have Tk loaded, such as the slave |
|---|
| 157 | # interpreter used by pkg_mkIndex. In theory it should have no |
|---|
| 158 | # side effects when run |
|---|
| 159 | if {[lsearch -exact [package names] "Tk"] != -1} { |
|---|
| 160 | |
|---|
| 161 | # compute a widget name we can use to create a temporary widget |
|---|
| 162 | set tmpWidget ".__tmp__" |
|---|
| 163 | set count 0 |
|---|
| 164 | while {[winfo exists $tmpWidget] == 1} { |
|---|
| 165 | set tmpWidget ".__tmp__$count" |
|---|
| 166 | incr count |
|---|
| 167 | } |
|---|
| 168 | |
|---|
| 169 | # steal options from the listbox |
|---|
| 170 | # we want darn near all options, so we'll go ahead and do |
|---|
| 171 | # them all. No harm done in adding the one or two that we |
|---|
| 172 | # don't use. |
|---|
| 173 | listbox $tmpWidget |
|---|
| 174 | foreach foo [$tmpWidget configure] { |
|---|
| 175 | if {[llength $foo] == 5} { |
|---|
| 176 | set option [lindex $foo 1] |
|---|
| 177 | set value [lindex $foo 4] |
|---|
| 178 | option add *Mclistbox.$option $value widgetDefault |
|---|
| 179 | |
|---|
| 180 | # these options also apply to the individual columns... |
|---|
| 181 | if {[string compare $option "foreground"] == 0 \ |
|---|
| 182 | || [string compare $option "background"] == 0 \ |
|---|
| 183 | || [string compare $option "font"] == 0} { |
|---|
| 184 | option add *Mclistbox*MclistboxColumn.$option $value \ |
|---|
| 185 | widgetDefault |
|---|
| 186 | } |
|---|
| 187 | } |
|---|
| 188 | } |
|---|
| 189 | destroy $tmpWidget |
|---|
| 190 | |
|---|
| 191 | # steal some options from label widgets; we only want a subset |
|---|
| 192 | # so we'll use a slightly different method. No harm in *not* |
|---|
| 193 | # adding in the one or two that we don't use... :-) |
|---|
| 194 | label $tmpWidget |
|---|
| 195 | foreach option [list Anchor Background Font \ |
|---|
| 196 | Foreground Height Image ] { |
|---|
| 197 | set values [$tmpWidget configure -[string tolower $option]] |
|---|
| 198 | option add *Mclistbox.label$option [lindex $values 3] |
|---|
| 199 | } |
|---|
| 200 | destroy $tmpWidget |
|---|
| 201 | |
|---|
| 202 | # these are unique to us... |
|---|
| 203 | option add *Mclistbox.columnBorderWidth 0 widgetDefault |
|---|
| 204 | option add *Mclistbox.columnRelief flat widgetDefault |
|---|
| 205 | option add *Mclistbox.labelBorderWidth 1 widgetDefault |
|---|
| 206 | option add *Mclistbox.labelRelief raised widgetDefault |
|---|
| 207 | option add *Mclistbox.labels 1 widgetDefault |
|---|
| 208 | option add *Mclistbox.resizableColumns 1 widgetDefault |
|---|
| 209 | option add *Mclistbox.selectcommand {} widgetDefault |
|---|
| 210 | option add *Mclistbox.fillcolumn {} widgetDefault |
|---|
| 211 | |
|---|
| 212 | # column options |
|---|
| 213 | option add *Mclistbox*MclistboxColumn.visible 1 widgetDefault |
|---|
| 214 | option add *Mclistbox*MclistboxColumn.resizable 1 widgetDefault |
|---|
| 215 | option add *Mclistbox*MclistboxColumn.position end widgetDefault |
|---|
| 216 | option add *Mclistbox*MclistboxColumn.label "" widgetDefault |
|---|
| 217 | option add *Mclistbox*MclistboxColumn.width 0 widgetDefault |
|---|
| 218 | option add *Mclistbox*MclistboxColumn.bitmap "" widgetDefault |
|---|
| 219 | option add *Mclistbox*MclistboxColumn.image "" widgetDefault |
|---|
| 220 | } |
|---|
| 221 | |
|---|
| 222 | ###################################################################### |
|---|
| 223 | # define the class bindings |
|---|
| 224 | ###################################################################### |
|---|
| 225 | |
|---|
| 226 | SetClassBindings |
|---|
| 227 | |
|---|
| 228 | } |
|---|
| 229 | |
|---|
| 230 | # ::mclistbox::mclistbox -- |
|---|
| 231 | # |
|---|
| 232 | # This is the command that gets exported. It creates a new |
|---|
| 233 | # mclistbox widget. |
|---|
| 234 | # |
|---|
| 235 | # Arguments: |
|---|
| 236 | # |
|---|
| 237 | # w path of new widget to create |
|---|
| 238 | # args additional option/value pairs (eg: -background white, etc.) |
|---|
| 239 | # |
|---|
| 240 | # Results: |
|---|
| 241 | # |
|---|
| 242 | # It creates the widget and sets up all of the default bindings |
|---|
| 243 | # |
|---|
| 244 | # Returns: |
|---|
| 245 | # |
|---|
| 246 | # The name of the newly create widget |
|---|
| 247 | |
|---|
| 248 | proc ::mclistbox::mclistbox {args} { |
|---|
| 249 | variable widgetOptions |
|---|
| 250 | |
|---|
| 251 | # perform a one time initialization |
|---|
| 252 | if {![info exists widgetOptions]} { |
|---|
| 253 | Init |
|---|
| 254 | } |
|---|
| 255 | |
|---|
| 256 | # make sure we at least have a widget name |
|---|
| 257 | if {[llength $args] == 0} { |
|---|
| 258 | error "wrong # args: should be \"mclistbox pathName ?options?\"" |
|---|
| 259 | } |
|---|
| 260 | |
|---|
| 261 | # ... and make sure a widget doesn't already exist by that name |
|---|
| 262 | if {[winfo exists [lindex $args 0]]} { |
|---|
| 263 | error "window name \"[lindex $args 0]\" already exists" |
|---|
| 264 | } |
|---|
| 265 | |
|---|
| 266 | # and check that all of the args are valid |
|---|
| 267 | foreach {name value} [lrange $args 1 end] { |
|---|
| 268 | Canonize [lindex $args 0] option $name |
|---|
| 269 | } |
|---|
| 270 | |
|---|
| 271 | # build it... |
|---|
| 272 | set w [eval Build $args] |
|---|
| 273 | |
|---|
| 274 | # set some bindings... |
|---|
| 275 | SetBindings $w |
|---|
| 276 | |
|---|
| 277 | # and we are done! |
|---|
| 278 | return $w |
|---|
| 279 | } |
|---|
| 280 | |
|---|
| 281 | # ::mclistbox::Build -- |
|---|
| 282 | # |
|---|
| 283 | # This does all of the work necessary to create the basic |
|---|
| 284 | # mclistbox. |
|---|
| 285 | # |
|---|
| 286 | # Arguments: |
|---|
| 287 | # |
|---|
| 288 | # w widget name |
|---|
| 289 | # args additional option/value pairs |
|---|
| 290 | # |
|---|
| 291 | # Results: |
|---|
| 292 | # |
|---|
| 293 | # Creates a new widget with the given name. Also creates a new |
|---|
| 294 | # namespace patterened after the widget name, as a child namespace |
|---|
| 295 | # to ::mclistbox |
|---|
| 296 | # |
|---|
| 297 | # Returns: |
|---|
| 298 | # |
|---|
| 299 | # the name of the widget |
|---|
| 300 | |
|---|
| 301 | proc ::mclistbox::Build {w args} { |
|---|
| 302 | variable widgetOptions |
|---|
| 303 | |
|---|
| 304 | # create the namespace for this instance, and define a few |
|---|
| 305 | # variables |
|---|
| 306 | namespace eval ::mclistbox::$w { |
|---|
| 307 | |
|---|
| 308 | variable options |
|---|
| 309 | variable widgets |
|---|
| 310 | variable misc |
|---|
| 311 | } |
|---|
| 312 | |
|---|
| 313 | # this gives us access to the namespace variables within |
|---|
| 314 | # this proc |
|---|
| 315 | upvar ::mclistbox::${w}::widgets widgets |
|---|
| 316 | upvar ::mclistbox::${w}::options options |
|---|
| 317 | upvar ::mclistbox::${w}::misc misc |
|---|
| 318 | |
|---|
| 319 | # initially we start out with no columns |
|---|
| 320 | set misc(columns) {} |
|---|
| 321 | |
|---|
| 322 | # this is our widget -- a frame of class Mclistbox. Naturally, |
|---|
| 323 | # it will contain other widgets. We create it here because |
|---|
| 324 | # we need it to be able to set our default options. |
|---|
| 325 | set widgets(this) [frame $w -class Mclistbox -takefocus 1] |
|---|
| 326 | |
|---|
| 327 | # this defines all of the default options. We get the |
|---|
| 328 | # values from the option database. Note that if an array |
|---|
| 329 | # value is a list of length one it is an alias to another |
|---|
| 330 | # option, so we just ignore it |
|---|
| 331 | foreach name [array names widgetOptions] { |
|---|
| 332 | if {[llength $widgetOptions($name)] == 1} continue |
|---|
| 333 | set optName [lindex $widgetOptions($name) 0] |
|---|
| 334 | set optClass [lindex $widgetOptions($name) 1] |
|---|
| 335 | set options($name) [option get $w $optName $optClass] |
|---|
| 336 | } |
|---|
| 337 | |
|---|
| 338 | # now apply any of the options supplied on the command |
|---|
| 339 | # line. This may overwrite our defaults, which is OK |
|---|
| 340 | if {[llength $args] > 0} { |
|---|
| 341 | array set options $args |
|---|
| 342 | } |
|---|
| 343 | |
|---|
| 344 | # the columns all go into a text widget since it has the |
|---|
| 345 | # ability to scroll. |
|---|
| 346 | set widgets(text) [text $w.text \ |
|---|
| 347 | -width 0 \ |
|---|
| 348 | -height 0 \ |
|---|
| 349 | -padx 0 \ |
|---|
| 350 | -pady 0 \ |
|---|
| 351 | -wrap none \ |
|---|
| 352 | -borderwidth 0 \ |
|---|
| 353 | -highlightthickness 0 \ |
|---|
| 354 | -takefocus 0 \ |
|---|
| 355 | -cursor {} \ |
|---|
| 356 | ] |
|---|
| 357 | |
|---|
| 358 | $widgets(text) configure -state disabled |
|---|
| 359 | |
|---|
| 360 | # here's the tricky part (shhhh... don't tell anybody!) |
|---|
| 361 | # we are going to create column that completely fills |
|---|
| 362 | # the base frame. We will use it to control the sizing |
|---|
| 363 | # of the widget. The trick is, we'll pack it in the frame |
|---|
| 364 | # and then place the text widget over it so it is never |
|---|
| 365 | # seen. |
|---|
| 366 | |
|---|
| 367 | set columnWidgets [NewColumn $w {__hidden__}] |
|---|
| 368 | set widgets(hiddenFrame) [lindex $columnWidgets 0] |
|---|
| 369 | set widgets(hiddenListbox) [lindex $columnWidgets 1] |
|---|
| 370 | set widgets(hiddenLabel) [lindex $columnWidgets 2] |
|---|
| 371 | |
|---|
| 372 | # by default geometry propagation is turned off, but for this |
|---|
| 373 | # super-secret widget we want it turned on. The idea is, we |
|---|
| 374 | # resize the listbox which resizes the frame which resizes the |
|---|
| 375 | # whole shibang. |
|---|
| 376 | pack propagate $widgets(hiddenFrame) on |
|---|
| 377 | |
|---|
| 378 | pack $widgets(hiddenFrame) -side top -fill both -expand y |
|---|
| 379 | place $widgets(text) -x 0 -y 0 -relwidth 1.0 -relheight 1.0 |
|---|
| 380 | raise $widgets(text) |
|---|
| 381 | |
|---|
| 382 | # we will later rename the frame's widget proc to be our |
|---|
| 383 | # own custom widget proc. We need to keep track of this |
|---|
| 384 | # new name, so we'll define and store it here... |
|---|
| 385 | set widgets(frame) ::mclistbox::${w}::$w |
|---|
| 386 | |
|---|
| 387 | # this moves the original frame widget proc into our |
|---|
| 388 | # namespace and gives it a handy name |
|---|
| 389 | rename ::$w $widgets(frame) |
|---|
| 390 | |
|---|
| 391 | # now, create our widget proc. Obviously (?) it goes in |
|---|
| 392 | # the global namespace. All mclistbox widgets will actually |
|---|
| 393 | # share the same widget proc to cut down on the amount of |
|---|
| 394 | # bloat. |
|---|
| 395 | proc ::$w {command args} \ |
|---|
| 396 | "eval ::mclistbox::WidgetProc {$w} \$command \$args" |
|---|
| 397 | |
|---|
| 398 | # ok, the thing exists... let's do a bit more configuration. |
|---|
| 399 | if {[catch "Configure $widgets(this) [array get options]" error]} { |
|---|
| 400 | catch {destroy $w} |
|---|
| 401 | } |
|---|
| 402 | |
|---|
| 403 | # and be prepared to handle selections.. (this, for -exportselection |
|---|
| 404 | # support) |
|---|
| 405 | selection handle $w [list ::mclistbox::SelectionHandler $w get] |
|---|
| 406 | |
|---|
| 407 | return $w |
|---|
| 408 | } |
|---|
| 409 | |
|---|
| 410 | # ::mclistbox::SelectionHandler -- |
|---|
| 411 | # |
|---|
| 412 | # handle reqests to set or retrieve the primary selection. This is |
|---|
| 413 | # the "guts" of the implementation of the -exportselection option. |
|---|
| 414 | # What a pain! Note that this command is *not* called as a result |
|---|
| 415 | # of the widget's "selection" command, but rather as a result of |
|---|
| 416 | # the global selection being set or cleared. |
|---|
| 417 | # |
|---|
| 418 | # If I read the ICCCM correctly (which is doubtful; who has time to |
|---|
| 419 | # read that thing thoroughly?), this should return each row as a tab |
|---|
| 420 | # separated list of values, and the whole as a newline separated |
|---|
| 421 | # list of rows. |
|---|
| 422 | # |
|---|
| 423 | # Arguments: |
|---|
| 424 | # |
|---|
| 425 | # w pathname of the widget |
|---|
| 426 | # type one of "own", "lose" or "get" |
|---|
| 427 | # offset only used if type is "get"; offset into the selection |
|---|
| 428 | # buffer where the returned data should begin |
|---|
| 429 | # length number of bytes to return |
|---|
| 430 | # |
|---|
| 431 | |
|---|
| 432 | proc ::mclistbox::SelectionHandler {w type {offset ""} {length ""}} { |
|---|
| 433 | upvar ::mclistbox::${w}::options options |
|---|
| 434 | upvar ::mclistbox::${w}::misc misc |
|---|
| 435 | upvar ::mclistbox::${w}::widgets widgets |
|---|
| 436 | |
|---|
| 437 | switch -exact $type { |
|---|
| 438 | |
|---|
| 439 | own { |
|---|
| 440 | selection own \ |
|---|
| 441 | -command [list ::mclistbox::SelectionHandler $w lose] \ |
|---|
| 442 | -selection PRIMARY \ |
|---|
| 443 | $w |
|---|
| 444 | } |
|---|
| 445 | |
|---|
| 446 | lose { |
|---|
| 447 | if {$options(-exportselection)} { |
|---|
| 448 | foreach id $misc(columns) { |
|---|
| 449 | $widgets(listbox$id) selection clear 0 end |
|---|
| 450 | } |
|---|
| 451 | } |
|---|
| 452 | } |
|---|
| 453 | |
|---|
| 454 | get { |
|---|
| 455 | set end [expr {$length + $offset - 1}] |
|---|
| 456 | |
|---|
| 457 | set column [lindex $misc(columns) 0] |
|---|
| 458 | set curselection [$widgets(listbox$column) curselection] |
|---|
| 459 | |
|---|
| 460 | # this is really, really slow (relatively speaking). |
|---|
| 461 | # but the only way I can think of to speed this up |
|---|
| 462 | # is to duplicate all the data in our hidden listbox, |
|---|
| 463 | # which I really don't want to do because of memory |
|---|
| 464 | # considerations. |
|---|
| 465 | set data "" |
|---|
| 466 | foreach index $curselection { |
|---|
| 467 | set rowdata [join [::mclistbox::WidgetProc-get $w $index] "\t"] |
|---|
| 468 | lappend data $rowdata |
|---|
| 469 | } |
|---|
| 470 | set data [join $data "\n"] |
|---|
| 471 | return [string range $data $offset $end] |
|---|
| 472 | } |
|---|
| 473 | |
|---|
| 474 | } |
|---|
| 475 | } |
|---|
| 476 | |
|---|
| 477 | # ::mclistbox::convert -- |
|---|
| 478 | # |
|---|
| 479 | # public routine to convert %x, %y and %W binding substitutions. |
|---|
| 480 | # Given an x, y and or %W value relative to a given widget, this |
|---|
| 481 | # routine will convert the values to be relative to the mclistbox |
|---|
| 482 | # widget. For example, it could be used in a binding like this: |
|---|
| 483 | # |
|---|
| 484 | # bind .mclistbox <blah> {doSomething [::mclistbox::convert %W -x %x]} |
|---|
| 485 | # |
|---|
| 486 | # Note that this procedure is *not* exported, but is indented for |
|---|
| 487 | # public use. It is not exported because the name could easily |
|---|
| 488 | # clash with existing commands. |
|---|
| 489 | # |
|---|
| 490 | # Arguments: |
|---|
| 491 | # |
|---|
| 492 | # w a widget path; typically the actual result of a %W |
|---|
| 493 | # substitution in a binding. It should be either a |
|---|
| 494 | # mclistbox widget or one of its subwidgets |
|---|
| 495 | # |
|---|
| 496 | # args should one or more of the following arguments or |
|---|
| 497 | # pairs of arguments: |
|---|
| 498 | # |
|---|
| 499 | # -x <x> will convert the value <x>; typically <x> will |
|---|
| 500 | # be the result of a %x substitution |
|---|
| 501 | # -y <y> will convert the value <y>; typically <y> will |
|---|
| 502 | # be the result of a %y substitution |
|---|
| 503 | # -W (or -w) will return the name of the mclistbox widget |
|---|
| 504 | # which is the parent of $w |
|---|
| 505 | # |
|---|
| 506 | # Returns: |
|---|
| 507 | # |
|---|
| 508 | # a list of the requested values. For example, a single -w will |
|---|
| 509 | # result in a list of one items, the name of the mclistbox widget. |
|---|
| 510 | # Supplying "-x 10 -y 20 -W" (in any order) will return a list of |
|---|
| 511 | # three values: the converted x and y values, and the name of |
|---|
| 512 | # the mclistbox widget. |
|---|
| 513 | |
|---|
| 514 | proc ::mclistbox::convert {w args} { |
|---|
| 515 | set result {} |
|---|
| 516 | if {![winfo exists $w]} { |
|---|
| 517 | error "window \"$w\" doesn't exist" |
|---|
| 518 | } |
|---|
| 519 | |
|---|
| 520 | while {[llength $args] > 0} { |
|---|
| 521 | set option [lindex $args 0] |
|---|
| 522 | set args [lrange $args 1 end] |
|---|
| 523 | |
|---|
| 524 | switch -exact -- $option { |
|---|
| 525 | -x { |
|---|
| 526 | set value [lindex $args 0] |
|---|
| 527 | set args [lrange $args 1 end] |
|---|
| 528 | set win $w |
|---|
| 529 | while {[winfo class $win] != "Mclistbox"} { |
|---|
| 530 | incr value [winfo x $win] |
|---|
| 531 | set win [winfo parent $win] |
|---|
| 532 | if {$win == "."} break |
|---|
| 533 | } |
|---|
| 534 | lappend result $value |
|---|
| 535 | } |
|---|
| 536 | |
|---|
| 537 | -y { |
|---|
| 538 | set value [lindex $args 0] |
|---|
| 539 | set args [lrange $args 1 end] |
|---|
| 540 | set win $w |
|---|
| 541 | while {[winfo class $win] != "Mclistbox"} { |
|---|
| 542 | incr value [winfo y $win] |
|---|
| 543 | set win [winfo parent $win] |
|---|
| 544 | if {$win == "."} break |
|---|
| 545 | } |
|---|
| 546 | lappend result $value |
|---|
| 547 | } |
|---|
| 548 | |
|---|
| 549 | -w - |
|---|
| 550 | -W { |
|---|
| 551 | set win $w |
|---|
| 552 | while {[winfo class $win] != "Mclistbox"} { |
|---|
| 553 | set win [winfo parent $win] |
|---|
| 554 | if {$win == "."} break; |
|---|
| 555 | } |
|---|
| 556 | lappend result $win |
|---|
| 557 | } |
|---|
| 558 | } |
|---|
| 559 | } |
|---|
| 560 | return $result |
|---|
| 561 | } |
|---|
| 562 | |
|---|
| 563 | # ::mclistbox::SetBindings -- |
|---|
| 564 | # |
|---|
| 565 | # Sets up the default bindings for the named widget |
|---|
| 566 | # |
|---|
| 567 | # Arguments: |
|---|
| 568 | # |
|---|
| 569 | # w the widget pathname for which the bindings should be assigned |
|---|
| 570 | # |
|---|
| 571 | # Results: |
|---|
| 572 | # |
|---|
| 573 | # The named widget will inheirit all of the default Mclistbox |
|---|
| 574 | # bindings. |
|---|
| 575 | |
|---|
| 576 | proc ::mclistbox::SetBindings {w} { |
|---|
| 577 | upvar ::mclistbox::${w}::widgets widgets |
|---|
| 578 | upvar ::mclistbox::${w}::options options |
|---|
| 579 | upvar ::mclistbox::${w}::misc misc |
|---|
| 580 | |
|---|
| 581 | # we must do this so that the columns fill the text widget in |
|---|
| 582 | # the y direction |
|---|
| 583 | bind $widgets(text) <Configure> \ |
|---|
| 584 | [list ::mclistbox::AdjustColumns $w %h] |
|---|
| 585 | |
|---|
| 586 | } |
|---|
| 587 | |
|---|
| 588 | # ::mclistbox::SetClassBindings -- |
|---|
| 589 | # |
|---|
| 590 | # Sets up the default bindings for the widget class |
|---|
| 591 | # |
|---|
| 592 | # Arguments: |
|---|
| 593 | # |
|---|
| 594 | # none |
|---|
| 595 | # |
|---|
| 596 | |
|---|
| 597 | proc ::mclistbox::SetClassBindings {} { |
|---|
| 598 | # this allows us to clean up some things when we go away |
|---|
| 599 | bind Mclistbox <Destroy> [list ::mclistbox::DestroyHandler %W] |
|---|
| 600 | |
|---|
| 601 | # steal all of the standard listbox bindings. Note that if a user |
|---|
| 602 | # clicks in a column, %W will return that column. This is bad, |
|---|
| 603 | # so we have to make a substitution in all of the bindings to |
|---|
| 604 | # compute the real widget name (ie: the name of the topmost |
|---|
| 605 | # frame) |
|---|
| 606 | foreach event [bind Listbox] { |
|---|
| 607 | set binding [bind Listbox $event] |
|---|
| 608 | regsub -all {%W} $binding {[::mclistbox::convert %W -W]} binding |
|---|
| 609 | regsub -all {%x} $binding {[::mclistbox::convert %W -x %x]} binding |
|---|
| 610 | regsub -all {%y} $binding {[::mclistbox::convert %W -y %y]} binding |
|---|
| 611 | bind Mclistbox $event $binding |
|---|
| 612 | } |
|---|
| 613 | |
|---|
| 614 | # these define bindings for the column labels for resizing. Note |
|---|
| 615 | # that we need both the name of this widget (calculated by $this) |
|---|
| 616 | # as well as the specific widget that the event occured over. |
|---|
| 617 | # Also note that $this is a constant string that gets evaluated |
|---|
| 618 | # when the binding fires. |
|---|
| 619 | # What a pain. |
|---|
| 620 | set this {[::mclistbox::convert %W -W]} |
|---|
| 621 | bind MclistboxMouseBindings <ButtonPress-1> \ |
|---|
| 622 | "::mclistbox::ResizeEvent $this buttonpress %W %x %X %Y" |
|---|
| 623 | bind MclistboxMouseBindings <ButtonRelease-1> \ |
|---|
| 624 | "::mclistbox::ResizeEvent $this buttonrelease %W %x %X %Y" |
|---|
| 625 | bind MclistboxMouseBindings <Enter> \ |
|---|
| 626 | "::mclistbox::ResizeEvent $this motion %W %x %X %Y" |
|---|
| 627 | bind MclistboxMouseBindings <Motion> \ |
|---|
| 628 | "::mclistbox::ResizeEvent $this motion %W %x %X %Y" |
|---|
| 629 | bind MclistboxMouseBindings <B1-Motion> \ |
|---|
| 630 | "::mclistbox::ResizeEvent $this drag %W %x %X %Y" |
|---|
| 631 | } |
|---|
| 632 | |
|---|
| 633 | # ::mclistbox::NewColumn -- |
|---|
| 634 | # |
|---|
| 635 | # Adds a new column to the mclistbox widget |
|---|
| 636 | # |
|---|
| 637 | # Arguments: |
|---|
| 638 | # |
|---|
| 639 | # w the widget pathname |
|---|
| 640 | # id the id for the new column |
|---|
| 641 | # |
|---|
| 642 | # Results: |
|---|
| 643 | # |
|---|
| 644 | # Creates a set of widgets which defines the column. Adds |
|---|
| 645 | # appropriate entries to the global array widgets for the |
|---|
| 646 | # new column. |
|---|
| 647 | # |
|---|
| 648 | # Note that this column is not added to the listbox by |
|---|
| 649 | # this proc. |
|---|
| 650 | # |
|---|
| 651 | # Returns: |
|---|
| 652 | # |
|---|
| 653 | # A list of three elements: the path to the column frame, |
|---|
| 654 | # the path to the column listbox, and the path to the column |
|---|
| 655 | # label, in that order. |
|---|
| 656 | |
|---|
| 657 | proc ::mclistbox::NewColumn {w id} { |
|---|
| 658 | upvar ::mclistbox::${w}::widgets widgets |
|---|
| 659 | upvar ::mclistbox::${w}::options options |
|---|
| 660 | upvar ::mclistbox::${w}::misc misc |
|---|
| 661 | upvar ::mclistbox::${w}::columnID columnID |
|---|
| 662 | |
|---|
| 663 | # the columns are all children of the text widget we created... |
|---|
| 664 | set frame \ |
|---|
| 665 | [frame $w.frame$id \ |
|---|
| 666 | -takefocus 0 \ |
|---|
| 667 | -highlightthickness 0 \ |
|---|
| 668 | -class MclistboxColumn \ |
|---|
| 669 | -background $options(-background) \ |
|---|
| 670 | ] |
|---|
| 671 | |
|---|
| 672 | set listbox \ |
|---|
| 673 | [listbox $frame.listbox \ |
|---|
| 674 | -takefocus 0 \ |
|---|
| 675 | -bd 0 \ |
|---|
| 676 | -setgrid $options(-setgrid) \ |
|---|
| 677 | -exportselection false \ |
|---|
| 678 | -selectmode $options(-selectmode) \ |
|---|
| 679 | -highlightthickness 0 \ |
|---|
| 680 | ] |
|---|
| 681 | |
|---|
| 682 | set label \ |
|---|
| 683 | [label $frame.label \ |
|---|
| 684 | -takefocus 0 \ |
|---|
| 685 | -relief raised \ |
|---|
| 686 | -bd 1 \ |
|---|
| 687 | -highlightthickness 0 \ |
|---|
| 688 | ] |
|---|
| 689 | |
|---|
| 690 | # define mappings from widgets to columns |
|---|
| 691 | set columnID($label) $id |
|---|
| 692 | set columnID($frame) $id |
|---|
| 693 | set columnID($listbox) $id |
|---|
| 694 | |
|---|
| 695 | # we're going to associate a new bindtag for the label to |
|---|
| 696 | # handle our resize bindings. Why? We want the bindings to |
|---|
| 697 | # be specific to this widget but we don't want to use the |
|---|
| 698 | # widget name. If we use the widget name then the bindings |
|---|
| 699 | # could get mixed up with user-supplied bindigs (via the |
|---|
| 700 | # "label bind" command). |
|---|
| 701 | set tag MclistboxLabel |
|---|
| 702 | bindtags $label [list MclistboxMouseBindings $label] |
|---|
| 703 | |
|---|
| 704 | # reconfigure the label based on global options |
|---|
| 705 | foreach option [list bd image height relief font anchor \ |
|---|
| 706 | background foreground borderwidth] { |
|---|
| 707 | if {[info exists options(-label$option)] \ |
|---|
| 708 | && $options(-label$option) != ""} { |
|---|
| 709 | $label configure -$option $options(-label$option) |
|---|
| 710 | } |
|---|
| 711 | } |
|---|
| 712 | |
|---|
| 713 | # reconfigure the column based on global options |
|---|
| 714 | foreach option [list borderwidth relief] { |
|---|
| 715 | if {[info exists options(-column$option)] \ |
|---|
| 716 | && $options(-column$option) != ""} { |
|---|
| 717 | $frame configure -$option $options(-column$option) |
|---|
| 718 | } |
|---|
| 719 | } |
|---|
| 720 | |
|---|
| 721 | # geometry propagation must be off so we can control the size |
|---|
| 722 | # of the listbox by setting the size of the containing frame |
|---|
| 723 | pack propagate $frame off |
|---|
| 724 | |
|---|
| 725 | pack $label -side top -fill x -expand n |
|---|
| 726 | pack $listbox -side top -fill both -expand y -pady 2 |
|---|
| 727 | |
|---|
| 728 | # any events that happen in the listbox gets handled by the class |
|---|
| 729 | # bindings. This has the unfortunate side effect |
|---|
| 730 | bindtags $listbox [list $w Mclistbox all] |
|---|
| 731 | |
|---|
| 732 | # return a list of the widgets we created. |
|---|
| 733 | return [list $frame $listbox $label] |
|---|
| 734 | } |
|---|
| 735 | |
|---|
| 736 | # ::mclistbox::Column-add -- |
|---|
| 737 | # |
|---|
| 738 | # Implements the "column add" widget command |
|---|
| 739 | # |
|---|
| 740 | # Arguments: |
|---|
| 741 | # |
|---|
| 742 | # w the widget pathname |
|---|
| 743 | # args additional option/value pairs which define the column |
|---|
| 744 | # |
|---|
| 745 | # Results: |
|---|
| 746 | # |
|---|
| 747 | # A column gets created and added to the listbox |
|---|
| 748 | |
|---|
| 749 | proc ::mclistbox::Column-add {w args} { |
|---|
| 750 | upvar ::mclistbox::${w}::widgets widgets |
|---|
| 751 | upvar ::mclistbox::${w}::options options |
|---|
| 752 | upvar ::mclistbox::${w}::misc misc |
|---|
| 753 | |
|---|
| 754 | variable widgetOptions |
|---|
| 755 | |
|---|
| 756 | set id "column-[llength $misc(columns)]" ;# a suitable default |
|---|
| 757 | |
|---|
| 758 | # if the first argument doesn't have a "-" as the first |
|---|
| 759 | # character, it is an id to associate with this column |
|---|
| 760 | if {![string match {-*} [lindex $args 0]]} { |
|---|
| 761 | # the first arg must be an id. |
|---|
| 762 | set id [lindex $args 0] |
|---|
| 763 | set args [lrange $args 1 end] |
|---|
| 764 | if {[lsearch -exact $misc(columns) $id] != -1} { |
|---|
| 765 | error "column \"$id\" already exists" |
|---|
| 766 | } |
|---|
| 767 | } |
|---|
| 768 | |
|---|
| 769 | # define some reasonable defaults, then add any specific |
|---|
| 770 | # values supplied by the user |
|---|
| 771 | set opts(-bitmap) {} |
|---|
| 772 | set opts(-image) {} |
|---|
| 773 | set opts(-visible) 1 |
|---|
| 774 | set opts(-resizable) 1 |
|---|
| 775 | set opts(-position) "end" |
|---|
| 776 | set opts(-width) 20 |
|---|
| 777 | set opts(-background) $options(-background) |
|---|
| 778 | set opts(-foreground) $options(-foreground) |
|---|
| 779 | set opts(-font) $options(-font) |
|---|
| 780 | set opts(-label) $id |
|---|
| 781 | |
|---|
| 782 | if {[expr {[llength $args]%2}] == 1} { |
|---|
| 783 | # hmmm. An odd number of elements in args |
|---|
| 784 | # if the last item is a valid option we'll give a different |
|---|
| 785 | # error than if its not |
|---|
| 786 | set option [::mclistbox::Canonize $w "column option" [lindex $args end]] |
|---|
| 787 | error "value for \"[lindex $args end]\" missing" |
|---|
| 788 | } |
|---|
| 789 | array set opts $args |
|---|
| 790 | |
|---|
| 791 | # figure out if we have any data in the listbox yet; we'll need |
|---|
| 792 | # this information in a minute... |
|---|
| 793 | if {[llength $misc(columns)] > 0} { |
|---|
| 794 | set col0 [lindex $misc(columns) 0] |
|---|
| 795 | set existingRows [$widgets(listbox$col0) size] |
|---|
| 796 | } else { |
|---|
| 797 | set existingRows 0 |
|---|
| 798 | } |
|---|
| 799 | |
|---|
| 800 | # create the widget and assign the associated paths to our array |
|---|
| 801 | set widgetlist [NewColumn $w $id] |
|---|
| 802 | |
|---|
| 803 | set widgets(frame$id) [lindex $widgetlist 0] |
|---|
| 804 | set widgets(listbox$id) [lindex $widgetlist 1] |
|---|
| 805 | set widgets(label$id) [lindex $widgetlist 2] |
|---|
| 806 | |
|---|
| 807 | # add this column to the list of known columns |
|---|
| 808 | lappend misc(columns) $id |
|---|
| 809 | |
|---|
| 810 | # configure the options. As a side effect, it will be inserted |
|---|
| 811 | # in the text widget |
|---|
| 812 | eval ::mclistbox::Column-configure {$w} {$id} [array get opts] |
|---|
| 813 | |
|---|
| 814 | # now, if there is any data already in the listbox, we need to |
|---|
| 815 | # add a corresponding number of blank items. At least, I *think* |
|---|
| 816 | # that's the right thing to do. |
|---|
| 817 | if {$existingRows > 0} { |
|---|
| 818 | set blanks {} |
|---|
| 819 | for {set i 0} {$i < $existingRows} {incr i} { |
|---|
| 820 | lappend blanks {} |
|---|
| 821 | } |
|---|
| 822 | eval {$widgets(listbox$id)} insert end $blanks |
|---|
| 823 | } |
|---|
| 824 | |
|---|
| 825 | InvalidateScrollbars $w |
|---|
| 826 | return $id |
|---|
| 827 | } |
|---|
| 828 | |
|---|
| 829 | # ::mclistbox::Column-configure -- |
|---|
| 830 | # |
|---|
| 831 | # Implements the "column configure" widget command |
|---|
| 832 | # |
|---|
| 833 | # Arguments: |
|---|
| 834 | # |
|---|
| 835 | # w widget pathname |
|---|
| 836 | # id column identifier |
|---|
| 837 | # args list of option/value pairs |
|---|
| 838 | |
|---|
| 839 | proc ::mclistbox::Column-configure {w id args} { |
|---|
| 840 | variable widgetOptions |
|---|
| 841 | variable columnOptions |
|---|
| 842 | |
|---|
| 843 | upvar ::mclistbox::${w}::widgets widgets |
|---|
| 844 | upvar ::mclistbox::${w}::options options |
|---|
| 845 | upvar ::mclistbox::${w}::misc misc |
|---|
| 846 | |
|---|
| 847 | # bail if they gave us a bogus id |
|---|
| 848 | set index [CheckColumnID $w $id] |
|---|
| 849 | |
|---|
| 850 | # define some shorthand |
|---|
| 851 | set listbox $widgets(listbox$id) |
|---|
| 852 | set frame $widgets(frame$id) |
|---|
| 853 | set label $widgets(label$id) |
|---|
| 854 | |
|---|
| 855 | if {[llength $args] == 0} { |
|---|
| 856 | # hmmm. User must be wanting all configuration information |
|---|
| 857 | # note that if the value of an array element is of length |
|---|
| 858 | # one it is an alias, which needs to be handled slightly |
|---|
| 859 | # differently |
|---|
| 860 | set results {} |
|---|
| 861 | foreach opt [lsort [array names columnOptions]] { |
|---|
| 862 | if {[llength $columnOptions($opt)] == 1} { |
|---|
| 863 | set alias $columnOptions($opt) |
|---|
| 864 | set optName $columnOptions($alias) |
|---|
| 865 | lappend results [list $opt $optName] |
|---|
| 866 | } else { |
|---|
| 867 | set optName [lindex $columnOptions($opt) 0] |
|---|
| 868 | set optClass [lindex $columnOptions($opt) 1] |
|---|
| 869 | set default [option get $frame $optName $optClass] |
|---|
| 870 | lappend results [list $opt $optName $optClass \ |
|---|
| 871 | $default $options($id:$opt)] |
|---|
| 872 | } |
|---|
| 873 | } |
|---|
| 874 | |
|---|
| 875 | return $results |
|---|
| 876 | |
|---|
| 877 | |
|---|
| 878 | } elseif {[llength $args] == 1} { |
|---|
| 879 | |
|---|
| 880 | # the user must be querying something... I need to get this |
|---|
| 881 | # to return a bona fide list like the "real" configure |
|---|
| 882 | # command, but it's not a priority at the moment. I still |
|---|
| 883 | # have to work on the option database support foo. |
|---|
| 884 | set option [::mclistbox::Canonize $w "column option" [lindex $args 0]] |
|---|
| 885 | |
|---|
| 886 | set value $options($id:$option) |
|---|
| 887 | set optName [lindex $columnOptions($option) 0] |
|---|
| 888 | set optClass [lindex $columnOptions($option) 1] |
|---|
| 889 | set default [option get $frame $optName $optClass] |
|---|
| 890 | set results [list $option $optName $optClass $default $value] |
|---|
| 891 | return $results |
|---|
| 892 | |
|---|
| 893 | } |
|---|
| 894 | |
|---|
| 895 | # if we have an odd number of values, bail. |
|---|
| 896 | if {[expr {[llength $args]%2}] == 1} { |
|---|
| 897 | # hmmm. An odd number of elements in args |
|---|
| 898 | error "value for \"[lindex $args end]\" missing" |
|---|
| 899 | } |
|---|
| 900 | |
|---|
| 901 | # Great. An even number of options. Let's make sure they |
|---|
| 902 | # are all valid before we do anything. Note that Canonize |
|---|
| 903 | # will generate an error if it finds a bogus option; otherwise |
|---|
| 904 | # it returns the canonical option name |
|---|
| 905 | foreach {name value} $args { |
|---|
| 906 | set name [::mclistbox::Canonize $w "column option" $name] |
|---|
| 907 | set opts($name) $value |
|---|
| 908 | } |
|---|
| 909 | |
|---|
| 910 | # if we get to here, the user is wanting to set some options |
|---|
| 911 | foreach option [array names opts] { |
|---|
| 912 | set value $opts($option) |
|---|
| 913 | set options($id:$option) $value |
|---|
| 914 | |
|---|
| 915 | switch -- $option { |
|---|
| 916 | -label { |
|---|
| 917 | $label configure -text $value |
|---|
| 918 | } |
|---|
| 919 | |
|---|
| 920 | -image - |
|---|
| 921 | -bitmap { |
|---|
| 922 | $label configure $option $value |
|---|
| 923 | } |
|---|
| 924 | |
|---|
| 925 | -width { |
|---|
| 926 | set font [$listbox cget -font] |
|---|
| 927 | set factor [font measure $options(-font) "0"] |
|---|
| 928 | set width [expr {$value * $factor}] |
|---|
| 929 | |
|---|
| 930 | $widgets(frame$id) configure -width $width |
|---|
| 931 | set misc(min-$widgets(frame$id)) $width |
|---|
| 932 | AdjustColumns $w |
|---|
| 933 | } |
|---|
| 934 | -font - |
|---|
| 935 | -foreground - |
|---|
| 936 | -background { |
|---|
| 937 | if {[string length $value] == 0} {set value $options($option)} |
|---|
| 938 | $listbox configure $option $value |
|---|
| 939 | } |
|---|
| 940 | |
|---|
| 941 | -resizable { |
|---|
| 942 | if {[catch { |
|---|
| 943 | if {$value} { |
|---|
| 944 | set options($id:-resizable) 1 |
|---|
| 945 | } else { |
|---|
| 946 | set options($id:-resizable) 0 |
|---|
| 947 | } |
|---|
| 948 | } msg]} { |
|---|
| 949 | error "expected boolean but got \"$value\"" |
|---|
| 950 | } |
|---|
| 951 | } |
|---|
| 952 | |
|---|
| 953 | -visible { |
|---|
| 954 | if {[catch { |
|---|
| 955 | if {$value} { |
|---|
| 956 | set options($id:-visible) 1 |
|---|
| 957 | $widgets(text) configure -state normal |
|---|
| 958 | $widgets(text) window configure 1.$index -window $frame |
|---|
| 959 | $widgets(text) configure -state disabled |
|---|
| 960 | |
|---|
| 961 | } else { |
|---|
| 962 | set options($id:-visible) 0 |
|---|
| 963 | $widgets(text) configure -state normal |
|---|
| 964 | $widgets(text) window configure 1.$index -window {} |
|---|
| 965 | $widgets(text) configure -state disabled |
|---|
| 966 | } |
|---|
| 967 | InvalidateScrollbars $w |
|---|
| 968 | } msg]} { |
|---|
| 969 | error "expected boolean but got \"$value\"" |
|---|
| 970 | } |
|---|
| 971 | |
|---|
| 972 | } |
|---|
| 973 | |
|---|
| 974 | -position { |
|---|
| 975 | if {[string compare $value "start"] == 0} { |
|---|
| 976 | set position 0 |
|---|
| 977 | |
|---|
| 978 | } elseif {[string compare $value "end"] == 0} { |
|---|
| 979 | |
|---|
| 980 | set position [expr {[llength $misc(columns)] -1}] |
|---|
| 981 | } else { |
|---|
| 982 | |
|---|
| 983 | # ought to check for a legal value here, but I'm |
|---|
| 984 | # lazy |
|---|
| 985 | set position $value |
|---|
| 986 | } |
|---|
| 987 | |
|---|
| 988 | if {$position >= [llength $misc(columns)]} { |
|---|
| 989 | set max [expr {[llength $misc(columns)] -1}] |
|---|
| 990 | error "bad position; must be in the range of 0-$max" |
|---|
| 991 | } |
|---|
| 992 | |
|---|
| 993 | # rearrange misc(columns) to reflect the new ordering |
|---|
| 994 | set current [lsearch -exact $misc(columns) $id] |
|---|
| 995 | set misc(columns) [lreplace $misc(columns) $current $current] |
|---|
| 996 | set misc(columns) [linsert $misc(columns) $position $id] |
|---|
| 997 | |
|---|
| 998 | set frame $widgets(frame$id) |
|---|
| 999 | $widgets(text) configure -state normal |
|---|
| 1000 | $widgets(text) window create 1.$position \ |
|---|
| 1001 | -window $frame -stretch 1 |
|---|
| 1002 | $widgets(text) configure -state disabled |
|---|
| 1003 | } |
|---|
| 1004 | |
|---|
| 1005 | } |
|---|
| 1006 | } |
|---|
| 1007 | } |
|---|
| 1008 | |
|---|
| 1009 | |
|---|
| 1010 | # ::mclistbox::DestroyHandler {w} -- |
|---|
| 1011 | # |
|---|
| 1012 | # Cleans up after a mclistbox widget is destroyed |
|---|
| 1013 | # |
|---|
| 1014 | # Arguments: |
|---|
| 1015 | # |
|---|
| 1016 | # w widget pathname |
|---|
| 1017 | # |
|---|
| 1018 | # Results: |
|---|
| 1019 | # |
|---|
| 1020 | # The namespace that was created for the widget is deleted, |
|---|
| 1021 | # and the widget proc is removed. |
|---|
| 1022 | |
|---|
| 1023 | proc ::mclistbox::DestroyHandler {w} { |
|---|
| 1024 | |
|---|
| 1025 | # kill off any idle event we might have pending |
|---|
| 1026 | if {[info exists ::mclistbox::${w}::misc(afterid)]} { |
|---|
| 1027 | catch { |
|---|
| 1028 | after cancel $::mclistbox::${w}::misc(afterid) |
|---|
| 1029 | unset ::mclistbox::${w}::misc(afterid) |
|---|
| 1030 | } |
|---|
| 1031 | } |
|---|
| 1032 | |
|---|
| 1033 | # if the widget actually being destroyed is of class Mclistbox, |
|---|
| 1034 | # crush the namespace and kill the proc. Get it? Crush. Kill. |
|---|
| 1035 | # Destroy. Heh. Danger Will Robinson! Oh, man! I'm so funny it |
|---|
| 1036 | # brings tears to my eyes. |
|---|
| 1037 | if {[string compare [winfo class $w] "Mclistbox"] == 0} { |
|---|
| 1038 | namespace delete ::mclistbox::$w |
|---|
| 1039 | rename $w {} |
|---|
| 1040 | } |
|---|
| 1041 | |
|---|
| 1042 | } |
|---|
| 1043 | |
|---|
| 1044 | # ::mclistbox::MassageIndex -- |
|---|
| 1045 | # |
|---|
| 1046 | # this proc massages indicies of the form @x,y such that |
|---|
| 1047 | # the coordinates are relative to the first listbox rather |
|---|
| 1048 | # than relative to the topmost frame. |
|---|
| 1049 | # |
|---|
| 1050 | # Arguments: |
|---|
| 1051 | # |
|---|
| 1052 | # w widget pathname |
|---|
| 1053 | # index an index of the form @x,y |
|---|
| 1054 | # |
|---|
| 1055 | # Results: |
|---|
| 1056 | # |
|---|
| 1057 | # Returns a new index with translated coordinates. This index |
|---|
| 1058 | # may be used directly by an internal listbox. |
|---|
| 1059 | |
|---|
| 1060 | proc ::mclistbox::MassageIndex {w index} { |
|---|
| 1061 | upvar ::mclistbox::${w}::widgets widgets |
|---|
| 1062 | upvar ::mclistbox::${w}::misc misc |
|---|
| 1063 | |
|---|
| 1064 | if {[regexp {@([0-9]+),([0-9]+)} $index matchvar x y]} { |
|---|
| 1065 | set id [lindex $misc(columns) 0] |
|---|
| 1066 | |
|---|
| 1067 | incr y -[winfo y $widgets(listbox$id)] |
|---|
| 1068 | incr y -[winfo y $widgets(frame$id)] |
|---|
| 1069 | incr x [winfo x $widgets(listbox$id)] |
|---|
| 1070 | incr x [winfo x $widgets(frame$id)] |
|---|
| 1071 | |
|---|
| 1072 | set index @${x},${y} |
|---|
| 1073 | } |
|---|
| 1074 | |
|---|
| 1075 | return $index |
|---|
| 1076 | } |
|---|
| 1077 | |
|---|
| 1078 | # ::mclistbox::WidgetProc -- |
|---|
| 1079 | # |
|---|
| 1080 | # This gets uses as the widgetproc for an mclistbox widget. |
|---|
| 1081 | # Notice where the widget is created and you'll see that the |
|---|
| 1082 | # actual widget proc merely evals this proc with all of the |
|---|
| 1083 | # arguments intact. |
|---|
| 1084 | # |
|---|
| 1085 | # Note that some widget commands are defined "inline" (ie: |
|---|
| 1086 | # within this proc), and some do most of their work in |
|---|
| 1087 | # separate procs. This is merely because sometimes it was |
|---|
| 1088 | # easier to do it one way or the other. |
|---|
| 1089 | # |
|---|
| 1090 | # Arguments: |
|---|
| 1091 | # |
|---|
| 1092 | # w widget pathname |
|---|
| 1093 | # command widget subcommand |
|---|
| 1094 | # args additional arguments; varies with the subcommand |
|---|
| 1095 | # |
|---|
| 1096 | # Results: |
|---|
| 1097 | # |
|---|
| 1098 | # Performs the requested widget command |
|---|
| 1099 | |
|---|
| 1100 | proc ::mclistbox::WidgetProc {w command args} { |
|---|
| 1101 | variable widgetOptions |
|---|
| 1102 | |
|---|
| 1103 | upvar ::mclistbox::${w}::widgets widgets |
|---|
| 1104 | upvar ::mclistbox::${w}::options options |
|---|
| 1105 | upvar ::mclistbox::${w}::misc misc |
|---|
| 1106 | upvar ::mclistbox::${w}::columnID columnID |
|---|
| 1107 | |
|---|
| 1108 | set command [::mclistbox::Canonize $w command $command] |
|---|
| 1109 | |
|---|
| 1110 | # some commands have subcommands. We'll check for that here |
|---|
| 1111 | # and mung the command and args so that we can treat them as |
|---|
| 1112 | # distinct commands in the following switch statement |
|---|
| 1113 | if {[string compare $command "column"] == 0} { |
|---|
| 1114 | set subcommand [::mclistbox::Canonize $w "column command" \ |
|---|
| 1115 | [lindex $args 0]] |
|---|
| 1116 | set command "$command-$subcommand" |
|---|
| 1117 | set args [lrange $args 1 end] |
|---|
| 1118 | |
|---|
| 1119 | } elseif {[string compare $command "label"] == 0} { |
|---|
| 1120 | set subcommand [::mclistbox::Canonize $w "label command" \ |
|---|
| 1121 | [lindex $args 0]] |
|---|
| 1122 | set command "$command-$subcommand" |
|---|
| 1123 | set args [lrange $args 1 end] |
|---|
| 1124 | } |
|---|
| 1125 | |
|---|
| 1126 | set result "" |
|---|
| 1127 | catch {unset priorSelection} |
|---|
| 1128 | |
|---|
| 1129 | # here we go. Error checking be damned! |
|---|
| 1130 | switch $command { |
|---|
| 1131 | xview { |
|---|
| 1132 | # note that at present, "xview <index>" is broken. I'm |
|---|
| 1133 | # not even sure how to do it. Unless I attach our hidden |
|---|
| 1134 | # listbox to the scrollbar and use it. Hmmm..... I'll |
|---|
| 1135 | # try that later. (FIXME) |
|---|
| 1136 | set result [eval {$widgets(text)} xview $args] |
|---|
| 1137 | InvalidateScrollbars $w |
|---|
| 1138 | } |
|---|
| 1139 | |
|---|
| 1140 | yview { |
|---|
| 1141 | if {[llength $args] == 0} { |
|---|
| 1142 | # length of zero means to fetch the yview; we can |
|---|
| 1143 | # get this from a single listbox |
|---|
| 1144 | set result [$widgets(hiddenListbox) yview] |
|---|
| 1145 | |
|---|
| 1146 | } else { |
|---|
| 1147 | |
|---|
| 1148 | # if it's one argument, it's an index. We'll pass that |
|---|
| 1149 | # index through the index command to properly translate |
|---|
| 1150 | # @x,y indicies, and place the value back in args |
|---|
| 1151 | if {[llength $args] == 1} { |
|---|
| 1152 | set index [::mclistbox::MassageIndex $w [lindex $args 0]] |
|---|
| 1153 | set args [list $index] |
|---|
| 1154 | } |
|---|
| 1155 | |
|---|
| 1156 | # run the yview command on every column. |
|---|
| 1157 | foreach id $misc(columns) { |
|---|
| 1158 | eval {$widgets(listbox$id)} yview $args |
|---|
| 1159 | } |
|---|
| 1160 | eval {$widgets(hiddenListbox)} yview $args |
|---|
| 1161 | |
|---|
| 1162 | InvalidateScrollbars $w |
|---|
| 1163 | |
|---|
| 1164 | set result "" |
|---|
| 1165 | } |
|---|
| 1166 | } |
|---|
| 1167 | |
|---|
| 1168 | activate { |
|---|
| 1169 | if {[llength $args] != 1} { |
|---|
| 1170 | error "wrong \# of args: should be $w activate index" |
|---|
| 1171 | } |
|---|
| 1172 | set index [::mclistbox::MassageIndex $w [lindex $args 0]] |
|---|
| 1173 | |
|---|
| 1174 | foreach id $misc(columns) { |
|---|
| 1175 | $widgets(listbox$id) activate $index |
|---|
| 1176 | } |
|---|
| 1177 | set result "" |
|---|
| 1178 | } |
|---|
| 1179 | |
|---|
| 1180 | bbox { |
|---|
| 1181 | if {[llength $args] != 1} { |
|---|
| 1182 | error "wrong \# of args: should be $w bbox index" |
|---|
| 1183 | } |
|---|
| 1184 | # get a real index. This will adjust @x,y indicies |
|---|
| 1185 | # to account for the label, if any. |
|---|
| 1186 | set index [::mclistbox::MassageIndex $w [lindex $args 0]] |
|---|
| 1187 | |
|---|
| 1188 | set id [lindex $misc(columns) 0] |
|---|
| 1189 | |
|---|
| 1190 | # we can get the x, y, and height from the first |
|---|
| 1191 | # column. |
|---|
| 1192 | set bbox [$widgets(listbox$id) bbox $index] |
|---|
| 1193 | if {[string length $bbox] == 0} {return ""} |
|---|
| 1194 | |
|---|
| 1195 | foreach {x y w h} $bbox {} |
|---|
| 1196 | |
|---|
| 1197 | # the x and y coordinates have to be adjusted for the |
|---|
| 1198 | # fact that the listbox is inside a frame, and the |
|---|
| 1199 | # frame is inside a text widget. All of those add tiny |
|---|
| 1200 | # offsets. Feh. |
|---|
| 1201 | incr y [winfo y $widgets(listbox$id)] |
|---|
| 1202 | incr y [winfo y $widgets(frame$id)] |
|---|
| 1203 | incr x [winfo x $widgets(listbox$id)] |
|---|
| 1204 | incr x [winfo x $widgets(frame$id)] |
|---|
| 1205 | |
|---|
| 1206 | # we can get the width by looking at the relative x |
|---|
| 1207 | # coordinate of the right edge of the last column |
|---|
| 1208 | set id [lindex $misc(columns) end] |
|---|
| 1209 | set w [expr {[winfo width $widgets(frame$id)] + \ |
|---|
| 1210 | [winfo x $widgets(frame$id)]}] |
|---|
| 1211 | set bbox [list $x $y [expr {$x + $w}] $h] |
|---|
| 1212 | set result $bbox |
|---|
| 1213 | } |
|---|
| 1214 | |
|---|
| 1215 | label-bind { |
|---|
| 1216 | # we are just too clever for our own good. (that's a |
|---|
| 1217 | # polite way of saying this is more complex than it |
|---|
| 1218 | # needs to be) |
|---|
| 1219 | |
|---|
| 1220 | set id [lindex $args 0] |
|---|
| 1221 | set index [CheckColumnID $w $id] |
|---|
| 1222 | |
|---|
| 1223 | set args [lrange $args 1 end] |
|---|
| 1224 | if {[llength $args] == 0} { |
|---|
| 1225 | set result [bind $widgets(label$id)] |
|---|
| 1226 | } else { |
|---|
| 1227 | |
|---|
| 1228 | # when we create a binding, we'll actually have the |
|---|
| 1229 | # binding run our own command with the user's command |
|---|
| 1230 | # as an argument. This way we can do some sanity checks |
|---|
| 1231 | # before running the command. So, when querying a binding |
|---|
| 1232 | # we need to only return the user's code |
|---|
| 1233 | set sequence [lindex $args 0] |
|---|
| 1234 | if {[llength $args] == 1} { |
|---|
| 1235 | set result [lindex [bind $widgets(label$id) $sequence] end] |
|---|
| 1236 | } else { |
|---|
| 1237 | |
|---|
| 1238 | # replace %W with our toplevel frame, then |
|---|
| 1239 | # do the binding |
|---|
| 1240 | set code [lindex $args 1] |
|---|
| 1241 | regsub -all {%W} $code $w code |
|---|
| 1242 | |
|---|
| 1243 | set result [bind $widgets(label$id) $sequence \ |
|---|
| 1244 | [list ::mclistbox::LabelEvent $w $id $code]] |
|---|
| 1245 | } |
|---|
| 1246 | } |
|---|
| 1247 | } |
|---|
| 1248 | |
|---|
| 1249 | column-add { |
|---|
| 1250 | eval ::mclistbox::Column-add {$w} $args |
|---|
| 1251 | AdjustColumns $w |
|---|
| 1252 | set result "" |
|---|
| 1253 | } |
|---|
| 1254 | |
|---|
| 1255 | column-delete { |
|---|
| 1256 | foreach id $args { |
|---|
| 1257 | set index [CheckColumnID $w $id] |
|---|
| 1258 | |
|---|
| 1259 | # remove the reference from our list of columns |
|---|
| 1260 | set misc(columns) [lreplace $misc(columns) $index $index] |
|---|
| 1261 | |
|---|
| 1262 | # whack the widget |
|---|
| 1263 | destroy $widgets(frame$id) |
|---|
| 1264 | |
|---|
| 1265 | # clear out references to the individual widgets |
|---|
| 1266 | unset widgets(frame$id) |
|---|
| 1267 | unset widgets(listbox$id) |
|---|
| 1268 | unset widgets(label$id) |
|---|
| 1269 | } |
|---|
| 1270 | InvalidateScrollbars $w |
|---|
| 1271 | set result "" |
|---|
| 1272 | } |
|---|
| 1273 | |
|---|
| 1274 | column-cget { |
|---|
| 1275 | if {[llength $args] != 2} { |
|---|
| 1276 | error "wrong # of args: should be \"$w column cget name option\"" |
|---|
| 1277 | } |
|---|
| 1278 | set id [::mclistbox::Canonize $w column [lindex $args 0]] |
|---|
| 1279 | set option [lindex $args 1] |
|---|
| 1280 | set data [::mclistbox::Column-configure $w $id $option] |
|---|
| 1281 | set result [lindex $data 4] |
|---|
| 1282 | } |
|---|
| 1283 | |
|---|
| 1284 | column-configure { |
|---|
| 1285 | set id [::mclistbox::Canonize $w column [lindex $args 0]] |
|---|
| 1286 | set args [lrange $args 1 end] |
|---|
| 1287 | set result [eval ::mclistbox::Column-configure {$w} {$id} $args] |
|---|
| 1288 | } |
|---|
| 1289 | |
|---|
| 1290 | column-names { |
|---|
| 1291 | if {[llength $args] != 0} { |
|---|
| 1292 | error "wrong # of args: should be \"$w column names\"" |
|---|
| 1293 | } |
|---|
| 1294 | set result $misc(columns) |
|---|
| 1295 | } |
|---|
| 1296 | |
|---|
| 1297 | column-nearest { |
|---|
| 1298 | if {[llength $args] != 1} { |
|---|
| 1299 | error "wrong # of args: should be \"$w column nearest x\"" |
|---|
| 1300 | } |
|---|
| 1301 | |
|---|
| 1302 | set x [lindex $args 0] |
|---|
| 1303 | set tmp [$widgets(text) index @$x,0] |
|---|
| 1304 | set tmp [split $tmp "."] |
|---|
| 1305 | set index [lindex $tmp 1] |
|---|
| 1306 | |
|---|
| 1307 | set result [lindex $misc(columns) $index] |
|---|
| 1308 | } |
|---|
| 1309 | |
|---|
| 1310 | cget { |
|---|
| 1311 | if {[llength $args] != 1} { |
|---|
| 1312 | error "wrong # args: should be $w cget option" |
|---|
| 1313 | } |
|---|
| 1314 | set opt [::mclistbox::Canonize $w option [lindex $args 0]] |
|---|
| 1315 | |
|---|
| 1316 | set result $options($opt) |
|---|
| 1317 | } |
|---|
| 1318 | |
|---|
| 1319 | |
|---|
| 1320 | configure { |
|---|
| 1321 | set result [eval ::mclistbox::Configure {$w} $args] |
|---|
| 1322 | |
|---|
| 1323 | } |
|---|
| 1324 | |
|---|
| 1325 | curselection { |
|---|
| 1326 | set id [lindex $misc(columns) 0] |
|---|
| 1327 | set result [$widgets(listbox$id) curselection] |
|---|
| 1328 | } |
|---|
| 1329 | |
|---|
| 1330 | delete { |
|---|
| 1331 | if {[llength $args] < 1 || [llength $args] > 2} { |
|---|
| 1332 | error "wrong \# of args: should be $w delete first ?last?" |
|---|
| 1333 | } |
|---|
| 1334 | |
|---|
| 1335 | # it's possible that the selection will change because |
|---|
| 1336 | # of something we do. So, grab the current selection before |
|---|
| 1337 | # we do anything. Just before returning we'll see if the |
|---|
| 1338 | # selection has changed. If so, we'll call our selectcommand |
|---|
| 1339 | if {$options(-selectcommand) != ""} { |
|---|
| 1340 | set col0 [lindex $misc(columns) 0] |
|---|
| 1341 | set priorSelection [$widgets(listbox$col0) curselection] |
|---|
| 1342 | } |
|---|
| 1343 | |
|---|
| 1344 | set index1 [::mclistbox::MassageIndex $w [lindex $args 0]] |
|---|
| 1345 | if {[llength $args] == 2} { |
|---|
| 1346 | set index2 [::mclistbox::MassageIndex $w [lindex $args 1]] |
|---|
| 1347 | } else { |
|---|
| 1348 | set index2 "" |
|---|
| 1349 | } |
|---|
| 1350 | |
|---|
| 1351 | # note we do an eval here to make index2 "disappear" if it |
|---|
| 1352 | # is set to an empty string. |
|---|
| 1353 | foreach id $misc(columns) { |
|---|
| 1354 | eval {$widgets(listbox$id)} delete $index1 $index2 |
|---|
| 1355 | } |
|---|
| 1356 | eval {$widgets(hiddenListbox)} delete $index1 $index2 |
|---|
| 1357 | |
|---|
| 1358 | InvalidateScrollbars $w |
|---|
| 1359 | |
|---|
| 1360 | set result "" |
|---|
| 1361 | } |
|---|
| 1362 | |
|---|
| 1363 | get { |
|---|
| 1364 | if {[llength $args] < 1 || [llength $args] > 2} { |
|---|
| 1365 | error "wrong \# of args: should be $w get first ?last?" |
|---|
| 1366 | } |
|---|
| 1367 | set index1 [::mclistbox::MassageIndex $w [lindex $args 0]] |
|---|
| 1368 | if {[llength $args] == 2} { |
|---|
| 1369 | set index2 [::mclistbox::MassageIndex $w [lindex $args 1]] |
|---|
| 1370 | } else { |
|---|
| 1371 | set index2 "" |
|---|
| 1372 | } |
|---|
| 1373 | |
|---|
| 1374 | set result [eval ::mclistbox::WidgetProc-get {$w} $index1 $index2] |
|---|
| 1375 | |
|---|
| 1376 | } |
|---|
| 1377 | |
|---|
| 1378 | index { |
|---|
| 1379 | |
|---|
| 1380 | if {[llength $args] != 1} { |
|---|
| 1381 | error "wrong \# of args: should be $w index index" |
|---|
| 1382 | } |
|---|
| 1383 | |
|---|
| 1384 | set index [::mclistbox::MassageIndex $w [lindex $args 0]] |
|---|
| 1385 | set id [lindex $misc(columns) 0] |
|---|
| 1386 | |
|---|
| 1387 | set result [$widgets(listbox$id) index $index] |
|---|
| 1388 | } |
|---|
| 1389 | |
|---|
| 1390 | insert { |
|---|
| 1391 | if {[llength $args] < 1} { |
|---|
| 1392 | error "wrong \# of args: should be $w insert ?element \ |
|---|
| 1393 | element...?" |
|---|
| 1394 | } |
|---|
| 1395 | |
|---|
| 1396 | # it's possible that the selection will change because |
|---|
| 1397 | # of something we do. So, grab the current selection before |
|---|
| 1398 | # we do anything. Just before returning we'll see if the |
|---|
| 1399 | # selection has changed. If so, we'll call our selectcommand |
|---|
| 1400 | if {$options(-selectcommand) != ""} { |
|---|
| 1401 | set col0 [lindex $misc(columns) 0] |
|---|
| 1402 | set priorSelection [$widgets(listbox$col0) curselection] |
|---|
| 1403 | } |
|---|
| 1404 | |
|---|
| 1405 | set index [::mclistbox::MassageIndex $w [lindex $args 0]] |
|---|
| 1406 | |
|---|
| 1407 | ::mclistbox::Insert $w $index [lrange $args 1 end] |
|---|
| 1408 | |
|---|
| 1409 | InvalidateScrollbars $w |
|---|
| 1410 | set result "" |
|---|
| 1411 | } |
|---|
| 1412 | |
|---|
| 1413 | nearest { |
|---|
| 1414 | if {[llength $args] != 1} { |
|---|
| 1415 | error "wrong \# of args: should be $w nearest y" |
|---|
| 1416 | } |
|---|
| 1417 | |
|---|
| 1418 | # translate the y coordinate into listbox space |
|---|
| 1419 | set id [lindex $misc(columns) 0] |
|---|
| 1420 | set y [lindex $args 0] |
|---|
| 1421 | incr y -[winfo y $widgets(listbox$id)] |
|---|
| 1422 | incr y -[winfo y $widgets(frame$id)] |
|---|
| 1423 | |
|---|
| 1424 | set col0 [lindex $misc(columns) 0] |
|---|
| 1425 | |
|---|
| 1426 | set result [$widgets(listbox$col0) nearest $y] |
|---|
| 1427 | } |
|---|
| 1428 | |
|---|
| 1429 | scan { |
|---|
| 1430 | foreach {subcommand x y} $args {} |
|---|
| 1431 | switch $subcommand { |
|---|
| 1432 | mark { |
|---|
| 1433 | # we have to treat scrolling in x and y differently; |
|---|
| 1434 | # scrolling in the y direction affects listboxes and |
|---|
| 1435 | # scrolling in the x direction affects the text widget. |
|---|
| 1436 | # to facilitate that, we need to keep a local copy |
|---|
| 1437 | # of the scan mark. |
|---|
| 1438 | set misc(scanmarkx) $x |
|---|
| 1439 | set misc(scanmarky) $y |
|---|
| 1440 | |
|---|
| 1441 | # set the scan mark for each column |
|---|
| 1442 | foreach id $misc(columns) { |
|---|
| 1443 | $widgets(listbox$id) scan mark $x $y |
|---|
| 1444 | } |
|---|
| 1445 | |
|---|
| 1446 | # we can't use the x coordinate given us, since it |
|---|
| 1447 | # is relative to whatever column we are over. So, |
|---|
| 1448 | # we'll just usr the results of [winfo pointerx]. |
|---|
| 1449 | $widgets(text) scan mark [winfo pointerx $w] $y |
|---|
| 1450 | } |
|---|
| 1451 | dragto { |
|---|
| 1452 | # we want the columns to only scan in the y direction, |
|---|
| 1453 | # so we'll force the x componant to remain constant |
|---|
| 1454 | foreach id $misc(columns) { |
|---|
| 1455 | $widgets(listbox$id) scan dragto $misc(scanmarkx) $y |
|---|
| 1456 | } |
|---|
| 1457 | |
|---|
| 1458 | # since the scan mark of the text widget was based |
|---|
| 1459 | # on the pointer location, so must be the x |
|---|
| 1460 | # coordinate to the dragto command. And since we |
|---|
| 1461 | # want the text widget to only scan in the x |
|---|
| 1462 | # direction, the y componant will remain constant |
|---|
| 1463 | $widgets(text) scan dragto \ |
|---|
| 1464 | [winfo pointerx $w] $misc(scanmarky) |
|---|
| 1465 | |
|---|
| 1466 | # make sure the scrollbars reflect the changes. |
|---|
| 1467 | InvalidateScrollbars $w |
|---|
| 1468 | } |
|---|
| 1469 | |
|---|
| 1470 | set result "" |
|---|
| 1471 | } |
|---|
| 1472 | } |
|---|
| 1473 | |
|---|
| 1474 | see { |
|---|
| 1475 | if {[llength $args] != 1} { |
|---|
| 1476 | error "wrong \# of args: should be $w see index" |
|---|
| 1477 | } |
|---|
| 1478 | set index [::mclistbox::MassageIndex $w [lindex $args 0]] |
|---|
| 1479 | |
|---|
| 1480 | foreach id $misc(columns) { |
|---|
| 1481 | $widgets(listbox$id) see $index |
|---|
| 1482 | } |
|---|
| 1483 | InvalidateScrollbars $w |
|---|
| 1484 | set result {} |
|---|
| 1485 | } |
|---|
| 1486 | |
|---|
| 1487 | selection { |
|---|
| 1488 | # it's possible that the selection will change because |
|---|
| 1489 | # of something we do. So, grab the current selection before |
|---|
| 1490 | # we do anything. Just before returning we'll see if the |
|---|
| 1491 | # selection has changed. If so, we'll call our selectcommand |
|---|
| 1492 | if {$options(-selectcommand) != ""} { |
|---|
| 1493 | set col0 [lindex $misc(columns) 0] |
|---|
| 1494 | set priorSelection [$widgets(listbox$col0) curselection] |
|---|
| 1495 | } |
|---|
| 1496 | |
|---|
| 1497 | set subcommand [lindex $args 0] |
|---|
| 1498 | set args [lrange $args 1 end] |
|---|
| 1499 | |
|---|
| 1500 | set prefix "wrong \# of args: should be $w" |
|---|
| 1501 | switch $subcommand { |
|---|
| 1502 | includes { |
|---|
| 1503 | if {[llength $args] != 1} { |
|---|
| 1504 | error "$prefix selection $subcommand index" |
|---|
| 1505 | } |
|---|
| 1506 | set index [::mclistbox::MassageIndex $w [lindex $args 0]] |
|---|
| 1507 | set id [lindex $misc(columns) 0] |
|---|
| 1508 | set result [$widgets(listbox$id) selection includes $index] |
|---|
| 1509 | } |
|---|
| 1510 | |
|---|
| 1511 | set { |
|---|
| 1512 | switch [llength $args] { |
|---|
| 1513 | 1 { |
|---|
| 1514 | set index1 [::mclistbox::MassageIndex $w \ |
|---|
| 1515 | [lindex $args 0]] |
|---|
| 1516 | set index2 "" |
|---|
| 1517 | } |
|---|
| 1518 | 2 { |
|---|
| 1519 | set index1 [::mclistbox::MassageIndex $w \ |
|---|
| 1520 | [lindex $args 0]] |
|---|
| 1521 | set index2 [::mclistbox::MassageIndex $w \ |
|---|
| 1522 | [lindex $args 1]] |
|---|
| 1523 | } |
|---|
| 1524 | default { |
|---|
| 1525 | error "$prefix selection clear first ?last?" |
|---|
| 1526 | } |
|---|
| 1527 | } |
|---|
| 1528 | |
|---|
| 1529 | if {$options(-exportselection)} { |
|---|
| 1530 | SelectionHandler $w own |
|---|
| 1531 | } |
|---|
| 1532 | if {$index1 != ""} { |
|---|
| 1533 | foreach id $misc(columns) { |
|---|
| 1534 | eval {$widgets(listbox$id)} selection set \ |
|---|
| 1535 | $index1 $index2 |
|---|
| 1536 | } |
|---|
| 1537 | } |
|---|
| 1538 | |
|---|
| 1539 | set result "" |
|---|
| 1540 | } |
|---|
| 1541 | |
|---|
| 1542 | anchor { |
|---|
| 1543 | if {[llength $args] != 1} { |
|---|
| 1544 | error "$prefix selection $subcommand index" |
|---|
| 1545 | } |
|---|
| 1546 | set index [::mclistbox::MassageIndex $w [lindex $args 0]] |
|---|
| 1547 | |
|---|
| 1548 | if {$options(-exportselection)} { |
|---|
| 1549 | SelectionHandler $w own |
|---|
| 1550 | } |
|---|
| 1551 | foreach id $misc(columns) { |
|---|
| 1552 | $widgets(listbox$id) selection anchor $index |
|---|
| 1553 | } |
|---|
| 1554 | set result "" |
|---|
| 1555 | } |
|---|
| 1556 | |
|---|
| 1557 | clear { |
|---|
| 1558 | switch [llength $args] { |
|---|
| 1559 | 1 { |
|---|
| 1560 | set index1 [::mclistbox::MassageIndex $w \ |
|---|
| 1561 | [lindex $args 0]] |
|---|
| 1562 | set index2 "" |
|---|
| 1563 | } |
|---|
| 1564 | 2 { |
|---|
| 1565 | set index1 [::mclistbox::MassageIndex $w \ |
|---|
| 1566 | [lindex $args 0]] |
|---|
| 1567 | set index2 [::mclistbox::MassageIndex $w \ |
|---|
| 1568 | [lindex $args 1]] |
|---|
| 1569 | } |
|---|
| 1570 | default { |
|---|
| 1571 | error "$prefix selection clear first ?last?" |
|---|
| 1572 | } |
|---|
| 1573 | } |
|---|
| 1574 | |
|---|
| 1575 | if {$options(-exportselection)} { |
|---|
| 1576 | SelectionHandler $w own |
|---|
| 1577 | } |
|---|
| 1578 | foreach id $misc(columns) { |
|---|
| 1579 | eval {$widgets(listbox$id)} selection clear \ |
|---|
| 1580 | $index1 $index2 |
|---|
| 1581 | } |
|---|
| 1582 | set result "" |
|---|
| 1583 | } |
|---|
| 1584 | } |
|---|
| 1585 | } |
|---|
| 1586 | |
|---|
| 1587 | size { |
|---|
| 1588 | set id [lindex $misc(columns) 0] |
|---|
| 1589 | set result [$widgets(listbox$id) size] |
|---|
| 1590 | } |
|---|
| 1591 | } |
|---|
| 1592 | |
|---|
| 1593 | # if the user has a selectcommand defined and the selection changed, |
|---|
| 1594 | # run the selectcommand |
|---|
| 1595 | if {[info exists priorSelection] && $options(-selectcommand) != ""} { |
|---|
| 1596 | set column [lindex $misc(columns) 0] |
|---|
| 1597 | set currentSelection [$widgets(listbox$column) curselection] |
|---|
| 1598 | if {[string compare $priorSelection $currentSelection] != 0} { |
|---|
| 1599 | # this logic keeps us from getting into some sort of |
|---|
| 1600 | # infinite loop of the selectcommand changes the selection |
|---|
| 1601 | # (not particularly well tested, but it seems like the |
|---|
| 1602 | # right thing to do...) |
|---|
| 1603 | if {![info exists misc(skipRecursiveCall)]} { |
|---|
| 1604 | set misc(skipRecursiveCall) 1 |
|---|
| 1605 | uplevel \#0 $options(-selectcommand) $currentSelection |
|---|
| 1606 | catch {unset misc(skipRecursiveCall)} |
|---|
| 1607 | } |
|---|
| 1608 | } |
|---|
| 1609 | } |
|---|
| 1610 | |
|---|
| 1611 | return $result |
|---|
| 1612 | } |
|---|
| 1613 | |
|---|
| 1614 | # ::mclistbox::WidgetProc-get -- |
|---|
| 1615 | # |
|---|
| 1616 | # Implements the "get" widget command |
|---|
| 1617 | # |
|---|
| 1618 | # Arguments: |
|---|
| 1619 | # |
|---|
| 1620 | # w widget path |
|---|
| 1621 | # args additional arguments to the get command |
|---|
| 1622 | |
|---|
| 1623 | proc ::mclistbox::WidgetProc-get {w args} { |
|---|
| 1624 | upvar ::mclistbox::${w}::widgets widgets |
|---|
| 1625 | upvar ::mclistbox::${w}::options options |
|---|
| 1626 | upvar ::mclistbox::${w}::misc misc |
|---|
| 1627 | |
|---|
| 1628 | set returnType "list" |
|---|
| 1629 | # the listbox "get" command returns different things |
|---|
| 1630 | # depending on whether it has one or two args. Internally |
|---|
| 1631 | # we *always* want a valid list, so we'll force a second |
|---|
| 1632 | # arg which in turn forces the listbox to return a list, |
|---|
| 1633 | # even if its a list of one element |
|---|
| 1634 | if {[llength $args] == 1} { |
|---|
| 1635 | lappend args [lindex $args 0] |
|---|
| 1636 | set returnType "listOfLists" |
|---|
| 1637 | } |
|---|
| 1638 | |
|---|
| 1639 | # get all the data from each column |
|---|
| 1640 | foreach id $misc(columns) { |
|---|
| 1641 | set data($id) [eval {$widgets(listbox$id)} get $args] |
|---|
| 1642 | } |
|---|
| 1643 | |
|---|
| 1644 | # now join the data together one row at a time. Ugh. |
|---|
| 1645 | set result {} |
|---|
| 1646 | set rows [llength $data($id)] |
|---|
| 1647 | for {set i 0} {$i < $rows} {incr i} { |
|---|
| 1648 | set this {} |
|---|
| 1649 | foreach column $misc(columns) { |
|---|
| 1650 | lappend this [lindex $data($column) $i] |
|---|
| 1651 | } |
|---|
| 1652 | lappend result $this |
|---|
| 1653 | } |
|---|
| 1654 | |
|---|
| 1655 | # now to unroll the list if necessary. If the user gave |
|---|
| 1656 | # us only one indicie we want to return a single list |
|---|
| 1657 | # of values. If they gave use two indicies we want to return |
|---|
| 1658 | # a list of lists. |
|---|
| 1659 | if {[string compare $returnType "list"] == 0} { |
|---|
| 1660 | return $result |
|---|
| 1661 | } else { |
|---|
| 1662 | return [lindex $result 0] |
|---|
| 1663 | } |
|---|
| 1664 | } |
|---|
| 1665 | |
|---|
| 1666 | # ::mclistbox::CheckColumnID -- |
|---|
| 1667 | # |
|---|
| 1668 | # returns the index of the id within our list of columns, or |
|---|
| 1669 | # reports an error if the id is invalid |
|---|
| 1670 | # |
|---|
| 1671 | # Arguments: |
|---|
| 1672 | # |
|---|
| 1673 | # w widget pathname |
|---|
| 1674 | # id a column id |
|---|
| 1675 | # |
|---|
| 1676 | # Results: |
|---|
| 1677 | # |
|---|
| 1678 | # Will compute and return the index of the column within the |
|---|
| 1679 | # list of columns (which happens to be it's -position, as it |
|---|
| 1680 | # turns out) or returns an error if the named column doesn't |
|---|
| 1681 | # exist. |
|---|
| 1682 | |
|---|
| 1683 | proc ::mclistbox::CheckColumnID {w id} { |
|---|
| 1684 | upvar ::mclistbox::${w}::misc misc |
|---|
| 1685 | |
|---|
| 1686 | set id [::mclistbox::Canonize $w column $id] |
|---|
| 1687 | set index [lsearch -exact $misc(columns) $id] |
|---|
| 1688 | return $index |
|---|
| 1689 | } |
|---|
| 1690 | |
|---|
| 1691 | # ::mclistbox::LabelEvent -- |
|---|
| 1692 | # |
|---|
| 1693 | # Handle user events on the column labels for the Mclistbox |
|---|
| 1694 | # class. |
|---|
| 1695 | # |
|---|
| 1696 | # Arguments: |
|---|
| 1697 | # |
|---|
| 1698 | # w widget pathname |
|---|
| 1699 | # id a column identifier |
|---|
| 1700 | # code tcl code to be evaluated. |
|---|
| 1701 | # |
|---|
| 1702 | # Results: |
|---|
| 1703 | # |
|---|
| 1704 | # Executes the code associate with an event, but only if the |
|---|
| 1705 | # event wouldn't otherwise potentially trigger a resize event. |
|---|
| 1706 | # |
|---|
| 1707 | # We use the cursor of the label to let us know whether the |
|---|
| 1708 | # code should be executed. If it is set to the cursor of the |
|---|
| 1709 | # mclistbox widget, the code will be executed. It is assumed |
|---|
| 1710 | # that if it is not the same cursor, it is the resize cursor |
|---|
| 1711 | # which should only be set if the cursor is very near a border |
|---|
| 1712 | # of a label and the column is resizable. |
|---|
| 1713 | |
|---|
| 1714 | proc ::mclistbox::LabelEvent {w id code} { |
|---|
| 1715 | upvar ::mclistbox::${w}::widgets widgets |
|---|
| 1716 | upvar ::mclistbox::${w}::options options |
|---|
| 1717 | |
|---|
| 1718 | # only fire the binding if the cursor is our default cursor |
|---|
| 1719 | # (ie: if we aren't in a "resize zone") |
|---|
| 1720 | set cursor [$widgets(label$id) cget -cursor] |
|---|
| 1721 | if {[string compare $cursor $options(-cursor)] == 0} { |
|---|
| 1722 | uplevel \#0 $code |
|---|
| 1723 | } |
|---|
| 1724 | } |
|---|
| 1725 | |
|---|
| 1726 | # ::mclistbox::HumanizeList -- |
|---|
| 1727 | # |
|---|
| 1728 | # Returns a human-readable form of a list by separating items |
|---|
| 1729 | # by columns, but separating the last two elements with "or" |
|---|
| 1730 | # (eg: foo, bar or baz) |
|---|
| 1731 | # |
|---|
| 1732 | # Arguments: |
|---|
| 1733 | # |
|---|
| 1734 | # list a valid tcl list |
|---|
| 1735 | # |
|---|
| 1736 | # Results: |
|---|
| 1737 | # |
|---|
| 1738 | # A string which as all of the elements joined with ", " or |
|---|
| 1739 | # the word " or " |
|---|
| 1740 | |
|---|
| 1741 | proc ::mclistbox::HumanizeList {list} { |
|---|
| 1742 | |
|---|
| 1743 | if {[llength $list] == 1} { |
|---|
| 1744 | return [lindex $list 0] |
|---|
| 1745 | } else { |
|---|
| 1746 | set list [lsort $list] |
|---|
| 1747 | set secondToLast [expr {[llength $list] -2}] |
|---|
| 1748 | set most [lrange $list 0 $secondToLast] |
|---|
| 1749 | set last [lindex $list end] |
|---|
| 1750 | |
|---|
| 1751 | return "[join $most {, }] or $last" |
|---|
| 1752 | } |
|---|
| 1753 | } |
|---|
| 1754 | |
|---|
| 1755 | # ::mclistbox::Canonize -- |
|---|
| 1756 | # |
|---|
| 1757 | # takes a (possibly abbreviated) option or command name and either |
|---|
| 1758 | # returns the canonical name or an error |
|---|
| 1759 | # |
|---|
| 1760 | # Arguments: |
|---|
| 1761 | # |
|---|
| 1762 | # w widget pathname |
|---|
| 1763 | # object type of object to canonize; must be one of "command", |
|---|
| 1764 | # "option", "column" or "column option". |
|---|
| 1765 | # opt the option (or command) to be canonized |
|---|
| 1766 | # |
|---|
| 1767 | # Returns: |
|---|
| 1768 | # |
|---|
| 1769 | # Returns either the canonical form of an option or command, |
|---|
| 1770 | # or raises an error if the option or command is unknown or |
|---|
| 1771 | # ambiguous. |
|---|
| 1772 | |
|---|
| 1773 | proc ::mclistbox::Canonize {w object opt} { |
|---|
| 1774 | variable widgetOptions |
|---|
| 1775 | variable columnOptions |
|---|
| 1776 | variable widgetCommands |
|---|
| 1777 | variable columnCommands |
|---|
| 1778 | variable labelCommands |
|---|
| 1779 | |
|---|
| 1780 | switch $object { |
|---|
| 1781 | command { |
|---|
| 1782 | if {[lsearch -exact $widgetCommands $opt] >= 0} { |
|---|
| 1783 | return $opt |
|---|
| 1784 | } |
|---|
| 1785 | |
|---|
| 1786 | # command names aren't stored in an array, and there |
|---|
| 1787 | # isn't a way to get all the matches in a list, so |
|---|
| 1788 | # we'll stuff the columns in a temporary array so |
|---|
| 1789 | # we can use [array names] |
|---|
| 1790 | set list $widgetCommands |
|---|
| 1791 | foreach element $list { |
|---|
| 1792 | set tmp($element) "" |
|---|
| 1793 | } |
|---|
| 1794 | set matches [array names tmp ${opt}*] |
|---|
| 1795 | } |
|---|
| 1796 | |
|---|
| 1797 | {label command} { |
|---|
| 1798 | if {[lsearch -exact $labelCommands $opt] >= 0} { |
|---|
| 1799 | return $opt |
|---|
| 1800 | } |
|---|
| 1801 | |
|---|
| 1802 | # command names aren't stored in an array, and there |
|---|
| 1803 | # isn't a way to get all the matches in a list, so |
|---|
| 1804 | # we'll stuff the columns in a temporary array so |
|---|
| 1805 | # we can use [array names] |
|---|
| 1806 | set list $labelCommands |
|---|
| 1807 | foreach element $list { |
|---|
| 1808 | set tmp($element) "" |
|---|
| 1809 | } |
|---|
| 1810 | set matches [array names tmp ${opt}*] |
|---|
| 1811 | } |
|---|
| 1812 | |
|---|
| 1813 | {column command} { |
|---|
| 1814 | if {[lsearch -exact $columnCommands $opt] >= 0} { |
|---|
| 1815 | return $opt |
|---|
| 1816 | } |
|---|
| 1817 | |
|---|
| 1818 | # command names aren't stored in an array, and there |
|---|
| 1819 | # isn't a way to get all the matches in a list, so |
|---|
| 1820 | # we'll stuff the columns in a temporary array so |
|---|
| 1821 | # we can use [array names] |
|---|
| 1822 | set list $columnCommands |
|---|
| 1823 | foreach element $list { |
|---|
| 1824 | set tmp($element) "" |
|---|
| 1825 | } |
|---|
| 1826 | set matches [array names tmp ${opt}*] |
|---|
| 1827 | } |
|---|
| 1828 | |
|---|
| 1829 | option { |
|---|
| 1830 | if {[info exists widgetOptions($opt)] \ |
|---|
| 1831 | && [llength $widgetOptions($opt)] == 3} { |
|---|
| 1832 | return $opt |
|---|
| 1833 | } |
|---|
| 1834 | set list [array names widgetOptions] |
|---|
| 1835 | set matches [array names widgetOptions ${opt}*] |
|---|
| 1836 | } |
|---|
| 1837 | |
|---|
| 1838 | {column option} { |
|---|
| 1839 | if {[info exists columnOptions($opt)]} { |
|---|
| 1840 | return $opt |
|---|
| 1841 | } |
|---|
| 1842 | set list [array names columnOptions] |
|---|
| 1843 | set matches [array names columnOptions ${opt}*] |
|---|
| 1844 | } |
|---|
| 1845 | |
|---|
| 1846 | column { |
|---|
| 1847 | upvar ::mclistbox::${w}::misc misc |
|---|
| 1848 | |
|---|
| 1849 | if {[lsearch -exact $misc(columns) $opt] != -1} { |
|---|
| 1850 | return $opt |
|---|
| 1851 | } |
|---|
| 1852 | |
|---|
| 1853 | # column names aren't stored in an array, and there |
|---|
| 1854 | # isn't a way to get all the matches in a list, so |
|---|
| 1855 | # we'll stuff the columns in a temporary array so |
|---|
| 1856 | # we can use [array names] |
|---|
| 1857 | set list $misc(columns) |
|---|
| 1858 | foreach element $misc(columns) { |
|---|
| 1859 | set tmp($element) "" |
|---|
| 1860 | } |
|---|
| 1861 | set matches [array names tmp ${opt}*] |
|---|
| 1862 | } |
|---|
| 1863 | } |
|---|
| 1864 | if {[llength $matches] == 0} { |
|---|
| 1865 | set choices [HumanizeList $list] |
|---|
| 1866 | error "unknown $object \"$opt\"; must be one of $choices" |
|---|
| 1867 | |
|---|
| 1868 | } elseif {[llength $matches] == 1} { |
|---|
| 1869 | # deal with option aliases |
|---|
| 1870 | set opt [lindex $matches 0] |
|---|
| 1871 | switch $object { |
|---|
| 1872 | option { |
|---|
| 1873 | if {[llength $widgetOptions($opt)] == 1} { |
|---|
| 1874 | set opt $widgetOptions($opt) |
|---|
| 1875 | } |
|---|
| 1876 | } |
|---|
| 1877 | |
|---|
| 1878 | {column option} { |
|---|
| 1879 | if {[llength $columnOptions($opt)] == 1} { |
|---|
| 1880 | set opt $columnOptions($opt) |
|---|
| 1881 | } |
|---|
| 1882 | } |
|---|
| 1883 | } |
|---|
| 1884 | |
|---|
| 1885 | return $opt |
|---|
| 1886 | |
|---|
| 1887 | } else { |
|---|
| 1888 | set choices [HumanizeList $list] |
|---|
| 1889 | error "ambiguous $object \"$opt\"; must be one of $choices" |
|---|
| 1890 | } |
|---|
| 1891 | } |
|---|
| 1892 | |
|---|
| 1893 | # ::mclistbox::Configure -- |
|---|
| 1894 | # |
|---|
| 1895 | # Implements the "configure" widget subcommand |
|---|
| 1896 | # |
|---|
| 1897 | # Arguments: |
|---|
| 1898 | # |
|---|
| 1899 | # w widget pathname |
|---|
| 1900 | # args zero or more option/value pairs (or a single option) |
|---|
| 1901 | # |
|---|
| 1902 | # Results: |
|---|
| 1903 | # |
|---|
| 1904 | # Performs typcial "configure" type requests on the widget |
|---|
| 1905 | |
|---|
| 1906 | proc ::mclistbox::Configure {w args} { |
|---|
| 1907 | variable widgetOptions |
|---|
| 1908 | |
|---|
| 1909 | upvar ::mclistbox::${w}::widgets widgets |
|---|
| 1910 | upvar ::mclistbox::${w}::options options |
|---|
| 1911 | upvar ::mclistbox::${w}::misc misc |
|---|
| 1912 | |
|---|
| 1913 | if {[llength $args] == 0} { |
|---|
| 1914 | # hmmm. User must be wanting all configuration information |
|---|
| 1915 | # note that if the value of an array element is of length |
|---|
| 1916 | # one it is an alias, which needs to be handled slightly |
|---|
| 1917 | # differently |
|---|
| 1918 | set results {} |
|---|
| 1919 | foreach opt [lsort [array names widgetOptions]] { |
|---|
| 1920 | if {[llength $widgetOptions($opt)] == 1} { |
|---|
| 1921 | set alias $widgetOptions($opt) |
|---|
| 1922 | set optName $widgetOptions($alias) |
|---|
| 1923 | lappend results [list $opt $optName] |
|---|
| 1924 | } else { |
|---|
| 1925 | set optName [lindex $widgetOptions($opt) 0] |
|---|
| 1926 | set optClass [lindex $widgetOptions($opt) 1] |
|---|
| 1927 | set default [option get $w $optName $optClass] |
|---|
| 1928 | lappend results [list $opt $optName $optClass \ |
|---|
| 1929 | $default $options($opt)] |
|---|
| 1930 | } |
|---|
| 1931 | } |
|---|
| 1932 | |
|---|
| 1933 | return $results |
|---|
| 1934 | } |
|---|
| 1935 | |
|---|
| 1936 | # one argument means we are looking for configuration |
|---|
| 1937 | # information on a single option |
|---|
| 1938 | if {[llength $args] == 1} { |
|---|
| 1939 | set opt [::mclistbox::Canonize $w option [lindex $args 0]] |
|---|
| 1940 | |
|---|
| 1941 | set optName [lindex $widgetOptions($opt) 0] |
|---|
| 1942 | set optClass [lindex $widgetOptions($opt) 1] |
|---|
| 1943 | set default [option get $w $optName $optClass] |
|---|
| 1944 | set results [list $opt $optName $optClass \ |
|---|
| 1945 | $default $options($opt)] |
|---|
| 1946 | return $results |
|---|
| 1947 | } |
|---|
| 1948 | |
|---|
| 1949 | # if we have an odd number of values, bail. |
|---|
| 1950 | if {[expr {[llength $args]%2}] == 1} { |
|---|
| 1951 | # hmmm. An odd number of elements in args |
|---|
| 1952 | error "value for \"[lindex $args end]\" missing" |
|---|
| 1953 | } |
|---|
| 1954 | |
|---|
| 1955 | # Great. An even number of options. Let's make sure they |
|---|
| 1956 | # are all valid before we do anything. Note that Canonize |
|---|
| 1957 | # will generate an error if it finds a bogus option; otherwise |
|---|
| 1958 | # it returns the canonical option name |
|---|
| 1959 | foreach {name value} $args { |
|---|
| 1960 | set name [::mclistbox::Canonize $w option $name] |
|---|
| 1961 | set opts($name) $value |
|---|
| 1962 | } |
|---|
| 1963 | |
|---|
| 1964 | # process all of the configuration options |
|---|
| 1965 | foreach option [array names opts] { |
|---|
| 1966 | |
|---|
| 1967 | set newValue $opts($option) |
|---|
| 1968 | if {[info exists options($option)]} { |
|---|
| 1969 | set oldValue $options($option) |
|---|
| 1970 | # set options($option) $newValue |
|---|
| 1971 | } |
|---|
| 1972 | |
|---|
| 1973 | switch -- $option { |
|---|
| 1974 | -exportselection { |
|---|
| 1975 | if {$newValue} { |
|---|
| 1976 | SelectionHandler $w own |
|---|
| 1977 | set options($option) 1 |
|---|
| 1978 | } else { |
|---|
| 1979 | set options($option) 0 |
|---|
| 1980 | } |
|---|
| 1981 | } |
|---|
| 1982 | |
|---|
| 1983 | -fillcolumn { |
|---|
| 1984 | # if the fill column changed, we need to adjust |
|---|
| 1985 | # the columns. |
|---|
| 1986 | AdjustColumns $w |
|---|
| 1987 | set options($option) $newValue |
|---|
| 1988 | } |
|---|
| 1989 | |
|---|
| 1990 | -takefocus { |
|---|
| 1991 | $widgets(frame) configure -takefocus $newValue |
|---|
| 1992 | set options($option) [$widgets(frame) cget $option] |
|---|
| 1993 | } |
|---|
| 1994 | |
|---|
| 1995 | -background { |
|---|
| 1996 | foreach id $misc(columns) { |
|---|
| 1997 | $widgets(listbox$id) configure -background $newValue |
|---|
| 1998 | $widgets(frame$id) configure -background $newValue |
|---|
| 1999 | } |
|---|
| 2000 | $widgets(frame) configure -background $newValue |
|---|
| 2001 | |
|---|
| 2002 | $widgets(text) configure -background $newValue |
|---|
| 2003 | set options($option) [$widgets(frame) cget $option] |
|---|
| 2004 | } |
|---|
| 2005 | |
|---|
| 2006 | # { the following all must be applied to each listbox } |
|---|
| 2007 | -foreground - |
|---|
| 2008 | -font - |
|---|
| 2009 | -selectborderwidth - |
|---|
| 2010 | -selectforeground - |
|---|
| 2011 | -selectbackground - |
|---|
| 2012 | -setgrid { |
|---|
| 2013 | foreach id $misc(columns) { |
|---|
| 2014 | $widgets(listbox$id) configure $option $newValue |
|---|
| 2015 | } |
|---|
| 2016 | $widgets(hiddenListbox) configure $option $newValue |
|---|
| 2017 | set options($option) [$widgets(hiddenListbox) cget $option] |
|---|
| 2018 | } |
|---|
| 2019 | |
|---|
| 2020 | # { the following all must be applied to each listbox and frame } |
|---|
| 2021 | -cursor { |
|---|
| 2022 | foreach id $misc(columns) { |
|---|
| 2023 | $widgets(listbox$id) configure $option $newValue |
|---|
| 2024 | $widgets(frame$id) configure -cursor $newValue |
|---|
| 2025 | } |
|---|
| 2026 | |
|---|
| 2027 | # -cursor also needs to be applied to the |
|---|
| 2028 | # frames of each column |
|---|
| 2029 | foreach id $misc(columns) { |
|---|
| 2030 | $widgets(frame$id) configure -cursor $newValue |
|---|
| 2031 | } |
|---|
| 2032 | |
|---|
| 2033 | $widgets(hiddenListbox) configure $option $newValue |
|---|
| 2034 | set options($option) [$widgets(hiddenListbox) cget $option] |
|---|
| 2035 | } |
|---|
| 2036 | |
|---|
| 2037 | # { this just requires to pack or unpack the labels } |
|---|
| 2038 | -labels { |
|---|
| 2039 | if {$newValue} { |
|---|
| 2040 | set newValue 1 |
|---|
| 2041 | foreach id $misc(columns) { |
|---|
| 2042 | pack $widgets(label$id) \ |
|---|
| 2043 | -side top -fill x -expand n \ |
|---|
| 2044 | -before $widgets(listbox$id) |
|---|
| 2045 | } |
|---|
| 2046 | pack $widgets(hiddenLabel) \ |
|---|
| 2047 | -side top -fill x -expand n \ |
|---|
| 2048 | -before $widgets(hiddenListbox) |
|---|
| 2049 | |
|---|
| 2050 | } else { |
|---|
| 2051 | set newValue |
|---|
| 2052 | foreach id $misc(columns) { |
|---|
| 2053 | pack forget $widgets(label$id) |
|---|
| 2054 | } |
|---|
| 2055 | pack forget $widgets(hiddenLabel) |
|---|
| 2056 | } |
|---|
| 2057 | set options($option) $newValue |
|---|
| 2058 | } |
|---|
| 2059 | |
|---|
| 2060 | -height { |
|---|
| 2061 | $widgets(hiddenListbox) configure -height $newValue |
|---|
| 2062 | InvalidateScrollbars $w |
|---|
| 2063 | set options($option) [$widgets(hiddenListbox) cget $option] |
|---|
| 2064 | } |
|---|
| 2065 | |
|---|
| 2066 | -width { |
|---|
| 2067 | if {$newValue == 0} { |
|---|
| 2068 | error "a -width of zero is not supported. " |
|---|
| 2069 | } |
|---|
| 2070 | |
|---|
| 2071 | $widgets(hiddenListbox) configure -width $newValue |
|---|
| 2072 | InvalidateScrollbars $w |
|---|
| 2073 | set options($option) [$widgets(hiddenListbox) cget $option] |
|---|
| 2074 | } |
|---|
| 2075 | |
|---|
| 2076 | # { the following all must be applied to each column frame } |
|---|
| 2077 | -columnborderwidth - |
|---|
| 2078 | -columnrelief { |
|---|
| 2079 | regsub {column} $option {} listboxoption |
|---|
| 2080 | foreach id $misc(columns) { |
|---|
| 2081 | $widgets(listbox$id) configure $listboxoption $newValue |
|---|
| 2082 | } |
|---|
| 2083 | $widgets(hiddenListbox) configure $listboxoption $newValue |
|---|
| 2084 | set options($option) [$widgets(hiddenListbox) cget \ |
|---|
| 2085 | $listboxoption] |
|---|
| 2086 | } |
|---|
| 2087 | |
|---|
| 2088 | -resizablecolumns { |
|---|
| 2089 | if {$newValue} { |
|---|
| 2090 | set options($option) 1 |
|---|
| 2091 | } else { |
|---|
| 2092 | set options($option) 0 |
|---|
| 2093 | } |
|---|
| 2094 | } |
|---|
| 2095 | |
|---|
| 2096 | # { the following all must be applied to each column header } |
|---|
| 2097 | -labelimage - |
|---|
| 2098 | -labelheight - |
|---|
| 2099 | -labelrelief - |
|---|
| 2100 | -labelfont - |
|---|
| 2101 | -labelanchor - |
|---|
| 2102 | -labelbackground - |
|---|
| 2103 | -labelforeground - |
|---|
| 2104 | -labelborderwidth { |
|---|
| 2105 | regsub {label} $option {} labeloption |
|---|
| 2106 | foreach id $misc(columns) { |
|---|
| 2107 | $widgets(label$id) configure $labeloption $newValue |
|---|
| 2108 | } |
|---|
| 2109 | $widgets(hiddenLabel) configure $labeloption $newValue |
|---|
| 2110 | set options($option) [$widgets(hiddenLabel) cget $labeloption] |
|---|
| 2111 | } |
|---|
| 2112 | |
|---|
| 2113 | # { the following apply only to the topmost frame} |
|---|
| 2114 | -borderwidth - |
|---|
| 2115 | -highlightthickness - |
|---|
| 2116 | -highlightcolor - |
|---|
| 2117 | -highlightbackground - |
|---|
| 2118 | -relief { |
|---|
| 2119 | $widgets(frame) configure $option $newValue |
|---|
| 2120 | set options($option) [$widgets(frame) cget $option] |
|---|
| 2121 | } |
|---|
| 2122 | |
|---|
| 2123 | -selectmode { |
|---|
| 2124 | set options($option) $newValue |
|---|
| 2125 | } |
|---|
| 2126 | |
|---|
| 2127 | -selectcommand { |
|---|
| 2128 | set options($option) $newValue |
|---|
| 2129 | } |
|---|
| 2130 | |
|---|
| 2131 | -xscrollcommand { |
|---|
| 2132 | InvalidateScrollbars $w |
|---|
| 2133 | set options($option) $newValue |
|---|
| 2134 | } |
|---|
| 2135 | |
|---|
| 2136 | -yscrollcommand { |
|---|
| 2137 | InvalidateScrollbars $w |
|---|
| 2138 | set options($option) $newValue |
|---|
| 2139 | } |
|---|
| 2140 | } |
|---|
| 2141 | } |
|---|
| 2142 | } |
|---|
| 2143 | |
|---|
| 2144 | # ::mclistbox::UpdateScrollbars -- |
|---|
| 2145 | # |
|---|
| 2146 | # This proc does the work of actually update the scrollbars to |
|---|
| 2147 | # reflect the current view |
|---|
| 2148 | # |
|---|
| 2149 | # Arguments: |
|---|
| 2150 | # |
|---|
| 2151 | # w widget pathname |
|---|
| 2152 | # |
|---|
| 2153 | # Results: |
|---|
| 2154 | # |
|---|
| 2155 | # Potentially changes the size or placement of the scrollbars |
|---|
| 2156 | # (if any) based on changes to the widget |
|---|
| 2157 | |
|---|
| 2158 | proc ::mclistbox::UpdateScrollbars {w} { |
|---|
| 2159 | upvar ::mclistbox::${w}::widgets widgets |
|---|
| 2160 | upvar ::mclistbox::${w}::options options |
|---|
| 2161 | upvar ::mclistbox::${w}::misc misc |
|---|
| 2162 | |
|---|
| 2163 | if {![winfo ismapped $w]} { |
|---|
| 2164 | catch {unset misc(afterid)} |
|---|
| 2165 | return |
|---|
| 2166 | } |
|---|
| 2167 | |
|---|
| 2168 | update idletasks |
|---|
| 2169 | if {[llength $misc(columns)] > 0} { |
|---|
| 2170 | if {[string length $options(-yscrollcommand)] != 0} { |
|---|
| 2171 | set col0 [lindex $misc(columns) 0] |
|---|
| 2172 | set yview [$widgets(listbox$col0) yview] |
|---|
| 2173 | eval $options(-yscrollcommand) $yview |
|---|
| 2174 | } |
|---|
| 2175 | |
|---|
| 2176 | if {[string length $options(-xscrollcommand)] != 0} { |
|---|
| 2177 | set col0 [lindex $misc(columns) 0] |
|---|
| 2178 | set xview [$widgets(text) xview] |
|---|
| 2179 | eval $options(-xscrollcommand) $xview |
|---|
| 2180 | } |
|---|
| 2181 | } |
|---|
| 2182 | catch {unset misc(afterid)} |
|---|
| 2183 | } |
|---|
| 2184 | |
|---|
| 2185 | # ::mclistbox::InvalidateScrollbars -- |
|---|
| 2186 | # |
|---|
| 2187 | # Schedules the scrollbars to be updated the next time |
|---|
| 2188 | # we are idle. |
|---|
| 2189 | # |
|---|
| 2190 | # Arguments: |
|---|
| 2191 | # |
|---|
| 2192 | # w widget pathname |
|---|
| 2193 | # |
|---|
| 2194 | # Results: |
|---|
| 2195 | # |
|---|
| 2196 | # sets up a proc to be run in the idle event handler |
|---|
| 2197 | |
|---|
| 2198 | proc ::mclistbox::InvalidateScrollbars {w} { |
|---|
| 2199 | |
|---|
| 2200 | upvar ::mclistbox::${w}::widgets widgets |
|---|
| 2201 | upvar ::mclistbox::${w}::options options |
|---|
| 2202 | upvar ::mclistbox::${w}::misc misc |
|---|
| 2203 | |
|---|
| 2204 | if {![info exists misc(afterid)]} { |
|---|
| 2205 | set misc(afterid) \ |
|---|
| 2206 | [after idle "catch {::mclistbox::UpdateScrollbars $w}"] |
|---|
| 2207 | } |
|---|
| 2208 | } |
|---|
| 2209 | |
|---|
| 2210 | # ::mclistbox::Insert -- |
|---|
| 2211 | # |
|---|
| 2212 | # This implements the "insert" widget command; it arranges for |
|---|
| 2213 | # one or more elements to be inserted into the listbox. |
|---|
| 2214 | # |
|---|
| 2215 | # Arguments: |
|---|
| 2216 | # |
|---|
| 2217 | # w widget pathname |
|---|
| 2218 | # index a valid listbox index to designate where the data is |
|---|
| 2219 | # to be inserted |
|---|
| 2220 | # arglist A list of values to be inserted. Each element of the |
|---|
| 2221 | # list will itself be treated as a list, one element for |
|---|
| 2222 | # each column. |
|---|
| 2223 | # |
|---|
| 2224 | # Results: |
|---|
| 2225 | # |
|---|
| 2226 | # Inserts the data into the list and updates the scrollbars |
|---|
| 2227 | |
|---|
| 2228 | proc ::mclistbox::Insert {w index arglist} { |
|---|
| 2229 | |
|---|
| 2230 | upvar ::mclistbox::${w}::widgets widgets |
|---|
| 2231 | upvar ::mclistbox::${w}::options options |
|---|
| 2232 | upvar ::mclistbox::${w}::misc misc |
|---|
| 2233 | |
|---|
| 2234 | foreach list $arglist { |
|---|
| 2235 | # make sure we have enough elements for each column |
|---|
| 2236 | for {set i [llength $list]} {$i < [llength $misc(columns)]} {incr i} { |
|---|
| 2237 | lappend list {} |
|---|
| 2238 | } |
|---|
| 2239 | |
|---|
| 2240 | set column 0 |
|---|
| 2241 | foreach id $misc(columns) { |
|---|
| 2242 | $widgets(listbox$id) insert $index [lindex $list $column] |
|---|
| 2243 | incr column |
|---|
| 2244 | } |
|---|
| 2245 | |
|---|
| 2246 | # we also want to add a bogus item to the hidden listbox. Why? |
|---|
| 2247 | # For standard listboxes, if you specify a height of zero the |
|---|
| 2248 | # listbox will resize to be just big enough to hold all the lines. |
|---|
| 2249 | # Since we use a hidden listbox to regulate the size of the widget |
|---|
| 2250 | # and we want this same behavior, this listbox needs the same number |
|---|
| 2251 | # of elements as the visible listboxes |
|---|
| 2252 | # |
|---|
| 2253 | # (NB: we might want to make this listbox contain the contents |
|---|
| 2254 | # of all columns as a properly formatted list; then the get |
|---|
| 2255 | # command can query this listbox instead of having to query |
|---|
| 2256 | # each individual listbox. The disadvantage is that it doubles |
|---|
| 2257 | # the memory required to hold all the data) |
|---|
| 2258 | $widgets(hiddenListbox) insert $index "x" |
|---|
| 2259 | } |
|---|
| 2260 | return "" |
|---|
| 2261 | } |
|---|
| 2262 | |
|---|
| 2263 | # ::mclistbox::ColumnIsHidden -- |
|---|
| 2264 | # |
|---|
| 2265 | # Returns a boolean representing whether a column is visible or |
|---|
| 2266 | # not |
|---|
| 2267 | # |
|---|
| 2268 | # Arguments: |
|---|
| 2269 | # |
|---|
| 2270 | # w widget pathname |
|---|
| 2271 | # id a column identifier |
|---|
| 2272 | # |
|---|
| 2273 | # Results: |
|---|
| 2274 | # |
|---|
| 2275 | # returns 1 if the column is visible (ie: not hidden), or 0 |
|---|
| 2276 | # if invisible. Note that the result doesn't consider whether |
|---|
| 2277 | # the column is actually viewable. Even if it has been scrolled |
|---|
| 2278 | # off screen, 1 will be returned as long as the column hasn't |
|---|
| 2279 | # been hidden by turning the visibility off. |
|---|
| 2280 | |
|---|
| 2281 | proc ::mclistbox::ColumnIsHidden {w id} { |
|---|
| 2282 | upvar ::mclistbox::${w}::widgets widgets |
|---|
| 2283 | upvar ::mclistbox::${w}::misc misc |
|---|
| 2284 | |
|---|
| 2285 | set retval 1 |
|---|
| 2286 | set col [lsearch -exact $misc(columns) $id] |
|---|
| 2287 | |
|---|
| 2288 | if {$col != ""} { |
|---|
| 2289 | set index "1.$col" |
|---|
| 2290 | catch { |
|---|
| 2291 | set window [$widgets(text) window cget $index -window] |
|---|
| 2292 | if {[string length $window] > 0 && [winfo exists $window]} { |
|---|
| 2293 | set retval 0 |
|---|
| 2294 | } |
|---|
| 2295 | } |
|---|
| 2296 | } |
|---|
| 2297 | return $retval |
|---|
| 2298 | } |
|---|
| 2299 | |
|---|
| 2300 | # ::mclistbox::AdjustColumns -- |
|---|
| 2301 | # |
|---|
| 2302 | # Adjusts the height and width of the individual columns. |
|---|
| 2303 | # |
|---|
| 2304 | # Arguments: |
|---|
| 2305 | # |
|---|
| 2306 | # w widget pathname |
|---|
| 2307 | # height height, in pixels, that the columns should be adjusted |
|---|
| 2308 | # to. If null, the height will be the height of the text |
|---|
| 2309 | # widget that underlies our columns. |
|---|
| 2310 | # |
|---|
| 2311 | # Results: |
|---|
| 2312 | # |
|---|
| 2313 | # All columns will be adjusted to fill the text widget in the y |
|---|
| 2314 | # direction. Also, if a -fillcolumn is defined, that column will |
|---|
| 2315 | # be grown, if necessary, to fill the widget in the x direction. |
|---|
| 2316 | |
|---|
| 2317 | proc ::mclistbox::AdjustColumns {w {height ""}} { |
|---|
| 2318 | upvar ::mclistbox::${w}::widgets widgets |
|---|
| 2319 | upvar ::mclistbox::${w}::options options |
|---|
| 2320 | upvar ::mclistbox::${w}::misc misc |
|---|
| 2321 | |
|---|
| 2322 | if {[string length $height] == 0} { |
|---|
| 2323 | set height [winfo height $widgets(text)] |
|---|
| 2324 | } |
|---|
| 2325 | |
|---|
| 2326 | # resize the height of each column so it matches the height |
|---|
| 2327 | # of the text widget, minus a few pixels. |
|---|
| 2328 | incr height -4 |
|---|
| 2329 | foreach id $misc(columns) { |
|---|
| 2330 | $widgets(frame$id) configure -height $height |
|---|
| 2331 | } |
|---|
| 2332 | |
|---|
| 2333 | # if we have a fillcolumn, change its width accordingly |
|---|
| 2334 | if {$options(-fillcolumn) != ""} { |
|---|
| 2335 | |
|---|
| 2336 | # make sure the column has been defined. If not, bail (?) |
|---|
| 2337 | if {![info exists widgets(frame$options(-fillcolumn))]} { |
|---|
| 2338 | return |
|---|
| 2339 | } |
|---|
| 2340 | set frame $widgets(frame$options(-fillcolumn)) |
|---|
| 2341 | set minwidth $misc(min-$frame) |
|---|
| 2342 | |
|---|
| 2343 | # compute current width of all columns |
|---|
| 2344 | set colwidth 0 |
|---|
| 2345 | set col 0 |
|---|
| 2346 | foreach id $misc(columns) { |
|---|
| 2347 | if {![ColumnIsHidden $w $id] && $id != $options(-fillcolumn)} { |
|---|
| 2348 | incr colwidth [winfo reqwidth $widgets(frame$id)] |
|---|
| 2349 | } |
|---|
| 2350 | } |
|---|
| 2351 | |
|---|
| 2352 | # this is just shorthand for later use... |
|---|
| 2353 | set id $options(-fillcolumn) |
|---|
| 2354 | |
|---|
| 2355 | # compute optimal width |
|---|
| 2356 | set optwidth [expr {[winfo width $widgets(text)] - \ |
|---|
| 2357 | (2 * [$widgets(text) cget -padx])}] |
|---|
| 2358 | |
|---|
| 2359 | # compute the width of our fill column |
|---|
| 2360 | set newwidth [expr {$optwidth - $colwidth}] |
|---|
| 2361 | |
|---|
| 2362 | if {$newwidth < $minwidth} { |
|---|
| 2363 | set newwidth $minwidth |
|---|
| 2364 | } |
|---|
| 2365 | |
|---|
| 2366 | # adjust the width of our fill column frame |
|---|
| 2367 | $widgets(frame$id) configure -width $newwidth |
|---|
| 2368 | |
|---|
| 2369 | } |
|---|
| 2370 | InvalidateScrollbars $w |
|---|
| 2371 | } |
|---|
| 2372 | |
|---|
| 2373 | # ::mclistbox::FindResizableNeighbor -- |
|---|
| 2374 | # |
|---|
| 2375 | # Returns the nearest resizable column to the left or right |
|---|
| 2376 | # of the named column. |
|---|
| 2377 | # |
|---|
| 2378 | # Arguments: |
|---|
| 2379 | # |
|---|
| 2380 | # w widget pathname |
|---|
| 2381 | # id column identifier |
|---|
| 2382 | # direction should be one of "right" or "left". Actually, anything |
|---|
| 2383 | # that doesn't match "right" will be treated as "left" |
|---|
| 2384 | # |
|---|
| 2385 | # Results: |
|---|
| 2386 | # |
|---|
| 2387 | # Will return the column identifier of the nearest resizable |
|---|
| 2388 | # column, or an empty string if none exists. |
|---|
| 2389 | |
|---|
| 2390 | proc ::mclistbox::FindResizableNeighbor {w id {direction right}} { |
|---|
| 2391 | upvar ::mclistbox::${w}::widgets widgets |
|---|
| 2392 | upvar ::mclistbox::${w}::options options |
|---|
| 2393 | upvar ::mclistbox::${w}::misc misc |
|---|
| 2394 | |
|---|
| 2395 | |
|---|
| 2396 | if {$direction == "right"} { |
|---|
| 2397 | set incr 1 |
|---|
| 2398 | set stop [llength $misc(columns)] |
|---|
| 2399 | set start [expr {[lsearch -exact $misc(columns) $id] + 1}] |
|---|
| 2400 | } else { |
|---|
| 2401 | set incr -1 |
|---|
| 2402 | set stop -1 |
|---|
| 2403 | set start [expr {[lsearch -exact $misc(columns) $id] - 1}] |
|---|
| 2404 | } |
|---|
| 2405 | |
|---|
| 2406 | for {set i $start} {$i != $stop} {incr i $incr} { |
|---|
| 2407 | set col [lindex $misc(columns) $i] |
|---|
| 2408 | if {![ColumnIsHidden $w $col] && $options($col:-resizable)} { |
|---|
| 2409 | return $col |
|---|
| 2410 | } |
|---|
| 2411 | } |
|---|
| 2412 | return "" |
|---|
| 2413 | } |
|---|
| 2414 | |
|---|
| 2415 | # ::mclistbox::ResizeEvent -- |
|---|
| 2416 | # |
|---|
| 2417 | # Handles the events which implement interactive column resizing |
|---|
| 2418 | # using the mouse. |
|---|
| 2419 | # |
|---|
| 2420 | # Arguments: |
|---|
| 2421 | # |
|---|
| 2422 | # w widget pathname |
|---|
| 2423 | # type type of event; must be one of "buttonpress", "drag", |
|---|
| 2424 | # "motion", or "buttonrelease" |
|---|
| 2425 | # widget the actual widget that the event occured over |
|---|
| 2426 | # x the x coordinate of the mouse, relative to $widget |
|---|
| 2427 | # X the root x coordinate of the mouse |
|---|
| 2428 | # Y the root y coordinate of the mouse |
|---|
| 2429 | # |
|---|
| 2430 | # The basic idea is this: |
|---|
| 2431 | # |
|---|
| 2432 | # whenever the cursor moves over the label, we examine it's x |
|---|
| 2433 | # coordinate to determine if its within a fixed amount of |
|---|
| 2434 | # pixels from the left or right edge. If it is, we reconfigure |
|---|
| 2435 | # the cursor to be a suitable "this thing is resizable" cursor. |
|---|
| 2436 | # |
|---|
| 2437 | # On a buttonclick, if the cursor is not the default cursor |
|---|
| 2438 | # (and thus, presumably the resize cursor), we set up some |
|---|
| 2439 | # state for an eventual resize. We figure out which columns |
|---|
| 2440 | # are to the left and right and base a maximum resize amount |
|---|
| 2441 | # for each direction. We also define the absolute X coordinate |
|---|
| 2442 | # of the buttonpress as a reference point for the drag. |
|---|
| 2443 | # |
|---|
| 2444 | # on a b1-motion, if the drag state exists, we look at the |
|---|
| 2445 | # absolute X value and use it to compute a delta value from |
|---|
| 2446 | # the reference (the X of the button press). We then resize the |
|---|
| 2447 | # left and right column frames by the delta amount. |
|---|
| 2448 | # |
|---|
| 2449 | # on a button release, we unset the state and the cursor, which |
|---|
| 2450 | # cancels all of the above. |
|---|
| 2451 | |
|---|
| 2452 | proc ::mclistbox::ResizeEvent {w type widget x X Y} { |
|---|
| 2453 | |
|---|
| 2454 | upvar ::mclistbox::${w}::widgets widgets |
|---|
| 2455 | upvar ::mclistbox::${w}::options options |
|---|
| 2456 | upvar ::mclistbox::${w}::misc misc |
|---|
| 2457 | upvar ::mclistbox::${w}::columnID columnID |
|---|
| 2458 | |
|---|
| 2459 | # if the widget doesn't allow resizable cursors, there's |
|---|
| 2460 | # nothing here to do... |
|---|
| 2461 | if {!$options(-resizablecolumns)} { |
|---|
| 2462 | return |
|---|
| 2463 | } |
|---|
| 2464 | |
|---|
| 2465 | # this lets us keep track of some internal state while |
|---|
| 2466 | # the user is dragging the mouse |
|---|
| 2467 | variable drag |
|---|
| 2468 | |
|---|
| 2469 | # this lets us define a small window around the edges of |
|---|
| 2470 | # the column. |
|---|
| 2471 | set threshold [expr {$options(-labelborderwidth) + 4}] |
|---|
| 2472 | |
|---|
| 2473 | # this is what we use for the "this is resizable" cursor |
|---|
| 2474 | set resizeCursor sb_h_double_arrow |
|---|
| 2475 | |
|---|
| 2476 | # if we aren't over an area that we care about, bail. |
|---|
| 2477 | if {![info exists columnID($widget)]} { |
|---|
| 2478 | return |
|---|
| 2479 | } |
|---|
| 2480 | |
|---|
| 2481 | # id refers to the column name |
|---|
| 2482 | set id $columnID($widget) |
|---|
| 2483 | |
|---|
| 2484 | switch $type { |
|---|
| 2485 | |
|---|
| 2486 | buttonpress { |
|---|
| 2487 | # we will do all the work of initiating a drag only if |
|---|
| 2488 | # the cursor is not the defined cursor. In theory this |
|---|
| 2489 | # will only be the case if the mouse moves over the area |
|---|
| 2490 | # in which a drag can happen. |
|---|
| 2491 | if {[$widgets(label$id) cget -cursor] == $resizeCursor} { |
|---|
| 2492 | if {$x <= $threshold} { |
|---|
| 2493 | set lid [::mclistbox::FindResizableNeighbor $w $id left] |
|---|
| 2494 | if {$lid == ""} return |
|---|
| 2495 | set drag(leftFrame) $widgets(frame$lid) |
|---|
| 2496 | set drag(rightFrame) $widgets(frame$id) |
|---|
| 2497 | |
|---|
| 2498 | set drag(leftListbox) $widgets(listbox$lid) |
|---|
| 2499 | set drag(rightListbox) $widgets(listbox$id) |
|---|
| 2500 | |
|---|
| 2501 | } else { |
|---|
| 2502 | set rid [::mclistbox::FindResizableNeighbor $w $id right] |
|---|
| 2503 | if {$rid == ""} return |
|---|
| 2504 | set drag(leftFrame) $widgets(frame$id) |
|---|
| 2505 | set drag(rightFrame) $widgets(frame$rid) |
|---|
| 2506 | |
|---|
| 2507 | set drag(leftListbox) $widgets(listbox$id) |
|---|
| 2508 | set drag(rightListbox) $widgets(listbox$rid) |
|---|
| 2509 | |
|---|
| 2510 | } |
|---|
| 2511 | |
|---|
| 2512 | |
|---|
| 2513 | set drag(leftWidth) [winfo width $drag(leftFrame)] |
|---|
| 2514 | set drag(rightWidth) [winfo width $drag(rightFrame)] |
|---|
| 2515 | |
|---|
| 2516 | # it seems to be a fact that windows can never be |
|---|
| 2517 | # less than one pixel wide. So subtract that one pixel |
|---|
| 2518 | # from our max deltas... |
|---|
| 2519 | set drag(maxDelta) [expr {$drag(rightWidth) - 1}] |
|---|
| 2520 | set drag(minDelta) -[expr {$drag(leftWidth) - 1}] |
|---|
| 2521 | |
|---|
| 2522 | set drag(x) $X |
|---|
| 2523 | } |
|---|
| 2524 | } |
|---|
| 2525 | |
|---|
| 2526 | motion { |
|---|
| 2527 | if {[info exists drag(x)]} {return} |
|---|
| 2528 | |
|---|
| 2529 | # this is just waaaaay too much work for a motion |
|---|
| 2530 | # event, IMO. |
|---|
| 2531 | |
|---|
| 2532 | set resizable 0 |
|---|
| 2533 | |
|---|
| 2534 | # is the column the user is over resizable? |
|---|
| 2535 | if {!$options($id:-resizable)} {return} |
|---|
| 2536 | |
|---|
| 2537 | # did the user click on the left of a column? |
|---|
| 2538 | if {$x < $threshold} { |
|---|
| 2539 | set leftColumn [::mclistbox::FindResizableNeighbor $w $id left] |
|---|
| 2540 | if {$leftColumn != ""} { |
|---|
| 2541 | set resizable 1 |
|---|
| 2542 | } |
|---|
| 2543 | |
|---|
| 2544 | } elseif {$x > [winfo width $widget] - $threshold} { |
|---|
| 2545 | set rightColumn [::mclistbox::FindResizableNeighbor $w $id \ |
|---|
| 2546 | right] |
|---|
| 2547 | if {$rightColumn != ""} { |
|---|
| 2548 | set resizable 1 |
|---|
| 2549 | } |
|---|
| 2550 | } |
|---|
| 2551 | |
|---|
| 2552 | # if it's resizable, change the cursor |
|---|
| 2553 | set cursor [$widgets(label$id) cget -cursor] |
|---|
| 2554 | if {$resizable && $cursor != $resizeCursor} { |
|---|
| 2555 | $widgets(label$id) configure -cursor $resizeCursor |
|---|
| 2556 | |
|---|
| 2557 | } elseif {!$resizable && $cursor == $resizeCursor} { |
|---|
| 2558 | $widgets(label$id) configure -cursor $options(-cursor) |
|---|
| 2559 | } |
|---|
| 2560 | } |
|---|
| 2561 | |
|---|
| 2562 | drag { |
|---|
| 2563 | # if the state is set up, do the drag... |
|---|
| 2564 | if {[info exists drag(x)]} { |
|---|
| 2565 | |
|---|
| 2566 | set delta [expr {$X - $drag(x)}] |
|---|
| 2567 | if {$delta >= $drag(maxDelta)} { |
|---|
| 2568 | set delta $drag(maxDelta) |
|---|
| 2569 | |
|---|
| 2570 | } elseif {$delta <= $drag(minDelta)} { |
|---|
| 2571 | set delta $drag(minDelta) |
|---|
| 2572 | } |
|---|
| 2573 | |
|---|
| 2574 | set lwidth [expr {$drag(leftWidth) + $delta}] |
|---|
| 2575 | set rwidth [expr {$drag(rightWidth) - $delta}] |
|---|
| 2576 | |
|---|
| 2577 | $drag(leftFrame) configure -width $lwidth |
|---|
| 2578 | $drag(rightFrame) configure -width $rwidth |
|---|
| 2579 | |
|---|
| 2580 | } |
|---|
| 2581 | } |
|---|
| 2582 | |
|---|
| 2583 | buttonrelease { |
|---|
| 2584 | set fillColumnID $options(-fillcolumn) |
|---|
| 2585 | if {[info exists drag(x)] && $fillColumnID != {}} { |
|---|
| 2586 | set fillColumnFrame $widgets(frame$fillColumnID) |
|---|
| 2587 | if {[string compare $drag(leftFrame) $fillColumnFrame] == 0 \ |
|---|
| 2588 | || [string compare $drag(rightFrame) $fillColumnFrame] == 0} { |
|---|
| 2589 | set width [$fillColumnFrame cget -width] |
|---|
| 2590 | set misc(minFillColumnSize) $width |
|---|
| 2591 | } |
|---|
| 2592 | set misc(min-$drag(leftFrame)) [$drag(leftFrame) cget -width] |
|---|
| 2593 | set misc(min-$drag(rightFrame)) [$drag(rightFrame) cget -width] |
|---|
| 2594 | } |
|---|
| 2595 | |
|---|
| 2596 | # reset the state and the cursor |
|---|
| 2597 | catch {unset drag} |
|---|
| 2598 | $widgets(label$id) configure -cursor $options(-cursor) |
|---|
| 2599 | |
|---|
| 2600 | } |
|---|
| 2601 | } |
|---|
| 2602 | } |
|---|
| 2603 | |
|---|
| 2604 | # end of mclistbox.tcl |
|---|