# $Id: scroll.tcl,v 2.9 2002/05/30 17:11:45 jfontain Exp $


# generic scroll widget with automatic scrollbars for scrolling widgets with the common tk scroll interface

class scroll {

    proc scroll {this scrollableClass parentPath args} composite {[new frame $parentPath] $args} {
        variable ${this}first
        variable ${this}last

        # scrollable class widget must accept the -[xy]scrollcommand options and [xy]view commands, such as canvas, text, table, ...
        set path $widget::($this,path)
        composite::manage $this [new $scrollableClass $path] scrolled\
            [new scrollbar $path -orient horizontal -highlightthickness 0] horizontal\
            [new scrollbar $path -highlightthickness 0] vertical [new frame $path] filler

        widget::configure $composite::($this,scrolled)\
            -xscrollcommand "scroll::update $this 1 0" -yscrollcommand "scroll::update $this 0 1"
        widget::configure $composite::($this,horizontal) -command "$composite::($this,scrolled,path) xview"
        widget::configure $composite::($this,vertical) -command "$composite::($this,scrolled,path) yview"

        grid propagate $widget::($this,path) 0                  ;# turn off propagation so that setting width and height is possible

        grid $composite::($this,scrolled,path) -sticky nsew -ipadx 0
        grid rowconfigure $path 0 -weight 1
        grid columnconfigure $path 0 -weight 1

        set ($this,0,1,path) $composite::($this,vertical,path)
        set ($this,1,0,path) $composite::($this,horizontal,path)
        set ($this,0,1,map) 1
        set ($this,1,0,map) 1

        for {set index -1} {$index>=-3} {incr index -1} {
            set ${this}first(0,1,$index) -2147483648
            set ${this}last(0,1,$index) -2147483648
            set ${this}first(1,0,$index) -2147483648
            set ${this}last(1,0,$index) -2147483648
        }

        composite::complete $this
    }

    proc ~scroll {this} {}

    proc options {this} {
        return [list\
            [list -automatic 1 1]\
            [list -scrollbarborderwidth $widget::option(scrollbar,borderwidth) $widget::option(scrollbar,borderwidth)]\
            [list\
                -scrollbarelementborderwidth\
                $widget::option(scrollbar,elementborderwidth) $widget::option(scrollbar,elementborderwidth)\
            ]\
            [list -scrollbarwidth $widget::option(scrollbar,width) $widget::option(scrollbar,width)]\
            [list -height $widget::option(canvas,height)]\
            [list -horizontal $($this,1,0,map) $($this,1,0,map)]\
            [list -vertical $($this,0,1,map) $($this,0,1,map)]\
            [list -width $widget::option(canvas,width)]\
        ]
    }

    proc set-automatic {this value} {
        if {$composite::($this,complete)} {
            error {option -automatic cannot be set dynamically}
        }
    }

    proc set-horizontal {this value} {
        if {$composite::($this,complete)} {
            error {option -horizontal cannot be set dynamically}
        }
        set ($this,1,0,map) $value
    }

    proc set-vertical {this value} {
        if {$composite::($this,complete)} {
            error {option -vertical cannot be set dynamically}
        }
        set ($this,0,1,map) $value
    }

    foreach option {borderwidth elementborderwidth width} {
        proc set-scrollbar$option {this value} "
            \$composite::(\$this,vertical,path) configure -$option \$value
            \$composite::(\$this,horizontal,path) configure -$option \$value
        "
    }

    proc set-height {this value} {
        $widget::($this,path) configure -height $value
    }

    proc set-width {this value} {
        $widget::($this,path) configure -width $value
    }

    proc update {this row column first last} {
        set path $($this,$row,$column,path)
        foreach {previousFirst previousLast} [$path get] {}
        set repetition [repetition $this $row $column $first $last]
        if {($first==$previousFirst)&&($last==$previousLast)} return                                                    ;# no change
        $path set $first $last                                                  ;# always set so that get always return valid values
        set visible [llength [grid info $path]]
        if {$repetition||!$composite::($this,-automatic)||(($last-$first)<1)} {
            # map scrollbar to kill a starting vibration
            if {!$visible&&$($this,$row,$column,map)} {
                grid $path -row $row -column $column -sticky nsew
                grid $composite::($this,filler,path) -sticky nsew -column 1 -row 1
            }
        } else {
            grid remove $path
            set visible [llength [grid info $($this,$column,$row,path)]]                                  ;# look at other scrollbar
            if {!$visible} {
                grid remove $composite::($this,filler,path)
            }
        }
    }

    proc repetition {this row column first last} {                   ;# check whether there is an infinitely long vibration starting
        variable ${this}first
        variable ${this}last

        set return [expr {\
            ($first==[set ${this}first($row,$column,-2)])&&\
            ($last==[set ${this}last($row,$column,-2)])&&\
            ([set ${this}first($row,$column,-1)]==[set ${this}first($row,$column,-3)])&&\
            ([set ${this}last($row,$column,-1)]==[set ${this}last($row,$column,-3)])\
        }]
        set ${this}first($row,$column,-3) [set ${this}first($row,$column,-2)]
        set ${this}last($row,$column,-3) [set ${this}last($row,$column,-2)]
        set ${this}first($row,$column,-2) [set ${this}first($row,$column,-1)]
        set ${this}last($row,$column,-2) [set ${this}last($row,$column,-1)]
        set ${this}first($row,$column,-1) $first
        set ${this}last($row,$column,-1) $last

        return $return
    }

}
