source: branches/blt4/gui/scripts/scroller.tcl @ 1897

Last change on this file since 1897 was 1852, checked in by gah, 14 years ago
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 {![_lock active x]} {
270                    # grid $itk_component(xsbar) -row 1 -column 0 -sticky ew
271                    if {$col == 1} {
272                        grid $itk_component(xsbar) -row $row -column $col -sticky ew
273                    }
274                }
275            } else {
276                grid forget $itk_component(xsbar)
277                _lock set x
278            }
279        }
280        y {
281            if {$state} {
282                if {![_lock active y]} {
283                    # grid $itk_component(ysbar) -row 0 -column 1 -sticky ns
284                    if {$row == 1} {
285                        grid $itk_component(ysbar) -row $row -column $col -sticky ns
286                    }
287                }
288            } else {
289                grid forget $itk_component(ysbar)
290                _lock set y
291            }
292        }
293    }
294}
295
296# ----------------------------------------------------------------------
297# USAGE: _fixframe <which>
298#
299# Invoked automatically whenever the canvas representing the "frame"
300# keyword is resized.  Updates the scrolling limits for the canvas
301# to the new size.
302# ----------------------------------------------------------------------
303itcl::body Rappture::Scroller::_fixframe {which} {
304    switch -- $which {
305        inner {
306            set w [winfo reqwidth $_frame.f]
307            set h [winfo reqheight $_frame.f]
308            $_frame configure -scrollregion [list 0 0 $w $h]
309            _fixframe outer
310            _lock reset x
311            _lock reset y
312            $_dispatcher event -idle !fixsize
313        }
314        outer {
315            if {[winfo width $_frame] > [winfo reqwidth $_frame.f]} {
316                $_frame itemconfigure frame -width [winfo width $_frame]
317            } else {
318                $_frame itemconfigure frame -width 0
319            }
320            if {[winfo height $_frame] > [winfo reqheight $_frame.f]} {
321                $_frame itemconfigure frame -height [winfo height $_frame]
322            } else {
323                $_frame itemconfigure frame -height 0
324            }
325        }
326    }
327}
328
329# ----------------------------------------------------------------------
330# USAGE: _fixsize
331#
332# Used internally to update the size options for the widget
333# whenever the -width/-height options change.
334# ----------------------------------------------------------------------
335itcl::body Rappture::Scroller::_fixsize {} {
336    if {$itk_option(-width) == "0" && $itk_option(-height) == "0"} {
337        # for default size, let the frame being controlled set the size
338        grid propagate $itk_component(hull) yes
339        if {$_frame == "$itk_component(hull).ifr"} {
340            set w [winfo reqwidth $_frame.f]
341            set h [winfo reqheight $_frame.f]
342            $_frame configure -width $w -height $h
343        }
344    } else {
345        # for specific size, set the overall size of the widget
346        grid propagate $itk_component(hull) no
347        set w $itk_option(-width); if {$w == "0"} { set w 1i }
348        set h $itk_option(-height); if {$h == "0"} { set h 1i }
349        component hull configure -width $w -height $h
350    }
351}
352
353# ----------------------------------------------------------------------
354# USAGE: _lock set <which>
355# USAGE: _lock reset <which>
356# USAGE: _lock active <which>
357#
358# Used internally to lock out vibrations when the x-scrollbar pops
359# into view.  When the x-scrollbar pops up, it reduces the space
360# available for the widget.  For some widgets (e.g., text widget)
361# this changes the view.  A long line may fall off screen, and the
362# x-scrollbar will no longer be necessary.  If the x-scrollbar just
363# appeared, then its lock is active, signalling that it should stay
364# up.
365# ----------------------------------------------------------------------
366itcl::body Rappture::Scroller::_lock {option which} {
367    switch -- $option {
368        set {
369            set _lock($which) 1
370            after cancel [itcl::code $this _lock reset $which]
371            after 50 [itcl::code $this _lock reset $which]
372        }
373        reset {
374            set _lock($which) 0
375        }
376        active {
377            return $_lock($which)
378        }
379        default {
380            error "bad option \"$option\": should be set, reset, active"
381        }
382    }
383}
384
385# ----------------------------------------------------------------------
386# OPTION: -xscrollmode
387# ----------------------------------------------------------------------
388itcl::configbody Rappture::Scroller::xscrollmode {
389    _fixsbar x
390}
391
392# ----------------------------------------------------------------------
393# OPTION: -yscrollmode
394# ----------------------------------------------------------------------
395itcl::configbody Rappture::Scroller::yscrollmode {
396    _fixsbar y
397}
398
399# ----------------------------------------------------------------------
400# OPTION: -xscrollside
401# ----------------------------------------------------------------------
402itcl::configbody Rappture::Scroller::xscrollside {
403    _fixsbar x
404}
405
406# ----------------------------------------------------------------------
407# OPTION: -yscrollside
408# ----------------------------------------------------------------------
409itcl::configbody Rappture::Scroller::yscrollside {
410    _fixsbar y
411}
412
413# ----------------------------------------------------------------------
414# OPTION: -width
415# ----------------------------------------------------------------------
416itcl::configbody Rappture::Scroller::width {
417    # check for proper value
418    winfo pixels $itk_component(hull) $itk_option(-width)
419
420    $_dispatcher event -idle !fixsize
421}
422
423# ----------------------------------------------------------------------
424# OPTION: -height
425# ----------------------------------------------------------------------
426itcl::configbody Rappture::Scroller::height {
427    # check for proper value
428    winfo pixels $itk_component(hull) $itk_option(-height)
429
430    $_dispatcher event -idle !fixsize
431}
Note: See TracBrowser for help on using the repository browser.