You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

697 lines
16 KiB
Tcl

This file contains invisible Unicode characters!

This file contains invisible Unicode characters that may be processed differently from what appears below. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to reveal hidden characters.

# iconlist.tcl
#
# Implements the icon-list megawidget used in the "Tk" standard file
# selection dialog boxes.
#
# Copyright (c) 1994-1998 Sun Microsystems, Inc.
# Copyright (c) 2009 Donal K. Fellows
#
# See the file "license.terms" for information on usage and redistribution of
# this file, and for a DISCLAIMER OF ALL WARRANTIES.
#
# API Summary:
# tk::IconList <path> ?<option> <value>? ...
# <path> add <imageName> <itemList>
# <path> cget <option>
# <path> configure ?<option>? ?<value>? ...
# <path> deleteall
# <path> destroy
# <path> get <itemIndex>
# <path> index <index>
# <path> invoke
# <path> see <index>
# <path> selection anchor ?<int>?
# <path> selection clear <first> ?<last>?
# <path> selection get
# <path> selection includes <item>
# <path> selection set <first> ?<last>?
package require Tk 8.6
::tk::Megawidget create ::tk::IconList ::tk::FocusableWidget {
variable w canvas sbar accel accelCB fill font index \
itemList itemsPerColumn list maxIH maxIW maxTH maxTW noScroll \
numItems oldX oldY options rect selected selection textList
constructor args {
next {*}$args
set accelCB {}
}
destructor {
my Reset
next
}
method GetSpecs {} {
concat [next] {
{-command "" "" ""}
{-font "" "" "TkIconFont"}
{-multiple "" "" "0"}
}
}
# ----------------------------------------------------------------------
method index i {
if {![info exist list]} {
set list {}
}
switch -regexp -- $i {
"^-?[0-9]+$" {
if {$i < 0} {
set i 0
}
if {$i >= [llength $list]} {
set i [expr {[llength $list] - 1}]
}
return $i
}
"^anchor$" {
return $index(anchor)
}
"^end$" {
return [llength $list]
}
"@-?[0-9]+,-?[0-9]+" {
scan $i "@%d,%d" x y
set item [$canvas find closest \
[$canvas canvasx $x] [$canvas canvasy $y]]
return [lindex [$canvas itemcget $item -tags] 1]
}
}
}
method selection {op args} {
switch -exact -- $op {
anchor {
if {[llength $args] == 1} {
set index(anchor) [$w index [lindex $args 0]]
} else {
return $index(anchor)
}
}
clear {
switch [llength $args] {
2 {
lassign $args first last
}
1 {
set first [set last [lindex $args 0]]
}
default {
return -code error -errorcode {TCL WRONGARGS} \
"wrong # args: should be\
\"[lrange [info level 0] 0 1] first ?last?\""
}
}
set first [$w index $first]
set last [$w index $last]
if {$first > $last} {
set tmp $first
set first $last
set last $tmp
}
set ind 0
foreach item $selection {
if {$item >= $first} {
set first $ind
break
}
incr ind
}
set ind [expr {[llength $selection] - 1}]
for {} {$ind >= 0} {incr ind -1} {
set item [lindex $selection $ind]
if {$item <= $last} {
set last $ind
break
}
}
if {$first > $last} {
return
}
set selection [lreplace $selection $first $last]
event generate $w <<ListboxSelect>>
my DrawSelection
}
get {
return $selection
}
includes {
return [expr {[lindex $args 0] in $selection}]
}
set {
switch [llength $args] {
2 {
lassign $args first last
}
1 {
set first [set last [lindex $args 0]]
}
default {
return -code error -errorcode {TCL WRONGARGS} \
"wrong # args: should be\
\"[lrange [info level 0] 0 1] first ?last?\""
}
}
set first [$w index $first]
set last [$w index $last]
if {$first > $last} {
set tmp $first
set first $last
set last $tmp
}
for {set i $first} {$i <= $last} {incr i} {
lappend selection $i
}
set selection [lsort -integer -unique $selection]
event generate $w <<ListboxSelect>>
my DrawSelection
}
}
}
method get item {
set rTag [lindex $list $item 2]
lassign $itemList($rTag) iTag tTag text serial
return $text
}
# Deletes all the items inside the canvas subwidget and reset the
# iconList's state.
#
method deleteall {} {
$canvas delete all
unset -nocomplain selected rect list itemList
set maxIW 1
set maxIH 1
set maxTW 1
set maxTH 1
set numItems 0
set noScroll 1
set selection {}
set index(anchor) ""
$sbar set 0.0 1.0
$canvas xview moveto 0
}
# Adds an icon into the IconList with the designated image and text
#
method add {image items} {
foreach text $items {
set iID item$numItems
set iTag [$canvas create image 0 0 -image $image -anchor nw \
-tags [list icon $numItems $iID]]
set tTag [$canvas create text 0 0 -text $text -anchor nw \
-font $options(-font) -fill $fill \
-tags [list text $numItems $iID]]
set rTag [$canvas create rect 0 0 0 0 -fill "" -outline "" \
-tags [list rect $numItems $iID]]
lassign [$canvas bbox $iTag] x1 y1 x2 y2
set iW [expr {$x2 - $x1}]
set iH [expr {$y2 - $y1}]
if {$maxIW < $iW} {
set maxIW $iW
}
if {$maxIH < $iH} {
set maxIH $iH
}
lassign [$canvas bbox $tTag] x1 y1 x2 y2
set tW [expr {$x2 - $x1}]
set tH [expr {$y2 - $y1}]
if {$maxTW < $tW} {
set maxTW $tW
}
if {$maxTH < $tH} {
set maxTH $tH
}
lappend list [list $iTag $tTag $rTag $iW $iH $tW $tH $numItems]
set itemList($rTag) [list $iTag $tTag $text $numItems]
set textList($numItems) [string tolower $text]
incr numItems
}
my WhenIdle Arrange
return
}
# Gets called when the user invokes the IconList (usually by
# double-clicking or pressing the Return key).
#
method invoke {} {
if {$options(-command) ne "" && [llength $selection]} {
uplevel #0 $options(-command)
}
}
# If the item is not (completely) visible, scroll the canvas so that it
# becomes visible.
#
method see rTag {
if {$noScroll} {
return
}
set sRegion [$canvas cget -scrollregion]
if {$sRegion eq ""} {
return
}
if {$rTag < 0 || $rTag >= [llength $list]} {
return
}
set bbox [$canvas bbox item$rTag]
set pad [expr {[$canvas cget -highlightthickness]+[$canvas cget -bd]}]
set x1 [lindex $bbox 0]
set x2 [lindex $bbox 2]
incr x1 [expr {$pad * -2}]
incr x2 [expr {$pad * -1}]
set cW [expr {[winfo width $canvas] - $pad*2}]
set scrollW [expr {[lindex $sRegion 2]-[lindex $sRegion 0]+1}]
set dispX [expr {int([lindex [$canvas xview] 0]*$scrollW)}]
set oldDispX $dispX
# check if out of the right edge
#
if {($x2 - $dispX) >= $cW} {
set dispX [expr {$x2 - $cW}]
}
# check if out of the left edge
#
if {($x1 - $dispX) < 0} {
set dispX $x1
}
if {$oldDispX ne $dispX} {
set fraction [expr {double($dispX) / double($scrollW)}]
$canvas xview moveto $fraction
}
}
# ----------------------------------------------------------------------
# Places the icons in a column-major arrangement.
#
method Arrange {} {
if {![info exists list]} {
if {[info exists canvas] && [winfo exists $canvas]} {
set noScroll 1
$sbar configure -command ""
}
return
}
set W [winfo width $canvas]
set H [winfo height $canvas]
set pad [expr {[$canvas cget -highlightthickness]+[$canvas cget -bd]}]
if {$pad < 2} {
set pad 2
}
incr W [expr {$pad*-2}]
incr H [expr {$pad*-2}]
set dx [expr {$maxIW + $maxTW + 8}]
if {$maxTH > $maxIH} {
set dy $maxTH
} else {
set dy $maxIH
}
incr dy 2
set shift [expr {$maxIW + 4}]
set x [expr {$pad * 2}]
set y [expr {$pad * 1}] ; # Why * 1 ?
set usedColumn 0
foreach sublist $list {
set usedColumn 1
lassign $sublist iTag tTag rTag iW iH tW tH
set i_dy [expr {($dy - $iH)/2}]
set t_dy [expr {($dy - $tH)/2}]
$canvas coords $iTag $x [expr {$y + $i_dy}]
$canvas coords $tTag [expr {$x + $shift}] [expr {$y + $t_dy}]
$canvas coords $rTag $x $y [expr {$x+$dx}] [expr {$y+$dy}]
incr y $dy
if {($y + $dy) > $H} {
set y [expr {$pad * 1}] ; # *1 ?
incr x $dx
set usedColumn 0
}
}
if {$usedColumn} {
set sW [expr {$x + $dx}]
} else {
set sW $x
}
if {$sW < $W} {
$canvas configure -scrollregion [list $pad $pad $sW $H]
$sbar configure -command ""
$canvas xview moveto 0
set noScroll 1
} else {
$canvas configure -scrollregion [list $pad $pad $sW $H]
$sbar configure -command [list $canvas xview]
set noScroll 0
}
set itemsPerColumn [expr {($H-$pad) / $dy}]
if {$itemsPerColumn < 1} {
set itemsPerColumn 1
}
my DrawSelection
}
method DrawSelection {} {
$canvas delete selection
$canvas itemconfigure selectionText -fill black
$canvas dtag selectionText
set cbg [ttk::style lookup TEntry -selectbackground focus]
set cfg [ttk::style lookup TEntry -selectforeground focus]
foreach item $selection {
set rTag [lindex $list $item 2]
foreach {iTag tTag text serial} $itemList($rTag) {
break
}
set bbox [$canvas bbox $tTag]
$canvas create rect $bbox -fill $cbg -outline $cbg \
-tags selection
$canvas itemconfigure $tTag -fill $cfg -tags selectionText
}
$canvas lower selection
return
}
# Creates an IconList widget by assembling a canvas widget and a
# scrollbar widget. Sets all the bindings necessary for the IconList's
# operations.
#
method Create {} {
variable hull
set sbar [ttk::scrollbar $hull.sbar -orient horizontal -takefocus 0]
catch {$sbar configure -highlightthickness 0}
set canvas [canvas $hull.canvas -highlightthick 0 -takefocus 1 \
-width 400 -height 120 -background white]
pack $sbar -side bottom -fill x -padx 2 -pady {0 2}
pack $canvas -expand yes -fill both -padx 2 -pady {2 0}
$sbar configure -command [list $canvas xview]
$canvas configure -xscrollcommand [list $sbar set]
# Initializes the max icon/text width and height and other variables
#
set maxIW 1
set maxIH 1
set maxTW 1
set maxTH 1
set numItems 0
set noScroll 1
set selection {}
set index(anchor) ""
set fg [option get $canvas foreground Foreground]
if {$fg eq ""} {
set fill black
} else {
set fill $fg
}
# Creates the event bindings.
#
bind $canvas <Configure> [namespace code {my WhenIdle Arrange}]
bind $canvas <1> [namespace code {my Btn1 %x %y}]
bind $canvas <B1-Motion> [namespace code {my Motion1 %x %y}]
bind $canvas <B1-Leave> [namespace code {my Leave1 %x %y}]
bind $canvas <Control-1> [namespace code {my CtrlBtn1 %x %y}]
bind $canvas <Shift-1> [namespace code {my ShiftBtn1 %x %y}]
bind $canvas <B1-Enter> [list tk::CancelRepeat]
bind $canvas <ButtonRelease-1> [list tk::CancelRepeat]
bind $canvas <Double-ButtonRelease-1> \
[namespace code {my Double1 %x %y}]
bind $canvas <Control-B1-Motion> {;}
bind $canvas <Shift-B1-Motion> [namespace code {my ShiftMotion1 %x %y}]
bind $canvas <<PrevLine>> [namespace code {my UpDown -1}]
bind $canvas <<NextLine>> [namespace code {my UpDown 1}]
bind $canvas <<PrevChar>> [namespace code {my LeftRight -1}]
bind $canvas <<NextChar>> [namespace code {my LeftRight 1}]
bind $canvas <Return> [namespace code {my ReturnKey}]
bind $canvas <KeyPress> [namespace code {my KeyPress %A}]
bind $canvas <Control-KeyPress> ";"
bind $canvas <Alt-KeyPress> ";"
bind $canvas <FocusIn> [namespace code {my FocusIn}]
bind $canvas <FocusOut> [namespace code {my FocusOut}]
return $w
}
# This procedure is invoked when the mouse leaves an entry window with
# button 1 down. It scrolls the window up, down, left, or right,
# depending on where the mouse left the window, and reschedules itself
# as an "after" command so that the window continues to scroll until the
# mouse moves back into the window or the mouse button is released.
#
method AutoScan {} {
if {![winfo exists $w]} return
set x $oldX
set y $oldY
if {$noScroll} {
return
}
if {$x >= [winfo width $canvas]} {
$canvas xview scroll 1 units
} elseif {$x < 0} {
$canvas xview scroll -1 units
} elseif {$y >= [winfo height $canvas]} {
# do nothing
} elseif {$y < 0} {
# do nothing
} else {
return
}
my Motion1 $x $y
set ::tk::Priv(afterId) [after 50 [namespace code {my AutoScan}]]
}
# ----------------------------------------------------------------------
# Event handlers
method Btn1 {x y} {
focus $canvas
set i [$w index @$x,$y]
if {$i eq ""} {
return
}
$w selection clear 0 end
$w selection set $i
$w selection anchor $i
}
method CtrlBtn1 {x y} {
if {$options(-multiple)} {
focus $canvas
set i [$w index @$x,$y]
if {$i eq ""} {
return
}
if {[$w selection includes $i]} {
$w selection clear $i
} else {
$w selection set $i
$w selection anchor $i
}
}
}
method ShiftBtn1 {x y} {
if {$options(-multiple)} {
focus $canvas
set i [$w index @$x,$y]
if {$i eq ""} {
return
}
if {[$w index anchor] eq ""} {
$w selection anchor $i
}
$w selection clear 0 end
$w selection set anchor $i
}
}
# Gets called on button-1 motions
#
method Motion1 {x y} {
set oldX $x
set oldY $y
set i [$w index @$x,$y]
if {$i eq ""} {
return
}
$w selection clear 0 end
$w selection set $i
}
method ShiftMotion1 {x y} {
set oldX $x
set oldY $y
set i [$w index @$x,$y]
if {$i eq ""} {
return
}
$w selection clear 0 end
$w selection set anchor $i
}
method Double1 {x y} {
if {[llength $selection]} {
$w invoke
}
}
method ReturnKey {} {
$w invoke
}
method Leave1 {x y} {
set oldX $x
set oldY $y
my AutoScan
}
method FocusIn {} {
$w state focus
if {![info exists list]} {
return
}
if {[llength $selection]} {
my DrawSelection
}
}
method FocusOut {} {
$w state !focus
$w selection clear 0 end
}
# Moves the active element up or down by one element
#
# Arguments:
# amount - +1 to move down one item, -1 to move back one item.
#
method UpDown amount {
if {![info exists list]} {
return
}
set curr [$w selection get]
if {[llength $curr] == 0} {
set i 0
} else {
set i [$w index anchor]
if {$i eq ""} {
return
}
incr i $amount
}
$w selection clear 0 end
$w selection set $i
$w selection anchor $i
$w see $i
}
# Moves the active element left or right by one column
#
# Arguments:
# amount - +1 to move right one column, -1 to move left one
# column
#
method LeftRight amount {
if {![info exists list]} {
return
}
set curr [$w selection get]
if {[llength $curr] == 0} {
set i 0
} else {
set i [$w index anchor]
if {$i eq ""} {
return
}
incr i [expr {$amount * $itemsPerColumn}]
}
$w selection clear 0 end
$w selection set $i
$w selection anchor $i
$w see $i
}
# Gets called when user enters an arbitrary key in the listbox.
#
method KeyPress key {
append accel $key
my Goto $accel
after cancel $accelCB
set accelCB [after 500 [namespace code {my Reset}]]
}
method Goto text {
if {![info exists list]} {
return
}
if {$text eq "" || $numItems == 0} {
return
}
if {[llength [$w selection get]]} {
set start [$w index anchor]
} else {
set start 0
}
set theIndex -1
set less 0
set len [string length $text]
set len0 [expr {$len - 1}]
set i $start
# Search forward until we find a filename whose prefix is a
# case-insensitive match with $text
while {1} {
if {[string equal -nocase -length $len0 $textList($i) $text]} {
set theIndex $i
break
}
incr i
if {$i == $numItems} {
set i 0
}
if {$i == $start} {
break
}
}
if {$theIndex > -1} {
$w selection clear 0 end
$w selection set $theIndex
$w selection anchor $theIndex
$w see $theIndex
}
}
method Reset {} {
unset -nocomplain accel
}
}
return
# Local Variables:
# mode: tcl
# fill-column: 78
# End: