source: trunk/gui/scripts/pager.tcl @ 3534

Last change on this file since 3534 was 3330, checked in by gah, 12 years ago

merge (by hand) with Rappture1.2 branch

File size: 23.5 KB
RevLine 
[3330]1# -*- mode: tcl; indent-tabs-mode: nil -*-
[1]2# ----------------------------------------------------------------------
3#  COMPONENT: pager - notebook for displaying pages of widgets
4#
5#  This widget is something like a tabbed notebook, but with a little
6#  more flexibility.  Pages can be inserted and deleted, and then shown
7#  in various arrangements.
8# ======================================================================
9#  AUTHOR:  Michael McLennan, Purdue University
[3177]10#  Copyright (c) 2004-2012  HUBzero Foundation, LLC
[115]11#
12#  See the file "license.terms" for information on usage and
13#  redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES.
[1]14# ======================================================================
15package require Itk
16package require BLT
17
[11]18option add *Pager.arrangement "pages" widgetDefault
[1]19option add *Pager.width 0 widgetDefault
20option add *Pager.height 0 widgetDefault
[11]21option add *Pager.padding 8 widgetDefault
22option add *Pager.crumbColor black widgetDefault
23option add *Pager.crumbNumberColor white widgetDefault
24option add *Pager.dimCrumbColor gray70 widgetDefault
25option add *Pager.activeCrumbColor blue widgetDefault
26option add *Pager.crumbFont \
[676]27    -*-helvetica-bold-r-normal-*-12-* widgetDefault
[1]28
[11]29blt::bitmap define Pager-arrow {
30#define arrow_width 9
31#define arrow_height 9
32static unsigned char arrow_bits[] = {
33   0x10, 0x00, 0x30, 0x00, 0x70, 0x00, 0xff, 0x00, 0xff, 0x01, 0xff, 0x00,
34   0x70, 0x00, 0x30, 0x00, 0x10, 0x00};
35}
36
[1]37itcl::class Rappture::Pager {
38    inherit itk::Widget
39
40    itk_option define -width width Width 0
41    itk_option define -height height Height 0
[11]42    itk_option define -padding padding Padding 0
43    itk_option define -crumbcolor crumbColor Foreground ""
44    itk_option define -crumbnumbercolor crumbNumberColor Foreground ""
45    itk_option define -crumbfont crumbFont Font ""
46    itk_option define -dimcrumbcolor dimCrumbColor DimForeground ""
47    itk_option define -activecrumbcolor activeCrumbColor ActiveForeground ""
48    itk_option define -arrangement arrangement Arrangement ""
[1]49
50    constructor {args} { # defined below }
51
52    public method insert {pos args}
53    public method delete {first {last ""}}
54    public method index {name}
[11]55    public method page {args}
56    public method current {args}
[993]57
[992]58    public method busy { bool }
[1]59
60    protected method _layout {}
[11]61    protected method _fixSize {}
62    protected method _drawCrumbs {how}
[1]63
[11]64    private variable _counter 0      ;# counter for page names
[1]65    private variable _dispatcher ""  ;# dispatcher for !events
66    private variable _pages ""       ;# list of known pages
[11]67    private variable _page2info      ;# maps page name => -frame,-title,-command
68    private variable _current ""     ;# page currently shown
[1]69}
[1929]70                                                                               
[1]71itk::usual Pager {
72}
73
74# ----------------------------------------------------------------------
75# CONSTRUCTOR
76# ----------------------------------------------------------------------
77itcl::body Rappture::Pager::constructor {args} {
78    Rappture::dispatcher _dispatcher
79    $_dispatcher register !layout
80    $_dispatcher dispatch $this !layout "[itcl::code $this _layout]; list"
[11]81    $_dispatcher register !fixsize
82    $_dispatcher dispatch $this !fixsize "[itcl::code $this _fixSize]; list"
[1]83
[11]84    itk_component add controls {
[1929]85        frame $itk_interior.cntls
[1]86    }
87
[11]88    itk_component add next {
[1929]89        button $itk_component(controls).next -text "Next >" \
90            -command [itcl::code $this current next>]
[11]91    }
92    pack $itk_component(next) -side right
93
94    itk_component add back {
[1929]95        button $itk_component(controls).back -text "< Back" \
96            -command [itcl::code $this current <back]
[11]97    }
98    pack $itk_component(back) -side left
99
100    set font [$itk_component(next) cget -font]
101    set ht [font metrics $font -linespace]
[1274]102    itk_component add breadcrumbarea {
[1929]103        frame $itk_interior.bcarea
[1274]104    }
[11]105    itk_component add breadcrumbs {
[1929]106        canvas $itk_component(breadcrumbarea).breadcrumbs \
107            -width 10 -height [expr {$ht+2}]
[11]108    }
[1274]109    pack $itk_component(breadcrumbs) -side left -expand yes -fill both \
[1929]110        -padx 8 -pady 8
[11]111
112    itk_component add line {
[1929]113        frame $itk_interior.line -height 2 -borderwidth 1 -relief sunken
[11]114    }
115
116
[1]117    itk_component add inside {
[1929]118        frame $itk_interior.inside
[1]119    }
[11]120    pack $itk_component(inside) -expand yes -fill both
121    pack propagate $itk_component(inside) no
[1]122
123    eval itk_initialize $args
[11]124    $_dispatcher event -idle !layout
[1]125}
126
127# ----------------------------------------------------------------------
[11]128# USAGE: insert <pos> ?-name <name>? ?-title <label>? ?-command <str>?
[1]129#
[11]130# Clients use this to insert a new page into this pager.  The page is
131# inserted into the list at position <pos>, which can be an integer
132# starting from 0 or the keyword "end".  The optional <name> can be
133# used to identify the page.  If it is not supplied, a name is created
134# for the page.  The -title and -command are other values associated
135# with the page.
136#
137# Returns the name used to identify the page.
[1]138# ----------------------------------------------------------------------
139itcl::body Rappture::Pager::insert {pos args} {
140    if {"end" == $pos} {
[1929]141        set pos [llength $_pages]
[1]142    } elseif {![string is integer $pos]} {
[1929]143        error "bad index \"$pos\": should be integer or \"end\""
[1]144    }
145
[11]146    Rappture::getopts args params {
[1929]147        value -name page#auto
148        value -title "Page #auto"
149        value -command ""
[11]150    }
151    if {[llength $args] > 0} {
[1929]152        error "wrong # args: should be \"insert pos ?-name n? ?-title t? ?-command c?\""
[11]153    }
[1]154
[11]155    incr _counter
156    if {$_counter > 1} {
[1929]157        set subst "#$_counter"
[11]158    } else {
[1929]159        set subst ""
[1]160    }
[11]161    if {[regexp {#auto} $params(-name)]} {
[1929]162        regsub -all {#auto} $params(-name) $subst params(-name)
[11]163    }
164    if {[regexp {#auto} $params(-title)]} {
[1929]165        regsub -all {#auto} $params(-title) $subst params(-title)
[11]166    }
167
168    # allocate the page
169    if {[info exists _page2info($params(-name)-frame)]} {
[1929]170        error "page \"$params(-name)\" already exists"
[11]171    }
172    set win $itk_component(inside).page$_counter
173    frame $win
174    set _page2info($params(-name)-frame) $win
175    set _page2info($params(-name)-title) $params(-title)
176    set _page2info($params(-name)-command) $params(-command)
177    set _pages [linsert $_pages $pos $params(-name)]
178
[24]179    bind $win <Configure> \
[1929]180        [itcl::code $_dispatcher event -idle !fixsize]
[11]181
[22]182    # the number of pages affects the arrangment -- force an update
183    configure -arrangement $itk_option(-arrangement)
184
[1]185    $_dispatcher event -idle !layout
186
[11]187    return $params(-name)
[1]188}
189
190# ----------------------------------------------------------------------
191# USAGE: delete <first> ?<last>?
192#
193# Clients use this to delete one or more pages from this widget.
194# The <first> and <last> represent the integer index of the desired
195# page.  You can use the "index" method to convert a page name to
196# its integer index.  If only <first> is specified, then that one
197# page is deleted.  If <last> is specified, then all pages in the
198# range <first> to <last> are deleted.
199# ----------------------------------------------------------------------
200itcl::body Rappture::Pager::delete {first {last ""}} {
201    if {$last == ""} {
[1929]202        set last $first
[1]203    }
204    if {![regexp {^[0-9]+|end$} $first]} {
[1929]205        error "bad index \"$first\": should be integer or \"end\""
[1]206    }
207    if {![regexp {^[0-9]+|end$} $last]} {
[1929]208        error "bad index \"$last\": should be integer or \"end\""
[1]209    }
210
211    foreach name [lrange $_pages $first $last] {
[1929]212        if {[info exists _page2info($name-frame)]} {
213            destroy $_page2info($name-frame)
214            unset _page2info($name-frame)
215            unset _page2info($name-title)
216            unset _page2info($name-command)
217        }
[1]218    }
219    set _pages [lreplace $_pages $first $last]
220
[22]221    # the number of pages affects the arrangment -- force an update
222    configure -arrangement $itk_option(-arrangement)
223
[1]224    $_dispatcher event -idle !layout
225}
226
227# ----------------------------------------------------------------------
[11]228# USAGE: index <name>|@n
[1]229#
230# Clients use this to convert a page <name> into its corresponding
[11]231# integer index.  Returns an error if the <name> is not recognized.
[1]232# ----------------------------------------------------------------------
233itcl::body Rappture::Pager::index {name} {
[11]234    set i [lsearch $_pages $name]
235    if {$i >= 0} {
[1929]236        return $i
[11]237    }
238    if {[regexp {^@([0-9]+)$} $name match i]} {
[1929]239        return $i
[11]240    }
241    error "bad page name \"$name\": should be @int or one of [join [lsort $_pages] {, }]"
[1]242}
243
244# ----------------------------------------------------------------------
[11]245# USAGE: page
246# USAGE: page <name>|@n ?-frame|-title|-command? ?<newvalue>?
[1]247#
248# Clients use this to get information about pages.  With no args, it
[11]249# returns a list of all page names.  Otherwise, it returns the
250# requested information for a page specified by its <name> or index
251# @n.  By default, it returns the -frame for the page, but it can
252# also return the -title and -command.  The -title and -command
253# can also be set to a <newvalue>.
[1]254# ----------------------------------------------------------------------
[11]255itcl::body Rappture::Pager::page {args} {
256    if {[llength $args] == 0} {
[1929]257        return $_pages
[1]258    }
[11]259    set i [index [lindex $args 0]]
260    set name [lindex $_pages $i]
261
262    set args [lrange $args 1 end]
263    Rappture::getopts args params {
[1929]264        flag what -frame default
265        flag what -title
266        flag what -command
[1]267    }
[11]268
269    if {[llength $args] == 0} {
[1929]270        set opt $params(what)
271        return $_page2info($name$opt)
[11]272    } elseif {[llength $args] == 1} {
[1929]273        set val [lindex $args 0]
274        if {$params(-title)} {
275            set _page2info($name-title) $val
276        } elseif {$params(-command)} {
277            set _page2info($name-command) $val
278        }
[11]279    } else {
[1929]280        error "wrong # args: should be \"page ?which? ?-frame|-title|-command? ?newvalue?\""
[11]281    }
[1]282}
283
284# ----------------------------------------------------------------------
[11]285# USAGE: current ?<name>|next>|<back?
286#
287# Used to query/set the current page in the notebook.  With no args,
288# it returns the name of the current page.  Otherwise, it sets the
289# current page.  The special token "next>" is used to set the pager
290# to the next logical page, and "<back" sets to the previous.
291# ----------------------------------------------------------------------
292itcl::body Rappture::Pager::current {args} {
293    switch -- [llength $args] {
[1929]294        0 {
295            return $_current
296        }
297        1 {
298            if {$itk_option(-arrangement) != "pages"} {
299                return ""
300            }
301            set name [lindex $args 0]
302            set index 0
303            if {$name == "next>"} {
304                if {$_current == ""} {
305                    set index 0
306                } else {
307                    set i [lsearch -exact $_pages $_current]
308                    set index [expr {$i+1}]
309                    if {$index >= [llength $_pages]} {
310                        set index [expr {[llength $_pages]-1}]
311                    }
312                }
313                set _current [lindex $_pages $index]
314            } elseif {$name == "<back"} {
315                if {$_current == ""} {
316                    set index end
317                } else {
318                    set i [lsearch -exact $_pages $_current]
319                    set index [expr {$i-1}]
320                    if {$index < 0} {
321                        set index 0
322                    }
323                }
324                set _current [lindex $_pages $index]
325            } else {
326                if {$name == ""} {
327                    set _current ""
328                    set index 0
329                } else {
330                    set index [lsearch -exact $_pages $name]
331                    if {$index < 0} {
332                        error "can't move to page \"$name\""
333                    }
334                    set _current [lindex $_pages $index]
335                }
336            }
[11]337
[1929]338            foreach w [pack slaves $itk_component(inside)] {
339                pack forget $w
340            }
341            if {$_current != ""} {
342                pack $_page2info($_current-frame) -expand yes -fill both \
343                    -padx $itk_option(-padding) -pady $itk_option(-padding)
344            }
[11]345
[1929]346            if {$index == 0} {
347                pack forget $itk_component(back)
348            } else {
349                set prev [expr {$index-1}]
350                if {$prev >= 0} {
351                    set label "< [page @$prev -title]"
352                } else {
353                    set label "< Back"
354                }
355                $itk_component(back) configure -text $label
356                pack $itk_component(back) -side left
357            }
358            if {$index == [expr {[llength $_pages]-1}]} {
359                pack forget $itk_component(next)
360            } else {
361                set next [expr {$index+1}]
362                if {$next <= [llength $_pages]} {
363                    set label "[page @$next -title] >"
364                } else {
365                    set label "Next >"
366                }
367                $itk_component(next) configure -text $label
368                pack $itk_component(next) -side right
369            }
370            _drawCrumbs current
[11]371
[1929]372            #
373            # If this new page has a command associated with it, then
374            # invoke it now.
375            #
376            if {"" != $_current
377                  && [string length $_page2info($_current-command)] > 0} {
378                uplevel #0 $_page2info($_current-command)
379            }
380        }
381        default {
382            error "wrong # args: should be \"current name|next>|<back\""
383        }
[11]384    }
385}
386
387# ----------------------------------------------------------------------
[1]388# USAGE: _layout
389#
390# Used internally to fix the current page management whenever pages
391# are added or deleted, or when the page arrangement changes.
392# ----------------------------------------------------------------------
393itcl::body Rappture::Pager::_layout {} {
[11]394    if {$itk_option(-arrangement) == "pages"} {
[1929]395        if {$_current == ""} {
396            set _current [lindex $_pages 0]
397            if {$_current != ""} {
398                current $_current
399            }
400        }
401        _drawCrumbs all
[11]402    }
403}
[1]404
[11]405# ----------------------------------------------------------------------
406# USAGE: _fixSize
407#
408# Invoked automatically whenever a page changes size or the -width
409# or -height options change.  When the -width/-height are zero, this
410# method computes the minimum size needed to accommodate all pages.
411# Otherwise, it passes the size request onto the hull.
412# ----------------------------------------------------------------------
413itcl::body Rappture::Pager::_fixSize {} {
[26]414    set sw [expr {[winfo screenwidth $itk_component(hull)]-200}]
415    set sh [expr {[winfo screenheight $itk_component(hull)]-200}]
416
[53]417    update  ;# force layout changes so sizes are correct
[11]418    switch -- $itk_option(-arrangement) {
[1929]419        pages {
420            if {$itk_option(-width) <= 0} {
421                set maxw [expr {
422                    [winfo reqwidth $itk_component(next)]
423                    + 10
424                    + [winfo reqwidth $itk_component(back)]}]
[11]425
[1929]426                foreach name $_pages {
427                    set w [winfo reqwidth $_page2info($name-frame)]
428                    if {$w > $maxw} { set maxw $w }
429                }
430                set maxw [expr {$maxw + 2*$itk_option(-padding)}]
431                if {$maxw > $sw} { set maxw $sw }
432                $itk_component(inside) configure -width $maxw
433            } else {
434                $itk_component(inside) configure -width $itk_option(-width)
435            }
[11]436
[1929]437            if {$itk_option(-height) <= 0} {
438                set maxh 0
439                foreach name $_pages {
440                    set h [winfo reqheight $_page2info($name-frame)]
441                    if {$h > $maxh} { set maxh $h }
442                }
443                set maxh [expr {$maxh + 2*$itk_option(-padding)}]
444                if {$maxh > $sh} { set maxh $sh }
445                $itk_component(inside) configure -height $maxh
446            } else {
447                $itk_component(inside) configure -height $itk_option(-height)
448            }
449        }
450        side-by-side {
451            if {$itk_option(-width) <= 0} {
452                set maxw [expr {
453                    [winfo reqwidth $itk_component(next)]
454                    + 10
455                    + [winfo reqwidth $itk_component(back)]}]
[11]456
[1929]457                set wtotal 0
458                foreach name $_pages {
459                    set w [winfo reqwidth $_page2info($name-frame)]
460                    set wtotal [expr {$wtotal + $w + 2*$itk_option(-padding)}]
461                }
462                if {$wtotal > $maxw} { set maxw $wtotal }
463                if {$maxw > $sw} { set maxw $sw }
464                $itk_component(inside) configure -width $maxw
465            } else {
466                $itk_component(inside) configure -width $itk_option(-width)
467            }
[11]468
[1929]469            if {$itk_option(-height) <= 0} {
470                set maxh 0
471                foreach name $_pages {
472                    set h [winfo reqheight $_page2info($name-frame)]
473                    if {$h > $maxh} { set maxh $h }
474                }
475                set maxh [expr {$maxh + 2*$itk_option(-padding)}]
476                if {$maxh > $sh} { set maxh $sh }
477                $itk_component(inside) configure -height $maxh
478            } else {
479                $itk_component(inside) configure -height $itk_option(-height)
480            }
481        }
[1]482    }
[11]483}
[1]484
[11]485# ----------------------------------------------------------------------
486# OPTION: -arrangement
487# ----------------------------------------------------------------------
488itcl::configbody Rappture::Pager::arrangement {
489    switch -- $itk_option(-arrangement) {
[1929]490        pages {
491            pack forget $itk_component(inside)
492            pack $itk_component(controls) -side bottom -fill x -padx 8 -pady 8
493            pack $itk_component(breadcrumbarea) -side top -fill x
494            pack $itk_component(line) -side top -fill x
495            pack $itk_component(inside) -expand yes -fill both
496            current [lindex $_pages 0]
497        }
498        side-by-side {
499            pack forget $itk_component(controls)
500            pack forget $itk_component(line)
501            pack forget $itk_component(breadcrumbarea)
[11]502
[1929]503            foreach w [pack slaves $itk_component(inside)] {
504                pack forget $w
505            }
506            foreach name $_pages {
507                pack $_page2info($name-frame) -side left \
508                    -expand yes -fill both \
509                    -padx $itk_option(-padding) -pady $itk_option(-padding)
510            }
511        }
512        default {
513            error "bad value \"$itk_option(-arrangement)\": should be pages or side-by-side"
514        }
[1]515    }
[11]516    $_dispatcher event -now !fixsize
[1]517}
518
519# ----------------------------------------------------------------------
[11]520# OPTION: -width
[1]521# ----------------------------------------------------------------------
[11]522itcl::configbody Rappture::Pager::width {
523    $_dispatcher event -idle !fixsize
524}
525
526# ----------------------------------------------------------------------
527# OPTION: -height
528# ----------------------------------------------------------------------
529itcl::configbody Rappture::Pager::height {
530    $_dispatcher event -idle !fixsize
531}
532
533# ----------------------------------------------------------------------
534# OPTION: -padding
535# ----------------------------------------------------------------------
536itcl::configbody Rappture::Pager::padding {
537    if {$_current != ""} {
[1929]538        pack $_page2info($_current-frame) -expand yes -fill both \
539            -padx $itk_option(-padding) -pady $itk_option(-padding)
[1]540    }
[11]541    $_dispatcher event -idle !fixsize
[1]542}
543
[11]544# ----------------------------------------------------------------------
545# USAGE: _drawCrumbs all|current
546#
547# Invoked automatically whenever the pages change.  The value "all"
548# signifies that the number of pages has changed, so all should be
549# redrawn.  The value "current" means that the current page has
550# changed, so there is just a simple color change.
551# ----------------------------------------------------------------------
552itcl::body Rappture::Pager::_drawCrumbs {how} {
553    set c $itk_component(breadcrumbs)
554    set fnt $itk_option(-crumbfont)
[1]555
[11]556    switch -- $how {
[1929]557        all {
558            $c delete all
[1]559
[1929]560            set x 0
561            set y [expr {[winfo reqheight $c]/2}]
562            set last [lindex $_pages end]
[1]563
[1929]564            set num 1
565            foreach name $_pages {
566                set ht [expr {[font metrics $fnt -linespace]+2}]
567                set id [$c create oval $x [expr {$y-$ht/2}] \
568                    [expr {$x+$ht}] [expr {$y+$ht/2}] \
569                    -outline "" -fill $itk_option(-dimcrumbcolor) \
570                    -tags $name]
571                set id [$c create text [expr {$x+$ht/2}] [expr {$y+1}] \
572                    -text $num -fill $itk_option(-crumbnumbercolor) \
573                    -tags [list $name $name-num]]
574                set x [expr {$x + $ht+2}]
[1]575
[1929]576                set id [$c create text $x [expr {$y+1}] -anchor w \
577                    -text [page $name -title] -font $fnt -tags $name]
[11]578
[1929]579                $c bind $name <Enter> [itcl::code $this _drawCrumbs active]
580                $c bind $name <Leave> [itcl::code $this _drawCrumbs current]
581                $c bind $name <ButtonPress> [itcl::code $this current $name]
[11]582
[1929]583                foreach {x0 y0 x1 y1} [$c bbox $id] break
584                set x [expr {$x + ($x1-$x0)+6}]
[11]585
[1929]586                if {$name != $last} {
587                    set id [$c create bitmap $x $y -anchor w \
588                        -bitmap Pager-arrow \
589                        -foreground $itk_option(-dimcrumbcolor)]
590                    foreach {x0 y0 x1 y1} [$c bbox $id] break
591                    set x [expr {$x + ($x1-$x0)+6}]
592                }
[11]593
[1929]594                incr num
595            }
[11]596
[1929]597            # fix the scrollregion in case we go off screen
598            $c configure -scrollregion [$c bbox all]
[11]599
[1929]600            _drawCrumbs current
601        }
602        current {
603            # make all crumbs dim
604            foreach name $_pages {
605                $c itemconfigure $name \
606                    -fill $itk_option(-dimcrumbcolor)
607                $c itemconfigure $name-num \
608                    -fill $itk_option(-crumbnumbercolor)
609            }
[11]610
[1929]611            # make all the current crumb bright
612            if {$_current != ""} {
613                $c itemconfigure $_current \
614                    -fill $itk_option(-crumbcolor)
615                $c itemconfigure $_current-num \
616                    -fill $itk_option(-crumbnumbercolor)
[11]617
[1929]618                # scroll the view to see the crumb
619                if {[$c bbox $_current] != ""} {
620                    foreach {x0 y0 x1 y1} [$c bbox $_current] break
621                    foreach {xm0 ym0 xm1 ym1} [$c bbox all] break
622                    set xm [expr {double($x0)/($xm1-$xm0)}]
623                    $c xview moveto $xm
624                }
625            } else {
626                $c xview moveto 0
627            }
628        }
629        active {
630            foreach tag [$c gettags current] {
631                if {[lsearch -exact $_pages $tag] >= 0} {
632                    $c itemconfigure $tag -fill $itk_option(-activecrumbcolor)
633                    $c itemconfigure $tag-num -fill white
634                }
635            }
636        }
[11]637    }
638}
[989]639
[993]640#
641# busy --
642#
[2744]643#       If true (this indicates a simulation is occurring), the widget
644#       should prevent the user from
645#               1) clicking an item previous in the breadcrumbs, and
646#               2) using the "back" button.
[993]647#
[989]648itcl::body Rappture::Pager::busy { bool } {
649    if { $bool } {
[1929]650        blt::busy hold $itk_component(breadcrumbs)
651        $itk_component(back) configure -state disabled
[989]652    } else {
[1929]653        blt::busy release $itk_component(breadcrumbs)
654        $itk_component(back) configure -state normal
[989]655    }
656}
Note: See TracBrowser for help on using the repository browser.