source: trunk/gui/scripts/scroller.tcl @ 3582

Last change on this file since 3582 was 3330, checked in by gah, 12 years ago

merge (by hand) with Rappture1.2 branch

File size: 14.8 KB
RevLine 
[3330]1# -*- mode: tcl; indent-tabs-mode: nil -*-
[1]2# ----------------------------------------------------------------------
3#  COMPONENT: scroller - adds scrollbars to any standard Tk widget
4#
5#  This widget provides automatic scrollbars for any standard Tk
6#  widget.  The scrolled widget should be created as a child of this
7#  widget, and is connected by calling the "contents" method.  Calling
8#  contents with the keyword "frame" creates an internal frame that
9#  allows any collection of widgets to be scrolled.
10# ======================================================================
11#  AUTHOR:  Michael McLennan, Purdue University
[3177]12#  Copyright (c) 2004-2012  HUBzero Foundation, LLC
[115]13#
14#  See the file "license.terms" for information on usage and
15#  redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES.
[1]16# ======================================================================
17package require Itk
18
19option add *Scroller.xScrollMode off widgetDefault
20option add *Scroller.yScrollMode auto widgetDefault
[1641]21option add *Scroller.xScrollSide bottom widgetDefault
22option add *Scroller.yScrollSide right widgetDefault
[1]23option add *Scroller.width 0 widgetDefault
24option add *Scroller.height 0 widgetDefault
25
26itcl::class Rappture::Scroller {
27    inherit itk::Widget
28
29    itk_option define -xscrollmode xScrollMode XScrollMode ""
30    itk_option define -yscrollmode yScrollMode YScrollMode ""
[1641]31    itk_option define -xscrollside xScrollSide XScrollSide ""
32    itk_option define -yscrollside yScrollSide YScrollSide ""
[1]33    itk_option define -width width Width 0
34    itk_option define -height height Height 0
35
36    constructor {args} { # defined below }
[464]37    destructor { # defined below }
[1]38
39    public method contents {{frame "!@#query"}}
40
41    protected method _widget2sbar {which args}
42    protected method _fixsbar {which {state ""}}
[11]43    protected method _fixframe {which}
[26]44    protected method _fixsize {}
[95]45    protected method _lock {option which}
[1]46
[26]47    private variable _dispatcher "" ;# dispatcher for !events
[1]48    private variable _contents ""   ;# widget being controlled
49    private variable _frame ""      ;# for "contents frame" calls
[95]50    private variable _lock          ;# for _lock on x/y scrollbar
[1]51}
[1641]52
[1]53itk::usual Scroller {
54    keep -background -activebackground -activerelief
55    keep -cursor
56    keep -highlightcolor -highlightthickness
57    keep -troughcolor
58}
59
60# ----------------------------------------------------------------------
61# CONSTRUCTOR
62# ----------------------------------------------------------------------
63itcl::body Rappture::Scroller::constructor {args} {
[95]64    array set _lock { x 0 y 0 }
65
[26]66    Rappture::dispatcher _dispatcher
67
68    $_dispatcher register !fixframe-inner
69    $_dispatcher dispatch $this !fixframe-inner \
[1641]70        "[itcl::code $this _fixframe inner]; list"
[26]71
72    $_dispatcher register !fixframe-outer
73    $_dispatcher dispatch $this !fixframe-outer \
[1641]74        "[itcl::code $this _fixframe outer]; list"
[26]75
76    $_dispatcher register !fixsize
77    $_dispatcher dispatch $this !fixsize \
[1641]78        "[itcl::code $this _fixsize]; list"
[26]79
[1]80    itk_component add xsbar {
[1641]81        scrollbar $itk_interior.xsbar -orient horizontal
[1]82    }
83    itk_component add ysbar {
[1641]84        scrollbar $itk_interior.ysbar -orient vertical
[1]85    }
86
[168]87    # we don't fix scrollbars when window is withdrawn, so
88    # fix them whenever a window pops up
89    bind $itk_component(hull) <Map> "
[1641]90        [itcl::code $this _fixsbar x]
91        [itcl::code $this _fixsbar y]
[168]92    "
93
[1641]94    grid rowconfigure $itk_component(hull) 1 -weight 1
95    grid columnconfigure $itk_component(hull) 1 -weight 1
[1]96
97    eval itk_initialize $args
98}
99
100# ----------------------------------------------------------------------
[464]101# DESTRUCTOR
102# ----------------------------------------------------------------------
103itcl::body Rappture::Scroller::destructor {} {
104    after cancel [itcl::code $this _lock reset x]
105    after cancel [itcl::code $this _lock reset y]
106}
107
108# ----------------------------------------------------------------------
[1]109# USAGE: contents ?<widget>|frame?
110#
111# Used to get/set the widget that is being scrolled.  With no args,
112# it returns the name of the widget currently connected to the
113# scrollbars.  Otherwise, the argument specifies a widget to be
114# controlled by the scrollbar.  If the argument is the keyword
115# "frame", then this method creates its own internal frame, which
116# can be packed with other widgets, and returns its name.
117# ----------------------------------------------------------------------
118itcl::body Rappture::Scroller::contents {{widget "!@#query"}} {
119    if {$widget == "!@#query"} {
[1375]120        if {$_contents == $_frame} {
[1641]121            return $_frame.f
[1375]122        }
[1641]123        return $_contents
[1]124    }
125
126    #
127    # If the widget is "", then unhook any existing widget.
128    #
129    if {$widget == ""} {
[1641]130        if {$_contents != ""} {
131            $_contents configure -xscrollcommand "" -yscrollcommand ""
132            grid forget $_contents
133        }
134        $itk_component(xsbar) configure -command ""
135        $itk_component(ysbar) configure -command ""
136        set _contents ""
[1]137
[1641]138        return ""
[1]139    }
140
141    #
142    # For the "frame" keyword, create a canvas that can be scrolled
143    # and return it as the frame being scrolled.
144    #
145    if {$widget == "frame"} {
[1641]146        if {$_frame == ""} {
147            set _frame [canvas $itk_component(hull).ifr -highlightthickness 0]
148            frame $_frame.f
149            $_frame create window 0 0 -anchor nw -window $_frame.f -tags frame
[1728]150            bind $_frame.f <Map> \
151                [itcl::code $_dispatcher event -idle !fixframe-inner]
[1641]152            bind $_frame.f <Configure> \
153                [itcl::code $_dispatcher event -idle !fixframe-inner]
154            bind $_frame <Configure> \
155                [itcl::code $_dispatcher event -idle !fixframe-outer]
156        }
157        set widget $_frame
[1]158    }
159
160    #
161    # Plug the specified widget into the scrollbars for this widget.
162    #
163    contents ""
[1641]164    grid $widget -row 1 -column 1 -sticky nsew
[1]165    $widget configure \
[1641]166        -xscrollcommand [itcl::code $this _widget2sbar x] \
167        -yscrollcommand [itcl::code $this _widget2sbar y]
[1]168
169    $itk_component(xsbar) configure -command [list $widget xview]
170    $itk_component(ysbar) configure -command [list $widget yview]
171    set _contents $widget
172
[1277]173    if {[string equal "x11" [tk windowingsystem]]} {
[1641]174        bind ${widget} <4> { %W yview scroll -5 units }
175        bind ${widget} <5> { %W yview scroll 5 units }
[1277]176    } else {
[1641]177        bind ${widget} <MouseWheel> {
178            %W yview scroll [expr {- (%D / 120) * 4}] units
179        }
[1277]180    }
[11]181    if {$widget == $_frame} {
[1641]182        return $_frame.f
[11]183    }
[1]184    return $widget
185}
186
187# ----------------------------------------------------------------------
188# USAGE: _widget2sbar <which> ?args...?
189#
190# Used internally to handle communication from the widget to the
191# scrollbar.  If the scrollbars are in "auto" mode, this provides a
192# hook where we can fix their display.
193# ----------------------------------------------------------------------
194itcl::body Rappture::Scroller::_widget2sbar {which args} {
195    if {$itk_option(-xscrollmode) == "auto"} {
[1641]196        _fixsbar x
[1]197    }
198    if {$itk_option(-yscrollmode) == "auto"} {
[1641]199        _fixsbar y
[1]200    }
201    eval $itk_component(${which}sbar) set $args
202}
203
204# ----------------------------------------------------------------------
205# USAGE: _fixsbar <which> ?<state>?
206#
207# Used internally to show/hide the scrollbar in the <which> direction,
208# which is either "x" or "y".  If the scrollbar is "on", then it is
209# always displayed.  If "off", never displayed.  And if "auto", then
210# it is displayed if needed for the current view.
211# ----------------------------------------------------------------------
212itcl::body Rappture::Scroller::_fixsbar {which {state ""}} {
[168]213    if {![winfo ismapped $itk_component(hull)]} {
[1641]214        #
215        # If we're not on yet screen, bail out!  This keeps bad
216        # numbers (from an empty or partially constructed widget)
217        # from prematurely influencing the scrollbar.
218        #
219        return
[168]220    }
221
[1]222    if {$state == ""} {
[1641]223        switch -- [string tolower $itk_option(-${which}scrollmode)] {
224            on - 1 - true - yes  { set state 1 }
225            off - 0 - false - no { set state 0 }
226            auto {
227                set state 0
228                if {$_contents != ""} {
229                    set lims [$_contents ${which}view]
230                    if {[lindex $lims 0] != 0 || [lindex $lims 1] != 1} {
231                        set state 1
232                    }
233                }
234            }
235            default {
236                set state 0
237            }
238        }
[1]239    }
240
[1641]241    set row 0
242    set col 0
243    switch -- [string tolower $itk_option(-${which}scrollside)] {
244        top {
245            set row 0
246            set col 1
247        }
248        bottom {
249            set row 2
250            set col 1
251        }
252        left {
253            set row 1
254            set col 0
255        }
256        right {
257            set row 1
258            set col 2
259        }
260        default {
261            set row 0
262            set col 0
263        }
264    }
265
[1]266    # show/hide the scrollbar depending on the desired state
267    switch -- $which {
[1641]268        x {
269            if {$state} {
[1865]270                if {$col == 1} {
271                    grid $itk_component(xsbar) -row $row -column $col -sticky ew
272                }
273            } else {
274                # handle the lock on the "forget" side, so scrollbar
275                # tends to appear, rather than disappear
[1641]276                if {![_lock active x]} {
[1865]277                    grid forget $itk_component(xsbar)
278                    _lock set x
[1641]279                }
280            }
281        }
282        y {
283            if {$state} {
[1865]284                if {$row == 1} {
285                    grid $itk_component(ysbar) -row $row -column $col -sticky ns
286                }
287            } else {
288                # handle the lock on the "forget" side, so scrollbar
289                # tends to appear, rather than disappear
[1641]290                if {![_lock active y]} {
[1865]291                    grid forget $itk_component(ysbar)
292                    _lock set y
[1641]293                }
294            }
295        }
[1]296    }
297}
298
299# ----------------------------------------------------------------------
[11]300# USAGE: _fixframe <which>
[1]301#
302# Invoked automatically whenever the canvas representing the "frame"
303# keyword is resized.  Updates the scrolling limits for the canvas
304# to the new size.
305# ----------------------------------------------------------------------
[11]306itcl::body Rappture::Scroller::_fixframe {which} {
307    switch -- $which {
[1641]308        inner {
[1728]309            set w [winfo reqwidth $_frame.f]
310            set h [winfo reqheight $_frame.f]
311            $_frame configure -scrollregion [list 0 0 $w $h]
[1742]312            _fixframe outer
[1728]313            _lock reset x
314            _lock reset y
[1641]315            $_dispatcher event -idle !fixsize
316        }
317        outer {
318            if {[winfo width $_frame] > [winfo reqwidth $_frame.f]} {
319                $_frame itemconfigure frame -width [winfo width $_frame]
320            } else {
321                $_frame itemconfigure frame -width 0
322            }
323            if {[winfo height $_frame] > [winfo reqheight $_frame.f]} {
324                $_frame itemconfigure frame -height [winfo height $_frame]
325            } else {
326                $_frame itemconfigure frame -height 0
327            }
328        }
[11]329    }
[1]330}
331
332# ----------------------------------------------------------------------
[26]333# USAGE: _fixsize
334#
335# Used internally to update the size options for the widget
336# whenever the -width/-height options change.
337# ----------------------------------------------------------------------
338itcl::body Rappture::Scroller::_fixsize {} {
339    if {$itk_option(-width) == "0" && $itk_option(-height) == "0"} {
[1641]340        # for default size, let the frame being controlled set the size
341        grid propagate $itk_component(hull) yes
342        if {$_frame == "$itk_component(hull).ifr"} {
343            set w [winfo reqwidth $_frame.f]
344            set h [winfo reqheight $_frame.f]
345            $_frame configure -width $w -height $h
346        }
[26]347    } else {
[1641]348        # for specific size, set the overall size of the widget
349        grid propagate $itk_component(hull) no
350        set w $itk_option(-width); if {$w == "0"} { set w 1i }
351        set h $itk_option(-height); if {$h == "0"} { set h 1i }
352        component hull configure -width $w -height $h
[26]353    }
354}
355
356# ----------------------------------------------------------------------
[95]357# USAGE: _lock set <which>
358# USAGE: _lock reset <which>
359# USAGE: _lock active <which>
[1]360#
361# Used internally to lock out vibrations when the x-scrollbar pops
362# into view.  When the x-scrollbar pops up, it reduces the space
363# available for the widget.  For some widgets (e.g., text widget)
364# this changes the view.  A long line may fall off screen, and the
365# x-scrollbar will no longer be necessary.  If the x-scrollbar just
366# appeared, then its lock is active, signalling that it should stay
367# up.
368# ----------------------------------------------------------------------
[95]369itcl::body Rappture::Scroller::_lock {option which} {
[1]370    switch -- $option {
[1641]371        set {
372            set _lock($which) 1
373            after cancel [itcl::code $this _lock reset $which]
374            after 50 [itcl::code $this _lock reset $which]
375        }
376        reset {
377            set _lock($which) 0
378        }
379        active {
380            return $_lock($which)
381        }
382        default {
383            error "bad option \"$option\": should be set, reset, active"
384        }
[1]385    }
386}
387
388# ----------------------------------------------------------------------
389# OPTION: -xscrollmode
390# ----------------------------------------------------------------------
391itcl::configbody Rappture::Scroller::xscrollmode {
392    _fixsbar x
393}
394
395# ----------------------------------------------------------------------
396# OPTION: -yscrollmode
397# ----------------------------------------------------------------------
398itcl::configbody Rappture::Scroller::yscrollmode {
399    _fixsbar y
400}
401
402# ----------------------------------------------------------------------
[1641]403# OPTION: -xscrollside
404# ----------------------------------------------------------------------
405itcl::configbody Rappture::Scroller::xscrollside {
406    _fixsbar x
407}
408
409# ----------------------------------------------------------------------
410# OPTION: -yscrollside
411# ----------------------------------------------------------------------
412itcl::configbody Rappture::Scroller::yscrollside {
413    _fixsbar y
414}
415
416# ----------------------------------------------------------------------
[1]417# OPTION: -width
418# ----------------------------------------------------------------------
419itcl::configbody Rappture::Scroller::width {
[26]420    # check for proper value
421    winfo pixels $itk_component(hull) $itk_option(-width)
422
423    $_dispatcher event -idle !fixsize
[1]424}
425
426# ----------------------------------------------------------------------
427# OPTION: -height
428# ----------------------------------------------------------------------
429itcl::configbody Rappture::Scroller::height {
[26]430    # check for proper value
431    winfo pixels $itk_component(hull) $itk_option(-height)
432
433    $_dispatcher event -idle !fixsize
[1]434}
Note: See TracBrowser for help on using the repository browser.