source: trunk/gui/scripts/balloon.tcl @ 5975

Last change on this file since 5975 was 5592, checked in by ldelgass, 9 years ago

Merge r5557:5561,r5574:5575 from 1.3 branch

File size: 24.9 KB
Line 
1# -*- mode: tcl; indent-tabs-mode: nil -*-
2# ----------------------------------------------------------------------
3#  COMPONENT: Balloon - toplevel popup window, like a cartoon balloon
4#
5#  This widget is used as a container for pop-up panels in Rappture.
6#  If shape extensions are supported, then the window is drawn as
7#  a cartoon balloon.  Otherwise, it is a raised panel, with a little
8#  line connecting it to the widget that brought it up.  When active,
9#  the panel has a global grab and focus, so the user must interact
10#  with it or dismiss it.
11# ======================================================================
12#  AUTHOR:  Michael McLennan, Purdue University
13#  Copyright (c) 2004-2012  HUBzero Foundation, LLC
14#
15#  See the file "license.terms" for information on usage and
16#  redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES.
17# ======================================================================
18package require Itk
19
20namespace eval Rappture { # forward declaration }
21
22option add *Balloon.dismissButton on widgetDefault
23option add *Balloon.padX 4 widgetDefault
24option add *Balloon.padY 4 widgetDefault
25option add *Balloon.titleBackground #999999 widgetDefault
26option add *Balloon.titleForeground white widgetDefault
27option add *Balloon.relief raised widgetDefault
28option add *Balloon.stemLength 16 widgetDefault
29
30itcl::class Rappture::Balloon {
31    inherit itk::Toplevel
32
33    itk_option define -background background Background ""
34    itk_option define -deactivatecommand deactivateCommand DeactivateCommand ""
35    itk_option define -dismissbutton dismissButton DismissButton "on"
36    itk_option define -padx padX Pad 0
37    itk_option define -pady padY Pad 0
38    itk_option define -title title Title ""
39    itk_option define -titlebackground titleBackground Background ""
40    itk_option define -titleforeground titleForeground Foreground ""
41    itk_option define -stemlength stemLength StemLength 20
42
43    constructor {args} { # defined below }
44
45    public method activate {where placement}
46    public method deactivate {}
47
48    protected method _createStems {}
49    protected method _place {where placement w h sw sh}
50
51    protected variable _stems   ;# windows for cartoon balloon stems
52    protected variable _masks   ;# masks for cartoon balloon stems
53    protected variable _fills   ;# lines for cartoon balloon stems
54
55    public proc outside {widget x y}
56
57    bind RapptureBalloon <ButtonPress> \
58        {if {[Rappture::Balloon::outside %W %X %Y]} {%W deactivate}}
59}
60
61itk::usual Balloon {
62}
63
64# ----------------------------------------------------------------------
65# CONSTRUCTOR
66# ----------------------------------------------------------------------
67itcl::body Rappture::Balloon::constructor {args} {
68    wm overrideredirect $itk_component(hull) yes
69    wm withdraw $itk_component(hull)
70    component hull configure -borderwidth 1 -relief solid -padx 0 -pady 0
71
72    itk_component add border {
73        frame $itk_interior.border -borderwidth 2
74    } {
75        usual
76        keep -relief
77    }
78    pack $itk_component(border) -expand yes -fill both
79
80    itk_component add titlebar {
81        frame $itk_component(border).tbar
82    } {
83        usual
84        rename -background -titlebackground titleBackground Background
85    }
86
87    itk_component add title {
88        label $itk_component(titlebar).title -width 1 -anchor w
89    } {
90        usual
91        rename -background -titlebackground titleBackground Background
92        rename -foreground -titleforeground titleForeground Foreground
93        rename -highlightbackground -titlebackground titleBackground Background
94        rename -text -title title Title
95    }
96    pack $itk_component(title) -side left -expand yes -fill both -padx 2
97
98    itk_component add dismiss {
99        button $itk_component(titlebar).dismiss \
100            -bitmap [Rappture::icon dismiss] \
101            -relief flat -overrelief raised -command "
102              Rappture::Tooltip::cue hide
103              [list $itk_component(hull) deactivate]
104            "
105    } {
106        usual
107        rename -background -titlebackground titleBackground Background
108        rename -foreground -titleforeground titleForeground Foreground
109        rename -highlightbackground -titlebackground titleBackground Background
110    }
111
112    itk_component add inner {
113        frame $itk_component(border).inner
114    }
115    pack $itk_component(inner) -expand yes -fill both
116
117    # add bindings to release the grab
118    set btags [bindtags $itk_component(hull)]
119    bindtags $itk_component(hull) [linsert $btags 1 RapptureBalloon]
120
121    eval itk_initialize $args
122
123    _createStems
124}
125
126
127# ----------------------------------------------------------------------
128# USAGE: _place <where> <place> <pw> <ph> <screenw> <screenh>
129#
130# Called by activate. Returns the exact location information given
131# the parameters.  If the window will not fit on the screen with the
132# requested placement, will loop through all possible placements to
133# find the best alternative.
134# ----------------------------------------------------------------------
135itcl::body Rappture::Balloon::_place {where place pw ph screenw screenh} {
136    # pw and ph are requested balloon window size
137
138    # set placement preference order
139    switch $place {
140        left {set plist {left above below right}}
141        right {set plist {right above below left}}
142        above {set plist {above below right left}}
143        below {set plist {below above right left}}
144    }
145
146    set ph_orig $ph
147    set pw_orig $pw
148
149    foreach placement $plist {
150        set pw $pw_orig
151        set ph $ph_orig
152        if {[winfo exists $where]} {
153            # location of top-left corner of root window
154            set rx [winfo rootx $where]
155            set ry [winfo rooty $where]
156
157            # size of widget we want to popup over
158            set width  [winfo width $where]
159            set height [winfo height $where]
160
161            # x and y will be location for popup
162            set x [expr {$rx + $width/2}]
163            set y [expr {$ry + $height/2}]
164
165            switch -- $placement {
166                left { set x [expr {$rx + 5}] }
167                right { set x [expr {$rx + $width - 5}] }
168                above { set y [expr {$ry + 5}] }
169                below { set y [expr {$ry + $height - 5}] }
170            }
171        } elseif {[regexp {^@([0-9]+),([0-9]+)$} $where match x y]} {
172            # got x and y
173        } else {
174            error "bad location \"$where\": should be widget or @x,y"
175        }
176
177        # compute stem image size
178        set s $_stems($placement)
179        set sw [image width $_fills($placement)]
180        set sh [image height $_fills($placement)]
181        set offscreen 0
182
183        switch -- $placement {
184            left {
185                set sx [expr {$x-$sw+3}]
186                set sy [expr {$y-$sh/2}]
187                set px [expr {$sx-$pw+3}]
188                set py [expr {$y-$ph/2}]
189
190                # make sure that the panel doesn't go off-screen
191                if {$py < 0} {
192                    incr offscreen [expr -$py]
193                    set py 0
194                }
195                if {$py+$ph > $screenh} {
196                    incr offscreen [expr {$py + $ph - $screenh}]
197                    set py [expr {$screenh - $ph}]
198                }
199                if {$px < 0} {
200                    incr offscreen [expr -$px]
201                    set pw [expr {$pw + $px}]
202                    set px 0
203                }
204            }
205            right {
206                set sx $x
207                set sy [expr {$y-$sh/2}]
208                set px [expr {$x+$sw-3}]
209                set py [expr {$y-$ph/2}]
210
211                # make sure that the panel doesn't go off-screen
212                if {$py < 0} {
213                    incr offscreen [expr -$py]
214                    set py 0
215                }
216                if {$py+$ph > $screenh} {
217                    incr offscreen [expr {$py + $ph - $screenh}]
218                    set py [expr {$screenh-$ph}]
219                }
220                if {$px+$pw > $screenw} {
221                    incr offscreen [expr {$px + $pw - $screenw}]
222                    set pw [expr {$screenw-$px}]
223                }
224            }
225            above {
226                set sx [expr {$x-$sw/2}]
227                set sy [expr {$y-$sh+3}]
228                set px [expr {$x-$pw/2}]
229                set py [expr {$sy-$ph+3}]
230
231                # make sure that the panel doesn't go off-screen
232                if {$px < 0} {
233                    incr offscreen [expr -$px]
234                    set px 0
235                }
236                if {$px+$pw > $screenw} {
237                    incr offscreen [expr {$px + $pw - $screenw}]
238                    set px [expr {$screenw-$pw}]
239                }
240                if {$py < 0} {
241                    incr offscreen [expr -$py]
242                    set ph [expr {$ph+$py}]
243                    set py 0
244                }
245            }
246            below {
247                set sx [expr {$x-$sw/2}]
248                set sy $y
249                set px [expr {$x-$pw/2}]
250                set py [expr {$y+$sh-3}]
251
252                # make sure that the panel doesn't go off-screen
253                if {$px < 0} {
254                    incr offscreen [expr -$px]
255                    set px 0
256                }
257                if {$px+$pw > $screenw} {
258                    incr offscreen [expr {$px + $pw - $screenw}]
259                    set px [expr {$screenw-$pw}]
260                }
261                if {$py+$ph > $screenh} {
262                    incr offscreen [expr {$py + $py - $screenh}]
263                    set ph [expr {$screenh-$py}]
264                }
265            }
266        }
267        set res($placement) [list $placement $offscreen $pw $ph $px $py $sx $sy]
268        if {$offscreen == 0} {
269            return "$placement $pw $ph $px $py $sx $sy"
270        }
271    }
272
273    # In the unlikely event that we arrived here, it is because no
274    # placement allowed the entire balloon window to be displayed.
275    # Loop through the results and return the best-case placement.
276    set _min 10000
277    foreach pl $plist {
278        set offscreen [lindex $res($pl) 1]
279        if {$offscreen < $_min} {
280            set _min $offscreen
281            set _min_pl $pl
282        }
283    }
284    return "$_min_pl [lrange $res($_min_pl) 2 end]"
285}
286
287# ----------------------------------------------------------------------
288# USAGE: activate <where> <placement>
289#
290# Clients use this to pop up this balloon panel pointing to the
291# <where> location, which should be a widget name or @X,Y.  The
292# <placement> indicates whether the panel should be left, right,
293# above, or below the <where> coordinate. Plecement is considered
294# a suggestion and may be changed to fit the popup in the screen.
295# ----------------------------------------------------------------------
296itcl::body Rappture::Balloon::activate {where placement} {
297    if {![info exists _stems($placement)]} {
298        error "bad placement \"$placement\": should be [join [lsort [array names _stems]] {, }]"
299    }
300
301    # if the panel is already up, take it down
302    deactivate
303
304    set p $itk_component(hull)
305    set screenw [winfo screenwidth $p]
306    set screenh [winfo screenheight $p]
307
308    set pw [winfo reqwidth $p]
309    if {$pw > $screenw} { set pw [expr {$screenw-10}] }
310    set ph [winfo reqheight $p]
311    if {$ph > $screenh} { set ph [expr {$screenh-10}] }
312
313    foreach {place pw ph px py sx sy} [_place $where $placement $pw $ph $screenw $screenh] break
314
315    set s $_stems($place)
316    if {[info exists _masks($place)]} {
317        shape set $s -bound photo $_masks($place)
318    }
319
320    if { $pw < 1 || $ph < 1 }  {
321        # I really don't know why this is happenning.  I believe this occurs
322        # when in a work space (i.e the main window is smaller than the root
323        # window). So for now, better to place the balloon window somewhere
324        # than to fail with a bad geometry.
325        wm geometry $p +$px+$py
326    } else {
327        wm geometry $p ${pw}x${ph}+$px+$py
328    }
329    wm deiconify $p
330    raise $p
331
332    wm geometry $s +$sx+$sy
333    wm deiconify $s
334    raise $s
335
336    # grab the mouse pointer
337    update
338    while {[catch {grab set $itk_component(hull)}]} {
339        after 100
340    }
341    focus $itk_component(hull)
342}
343
344# ----------------------------------------------------------------------
345# USAGE: deactivate
346#
347# Clients use this to take down the balloon panel if it is on screen.
348# ----------------------------------------------------------------------
349itcl::body Rappture::Balloon::deactivate {} {
350    if {[string length $itk_option(-deactivatecommand)] > 0} {
351        uplevel #0 $itk_option(-deactivatecommand)
352    }
353
354    grab release $itk_component(hull)
355    wm withdraw $itk_component(hull)
356    foreach dir {left right above below} {
357        wm withdraw $_stems($dir)
358    }
359}
360
361# ----------------------------------------------------------------------
362# USAGE: _createStems
363#
364# Used internally to create the stems that connect a balloon panel
365# to its anchor point, in all four possible directions:  left, right,
366# above, and below.
367# ----------------------------------------------------------------------
368itcl::body Rappture::Balloon::_createStems {} {
369    # destroy any existing stems
370    foreach dir [array names _stems] {
371        destroy $_stems($dir)
372        unset _stems($dir)
373    }
374    foreach dir [array names _masks] {
375        image delete $_masks($dir)
376        unset _masks($dir)
377    }
378    foreach dir [array names _fills] {
379        image delete $_fills($dir)
380        unset _fills($dir)
381    }
382
383    if {[catch {package require Shape}] == 0} {
384        #
385        # We have the Shape extension.  Use it to create nice
386        # looking (triangle-shaped) stems.
387        #
388        set s $itk_option(-stemlength)
389        foreach dir {left right above below} {
390            switch -- $dir {
391                left - right {
392                    set sw [expr {$s+2}]
393                    set sh $s
394                }
395                above - below {
396                    set sw $s
397                    set sh [expr {$s+2}]
398                }
399            }
400
401            set _stems($dir) [toplevel $itk_interior.s$dir -borderwidth 0]
402            label $_stems($dir).l \
403                -width $sw -height $sh -borderwidth 0
404            pack $_stems($dir).l -expand yes -fill both
405
406            wm withdraw $_stems($dir)
407            wm overrideredirect $_stems($dir) yes
408
409            #
410            # Draw the triangle part of the stem, with a black outline
411            # and light/dark highlights:
412            #
413            #     --------  ---       LEFT STEM
414            #    |..##    |  ^
415            #    |  ..##  |  |        . = light color
416            #    |    ..##|  | s      @ = dark color
417            #    |    @@##|  |        # = black
418            #    |  @@##  |  |
419            #    |@@##    |  v
420            #     --------  ---
421            #    |<------>|
422            #        s+2
423            #
424            set _masks($dir) [image create photo -width $sw -height $sh]
425            set _fills($dir) [image create photo -width $sw -height $sh]
426
427            set bg $itk_option(-background)
428            set light [Rappture::color::brightness $bg 0.4]
429            set dark [Rappture::color::brightness $bg -0.4]
430            set rgb [winfo rgb . $bg]
431            set flatbg [format "#%04x%04x%04x" [lindex $rgb 0] [lindex $rgb 1] [lindex $rgb 2]]
432            switch -- $itk_option(-relief) {
433                raised {
434                    set light [Rappture::color::brightness $bg 0.4]
435                    set dark [Rappture::color::brightness $bg -0.4]
436                    set bg $flatbg
437                }
438                flat - solid {
439                    set light $flatbg
440                    set dark $flatbg
441                    set bg $flatbg
442                }
443                sunken {
444                    set light [Rappture::color::brightness $bg -0.4]
445                    set dark [Rappture::color::brightness $bg 0.4]
446                    set bg $flatbg
447                }
448            }
449            set bg [format "#%04x%04x%04x" [lindex $rgb 0] [lindex $rgb 1] [lindex $rgb 2]]
450
451            $_fills($dir) put $bg -to 0 0 $sw $sh
452
453            switch -- $dir {
454              left {
455                set i 0
456                for {set j 0} {$j < $s/2} {incr j} {
457                    set ybtm [expr {$s-$j-1}]
458                    $_fills($dir) put $dark \
459                        -to $i [expr {$ybtm-1}] [expr {$i+2}] [expr {$ybtm+1}]
460                    $_fills($dir) put black \
461                        -to [expr {$i+2}] $ybtm [expr {$i+4}] [expr {$ybtm+1}]
462
463                    set ytop $j
464                    set ytoffs [expr {($j == $s/2-1) ? 1 : 2}]
465                    $_fills($dir) put $light \
466                        -to $i $ytop [expr {$i+2}] [expr {$ytop+$ytoffs}]
467                    $_fills($dir) put black \
468                        -to [expr {$i+2}] $ytop [expr {$i+4}] [expr {$ytop+1}]
469                    incr i 2
470                }
471                $_stems($dir).l configure -image $_fills($dir)
472
473                $_masks($dir) put black -to 0 0 $sw $sh
474                set i 0
475                for {set j 0} {$j < $s/2} {incr j} {
476                    for {set k [expr {$i+4}]} {$k < $s+2} {incr k} {
477                        $_masks($dir) transparency set $k $j yes
478                        $_masks($dir) transparency set $k [expr {$s-$j-1}] yes
479                    }
480                    incr i 2
481                }
482              }
483              right {
484                set i $sw
485                for {set j 0} {$j < $s/2} {incr j} {
486                    set ybtm [expr {$s-$j-1}]
487                    $_fills($dir) put $dark \
488                        -to [expr {$i-2}] [expr {$ybtm-1}] $i [expr {$ybtm+1}]
489                    $_fills($dir) put black \
490                        -to [expr {$i-4}] $ybtm [expr {$i-2}] [expr {$ybtm+1}]
491
492                    set ytop $j
493                    set ytoffs [expr {($j == $s/2-1) ? 1 : 2}]
494                    $_fills($dir) put $light \
495                        -to [expr {$i-2}] $ytop $i [expr {$ytop+$ytoffs}]
496                    $_fills($dir) put black \
497                        -to [expr {$i-4}] $ytop [expr {$i-2}] [expr {$ytop+1}]
498                    incr i -2
499                }
500                $_stems($dir).l configure -image $_fills($dir)
501
502                $_masks($dir) put black -to 0 0 $sw $sh
503                set i $sw
504                for {set j 0} {$j < $s/2} {incr j} {
505                    for {set k 0} {$k < $i-4} {incr k} {
506                        $_masks($dir) transparency set $k $j yes
507                        $_masks($dir) transparency set $k [expr {$s-$j-1}] yes
508                    }
509                    incr i -2
510                }
511              }
512              above {
513                set i 0
514                for {set j 0} {$j < $s/2} {incr j} {
515                    set xrhs [expr {$s-$j-1}]
516                    $_fills($dir) put $dark \
517                        -to [expr {$xrhs-1}] $i [expr {$xrhs+1}] [expr {$i+2}]
518                    $_fills($dir) put black \
519                        -to $xrhs [expr {$i+2}] [expr {$xrhs+1}] [expr {$i+4}]
520
521                    set xlhs $j
522                    set xloffs [expr {($j == $s/2-1) ? 1 : 2}]
523                    $_fills($dir) put $light \
524                        -to $xlhs $i [expr {$xlhs+$xloffs}] [expr {$i+2}]
525                    $_fills($dir) put black \
526                        -to $xlhs [expr {$i+2}] [expr {$xlhs+1}] [expr {$i+4}]
527                    incr i 2
528                }
529                $_stems($dir).l configure -image $_fills($dir)
530
531                $_masks($dir) put black -to 0 0 $sw $sh
532                set i 0
533                for {set j 0} {$j < $s/2} {incr j} {
534                    for {set k [expr {$i+4}]} {$k < $s+2} {incr k} {
535                        $_masks($dir) transparency set $j $k yes
536                        $_masks($dir) transparency set [expr {$s-$j-1}] $k yes
537                    }
538                    incr i 2
539                }
540              }
541              below {
542                set i $sh
543                for {set j 0} {$j < $s/2} {incr j} {
544                    set xrhs [expr {$s-$j-1}]
545                    $_fills($dir) put $dark \
546                        -to [expr {$xrhs-1}] [expr {$i-2}] [expr {$xrhs+1}] $i
547                    $_fills($dir) put black \
548                        -to $xrhs [expr {$i-4}] [expr {$xrhs+1}] [expr {$i-2}]
549
550                    set xlhs $j
551                    set xloffs [expr {($j == $s/2-1) ? 1 : 2}]
552                    $_fills($dir) put $light \
553                        -to $xlhs [expr {$i-2}] [expr {$xlhs+$xloffs}] $i
554                    $_fills($dir) put black \
555                        -to $xlhs [expr {$i-4}] [expr {$xlhs+1}] [expr {$i-2}]
556                    incr i -2
557                }
558                $_stems($dir).l configure -image $_fills($dir)
559
560                $_masks($dir) put black -to 0 0 $sw $sh
561                set i $sh
562                for {set j 0} {$j < $s/2} {incr j} {
563                    for {set k 0} {$k < $i-4} {incr k} {
564                        $_masks($dir) transparency set $j $k yes
565                        $_masks($dir) transparency set [expr {$s-$j-1}] $k yes
566                    }
567                    incr i -2
568                }
569              }
570            }
571        }
572    } else {
573        #
574        # No shape extension.  Do the best we can by creating a
575        # black line for all directions.
576        #
577        foreach {dir w h} [list \
578            left   $itk_option(-stemlength) 3 \
579            right  $itk_option(-stemlength) 3 \
580            above  3 $itk_option(-stemlength) \
581            below  3 $itk_option(-stemlength) \
582        ] {
583            set _stems($dir) [toplevel $itk_interior.s$dir \
584                -width $w -height $h \
585                -borderwidth 1 -relief solid -background black]
586            wm withdraw $_stems($dir)
587            wm overrideredirect $_stems($dir) yes
588
589            # create this for size, even though we don't really use it
590            set _fills($dir) [image create photo -width $w -height $h]
591        }
592    }
593}
594
595# ----------------------------------------------------------------------
596# USAGE: outside <widget> <x> <y>
597#
598# Used internally to see if the click point <x>,<y> is outside of
599# this widget.  If so, the widget usually releases is grab and
600# deactivates.
601# ----------------------------------------------------------------------
602itcl::body Rappture::Balloon::outside {widget x y} {
603    return [expr {$x < [winfo rootx $widget]
604             || $x > [winfo rootx $widget]+[winfo width $widget]
605             || $y < [winfo rooty $widget]
606             || $y > [winfo rooty $widget]+[winfo height $widget]}]
607}
608
609# ----------------------------------------------------------------------
610# CONFIGURATION OPTION: -background
611# ----------------------------------------------------------------------
612itcl::configbody Rappture::Balloon::background {
613    _createStems
614}
615
616# ----------------------------------------------------------------------
617# CONFIGURATION OPTION: -stemlength
618#
619# Used internally to create the stems that connect a balloon panel
620# to its anchor point, in all four possible directions:  left, right,
621# above, and below.
622# ----------------------------------------------------------------------
623itcl::configbody Rappture::Balloon::stemlength {
624    if {$itk_option(-stemlength) % 2 != 0} {
625        error "stem length should be an even number of pixels"
626    }
627}
628
629# ----------------------------------------------------------------------
630# CONFIGURATION OPTION: -dismissbutton
631# ----------------------------------------------------------------------
632itcl::configbody Rappture::Balloon::dismissbutton {
633    if {![string is boolean $itk_option(-dismissbutton)]} {
634        error "bad value \"$itk_option(-dismissbutton)\": should be on/off, 1/0, true/false, yes/no"
635    }
636    if {$itk_option(-dismissbutton)} {
637        pack $itk_component(titlebar) -before $itk_component(inner) \
638            -side top -fill x
639        pack $itk_component(dismiss) -side right -padx 4
640    } elseif {"" != $itk_option(-title)} {
641        pack $itk_component(titlebar) -before $itk_component(inner) \
642            -side top -fill x
643        pack forget $itk_component(dismiss)
644    } else {
645        pack forget $itk_component(titlebar)
646    }
647}
648
649# ----------------------------------------------------------------------
650# CONFIGURATION OPTION: -padx
651# ----------------------------------------------------------------------
652itcl::configbody Rappture::Balloon::padx {
653    pack $itk_component(inner) -padx $itk_option(-padx)
654}
655
656# ----------------------------------------------------------------------
657# CONFIGURATION OPTION: -pady
658# ----------------------------------------------------------------------
659itcl::configbody Rappture::Balloon::pady {
660    pack $itk_component(inner) -pady $itk_option(-pady)
661}
662
663# ----------------------------------------------------------------------
664# CONFIGURATION OPTION: -title
665# ----------------------------------------------------------------------
666itcl::configbody Rappture::Balloon::title {
667    if {"" != $itk_option(-title) || $itk_option(-dismissbutton)} {
668        pack $itk_component(titlebar) -before $itk_component(inner) \
669            -side top -fill x
670        if {$itk_option(-dismissbutton)} {
671            pack $itk_component(dismiss) -side right -padx 4
672        } else {
673            pack forget $itk_component(dismiss)
674        }
675    } else {
676        pack forget $itk_component(titlebar)
677    }
678}
Note: See TracBrowser for help on using the repository browser.