source: tol/tolbase/lib/mkWidgets1.3/Treecontrol.tcl @ 3ed16ee

1941-docker-image-to-testdevel
Last change on this file since 3ed16ee 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.1 KB
Line 
1package require Metawidget
2
3# name: Treecontrol
4# args: args: option-value pairs
5# for the first instance, defines three images for an expanded, a collapsed
6# and a leaf node, and defines class bindings. then creates internal widgets,
7# finally inits private variables and widget options.
8metawidget create Treecontrol {
9  if { ! [ourinfo exists pCol] } {
10    our pCol [image create bitmap -back black -fore gray60 -data {
11      #define _width 16
12      #define _height 9
13      static char _bits[] = {
14        0xff, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
15        0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0xff, 0x01
16      }
17    } -maskdata {
18      #define _width 16
19      #define _height 9
20      static char _bits[] = {
21        0xff, 0x01, 0x01, 0x01, 0x11, 0x01, 0x11, 0x01, 0x7d, 0x01,
22        0x11, 0x01, 0x11, 0x01, 0x01, 0x01, 0xff, 0x01
23      }
24    }]
25    our pExp [image create bitmap -back black -fore gray60 -data {
26      #define _width 16
27      #define _height 9
28      static char _bits[] = {
29        0xff, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
30        0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0xff, 0x01
31      }
32    } -maskdata {
33      #define _width 16
34      #define _height 9
35      static char _bits[] = {
36        0xff, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x7d, 0x01,
37        0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0xff, 0x01
38      }
39    }]
40    our pLeaf [image create bitmap -fore gray60 -data {
41      #define _width 16
42      #define _height 9
43      static char _bits[] = {
44        0xff, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
45        0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0xff, 0x01
46      }
47    }]
48
49    foreach { sBind sStep iDir sAction iMode } {
50      <Button-1>            m  0 n s   <Control-1>           m  0 t s
51      <Shift-1>             m  0 n r   <Shift-Control-1>     m  0 a r
52
53      <Up>                  r -1 n s   <Down>                r  1 n s
54      <Shift-Up>            r -1 n r   <Shift-Down>          r  1 n r
55      <Control-Up>          r -1 c c   <Control-Down>        r  1 c c
56      <Shift-Control-Up>    r -1 a r   <Shift-Control-Down>  r  1 a r
57
58      <Prior>               p -1 n s   <Next>                p  1 n s
59      <Shift-Prior>         p -1 n r   <Shift-Next>          p  1 n r
60      <Control-Prior>       p -1 c c   <Control-Next>        p  1 c c
61      <Shift-Control-Prior> p -1 a r   <Shift-Control-Next>  p  1 a r
62
63      <Control-space>       r  0 t s
64    } {
65      bind Treecontroltext $sBind "\[winfo parent %W\] _select $sStep $iDir $sAction $iMode"
66    }
67
68    # expands and collapses on left/right cursor keys
69    bind Treecontroltext <Left>        "\[winfo parent %W\] _leftright left"
70    bind Treecontroltext <Right>       "\[winfo parent %W\] _leftright right"
71    bind Treecontroltext <Shift-Left>  "\[winfo parent %W\] _leftright left 1"
72    bind Treecontroltext <Shift-Right> "\[winfo parent %W\] _leftright right 1"
73
74    # jump to start or end, should be improved one day
75    bind Treecontroltext <Home>        "%W see 1.0"
76    bind Treecontroltext <End>         "%W see end"
77
78    # take over some useful Text class bindings
79    foreach sBind {<MouseWheel> <B2-Motion> <Button-2> <<Copy>>} {
80      bind Treecontroltext $sBind [bind Text $sBind]
81    }
82
83    # create string of tabs for indention. 8.0 doesn't know "string repeat"!
84    for { set i 0 } { $i < 10 } { incr i } { append lTabs "\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t" }
85    our lTabs $lTabs
86  }
87
88  scrollbar $this.scrh -command "$this.text xview" -orient horiz
89  scrollbar $this.scrv -command "$this.text yview"
90
91  # nodes will be 22 pixels indented, ideal for 16x16 icons
92  text $this.text -wrap none -cursor arrow -spacing1 1 -tabs {3 22} \
93                  -border 0 -bg white -padx 1 -pady 1 -insertofftime 0 -insertwidth 0 \
94                  -xscroll "$this.scrh set" -yscroll "$this.scrv set"
95
96  pack $this.scrv -side right -fill y
97  pack $this.scrh -side bottom -fill x
98  pack $this.text -fill both -expand 1
99
100  # change Text to Treecontrol class bindings
101  bindtags $this.text [mkw.lchange [bindtags $this.text] Text Treecontroltext]
102
103  $this.text tag config cursor -border 1 -relief solid
104
105  my lNodes {}            ;# list of all nodes (unordered)
106  my iCursor 0            ;# row where cursor is
107  my iAnchor 0            ;# row for selection anchor
108
109  # define root node properties. will never change. fields are:
110  # text, image, parent, children, depth, expanded, final, tags, userdata
111  my aNodes() [list $this {} {} {} 0 1 1 {} {}]
112
113  -image {}       ;# image for root node
114  -text $this     ;# text for root node
115
116  my -onexpand {}         ;# eval cmd when any node is expanded
117  my -onselect {}         ;# eval cmd when any node is selected
118  my -selectmode multiple ;# selectmode: single or multiple
119  my -fullexpand 1        ;# allows recursive expanding of nodes
120} {} -default text
121
122# name: _dump
123# args: sNode: node to print tree for
124#       iIndent: indention. don't specify!
125# for debug purposes. prints a tree starting with node $sNode.
126metawidget proc Treecontrol _dump { {sNode {}} {iIndent 0} } {
127  puts "[string range [our lTabs] 3 $iIndent]$sNode               [lindex [my aNodes($sNode)] 2]   [lrange [my aNodes($sNode)] 4 end]"
128
129  foreach sChild [lindex [my aNodes($sNode)] 3] {
130    _dump $sChild [expr $iIndent+2]
131  }
132}
133
134# name: _checkNode
135# args: sNode: node noame
136# throws an exception if the given node doesn't exist.
137metawidget proc Treecontrol _checkNode { sNode } {
138  if { [myinfo exists aNodes($sNode)] } return
139  error "Treecontrol $this: Node $sNode does not exist."
140}
141
142# name: _toggle
143# args: bAll: flag if recursive or not
144# expands or collapses the node where the +/- symbol was clicked.
145metawidget proc Treecontrol _toggle { bAll } {
146  # get node name. is part of tag list behind the node's text.
147  set sNode [lindex [$this.text tag names {current lineend-1ch}] 0]
148
149  # no recursive expand if disallowed by user
150  set bAll [expr $bAll && [my -fullexpand]]
151
152  # look in node's properties. expand if it was collapsed and vice versa.
153  switch -- [lindex [my aNodes($sNode)] 5] {
154     1 { collapse $sNode $bAll }
155    -1 { expand   $sNode $bAll }
156  }
157}
158
159# name: _leftright
160# args: sDir: left or right
161#       bAll: flag if recursive or not
162# expands or collapses selected nodes, triggered by cursor keys.
163# if bAll is true, does it for all children as well.
164metawidget proc Treecontrol _leftright { sDir {bAll 0} } {
165  # no recursive expand if disallowed by user
166  set bAll [expr $bAll && [my -fullexpand]]
167
168  foreach sNode [selection get] {
169    switch $sDir {
170      left  { collapse $sNode $bAll }
171      right { expand   $sNode $bAll }
172    }
173  }
174}
175
176# name: _select
177# args: sStep  : m: mouse, r: row, p: page
178#       iDir   : 0 for mouse, 1 or -1 otherwise
179#       sAction: n: new selection, a: append, c: cursor only, t: toggle
180#       sMode  : s: single cell, r: range of cells, c: cursor only
181#
182# sets the selection, using the standard sel tag.
183metawidget proc Treecontrol _select { sStep iDir sAction sMode } {
184  focus $this.text
185
186  if { [lsearch [$this.text tag names current] _tSign] != -1 } {
187    _toggle [mkw.decode $sMode {s 0 r 1}]
188    return
189  }
190
191  # step 1: determine the cursor new row
192  switch $sStep {
193    m {
194      set iRow [lindex [split [$this.text index current] .] 0]
195    }
196    r {
197      set iMaxRow [expr [lindex [split [$this.text index end] .] 0] - 2]
198      set iRow [expr [my iCursor]+$iDir]
199      if { $iRow < 1 }        { set iRow 1 }
200      if { $iRow > $iMaxRow } { set iRow $iMaxRow }
201    }
202    p {
203      # widget height divided thru line height gives number of visible rows
204      set iRowH [expr [font metrics [$this.text cget -font] -linespace]+1]
205      set iStep [expr [winfo height $this.text]/$iRowH]
206      set iMaxRow [expr [lindex [split [$this.text index end] .] 0] - 2]
207      set iRow [expr [my iCursor]+$iDir*$iStep]
208      if { $iRow < 1 }        { set iRow 1 }
209      if { $iRow > $iMaxRow } { set iRow $iMaxRow }
210    }
211  }
212
213  # step 2: get indexes of that row's node and set cursor tag there
214  $this.text tag remove cursor 1.0 end
215  mkw.lassign [$this.text tag nextrange _tNode $iRow.0] sC1 sC2
216  if { ! [info exists sC1] || ! [info exists sC2] } return
217  $this.text tag add cursor $sC1 $sC2
218  my iCursor $iRow
219
220  # step 3: determine if sel tag will be removed or added to cells
221  switch $sAction {
222    n - a {
223      set sMethod add
224    }
225    t {
226      if { [lsearch [$this.text tag names $sC1] sel] != -1 } {
227        set sMethod remove
228      } else {
229        set sMethod add
230      }
231    }
232  }
233
234  # step 4: remove existing sel tags, if a new selection is set
235  if { $sAction == "n" || [my -selectmode] == "single" } {
236    $this.text tag remove sel 1.0 end
237  }
238
239  # step 5: actually set the sel tag
240  switch $sMode.[my -selectmode] {
241    s.single - s.multiple - r.single {
242      # for single cells, add or remove sel tag
243      $this.text tag $sMethod sel $sC1 $sC2
244      my iAnchor [my iCursor]
245    }
246    r.multiple {
247      # for a range, do all from anchor to cursor
248      set iR1 [expr ($iRow<[my iAnchor])?$iRow:[my iAnchor]]
249      set iR2 [expr ($iRow>[my iAnchor])?$iRow:[my iAnchor]]
250      for { set i $iR1 } { $i <= $iR2 } { incr i } {
251        mkw.lassign [$this.text tag nextrange _tNode $i.0] sI1 sI2
252        $this.text tag add sel $sI1 $sI2
253      }
254    }
255  }
256
257  $this.text tag raise sel
258  $this.text tag raise cursor
259  $this.text see $iRow.0
260
261  # if -onselect script is specified, evaluate it.
262  eval [my -onselect]
263}
264
265# name: _visible
266# args: sNode: node name
267# returns 1 if the node is visible, i.e. if it's part of the text widget.
268# can still be physically invisible because the viewport is somewhere else.
269metawidget proc Treecontrol _visible { sNode } {
270  if { [$this.text tag range $sNode] == {} } {
271    return 0
272  } else {
273    return 1
274  }
275}
276
277# name: _drawNode
278# args: sNode: node name
279#       iRow: row to draw node
280# draws a node at the specified row. if row is not given, it calculates it.
281# this will draw the plain node but no children.
282metawidget proc Treecontrol _drawNode { sNode {iRow {}} } {
283  # get row if not calculated already
284  if { $iRow == {} } {
285    set iRow [_calcRow $sNode]
286  }
287
288  # extract some node properties
289  mkw.lassign [my aNodes($sNode)] sText pImage x x iIndent bExp
290
291  # create new line and tabs for indention
292  $this.text insert $iRow.0 "\n"
293  $this.text insert $iRow.0 [string range [our lTabs] 1 $iIndent]
294
295  # create +/-/. symbol, mark it with tag to add mouse click bindinds
296  $this.text image create $iRow.end -image [mkw.decode $bExp "-1 [our pCol] 1 [our pExp] 0 [our pLeaf]"]
297  $this.text tag add _tSign $iRow.end-1char
298
299  # add node image, if specified, and add tag for selection bindings.
300  if { $pImage != {} } {
301    $this.text image create $iRow.end -image $pImage
302    $this.text tag add _tImg $iRow.end-1ch
303  }
304
305  # then insert placeholders and add tag for selection bindings.
306  $this.text insert $iRow.end "  "
307  $this.text tag add $sNode $iRow.end-2ch $iRow.end
308  $this.text tag add _tNode $iRow.end-2ch $iRow.end
309
310  # if any user tags are specified, add them.
311  foreach sTag [lindex [my aNodes($sNode)] 7] {
312    $this.text tag add $sTag $iRow.end-2ch $iRow.end
313  }
314
315  # insert the node's caption and make it the first in the tag list.
316  $this.text insert $iRow.end-1ch "$sText"
317  $this.text tag lower $sNode
318}
319
320# name: _drawChildren
321# args: sNode: node name
322# draws the children for the given node. if a child is in expanded state,
323# will draw its children too and so on.
324metawidget proc Treecontrol _drawChildren { sNode } {
325  # only do it when it says it is expanded
326  if { [lindex [my aNodes($sNode)] 5] != 1 } return
327
328  set lChildren [lindex [my aNodes($sNode)] 3]
329  set iRow [expr [_getRow $sNode]+1]
330
331  # first draw the direct children...
332  foreach sChild $lChildren {
333    _drawNode $sChild $iRow
334    incr iRow
335  }
336
337  # ...then draw their children
338  foreach sChild $lChildren {
339    _drawChildren $sChild
340  }
341}
342
343# name: _undrawNode
344# args: sNode: node name
345# undraws a node. does not touch its children.
346metawidget proc Treecontrol _undrawNode { sNode } {
347  set iR1 [_getRow $sNode]
348  set iR2 [expr $iR1+1]
349  $this.text delete $iR1.0 $iR2.0
350}
351
352# name: _undrawChildren
353# args: sNode: node name
354# undraws the children of a node. node must me visible when this is called!
355metawidget proc Treecontrol _undrawChildren { sNode } {
356  set iR1 [expr [_getRow $sNode]+1]
357  set iR2 [expr $iR1+[_calcChildren $sNode]]
358  $this.text delete $iR1.0 $iR2.0
359}
360
361# name: _calcChildren
362# args: sNode: node name
363# calculates the number of *visible* children, i.e. where their parent is
364# expanded. uses plain recursion and looks at node properties. (yes, marks
365# would have done that, too. for me this worked better.)
366metawidget proc Treecontrol _calcChildren { sNode } {
367  if { [lindex [my aNodes($sNode)] 5] != 1 } {
368    return 0
369  }
370
371  set lChildren [lindex [my aNodes($sNode)] 3]
372  set iNum [llength $lChildren]
373
374  foreach sChild $lChildren {
375    incr iNum [_calcChildren $sChild]
376  }
377
378  return $iNum
379}
380
381# name: _calcRow
382# args: sNode: node name
383# calculates the row where a node should be drawn. used when the node is
384# not yet visible (otherwise _getRow would apply). the node's parent *must*
385# be expanded or an empty string is returned.
386metawidget proc Treecontrol _calcRow { sNode } {
387  set sParent [lindex [my aNodes($sNode)] 2]
388  if { [lindex [my aNodes($sParent)] 5] != 1 } return
389
390  # get siblings and the node's predecessor
391  set lSiblings [lindex [my aNodes($sParent)] 3]
392  set iPos [lsearch $lSiblings $sNode]
393  set sPrev [lindex $lSiblings [expr $iPos-1]]
394
395  # no predecessor means that the node is the first and its row is one below
396  # its parent. otherwise the row is below the predecessor plus any expanded
397  # children.
398  if { $sPrev == {} } {
399    set iParRow [_getRow $sParent]
400    if { $iParRow == {} } { return Damn! }
401    set iRow [expr $iParRow+1]
402  } else {
403    set iRow [expr [_getRow $sPrev]+[_calcChildren $sPrev]+1]
404  }
405
406  return $iRow
407}
408
409# name: _getRow
410# args: sNode: node name
411# returns the row of a node. node must be visible. looks for a tag with the
412# node's name and returns its position's row portion.
413metawidget proc Treecontrol _getRow { sNode } {
414  set lRange [$this.text tag ranges $sNode]
415  return [lindex [split $lRange .] 0]
416}
417
418# name: _recalcDepth
419# args: sNode: node name
420# updates the depth field in the node's properties and all of its children.
421# called when a node has been reparented.
422metawidget proc Treecontrol _recalcDepth { sNode } {
423  set sParent [lindex [my aNodes($sNode)] 2]
424  my aNodes($sNode) [lreplace [my aNodes($sNode)] 4 4 [expr [lindex [my aNodes($sParent)] 4]+1]]
425
426  foreach sChild [lindex [my aNodes($sNode)] 3] {
427    _recalcDepth $sChild
428  }
429}
430
431# name: _setSymbol
432# args: sNode: node name
433# sets the +/-/. symbol depending on the node's state. -1 is collapsed,
434# 1 is expanded, 0 is a leaf (no children).
435metawidget proc Treecontrol _setSymbol { sNode } {
436  # node must be visible. exits if not.
437  set iRow [_getRow $sNode]
438
439  if { $iRow == {} } return
440
441  set sIndex $iRow.[lindex [my aNodes($sNode)] 4]
442  set sImage [mkw.decode [lindex [my aNodes($sNode)] 5] "-1 [our pCol] 1 [our pExp] 0 [our pLeaf]"]
443
444  $this.text image config $sIndex -image $sImage
445}
446
447# name: _delete
448# args: sNode: node name
449# deletes a node and all children logically. calls to _undrawChildren
450# and _undrawNode for the visual representation must have been made prior.
451# does not update the parent's children list (would slow down recursion).
452metawidget proc Treecontrol _delete { sNode } {
453  foreach sChild [lindex [my aNodes($sNode)] 3] {
454    _delete $sChild
455  }
456
457  unmy aNodes($sNode)
458  my lNodes [mkw.lshrink [my lNodes] $sNode]
459}
460
461# name: -text
462# args: sText: text for root node
463# sets the text in the first line's node.
464metawidget proc Treecontrol -text { sText } {
465  if { [my -image] == {} } {
466    set sInd 1.0
467  } else {
468    set sInd 1.1
469  }
470
471  $this.text delete $sInd 2.0
472  $this.text insert $sInd " $sText\n"
473  $this.text tag add {} $sInd 1.end
474
475  my -text $sText
476}
477
478# name: -image
479# args: sImage: image for root node
480# sets or removes the image in the first line's node.
481metawidget proc Treecontrol -image { sImage } {
482  catch {
483    $this.text image cget 1.0 -image
484    $this.text delete 1.0
485  }
486
487  if { $sImage != {} } {
488    $this.text image create 1.0 -image $sImage
489  }
490
491  my -image $sImage
492}
493
494# name: -fullexpand
495# args: sFull: boolean
496# controls whether recursive expanding of nodes shall be allowed
497metawidget proc Treecontrol -fullexpand { sFull } {
498  set sFull [mkw.complete $sFull {yes true on no false off 0 1}]
499  my -fullexpand [mkw.decode $sFull {yes 1 true 1 on 1 1 1} 0]
500}
501
502# name: -selectmode
503# args: sMode: single, multiple
504# if single, only one node can be selected.
505metawidget proc Treecontrol -selectmode { sMode } {
506  my -selectmode [mkw.complete $sMode {single multiple}]
507}
508
509# name: expand
510# args: sNode: node name
511#       bAll: true if recursive
512# expands a node and eventually all children. prior to that it evaluates
513# the -onexpand callback command, if specified and if the node is not in
514# "final" state. final state means that all children are defined, therefore
515# no callback for new children is required. otherwise the -onexpand command
516# gets sNode as an argument and should create the node's children with
517# insert. This feature is necessary for huge trees (e.g. file
518# system trees) with too many nodes to be created a priori.
519metawidget proc Treecontrol expand { sNode {bAll 0} } {
520  # only do it if collapsed
521  if { [lindex [my aNodes($sNode)] 5] == -1 } {
522    # callback for children, if specified and node is not marked as final.
523    # delete existing children from last callback first, then do new callback.
524    if { [my -onexpand] != {} && ! [lindex [my aNodes($sNode)] 6] } {
525      foreach sChild [lindex [my aNodes($sNode)] 3] {
526        _delete $sChild
527      }
528
529      my aNodes($sNode) [lreplace [my aNodes($sNode)] 3 3 {}]
530      eval [my -onexpand] {$sNode}
531    }
532
533    # update expanded flag (expr maps 0 to 0 and !=0 to 1)
534    my aNodes($sNode) [lreplace [my aNodes($sNode)] 5 5 [expr !![llength [lindex [my aNodes($sNode)] 3]]]]
535
536    # if the node is currently visible, draw symbol and children
537    if { [_visible $sNode] } {
538      _setSymbol $sNode
539      _drawChildren $sNode
540    }
541  }
542
543  # eventually expand all children, too
544  if { $bAll } {
545    foreach sChild [lindex [my aNodes($sNode)] 3] {
546      expand $sChild 1
547    }
548  }
549}
550
551# name: collapse
552# args: sNode: node name
553#       bAll: true if recursive
554# collapses a node and eventually all children.
555metawidget proc Treecontrol collapse { sNode {bAll 0} } {
556  # do it only if expanded
557  if { [lindex [my aNodes($sNode)] 5] == 1 } {
558    # undraw the kids if node is currently visible
559    if { [_visible $sNode] } {
560      _undrawChildren $sNode
561    }
562
563    # update node properties (expr maps 0 to 0 and !=0 to -1)
564    my aNodes($sNode) [lreplace [my aNodes($sNode)] 5 5 [expr -1*(!![llength [lindex [my aNodes($sNode)] 3]])]]
565    _setSymbol $sNode
566  }
567
568  # eventually collapse all children, too
569  if { $bAll } {
570    foreach sChild [lindex [my aNodes($sNode)] 3] {
571      collapse $sChild 1
572    }
573  }
574}
575
576# name: insert
577# args: sNode: node name
578#       args: option-value pairs
579# inserts a new node. creates a record for the node properties that
580# would make the node a child of the root node. then processes the options.
581metawidget proc Treecontrol insert { sNode args } {
582  if { [myinfo exists aNodes($sNode)] } {
583    error "Treecontrol $this: Node $sNode already exists."
584  }
585
586  # if no callback specified, then the new node is a leaf by default.
587  # otherwise it *might* have children, therefore it's a collapsed node.
588  if { [my -onexpand] == {} } {
589    set iExpand 0
590  } else {
591    set iExpand -1
592  }
593
594  # create a property record for this node. fields are:
595  # text, image, parent, children, depth, expanded, final, tags, userdata
596  my aNodes($sNode) [list {} {} {} {} 0 $iExpand 0 {} {}]
597  my lNodes [linsert [my lNodes] end $sNode]
598
599  eval nodeconfigure {$sNode} -text {$sNode} -parent {{}} $args
600}
601
602# name: delete
603# args: sNode: node name
604# deletes a node logically and visually. if sNode is not specified, the
605# entire tree is deleted.
606metawidget proc Treecontrol delete { {sNode {}} } {
607  if { $sNode == {} } {
608    # shortcut for root node: delete direct children, clear text widget
609    foreach sChild [lindex [my aNodes()] 3] {
610      _delete $sChild
611    }
612
613    my aNodes() [lreplace [my aNodes()] 3 3 {}]
614    $this.text delete 2.0 end
615  } else {
616    _checkNode $sNode
617
618    # erase visual representation
619    if { [_visible $sNode] } {
620      _undrawChildren $sNode
621      _undrawNode $sNode
622    }
623
624    # update children list of parent
625    set sParent [lindex [my aNodes($sNode)] 2]
626    my aNodes($sParent) [lreplace [my aNodes($sParent)] 3 3 [mkw.lshrink [lindex [my aNodes($sParent)] 3] $sNode]]
627
628    # erase logical representation
629    _delete $sNode
630  }
631}
632
633# name: nodeconfigure
634# args: sNode: node name
635#       args: option-value pairs
636# configures a node. only allows the four special options. to modify color,
637# font etc., use tags.
638metawidget proc Treecontrol nodeconfigure { sNode args } {
639  _checkNode $sNode
640
641  mkw.options $args {-parent *} {-text *} {-image *} {-final *} {-user *}
642
643  # new node caption
644  if { [info exists -text] } {
645    my aNodes($sNode) [lreplace [my aNodes($sNode)] 0 0 ${-text}]
646
647    if { [_visible $sNode] } {
648      mkw.lassign [$this.text tag ranges $sNode] sI1 sI2
649      $this.text delete $sI1+1ch $sI2-1ch
650      $this.text insert $sI1+1ch ${-text}
651    }
652  }
653
654  # new image, or no image. detects existing image with _tImg tag.
655  if { [info exists -image] } {
656    my aNodes($sNode) [lreplace [my aNodes($sNode)] 1 1 ${-image}]
657
658    if { [_visible $sNode] } {
659      mkw.lassign [$this.text tag ranges $sNode] sI1 sI2
660      if { [lindex [$this.text tag names $sI1-1ch] 0] == "_tImg" } {
661        $this.text delete $sI1-1ch
662      }
663      if { ${-image} != {} } {
664        $this.text image create $sI1-1ch -image ${-image}
665        $this.text tag add _tImg $sI1-1ch
666      }
667    }
668  }
669
670  # reparent the node. erases visual stuff, then reorganizes logical
671  # structures, then repaints the visual again.
672  if { [info exists -parent] } {
673    _checkNode ${-parent}
674
675    # delete any visual representation
676    if { [_visible $sNode] } {
677      _undrawChildren $sNode
678      _undrawNode $sNode
679    }
680
681    # update children list of old and new parent
682    set sOldParent [lindex [my aNodes($sNode)] 2]
683    my aNodes($sOldParent) [lreplace [my aNodes($sOldParent)] 3 3 [mkw.lshrink [lindex [my aNodes($sOldParent)] 3] $sNode]]
684    my aNodes(${-parent})  [lreplace [my aNodes(${-parent})] 3 3 [mkw.lextend [lindex [my aNodes(${-parent})] 3] $sNode]]
685
686    # store new parent and recalculate indention
687    my aNodes($sNode) [lreplace [my aNodes($sNode)] 2 2 ${-parent}]
688    _recalcDepth $sNode
689
690    # if new parent was a leaf, it now has a child and is a collapsed node
691    if { [lindex [my aNodes(${-parent})] 5] == 0 } {
692      my aNodes(${-parent}) [lreplace [my aNodes(${-parent})] 5 5 -1]
693      _setSymbol ${-parent}
694    }
695
696    # if new parent is visible and expanded, draw its new child now
697    if { [_visible ${-parent}] && [lindex [my aNodes(${-parent})] 5] == 1 } {
698      _drawNode $sNode
699      _drawChildren $sNode
700    }
701
702    # if that was the old parent's last child, turn it into a leaf
703    if { ! [llength [lindex [my aNodes($sOldParent)] 3]] } {
704      my aNodes($sOldParent) [lreplace [my aNodes($sOldParent)] 5 5 0]
705      _setSymbol $sOldParent
706    }
707  }
708
709  # change final state.
710  if { [info exists -final] } {
711    set sFinal [mkw.complete ${-final} {yes true on 1 no false off 0}]
712    set bFinal [mkw.decode $sFinal {true 1 yes 1 on 1 1 1} 0]
713    my aNodes($sNode) [lreplace [my aNodes($sNode)] 6 6 $bFinal]
714
715    if { $bFinal && ! [llength [lindex [my aNodes($sNode)] 3]] } {
716      my aNodes($sNode) [lreplace [my aNodes($sNode)] 5 5 0]
717    } elseif { ! $bFinal && [lindex [my aNodes($sNode)] 5] == 0 } {
718      my aNodes($sNode) [lreplace [my aNodes($sNode)] 5 5 -1]
719    }
720
721    _setSymbol $sNode
722  }
723
724  # arbitrary user data. simply replace element in property list.
725  if { [info exists -user] } {
726    my aNodes($sNode) [lreplace [my aNodes($sNode)] 8 8 ${-user}]
727  }
728}
729
730# name: nodecget
731# args: sNode: node name
732#       args: option name
733# returns the value of an option, including some read-only options. most
734# of the options are taken from the node's property record. (note: a node
735# can be expanded but not visible, e.g. if the parent is collapsed.)
736metawidget proc Treecontrol nodecget { sNode args } {
737  _checkNode $sNode
738
739  set lOptions {-text -image -parent -children -indention -expanded -final -tags -user -visible}
740  set sOption [mkw.complete $args $lOptions]
741
742  if { $sOption == "-visible" } {
743    return [_visible $sNode]
744  } else {
745    return [lindex [my aNodes($sNode)] [lsearch $lOptions $sOption]]
746  }
747}
748
749# name: move
750# args: sNode: node name
751#       args: options and a position specifier
752# changes the siblings order and also reparents a node. moves sNode to a
753# position specified in args. args is normally an integer or "end",
754# specifying the position amongst sNode's siblings. if -byname is found,
755# args must contain a node name instead of an index. sNode will be moved
756# to that new location. with -after, it will be placed after the destination
757# node. if the latter belongs to a different parent, sNode is reparented.
758metawidget proc Treecontrol move { sNode args } {
759  _checkNode $sNode
760
761  # extract options. what's left is either an index or a node name
762  set args [mkw.options $args * {-byname} {-after}]
763  set iPos [lindex $args 0]
764
765  # if it's a node name, then reparent sNode first, if necessary
766  if { [info exists -byname] } {
767    set sDest $iPos
768    _checkNode $sDest
769
770    if { [lindex [my aNodes($sDest)] 2] != [lindex [my aNodes($sNode)] 2] } {
771      nodeconfigure $sNode -parent [lindex [my aNodes($sDest)] 2]
772    }
773  }
774
775  # get siblings without sNode itself
776  set sParent [lindex [my aNodes($sNode)] 2]
777  set lSiblings [mkw.lshrink [lindex [my aNodes($sParent)] 3] $sNode]
778
779  # transform destination node or "end" string into index.
780  if { [info exists -byname] } {
781          set iPos [lsearch $lSiblings $iPos]
782
783    if { [info exists -after] } {
784      incr iPos
785    }
786  } elseif { $iPos == "end" } {
787    set iPos [llength $lSiblings]
788  }
789
790  # erase visual stuff
791  if { [_visible $sNode] } {
792    _undrawChildren $sNode
793    _undrawNode $sNode
794  }
795
796  # update data structures
797  my aNodes($sParent) [lreplace [my aNodes($sParent)] 3 3 [linsert $lSiblings $iPos $sNode]]
798
799  # redraw node, if new parent is visible and expanded
800  if { [_visible $sParent] && [lindex [my aNodes($sParent)] 5] == 1 } {
801    _drawNode $sNode
802    _drawChildren $sNode
803  }
804}
805
806# name: tag
807# args: sAction: almost all commands allowed for tags
808#       sTag: tag name. must not be a column name
809#       args: depends on sAction
810# similar to the "text tag" command. some tag commands require special
811# treatment so the don't mix up the internal tags. tags cannot simply be
812# applied to the text widget because a node is not always visible. therefore
813# each node maintains a list with its tags. when the node is drawn later,
814# the tags are applied.
815metawidget proc Treecontrol tag { sAction sTag args } {
816  # node names and sel are reserved, can't be used as user tags.
817  if { [lsearch [linsert [my lNodes] end "sel"] $sTag] != -1 } {
818    error "Gridcontrol $this: Tag name $sTag not allowed"
819  }
820
821  set sAction [mkw.complete $sAction {add bind cget configure delete lower raise names nodes remove}]
822
823  switch $sAction {
824    add - remove {
825      # args is a list of nodes. adds or removes the tags from each node's
826      # tag list. then, if the node is visible, updates the visual portion.
827      set sCmd [mkw.decode $sAction {add mkw.lextend remove mkw.lshrink}]
828
829      foreach sNode $args {
830        _checkNode $sNode
831        set lTags [lindex [my aNodes($sNode)] 7]
832        my aNodes($sNode) [lreplace [my aNodes($sNode)] 7 7 [$sCmd $lTags $sTag]]
833
834        if { [_visible $sNode] } {
835          mkw.lassign [$this.text tag ranges $sNode] sI1 sI2
836          $this.text tag $sAction $sTag $sI1 $sI2
837        }
838      }
839    }
840
841    names {
842      # args is a node name. simply returns the node's tag list.
843      return [lindex [my aNodes([lindex $args 0])] 7]
844    }
845
846    nodes {
847      # args is irrelevant. checks for all nodes, if the given tag is part or
848      # their tag list. if so, appends the node to a list. returns the list.
849      set lNodes {}
850      foreach sNode [my lNodes] {
851        if { [lsearch [lindex [my aNodes($sNode)] 7] $sTag] == -1 } continue
852        lappend lNodes $sNode
853      }
854
855      return $lNodes
856    }
857
858    lower {
859      # the internal tags (those with the node 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 sNode [tag nodes $sTag] {
864        $this.text tag lower $sNode
865      }
866    }
867
868    default {
869      eval $this.text tag $sAction {$sTag} $args
870    }
871  }
872}
873
874# name: Listcontrol: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 nodes. set accepts
878# a list (in the first element of args) of node names.
879metawidget proc Treecontrol selection { sWhat args } {
880  switch [mkw.complete $sWhat {clear get set}] {
881    clear {
882      $this.text tag remove sel 1.0 end
883    }
884    get {
885      set lNodes {}
886
887      foreach { sI1 sI2 } [$this.text tag ranges sel] {
888        lappend lNodes [lindex [$this.text tag names $sI1] 0]
889      }
890
891      return $lNodes
892    }
893    set {
894      $this.text tag remove sel 1.0 end
895
896      foreach sNode [lindex $args 0] {
897        _checkNode $sNode
898        mkw.lassign [$this.text tag ranges $sNode] sI1 sI2
899        $this.text tag add sel $sI1 $sI2
900      }
901    }
902  }
903}
904
905# name: get
906# args: iFirst, iLast: row number, or "current", or "cursor"
907# returns the node or a range of nodes between the rows specified by
908# iFirst (and iLast). "current" denotes the mouse position,
909# "cursor" the position of the node that has the cursor.
910metawidget proc Treecontrol get { iFirst {iLast {}} } {
911  if { $iFirst == "current" } {
912    set iFirst [lindex [split [$this.text index current] .] 0]
913  } elseif { $iFirst == "cursor" } {
914    set iFirst [my iCursor]
915  }
916
917  if { $iLast == {} } {
918    set iLast $iFirst
919  } elseif { $iLast == "current" } {
920    set iLast [lindex [split [$this.text index current] .] 0]
921  } elseif { $iLast == "cursor" } {
922    set iLast [my iCursor]
923  }
924
925  set lNodes {}
926  set iN1 [expr ($iFirst<$iLast)?$iFirst:$iLast]
927  set iN2 [expr ($iFirst>$iLast)?$iFirst:$iLast]
928
929  for { set i $iN1 } { $i <= $iN2 } { incr i } {
930    lappend lNodes [lindex [$this.text tag names $i.end-1ch] 0]
931  }
932
933  return $lNodes
934}
935
936# name: see
937# args: sNode: node name
938# makes a node visible in the viewport of the text widget. expands all
939# parents first up to the root node, then makes the text widget
940# put the node's tag into the viewport.
941metawidget proc Treecontrol see { sNode } {
942  _checkNode $sNode
943
944  set sParent [lindex [my aNodes($sNode)] 2]
945  while { $sParent != {} } {
946    expand $sParent
947    set sParent [lindex [my aNodes($sParent)] 2]
948  }
949
950  $this.text see [lindex [$this.text tag ranges $sNode] 0]
951}
952
953# name: nodebind
954# args: sNode: node name
955#       args: as accepted by bind
956# allows for bindings to individual nodes.
957metawidget proc Treecontrol nodebind { sNode args } {
958  _checkNode $sNode
959  eval $this.text tag bind {$sNode} $args
960}
961
962# name: Gridcontrol:bind
963# args: args: as accepted by the bind command.
964# applies bindings to the main widget.
965metawidget proc Treecontrol bind_ { sWhat args } {
966  eval bind $this.text $args
967}
968
969# name: Gridcontrol:nodes
970# args: -
971# returns a list with all nodes, unordered.
972metawidget proc Treecontrol nodes {} {
973  return [my lNodes]
974}
975
976# name: tree
977# args: sNode: node name
978# returns the structure of the subtree specified by sNode as a recursive
979# list. each list element consists itself of two elements: the node and
980# its child nodes, and their child nodes, and their child nodes ...
981metawidget proc Treecontrol tree { {sNode {}} } {
982  _checkNode $sNode
983
984  set lChildren {}
985  foreach sChild [lindex [my aNodes($sNode)] 3] {
986    lappend lChildren [tree $sChild]
987  }
988
989  return [list $sNode $lChildren]
990}
991
992metawidget command Treecontrol _dump         _dump
993metawidget command Treecontrol _toggle       _toggle
994metawidget command Treecontrol _leftright    _leftright
995metawidget command Treecontrol _select       _select
996
997metawidget command Treecontrol expand        expand
998metawidget command Treecontrol collapse      collapse
999metawidget command Treecontrol insert        insert
1000metawidget command Treecontrol delete        delete
1001metawidget command Treecontrol nodeconfigure nodeconfigure
1002metawidget command Treecontrol nodecget      nodecget
1003metawidget command Treecontrol move          move
1004metawidget command Treecontrol tag           tag
1005metawidget command Treecontrol selection     selection
1006metawidget command Treecontrol get           get
1007metawidget command Treecontrol see           see
1008metawidget command Treecontrol nodebind      nodebind
1009metawidget command Treecontrol bind          bind_
1010metawidget command Treecontrol nodes         nodes
1011metawidget command Treecontrol tree          tree
1012
1013metawidget option  Treecontrol -text         -text
1014metawidget option  Treecontrol -image        -image
1015metawidget option  Treecontrol -fullexpand   -fullexpand
1016metawidget option  Treecontrol -selectmode   -selectmode
1017metawidget option  Treecontrol -onexpand
1018metawidget option  Treecontrol -onselect
1019
1020proc testGetNodes { sFolder } {
1021  puts "Require children for node $sFolder"
1022
1023  foreach sFile [glob -nocomplain $sFolder/*] {
1024    set sTail [file tail $sFile]
1025
1026    if { [file isfile $sFile] } {
1027      .tree insert $sFile -text $sTail -image pFi -parent $sFolder -final 1
1028    } else {
1029      .tree insert $sFile -text $sTail -image pFo -parent $sFolder
1030    }
1031  }
1032
1033  .tree nodeconf $sFolder -final 1
1034}
1035
1036proc testGetFirst { sPath } {
1037  .tree delete {}
1038  .tree config -text $sPath
1039
1040  foreach sFile [glob -nocomplain $sPath/*] {
1041    set sTail [file tail $sFile]
1042
1043    if { [file isfile $sFile] } {
1044      .tree insert $sFile -text $sTail -image pFi -final 1
1045    } else {
1046      .tree insert $sFile -text $sTail -image pFo
1047    }
1048  }
1049}
1050
1051proc test {} {
1052  image create photo pNo -file ./demos/images/Nodes.gif
1053  image create photo pFo -file ./demos/images/Folder.gif
1054  image create photo pFi -file ./demos/images/File.gif
1055
1056  pack [frame .ctrl] -side top -fill x
1057  pack [button .ctrl.brws -text "Browse" -command {testGetFirst $sPath}] -side right
1058  pack [entry  .ctrl.path -textvariable sPath] -side left -fill x -expand 1
1059  pack [treecontrol .tree -text "(Enter a path and press Browse)" -image pNo -onexpand testGetNodes -fullexpand 1] -side bottom -fill both -expand 1
1060}
1061
1062#test
1063
Note: See TracBrowser for help on using the repository browser.