Posted to tcl by jenglish at Wed Nov 17 22:13:45 GMT 2010view pretty

### scrollview metawidget.
#
# Usage:
#	scrollview $sv
#	$sv manage $sv.child
#	$sv see $sv.$descendant...
#

package provide scrollview 1.1

namespace eval scrollview {
    # State variables:
    #	win 		-- renamed original widget command
    #	slave		-- managed slave.
    #   [xy].constrain	-- 1 => constrain; 0 => scroll
    #	[xy].command	-- scroll command
    #	[xy].first	-- current scroll position
    #	[xy].requested	-- requested size from slave
    #	[xy].available	-- available size
    #	[xy].increment	-- default increment for [$sv scroll $n units]
    #
    variable defaults {
	win		""
	slave		""
	x.constrain	1
	y.constrain	1
	x.command	scrollview::nop
	y.command	scrollview::nop
	x.first		0
	y.first		0
	x.requested	0
	y.requested	0
	x.increment	10
	y.increment	10
    }
    proc min {a b} { expr {$a < $b ? $a : $b} }
    proc max {a b} { expr {$a > $b ? $a : $b} }
    proc nop {args} {}
}

### Defaults.
#
# Default -width 10c and -height 7c are the BWidget ScrollableFrame defaults,
# which in turn are taken from the core Canvas widget.
#
option add *Scrollview.width 	10c
option add *Scrollview.height 	7c

### Bindings.
#
bind Scrollview <Destroy>	{ scrollview::Destroy %W }
bind Scrollview <Configure>	{ scrollview::Resize %W }

proc scrollview::Destroy {w} {
    upvar #0 $w W
    unset -nocomplain W
    rename $w {}
}

proc scrollview::Resize {w} {
    upvar #0 $w W
    Refresh $w
    set W(x.first) [First $w x $W(x.first)]
    set W(y.first) [First $w y $W(y.first)]
    PlaceSlave $w
    Notify $w {x y}
}

### Internal utilities.
#

## PlaceSlave -- adjust position of slave window.
#
proc scrollview::PlaceSlave {w} {
    upvar #0 $w W
    if {$W(slave) eq ""} { return }
    place $W(slave) -in $w \
    	-x [expr {-$W(x.first)}] \
	-y [expr {-$W(y.first)}] \
	-width  [SlaveSize $w x] \
	-height [SlaveSize $w y] \
	;
}

## SlaveSize --
#
proc scrollview::SlaveSize {w axis} {
    upvar #0 $w W

    if {$W($axis.constrain)} {
    	return $W($axis.available)
    } else {
    	return $W($axis.requested)
    }
    # Other possibilities:
    #	stretch	-- [max $W($axis.available) $W($axis.requested)]
    #	shrink	-- [min $W($axis.available) $W($axis.requested)] (not useful)
}

## Refresh -- update sizes
#
proc scrollview::Refresh {w} {
    upvar #0 $w W
    set W(x.available) [winfo width  $w]
    set W(y.available) [winfo height $w]
    if {$W(slave) ne ""} {
	set W(x.requested) [winfo reqwidth  $W(slave)]
	set W(y.requested) [winfo reqheight $W(slave)]
    }
}

## Notify $w ?$axes? -- invoke -[xy]scrollcommands
#
proc scrollview::Notify {w {axes "x y"}} {
    upvar #0 $w W
    foreach axis $axes {
	set total [max 1.0 [expr { double($W($axis.requested)) }]]
	set first [expr { double($W($axis.first))/$total } ]
	set last  [expr { double($W($axis.first)+$W($axis.available))/$total }]
	uplevel #0 [linsert $W($axis.command) end $first $last]
    }
}

## First --
#
proc scrollview::First {w axis pos} {
    upvar #0 $w W
    set lim [expr {$W($axis.requested) - $W($axis.available)}]
    return [max 0 [min $lim $pos]]
}

proc scrollview::SetFirst {w axis pos} {
    upvar #0 $w W
    set W($axis.first) [First $w $axis $pos]
    PlaceSlave $w
    Notify $w $axis
}

## Widget constructor.
#
interp alias {} ::scrollview {} ::scrollview::Constructor
proc scrollview::Constructor {w args} {
    upvar #0 $w W
    variable defaults

    array set W $defaults

    frame $w -class Scrollview
    rename $w [set W(win) ::scrollview::_$w]
    interp alias {} $w {} ::scrollview::Dispatch $w

    uplevel 1 [linsert $args 0 $w configure]

    return $w
}

proc scrollview::Dispatch {w command args} {
    uplevel 1 [linsert $args 0 [namespace which -command $command] $w]
}

### Widget methods:
#

## $sv configure option value...
#
proc scrollview::configure {w args} {
    upvar #0 $w W

    set frameopts [list]
    foreach {option value} $args {
	switch -- $option {
	    -xscrollcommand	{ set W(x.command) $value;set W(x.constrain) 0 }
	    -yscrollcommand	{ set W(y.command) $value;set W(y.constrain) 0 }
	    -xscrollincrement	{ set W(x.increment) $value }
	    -yscrollincrement	{ set W(y.increment) $value }
	    -width		-
	    -height		{ lappend frameopts $option $value }
	    default		{ error "Unrecognized option $option" }
	}
    }
    if {[llength $frameopts]} {
	eval [linsert $frameopts 0 $W(win) configure]
    }
}

## $sv manage $child
#
proc scrollview::manage {w slave} {
    upvar #0 $w W
    if {$W(slave) ne ""} {
	place forget $W(slave)
    }
    set W(slave) $slave
    Resize $w
}

## $sv xview, $sv yview -- standard scrolling methods.
#
proc scrollview::xview {w method amount {units ""}} {
    XYview $w x $method $amount $units
}

proc scrollview::yview {w method amount {units ""}} {
    XYview $w y $method $amount $units
}

proc scrollview::XYview {w axis method amount units} {
    switch -- $method {
	moveto	{ MoveTo $w $axis $amount }
	scroll	{ Scroll-$units $w $axis $amount }
	default { error "Unknown scroll directive $method" }
    }
}

proc scrollview::MoveTo {w axis fraction} {
    upvar #0 $w W
    SetFirst $w $axis [expr {int($fraction * $W($axis.requested))}]
}

proc scrollview::Scroll-units {w axis count} {
    upvar #0 $w W
    SetFirst $w $axis [expr {$W($axis.first) + $W($axis.increment)*$count}]
}
proc scrollview::Scroll-pages {w axis count} {
    upvar #0 $w W
    SetFirst $w $axis [expr {$W($axis.first) + $count*$W($axis.available)}]
}

## $sv see $descendant --
#	Ensure that $descendant is visible.
#
proc scrollview::see {w descendant} {
    upvar #0 $w W
    set xpos [set ypos 0]
    set d $descendant
    while {$d ne $W(slave)} {
	incr xpos [winfo x $d]
	incr ypos [winfo y $d]
	set d [winfo parent $d]
	if {$d eq "."} {
	    return -code error "$descendant not a descendant of $w"
	}
    }

    See $w x $xpos [winfo width  $descendant]
    See $w y $ypos [winfo height $descendant]
}

proc scrollview::See {w axis pos len} {
    upvar #0 $w W
    # ENSURE: first <= pos <= pos + len <= first + available
    # If both can't be satisfied, ensure first <= pos.
    set min [expr {$pos + $len - $W($axis.available)}]
    if {$W($axis.first) < $min} { SetFirst $w $axis $min }
    if {$pos < $W($axis.first)} { SetFirst $w $axis $pos }
}

#*EOF*