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