source: branches/1.3/gui/scripts/panes.tcl @ 5115

Last change on this file since 5115 was 5059, checked in by ldelgass, 9 years ago

merge r5056 from trunk

File size: 19.8 KB
Line 
1# -*- mode: tcl; indent-tabs-mode: nil -*-
2# ----------------------------------------------------------------------
3#  COMPONENT: Panes - creates a series of adjustable panes
4#
5#  This is a simple paned window with an adjustable sash.
6#  the same quantity, but for various ranges of input values.
7#  It also manages the controls to select and visualize the data.
8# ======================================================================
9#  AUTHOR:  Michael McLennan, Purdue University
10#  Copyright (c) 2004-2015  HUBzero Foundation, LLC
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 *Panes.width 3i widgetDefault
18option add *Panes.height 3i widgetDefault
19option add *Panes.sashRelief sunken widgetDefault
20option add *Panes.sashWidth 2 widgetDefault
21option add *Panes.sashPadding 4 widgetDefault
22option add *Panes.orientation vertical widgetDefault
23
24itcl::class Rappture::Panes {
25    inherit itk::Widget
26
27    itk_option define -sashrelief sashRelief SashRelief ""
28    itk_option define -sashwidth sashWidth SashWidth 0
29    itk_option define -sashpadding sashPadding SashPadding 0
30    itk_option define -orientation orientation Orientation ""
31
32    constructor {args} { # defined below }
33
34    public method insert {pos args}
35    public method pane {pos}
36    public method visibility {pos args}
37    public method fraction {pos args}
38    public method hilite {state sash}
39    public method size {}
40
41    protected method _grab {pane X Y}
42    protected method _drag {pane X Y}
43    protected method _drop {pane X Y}
44    protected method _fixLayout {args}
45    protected method _fixSashes {args}
46
47    private variable _dispatcher ""  ;# dispatcher for !events
48    private variable _panes ""       ;# list of pane frames
49    private variable _visibility ""  ;# list of visibilities for panes
50    private variable _counter 0      ;# counter for auto-generated names
51    private variable _reqfrac 0.0    ;# requested fraction size of each pane
52    private variable _dragfrom 0     ;# starting coordinate of drag operation
53    private variable _dragfrac 0     ;# limit on fraction of drag operation
54}
55
56itk::usual Panes {
57    keep -background -cursor -sashwidth -sashrelief
58}
59
60# ----------------------------------------------------------------------
61# CONSTRUCTOR
62# ----------------------------------------------------------------------
63itcl::body Rappture::Panes::constructor {args} {
64    itk_option add hull.width hull.height
65
66    # create a dispatcher for events
67    Rappture::dispatcher _dispatcher
68    $_dispatcher register !layout
69    $_dispatcher dispatch $this !layout [itcl::code $this _fixLayout]
70    $_dispatcher register !sashes
71    $_dispatcher dispatch $this !sashes [itcl::code $this _fixSashes]
72
73    # fix the layout whenever the window size changes
74    bind Panes <Configure> [itcl::code %W _fixLayout]
75
76    set pname "pane[incr _counter]"
77    itk_component add $pname {
78        frame $itk_interior.$pname
79    }
80
81    lappend _panes $pname
82    lappend _visibility 1
83    set _reqfrac 0.5
84
85    eval itk_initialize $args
86
87    # make sure we fix up the layout at some point
88    $_dispatcher event -idle !layout
89}
90
91# ----------------------------------------------------------------------
92# USAGE: insert <pos> ?-fraction f?
93#
94# Adds a new page to this widget at the given position <pos>.
95# ----------------------------------------------------------------------
96itcl::body Rappture::Panes::insert {pos args} {
97    Rappture::getopts args params {
98        value -fraction 0.5
99    }
100    if {[llength $args] > 0} {
101        error "wrong # args: should be \"insert pos ?-fraction f?\""
102    }
103
104    set pname "pane[incr _counter]"
105    set sash "${pname}sash"
106    itk_component add $sash {
107        frame $itk_interior.$sash
108    } {
109        usual
110        ignore -cursor
111    }
112    bind $itk_component($sash) <Enter> [itcl::code $this hilite on $sash]
113    bind $itk_component($sash) <Leave> [itcl::code $this hilite off $sash]
114
115    itk_component add ${sash}ridge {
116        frame $itk_component($sash).ridge
117    } {
118        usual
119        rename -relief -sashrelief sashRelief SashRelief
120        ignore -borderwidth
121    }
122    if {$itk_option(-orientation) eq "vertical"} {
123        pack $itk_component(${sash}ridge) -fill x
124        $itk_component($sash) configure -cursor sb_v_double_arrow
125        $itk_component(${sash}ridge) configure -cursor sb_v_double_arrow
126    } else {
127        pack $itk_component(${sash}ridge) -fill y -side left
128        $itk_component($sash) configure -cursor sb_h_double_arrow
129        $itk_component(${sash}ridge) configure -cursor sb_h_double_arrow
130    }
131    foreach comp [list $sash ${sash}ridge] {
132        bind $itk_component($comp) <ButtonPress-1> \
133            [itcl::code $this _grab $pname %X %Y]
134        bind $itk_component($comp) <B1-Motion> \
135            [itcl::code $this _drag $pname %X %Y]
136        bind $itk_component($comp) <ButtonRelease-1> \
137            [itcl::code $this _drop $pname %X %Y]
138    }
139
140
141    itk_component add $pname {
142        frame $itk_interior.$pname
143    }
144    set _panes [linsert $_panes $pos $pname]
145    set _visibility [linsert $_visibility $pos 1]
146    set _reqfrac [linsert $_reqfrac $pos $params(-fraction)]
147
148    # fix sash characteristics
149    $_dispatcher event -idle !sashes
150
151    # make sure we fix up the layout at some point
152    $_dispatcher event -idle !layout
153
154    return $itk_component($pname)
155}
156
157# ----------------------------------------------------------------------
158# USAGE: pane <pos>
159#
160# Returns the frame representing the pane at position <pos>.
161# ----------------------------------------------------------------------
162itcl::body Rappture::Panes::pane {pos} {
163    set pname [lindex $_panes $pos]
164    if {[info exists itk_component($pname)]} {
165        return $itk_component($pname)
166    }
167    return ""
168}
169
170# ----------------------------------------------------------------------
171# USAGE: visibility <pos> ?<newval>? ?<pos> <newval> ...?
172#
173# Clients use this to get/set the visibility of the pane at position
174# <pos>.  Can also be used to set the visibility for multiple panes
175# if multiple <pos>/<newval> pairs are specified in the same command.
176# ----------------------------------------------------------------------
177itcl::body Rappture::Panes::visibility {pos args} {
178    if {[llength $args] == 0} {
179        return [lindex $_visibility $pos]
180    }
181    if {[llength $args] % 2 == 0} {
182        error "wrong # args: should be \"visibility pos ?val pos val ...?\""
183    }
184
185    set args [linsert $args 0 $pos]
186    foreach {pos newval} $args {
187        if {![string is boolean -strict $newval]} {
188            error "bad value \"$newval\": should be boolean"
189        }
190        if {$pos eq "end" || ($pos >= 0 && $pos < [llength $_visibility])} {
191            set _visibility [lreplace $_visibility $pos $pos [expr {$newval}]]
192            $_dispatcher event -idle !layout
193        } else {
194            error "bad index \"$pos\": out of range"
195        }
196    }
197}
198
199# ----------------------------------------------------------------------
200# USAGE: fraction <pos> ?<newval>? ?<pos> <newval> ...?
201#
202# Clients use this to get/set the fraction of real estate associated
203# with the pane at position <pos>.  Can also be used to set the
204# fractions for multiple panes if multiple <pos>/<newval> pairs
205# are specified in the same command.
206# ----------------------------------------------------------------------
207itcl::body Rappture::Panes::fraction {pos args} {
208    if {[llength $args] == 0} {
209        return [lindex $_reqfrac $pos]
210    }
211    if {[llength $args] % 2 == 0} {
212        error "wrong # args: should be \"fraction pos ?val pos val ...?\""
213    }
214
215    set args [linsert $args 0 $pos]
216    foreach {pos newval} $args {
217        if {![string is double -strict $newval]} {
218            error "bad value \"$newval\": should be fraction 0-1"
219        }
220        if {$pos eq "end" || ($pos >= 0 && $pos < [llength $_reqfrac])} {
221            set _reqfrac [lreplace $_reqfrac $pos $pos $newval]
222            $_dispatcher event -idle !layout
223        } else {
224            error "bad index \"$pos\": out of range"
225        }
226    }
227}
228
229# ----------------------------------------------------------------------
230# USAGE: hilite <state> <sash>
231#
232# Invoked automatically whenever the user touches a sash.  Highlights
233# the sash by changing its size or relief.
234# ----------------------------------------------------------------------
235itcl::body Rappture::Panes::hilite {state sash} {
236    switch -- $itk_option(-sashrelief) {
237      flat {
238        if {$state} {
239            $itk_component(${sash}ridge) configure -background black
240        } else {
241            $itk_component(${sash}ridge) configure -background $itk_option(-background)
242        }
243      }
244      sunken {
245        if {$state} {
246            $itk_component(${sash}ridge) configure -relief raised
247        } else {
248            $itk_component(${sash}ridge) configure -relief sunken
249        }
250      }
251      raised {
252        if {$state} {
253            $itk_component(${sash}ridge) configure -relief sunken
254        } else {
255            $itk_component(${sash}ridge) configure -relief raised
256        }
257      }
258      solid {
259        if {$state} {
260            $itk_component($sash) configure -background black
261        } else {
262            $itk_component($sash) configure \
263                -background $itk_option(-background)
264        }
265      }
266    }
267}
268
269# ----------------------------------------------------------------------
270# USAGE: size
271#
272# Returns the number of panes in this widget.  That makes it easier
273# to index the various panes, since indices run from 0 to size-1.
274# ----------------------------------------------------------------------
275itcl::body Rappture::Panes::size {} {
276    return [llength $_panes]
277}
278
279# ----------------------------------------------------------------------
280# USAGE: _grab <pane> <X> <Y>
281#
282# Invoked automatically when the user clicks on a sash, to initiate
283# movement.
284# ----------------------------------------------------------------------
285itcl::body Rappture::Panes::_grab {pname X Y} {
286    set pos [lsearch $_panes $pname]
287    if {$pos < 0} return
288    set frac0 [lindex $_reqfrac [expr {$pos-1}]]
289    set frac1 [lindex $_reqfrac $pos]
290    set _dragfrac [expr {$frac0+$frac1}]
291
292    if {$itk_option(-orientation) eq "vertical"} {
293        set _dragfrom $Y
294    } else {
295        set _dragfrom $X
296    }
297}
298
299# ----------------------------------------------------------------------
300# USAGE: _drag <pane> <X> <Y>
301#
302# Invoked automatically as the user drags a sash, to resize the panes.
303# ----------------------------------------------------------------------
304itcl::body Rappture::Panes::_drag {pname X Y} {
305    set pos [lsearch $_panes $pname]
306    if {$pos < 0} return
307    set frac [lindex $_reqfrac $pos]
308
309    if {$itk_option(-orientation) eq "vertical"} {
310        set delY [expr {$_dragfrom-$Y}]
311        set Ymax  [winfo height $itk_component(hull)]
312        set delta [expr {double($delY)/$Ymax}]
313        set frac [expr {$frac + $delta}]
314        set _dragfrom $Y
315    } else {
316        set delX [expr {$_dragfrom-$X}]
317        set Xmax  [winfo width $itk_component(hull)]
318        set delta [expr {double($delX)/$Xmax}]
319        set frac [expr {$frac + $delta}]
320        set _dragfrom $X
321    }
322    if {$delta == 0.0} {
323        return
324    }
325
326    # set limits so the pane can't get too large or too small
327    if {$frac < 0.05} {
328        set frac 0.05
329    }
330    if {$frac > $_dragfrac-0.05} {
331        set frac [expr {$_dragfrac-0.05}]
332    }
333
334    # replace the fractions for this pane and the one before it
335    set prevfrac [expr {$_dragfrac-$frac}]
336    set _reqfrac [lreplace $_reqfrac [expr {$pos-1}] $pos $prevfrac $frac]
337
338    # normalize all fractions and fix the layout
339    _fixLayout
340
341    return $frac
342}
343
344# ----------------------------------------------------------------------
345# USAGE: _drop <pane> <X> <Y>
346#
347# Invoked automatically as the user drops a sash, to resize the panes.
348# ----------------------------------------------------------------------
349itcl::body Rappture::Panes::_drop {pname X Y} {
350    set frac [_drag $pname $X $Y]
351}
352
353# ----------------------------------------------------------------------
354# USAGE: _fixLayout ?<eventArgs>...?
355#
356# Used internally to update the layout of panes whenever a new pane
357# is added or a sash is moved.
358# ----------------------------------------------------------------------
359itcl::body Rappture::Panes::_fixLayout {args} {
360    # normalize the fractions for all panes to they add to 1.0
361    set total 0
362    foreach f $_reqfrac v $_visibility {
363        if {$v && $f > 0} {
364            set total [expr {$total + $f}]
365        }
366    }
367    if {$total == 0.0} { set total 1 }
368
369    set normfrac ""
370    foreach f $_reqfrac v $_visibility {
371        if {$v} {
372            lappend normfrac [expr {double($f)/$total}]
373        } else {
374            lappend normfrac [expr {double($f)/$total}]
375        }
376    }
377
378    # note that sash padding can be a single number or different on each side
379    if {[llength $itk_option(-sashpadding)] == 1} {
380        set pad [expr {2*$itk_option(-sashpadding)}]
381    } else {
382        set pad [expr [join $itk_option(-sashpadding) +]]
383    }
384
385    if {$itk_option(-orientation) eq "vertical"} {
386        set h [winfo height $itk_component(hull)]
387        set sh [expr {$itk_option(-sashwidth) + $pad}]
388
389        set plist ""
390        set flist ""
391        foreach p $_panes f $normfrac v $_visibility {
392            set sash ${p}sash
393            if {$v} {
394                # this pane is visible -- make room for it
395                lappend plist $p
396                lappend flist $f
397                if {[info exists itk_component($sash)]} {
398                    set h [expr {$h - $sh}]
399                }
400            } else {
401                # this pane is not visible -- remove sash
402                if {[info exists itk_component($sash)]} {
403                    place forget $itk_component($sash)
404                }
405                place forget $itk_component($p)
406            }
407        }
408
409        # lay out the various panes
410        set y 0
411        foreach p $plist f $flist {
412            set sash ${p}sash
413            if {[info exists itk_component($sash)]} {
414                place $itk_component($sash) -y $y -relx 0.5 -anchor n \
415                    -relwidth 1.0 -height $sh
416                set y [expr {$y + $sh}]
417            }
418           
419            set ph [expr {$h*$f}]
420            place $itk_component($p) -y $y -relx 0.5 -anchor n \
421                -relwidth 1.0 -height $ph
422            set y [expr {$y + $ph}]
423        }
424    } else {
425        set w [winfo width $itk_component(hull)]
426        set sw [expr {$itk_option(-sashwidth) + $pad}]
427
428        set plist ""
429        set flist ""
430        foreach p $_panes f $normfrac v $_visibility {
431            set sash ${p}sash
432            if {$v} {
433                # this pane is visible -- make room for it
434                lappend plist $p
435                lappend flist $f
436                if {[info exists itk_component($sash)]} {
437                    set w [expr {$w - $sw}]
438                }
439            } else {
440                # this pane is not visible -- remove sash
441                if {[info exists itk_component($sash)]} {
442                    place forget $itk_component($sash)
443                }
444                place forget $itk_component($p)
445            }
446        }
447
448        # lay out the various panes
449        set x 0
450        foreach p $plist f $flist {
451            set sash ${p}sash
452            if {[info exists itk_component($sash)]} {
453                place $itk_component($sash) -x $x -rely 0.5 -anchor w \
454                    -relheight 1.0 -width $sw
455                set x [expr {$x + $sw}]
456            }
457           
458            set pw [expr {$w*$f}]
459            place $itk_component($p) -x $x -rely 0.5 -anchor w \
460                -relheight 1.0 -width $pw
461            set x [expr {$x + $pw}]
462        }
463    }   
464}
465
466# ----------------------------------------------------------------------
467# USAGE: _fixSashes
468#
469# Used internally to fix the appearance of sashes whenever a new
470# sash appears or the controlling configuration options change.
471# ----------------------------------------------------------------------
472itcl::body Rappture::Panes::_fixSashes {args} {
473    if {$itk_option(-orientation) eq "vertical"} {
474        set ht [winfo pixels $itk_component(hull) $itk_option(-sashwidth)]
475        set bd [expr {$ht/2}]
476        foreach pane $_panes {
477            set sash "${pane}sashridge"
478            if {[info exists itk_component($sash)]} {
479                $itk_component($sash) configure -height $ht \
480                    -borderwidth $bd -relief $itk_option(-sashrelief)
481                pack $itk_component($sash) -pady $itk_option(-sashpadding) \
482                    -side top
483            }
484        }
485    } else {
486        set w [winfo pixels $itk_component(hull) $itk_option(-sashwidth)]
487        set bd [expr {$w/2}]
488        foreach pane $_panes {
489            set sash "${pane}sashridge"
490            if {[info exists itk_component($sash)]} {
491                $itk_component($sash) configure -width $w \
492                    -borderwidth $bd -relief $itk_option(-sashrelief)
493                pack $itk_component($sash) -padx $itk_option(-sashpadding) \
494                    -side left
495            }
496        }
497    }
498}
499
500# ----------------------------------------------------------------------
501# CONFIGURATION OPTION: -sashrelief
502# ----------------------------------------------------------------------
503itcl::configbody Rappture::Panes::sashrelief {
504    $_dispatcher event -idle !sashes
505}
506
507# ----------------------------------------------------------------------
508# CONFIGURATION OPTION: -sashwidth
509# ----------------------------------------------------------------------
510itcl::configbody Rappture::Panes::sashwidth {
511    $_dispatcher event -idle !sashes
512}
513
514# ----------------------------------------------------------------------
515# CONFIGURATION OPTION: -sashpadding
516# ----------------------------------------------------------------------
517itcl::configbody Rappture::Panes::sashpadding {
518    set count 0
519    foreach val $itk_option(-sashpadding) {
520        if {![string is integer -strict $val]} {
521            error "bad padding value \"$val\": should be integer"
522        }
523        incr count
524    }
525    if {$count < 1 || $count > 2} {
526        error "bad padding value \"$itk_option(-sashpadding)\": should be \"#\" or \"# #\""
527    }
528    $_dispatcher event -idle !sashes
529}
530
531# ----------------------------------------------------------------------
532# CONFIGURATION OPTION: -orientation
533# ----------------------------------------------------------------------
534itcl::configbody Rappture::Panes::orientation {
535    foreach pname $_panes {
536        set sash "${pname}sash"
537        if {$itk_option(-orientation) eq "vertical"} {
538            place $itk_component($pname) -x 0 -relx 0.5 -relwidth 1 \
539                -y 0 -rely 0 -relheight 0
540
541            if {[info exists itk_component($sash)]} {
542                place $itk_component($sash) -x 0 -relx 0.5 -relwidth 1 \
543                    -y 0 -rely 0 -relheight 0
544                $itk_component($sash) configure \
545                    -cursor sb_v_double_arrow
546
547                pack $itk_component(${sash}ridge) -fill x -side top
548                $itk_component(${sash}ridge) configure \
549                    -cursor sb_v_double_arrow
550            }
551        } else {
552            place $itk_component($pname) -y 0 -rely 0.5 -relheight 1 \
553                -x 0 -relx 0 -relwidth 0
554
555            if {[info exists itk_component($sash)]} {
556                place $itk_component($sash) -y 0 -rely 0.5 -relheight 1 \
557                    -x 0 -relx 0 -relwidth 0
558                $itk_component($sash) configure \
559                    -cursor sb_h_double_arrow
560
561                pack $itk_component(${sash}ridge) -fill y -side left
562                $itk_component(${sash}ridge) configure \
563                    -cursor sb_h_double_arrow
564            }
565        }
566    }
567
568    # fix sash characteristics
569    $_dispatcher event -idle !sashes
570
571    # make sure we fix up the layout at some point
572    $_dispatcher event -idle !layout
573}
Note: See TracBrowser for help on using the repository browser.