source: tol/tolbase/lib/mkWidgets1.3/Gridcontrol.tcl @ 32535cc

1941-docker-image-to-testdevel
Last change on this file since 32535cc was 32535cc, checked in by Jorge Pérez <josp.jorge@…>, 18 years ago

adding more types to config, added mkWidget1.3

git-svn-id: ​https://www.tol-project.org/svn/tolp/trunk@16 be4846ae-f051-483a-831f-b64e7d08527f

  • Property mode set to 100644
File size: 34.0 KB
Line 
1package require Metawidget
2
3# name: Gridcontrol
4# args: args: option-value pairs
5# tries to find a font for column and row separation, creates all widgets,
6# preconfigures tags, sets all bindings for the selection and inits
7# private variables.
8metawidget create Gridcontrol {
9  if { ! [ourinfo exists bInit] } {
10    # bindings for selection in main text widget
11    foreach { sBind sStep iDir sAction sMode } {
12      <Button-1>            m  0 n s   <Control-1>           m  0 t s
13      <Shift-1>             m  0 n r   <Shift-Control-1>     m  0 a r
14      <Button1-Motion>      m  0 n r
15
16      <Left>                c -1 n s   <Right>               c  1 n s
17      <Shift-Left>          c -1 n r   <Shift-Right>         c  1 n r
18      <Control-Left>        c -1 c c   <Control-Right>       c  1 c c
19      <Shift-Control-Left>  c -1 a r   <Shift-Control-Right> c  1 a r
20
21      <Up>                  r -1 n s   <Down>                r  1 n s
22      <Shift-Up>            r -1 n r   <Shift-Down>          r  1 n r
23      <Control-Up>          r -1 c c   <Control-Down>        r  1 c c
24      <Shift-Control-Up>    r -1 a r   <Shift-Control-Down>  r  1 a r
25
26      <Prior>               p -1 n s   <Next>                p  1 n s
27      <Shift-Prior>         p -1 n r   <Shift-Next>          p  1 n r
28      <Control-Prior>       p -1 c c   <Control-Next>        p  1 c c
29      <Shift-Control-Prior> p -1 a r   <Shift-Control-Next>  p  1 a r
30
31      <Control-space>       c  0 t s
32    } {
33      bind Gridcontroltext $sBind "\[winfo parent %W\] _select $sStep $iDir $sAction $sMode"
34    }
35
36    # jump to start or end, should be improved one day
37    bind Gridcontroltext <Home>        "%W see 1.0"
38    bind Gridcontroltext <End>         "%W see end"
39
40    # take over some useful Text class bindings
41    foreach sBind {<MouseWheel> <B2-Motion> <Button-2> <<Copy>>} {
42      bind Gridcontroltext $sBind [bind Text $sBind]
43    }
44
45    # find fonts for column and row separation
46    _findSepFonts
47
48    our bInit 1
49  }
50
51  # internal widgets, gridded later
52  frame     $this.edge -bg gray -border 1 -relief raised
53  scrollbar $this.scrh -command "$this _dragX" -orient horiz
54  scrollbar $this.scrv -command "$this _dragY"
55
56  text $this.cols -wrap none -cursor arrow -height 1 -spacing1 1 -spacing3 1 \
57                   -border 0 -bg gray -padx 1 -pady 0
58
59  text $this.rows -wrap none -cursor arrow -width 1 -spacing1 1 -spacing3 1 \
60                   -border 0 -bg gray -padx 0 -pady 1
61
62  text $this.text -wrap none -cursor arrow -spacing1 1 -spacing3 1 \
63                   -border 0 -bg white -padx 1 -pady 1 -insertofftime 0 -insertwidth 0 \
64                   -xscroll "$this _moveX" -yscroll "$this _moveY"
65
66  grid $this.edge -column 0 -row 0 -sticky news
67  grid $this.cols -column 1 -row 0 -sticky news
68  grid $this.rows -column 0 -row 1 -sticky news
69  grid $this.text -column 1 -row 1 -sticky news
70  grid $this.scrv -column 2 -row 0 -sticky ns -rowspan    3
71  grid $this.scrh -column 0 -row 2 -sticky we -columnspan 2
72  grid columnconf $this 1 -weight 1
73  grid rowconf    $this 1 -weight 1
74
75  # tags for column header and column separator
76  $this.cols tag add    end 1.end
77  $this.cols tag config tCols -border 1 -relief raised
78  $this.cols tag config tCsep -back gray -font [our sColFont]
79
80  # tags for row header and row separator
81  $this.rows tag config tRows -border 1 -relief raised -lmargin1 2
82  $this.rows tag config tRsep -back gray -font [our sRowFont] -spacing1 0 -spacing3 0
83
84  # tags for main text widget: column and row separator and cursor (black frame)
85  $this.text tag config tCsep -back gray -font [our sColFont]
86  $this.text tag config tRsep -back gray -font [our sRowFont] -spacing1 0 -spacing3 0
87  $this.text tag config cursor -border 1 -relief solid
88
89  # take Text class bindings out, no insertion cursor etc.
90  bindtags $this.cols [mkw.lshrink [bindtags $this.cols] Text]
91  bindtags $this.rows [mkw.lshrink [bindtags $this.rows] Text]
92  bindtags $this.text [mkw.lchange [bindtags $this.text] Text Gridcontroltext]
93
94  # bindings for selection in columns and rows
95  foreach { sBind sAction sMode } { <Button-1> n s   <Control-1>       t s
96                                    <Shift-1>  n r   <Control-Shift-1> a r } {
97    $this.cols tag bind tCsel $sBind "$this _select2 c $sAction $sMode"
98    $this.rows tag bind tRows $sBind "$this _select2 r $sAction $sMode"
99  }
100
101  # bindings to resize a column
102  $this.cols tag bind tDrag <Button-1>        "$this _grab"
103  $this.cols tag bind tDrag <B1-Motion>       "$this _drag"
104  $this.cols tag bind tDrag <ButtonRelease-1> "$this _drop"
105
106  # binding for top left corner, selects all
107  bind $this.edge <1> "$this _select2 a a r"
108
109  my iRows 0                          ;# number of rows
110  my lCols {}                         ;# list of column names
111  my lData {{}}                       ;# table of data
112
113  my -update full                     ;# redraw method when column resized
114  my -selectmode multiple             ;# one or more cells selectable?
115  my -onselect {}                     ;# command to eval on selection
116
117  -font [$this.text cget -font]
118} {} -default text
119
120# name: _findSepFonts
121# args: -
122# columns are separated by a 1 pixel line, which is in fact a space character
123# with a special font. same goes for the rows. the loops search for two fonts
124# on that system, one where the space char is 1 pixel wide, one where the
125# spacing is 1 pixel high. font names are stored in class-common variables.
126metawidget proc Gridcontrol _findSepFonts {} {
127  for { set iBest 1 } { $iBest <= 4 } { incr iBest } {
128    for { set iSize 1 } { $iSize <= 4 } { incr iSize } {
129      foreach sFamily [font families] {
130        set sDescr [list $sFamily $iSize]
131
132        if { [font measure $sDescr " "] == $iBest } {
133          our sColFont $sDescr
134        }
135        if { [font metrics $sDescr -linespace] == $iBest } {
136          our sRowFont $sDescr
137        }
138
139        if { [ourinfo exists sColFont] && [ourinfo exists sRowFont] } return
140      }
141    }
142  }
143}
144
145# name: _dragX
146# args: args: from "scrollbar -command"
147# called on horizontal scrollbar dragging. sets the view for column and
148# main text widget simultaneously.
149metawidget proc Gridcontrol _dragX { args } {
150  regsub {1 units$} $args {2 units} args
151  eval $this.cols xview $args
152  eval $this.text xview $args
153}
154
155# name: _dragY
156# args: args: from "scrollbar -command"
157# called on vertical scrollbar dragging. sets the view for row and
158# main text widget simultaneously. always scrolls two rows, so that
159# a separator row is never on top.
160metawidget proc Gridcontrol _dragY { args } {
161  regsub {1 units$} $args {2 units} args
162  eval $this.rows yview $args
163  eval $this.text yview $args
164
165  if { [$this.text tag names @0,0] == "tRsep" } {
166    $this.rows yview scroll 1 units
167    $this.text yview scroll 1 units
168  }
169}
170
171# name: _moveX
172# args: args: from "text -scrollx"
173# called when main text widgets scrolls horizontally. adjusts the column
174# text widget and the scrollbar.
175metawidget proc Gridcontrol _moveX { args } {
176  eval $this.scrh set $args
177  eval $this.cols xview moveto [lindex $args 0]
178}
179
180# name: _moveY
181# args: args: from "text -scrolly"
182# called when main text widgets scrolls vertically. adjusts the row
183# text widget and the scrollbar.
184metawidget proc Gridcontrol _moveY { args } {
185  eval $this.scrv set $args
186  eval $this.rows yview moveto [lindex $args 0]
187}
188
189# name: _setTabs
190# args: bAll: if false, only tabs in column widget are set
191# called when a column is resized. calculates the tabs for column and main
192# widget. each column is separated by a tab. the text position inside a column
193# is also based on a tab.
194metawidget proc Gridcontrol _setTabs { {bAll 1} } {
195  set lTabs {}
196  set iX 0
197
198  foreach sColumn [my lCols] {
199    mkw.lassign [my aCols($sColumn)] iWidth sAlign
200
201    # create a tab based on column alignment. For right align, the tab must
202    # be 1 space left of the column separating tab (it's the text widget!)
203    switch $sAlign {
204      l { lappend lTabs [expr $iX]         }
205      r { lappend lTabs [expr $iX+$iWidth-[my iOneSpace]] }
206      c { lappend lTabs [expr $iX+$iWidth/2] }
207      n { lappend lTabs [expr $iX+$iWidth/2] }
208    }
209
210    # create tab for column separation
211    lappend lTabs $sAlign [expr $iX+$iWidth] c
212    incr iX $iWidth
213  }
214
215  $this.cols config -tabs $lTabs
216
217  # set for main window, if indicated (depends on -update)
218  if { $bAll } {
219    $this.text config -tabs $lTabs
220  }
221}
222
223# name: _getValues
224# args: sColumn: valid column name
225# called before dragging a column. extracts the values of the given column
226# out of the lData table and returns them as a list. speeds up text clipping!
227metawidget proc Gridcontrol _getValues { sColumn } {
228  set iPos [_findColumn $sColumn]
229  set lValues {}
230  foreach lRow [lrange [my lData] 1 end] {
231    lappend lValues [lindex $lRow $iPos]
232  }
233
234  return $lValues
235}
236
237# name: _clipText
238# args: sText: text to clip
239#       sColumn: column to make sText fit into
240# returns a portion of sText that is not wider then the width of sColumn.
241metawidget proc Gridcontrol _clipText { sText sColumn } {
242  # get column width and substract the width of two tab chars.
243  # strange, but the text widget allocates pixels for a tab.
244  set iColWidth [expr [lindex [my aCols($sColumn)] 0]-[my iTwoTabs]-1]
245
246  # for each cycle, cut off one character at the end and see
247  # if required space is less then available one. if so, get out.
248  for { set i [expr [string length $sText]-2] } { $i >= -1 } { incr i -1 } {
249    if { [font measure [my -font] $sText] <= $iColWidth } break;
250    set sText [string range $sText 0 $i]
251  }
252
253  return $sText
254}
255
256# name: _clipHeader
257# args: sColumn: valid column name
258# called when a column is dragged. clips the column header by replacing it.
259# therefore the tCsel tag needs to be added again. tCsel is the left portion
260# of a column, its bindings cause the column to be selected. (the right side
261# is tDrag, to drag a column).
262metawidget proc Gridcontrol _clipHeader { sColumn } {
263  mkw.lassign [$this.cols tag ranges $sColumn] sI1 sI2
264  $this.cols delete $sI1+1ch $sI2-2ch
265  $this.cols insert $sI1+1ch [_clipText [lindex [my aCols($sColumn)] 2] $sColumn]
266
267  mkw.lassign [$this.cols tag ranges $sColumn] sI1 sI2
268  $this.cols tag add tCsel $sI1 $sI2-2ch
269}
270
271# name: _clipColumn
272# args: sColumn: valid column name
273#       vValues: name of private variable that holds column values
274# called when a column is dragged. clips the values in the main widget.
275# vValues must contain the values, or they are retrieved. clips each
276# cell by replacing the current value with the clipped value.
277metawidget proc Gridcontrol _clipColumn { sColumn {vValues {}} } {
278  if { $vValues == {} } {
279    set lValues [_getValues $sColumn]
280  } else {
281    set lValues [my $vValues]
282  }
283
284  foreach { sI1 sI2 } [$this.text tag ranges $sColumn] sValue $lValues {
285    $this.text delete $sI1+1ch $sI2-1ch
286    $this.text insert $sI1+1ch [_clipText $sValue $sColumn]
287  }
288}
289
290# name: _grab
291# args: {}
292# called before column resize. stores some values in private variables for
293# quicker access during actual dragging: column name into sCol, update mode
294# in numeric form in iUpd, mouse x coord in iX0, column values in lValues.
295metawidget proc Gridcontrol _grab {} {
296  my sCol [lindex [$this.cols tag names current] 0]
297  my iUpd [mkw.decode [my -update] {none 0 partial 1 full 2}]
298  my iX0 [expr [lindex [my aCols([my sCol])] 0]-[winfo pointerx .]]
299  my lValues [_getValues [my sCol]]
300}
301
302# name: _drag
303# args: -
304# called during dragging. calculates new width and udpates column data
305# with it. sets tabs and clips column and, if -update == full, text.
306metawidget proc Gridcontrol _drag {} {
307  set iX [expr [my iX0]+[winfo pointerx .]]
308  my aCols([my sCol]) [lreplace [my aCols([my sCol])] 0 0 $iX]
309
310  _setTabs [my iUpd]
311  _clipHeader [my sCol]
312
313  if { [my iUpd] == 2 } {
314    _clipColumn [my sCol] lValues
315  }
316}
317
318# name: _drop
319# args: -
320# called after column resize. sets tabs and clips one last time.
321metawidget proc Gridcontrol _drop {} {
322  _setTabs
323  _clipHeader [my sCol]
324  _clipColumn [my sCol] lValues
325}
326
327# name: _findColumn
328# args: sColumn: column name
329# checks if given column is valid and returns its position.
330metawidget proc Gridcontrol _findColumn { sColumn } {
331  set iPos [lsearch [my lCols] $sColumn]
332  if { $iPos == -1 } {
333    error "Gridcontrol $this: Unknown column '$sColumn'."
334  }
335
336  return $iPos
337}
338
339# name: _resolveArea
340# args: sCell1/2: Cell or Range definition
341# resolves a cell range specified by sCell1 and sCell2 into column names,
342# row numbers and column positions. sCell1/2 can have formats like *.*,
343# MyCol.*, *.5, MyCol.5 and identifies a cell, a column, a row or all.
344# therefore sCell1 and sCell2 define a rectangular shaped range of cells.
345# the result is a list with 5 elements: the column names of that range,
346# the first and last row number, and the first and last column positions.
347metawidget proc Gridcontrol _resolveArea { {sCell1 *.*} {sCell2 {}} } {
348  if { $sCell2 == {} } {
349    set sCell2 $sCell1
350  }
351
352  mkw.lassign [split $sCell1 .] sC1 iR1
353  mkw.lassign [split $sCell2 .] sC2 iR2
354
355  if { $sC1 == "*" } {
356    set sC1 [lindex [my lCols] 0]
357  }
358  if { $iR1 == "*" } {
359    set iR1 1
360  }
361
362  if { $sC2 == "*" } {
363    set sC2 [lindex [my lCols] end]
364  }
365  if { $iR2 == "*" } {
366    set iR2 [my iRows]
367  }
368
369  set iC1 [_findColumn $sC1]
370  set iC2 [_findColumn $sC2]
371
372  set iCol1 [expr ($iC1<$iC2)?$iC1:$iC2]
373  set iCol2 [expr ($iC1>$iC2)?$iC1:$iC2]
374  set iRow1 [expr ($iR1<$iR2)?$iR1:$iR2]
375  set iRow2 [expr ($iR1>$iR2)?$iR1:$iR2]
376
377  return [list [lrange [my lCols] $iCol1 $iCol2] $iRow1 $iRow2 $iCol1 $iCol2]
378}
379
380# name: _select
381# args: sStep  : m: mouse, c: column, r: row, p: page
382#       iDir   : 0 for mouse, 1 or -1 otherwise
383#       sAction: n: new selection, a: append, c: cursor only, t: toggle
384#       sMode  : s: single cell, r: range of cells, c: cursor only
385# sets the selection, using the standard sel tag.
386metawidget proc Gridcontrol _select { sStep iDir sAction sMode } {
387  focus $this.text
388
389  # step 1: determine the new cursor position
390  if { $sStep == "m" } {
391    # get index at mouse position ([... index current] does not update during
392    # <B1-Motion> events. cells would not get selected with dragging).
393    set iX [expr [winfo pointerx $this.text]-[winfo rootx $this.text]]
394    set iY [expr [winfo pointery $this.text]-[winfo rooty $this.text]]
395    set sInd [$this.text index @$iX,$iY]
396
397    # split sInd into column and row.
398    set iRow [expr ([lindex [split $sInd .] 0]+1)/2]
399    set sCol [lindex [$this.text tag names $sInd] 0]
400
401    # leave, if we had hit an invalid area
402    if { [lsearch "{} tCsep tRsep end" $sCol] != -1 } return
403  } else {
404    # get current cursor position...
405    if { ! [myinfo exists lCursor] } return
406    mkw.lassign [my lCursor] sCol iRow
407
408    # ...and calculate new position
409    switch $sStep {
410      c {
411        set iPos [expr [lsearch [my lCols] $sCol]+$iDir]
412        if { $iPos >= 0 && $iPos < [llength [my lCols]]} {
413          set sCol [lindex [my lCols] $iPos]
414        }
415      }
416      r {
417        incr iRow $iDir
418        if { $iRow < 1 } { set iRow 1 }
419        if { $iRow > [my iRows] } { set iRow [my iRows] }
420      }
421      p {
422        # widget height divided thru line height gives number of visible rows
423        set iRowH [expr [font metrics [my -font] -linespace]+1]
424        set iStep [expr [winfo height $this.text]/$iRowH]
425
426        incr iRow [expr $iDir*$iStep]
427        if { $iRow < 1 } { set iRow 1 }
428        if { $iRow > [my iRows] } { set iRow [my iRows] }
429      }
430    }
431  }
432
433  # step 2: get indexes of new cursor cell and set cursor tag there
434  mkw.lassign [$this.text tag nextrange $sCol [expr $iRow*2-1].0] sIc1 sIc2
435  if { ! [info exists sIc1] } return
436  $this.text tag remove cursor 1.0 end
437  $this.text tag add cursor $sIc1 $sIc2
438  my lCursor [list $sCol $iRow]
439
440  # step 3: determine if sel tag will be removed or added to cells
441  switch $sAction {
442    n - a {
443      set sMethod add
444    }
445    t {
446      # if it's already part of the column's tags, remove it
447      if { [lsearch [$this.text tag names $sIc1] sel] != -1 } {
448        set sMethod remove
449      } else {
450        set sMethod add
451      }
452    }
453  }
454
455  # step 4: remove existing sel tags, if a new selection is set
456  if { $sAction == "n" || [my -selectmode] == "single" } {
457    $this.text tag remove sel 1.0 end
458  }
459
460  # step 5: actually set the sel tag
461  switch $sMode.[my -selectmode] {
462    s.single - s.multiple - r.single {
463      # for single cells, add or remove sel tag
464      $this.text tag $sMethod sel $sIc1 $sIc2
465      my lAnchor [my lCursor]
466    }
467    r.multiple {
468      if { ! [myinfo exists lAnchor] } {
469        my lAnchor [my lCursor]
470      }
471
472      # for a range, loop thru columns and set sel for affected rows
473      mkw.lassign [my lAnchor] sACol iARow
474      set iC1 [lsearch [my lCols] $sACol]
475      set iC2 [lsearch [my lCols] $sCol]
476      set iI1 [expr ($iARow<$iRow?$iARow:$iRow)*2-2]
477      set iI2 [expr ($iARow>$iRow?$iARow:$iRow)*2-1]
478
479      if { $iC2 < $iC1 } {
480        set iC3 $iC2
481        set iC2 $iC1
482        set iC1 $iC3
483      }
484
485      for { set i $iC1 } { $i <= $iC2 } { incr i } {
486        set sColumn [lindex [my lCols] $i]
487        eval $this.text tag $sMethod sel [lrange [$this.text tag ranges $sColumn] $iI1 $iI2]
488      }
489    }
490  }
491
492  $this.text tag raise sel
493  $this.text tag raise cursor
494  $this.text see $sIc1
495
496  if { [$this.text tag names @0,0] == "tRsep" } {
497    $this.rows yview scroll 1 units
498    $this.text yview scroll 1 units
499  }
500
501  # if -onselect script is specified, evaluate it.
502  eval [my -onselect]
503}
504
505# name: _select2
506# args: see _select
507# called for selection of entire rows or columns. modifies the anchor points
508# lCursor and lAnchor and calls _select to actually set the seletion.
509metawidget proc Gridcontrol _select2 { sStep sAction sMode } {
510  if { [my -selectmode] == "single" } return
511
512  switch $sStep {
513    a {
514      my lCursor [list [lindex [my lCols] 0] 1]
515      my lAnchor [list [lindex [my lCols] end] [my iRows]]
516    }
517    c {
518      set sInd [$this.cols index current]
519      set sCol [lindex [$this.cols tag names $sInd] 0]
520      my lCursor [list $sCol 1]
521
522      if { $sMode == "s" } {
523        my lAnchor [list $sCol [my iRows]]
524      } else {
525        my lAnchor [lreplace [my lAnchor] 1 1 [my iRows]]
526      }
527    }
528    r {
529      set sInd [$this.rows index current]
530      set iRow [expr ([lindex [split $sInd .] 0]+1)/2]
531      my lCursor [list [lindex [my lCols] 0] $iRow]
532
533      if { $sMode == "s" } {
534        my lAnchor [list [lindex [my lCols] end] $iRow]
535      } else {
536        my lAnchor [lreplace [my lAnchor] 0 0 [lindex [my lCols] end]]
537      }
538    }
539  }
540
541  if { [my lCursor] != "" } {
542    _select c 0 $sAction r
543  }
544}
545
546# name: -update
547# args: sMode: update mode for column resizing
548# none resizes the column only, not the text. partial resizes the text but
549# does not do clipping. full does everything.
550metawidget proc Gridcontrol -update { sMode } {
551  my -update [mkw.complete $sMode {full partial none}]
552}
553
554# name: -selectmode
555# args: sMode: single, multiple
556# if single, only one cell can be selected.
557metawidget proc Gridcontrol -selectmode { sMode } {
558  my -selectmode [mkw.complete $sMode {single multiple}]
559}
560
561# name: -font
562# args: sFont: font specification
563# sets the fonts for all three text widgets. separate fonts would have made
564# geometry management too complicated.
565metawidget proc Gridcontrol -font { sFont } {
566  # set font
567  $this.text config -font $sFont
568  $this.cols config -font $sFont
569  $this.rows config -font $sFont
570
571  my -font $sFont
572
573  # calculate new width of those strange placeholders
574  my iOneSpace [font measure $sFont " "]
575  my iTwoTabs  [font measure $sFont "\t\t"]
576
577  # clip everything, in case font is larger then old one
578  foreach sCol [my lCols] {
579    _clipHeader $sCol
580    _clipColumn $sCol
581  }
582}
583
584# name: column
585# args: sAction: insert, delete etc.
586#       sColumn: column to work on
587#       args: depends on sAction
588# everything regarding columns. see comments below.
589metawidget proc Gridcontrol column { sAction {sColumn {}} args } {
590  switch [mkw.complete $sAction {insert delete configure cget fit bind names exists}] {
591    insert {
592      if { [lsearch [my lCols] $sColumn] != -1 } {
593        error "Gridcontrol $this: Column '$sColumn' already exists."
594      }
595
596      # first elem in $args is desired column position. the name of the column
597      # currently at this position is retrieved.
598      set iPos [lindex $args 0]
599      if { $iPos == "end" || $iPos == "" } {
600        set sTag end
601      } else {
602        set sTag [lindex [my lCols] $iPos]
603      }
604
605      # update column list, create record for new column with defaults
606      my lCols [linsert [my lCols] $iPos $sColumn]
607      my aCols($sColumn) [list 120 l $sColumn]
608
609      # update data table: insert empty string at new column position
610      for { set i 1 } { $i <= [my iRows] } { incr i } {
611        my lData [lreplace [my lData] $i $i [linsert [lindex [my lData] $i] $iPos {}]]
612      }
613
614      # update main widget: the column at the insertion position gives us the
615      # indexes where to insert. inserts two tabs and a space. the first tab
616      # is used for text anchor, the second is the column separator tab. the
617      # space becomes the gray vertical line. the new column including the
618      # two tabs gets a tag assigned with the same name as the column!
619      foreach { sI1 sI2 } [$this.text tag ranges $sTag] {
620        $this.text insert $sI1 "\t\t "
621        $this.text tag add $sColumn $sI1 $sI1+2ch
622        $this.text tag add tCsep $sI1+2ch
623      }
624
625      # update column widget: similar to above. tags are: tCols spans the
626      # new column and gives it 3d look. tCsep is the gap in between. tDrag
627      # covers the right tab and has bindings for column resizing.
628      mkw.lassign [$this.cols tag ranges $sTag] sI1
629      $this.cols insert $sI1 "\t\t "
630      $this.cols tag add tCols $sI1 $sI1+2ch
631      $this.cols tag add tCsep $sI1+2ch
632      $this.cols tag add tDrag $sI1+1ch $sI1+3ch
633      $this.cols tag add $sColumn $sI1 $sI1+3ch
634      $this.cols tag lower $sColumn    ;# make it the first in the tag list
635
636      # set any passed option/value pairs in the rest of $args
637      eval column config {$sColumn} -text {$sColumn} [lrange $args 1 end]
638      _setTabs
639    }
640
641    delete {
642      set iCol [$this _findColumn $sColumn]
643
644      # update data table: delete fields of that column
645      #loop i 1 [my iRows] {}
646      for { set i 1 } { $i <= [my iRows] } { incr i } {
647        my lData [lreplace [my lData] $i $i [lreplace [lindex [my lData] $i] $iCol $iCol]]
648      }
649
650      # update column list, forget column record
651      my lCols [lreplace [my lCols] $iCol]
652      unmy aCols($sColumn)
653
654      # update main widget: delete all text with the column name's tag
655      foreach { sI1 sI2 } [$this.text tag ranges $sColumn] {
656        $this.text delete $sI1 $sI2+1ch
657      }
658
659      # update column widget: similar to above
660      mkw.lassign [$this.cols tag ranges $sColumn] sI1 sI2
661      $this.cols delete $sI1 $sI2
662
663      _setTabs
664    }
665
666    configure {
667      _findColumn $sColumn
668
669      set args [mkw.options $args * {-width *} {-align *} {-text *}]
670
671      # all non-special options, please proceed to the column's tag.
672      if { $args != {} } {
673        eval $this.cols tag configure {$sColumn} $args
674      }
675
676      # change width of column. tabs likely to change, clipping required.
677      if { [info exists -width] } {
678        my aCols($sColumn) [lreplace [my aCols($sColumn)] 0 0 ${-width}]
679        _setTabs
680        _clipHeader $sColumn
681        _clipColumn $sColumn
682      }
683
684      # change alignment. update tabs.
685      if { [info exists -align] } {
686        set sAlign [mkw.complete ${-align} {left right center numeric}]
687        my aCols($sColumn) [lreplace [my aCols($sColumn)] 1 1 [string index $sAlign 0]]
688        _setTabs
689      }
690
691      # change column title. clipping will let it appear.
692      if { [info exists -text] } {
693        my aCols($sColumn) [lreplace [my aCols($sColumn)] 2 2 ${-text}]
694        _clipHeader $sColumn
695      }
696    }
697
698    cget {
699      _findColumn $sColumn
700
701      # if it's not a special option, try to get value from the column's tag
702      if { [catch {
703        set sOption [mkw.complete $args {-width -align -text}]
704      }] } {
705        return [$this.cols tag cget $sColumn $args]
706      }
707
708      # otherwise return value from column record.
709      switch -- $sOption {
710        -width  {
711          return [lindex [my aCols($sColumn)] 0]
712        }
713        -align {
714          return [mkw.decode [lindex [my aCols($sColumn)] 1] {l left r right c center n numeric}]
715        }
716        -text {
717          return [lindex [my aCols($sColumn)] 2]
718        }
719      }
720    }
721
722    fit {       ;# autosizes a column
723      set iCol [_findColumn $sColumn]
724      set iWidth 0
725      set sFont [$this.text cget -font]
726
727      foreach lValues [my lData] {
728        set sValue [lindex $lValues $iCol]
729        set iReqw [font measure $sFont "\t$sValue \t"]
730        if { $iReqW > $iWidth } { set iWidth $iReqW }
731      }
732
733      column config $sColumn -width $iWidth
734    }
735
736    bind {      ;# bind command for column. is applied to the column's tag.
737      _findColumn $sColumn
738      eval $this.cols tag bind {$sColumn} $args
739    }
740
741    names {
742      return [my lCols]
743    }
744
745    exists {
746      return [myinfo exists aCols($sColumn)]
747    }
748  }
749}
750
751# name: insert
752# args: iRow: position to insert new row
753#       args: row values.
754# inserts one or more rows at the position specified by iRow. each element
755# in args must contain the values for an entire row.
756metawidget proc Gridcontrol insert { iRow args } {
757  if { $iRow == "end" || $iRow > [my iRows] } {
758    set iRow [expr [my iRows]+1]
759  } elseif { $iRow < 1 } {
760    set iRow 1
761  }
762
763  foreach lValues $args {
764    # update data table and row count
765    my lData [linsert [my lData] $iRow $lValues]
766    my iRows [expr [my iRows]+1]
767
768    # insert one line for the values and one for row separator
769    set iLine [expr $iRow*2-1]
770    $this.text insert $iLine.0 "\n\n"
771    $this.text tag add tRsep $iLine.0+1lines $iLine.0+2lines
772
773    # insert text similar to "column insert" (same technique, same tags)
774    foreach sValue $lValues sColumn [my lCols] {
775      $this.text insert $iLine.end "\t\t "
776      $this.text tag add $sColumn $iLine.end-3ch $iLine.end-1ch
777      $this.text tag add tCsep $iLine.end-1ch
778      $this.text insert $iLine.end-2ch [_clipText $sValue $sColumn]
779    }
780    $this.text tag add end $iLine.end
781
782    # update rows widget: one line for row number, one for separator
783    set sI [$this.rows index end]
784    $this.rows insert end "[my iRows]\n\n"
785    $this.rows tag add tRows $sI-1lines $sI
786    $this.rows tag add tRsep $sI $sI+1lines
787    incr iRow
788  }
789
790  # let rows widget dynamically grow or shrink
791  $this.rows config -width [expr [string length [my iRows]]+1]
792}
793
794# name: delete
795# args: iRow1: first row to delete
796#       iRow2: last row to delete
797# deletes one or more rows. iRow1 and iRow2 specifiy a range of rows.
798metawidget proc Gridcontrol delete { iRow1 {iRow2 {}} } {
799  if { $iRow1 == "end" || $iRow1 > [my iRows] } {
800    set iRow1 [my iRows]
801  } elseif { $iRow1 < 1 } {
802    set iRow1 1
803  }
804
805  if { $iRow2 == "end" || $iRow2 > [my iRows] } {
806    set iRow2 [my iRows]
807  } elseif { $iRow2 == {} } {
808    set iRow2 $iRow1
809  } elseif { $iRow2 < 1 } {
810    set iRow2 1
811  }
812
813  # clean up data table and row count
814  my lData [lreplace [my lData] $iRow1 $iRow2]
815  my iRows [expr [my iRows]+$iRow1-$iRow2-1]
816
817  # delete rows from main widget
818  set iLine1 [expr $iRow1*2-1]
819  set iLine2 [expr $iRow2*2+1]
820  $this.text delete $iLine1.0 $iLine2.0
821
822  # delete trailing rows from rows widget
823  set sI [$this.rows index end]
824  $this.rows delete $sI-[expr ($iRow2-$iRow1)*2+3]lines $sI-1lines
825  $this.rows config -width [expr [string length [my iRows]]+1]
826}
827
828# name: set
829# args: sCell: cell specifier
830#       args: list of value-lists to be set
831# sets new values for a cell or a range of cells. args must be a two-
832# dimensional table that is embedded at the position specified by sCell.
833metawidget proc Gridcontrol set_ { sCell args } {
834  mkw.lassign [_resolveArea $sCell] lCols iRow1 iRow2 iCol1
835
836  foreach lValues $args {
837    set iCol2 [expr $iCol1+[llength $lValues]-1]
838    set lRow  [lindex [my lData] $iRow1]
839    set lRow  [eval lreplace \$lRow $iCol1 $iCol2 $lValues]
840    my lData [lreplace [my lData] $iRow1 $iRow1 $lRow]
841    incr iRow1
842  }
843
844  foreach sCol $lCols {
845    _clipColumn $sCol
846  }
847}
848
849# name: get
850# args: sCell1/2: cell specifier
851# returns the values of a cell or a range of cells. sCell1 specifies
852# the top-left corner, sCell2 the bottom-right corner of the cell range.
853# the return list is a two-dimensional table (a list of lists).
854metawidget proc Gridcontrol get { {sCell1 *.*} {sCell2 {}} } {
855  mkw.lassign [_resolveArea $sCell1 $sCell2] lCols iRow1 iRow2 iCol1 iCol2
856  set lData {}
857
858  for { set i $iRow1 } { $i <= $iRow2 } { incr i } {
859    lappend lData [lrange [lindex [my lData] $i] $iCol1 $iCol2]
860  }
861
862  return $lData
863}
864
865# name: tag
866# args: sAction: almost all commands allowed for tags
867#       sTag: tag name. must not be a column name
868#       args: depends on sAction
869# similar to the "text tag" command. some tag commands require special
870# treatment so the don't mix up the internal tags. careful with fonts:
871# geometry might get out of wack.
872metawidget proc Gridcontrol tag { sAction sTag args } {
873  # column names are reserved, the sel tag is not.
874  if { [lsearch [my lCols] $sTag] != -1 } {
875    error "Gridcontrol $this: Tag name $sTag not allowed"
876  }
877
878  switch [mkw.complete $sAction {add bind cget configure delete lower names raise ranges remove}] {
879    add - remove {
880      # tag ranges are given by cell specifiers in the first two elems of $args.
881      # they are resolved to columns and rows, then true text indexes. then the
882      # user's tag is added or removed.
883      mkw.lassign [_resolveArea [lindex $args 0] [lindex $args 1]] lCols iR1 iR2
884      set iL1 [expr $iR1*2-2]
885      set iL2 [expr $iR2*2-1]
886      foreach sCol $lCols {
887        eval $this.text tag $sAction {$sTag} [lrange [$this.text tag ranges $sCol] $iL1 $iL2]
888      }
889    }
890
891    ranges {
892      # converts the text indexes returned by [text tag ranges] into cell
893      # indexes. e.g 24.5 (row 24, character pos. 5) maps to MyCol.24.
894      set lCells {}
895      foreach { sI1 sI2 } [$this.text tag ranges $sTag] {
896        mkw.lassign [split $sI1 .] iLine iCol
897        set sCol [lindex [$this.text tag names $sI1] 0]
898        set iRow [expr $iLine/2+1]
899        lappend lCells $sCol.$iRow
900      }
901
902      return $lCells
903    }
904
905    names {
906      # takes $sTag as a cell, converts it into an index and gets its tags
907      mkw.lassign [_resolveArea $sTag] lCols iR1 iR2
908      set sInd [lindex [$this.text tag ranges [lindex $lCols 0]] [expr $iR1*2-2]]
909      return [$this.text tag names $sInd]
910    }
911
912    lower {
913      # the internal tags (those that have the column names) *must* be the first
914      # one returned by [text tag names]. the loop takes care of that.
915      eval $this.text tag lower {$sTag} $args
916
917      foreach sColumn [my lCols] {
918        $this.text tag lower $sColumn
919      }
920    }
921
922    default {
923      # all other commands are directly applied.
924      eval $this.text tag $sAction {$sTag} $args
925    }
926  }
927}
928
929# name: see
930# args: sCell: cell specifier
931# makes a give cell visible. simply transforms the cell spec into a
932# real text index and calls the "see" command.
933metawidget proc Gridcontrol see { sCell } {
934  mkw.lassign [_resolveArea $sCell] sCol iRow
935  $this.text see [lindex [$this.text tag nextrange $sCol $iRow.0] 0]
936}
937
938# name: bind
939# args: args: as accepted by the bind command.
940# applies bindings to the main widget.
941metawidget proc Gridcontrol bind_ { args } {
942  eval bind $this.text $args
943}
944
945# name: rows
946# args: -
947# returns the number of rows.
948metawidget proc Gridcontrol rows {} {
949  return [my iRows]
950}
951
952metawidget command Gridcontrol _dragX   _dragX
953metawidget command Gridcontrol _dragY   _dragY
954metawidget command Gridcontrol _moveX   _moveX
955metawidget command Gridcontrol _moveY   _moveY
956metawidget command Gridcontrol _grab    _grab
957metawidget command Gridcontrol _drag    _drag
958metawidget command Gridcontrol _drop    _drop
959metawidget command Gridcontrol _select  _select
960metawidget command Gridcontrol _select2 _select2
961
962metawidget command Gridcontrol column   column
963metawidget command Gridcontrol insert   insert
964metawidget command Gridcontrol delete   delete
965metawidget command Gridcontrol set      set_
966metawidget command Gridcontrol get      get
967metawidget command Gridcontrol tag      tag
968metawidget command Gridcontrol see      see
969metawidget command Gridcontrol bind     bind_
970metawidget command Gridcontrol rows     rows
971
972metawidget option  Gridcontrol -update     -update
973metawidget option  Gridcontrol -selectmode -selectmode
974metawidget option  Gridcontrol -font       -font
975metawidget option  Gridcontrol -onselect
976
977proc testSetValue {} {
978  global sValue
979
980  foreach sCell [.grid tag ranges sel] {
981    .grid set $sCell $sValue
982  }
983}
984
985proc test {} {
986  pack [frame .ctrl] -fill x
987  pack [label .ctrl.capt -text "Select some cells, enter a number in the entry below and press Enter"]
988  pack [entry .ctrl.entr -textvariable sValue] -side left -fill x -expand 1
989  bind .ctrl.entr <Key-Return> {.ctrl.setv invoke}
990
991  pack [button .ctrl.setv -text Set -command {
992    foreach sCell [.grid tag ranges sel] {
993      if { [lsearch [.grid tag names $sCell] tSum] != -1 } continue
994      .grid set $sCell [list $sValue]
995    }
996
997    for { set i 1 } { $i <= 3 } { incr i } {
998      set lValues [.grid get val$i.1 val$i.4]
999      set fSum 0
1000      foreach sVal $lValues {
1001        catch { set fSum [expr $fSum+$sVal] }
1002      }
1003
1004      .grid set val$i.5 $fSum
1005    }
1006    for { set i 1 } { $i <= 5 } { incr i } {
1007      set lValues [.grid get val1.$i val3.$i]
1008      set fSum 0
1009      foreach sVal [lindex $lValues 0] {
1010        catch { set fSum [expr $fSum+$sVal] }
1011      }
1012
1013      .grid set vsum.$i $fSum
1014    }
1015  }] -side right
1016
1017
1018  pack [gridcontrol .grid -font {Courier 12}] -fill both -expand 1
1019
1020  .grid column insert val1 end -text "Value 1" -width 80 -align c
1021  .grid column insert val2 end -text "Value 2" -width 80 -align c
1022  .grid column insert val3 end -text "Value 3" -width 80 -align c
1023  .grid column insert vsum end -text "Sum"     -width 80 -align c
1024
1025  .grid insert end [list {} {} {} {}]
1026  .grid insert end [list {} {} {} {}]
1027  .grid insert end [list {} {} {} {}]
1028  .grid insert end [list {} {} {} {}]
1029  .grid insert end [list {} {} {} {}]
1030
1031  .grid tag config tSum -back yellow;
1032  .grid tag add tSum vsum.*
1033  .grid tag add tSum *.5
1034
1035  .grid tag add tTot vsum.5
1036  .grid tag config tTot -back red
1037
1038  .grid bind <Double-1> {
1039     set sValue [lindex [lindex [.grid get [lindex [.grid tag ranges sel] 0]] 0] 0]
1040  }
1041}
1042
1043#test
Note: See TracBrowser for help on using the repository browser.