diff options
author | William Joye <wjoye@cfa.harvard.edu> | 2016-10-27 18:27:18 (GMT) |
---|---|---|
committer | William Joye <wjoye@cfa.harvard.edu> | 2016-10-27 18:27:18 (GMT) |
commit | 32c31a3b172990fd736e0ed21bcab1e1d6ab6570 (patch) | |
tree | d36501971e32ebf2eb2c1c0d813e0af01d83a04a /library | |
download | blt-32c31a3b172990fd736e0ed21bcab1e1d6ab6570.zip blt-32c31a3b172990fd736e0ed21bcab1e1d6ab6570.tar.gz blt-32c31a3b172990fd736e0ed21bcab1e1d6ab6570.tar.bz2 |
Squashed 'tktable/' content from commit 1429721
git-subtree-dir: tktable
git-subtree-split: 142972112150475defaaf03047d9cac2efe69662
Diffstat (limited to 'library')
-rwxr-xr-x | library/tkTable.tcl | 825 | ||||
-rwxr-xr-x | library/tktable.py | 651 |
2 files changed, 1476 insertions, 0 deletions
diff --git a/library/tkTable.tcl b/library/tkTable.tcl new file mode 100755 index 0000000..0343a43 --- /dev/null +++ b/library/tkTable.tcl @@ -0,0 +1,825 @@ +# table.tcl -- +# +# Version align with tkTable 2.7, jeff at hobbs org +# This file defines the default bindings for Tk table widgets +# and provides procedures that help in implementing those bindings. +# +# RCS: @(#) $Id: tkTable.tcl,v 1.1.1.1 2011/03/01 20:00:38 joye Exp $ + +#-------------------------------------------------------------------------- +# ::tk::table::Priv elements used in this file: +# +# x && y - Coords in widget +# afterId - Token returned by "after" for autoscanning. +# tablePrev - The last element to be selected or deselected +# during a selection operation. +# mouseMoved - Boolean to indicate whether mouse moved while +# the button was pressed. +# borderInfo - Boolean to know if the user clicked on a border +# borderB1 - Boolean that set whether B1 can be used for the +# interactiving resizing +#-------------------------------------------------------------------------- + +namespace eval ::tk::table { + # Ensure that a namespace is created for us + variable Priv + array set Priv [list x 0 y 0 afterId {} mouseMoved 0 \ + borderInfo {} borderB1 1] +} + +# ::tk::table::ClipboardKeysyms -- +# This procedure is invoked to identify the keys that correspond to +# the "copy", "cut", and "paste" functions for the clipboard. +# +# Arguments: +# copy - Name of the key (keysym name plus modifiers, if any, +# such as "Meta-y") used for the copy operation. +# cut - Name of the key used for the cut operation. +# paste - Name of the key used for the paste operation. + +proc ::tk::table::ClipboardKeysyms {copy cut paste} { + bind Table <$copy> {tk_tableCopy %W} + bind Table <$cut> {tk_tableCut %W} + bind Table <$paste> {tk_tablePaste %W} +} +::tk::table::ClipboardKeysyms <Copy> <Cut> <Paste> + +## +## Interactive cell resizing, affected by -resizeborders option +## +bind Table <3> { + ## You might want to check for cell returned if you want to + ## restrict the resizing of certain cells + %W border mark %x %y +} +bind Table <B3-Motion> { %W border dragto %x %y } + +## Button events + +bind Table <1> { ::tk::table::Button1 %W %x %y } +bind Table <B1-Motion> { ::tk::table::B1Motion %W %x %y } + +bind Table <ButtonRelease-1> { + if {$::tk::table::Priv(borderInfo) == "" && [winfo exists %W]} { + ::tk::table::CancelRepeat + %W activate @%x,%y + } +} +bind Table <Double-1> { + # empty +} + +bind Table <Shift-1> {::tk::table::BeginExtend %W [%W index @%x,%y]} +bind Table <Control-1> {::tk::table::BeginToggle %W [%W index @%x,%y]} +bind Table <B1-Enter> {::tk::table::CancelRepeat} +bind Table <B1-Leave> { + if {$::tk::table::Priv(borderInfo) == ""} { + array set ::tk::table::Priv {x %x y %y} + ::tk::table::AutoScan %W + } +} +bind Table <2> { + %W scan mark %x %y + array set ::tk::table::Priv {x %x y %y} + set ::tk::table::Priv(mouseMoved) 0 +} +bind Table <B2-Motion> { + if {(%x != $::tk::table::Priv(x)) || (%y != $::tk::table::Priv(y))} { + set ::tk::table::Priv(mouseMoved) 1 + } + if {$::tk::table::Priv(mouseMoved)} { %W scan dragto %x %y } +} +bind Table <ButtonRelease-2> { + if {!$::tk::table::Priv(mouseMoved)} { tk_tablePaste %W [%W index @%x,%y] } +} + +## Key events + +# This forces a cell commit if an active cell exists +bind Table <<Table_Commit>> { + catch {%W activate active} +} +# Remove this if you don't want cell commit to occur on every Leave for +# the table (via mouse) or FocusOut (loss of focus by table). +event add <<Table_Commit>> <Leave> <FocusOut> + +bind Table <Shift-Up> {::tk::table::ExtendSelect %W -1 0} +bind Table <Shift-Down> {::tk::table::ExtendSelect %W 1 0} +bind Table <Shift-Left> {::tk::table::ExtendSelect %W 0 -1} +bind Table <Shift-Right> {::tk::table::ExtendSelect %W 0 1} +bind Table <Prior> {%W yview scroll -1 pages; %W activate topleft} +bind Table <Next> {%W yview scroll 1 pages; %W activate topleft} +bind Table <Control-Prior> {%W xview scroll -1 pages} +bind Table <Control-Next> {%W xview scroll 1 pages} +bind Table <Home> {%W see origin} +bind Table <End> {%W see end} +bind Table <Control-Home> { + %W selection clear all + %W activate origin + %W selection set active + %W see active +} +bind Table <Control-End> { + %W selection clear all + %W activate end + %W selection set active + %W see active +} +bind Table <Shift-Control-Home> {::tk::table::DataExtend %W origin} +bind Table <Shift-Control-End> {::tk::table::DataExtend %W end} +bind Table <Select> {::tk::table::BeginSelect %W [%W index active]} +bind Table <Shift-Select> {::tk::table::BeginExtend %W [%W index active]} +bind Table <Control-slash> {::tk::table::SelectAll %W} +bind Table <Control-backslash> { + if {[string match browse [%W cget -selectmode]]} {%W selection clear all} +} +bind Table <Up> {::tk::table::MoveCell %W -1 0} +bind Table <Down> {::tk::table::MoveCell %W 1 0} +bind Table <Left> {::tk::table::MoveCell %W 0 -1} +bind Table <Right> {::tk::table::MoveCell %W 0 1} +bind Table <KeyPress> {::tk::table::Insert %W %A} +bind Table <BackSpace> {::tk::table::BackSpace %W} +bind Table <Delete> {%W delete active insert} +bind Table <Escape> {%W reread} + +#bind Table <Return> {::tk::table::MoveCell %W 1 0} +bind Table <Return> {::tk::table::Insert %W "\n"} + +bind Table <Control-Left> {%W icursor [expr {[%W icursor]-1}]} +bind Table <Control-Right> {%W icursor [expr {[%W icursor]+1}]} +bind Table <Control-e> {%W icursor end} +bind Table <Control-a> {%W icursor 0} +bind Table <Control-k> {%W delete active insert end} +bind Table <Control-equal> {::tk::table::ChangeWidth %W active 1} +bind Table <Control-minus> {::tk::table::ChangeWidth %W active -1} + +# Ignore all Alt, Meta, and Control keypresses unless explicitly bound. +# Otherwise, if a widget binding for one of these is defined, the +# <KeyPress> class binding will also fire and insert the character, +# which is wrong. Ditto for Tab. + +bind Table <Alt-KeyPress> {# nothing} +bind Table <Meta-KeyPress> {# nothing} +bind Table <Control-KeyPress> {# nothing} +bind Table <Any-Tab> {# nothing} +if {[string match "macintosh" $::tcl_platform(platform)]} { + bind Table <Command-KeyPress> {# nothing} +} + +# ::tk::table::GetSelection -- +# This tries to obtain the default selection. On Unix, we first try +# and get a UTF8_STRING, a type supported by modern Unix apps for +# passing Unicode data safely. We fall back on the default STRING +# type otherwise. On Windows, only the STRING type is necessary. +# Arguments: +# w The widget for which the selection will be retrieved. +# Important for the -displayof property. +# sel The source of the selection (PRIMARY or CLIPBOARD) +# Results: +# Returns the selection, or an error if none could be found +# +if {[string compare $::tcl_platform(platform) "unix"]} { + proc ::tk::table::GetSelection {w {sel PRIMARY}} { + if {[catch {selection get -displayof $w -selection $sel} txt]} { + return -code error "could not find default selection" + } else { + return $txt + } + } +} else { + proc ::tk::table::GetSelection {w {sel PRIMARY}} { + if {[catch {selection get -displayof $w -selection $sel \ + -type UTF8_STRING} txt] \ + && [catch {selection get -displayof $w -selection $sel} txt]} { + return -code error "could not find default selection" + } else { + return $txt + } + } +} + +# ::tk::table::CancelRepeat -- +# A copy of tkCancelRepeat, just in case it's not available or changes. +# This procedure is invoked to cancel an auto-repeat action described +# by ::tk::table::Priv(afterId). It's used by several widgets to auto-scroll +# the widget when the mouse is dragged out of the widget with a +# button pressed. +# +# Arguments: +# None. + +proc ::tk::table::CancelRepeat {} { + variable Priv + after cancel $Priv(afterId) + set Priv(afterId) {} +} + +# ::tk::table::Insert -- +# +# Insert into the active cell +# +# Arguments: +# w - the table widget +# s - the string to insert +# Results: +# Returns nothing +# +proc ::tk::table::Insert {w s} { + if {[string compare $s {}]} { + $w insert active insert $s + } +} + +# ::tk::table::BackSpace -- +# +# BackSpace in the current cell +# +# Arguments: +# w - the table widget +# Results: +# Returns nothing +# +proc ::tk::table::BackSpace {w} { + set cur [$w icursor] + if {[string compare {} $cur] && $cur} { + $w delete active [expr {$cur-1}] + } +} + +# ::tk::table::Button1 -- +# +# This procedure is called to handle selecting with mouse button 1. +# It will distinguish whether to start selection or mark a border. +# +# Arguments: +# w - the table widget +# x - x coord +# y - y coord +# Results: +# Returns nothing +# +proc ::tk::table::Button1 {w x y} { + variable Priv + # + # $Priv(borderInfo) is null if the user did not click on a border + # + if {$Priv(borderB1) == 1} { + set Priv(borderInfo) [$w border mark $x $y] + # account for what resizeborders are set [Bug 876320] (ferenc) + set rbd [$w cget -resizeborders] + if {$rbd == "none" || ![llength $Priv(borderInfo)] + || ($rbd == "col" && [lindex $Priv(borderInfo) 1] == "") + || ($rbd == "row" && [lindex $Priv(borderInfo) 0] == "")} { + set Priv(borderInfo) "" + } + } else { + set Priv(borderInfo) "" + } + if {$Priv(borderInfo) == ""} { + # + # Only do this when a border wasn't selected + # + if {[winfo exists $w]} { + ::tk::table::BeginSelect $w [$w index @$x,$y] + focus $w + } + array set Priv [list x $x y $y] + set Priv(mouseMoved) 0 + } +} + +# ::tk::table::B1Motion -- +# +# This procedure is called to start processing mouse motion events while +# button 1 moves while pressed. It will distinguish whether to change +# the selection or move a border. +# +# Arguments: +# w - the table widget +# x - x coord +# y - y coord +# Results: +# Returns nothing +# +proc ::tk::table::B1Motion {w x y} { + variable Priv + + # If we already had motion, or we moved more than 1 pixel, + # then we start the Motion routine + if {$Priv(borderInfo) != ""} { + # + # If the motion is on a border, drag it and skip the rest + # of this binding. + # + $w border dragto $x $y + } else { + # + # If we already had motion, or we moved more than 1 pixel, + # then we start the Motion routine + # + if { + $::tk::table::Priv(mouseMoved) + || abs($x-$::tk::table::Priv(x)) > 1 + || abs($y-$::tk::table::Priv(y)) > 1 + } { + set ::tk::table::Priv(mouseMoved) 1 + } + if {$::tk::table::Priv(mouseMoved)} { + ::tk::table::Motion $w [$w index @$x,$y] + } + } +} + +# ::tk::table::BeginSelect -- +# +# This procedure is typically invoked on button-1 presses. It begins +# the process of making a selection in the table. Its exact behavior +# depends on the selection mode currently in effect for the table; +# see the Motif documentation for details. +# +# Arguments: +# w - The table widget. +# el - The element for the selection operation (typically the +# one under the pointer). Must be in row,col form. + +proc ::tk::table::BeginSelect {w el} { + variable Priv + if {[scan $el %d,%d r c] != 2} return + switch [$w cget -selectmode] { + multiple { + if {[$w tag includes title $el]} { + ## in the title area + if {$r < [$w cget -titlerows]+[$w cget -roworigin]} { + ## We're in a column header + if {$c < [$w cget -titlecols]+[$w cget -colorigin]} { + ## We're in the topleft title area + set inc topleft + set el2 end + } else { + set inc [$w index topleft row],$c + set el2 [$w index end row],$c + } + } else { + ## We're in a row header + set inc $r,[$w index topleft col] + set el2 $r,[$w index end col] + } + } else { + set inc $el + set el2 $el + } + if {[$w selection includes $inc]} { + $w selection clear $el $el2 + } else { + $w selection set $el $el2 + } + } + extended { + $w selection clear all + if {[$w tag includes title $el]} { + if {$r < [$w cget -titlerows]+[$w cget -roworigin]} { + ## We're in a column header + if {$c < [$w cget -titlecols]+[$w cget -colorigin]} { + ## We're in the topleft title area + $w selection set $el end + } else { + $w selection set $el [$w index end row],$c + } + } else { + ## We're in a row header + $w selection set $el $r,[$w index end col] + } + } else { + $w selection set $el + } + $w selection anchor $el + set Priv(tablePrev) $el + } + default { + if {![$w tag includes title $el]} { + $w selection clear all + $w selection set $el + set Priv(tablePrev) $el + } + $w selection anchor $el + } + } +} + +# ::tk::table::Motion -- +# +# This procedure is called to process mouse motion events while +# button 1 is down. It may move or extend the selection, depending +# on the table's selection mode. +# +# Arguments: +# w - The table widget. +# el - The element under the pointer (must be in row,col form). + +proc ::tk::table::Motion {w el} { + variable Priv + if {![info exists Priv(tablePrev)]} { + set Priv(tablePrev) $el + return + } + if {[string match $Priv(tablePrev) $el]} return + switch [$w cget -selectmode] { + browse { + $w selection clear all + $w selection set $el + set Priv(tablePrev) $el + } + extended { + # avoid tables that have no anchor index yet. + if {[catch {$w index anchor}]} { return } + scan $Priv(tablePrev) %d,%d r c + scan $el %d,%d elr elc + if {[$w tag includes title $el]} { + if {$r < [$w cget -titlerows]+[$w cget -roworigin]} { + ## We're in a column header + if {$c < [$w cget -titlecols]+[$w cget -colorigin]} { + ## We're in the topleft title area + $w selection clear anchor end + } else { + $w selection clear anchor [$w index end row],$c + } + $w selection set anchor [$w index end row],$elc + } else { + ## We're in a row header + $w selection clear anchor $r,[$w index end col] + $w selection set anchor $elr,[$w index end col] + } + } else { + $w selection clear anchor $Priv(tablePrev) + $w selection set anchor $el + } + set Priv(tablePrev) $el + } + } +} + +# ::tk::table::BeginExtend -- +# +# This procedure is typically invoked on shift-button-1 presses. It +# begins the process of extending a selection in the table. Its +# exact behavior depends on the selection mode currently in effect +# for the table; see the Motif documentation for details. +# +# Arguments: +# w - The table widget. +# el - The element for the selection operation (typically the +# one under the pointer). Must be in numerical form. + +proc ::tk::table::BeginExtend {w el} { + # avoid tables that have no anchor index yet. + if {[catch {$w index anchor}]} { return } + if {[string match extended [$w cget -selectmode]] && + [$w selection includes anchor]} { + ::tk::table::Motion $w $el + } +} + +# ::tk::table::BeginToggle -- +# +# This procedure is typically invoked on control-button-1 presses. It +# begins the process of toggling a selection in the table. Its +# exact behavior depends on the selection mode currently in effect +# for the table; see the Motif documentation for details. +# +# Arguments: +# w - The table widget. +# el - The element for the selection operation (typically the +# one under the pointer). Must be in numerical form. + +proc ::tk::table::BeginToggle {w el} { + if {[string match extended [$w cget -selectmode]]} { + variable Priv + set Priv(tablePrev) $el + $w selection anchor $el + if {[$w tag includes title $el]} { + scan $el %d,%d r c + if {$r < [$w cget -titlerows]+[$w cget -roworigin]} { + ## We're in a column header + if {$c < [$w cget -titlecols]+[$w cget -colorigin]} { + ## We're in the topleft title area + set end end + } else { + set end [$w index end row],$c + } + } else { + ## We're in a row header + set end $r,[$w index end col] + } + } else { + ## We're in a non-title cell + set end $el + } + if {[$w selection includes $end]} { + $w selection clear $el $end + } else { + $w selection set $el $end + } + } +} + +# ::tk::table::AutoScan -- +# This procedure is invoked when the mouse leaves an table 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. +# +# Arguments: +# w - The table window. + +proc ::tk::table::AutoScan {w} { + if {![winfo exists $w]} return + variable Priv + set x $Priv(x) + set y $Priv(y) + if {$y >= [winfo height $w]} { + $w yview scroll 1 units + } elseif {$y < 0} { + $w yview scroll -1 units + } elseif {$x >= [winfo width $w]} { + $w xview scroll 1 units + } elseif {$x < 0} { + $w xview scroll -1 units + } else { + return + } + ::tk::table::Motion $w [$w index @$x,$y] + set Priv(afterId) [after 50 ::tk::table::AutoScan $w] +} + +# ::tk::table::MoveCell -- +# +# Moves the location cursor (active element) by the specified number +# of cells and changes the selection if we're in browse or extended +# selection mode. If the new cell is "hidden", we skip to the next +# visible cell if possible, otherwise just abort. +# +# Arguments: +# w - The table widget. +# x - +1 to move down one cell, -1 to move up one cell. +# y - +1 to move right one cell, -1 to move left one cell. + +proc ::tk::table::MoveCell {w x y} { + if {[catch {$w index active row} r]} return + set c [$w index active col] + set cell [$w index [incr r $x],[incr c $y]] + while {[string compare [set true [$w hidden $cell]] {}]} { + # The cell is in some way hidden + if {[string compare $true [$w index active]]} { + # The span cell wasn't the previous cell, so go to that + set cell $true + break + } + if {$x > 0} {incr r} elseif {$x < 0} {incr r -1} + if {$y > 0} {incr c} elseif {$y < 0} {incr c -1} + if {[string compare $cell [$w index $r,$c]]} { + set cell [$w index $r,$c] + } else { + # We couldn't find a non-hidden cell, just don't move + return + } + } + $w activate $cell + $w see active + switch [$w cget -selectmode] { + browse { + $w selection clear all + $w selection set active + } + extended { + variable Priv + $w selection clear all + $w selection set active + $w selection anchor active + set Priv(tablePrev) [$w index active] + } + } +} + +# ::tk::table::ExtendSelect -- +# +# Does nothing unless we're in extended selection mode; in this +# case it moves the location cursor (active element) by the specified +# number of cells, and extends the selection to that point. +# +# Arguments: +# w - The table widget. +# x - +1 to move down one cell, -1 to move up one cell. +# y - +1 to move right one cell, -1 to move left one cell. + +proc ::tk::table::ExtendSelect {w x y} { + if {[string compare extended [$w cget -selectmode]] || + [catch {$w index active row} r]} return + set c [$w index active col] + $w activate [incr r $x],[incr c $y] + $w see active + ::tk::table::Motion $w [$w index active] +} + +# ::tk::table::DataExtend +# +# This procedure is called for key-presses such as Shift-KEndData. +# If the selection mode isnt multiple or extend then it does nothing. +# Otherwise it moves the active element to el and, if we're in +# extended mode, extends the selection to that point. +# +# Arguments: +# w - The table widget. +# el - An integer cell number. + +proc ::tk::table::DataExtend {w el} { + set mode [$w cget -selectmode] + if {[string match extended $mode]} { + $w activate $el + $w see $el + if {[$w selection includes anchor]} {::tk::table::Motion $w $el} + } elseif {[string match multiple $mode]} { + $w activate $el + $w see $el + } +} + +# ::tk::table::SelectAll +# +# This procedure is invoked to handle the "select all" operation. +# For single and browse mode, it just selects the active element. +# Otherwise it selects everything in the widget. +# +# Arguments: +# w - The table widget. + +proc ::tk::table::SelectAll {w} { + if {[regexp {^(single|browse)$} [$w cget -selectmode]]} { + $w selection clear all + catch {$w selection set active} + } elseif {[$w cget -selecttitles]} { + $w selection set [$w cget -roworigin],[$w cget -colorigin] end + } else { + $w selection set origin end + } +} + +# ::tk::table::ChangeWidth -- +# +# Adjust the widget of the specified cell by $a. +# +# Arguments: +# w - The table widget. +# i - cell index +# a - amount to adjust by + +proc ::tk::table::ChangeWidth {w i a} { + set tmp [$w index $i col] + if {[set width [$w width $tmp]] >= 0} { + $w width $tmp [incr width $a] + } else { + $w width $tmp [incr width [expr {-$a}]] + } +} + +# tk_tableCopy -- +# +# This procedure copies the selection from a table widget into the +# clipboard. +# +# Arguments: +# w - Name of a table widget. + +proc tk_tableCopy w { + if {[selection own -displayof $w] == "$w"} { + clipboard clear -displayof $w + catch {clipboard append -displayof $w [::tk::table::GetSelection $w]} + } +} + +# tk_tableCut -- +# +# This procedure copies the selection from a table widget into the +# clipboard, then deletes the selection (if it exists in the given +# widget). +# +# Arguments: +# w - Name of a table widget. + +proc tk_tableCut w { + if {[selection own -displayof $w] == "$w"} { + clipboard clear -displayof $w + catch { + clipboard append -displayof $w [::tk::table::GetSelection $w] + $w cursel {} + $w selection clear all + } + } +} + +# tk_tablePaste -- +# +# This procedure pastes the contents of the clipboard to the specified +# cell (active by default) in a table widget. +# +# Arguments: +# w - Name of a table widget. +# cell - Cell to start pasting in. +# +proc tk_tablePaste {w {cell {}}} { + if {[string compare {} $cell]} { + if {[catch {::tk::table::GetSelection $w} data]} return + } else { + if {[catch {::tk::table::GetSelection $w CLIPBOARD} data]} { + return + } + set cell active + } + tk_tablePasteHandler $w [$w index $cell] $data + if {[$w cget -state] == "normal"} {focus $w} +} + +# tk_tablePasteHandler -- +# +# This procedure handles how data is pasted into the table widget. +# This handles data in the default table selection form. +# +# NOTE: this allows pasting into all cells except title cells, +# even those with -state disabled +# +# Arguments: +# w - Name of a table widget. +# cell - Cell to start pasting in. +# +proc tk_tablePasteHandler {w cell data} { + # + # Don't allow pasting into the title cells + # + if {[$w tag includes title $cell]} { + return + } + + set rows [expr {[$w cget -rows]-[$w cget -roworigin]}] + set cols [expr {[$w cget -cols]-[$w cget -colorigin]}] + set r [$w index $cell row] + set c [$w index $cell col] + set rsep [$w cget -rowseparator] + set csep [$w cget -colseparator] + ## Assume separate rows are split by row separator if specified + ## If you were to want multi-character row separators, you would need: + # regsub -all $rsep $data <newline> data + # set data [join $data <newline>] + if {[string compare {} $rsep]} { set data [split $data $rsep] } + set row $r + foreach line $data { + if {$row > $rows} break + set col $c + ## Assume separate cols are split by col separator if specified + ## Unless a -separator was specified + if {[string compare {} $csep]} { set line [split $line $csep] } + ## If you were to want multi-character col separators, you would need: + # regsub -all $csep $line <newline> line + # set line [join $line <newline>] + foreach item $line { + if {$col > $cols} break + $w set $row,$col $item + incr col + } + incr row + } +} + +# tk::table::Sort -- +# +# This procedure handles how data is sorted in the table widget. +# This isn't currently used by tktable, but can be called by the user. +# It's behavior may change in the future. +# +# Arguments: +# w - Name of a table widget. +# start - start cell of rectangle to sort +# end - end cell of rectangle to sort +# col - column within rectangle to sort on +# args - passed to lsort proc (ie: -integer -decreasing) + +proc ::tk::table::Sort {w start end col args} { + set start [$w index $start] + set end [$w index $end] + scan $start %d,%d sr sc + scan $end %d,%d er ec + if {($col < $sc) || ($col > $ec)} { + return -code error "$col is not within sort range $sc to $ec" + } + set col [expr {$col - $sc}] + + set data {} + for {set i $sr} {$i <= $er} {incr i} { + lappend data [$w get $i,$sc $i,$ec] + } + + set i $sr + foreach row [eval [list lsort -index $col] $args [list $data]] { + $w set row $i,$sc $row + incr i + } +} diff --git a/library/tktable.py b/library/tktable.py new file mode 100755 index 0000000..d293f9f --- /dev/null +++ b/library/tktable.py @@ -0,0 +1,651 @@ +# Updated tktable.py wrapper for Python 2.x with Tkinter. +# Improvements over previous version can be seen at: +# https://sf.net/tracker2/?func=detail&aid=2244167&group_id=11464&atid=311464 +# +# Copyright (c) 2008, Guilherme Polo +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. + +""" +This contains a wrapper class for the tktable widget as well a class for using +tcl arrays that are, in some instances, required by tktable. +""" + +__author__ = "Guilherme Polo <ggpolo@gmail.com>" + +__all__ = ["ArrayVar", "Table"] + +import Tkinter + +def _setup_master(master): + if master is None: + if Tkinter._support_default_root: + master = Tkinter._default_root or Tkinter.Tk() + else: + raise RuntimeError("No master specified and Tkinter is " + "configured to not support default master") + return master + +class ArrayVar(Tkinter.Variable): + def __init__(self, master=None, name=None): + # Tkinter.Variable.__init__ is not called on purpose! I don't wanna + # see an ugly _default value in the pretty array. + self._master = _setup_master(master) + self._tk = self._master.tk + if name: + self._name = name + else: + self._name = 'PY_VAR%s' % id(self) + + def get(self, index=None): + if index is None: + res = {} + for key in self.names(): + res[key] = self._tk.globalgetvar(str(self), key) + return res + + return self._tk.globalgetvar(str(self), str(index)) + + def names(self): + return self._tk.call('array', 'names', self._name) + + def set(self, key, value): + self._tk.globalsetvar(str(self), str(key), value) + + +class Table(Tkinter.Widget): + """Create and manipulate tables.""" + + _switches = ('holddimensions', 'holdselection', 'holdtags', 'holdwindows', + 'keeptitles', '-') + _tabsubst_format = ('%c', '%C', '%i', '%r', '%s', '%S', '%W') + _tabsubst_commands = ('browsecommand', 'browsecmd', 'command', + 'selectioncommand', 'selcmd', + 'validatecommand', 'valcmd') + + def __init__(self, master=None, **kw): + master = _setup_master(master) + try: + master.tk.call('package', 'require', 'Tktable') + except Tkinter.TclError: + try: + master.tk.call('load', 'Tktable.dll', 'Tktable') + except Tkinter.TclError: + master.tk.call('load', '', 'Tktable') + + Tkinter.Widget.__init__(self, master, 'table', kw) + + + def _options(self, cnf, kw=None): + if kw: + cnf = Tkinter._cnfmerge((cnf, kw)) + else: + cnf = Tkinter._cnfmerge(cnf) + + res = () + for k, v in cnf.iteritems(): + if callable(v): + if k in self._tabsubst_commands: + v = "%s %s" % (self._register(v, self._tabsubst), + ' '.join(self._tabsubst_format)) + else: + v = self._register(v) + res += ('-%s' % k, v) + + return res + + + def _tabsubst(self, *args): + if len(args) != len(self._tabsubst_format): + return args + + tk = self.tk + c, C, i, r, s, S, W = args + e = Tkinter.Event() + + e.widget = self + e.c = tk.getint(c) + e.i = tk.getint(i) + e.r = tk.getint(r) + e.C = (e.r, e.c) + try: + e.s = tk.getint(s) + except Tkinter.TclError: + e.s = s + try: + e.S = tk.getint(S) + except Tkinter.TclError: + e.S = S + e.W = W + + return (e,) + + + def _handle_switches(self, args): + args = args or () + return tuple(('-%s' % x) for x in args if x in self._switches) + + + def activate(self, index): + """Set the active cell to the one indicated by index.""" + self.tk.call(self._w, 'activate', index) + + + def bbox(self, first, last=None): + """Return the bounding box for the specified cell (range) as a + 4-tuple of x, y, width and height in pixels. It clips the box to + the visible portion, if any, otherwise an empty tuple is returned.""" + return self._getints(self.tk.call(self._w, 'bbox', first, last)) or () + + + def clear(self, option, first=None, last=None): + """This is a convenience routine to clear certain state information + managed by the table. first and last represent valid table indices. + If neither are specified, then the command operates on the whole + table.""" + self.tk.call(self._w, 'clear', option, first, last) + + + def clear_cache(self, first=None, last=None): + """Clear the specified section of the cache, if the table has been + keeping one.""" + self.clear('cache', first, last) + + + def clear_sizes(self, first=None, last=None): + """Clear the specified row and column areas of specific height/width + dimensions. When just one index is specified, for example 2,0, that + is interpreted as row 2 and column 0.""" + self.clear('sizes', first, last) + + + def clear_tags(self, first=None, last=None): + """Clear the specified area of tags (all row, column and cell tags).""" + self.clear('tags', first, last) + + + def clear_all(self, first=None, last=None): + """Perform all of the above clear functions on the specified area.""" + self.clear('all', first, last) + + + def curselection(self, value=None): + """With no arguments, it returns the sorted indices of the currently + selected cells. Otherwise it sets all the selected cells to the given + value if there is an associated ArrayVar and the state is not + disabled.""" + result = self.tk.call(self._w, 'curselection', value) + if value is None: + return result + + + def curvalue(self, value=None): + """If no value is given, the value of the cell being edited (indexed + by active) is returned, else it is set to the given value. """ + return self.tk.call(self._w, 'curvalue', value) + + + def delete_active(self, index1, index2=None): + """Deletes text from the active cell. If only one index is given, + it deletes the character after that index, otherwise it deletes from + the first index to the second. index can be a number, insert or end.""" + self.tk.call(self._w, 'delete', 'active', index1, index2) + + + def delete_cols(self, index, count=None, switches=None): + args = self._handle_switches(switches) + (index, count) + self.tk.call(self._w, 'delete', 'cols', *args) + + + def delete_rows(self, index, count=None, switches=None): + args = self._handle_switches(switches) + (index, count) + self.tk.call(self._w, 'delete', 'rows', *args) + + + def get(self, first, last=None): + """Returns the value of the cells specified by the table indices + first and (optionally) last.""" + return self.tk.call(self._w, 'get', first, last) + + + def height(self, row=None, **kwargs): + """If row and kwargs are not given, a list describing all rows for + which a width has been set is returned. + If row is given, the height of that row is returnd. + If kwargs is given, then it sets the key/value pairs, where key is a + row and value represents the height for the row.""" + if row is None and not kwargs: + pairs = self.tk.splitlist(self.tk.call(self._w, 'height')) + return dict(pair.split() for pair in pairs) + elif row: + return int(self.tk.call(self._w, 'height', str(row))) + + args = Tkinter._flatten(kwargs.items()) + self.tk.call(self._w, 'height', *args) + + + def hidden(self, *args): + """When called without args, it returns all the hidden cells (those + cells covered by a spanning cell). If one index is specified, it + returns the spanning cell covering that index, if any. If multiple + indices are specified, it returns 1 if all indices are hidden cells, + 0 otherwise.""" + return self.tk.call(self._w, 'hidden', *args) + + + def icursor(self, arg=None): + """If arg is not specified, return the location of the insertion + cursor in the active cell. Otherwise, set the cursor to that point in + the string. + + 0 is before the first character, you can also use insert or end for + the current insertion point or the end of the text. If there is no + active cell, or the cell or table is disabled, this will return -1.""" + return self.tk.call(self._w, 'icursor', arg) + + + def index(self, index, rc=None): + """Return the integer cell coordinate that corresponds to index in the + form row, col. If rc is specified, it must be either 'row' or 'col' so + only the row or column index is returned.""" + res = self.tk.call(self._w, 'index', index, rc) + if rc is None: + return res + else: + return int(res) + + + def insert_active(self, index, value): + """The value is a text string which is inserted at the index postion + of the active cell. The cursor is then positioned after the new text. + index can be a number, insert or end. """ + self.tk.call(self._w, 'insert', 'active', index, value) + + + def insert_cols(self, index, count=None, switches=None): + args = self._handle_switches(switches) + (index, count) + self.tk.call(self._w, 'insert', 'cols', *args) + + + def insert_rows(self, index, count=None, switches=None): + args = self._handle_switches(switches) + (index, count) + self.tk.call(self._w, 'insert', 'rows', *args) + + + #def postscript(self, **kwargs): + # """Skip this command if you are under Windows. + # + # Accepted options: + # colormap, colormode, file, channel, first, fontmap, height, + # last, pageanchor, pageheight, pagewidth, pagex, pagey, rotate, + # width, x, y + # """ + # args = () + # for key, val in kwargs.iteritems(): + # args += ('-%s' % key, val) + # + # return self.tk.call(self._w, 'postscript', *args) + + + def reread(self): + """Rereads the old contents of the cell back into the editing buffer. + Useful for a key binding when <Escape> is pressed to abort the edit + (a default binding).""" + self.tk.call(self._w, 'reread') + + + def scan_mark(self, x, y): + self.tk.call(self._w, 'scan', 'mark', x, y) + + + def scan_dragto(self, x, y): + self.tk.call(self._w, 'scan', 'dragto', x, y) + + + def see(self, index): + self.tk.call(self._w, 'see', index) + + + def selection_anchor(self, index): + self.tk.call(self._w, 'selection', 'anchor', index) + + + def selection_clear(self, first, last=None): + self.tk.call(self._w, 'selection', 'clear', first, last) + + + def selection_includes(self, index): + return self.getboolean(self.tk.call(self._w, 'selection', 'includes', + index)) + + + def selection_set(self, first, last=None): + self.tk.call(self._w, 'selection', 'set', first, last) + + + def set(self, rc=None, index=None, *args, **kwargs): + """If rc is specified (either 'row' or 'col') then it is assumes that + args (if given) represents values which will be set into the + subsequent columns (if row is specified) or rows (for col). + If index is not None and args is not given, then it will return the + value(s) for the cell(s) specified. + + If kwargs is given, assumes that each key in kwargs is a index in this + table and sets the specified index to the associated value. Table + validation will not be triggered via this method. + + Note that the table must have an associated array (defined through the + variable option) in order to this work.""" + if not args and index is not None: + if rc: + args = (rc, index) + else: + args = (index, ) + return self.tk.call(self._w, 'set', *args) + + if rc is None: + args = Tkinter._flatten(kwargs.items()) + self.tk.call(self._w, 'set', *args) + else: + self.tk.call(self._w, 'set', rc, index, args) + + + def spans(self, index=None, **kwargs): + """Manipulate row/col spans. + + When called with no arguments, all known spans are returned as a dict. + When called with only the index, the span for that index only is + returned, if any. Otherwise kwargs is assumed to contain keys/values + pairs used to set spans. A span starts at the row,col defined by a key + and continues for the specified number of rows,cols specified by + its value. A span of 0,0 unsets any span on that cell.""" + if kwargs: + args = Tkinter._flatten(kwargs.items()) + self.tk.call(self._w, 'spans', *args) + else: + return self.tk.call(self._w, 'spans', index) + + + def tag_cell(self, tagname, *args): + return self.tk.call(self._w, 'tag', 'cell', tagname, *args) + + + def tag_cget(self, tagname, option): + return self.tk.call(self._w, 'tag', 'cget', tagname, '-%s' % option) + + + def tag_col(self, tagname, *args): + return self.tk.call(self._w, 'tag', 'col', tagname, *args) + + + def tag_configure(self, tagname, option=None, **kwargs): + """Query or modify options associated with the tag given by tagname. + + If no option is specified, a dict describing all of the available + options for tagname is returned. If option is specified, then the + command returns a list describing the one named option. Lastly, if + kwargs is given then it corresponds to option-value pairs that should + be modified.""" + if option is None and not kwargs: + split1 = self.tk.splitlist( + self.tk.call(self._w, 'tag', 'configure', tagname)) + + result = {} + for item in split1: + res = self.tk.splitlist(item) + result[res[0]] = res[1:] + + return result + + elif option: + return self.tk.call(self._w, 'tag', 'configure', tagname, + '-%s' % option) + + else: + args = () + for key, val in kwargs.iteritems(): + args += ('-%s' % key, val) + + self.tk.call(self._w, 'tag', 'configure', tagname, *args) + + + def tag_delete(self, tagname): + self.tk.call(self._w, 'tag', 'delete', tagname) + + + def tag_exists(self, tagname): + return self.getboolean(self.tk.call(self._w, 'tag', 'exists', tagname)) + + + def tag_includes(self, tagname, index): + return self.getboolean(self.tk.call(self._w, 'tag', 'includes', + tagname, index)) + + + def tag_lower(self, tagname, belowthis=None): + self.tk.call(self._w, 'tag', 'lower', belowthis) + + + def tag_names(self, pattern=None): + return self.tk.call(self._w, 'tag', 'names', pattern) + + + def tag_raise(self, tagname, abovethis=None): + self.tk.call(self._w, 'tag', 'raise', tagname, abovethis) + + + def tag_row(self, tagname, *args): + return self.tk.call(self._w, 'tag', 'row', tagname, *args) + + + def validate(self, index): + """Explicitly validates the specified index based on the current + callback set for the validatecommand option. Return 0 or 1 based on + whether the cell was validated.""" + return self.tk.call(self._w, 'validate', index) + + + @property + def version(self): + """Return tktable's package version.""" + return self.tk.call(self._w, 'version') + + + def width(self, column=None, **kwargs): + """If column and kwargs are not given, a dict describing all columns + for which a width has been set is returned. + If column is given, the width of that column is returnd. + If kwargs is given, then it sets the key/value pairs, where key is a + column and value represents the width for the column.""" + if column is None and not kwargs: + pairs = self.tk.splitlist(self.tk.call(self._w, 'width')) + return dict(pair.split() for pair in pairs) + elif column: + return int(self.tk.call(self._w, 'width', str(column))) + + args = Tkinter._flatten(kwargs.items()) + self.tk.call(self._w, 'width', *args) + + + def window_cget(self, index, option): + return self.tk.call(self._w, 'window', 'cget', index, option) + + + def window_configure(self, index, option=None, **kwargs): + """Query or modify options associated with the embedded window given + by index. This should also be used to add a new embedded window into + the table. + + If no option is specified, a dict describing all of the available + options for index is returned. If option is specified, then the + command returns a list describing the one named option. Lastly, if + kwargs is given then it corresponds to option-value pairs that should + be modified.""" + if option is None and not kwargs: + return self.tk.call(self._w, 'window', 'configure', index) + elif option: + return self.tk.call(self._w, 'window', 'configure', index, + '-%s' % option) + else: + args = () + for key, val in kwargs.iteritems(): + args += ('-%s' % key, val) + + self.tk.call(self._w, 'window', 'configure', index, *args) + + + def window_delete(self, *indexes): + self.tk.call(self._w, 'window', 'delete', *indexes) + + + def window_move(self, index_from, index_to): + self.tk.call(self._w, 'window', 'move', index_from, index_to) + + + def window_names(self, pattern=None): + return self.tk.call(self._w, 'window', 'names', pattern) + + + def xview(self, index=None): + """If index is not given a tuple containing two fractions is returned, + each fraction is between 0 and 1. Together they describe the + horizontal span that is visible in the window. + + If index is given the view in the window is adjusted so that the + column given by index is displayed at the left edge of the window.""" + res = self.tk.call(self._w, 'xview', index) + if index is None: + return self._getdoubles(res) + + + def xview_moveto(self, fraction): + """Adjusts the view in the window so that fraction of the total width + of the table text is off-screen to the left. The fraction parameter + must be a fraction between 0 and 1.""" + self.tk.call(self._w, 'xview', 'moveto', fraction) + + + def xview_scroll(self, number, what): + """Shift the view in the window left or right according to number and + what. The 'number' parameter must be an integer. The 'what' parameter + must be either units or pages or an abbreviation of one of these. + + If 'what' is units, the view adjusts left or right by number cells on + the display; if it is pages then the view adjusts by number screenfuls. + If 'number' is negative then cells farther to the left become visible; + if it is positive then cells farther to the right become visible. """ + self.tk.call(self._w, 'xview', 'scroll', number, what) + + + def yview(self, index=None): + """If index is not given a tuple containing two fractions is returned, + each fraction is between 0 and 1. The first element gives the position + of the table element at the top of the window, relative to the table + as a whole. The second element gives the position of the table element + just after the last one in the window, relative to the table as a + whole. + + If index is given the view in the window is adjusted so that the + row given by index is displayed at the top of the window.""" + res = self.tk.call(self._w, 'yview', index) + if index is None: + return self._getdoubles(res) + + + def yview_moveto(self, fraction): + """Adjusts the view in the window so that the element given by + fraction appears at the top of the window. The fraction parameter + must be a fraction between 0 and 1.""" + self.tk.call(self._w, 'yview', 'moveto', fraction) + + + def yview_scroll(self, number, what): + """Adjust the view in the window up or down according to number and + what. The 'number' parameter must be an integer. The 'what' parameter + must be either units or pages or an abbreviation of one of these. + + If 'what' is units, the view adjusts up or down by number cells; if it + is pages then the view adjusts by number screenfuls. + If 'number' is negative then earlier elements become visible; if it + is positive then later elements become visible. """ + self.tk.call(self._w, 'yview', 'scroll', number, what) + + +# Sample test taken from tktable cvs, original tktable python wrapper +def sample_test(): + from Tkinter import Tk, Label, Button + + def test_cmd(event): + if event.i == 0: + return '%i, %i' % (event.r, event.c) + else: + return 'set' + + def browsecmd(event): + print "event:", event.__dict__ + print "curselection:", test.curselection() + print "active cell index:", test.index('active') + print "active:", test.index('active', 'row') + print "anchor:", test.index('anchor', 'row') + + root = Tk() + + var = ArrayVar(root) + for y in range(-1, 4): + for x in range(-1, 5): + index = "%i,%i" % (y, x) + var.set(index, index) + + label = Label(root, text="Proof-of-existence test for Tktable") + label.pack(side = 'top', fill = 'x') + + quit = Button(root, text="QUIT", command=root.destroy) + quit.pack(side = 'bottom', fill = 'x') + + test = Table(root, + rows=10, + cols=5, + state='disabled', + width=6, + height=6, + titlerows=1, + titlecols=1, + roworigin=-1, + colorigin=-1, + selectmode='browse', + selecttype='row', + rowstretch='unset', + colstretch='last', + browsecmd=browsecmd, + flashmode='on', + variable=var, + usecommand=0, + command=test_cmd) + test.pack(expand=1, fill='both') + test.tag_configure('sel', background = 'yellow') + test.tag_configure('active', background = 'blue') + test.tag_configure('title', anchor='w', bg='red', relief='sunken') + root.mainloop() + +if __name__ == '__main__': + sample_test() |