1# Copyright (c) 2002-2011 Tim Baker
2
3bind TreeCtrl <Motion> {
4    TreeCtrl::CursorCheck %W %x %y
5    TreeCtrl::MotionInHeader %W %x %y
6    TreeCtrl::MotionInButtons %W %x %y
7}
8bind TreeCtrl <Leave> {
9    TreeCtrl::CursorCancel %W
10    TreeCtrl::MotionInHeader %W
11    TreeCtrl::MotionInButtons %W
12}
13bind TreeCtrl <ButtonPress-1> {
14    TreeCtrl::ButtonPress1 %W %x %y
15}
16bind TreeCtrl <Double-ButtonPress-1> {
17    TreeCtrl::DoubleButton1 %W %x %y
18}
19bind TreeCtrl <Button1-Motion> {
20    TreeCtrl::Motion1 %W %x %y
21}
22bind TreeCtrl <ButtonRelease-1> {
23    TreeCtrl::Release1 %W %x %y
24}
25bind TreeCtrl <Shift-ButtonPress-1> {
26    set TreeCtrl::Priv(buttonMode) normal
27    TreeCtrl::BeginExtend %W [%W item id {nearest %x %y}]
28}
29# Command-click should provide a discontinuous selection on OSX
30switch -- [tk windowingsystem] {
31    "aqua" { set modifier Command }
32    default { set modifier Control }
33}
34bind TreeCtrl <$modifier-ButtonPress-1> {
35    set TreeCtrl::Priv(buttonMode) normal
36    TreeCtrl::BeginToggle %W [%W item id {nearest %x %y}]
37}
38bind TreeCtrl <Button1-Leave> {
39    TreeCtrl::Leave1 %W %x %y
40}
41bind TreeCtrl <Button1-Enter> {
42    TreeCtrl::Enter1 %W %x %y
43}
44
45bind TreeCtrl <KeyPress-Up> {
46    TreeCtrl::SetActiveItem %W [TreeCtrl::UpDown %W active -1]
47}
48bind TreeCtrl <Shift-KeyPress-Up> {
49    TreeCtrl::Extend %W above
50}
51bind TreeCtrl <KeyPress-Down> {
52    TreeCtrl::SetActiveItem %W [TreeCtrl::UpDown %W active 1]
53}
54bind TreeCtrl <Shift-KeyPress-Down> {
55    TreeCtrl::Extend %W below
56}
57bind TreeCtrl <KeyPress-Left> {
58    if {![TreeCtrl::Has2DLayout %W]} {
59	%W item collapse [%W item id active]
60    } else {
61	TreeCtrl::SetActiveItem %W [TreeCtrl::LeftRight %W active -1]
62    }
63}
64bind TreeCtrl <Shift-KeyPress-Left> {
65    TreeCtrl::Extend %W left
66}
67bind TreeCtrl <Control-KeyPress-Left> {
68    %W xview scroll -1 pages
69}
70bind TreeCtrl <KeyPress-Right> {
71    if {![TreeCtrl::Has2DLayout %W]} {
72	%W item expand [%W item id active]
73    } else {
74	TreeCtrl::SetActiveItem %W [TreeCtrl::LeftRight %W active 1]
75    }
76}
77bind TreeCtrl <Shift-KeyPress-Right> {
78    TreeCtrl::Extend %W right
79}
80bind TreeCtrl <Control-KeyPress-Right> {
81    %W xview scroll 1 pages
82}
83bind TreeCtrl <KeyPress-Prior> {
84    %W yview scroll -1 pages
85    if {[%W item id {nearest 0 0}] ne ""} {
86	%W activate {nearest 0 0}
87    }
88}
89bind TreeCtrl <KeyPress-Next> {
90    %W yview scroll 1 pages
91    if {[%W item id {nearest 0 0}] ne ""} {
92	%W activate {nearest 0 0}
93    }
94}
95bind TreeCtrl <Control-KeyPress-Prior> {
96    %W xview scroll -1 pages
97}
98bind TreeCtrl <Control-KeyPress-Next> {
99    %W xview scroll 1 pages
100}
101bind TreeCtrl <KeyPress-Home> {
102    %W xview moveto 0
103}
104bind TreeCtrl <KeyPress-End> {
105    %W xview moveto 1
106}
107bind TreeCtrl <Control-KeyPress-Home> {
108    TreeCtrl::SetActiveItem %W [%W item id {first visible state enabled}]
109}
110bind TreeCtrl <Shift-Control-KeyPress-Home> {
111    TreeCtrl::DataExtend %W [%W item id {first visible state enabled}]
112}
113bind TreeCtrl <Control-KeyPress-End> {
114    TreeCtrl::SetActiveItem %W [%W item id {last visible state enabled}]
115}
116bind TreeCtrl <Shift-Control-KeyPress-End> {
117    TreeCtrl::DataExtend %W [%W item id {last visible state enabled}]
118}
119bind TreeCtrl <<Copy>> {
120    if {[string equal [selection own -displayof %W] "%W"]} {
121	clipboard clear -displayof %W
122	clipboard append -displayof %W [selection get -displayof %W]
123    }
124}
125bind TreeCtrl <KeyPress-space> {
126    TreeCtrl::BeginSelect %W [%W item id active]
127}
128bind TreeCtrl <KeyPress-Select> {
129    TreeCtrl::BeginSelect %W [%W item id active]
130}
131bind TreeCtrl <Control-Shift-KeyPress-space> {
132    TreeCtrl::BeginExtend %W [%W item id active]
133}
134bind TreeCtrl <Shift-KeyPress-Select> {
135    TreeCtrl::BeginExtend %W [%W item id active]
136}
137bind TreeCtrl <KeyPress-Escape> {
138    TreeCtrl::Cancel %W
139}
140bind TreeCtrl <Control-KeyPress-slash> {
141    TreeCtrl::SelectAll %W
142}
143bind TreeCtrl <Control-KeyPress-backslash> {
144    if {[string compare [%W cget -selectmode] "browse"]} {
145	%W selection clear
146    }
147}
148
149bind TreeCtrl <KeyPress-plus> {
150    %W item expand [%W item id active]
151}
152bind TreeCtrl <KeyPress-minus> {
153    %W item collapse [%W item id active]
154}
155bind TreeCtrl <KeyPress-Return> {
156    %W item toggle [%W item id active]
157}
158
159
160# Additional Tk bindings that aren't part of the Motif look and feel:
161
162bind TreeCtrl <ButtonPress-2> {
163    focus %W
164    TreeCtrl::ScanMark %W %x %y
165}
166bind TreeCtrl <Button2-Motion> {
167    TreeCtrl::ScanDrag %W %x %y
168}
169
170if {$tcl_platform(platform) eq "windows"} {
171    bind TreeCtrl <Control-ButtonPress-3> {
172	TreeCtrl::ScanMark %W %x %y
173    }
174    bind TreeCtrl <Control-Button3-Motion> {
175	TreeCtrl::ScanDrag %W %x %y
176    }
177}
178if {[string equal [tk windowingsystem] "aqua"]} {
179    # Middle mouse on Mac OSX
180    bind TreeCtrl <ButtonPress-3> {
181	TreeCtrl::ScanMark %W %x %y
182    }
183    bind TreeCtrl <Button3-Motion> {
184	TreeCtrl::ScanDrag %W %x %y
185    }
186}
187
188# MouseWheel
189if {[string equal "x11" [tk windowingsystem]]} {
190    # Support for mousewheels on Linux/Unix commonly comes through mapping
191    # the wheel to the extended buttons.  If you have a mousewheel, find
192    # Linux configuration info at:
193    #	http://www.inria.fr/koala/colas/mouse-wheel-scroll/
194    bind TreeCtrl <4> {
195	if {!$tk_strictMotif} {
196	    %W yview scroll -5 units
197	}
198    }
199    bind TreeCtrl <5> {
200	if {!$tk_strictMotif} {
201	    %W yview scroll 5 units
202	}
203    }
204} elseif {[string equal [tk windowingsystem] "aqua"]} {
205    bind TreeCtrl <MouseWheel> {
206	%W yview scroll [expr {- (%D)}] units
207    }
208} else {
209    bind TreeCtrl <MouseWheel> {
210	%W yview scroll [expr {- (%D / 120) * 4}] units
211    }
212}
213
214namespace eval ::TreeCtrl {
215    variable Priv
216    array set Priv {
217	prev {}
218    }
219
220    if {[info procs ::lassign] eq ""} {
221	proc lassign {values args} {
222	    uplevel 1 [list foreach $args [linsert $values end {}] break]
223	    lrange $values [llength $args] end
224	}
225    }
226}
227
228# Retrieve filelist bindings from this dir
229source [file join [file dirname [info script]] filelist-bindings.tcl]
230
231# ::TreeCtrl::ColumnCanResizeLeft --
232#
233# Return 1 if the given column should be resized by the left edge.
234#
235# Arguments:
236# w		The treectrl widget.
237# column	The column.
238
239proc ::TreeCtrl::ColumnCanResizeLeft {w column} {
240    if {[$w column cget $column -lock] eq "right"} {
241	return 1
242    }
243    return 0
244}
245
246# ::TreeCtrl::ColumnCanMoveHere --
247#
248# Return 1 if the given column can be moved before another.
249#
250# Arguments:
251# w		The treectrl widget.
252# column	The column.
253# before	The column to place 'column' before.
254
255proc ::TreeCtrl::ColumnCanMoveHere {w column before} {
256    if {[$w column compare $column == $before] ||
257	    ([$w column order $column] == [$w column order $before] - 1)} {
258	return 0
259    }
260    set lock [$w column cget $column -lock]
261    return [expr {[$w column compare $before >= "first lock $lock"] &&
262	[$w column compare $before <= "last lock $lock next"]}]
263}
264
265# ::TreeCtrl::ColumnDragFindBefore --
266#
267# This is called when dragging a column header. The result is 1 if the given
268# coordinates are near a column header before which the dragged column can
269# be moved.
270#
271# Arguments:
272# w		The treectrl widget.
273# x		Window x-coord.
274# y		Window y-coord.
275# dragColumn	The column being dragged.
276# indColumn_	Out: what to set -indicatorcolumn to.
277# indSide_	Out: what to set -indicatorside to.
278
279proc ::TreeCtrl::ColumnDragFindBefore {w x y dragColumn indColumn_ indSide_} {
280    upvar $indColumn_ indColumn
281    upvar $indSide_ indSide
282
283    set lock [$w column cget $dragColumn -lock]
284    scan [$w bbox header.$lock] "%d %d %d %d" minX y1 maxX y2
285    if {$x < $minX} {
286	set x $minX
287    }
288    if {$x >= $maxX} {
289	set x [expr {$maxX - 1}]
290    }
291    $w identify -array id $x $y
292    if {$id(where) ne "header"} {
293	return 0
294    }
295    set indColumn $id(column)
296    if {[$w column compare $indColumn == $dragColumn]} {
297	return 0
298    }
299
300    # The given $x is either the left edge or the right edge of the column
301    # header that is being dragged depending on which direction the user
302    # is dragging the column.
303    # When dragging to the left, the indicator column is chosen to be the
304    # leftmost column whose mid-way point is greater than the left edge of the
305    # dragged header.
306    # When dragging to the right, the indicator column is chosen to be the
307    # rightmost column whose mid-way point is less than the right edge of the
308    # dragged header.
309    if {[$w column compare $indColumn != "tail"]} {
310	variable Priv
311	scan [$w header bbox $Priv(header) $indColumn] "%d %d %d %d" x1 y1 x2 y2
312	# Hack - ignore canvaspadx
313	if {[$w column cget $indColumn -lock] eq "none" &&
314		[$w column compare $indColumn == "first visible lock none"]} {
315	    incr x1 [lindex [$w cget -canvaspadx] 0]
316	}
317	if {[$w column compare $dragColumn < $indColumn]} {
318	    if {$x < $x1 + ($x2 - $x1) / 2} {
319		set indColumn [$w column id "$indColumn prev visible"]
320		set indColumn [GetSpanStartColumn $w $Priv(header) $indColumn]
321	    }
322	} else {
323	    if {$x > $x1 + ($x2 - $x1) / 2} {
324		# Find the column at the start of the next visible span
325		set starts [GetSpanStarts $w $Priv(header)]
326		for {set i [$w column order $indColumn]} {true} {incr i} {
327		    if {[$w column compare [lindex $starts $i] > $indColumn]} break
328		}
329		set indColumn [lindex $starts $i]
330	    }
331	}
332    }
333
334    set before $indColumn
335    set prev [$w column id "$dragColumn prev visible"]
336    set next [$w column id "$dragColumn next visible"]
337    if {[$w column compare $indColumn == "tail"]} {
338	set indSide left
339	set indColumn [$w column id "last lock none visible"]
340	set indSide right
341    } elseif {$prev ne "" && [$w column compare $prev == $indColumn]} {
342	set indSide left
343    } elseif {$next ne "" && [$w column compare $next == $indColumn]} {
344	set before [$w column id "$indColumn next visible"]
345	set indSide right
346    } else {
347	scan [$w column bbox $indColumn] "%d %d %d %d" x1 y1 x2 y2
348	if {$x < $x1 + ($x2 - $x1) / 2} {
349	    set indSide left
350	} else {
351	    set before [$w column id "$indColumn next visible"]
352	    set indSide right
353	}
354    }
355    if {$before eq "" || [$w column compare $before > "last lock $lock next"]} {
356	set before [$w column id "last lock $lock next"]
357    }
358    return [ColumnCanMoveHere $w $dragColumn $before]
359}
360
361# ::TreeCtrl::ListElementWindows --
362#
363# Return a list of Tk windows in window elements in a column header.
364#
365# Arguments:
366# T		The treectrl widget.
367# H		Header id
368# C		Column id
369
370proc ::TreeCtrl::ListElementWindows {T H C} {
371    set S [$T header style set $H $C]
372    if {$S eq ""} return
373    set result {}
374    foreach E [$T header style elements $H $C] {
375	if {[$T element type $E] eq "window"} {
376	    set window [$T header element cget $H $C $E -window]
377	    if {$window ne ""} {
378		lappend result $window
379	    }
380	}
381    }
382    return $result
383}
384
385# ::TreeCtrl::ColumnDragRestackWindows --
386#
387# Restack windows in window elements so that windows in dragged headers
388# are above all other windows in undragged headers.
389#
390# Arguments:
391# T		The treectrl widget.
392
393proc ::TreeCtrl::ColumnDragRestackWindows {T} {
394    variable Priv
395    set C [$T header dragcget -imagecolumn]
396    set lock [$T column cget $C -lock]
397    set span [$T header dragcget -imagespan]
398    set last [$T column id [list $C span $span]]
399    set dragged [$T column id [list range $C $last]]
400    foreach H [$T header id all] {
401	set prev ""
402	set lowest ""
403	foreach C $dragged {
404	    foreach win [ListElementWindows $T $H $C] {
405		if {$prev eq ""} {
406		    set lowest $win
407		} else {
408		    raise $win $prev
409		}
410		set prev $win
411	    }
412	}
413	if {$lowest eq ""} continue
414	foreach C [$T column id "lock $lock !tail"] {
415	    if {[lsearch -exact $dragged $C] != -1} continue
416	    foreach win [ListElementWindows $T $H $C] {
417		lower $win $lowest
418	    }
419	}
420    }
421    return
422}
423
424# ::TreeCtrl::CursorAction --
425#
426# If the given point is at the left or right edge of a resizable column
427# header, the result is "action header-resize header H column C".
428# If the given point is in a header with -button=TRUE, the result is
429# "action header-button header H column C".
430#
431# Arguments:
432# w		The treectrl widget.
433# x		Window coord of pointer.
434# y		Window coord of pointer.
435
436proc ::TreeCtrl::CursorAction {w x y var_} {
437    upvar $var_ var
438    variable Priv
439    $w identify -array id $x $y
440
441    set var(action) ""
442    if {$id(where) eq "header"} {
443	set var(header) $id(header)
444	set column $id(column)
445	set side $id(side)
446	if {$side eq ""} {
447	    if {[scan [$w bbox header.left] "%d %d %d %d" x1 y1 x2 y2] == 4} {
448		if {$x < $x2 + 4 && $x >= $x2} {
449		    set column [$w column id "last visible lock left"]
450		    set side right
451		}
452	    }
453	    if {[scan [$w bbox header.right] "%d %d %d %d" x1 y1 x2 y2] == 4} {
454		if {$x >= $x1 - 4 && $x < $x1} {
455		    set column [$w column id "first visible lock right"]
456		    set side left
457		}
458	    }
459	}
460	if {$side eq "left"} {
461	    if {[ColumnCanResizeLeft $w $column]} {
462		if {[$w column cget $column -resize]} {
463		    array set var [list action "header-resize" column $column]
464		    return
465		}
466	    } else {
467		# Resize the previous column
468		if {[$w column compare $column == tail]} {
469		    set prev [$w column id "last visible lock none"]
470		    if {$prev eq ""} {
471			set prev [$w column id "last visible lock left"]
472		    }
473		} else {
474		    set prev [$w column id "$column prev visible"]
475		}
476		if {$prev ne "" && [$w column cget $prev -resize]} {
477		    array set var [list action "header-resize" column $prev]
478		    return
479		}
480	    }
481	} elseif {$side eq "right"} {
482	    # Get the last visible column in the span
483	    set span [$w header span $id(header) $column]
484	    set last [$w column id "$column span $span"]
485	    set columns [$w column id [list range $column $last visible]]
486	    set column2 [lindex $columns end]
487	    if {[ColumnCanResizeLeft $w $column2]} {
488		# Resize the next column
489		set next [$w column id "$column2 next visible !tail"]
490		if {$next ne "" && [$w column cget $next -resize]} {
491		    array set var [list action "header-resize" column $next]
492		    return
493		}
494	    } else {
495		if {[$w column cget $column2 -resize]} {
496		    array set var [list action "header-resize" column $column2]
497		    return
498		}
499	    }
500	}
501	if {[$w column compare $column == "tail"]} {
502	    # Can't -resize or -button the tail column
503	} elseif {[$w header cget $id(header) $column -button]} {
504	    array set var [list action "header-button" column $column]
505	    return
506	}
507    }
508    return
509}
510
511# ::TreeCtrl::CursorCheck --
512#
513# Sees if the given pointer coordinates are near the edge of a resizable
514# column in the header. If so and the treectrl's cursor is not already
515# set to sb_h_double_arrow, then the current cursor is saved and changed
516# to sb_h_double_arrow, and an [after] callback to CursorCheckAux is
517# scheduled.
518#
519# Arguments:
520# w		The treectrl widget.
521# x		Window coord of pointer.
522# y		Window coord of pointer.
523
524proc ::TreeCtrl::CursorCheck {w x y} {
525    variable Priv
526    CursorAction $w $x $y action
527    # If we are in the middle of resizing a column, don't cancel the cursor
528    if {[info exists Priv(buttonMode)] && $Priv(buttonMode) eq "resize"} {
529	array set action {action "header-resize" header XXX column XXX}
530    }
531    if {$action(action) ne "header-resize"} {
532	CursorCancel $w
533	return
534    }
535    set cursor sb_h_double_arrow
536    if {$cursor ne [$w cget -cursor]} {
537	if {![info exists Priv(cursor,$w)]} {
538	    set Priv(cursor,$w) [$w cget -cursor]
539	}
540	$w configure -cursor $cursor
541    }
542    if {[info exists Priv(cursor,afterId,$w)]} {
543	after cancel $Priv(cursor,afterId,$w)
544    }
545    set Priv(cursor,afterId,$w) [after 150 [list TreeCtrl::CursorCheckAux $w]]
546    return
547}
548
549# ::TreeCtrl::CursorCheckAux --
550#
551# Get's the location of the pointer and calls CursorCheck if the treectrl's
552# cursor was previously set to sb_h_double_arrow.
553#
554# Arguments:
555# w		The treectrl widget.
556
557proc ::TreeCtrl::CursorCheckAux {w} {
558    variable Priv
559    if {![winfo exists $w]} return
560    set x [winfo pointerx $w]
561    set y [winfo pointery $w]
562    if {[info exists Priv(cursor,$w)]} {
563	set x [expr {$x - [winfo rootx $w]}]
564	set y [expr {$y - [winfo rooty $w]}]
565	CursorCheck $w $x $y
566    }
567    return
568}
569
570# ::TreeCtrl::CursorCancel --
571#
572# Restores the treectrl's cursor if it was changed to sb_h_double_arrow.
573# Cancels any pending [after] callback to CursorCheckAux.
574#
575# Arguments:
576# w		The treectrl widget.
577
578proc ::TreeCtrl::CursorCancel {w} {
579    variable Priv
580    if {[info exists Priv(cursor,$w)]} {
581	$w configure -cursor $Priv(cursor,$w)
582	unset Priv(cursor,$w)
583    }
584    if {[info exists Priv(cursor,afterId,$w)]} {
585	after cancel $Priv(cursor,afterId,$w)
586	unset Priv(cursor,afterId,$w)
587    }
588    return
589}
590
591# ::TreeCtrl::GetSpanStarts --
592#
593# This procedure returns a list of column ids, one per tree column.
594# Each column id indicates the column at the start of a span.
595#
596# Arguments:
597# T		The treectrl widget.
598# H		Header id
599
600proc ::TreeCtrl::GetSpanStarts {T H} {
601    set columns [list]
602    set spans [$T header span $H]
603    if {[lindex [lsort -integer $spans] end] eq 1} {
604	return [$T column list]
605    }
606    for {set index 0} {$index < [$T column count]} {}  {
607	set Cspan [$T column id "order $index"]
608	set span [lindex $spans $index]
609	if {![$T column cget $Cspan -visible]} {
610	    set span 1
611	}
612	while {$span > 0 && $index < [$T column count]} {
613	    if {[$T column cget "order $index" -lock] ne [$T column cget $Cspan -lock]} break
614	    lappend columns $Cspan
615	    incr span -1
616	    incr index
617	}
618    }
619    return $columns
620}
621
622# ::TreeCtrl::GetSpanStartColumn --
623#
624# This procedure returns the column at the start of a span which covers the
625# given column.
626#
627# Arguments:
628# T		The treectrl widget.
629# H		Header id
630# C		Column id
631
632proc ::TreeCtrl::GetSpanStartColumn {T H C} {
633    set columns [GetSpanStarts $T $H]
634    return [lindex $columns [$T column order $C]]
635}
636
637# ::TreeCtrl::SetHeaderState --
638#
639# This procedure sets the state of a header-column and remembers that
640# header-column.  If a different header-column is passed later the previous
641# header-column's state is set to 'normal'.
642#
643# Arguments:
644# T		The treectrl widget.
645# H		Header id
646# C		Column id
647# state		active|normal|pressed
648
649proc ::TreeCtrl::SetHeaderState {T H C state} {
650    variable Priv
651    if {[info exists Priv(inheader,$T)]} {
652	lassign $Priv(inheader,$T) Hprev Cprev
653    } else {
654	if {$H eq "" || $C eq ""} return
655	set Hprev [set Cprev ""]
656    }
657    if {$H ne $Hprev || $C ne $Cprev} {
658	if {$Hprev ne "" && [$T header id $Hprev] ne ""} {
659	    if {$Cprev ne "" && [$T column id $Cprev] ne ""} {
660		$T header configure $Hprev $Cprev -state normal
661		TryEvent $T Header state [list H $Hprev C $Cprev s normal]
662	    }
663	}
664    }
665    if {$H eq "" || $C eq ""} {
666	unset Priv(inheader,$T)
667    } else {
668	$T header configure $H $C -state $state
669	TryEvent $T Header state [list H $H C $C s $state]
670	set Priv(inheader,$T) [list $H $C]
671    }
672    return
673}
674
675# ::TreeCtrl::ClearHeaderState --
676#
677# If a header-column's state was previously set via SetHeaderState then
678# that column's state is set to normal and the header-column is forgotten.
679#
680# Arguments:
681# T		The treectrl widget.
682# H		Header id
683# C		Column id
684# state		active|normal|pressed
685
686proc ::TreeCtrl::ClearHeaderState {T} {
687    SetHeaderState $T "" "" ""
688    return
689}
690
691# ::TreeCtrl::MotionInHeader --
692#
693# This procedure updates the active/normal states of column headers as the
694# mouse pointer moves in and out of them. Typically this results in visual
695# feedback by changing the appearance of the headers.
696#
697# Arguments:
698# w		The treectrl widget.
699# args		x y coords if the pointer is in the window, or an empty list.
700
701proc ::TreeCtrl::MotionInHeader {w args} {
702    variable Priv
703    if {[llength $args]} {
704	set x [lindex $args 0]
705	set y [lindex $args 1]
706	CursorAction $w $x $y action
707    } else {
708	array set action {action ""}
709    }
710    if {[info exists Priv(inheader,$w)]} {
711	lassign $Priv(inheader,$w) headerPrev columnPrev
712    } else {
713	set headerPrev [set columnPrev ""]
714    }
715    set header ""
716    set column ""
717    if {$action(action) eq "header-button"} {
718	set header $action(header)
719	set column $action(column)
720    } elseif {$action(action) eq "header-resize"} {
721	set header $action(header)
722	set column [GetSpanStartColumn $w $header $action(column)]
723    }
724    if {$header ne $headerPrev || $column ne $columnPrev} {
725	if {$column ne ""} {
726	    SetHeaderState $w $header $column active
727	} else {
728	    ClearHeaderState $w
729	}
730    }
731    return
732}
733
734# ::TreeCtrl::MotionInButtons --
735#
736# This procedure updates the active/normal states of item buttons.
737# Typically this results in visual feedback by changing the appearance
738# of the buttons.
739#
740# Arguments:
741# T		The treectrl widget.
742# args		x y coords if the pointer is in the window, or an empty list.
743
744proc ::TreeCtrl::MotionInButtons {T args} {
745    variable Priv
746    set button ""
747    if {[llength $args]} {
748	set x [lindex $args 0]
749	set y [lindex $args 1]
750	$T identify -array id $x $y
751	if {$id(where) eq "item" && $id(button)} {
752	    set button $id(item)
753	}
754    }
755    if {[info exists Priv(inbutton,$T)]} {
756	set prevButton $Priv(inbutton,$T)
757    } else {
758	set prevButton ""
759    }
760    if {$button ne $prevButton} {
761	if {$prevButton ne ""} {
762	    if {[$T item id $prevButton] ne ""} {
763		$T item buttonstate $prevButton normal
764	    }
765	}
766	if {$button ne ""} {
767	    $T item buttonstate $button active
768	    set Priv(inbutton,$T) $button
769	} else {
770	    unset Priv(inbutton,$T)
771	}
772    }
773    if {[$T notify bind TreeCtrlButtonNotifyScroll] eq ""} {
774	$T notify bind TreeCtrlButtonNotifyScroll <Scroll> {
775	    TreeCtrl::ButtonNotifyScroll %T
776	}
777    }
778    return
779}
780
781# ::TreeCtrl::ButtonNotifyScroll --
782#
783# Called when a <Scroll> event occurs and a button is in the active state.
784# Finds the mouse pointer coords and calls MotionInButtons to update the
785# state of affected buttons.
786#
787# Arguments:
788# T		The treectrl widget.
789
790proc ::TreeCtrl::ButtonNotifyScroll {T} {
791    set x [expr {[winfo pointerx $T] - [winfo rootx $T]}]
792    set y [expr {[winfo pointery $T] - [winfo rooty $T]}]
793    MotionInButtons $T $x $y
794    return
795}
796
797# ::TreeCtrl::ButtonPress1 --
798#
799# Handle <ButtonPress-1> event.
800#
801# Arguments:
802# w		The treectrl widget.
803# x		Window x coord.
804# y		Window y coord.
805
806proc ::TreeCtrl::ButtonPress1 {w x y} {
807    variable Priv
808    focus $w
809
810    $w identify -array id $x $y
811    if {$id(where) eq ""} {
812	return
813    }
814
815    if {$id(where) eq "item"} {
816	set item $id(item)
817	if {$id(button)} {
818	    if {[$w cget -buttontracking]} {
819		$w item buttonstate $item pressed
820		set Priv(buttonMode) buttonTracking
821		set Priv(buttontrack,item) $item
822	    } else {
823		$w item toggle $item -animate
824	    }
825	    return
826	} elseif {$id(line) ne ""} {
827	    $w item toggle $id(line)
828	    return
829	}
830    }
831    set Priv(buttonMode) ""
832    if {$id(where) eq "header"} {
833	CursorAction $w $x $y action
834	if {$action(action) eq "header-resize"} {
835	    set column $action(column)
836	    set Priv(buttonMode) resize
837	    set Priv(header) $action(header)
838	    set Priv(column) $column
839	    set Priv(x) $x
840	    set Priv(y) $y
841	    set Priv(width) [$w column width $column]
842	    return
843	}
844	set column $id(column)
845	if {$action(action) eq "header-button"} {
846	    set Priv(buttonMode) header
847	    SetHeaderState $w $action(header) $column pressed
848	} else {
849	    if {[$w column compare $column == "tail"]} return
850	    if {![$w header dragcget -enable]} return
851	    if {![$w header dragcget $action(header) -enable]} return
852	    set Priv(buttonMode) dragColumnWait
853	}
854	set Priv(header) $action(header)
855	set Priv(column) $column
856	set Priv(columnDrag,x) $x
857	set Priv(columnDrag,y) $y
858	return
859    }
860    set item $id(item)
861    if {![$w item enabled $item]} {
862	return
863    }
864
865    # If the initial mouse-click is in a locked column, restrict scrolling
866    # to the vertical.
867    set count [scan [$w contentbox] "%d %d %d %d" x1 y1 x2 y2]
868    if {$count != -1 && $x >= $x1 && $x < $x2} {
869	set Priv(autoscan,direction,$w) xy
870    } else {
871	set Priv(autoscan,direction,$w) y
872    }
873
874    set Priv(buttonMode) normal
875    BeginSelect $w $item
876    return
877}
878
879# ::TreeCtrl::DoubleButtonPress1 --
880#
881# Handle <Double-ButtonPress-1> event.
882#
883# Arguments:
884# w		The treectrl widget.
885# x		Window x coord.
886# y		Window y coord.
887
888proc ::TreeCtrl::DoubleButton1 {w x y} {
889
890    $w identify -array id $x $y
891    if {$id(where) eq ""} {
892	return
893    }
894    if {$id(where) eq "item"} {
895	if {$id(button)} {
896	    if {[$w cget -buttontracking]} {
897		# There is no <ButtonRelease> so just toggle it
898		$w item toggle $id(item) -animate
899	    } else {
900		$w item toggle $id(item) -animate
901	    }
902	    return
903	} elseif {$id(line) ne ""} {
904	    $w item toggle $id(line)
905	    return
906	}
907    }
908    if {$id(where) eq "header"} {
909	CursorAction $w $x $y action
910	# Double-click between columns to set default column width
911	if {$action(action) eq "header-resize"} {
912	    set column $action(column)
913	    $w column configure $column -width ""
914	    CursorCheck $w $x $y
915	    MotionInHeader $w $x $y
916	} else {
917	    ButtonPress1 $w $x $y
918	}
919    }
920    return
921}
922
923# ::TreeCtrl::Motion1 --
924#
925# Handle <Button1-Motion> event.
926#
927# Arguments:
928# w		The treectrl widget.
929# x		Window x coord.
930# y		Window y coord.
931
932proc ::TreeCtrl::Motion1 {w x y} {
933    variable Priv
934    if {![info exists Priv(buttonMode)]} return
935    switch $Priv(buttonMode) {
936	header {
937	    $w identify -array id $x $y
938	    if {$id(where) ne "header" ||
939		    $id(header) ne $Priv(header) ||
940		    $id(column) ne $Priv(column)} {
941		if {[$w header cget $Priv(header) $Priv(column) -state] eq "pressed"} {
942		    ClearHeaderState $w
943		}
944	    } else {
945		if {[$w header cget $Priv(header) $Priv(column) -state] ne "pressed"} {
946		    SetHeaderState $w $Priv(header) $Priv(column) pressed
947		}
948		if {[$w header dragcget -enable] &&
949		    [$w header dragcget $Priv(header) -enable] &&
950		    (abs($Priv(columnDrag,x) - $x) > 4)} {
951		    set Priv(columnDrag,x) $x
952		    $w header dragconfigure \
953			-imagecolumn $Priv(column) \
954			-imageoffset [expr {$x - $Priv(columnDrag,x)}] \
955			-imagespan [$w header span $Priv(header) $Priv(column)]
956		    ColumnDragRestackWindows $w
957		    set Priv(buttonMode) dragColumn
958		    TryEvent $w ColumnDrag begin [list H $Priv(header) C $Priv(column)]
959		    # Allow binding scripts to cancel the drag
960		    if {[$w header dragcget -imagecolumn] eq ""} {
961			set Priv(buttonMode) header
962		    }
963		}
964	    }
965	}
966	buttonTracking {
967	    $w identify -array id $x $y
968	    set itemTrack $Priv(buttontrack,item)
969	    set exists [expr {[$w item id $itemTrack] ne ""}]
970	    set mouseover 0
971	    if {$id(where) eq "item" && $id(button)} {
972		if {$exists && [$w item compare $itemTrack == $id(item)]} {
973		    set mouseover 1
974		}
975	    }
976	    if {$mouseover} {
977		$w item buttonstate $itemTrack pressed
978	    } elseif {$exists} {
979		$w item buttonstate $itemTrack normal
980	    }
981	}
982	dragColumnWait {
983	    if {(abs($Priv(columnDrag,x) - $x) > 4)} {
984		set Priv(columnDrag,x) $x
985		$w header dragconfigure \
986		    -imagecolumn $Priv(column) \
987		    -imageoffset [expr {$x - $Priv(columnDrag,x)}] \
988		    -imagespan [$w header span $Priv(header) $Priv(column)]
989		ColumnDragRestackWindows $w
990		set Priv(buttonMode) dragColumn
991		TryEvent $w ColumnDrag begin [list H $Priv(header) C $Priv(column)]
992		# Allow binding scripts to cancel the drag
993		if {[$w header dragcget -imagecolumn] eq ""} {
994		    unset Priv(buttonMode)
995		}
996	    }
997	}
998	dragColumn {
999	    scan [$w bbox header] "%d %d %d %d" x1 y1 x2 y2
1000	    if {$y < $y1 - 30 || $y >= $y2 + 30} {
1001		set inside 0
1002	    } else {
1003		set inside 1
1004	    }
1005	    if {$inside && ([$w header dragcget -imagecolumn] eq "")} {
1006		$w header dragconfigure -imagecolumn $Priv(column)
1007	    } elseif {!$inside && ([$w header dragcget -imagecolumn] ne "")} {
1008		$w header dragconfigure -imagecolumn "" -indicatorcolumn ""
1009	    }
1010	    if {$inside} {
1011		set offset [expr {$x - $Priv(columnDrag,x)}]
1012		$w header dragconfigure -imageoffset $offset
1013
1014		# When dragging to the left, use the left edge of the dragged
1015		# header to choose the -indicatorcolumn.  When dragging to the
1016		# right, use the right edge.
1017		scan [$w header bbox $Priv(header) $Priv(column)] "%d %d %d %d" x1 y1 x2 y2
1018		if {$offset > 0} {
1019		    set xEdge [expr {$offset + $x2}]
1020		} else {
1021		    set xEdge [expr {$offset + $x1}]
1022		}
1023
1024		if {[ColumnDragFindBefore $w $xEdge $Priv(columnDrag,y) $Priv(column) indColumn indSide]} {
1025		    set prevIndColumn [$w header dragcget -indicatorcolumn]
1026		    $w header dragconfigure \
1027			-indicatorcolumn $indColumn \
1028			-indicatorside $indSide \
1029			-indicatorspan [$w header span $Priv(header) $indColumn]
1030		    if {$indColumn != $prevIndColumn} {
1031			TryEvent $w ColumnDrag indicator [list H $Priv(header) C $indColumn]
1032		    }
1033		} else {
1034		    $w header dragconfigure -indicatorcolumn ""
1035		}
1036	    }
1037	    if {[$w column cget $Priv(column) -lock] eq "none"} {
1038		ColumnDragScrollCheck $w $x $y
1039	    }
1040	}
1041	normal {
1042	    set Priv(x) $x
1043	    set Priv(y) $y
1044	    SelectionMotion $w [$w item id [list nearest $x $y]]
1045	    set Priv(autoscan,command,$w) {SelectionMotion %T [%T item id "nearest %x %y"]}
1046	    AutoScanCheck $w $x $y
1047	}
1048	resize {
1049	    if {[ColumnCanResizeLeft $w $Priv(column)]} {
1050		set width [expr {$Priv(width) + $Priv(x) - $x}]
1051	    } else {
1052		set width [expr {$Priv(width) + $x - $Priv(x)}]
1053	    }
1054	    set minWidth [$w column cget $Priv(column) -minwidth]
1055	    set maxWidth [$w column cget $Priv(column) -maxwidth]
1056	    if {$minWidth eq ""} {
1057		set minWidth 0
1058	    }
1059	    if {$width < $minWidth} {
1060		set width $minWidth
1061	    }
1062	    if {($maxWidth ne "") && ($width > $maxWidth)} {
1063		set width $maxWidth
1064	    }
1065	    if {$width == 0} {
1066		incr width
1067	    }
1068	    switch -- [$w cget -columnresizemode] {
1069		proxy {
1070		    scan [$w column bbox $Priv(column)] "%d %d %d %d" x1 y1 x2 y2
1071		    if {[ColumnCanResizeLeft $w $Priv(column)]} {
1072			# Use "ne" because -columnproxy could be ""
1073			if {$x2 - $width ne [$w cget -columnproxy]} {
1074			    $w configure -columnproxy [expr {$x2 - $width}]
1075			}
1076		    } else {
1077			if {($x1 + $width - 1) ne [$w cget -columnproxy]} {
1078			    $w configure -columnproxy [expr {$x1 + $width - 1}]
1079			}
1080		    }
1081		}
1082		realtime {
1083		    if {[$w column cget $Priv(column) -width] != $width} {
1084			$w column configure $Priv(column) -width $width
1085		    }
1086		}
1087	    }
1088	}
1089    }
1090    return
1091}
1092
1093# ::TreeCtrl::Leave1 --
1094#
1095# Handle <Button1-Leave> event.
1096#
1097# Arguments:
1098# w		The treectrl widget.
1099# x		Window x coord.
1100# y		Window y coord.
1101
1102proc ::TreeCtrl::Leave1 {w x y} {
1103    variable Priv
1104    if {![info exists Priv(buttonMode)]} return
1105    switch $Priv(buttonMode) {
1106	header {
1107	    if {[$w header cget $Priv(header) $Priv(column) -state] eq "pressed"} {
1108		ClearHeaderState $w
1109	    }
1110	}
1111    }
1112    return
1113}
1114
1115# ::TreeCtrl::Enter1 --
1116#
1117# Handle <Button1-Enter> event.
1118#
1119# Arguments:
1120# w		The treectrl widget.
1121# x		Window x coord.
1122# y		Window y coord.
1123
1124proc ::TreeCtrl::Enter1 {w x y} {
1125    variable Priv
1126    if {![info exists Priv(buttonMode)]} return
1127    switch $Priv(buttonMode) {
1128	default {}
1129    }
1130    return
1131}
1132
1133# ::TreeCtrl::Release1 --
1134#
1135# Handle <ButtonRelease-1> event.
1136#
1137# Arguments:
1138# w		The treectrl widget.
1139# x		Window x coord.
1140# y		Window y coord.
1141
1142proc ::TreeCtrl::Release1 {w x y} {
1143    variable Priv
1144    if {![info exists Priv(buttonMode)]} return
1145    switch $Priv(buttonMode) {
1146	header {
1147	    if {[$w header cget $Priv(header) $Priv(column) -state] eq "pressed"} {
1148		SetHeaderState $w $Priv(header) $Priv(column) active
1149		TryEvent $w Header invoke [list H $Priv(header) C $Priv(column)]
1150	    }
1151	    CursorCheck $w $x $y
1152	    MotionInHeader $w $x $y
1153	}
1154	buttonTracking {
1155	    $w identify -array id $x $y
1156	    set itemTrack $Priv(buttontrack,item)
1157	    set exists [expr {[$w item id $itemTrack] ne ""}]
1158	    if {$id(where) eq "item" && $id(button)} {
1159		if {$exists && [$w item compare $itemTrack == $id(item)]} {
1160		    $w item buttonstate $id(item) active
1161		    $w item toggle $itemTrack -animate
1162		}
1163	    }
1164	}
1165	dragColumn {
1166	    AutoScanCancel $w
1167	    ClearHeaderState $w
1168	    if {[$w header dragcget -imagecolumn] ne ""} {
1169		set visible 1
1170	    } else {
1171		set visible 0
1172	    }
1173	    set column [$w header dragcget -indicatorcolumn]
1174	    $w header dragconfigure -imagecolumn "" -indicatorcolumn ""
1175	    if {$visible && ($column ne "")} {
1176		# If dragging to the right, drop after the last column in the
1177		# span of the indicator column.
1178		if {[$w column order $Priv(column)] < [$w column order $column]} {
1179		    set span [$w header dragcget -indicatorspan]
1180		    set column [$w column id "$column span $span next"]
1181		}
1182		set lock [$w column cget $Priv(column) -lock]
1183		if {$column eq "" || [$w column compare $column > "last lock $lock next"]} {
1184		    set column [$w column id "last lock $lock next"]
1185		}
1186		TryEvent $w ColumnDrag receive [list H $Priv(header) C $Priv(column) b $column]
1187	    }
1188	    CursorCheck $w $x $y
1189	    MotionInHeader $w $x $y
1190	    TryEvent $w ColumnDrag end [list H $Priv(header) C $Priv(column)]
1191	}
1192	normal {
1193	    AutoScanCancel $w
1194	    set nearest [$w item id [list nearest $x $y]]
1195	    if {$nearest ne ""} {
1196		$w activate $nearest
1197	    }
1198set Priv(prev) ""
1199	}
1200	resize {
1201	    if {[$w cget -columnproxy] ne ""} {
1202		scan [$w column bbox $Priv(column)] "%d %d %d %d" x1 y1 x2 y2
1203		if {[ColumnCanResizeLeft $w $Priv(column)]} {
1204		    set width [expr {$x2 - [$w cget -columnproxy]}]
1205		} else {
1206		    set width [expr {[$w cget -columnproxy] - $x1 + 1}]
1207		}
1208		$w configure -columnproxy {}
1209		$w column configure $Priv(column) -width $width
1210	    }
1211	    # Clear buttonMode early so CursorCheck doesn't exit
1212	    unset Priv(buttonMode)
1213	    CursorCheck $w $x $y
1214	    MotionInHeader $w $x $y
1215	    return
1216	}
1217    }
1218    unset Priv(buttonMode)
1219    return
1220}
1221
1222# ::TreeCtrl::BeginSelect --
1223#
1224# This procedure is typically invoked on button-1 presses.  It begins
1225# the process of making a selection in the treectrl.  Its exact behavior
1226# depends on the selection mode currently in effect for the treectrl.
1227#
1228# Arguments:
1229# w		The treectrl widget.
1230# item		The item for the selection operation (typically the
1231#		one under the pointer).
1232
1233proc ::TreeCtrl::BeginSelect {w item} {
1234    variable Priv
1235    if {$item eq ""} return
1236    if {[string equal [$w cget -selectmode] "multiple"]} {
1237	if {[$w selection includes $item]} {
1238	    $w selection clear $item
1239	} else {
1240	    $w selection add $item
1241	}
1242    } else {
1243	$w selection anchor $item
1244	$w selection modify $item all
1245	set Priv(selection) {}
1246	set Priv(prev) $item
1247    }
1248    return
1249}
1250
1251# ::TreeCtrl::SelectionMotion --
1252#
1253# This procedure is called to process mouse motion events while
1254# button 1 is down.  It may move or extend the selection, depending
1255# on the treectrl's selection mode.
1256#
1257# Arguments:
1258# w		The treectrl widget.
1259# item-		The item under the pointer.
1260
1261proc ::TreeCtrl::SelectionMotion {w item} {
1262    variable Priv
1263
1264    if {$item eq ""} return
1265    set item [$w item id $item]
1266    if {$item eq $Priv(prev)} return
1267    if {![$w item enabled $item]} return
1268
1269    switch [$w cget -selectmode] {
1270	browse {
1271	    $w selection modify $item all
1272	    set Priv(prev) $item
1273	}
1274	extended {
1275	    set i $Priv(prev)
1276	    set select {}
1277	    set deselect {}
1278	    if {$i eq ""} {
1279		set i $item
1280		lappend select $item
1281		set hack [$w item compare $item == anchor]
1282	    } else {
1283		set hack 0
1284	    }
1285	    if {[$w selection includes anchor] || $hack} {
1286		set deselect [concat $deselect [$w item range $i $item]]
1287		set select [concat $select [$w item range anchor $item]]
1288	    } else {
1289		set deselect [concat $deselect [$w item range $i $item]]
1290		set deselect [concat $deselect [$w item range anchor $item]]
1291	    }
1292	    if {![info exists Priv(selection)]} {
1293		set Priv(selection) [$w selection get]
1294	    }
1295	    while {[$w item compare $i < $item] && [$w item compare $i < anchor]} {
1296		if {[lsearch $Priv(selection) $i] >= 0} {
1297		    lappend select $i
1298		}
1299		set i [$w item id "$i next visible"]
1300	    }
1301	    while {[$w item compare $i > $item] && [$w item compare $i > anchor]} {
1302		if {[lsearch $Priv(selection) $i] >= 0} {
1303		    lappend select $i
1304		}
1305		set i [$w item id "$i prev visible"]
1306	    }
1307	    set Priv(prev) $item
1308	    $w selection modify $select $deselect
1309	}
1310    }
1311    return
1312}
1313
1314# ::TreeCtrl::BeginExtend --
1315#
1316# This procedure is typically invoked on shift-button-1 presses.  It
1317# begins the process of extending a selection in the treectrl.  Its
1318# exact behavior depends on the selection mode currently in effect
1319# for the treectrl.
1320#
1321# Arguments:
1322# w		The treectrl widget.
1323# item-		The item for the selection operation (typically the
1324#		one under the pointer).
1325
1326proc ::TreeCtrl::BeginExtend {w item} {
1327    if {[string equal [$w cget -selectmode] "extended"]} {
1328	if {[$w selection includes anchor]} {
1329	    SelectionMotion $w $item
1330	} else {
1331	    # No selection yet; simulate the begin-select operation.
1332	    BeginSelect $w $item
1333	}
1334    }
1335    return
1336}
1337
1338# ::TreeCtrl::BeginToggle --
1339#
1340# This procedure is typically invoked on control-button-1 presses.  It
1341# begins the process of toggling a selection in the treectrl.  Its
1342# exact behavior depends on the selection mode currently in effect
1343# for the treectrl.
1344#
1345# Arguments:
1346# w		The treectrl widget.
1347# item		The item for the selection operation (typically the
1348#		one under the pointer).
1349
1350proc ::TreeCtrl::BeginToggle {w item} {
1351    variable Priv
1352    if {$item eq ""} return
1353    if {[string equal [$w cget -selectmode] "extended"]} {
1354	set Priv(selection) [$w selection get]
1355	set Priv(prev) $item
1356	$w selection anchor $item
1357	if {[$w selection includes $item]} {
1358	    $w selection clear $item
1359	} else {
1360	    $w selection add $item
1361	}
1362    }
1363    return
1364}
1365
1366# ::TreeCtrl::AutoScanCheck --
1367#
1368# Sees if the given pointer coords are outside the content area of the
1369# treectrl (ie, not including borders or column headers) or within
1370# -scrollmargin distance of the edges of the content area. If so and
1371# auto-scanning is not already in progress, then the window is scrolled
1372# and an [after] callback to AutoScanCheckAux is scheduled.
1373#
1374# Arguments:
1375# w		The treectrl widget.
1376# x		Window x coord.
1377# y		Window y coord.
1378
1379proc ::TreeCtrl::AutoScanCheck {w x y} {
1380    variable Priv
1381    # Could have clicked in locked column
1382    if {[scan [$w contentbox] "%d %d %d %d" x1 y1 x2 y2] == -1} {
1383	if {[scan [$w bbox left] "%d %d %d %d" x1 y1 x2 y2] == -1} {
1384	    scan [$w bbox right] "%d %d %d %d" x1 y1 x2 y2
1385	}
1386    }
1387    set margin [winfo pixels $w [$w cget -scrollmargin]]
1388    if {![info exists Priv(autoscan,direction,$w)]} {
1389	set Priv(autoscan,direction,$w) xy
1390    }
1391    set scrollX [string match *x* $Priv(autoscan,direction,$w)]
1392    set scrollY [string match *y* $Priv(autoscan,direction,$w)]
1393    if {($scrollX && (($x < $x1 + $margin) || ($x >= $x2 - $margin))) ||
1394	($scrollY && (($y < $y1 + $margin) || ($y >= $y2 - $margin)))} {
1395	if {[info exists Priv(autoscan,afterId,$w)]} return
1396	if {$scrollY && $y >= $y2 - $margin} {
1397	    $w yview scroll 1 units
1398	    set delay [$w cget -yscrolldelay]
1399	} elseif {$scrollY && $y < $y1 + $margin} {
1400	    $w yview scroll -1 units
1401	    set delay [$w cget -yscrolldelay]
1402	} elseif {$scrollX && $x >= $x2 - $margin} {
1403	    $w xview scroll 1 units
1404	    set delay [$w cget -xscrolldelay]
1405	} elseif {$scrollX && $x < $x1 + $margin} {
1406	    $w xview scroll -1 units
1407	    set delay [$w cget -xscrolldelay]
1408	}
1409	set count [scan $delay "%d %d" d1 d2]
1410	if {[info exists Priv(autoscan,scanning,$w)]} {
1411	    if {$count == 2} {
1412		set delay $d2
1413	    }
1414	} else {
1415	    if {$count == 2} {
1416		set delay $d1
1417	    }
1418	    set Priv(autoscan,scanning,$w) 1
1419	}
1420	if {$Priv(autoscan,command,$w) ne ""} {
1421	    set command [string map [list %T $w %x $x %y $y] $Priv(autoscan,command,$w)]
1422	    eval $command
1423	}
1424	set Priv(autoscan,afterId,$w) [after $delay [list TreeCtrl::AutoScanCheckAux $w]]
1425	return
1426    }
1427    AutoScanCancel $w
1428    return
1429}
1430
1431# ::TreeCtrl::AutoScanCheckAux --
1432#
1433# Gets the location of the pointer and calls AutoScanCheck.
1434#
1435# Arguments:
1436# w		The treectrl widget.
1437
1438proc ::TreeCtrl::AutoScanCheckAux {w} {
1439    variable Priv
1440    if {![winfo exists $w]} return
1441    # Not quite sure how this can happen
1442    if {![info exists Priv(autoscan,afterId,$w)]} return
1443    unset Priv(autoscan,afterId,$w)
1444    set x [winfo pointerx $w]
1445    set y [winfo pointery $w]
1446    set x [expr {$x - [winfo rootx $w]}]
1447    set y [expr {$y - [winfo rooty $w]}]
1448    AutoScanCheck $w $x $y
1449    return
1450}
1451
1452# ::TreeCtrl::AutoScanCancel --
1453#
1454# Cancels any pending [after] callback to AutoScanCheckAux.
1455#
1456# Arguments:
1457# w		The treectrl widget.
1458
1459proc ::TreeCtrl::AutoScanCancel {w} {
1460    variable Priv
1461    if {[info exists Priv(autoscan,afterId,$w)]} {
1462	after cancel $Priv(autoscan,afterId,$w)
1463	unset Priv(autoscan,afterId,$w)
1464    }
1465    unset -nocomplain Priv(autoscan,scanning,$w)
1466    return
1467}
1468
1469# ::TreeCtrl::ColumnDragScrollCheck --
1470#
1471# Sees if the given pointer coords are outside the left or right edges of
1472# the content area of the treectrl (ie, not including borders). If so and
1473# auto-scanning is not already in progress, then the window is scrolled
1474# horizontally and the column drag-image is repositioned, and an [after]
1475# callback to ColumnDragScrollCheckAux is scheduled.
1476#
1477# Arguments:
1478# w		The treectrl widget.
1479# x		Window coord of pointer.
1480# y		Window coord of pointer.
1481
1482proc ::TreeCtrl::ColumnDragScrollCheck {w x y} {
1483    variable Priv
1484
1485    # When dragging to the left, use the left edge of the dragged
1486    # header to choose the -indicatorcolumn.  When dragging to the
1487    # right, use the right edge.
1488    scan [$w header bbox $Priv(header) $Priv(column)] "%d %d %d %d" x1 y1 x2 y2
1489    set offset [$w header dragcget -imageoffset]
1490    if {$offset > 0} {
1491	set xEdge [expr {$offset + $x2}]
1492    } else {
1493	set xEdge [expr {$offset + $x1}]
1494    }
1495
1496    scan [$w bbox header.none] "%d %d %d %d" x1 y1 x2 y2
1497
1498    if {($x < $x1) || ($x >= $x2)} {
1499	if {![info exists Priv(autoscan,afterId,$w)]} {
1500	    set bbox1 [$w column bbox $Priv(column)]
1501	    if {$xEdge >= $x2} {
1502		$w xview scroll 1 units
1503	    } else {
1504		$w xview scroll -1 units
1505	    }
1506	    set bbox2 [$w column bbox $Priv(column)]
1507	    if {[lindex $bbox1 0] != [lindex $bbox2 0]} {
1508		incr Priv(columnDrag,x) [expr {[lindex $bbox2 0] - [lindex $bbox1 0]}]
1509		$w header dragconfigure -imageoffset [expr {$x - $Priv(columnDrag,x)}]
1510
1511		if {[ColumnDragFindBefore $w $xEdge $Priv(columnDrag,y) $Priv(column) indColumn indSide]} {
1512		    $w header dragconfigure -indicatorcolumn $indColumn \
1513			-indicatorside $indSide
1514		} else {
1515		    $w header dragconfigure -indicatorcolumn ""
1516		}
1517	    }
1518	    set Priv(autoscan,afterId,$w) [after 50 [list TreeCtrl::ColumnDragScrollCheckAux $w]]
1519	}
1520	return
1521    }
1522    AutoScanCancel $w
1523    return
1524}
1525
1526# ::TreeCtrl::ColumnDragScrollCheckAux --
1527#
1528# Gets the location of the pointer and calls ColumnDragScrollCheck.
1529#
1530# Arguments:
1531# w		The treectrl widget.
1532
1533proc ::TreeCtrl::ColumnDragScrollCheckAux {w} {
1534    variable Priv
1535    if {![winfo exists $w]} return
1536    # Not quite sure how this can happen
1537    if {![info exists Priv(autoscan,afterId,$w)]} return
1538    unset Priv(autoscan,afterId,$w)
1539    set x [winfo pointerx $w]
1540    set y [winfo pointery $w]
1541    set x [expr {$x - [winfo rootx $w]}]
1542    set y [expr {$y - [winfo rooty $w]}]
1543    ColumnDragScrollCheck $w $x $y
1544    return
1545}
1546
1547# ::TreeCtrl::Has2DLayout --
1548#
1549# Determine if items are displayed in a 2-dimensional arrangement.
1550# This is used by the <Left> and <Right> bindings.
1551#
1552# Arguments:
1553# w		The treectrl widget.
1554
1555proc ::TreeCtrl::Has2DLayout {T} {
1556    if {[$T cget -orient] ne "vertical" || [$T cget -wrap] ne ""} {
1557	return 1
1558    }
1559    set item [$T item id "last visible"]
1560    if {$item ne ""} {
1561	lassign [$T item rnc $item] row column
1562	if {$column > 0} {
1563	    return 1
1564	}
1565    }
1566    return 0
1567}
1568
1569# ::TreeCtrl::UpDown --
1570#
1571# Returns the id of an item above or below the given item that the active
1572# item could be set to. If the given item isn't visible, the first visible
1573# enabled item is returned. An attempt is made to choose an item in the
1574# same column over repeat calls; this gives a better result if some rows
1575# have less items than others. Only enabled items are considered.
1576#
1577# Arguments:
1578# w		The treectrl widget.
1579# item		Item to move from, typically the active item.
1580# n		+1 to move down, -1 to move up.
1581
1582proc ::TreeCtrl::UpDown {w item n} {
1583    variable Priv
1584    set rnc [$w item rnc $item]
1585    if {$rnc eq ""} {
1586	return [$w item id {first visible state enabled}]
1587    }
1588    scan $rnc "%d %d" row col
1589    set Priv(keyNav,row,$w) [expr {$row + $n}]
1590    if {![info exists Priv(keyNav,rnc,$w)] || $rnc ne $Priv(keyNav,rnc,$w)} {
1591	set Priv(keyNav,col,$w) $col
1592    }
1593    set item2 [$w item id "rnc $Priv(keyNav,row,$w) $Priv(keyNav,col,$w)"]
1594    if {[$w item compare $item == $item2]} {
1595	set Priv(keyNav,row,$w) $row
1596	if {![$w item enabled $item2]} {
1597	    return ""
1598	}
1599    } else {
1600	set Priv(keyNav,rnc,$w) [$w item rnc $item2]
1601	if {![$w item enabled $item2]} {
1602	    return [UpDown $w $item2 $n]
1603	}
1604    }
1605    return $item2
1606}
1607
1608# ::TreeCtrl::LeftRight --
1609#
1610# Returns the id of an item left or right of the given item that the active
1611# item could be set to. If the given item isn't visible, the first visible
1612# enabled item is returned. An attempt is made to choose an item in the
1613# same row over repeat calls; this gives a better result if some columns
1614# have less items than others. Only enabled items are considered.
1615#
1616# Arguments:
1617# w		The treectrl widget.
1618# item		Item to move from, typically the active item.
1619# n		+1 to move right, -1 to move left.
1620
1621proc ::TreeCtrl::LeftRight {w item n} {
1622    variable Priv
1623    set rnc [$w item rnc $item]
1624    if {$rnc eq ""} {
1625	return [$w item id {first visible state enabled}]
1626    }
1627    scan $rnc "%d %d" row col
1628    set Priv(keyNav,col,$w) [expr {$col + $n}]
1629    if {![info exists Priv(keyNav,rnc,$w)] || $rnc ne $Priv(keyNav,rnc,$w)} {
1630	set Priv(keyNav,row,$w) $row
1631    }
1632    set item2 [$w item id "rnc $Priv(keyNav,row,$w) $Priv(keyNav,col,$w)"]
1633    if {[$w item compare $item == $item2]} {
1634	set Priv(keyNav,col,$w) $col
1635	if {![$w item enabled $item2]} {
1636	    return ""
1637	}
1638    } else {
1639	set Priv(keyNav,rnc,$w) [$w item rnc $item2]
1640	if {![$w item enabled $item2]} {
1641	    return [LeftRight $w $item2 $n]
1642	}
1643    }
1644    return $item2
1645}
1646
1647# ::TreeCtrl::SetActiveItem --
1648#
1649# Sets the active item, scrolls it into view, and makes it the only selected
1650# item. If -selectmode is extended, makes the active item the anchor of any
1651# future extended selection.
1652#
1653# Arguments:
1654# w		The treectrl widget.
1655# item		The new active item, or "".
1656
1657proc ::TreeCtrl::SetActiveItem {w item} {
1658    if {$item eq ""} return
1659    $w activate $item
1660    $w see active
1661    $w selection modify active all
1662    switch [$w cget -selectmode] {
1663	extended {
1664	    $w selection anchor active
1665	    set Priv(prev) [$w item id active]
1666	    set Priv(selection) {}
1667	}
1668    }
1669    return
1670}
1671
1672# ::TreeCtrl::Extend --
1673#
1674# Does nothing unless we're in extended selection mode;  in this
1675# case it moves the location cursor (active item) up, down, left or
1676# right, and extends the selection to that point.
1677#
1678# Arguments:
1679# w		The treectrl widget.
1680# dir		up, down, left or right
1681
1682proc ::TreeCtrl::Extend {w dir} {
1683    variable Priv
1684    if {[string compare [$w cget -selectmode] "extended"]} {
1685	return
1686    }
1687    if {![info exists Priv(selection)]} {
1688	$w selection add active
1689	set Priv(selection) [$w selection get]
1690    }
1691    switch -- $dir {
1692	above { set item [UpDown $w active -1] }
1693	below { set item [UpDown $w active 1] }
1694	left { set item [LeftRight $w active -1] }
1695	right { set item [LeftRight $w active 1] }
1696    }
1697    if {$item eq ""} return
1698    $w activate $item
1699    $w see active
1700    SelectionMotion $w [$w item id active]
1701    return
1702}
1703
1704# ::TreeCtrl::DataExtend
1705#
1706# This procedure is called for key-presses such as Shift-KEndData.
1707# If the selection mode isn't multiple or extended then it does nothing.
1708# Otherwise it moves the active item and, if we're in
1709# extended mode, extends the selection to that point.
1710#
1711# Arguments:
1712# w		The treectrl widget.
1713# item		Item to become new active item.
1714
1715proc ::TreeCtrl::DataExtend {w item} {
1716    if {$item eq ""} return
1717    set mode [$w cget -selectmode]
1718    if {[string equal $mode "extended"]} {
1719	$w activate $item
1720	$w see $item
1721        if {[$w selection includes anchor]} {
1722	    SelectionMotion $w $item
1723	}
1724    } elseif {[string equal $mode "multiple"]} {
1725	$w activate $item
1726	$w see $item
1727    }
1728    return
1729}
1730
1731# ::TreeCtrl::Cancel
1732#
1733# This procedure is invoked to cancel an extended selection in
1734# progress.  If there is an extended selection in progress, it
1735# restores all of the items between the active one and the anchor
1736# to their previous selection state.
1737#
1738# Arguments:
1739# w		The treectrl widget.
1740
1741proc ::TreeCtrl::Cancel w {
1742    variable Priv
1743    if {[string compare [$w cget -selectmode] "extended"]} {
1744	return
1745    }
1746    set first [$w item id anchor]
1747    set last $Priv(prev)
1748    if { [string equal $last ""] } {
1749	# Not actually doing any selection right now
1750	return
1751    }
1752    if {[$w item compare $first > $last]} {
1753	set tmp $first
1754	set first $last
1755	set last $tmp
1756    }
1757    set select {}
1758    set deselect {}
1759    foreach item [$w item id "range $first $last visible"] {
1760	if {[lsearch $Priv(selection) $item] == -1} {
1761	    lappend deselect $item
1762	} else {
1763	    lappend select $item
1764	}
1765    }
1766    $w selection modify $select $deselect
1767    return
1768}
1769
1770# ::TreeCtrl::SelectAll
1771#
1772# This procedure is invoked to handle the "select all" operation.
1773# For single and browse mode, it just selects the active item.
1774# Otherwise it selects everything in the widget.
1775#
1776# Arguments:
1777# w		The treectrl widget.
1778
1779proc ::TreeCtrl::SelectAll w {
1780    set mode [$w cget -selectmode]
1781    if {[string equal $mode "single"] || [string equal $mode "browse"]} {
1782	$w selection modify active all
1783    } else {
1784	$w selection add all
1785    }
1786    return
1787}
1788
1789# ::TreeCtrl::MarqueeBegin --
1790#
1791# Shows the selection rectangle at the given coords.
1792#
1793# Arguments:
1794# w		The treectrl widget.
1795# x		Window coord of pointer.
1796# y		Window coord of pointer.
1797
1798proc ::TreeCtrl::MarqueeBegin {w x y} {
1799    set x [$w canvasx $x]
1800    set y [$w canvasy $y]
1801    $w marquee coords $x $y $x $y
1802    $w marquee configure -visible yes
1803    return
1804}
1805
1806# ::TreeCtrl::MarqueeUpdate --
1807#
1808# Resizes the selection rectangle.
1809#
1810# Arguments:
1811# w		The treectrl widget.
1812# x		Window coord of pointer.
1813# y		Window coord of pointer.
1814
1815proc ::TreeCtrl::MarqueeUpdate {w x y} {
1816    set x [$w canvasx $x]
1817    set y [$w canvasy $y]
1818    $w marquee corner $x $y
1819    return
1820}
1821
1822# ::TreeCtrl::MarqueeEnd --
1823#
1824# Hides the selection rectangle.
1825#
1826# Arguments:
1827# w		The treectrl widget.
1828# x		Window coord of pointer.
1829# y		Window coord of pointer.
1830
1831proc ::TreeCtrl::MarqueeEnd {w x y} {
1832    $w marquee configure -visible no
1833    return
1834}
1835
1836# ::TreeCtrl::ScanMark --
1837#
1838# Marks the start of a possible scan drag operation.
1839#
1840# Arguments:
1841# w		The treectrl widget.
1842# x		Window coord of pointer.
1843# y		Window coord of pointer.
1844
1845proc ::TreeCtrl::ScanMark {w x y} {
1846    variable Priv
1847    $w scan mark $x $y
1848    set Priv(x) $x
1849    set Priv(y) $y
1850    set Priv(mouseMoved) 0
1851    return
1852}
1853
1854# ::TreeCtrl::ScanDrag --
1855#
1856# Performs a scan drag if the mouse moved.
1857#
1858# Arguments:
1859# w		The treectrl widget.
1860# x		Window coord of pointer.
1861# y		Window coord of pointer.
1862
1863proc ::TreeCtrl::ScanDrag {w x y} {
1864    variable Priv
1865    if {![info exists Priv(x)]} { set Priv(x) $x }
1866    if {![info exists Priv(y)]} { set Priv(y) $y }
1867    if {($x != $Priv(x)) || ($y != $Priv(y))} {
1868	set Priv(mouseMoved) 1
1869    }
1870    if {[info exists Priv(mouseMoved)] && $Priv(mouseMoved)} {
1871	$w scan dragto $x $y
1872    }
1873    return
1874}
1875
1876# ::TreeCtrl::TryEvent --
1877#
1878# This procedure is used to cause a treectrl to generate a dynamic event.
1879# If the treectrl doesn't have the event defined (because you didn't call
1880# the [notify install] command) nothing happens. TreeCtrl::PercentsCmd is
1881# used to perform %-substitution on any scripts bound to the event.
1882#
1883# Arguments:
1884# T		The treectrl widget.
1885# event		Name of event.
1886# detail	Name of detail or "".
1887# charMap	%-char substitution list (even number of elements).
1888
1889proc ::TreeCtrl::TryEvent {T event detail charMap} {
1890    if {[lsearch -exact [$T notify eventnames] $event] == -1} return
1891    if {$detail ne ""} {
1892	if {[lsearch -exact [$T notify detailnames $event] $detail] == -1} return
1893	$T notify generate <$event-$detail> $charMap "::TreeCtrl::PercentsCmd $T"
1894    } else {
1895	$T notify generate <$event> $charMap "::TreeCtrl::PercentsCmd $T"
1896    }
1897    return
1898}
1899
1900# ::TreeCtrl::PercentsCmd --
1901#
1902# This command is passed to [notify generate] to perform %-substitution on
1903# scripts bound to dynamic events. It supports the same set of substitution
1904# characters as the built-in static events (plus any event-specific chars).
1905#
1906# Arguments:
1907# T		The treectrl widget.
1908# char		%-char to be replaced in bound scripts.
1909# object	Same arg passed to [notify bind].
1910# event		Name of event.
1911# detail	Name of detail or "".
1912# charMap	%-char substitution list (even number of elements).
1913
1914proc ::TreeCtrl::PercentsCmd {T char object event detail charMap} {
1915    if {$detail ne ""} {
1916	set pattern <$event-$detail>
1917    } else {
1918	set pattern <$event>
1919    }
1920    switch -- $char {
1921	d { return $detail }
1922	e { return $event }
1923	P { return $pattern }
1924	W { return $object }
1925	T { return $T }
1926	? {
1927	    array set map $charMap
1928	    array set map [list T $T W $object P $pattern e $event d $detail]
1929	    return [array get map]
1930	}
1931	default {
1932	    array set map [list $char $char]
1933	    array set map $charMap
1934	    return $map($char)
1935	}
1936    }
1937    return
1938}
1939
1940namespace eval TreeCtrl {
1941catch {
1942    foreach theme [ttk::style theme names] {
1943	ttk::style theme settings $theme {
1944	    ttk::style configure TreeCtrlHeading -relief raised -font TkHeadingFont
1945	    ttk::style map TreeCtrlHeading -relief {
1946		pressed sunken
1947	    }
1948	}
1949    }
1950}
1951}
1952