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

Last change on this file since 2742 was 1865, checked in by mmc, 14 years ago

Fixed the Scroller to favor keeping the scrollbar on screen, so we don't
get stuck in a mode where they disappear.

Fixed the Panes widget to do the layout with reqwidth instead of width.
When the widget first appears, the sashes have a width of 1 pixel, but
they immediately show up as 10 pixels, so the layout was off.

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