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

Last change on this file since 1652 was 1652, checked in by gah, 15 years ago
File size: 14.5 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 scrollset {
80        blt::scrollset $itk_interior.ss
81    }
82    itk_component add xsbar {
83        blt::tk::scrollbar $itk_interior.xsbar -orient horizontal
84    }
85    itk_component add ysbar {
86        blt::tk::scrollbar $itk_interior.ysbar -orient vertical
87    }
88
89    # we don't fix scrollbars when window is withdrawn, so
90    # fix them whenever a window pops up
91    bind $itk_component(hull) <Map> "
92        [itcl::code $this _fixsbar x]
93        [itcl::code $this _fixsbar y]
94    "
95
96    grid rowconfigure $itk_component(hull) 1 -weight 1
97    grid columnconfigure $itk_component(hull) 1 -weight 1
98
99    eval itk_initialize $args
100}
101
102# ----------------------------------------------------------------------
103# DESTRUCTOR
104# ----------------------------------------------------------------------
105itcl::body Rappture::Scroller::destructor {} {
106    after cancel [itcl::code $this _lock reset x]
107    after cancel [itcl::code $this _lock reset y]
108}
109
110# ----------------------------------------------------------------------
111# USAGE: contents ?<widget>|frame?
112#
113# Used to get/set the widget that is being scrolled.  With no args,
114# it returns the name of the widget currently connected to the
115# scrollbars.  Otherwise, the argument specifies a widget to be
116# controlled by the scrollbar.  If the argument is the keyword
117# "frame", then this method creates its own internal frame, which
118# can be packed with other widgets, and returns its name.
119# ----------------------------------------------------------------------
120itcl::body Rappture::Scroller::contents {{widget "!@#query"}} {
121    if {$widget == "!@#query"} {
122        if {$_contents == $_frame} {
123            return $_frame.f
124        }
125        return $_contents
126    }
127
128    #
129    # If the widget is "", then unhook any existing widget.
130    #
131    if {$widget == ""} {
132        if {$_contents != ""} {
133            $_contents configure -xscrollcommand "" -yscrollcommand ""
134            grid forget $_contents
135        }
136        $itk_component(xsbar) configure -command ""
137        $itk_component(ysbar) configure -command ""
138        set _contents ""
139
140        return ""
141    }
142
143    #
144    # For the "frame" keyword, create a canvas that can be scrolled
145    # and return it as the frame being scrolled.
146    #
147    if {$widget == "frame"} {
148        if {$_frame == ""} {
149            set _frame [canvas $itk_component(hull).ifr -highlightthickness 0]
150            frame $_frame.f
151            $_frame create window 0 0 -anchor nw -window $_frame.f -tags frame
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
158    }
159
160    #
161    # Plug the specified widget into the scrollbars for this widget.
162    #
163    contents ""
164    grid $widget -row 1 -column 1 -sticky nsew
165    $widget configure \
166        -xscrollcommand [itcl::code $this _widget2sbar x] \
167        -yscrollcommand [itcl::code $this _widget2sbar y]
168
169    $itk_component(xsbar) configure -command [list $widget xview]
170    $itk_component(ysbar) configure -command [list $widget yview]
171    set _contents $widget
172
173    if {[string equal "x11" [tk windowingsystem]]} {
174        bind ${widget} <4> { %W yview scroll -5 units }
175        bind ${widget} <5> { %W yview scroll 5 units }
176    } else {
177        bind ${widget} <MouseWheel> {
178            %W yview scroll [expr {- (%D / 120) * 4}] units
179        }
180    }
181    if {$widget == $_frame} {
182        return $_frame.f
183    }
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"} {
196        _fixsbar x
197    }
198    if {$itk_option(-yscrollmode) == "auto"} {
199        _fixsbar y
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 ""}} {
213    if {![winfo ismapped $itk_component(hull)]} {
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
220    }
221
222    if {$state == ""} {
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        }
239    }
240
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
266    # show/hide the scrollbar depending on the desired state
267    switch -- $which {
268        x {
269            if {$state} {
270                if {![_lock active x]} {
271                    # grid $itk_component(xsbar) -row 1 -column 0 -sticky ew
272                    if {$col == 1} {
273                        grid $itk_component(xsbar) -row $row -column $col -sticky ew
274                    }
275                }
276            } else {
277                grid forget $itk_component(xsbar)
278                _lock set x
279            }
280        }
281        y {
282            if {$state} {
283                if {![_lock active y]} {
284                    # grid $itk_component(ysbar) -row 0 -column 1 -sticky ns
285                    if {$row == 1} {
286                        grid $itk_component(ysbar) -row $row -column $col -sticky ns
287                    }
288                }
289            } else {
290                grid forget $itk_component(ysbar)
291                _lock set y
292            }
293        }
294    }
295}
296
297# ----------------------------------------------------------------------
298# USAGE: _fixframe <which>
299#
300# Invoked automatically whenever the canvas representing the "frame"
301# keyword is resized.  Updates the scrolling limits for the canvas
302# to the new size.
303# ----------------------------------------------------------------------
304itcl::body Rappture::Scroller::_fixframe {which} {
305    switch -- $which {
306        inner {
307            $_frame configure -scrollregion [$_frame bbox all]
308            $_dispatcher event -idle !fixsize
309        }
310        outer {
311            if {[winfo width $_frame] > [winfo reqwidth $_frame.f]} {
312                $_frame itemconfigure frame -width [winfo width $_frame]
313            } else {
314                $_frame itemconfigure frame -width 0
315            }
316            if {[winfo height $_frame] > [winfo reqheight $_frame.f]} {
317                $_frame itemconfigure frame -height [winfo height $_frame]
318            } else {
319                $_frame itemconfigure frame -height 0
320            }
321        }
322    }
323}
324
325# ----------------------------------------------------------------------
326# USAGE: _fixsize
327#
328# Used internally to update the size options for the widget
329# whenever the -width/-height options change.
330# ----------------------------------------------------------------------
331itcl::body Rappture::Scroller::_fixsize {} {
332    if {$itk_option(-width) == "0" && $itk_option(-height) == "0"} {
333        # for default size, let the frame being controlled set the size
334        grid propagate $itk_component(hull) yes
335        if {$_frame == "$itk_component(hull).ifr"} {
336            set w [winfo reqwidth $_frame.f]
337            set h [winfo reqheight $_frame.f]
338            $_frame configure -width $w -height $h
339        }
340    } else {
341        # for specific size, set the overall size of the widget
342        grid propagate $itk_component(hull) no
343        set w $itk_option(-width); if {$w == "0"} { set w 1i }
344        set h $itk_option(-height); if {$h == "0"} { set h 1i }
345        component hull configure -width $w -height $h
346    }
347}
348
349# ----------------------------------------------------------------------
350# USAGE: _lock set <which>
351# USAGE: _lock reset <which>
352# USAGE: _lock active <which>
353#
354# Used internally to lock out vibrations when the x-scrollbar pops
355# into view.  When the x-scrollbar pops up, it reduces the space
356# available for the widget.  For some widgets (e.g., text widget)
357# this changes the view.  A long line may fall off screen, and the
358# x-scrollbar will no longer be necessary.  If the x-scrollbar just
359# appeared, then its lock is active, signalling that it should stay
360# up.
361# ----------------------------------------------------------------------
362itcl::body Rappture::Scroller::_lock {option which} {
363    switch -- $option {
364        set {
365            set _lock($which) 1
366            after cancel [itcl::code $this _lock reset $which]
367            after 50 [itcl::code $this _lock reset $which]
368        }
369        reset {
370            set _lock($which) 0
371        }
372        active {
373            return $_lock($which)
374        }
375        default {
376            error "bad option \"$option\": should be set, reset, active"
377        }
378    }
379}
380
381# ----------------------------------------------------------------------
382# OPTION: -xscrollmode
383# ----------------------------------------------------------------------
384itcl::configbody Rappture::Scroller::xscrollmode {
385    _fixsbar x
386}
387
388# ----------------------------------------------------------------------
389# OPTION: -yscrollmode
390# ----------------------------------------------------------------------
391itcl::configbody Rappture::Scroller::yscrollmode {
392    _fixsbar y
393}
394
395# ----------------------------------------------------------------------
396# OPTION: -xscrollside
397# ----------------------------------------------------------------------
398itcl::configbody Rappture::Scroller::xscrollside {
399    _fixsbar x
400}
401
402# ----------------------------------------------------------------------
403# OPTION: -yscrollside
404# ----------------------------------------------------------------------
405itcl::configbody Rappture::Scroller::yscrollside {
406    _fixsbar y
407}
408
409# ----------------------------------------------------------------------
410# OPTION: -width
411# ----------------------------------------------------------------------
412itcl::configbody Rappture::Scroller::width {
413    # check for proper value
414    winfo pixels $itk_component(hull) $itk_option(-width)
415
416    $_dispatcher event -idle !fixsize
417}
418
419# ----------------------------------------------------------------------
420# OPTION: -height
421# ----------------------------------------------------------------------
422itcl::configbody Rappture::Scroller::height {
423    # check for proper value
424    winfo pixels $itk_component(hull) $itk_option(-height)
425
426    $_dispatcher event -idle !fixsize
427}
Note: See TracBrowser for help on using the repository browser.