source: trunk/gui/scripts/sidebarframe.tcl @ 3172

Last change on this file since 3172 was 3172, checked in by gah, 12 years ago
File size: 20.0 KB
Line 
1# ----------------------------------------------------------------------
2#  COMPONENT: SidebarFrame - pop-out controls for visualization widgets
3#
4#  The sidebar provides a way to put a thin strip of controls along the
5#  side of a visualization widget, with tabs that cause control panels
6#  to pop out.  The SidebarFrame has an empty frame (component "frame")
7#  on the left and a sidebar that pops out on the right.
8# ======================================================================
9#  AUTHOR:  George Howlett, Michael McLennan, Purdue University
10#  Copyright (c) 2004-2009  Purdue Research Foundation
11#
12#  See the file "license.terms" for information on usage and
13#  redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES.
14# ======================================================================
15package require Itk
16
17option add *SidebarFrame.width 3i widgetDefault
18option add *SidebarFrame.height 3i widgetDefault
19option add *SidebarFrame.titlebarBackground #6666cc widgetDefault
20option add *SidebarFrame.titlebarForeground white widgetDefault
21option add *SidebarFrame.controlBackground gray widgetDefault
22option add *SidebarFrame*cntls*highlightBackground gray widgetDefault
23option add *SidebarFrame.sashRelief flat widgetDefault
24option add *SidebarFrame.sashActiveRelief solid widgetDefault
25option add *SidebarFrame.sashColor gray widgetDefault
26option add *SidebarFrame.sashWidth 2 widgetDefault
27option add *SidebarFrame.sashPadding 2 widgetDefault
28option add *SidebarFrame.sashCursor sb_h_double_arrow
29
30itcl::class Rappture::SidebarFrame {
31    inherit itk::Widget
32
33    itk_option define -sashrelief sashRelief Relief ""
34    itk_option define -sashactiverelief sashActiveRelief SashActiveRelief ""
35    itk_option define -sashcolor sashColor SashColor ""
36    itk_option define -sashwidth sashWidth SashWidth 0
37    itk_option define -sashpadding sashPadding SashPadding 0
38    itk_option define -sashcursor sashCursor Cursor ""
39
40    public variable resizeframe 1
41    constructor {args} { # defined below }
42
43    public method insert {pos args}
44    public method panel {which}
45    public method select {which}
46    public method pop {what}
47    public method width { size } {
48        set _width $size
49    }
50    public method enable { which }
51    public method disable { which }
52
53    protected method _toggleTab {which}
54    protected method _sash {op x}
55    protected method _fixLayout {args}
56    protected method TabIndex { which }
57
58    private variable _dispatcher ""  ;# dispatcher for !events
59    private variable _state "closed" ;# sidebar open/closed
60    private variable _panels         ;# maps panel => title, etc.
61    private variable _selected ""    ;# selected panel
62    private variable _width "auto"   ;# width adjusted by sash or "auto"
63    private variable _counter 0      ;# counter for auto-generated names
64}
65
66itk::usual SidebarFrame {
67    keep -background -cursor
68    keep -titlebarbackground -titlebarforeground
69}
70
71# ----------------------------------------------------------------------
72# CONSTRUCTOR
73# ----------------------------------------------------------------------
74itcl::body Rappture::SidebarFrame::constructor {args} {
75    itk_option add hull.width hull.height
76
77    # create a dispatcher for events
78    Rappture::dispatcher _dispatcher
79    $_dispatcher register !layout
80    $_dispatcher dispatch $this !layout [itcl::code $this _fixLayout]
81
82    # fix the layout whenever the window size changes
83    bind SidebarFrame <Configure> [itcl::code %W _fixLayout]
84
85    #
86    # Empty frame for main widget
87    #
88    itk_component add frame {
89        frame $itk_interior.f
90    }
91
92    #
93    # Sash along the left side of sidebar
94    #
95    itk_component add sashbg {
96        frame $itk_interior.sashbg
97    } {
98        usual
99        rename -cursor -sashcursor sashCursor Cursor
100    }
101
102    itk_component add sash {
103        frame $itk_component(sashbg).sash -borderwidth 1
104    } {
105        usual
106        ignore -background -borderwidth
107        rename -relief -sashrelief sashRelief Relief
108        rename -width -sashwidth sashWidth SashWidth
109        rename -cursor -sashcursor sashCursor Cursor
110    }
111    pack $itk_component(sash) -side left -fill y
112
113    foreach c {sash sashbg} {
114        bind $itk_component($c) <Enter> \
115            [itcl::code $this _sash enter %X]
116        bind $itk_component($c) <Leave> \
117            [itcl::code $this _sash leave %X]
118        bind $itk_component($c) <ButtonPress-1> \
119            [itcl::code $this _sash grab %X]
120        bind $itk_component($c) <B1-Motion> \
121            [itcl::code $this _sash drag %X]
122        bind $itk_component($c) <ButtonRelease-1> \
123            [itcl::code $this _sash release %X]
124    }
125
126    itk_component add sidebar {
127        frame $itk_interior.sbar
128    }
129
130    #
131    # Title bar along top of sidebar
132    #
133    itk_component add titlebar {
134        frame $itk_component(sidebar).tbar -highlightthickness 0
135    } {
136        usual
137        ignore -highlightthickness
138        rename -background -titlebarbackground titlebarBackground Background
139    }
140    pack $itk_component(titlebar) -side top -fill x
141
142    itk_component add popbutton {
143        button $itk_component(titlebar).popb \
144            -borderwidth 1 -relief flat -overrelief raised \
145            -highlightthickness 0 \
146            -image [Rappture::icon sbar-open] \
147            -command [itcl::code $this pop toggle]
148    } {
149        usual
150        ignore -borderwidth -relief -overrelief -highlightthickness
151        rename -background -titlebarbackground titlebarBackground Background
152        rename -activebackground -titlebarbackground titlebarBackground Background
153    }
154    pack $itk_component(popbutton) -side left -padx 6 -pady 2
155    Rappture::Tooltip::for $itk_component(popbutton) \
156        "Open/close the sidebar"
157
158    itk_component add title {
159        label $itk_component(titlebar).title -anchor w -font "Arial 10"
160    } {
161        usual
162        ignore -font
163        rename -foreground -titlebarforeground titlebarForeground Foreground
164        rename -background -titlebarbackground titlebarBackground Background
165    }
166    pack $itk_component(title) -side left -expand yes -fill both -padx 1 -pady 1
167
168    #
169    # Area for active panel
170    #
171    itk_component add area {
172        Rappture::Scroller $itk_component(sidebar).area \
173            -xscrollmode auto -yscrollmode auto \
174            -highlightthickness 0
175    }
176    $itk_component(area) contents frame
177
178    itk_component add controlbar {
179        frame $itk_component(sidebar).cbar
180    } {
181        usual
182        rename -background -controlbackground controlBackground Background
183    }
184    pack $itk_component(controlbar) -side left -fill y
185
186    #
187    # Control area above the tabs
188    #
189    itk_component add controls {
190        frame $itk_component(controlbar).cntls -height 20
191    } {
192        usual
193        rename -background -controlbackground controlBackground Background
194    }
195    pack $itk_component(controls) -side top -pady {8 20}
196
197    #
198    # Tabs used to select sidebar panels. 
199    #
200    # Note:  Bugs in BLT 2.4 tabset/VNC server crashes the server
201    #        when -outerpad is set to 0.
202    #
203    itk_component add tabs {
204        blt::tabset $itk_component(controlbar).tabs \
205            -highlightthickness 0 -tearoff 0 -side left \
206            -bd 0 -gap 0 -tabborderwidth 1 \
207            -outerpad 1
208    } {
209        keep -background -cursor
210        ignore -highlightthickness -borderwidth
211        rename -highlightbackground -controlbackground controlBackground \
212            Background
213        rename -background -controlbackground controlBackground \
214            Background
215    }
216    pack $itk_component(tabs) -side top -expand yes -anchor e -padx {4 0} -fill y
217
218    eval itk_initialize $args
219
220    # make sure we fix up the layout at some point
221    $_dispatcher event -idle !layout
222}
223
224# ----------------------------------------------------------------------
225# USAGE: insert <pos> ?-title t? ?-icon i?
226#
227# Adds a new panel into this widget at the given position <pos>.  The
228# panel has a tab with the specified -icon, and is labeled by the
229# -title string in the titlebar area when it is selected.
230# ----------------------------------------------------------------------
231itcl::body Rappture::SidebarFrame::insert {pos args} {
232    Rappture::getopts args panel "
233        value -title Options
234        value -icon [Rappture::icon cboff]
235    "
236    if {[llength $args] > 0} {
237        error "wrong # args: should be \"insert pos ?-title t? ?-icon i?\""
238    }
239
240    set f [$itk_component(area) contents]
241    set pname "panel[incr _counter]"
242    itk_component add $pname {
243        frame $f.$pname
244    }
245
246    $itk_component(tabs) insert end $pname \
247        -image $panel(-icon) -text "" -padx 0 -pady 0 \
248        -command [itcl::code $this _toggleTab $pname]
249
250    Rappture::Tooltip::text $itk_component(tabs)-$pname \
251        "Open/close sidebar for $panel(-title)"
252    $itk_component(tabs) bind $pname <Enter> \
253        [list ::Rappture::Tooltip::tooltip pending %W-$pname @%X,%Y]
254    $itk_component(tabs) bind $pname <Leave> \
255        [list ::Rappture::Tooltip::tooltip cancel]
256    $itk_component(tabs) bind $pname <ButtonPress> \
257        [list ::Rappture::Tooltip::tooltip cancel]
258    $itk_component(tabs) bind $pname <KeyPress> \
259        [list ::Rappture::Tooltip::tooltip cancel]
260
261    set _panels($pname-title) $panel(-title)
262    lappend _panels(all) $pname
263    if {$_selected == ""} {
264        set _selected $pname
265        if {$_state == "open"} {
266            $itk_component(title) configure -text $panel(-title)
267        }
268    }
269
270    return $itk_component($pname)
271}
272
273# ----------------------------------------------------------------------
274# USAGE: panel <which>
275#
276# Returns the frame representing the requested panel.  The <which>
277# argument can be a panel index, name, or title, or the keyword
278# "current" for the selected panel.
279# ----------------------------------------------------------------------
280itcl::body Rappture::SidebarFrame::panel {which} {
281    switch -glob -- $which {
282        current {
283            return $itk_component($_selected)
284        }
285        [0-9]* {
286            set pname [lindex $_panels(all) $which]
287            return $itk_component($pname)
288        }
289        panel[0-9]* {
290            if {[info exists itk_component($which)]} {
291                return $itk_component($which)
292            }
293            error "bad panel name \"$which\""
294        }
295        default {
296            foreach pname $_panels(all) {
297                if {[string equal $_panels($pname-title) $which]} {
298                    return $itk_component($pname)
299                }
300            }
301            error "bad panel title \"$which\""
302        }
303    }
304}
305
306# ----------------------------------------------------------------------
307# USAGE: select <which>
308#
309# Pops open the sidebar and selects the specified panel.  The <which>
310# argument can be a panel index, name, or title.
311# ----------------------------------------------------------------------
312itcl::body Rappture::SidebarFrame::select {which} {
313    set pname ""
314    switch -glob -- $which {
315        [0-9]* {
316            set pname [lindex $_panels(all) $which]
317        }
318        panel[0-9]* {
319            if {[info exists itk_component($which)]} {
320                set pname $which
321            }
322        }
323        default {
324            foreach p $_panels(all) {
325                if {[string equal $_panels($p-title) $which]} {
326                    set pname $p
327                    break
328                }
329            }
330        }
331    }
332    if {$pname == ""} {
333        error "bad panel name \"$which\": should be panel id, title, or index"
334    }
335
336    if {$_state == "closed"} {
337        pop open
338    }
339
340    set minw [winfo reqwidth $itk_component(controlbar)]
341    if {$_width != "auto" && $_width < $minw+50} {
342        set _width [expr {$minw+50}]
343        $_dispatcher event -idle !layout
344    }
345    set n [$itk_component(tabs) index -name $pname]
346    $itk_component(tabs) select $n
347
348    $itk_component(title) configure -text $_panels($pname-title)
349
350    set f [$itk_component(area) contents]
351    foreach w [pack slaves $f] {
352        pack forget $w
353    }
354    pack $itk_component($pname) -expand yes -fill both
355
356    #
357    # HACK ALERT!  Force the scroller to check the size of the
358    # panel that we just slid in under the covers.  Make it
359    # think the panel and the scroller itself have changed size.
360    #
361    event generate [winfo parent $f] <Configure>
362    event generate $f <Configure>
363
364    set _selected $pname
365    return $pname
366}
367
368# ----------------------------------------------------------------------
369# USAGE: pop open|close|toggle
370#
371# Used to open/close the sidebar area.  When open, the selected panel
372# appears and the titlebar shows its name.
373# ----------------------------------------------------------------------
374itcl::body Rappture::SidebarFrame::pop {how} {
375    if {$how == "toggle"} {
376        if {$_state == "closed"} {
377            set how "open"
378        } else {
379            set how "close"
380        }
381    }
382
383    switch -- $how {
384        open {
385            $itk_component(popbutton) configure \
386                -image [Rappture::icon sbar-closed]
387            pack $itk_component(area) -side right -expand yes -fill both
388
389            set _state "open"
390            select $_selected
391            $_dispatcher event -idle !layout
392        }
393        close {
394            $itk_component(popbutton) configure \
395                -image [Rappture::icon sbar-open]
396            $itk_component(title) configure -text ""
397            pack forget $itk_component(area)
398
399            set _state "closed"
400            $_dispatcher event -idle !layout
401        }
402        default {
403            error "bad option \"$how\": should be open, close, toggle"
404        }
405    }
406}
407
408# ----------------------------------------------------------------------
409# USAGE: disable <which>
410#
411# Pops open the sidebar and selects the specified panel.  The <which>
412# argument can be a panel index, name, or title.
413# ----------------------------------------------------------------------
414itcl::body Rappture::SidebarFrame::disable {which} {
415    set index [TabIndex $which]
416    set tab [$itk_component(tabs) get $index]
417    $itk_component(tabs) tab configure $tab -state disabled
418}
419
420
421# ----------------------------------------------------------------------
422# USAGE: enable <which>
423#
424# Pops open the sidebar and selects the specified panel.  The <which>
425# argument can be a panel index, name, or title.
426# ----------------------------------------------------------------------
427itcl::body Rappture::SidebarFrame::enable {which} {
428    set index [TabIndex $which]
429    set tab [$itk_component(tabs) get $index]
430    $itk_component(tabs) tab configure $tab -state normal
431}
432
433# ----------------------------------------------------------------------
434# USAGE: TabIndex <which>
435#
436# Pops open the sidebar and selects the specified panel.  The <which>
437# argument can be a panel index, name, or title.
438# ----------------------------------------------------------------------
439itcl::body Rappture::SidebarFrame::TabIndex {which} {
440    set pname ""
441    switch -glob -- $which {
442        [0-9]* {
443            set pname [lindex $_panels(all) $which]
444        }
445        panel[0-9]* {
446            if {[info exists itk_component($which)]} {
447                set pname $which
448            }
449        }
450        default {
451            foreach p $_panels(all) {
452                if {[string equal $_panels($p-title) $which]} {
453                    set pname $p
454                    break
455                }
456            }
457        }
458    }
459    if {$pname == ""} {
460        error "bad panel name \"$which\": should be panel id, title, or index"
461    }
462    set n [$itk_component(tabs) index -name $pname]
463    return $n
464}
465
466# ----------------------------------------------------------------------
467# USAGE: _toggleTab <which>
468#
469# Invoked automatically when the user clicks on a tab for the sidebar.
470# If the sidebar is closed, it is automatically opened and the tab is
471# selected.  If the sidebar is opened, then it's closed.
472# ----------------------------------------------------------------------
473itcl::body Rappture::SidebarFrame::_toggleTab {which} {
474    if {$_state == "closed"} {
475        pop open
476        select $which
477    } elseif {[$itk_component(tabs) index -name $_selected]
478          == [$itk_component(tabs) index -name $which]} {
479        pop close
480    } else {
481        select $which
482    }
483}
484
485# ----------------------------------------------------------------------
486# USAGE: _sash <op> <X>
487#
488# Invoked automatically when the user clicks/drags on a sash, to resize
489# the sidebar.
490# ----------------------------------------------------------------------
491itcl::body Rappture::SidebarFrame::_sash {op X} {
492    switch -- $op {
493        enter {
494            # mouse over sash -- make it active
495            if {$itk_option(-sashactiverelief) != ""} {
496                $itk_component(sash) configure -relief $itk_option(-sashactiverelief)
497            }
498        }
499        leave {
500            # mouse left sash -- back to normal
501            $itk_component(sash) configure -relief $itk_option(-sashrelief)
502        }
503        grab {
504            if {$_state == "closed"} { pop open }
505            _sash drag $X
506        }
507        drag {
508            set w [winfo width $itk_component(hull)]
509            set minw [winfo reqwidth $itk_component(controlbar)]
510            set dx [expr {$X - [winfo rootx $itk_component(hull)]}]
511            set sashw [winfo reqwidth $itk_component(sashbg)]
512            set _width [expr {$w - $dx - $sashw/2}]
513
514            if {$_width < $minw} { set _width $minw }
515            if {$_width > $w-50} { set _width [expr {$w-50}] }
516            _fixLayout
517        }
518        release {
519            set minw [winfo reqwidth $itk_component(controlbar)]
520            if {$_width-$minw < 40} {
521                set _width "auto"
522                pop close
523            }
524        }
525        default {
526            error "bad option \"$op\": should be enter, leave, grab, drag, release"
527        }
528    }
529}
530
531# ----------------------------------------------------------------------
532# USAGE: _fixLayout ?<eventArgs>...?
533#
534# Used internally to update the layout of panes whenever a new pane
535# is added or a sash is moved.
536# ----------------------------------------------------------------------
537itcl::body Rappture::SidebarFrame::_fixLayout {args} {
538    set w [winfo width $itk_component(hull)]
539    set h [winfo height $itk_component(hull)]
540
541    set sashw [winfo reqwidth $itk_component(sashbg)]
542
543    set tabw [winfo reqwidth $itk_component(tabs)]
544    set btnw [winfo reqwidth $itk_component(controls)]
545    set ctrlw [expr {($tabw > $btnw) ? $tabw : $btnw}]
546
547    if {$_state == "closed"} {
548        set sbarw $ctrlw
549    } else {
550        if {$_width == "auto"} {
551            # pop open to the size of the widest pane
552            set sbarw 0
553            foreach pname $_panels(all) {
554                set pw [winfo reqwidth $itk_component($pname)]
555                if {$pw > $sbarw} {
556                    set sbarw $pw
557                }
558            }
559            set sbarw [expr {$sbarw + $ctrlw + $sashw}]
560        } else {
561            set sbarw $_width
562        }
563    }
564
565    # don't let the sidebar take up too much of the window area
566    if {$sbarw > 0.75*$w} {
567        set sbarw [expr {int(0.75*$w)}]
568    }
569
570    set x1 [expr {$w - $sbarw - $sashw}]
571    set x2 [expr {$w - $sbarw}]
572    if { $resizeframe } {
573        set framew $x1
574    } else {
575        set framew [expr $w - $ctrlw]
576    }
577    place $itk_component(frame) -x 0 -y 0 -anchor nw -width $framew -height $h
578    place $itk_component(sashbg) -x $x1 -y 0 -anchor nw -width $sashw -height $h
579    place $itk_component(sidebar) -x $x2 -y 0 -anchor nw -width $sbarw -height $h
580}
581
582# ----------------------------------------------------------------------
583# CONFIGURATION OPTION: -sashpadding
584# ----------------------------------------------------------------------
585itcl::configbody Rappture::SidebarFrame::sashpadding {
586    pack $itk_component(sash) -padx $itk_option(-sashpadding)
587}
588
589# ----------------------------------------------------------------------
590# CONFIGURATION OPTION: -sashcolor
591# ----------------------------------------------------------------------
592itcl::configbody Rappture::SidebarFrame::sashcolor {
593    if {$itk_option(-sashcolor) != ""} {
594        $itk_component(sash) configure -background $itk_option(-sashcolor)
595    } else {
596        $itk_component(sash) configure -background $itk_option(-background)
597    }
598}
599
Note: See TracBrowser for help on using the repository browser.