source: tol/tolbaseBLT/lib/mkWidgets1.3/Listcontrol.tcl @ 56ade3b

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

fixes #1932, se ha creado la rama trunk/tolbaseBLT copia de trunk/tolbase

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

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