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

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