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

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

merge (by hand) with Rappture1.2 branch

File size: 16.0 KB
RevLine 
[3330]1# -*- mode: tcl; indent-tabs-mode: nil -*-
[1]2# ----------------------------------------------------------------------
3#  COMPONENT: tooltip - help information that pops up beneath a widget
4#
5#  This file provides support for tooltips, which are little bits
6#  of help information that pop up beneath a widget.
7#
8#  Tooltips can be registered for various widgets as follows:
9#
10#    Rappture::Tooltip::for .w "Some help text."
11#    Rappture::Tooltip::for .x.y "Some more help text."
12#
13#  Tooltips can also be popped up as an error cue beneath a widget
14#  with bad information:
15#
16#    Rappture::Tooltip::cue .w "Bad data in this widget."
17#
18# ======================================================================
19#  AUTHOR:  Michael McLennan, Purdue University
[3177]20#  Copyright (c) 2004-2012  HUBzero Foundation, LLC
[115]21#
22#  See the file "license.terms" for information on usage and
23#  redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES.
[1]24# ======================================================================
25package require Itk
26
27option add *Tooltip.background white widgetDefault
28option add *Tooltip.outline black widgetDefault
29option add *Tooltip.borderwidth 1 widgetDefault
[676]30option add *Tooltip.font -*-helvetica-medium-r-normal-*-12-* widgetDefault
[11]31option add *Tooltip.wrapLength 4i widgetDefault
[1]32
33itcl::class Rappture::Tooltip {
34    inherit itk::Toplevel
35
36    itk_option define -outline outline Outline ""
37    itk_option define -icon icon Icon ""
38    itk_option define -message message Message ""
[3186]39    itk_option define -log log Log ""
[1]40
41    constructor {args} { # defined below }
42
43    public method show {where}
44    public method hide {}
45
[3186]46    private variable _showing 0  ;# time when tooltip popped up on screen
47
48    public proc for {widget text args}
[11]49    public proc text {widget args}
[3186]50    private common catalog    ;# maps widget => -message and -log
[1]51
[11]52    public proc tooltip {option args}
[1]53    private common pending "" ;# after ID for pending "tooltip show"
54
55    public proc cue {option args}
56
57    bind RapptureTooltip <Enter> \
[1614]58        [list ::Rappture::Tooltip::tooltip pending %W]
[1]59    bind RapptureTooltip <Leave> \
[1614]60        [list ::Rappture::Tooltip::tooltip cancel]
[1]61    bind RapptureTooltip <ButtonPress> \
[1614]62        [list ::Rappture::Tooltip::tooltip cancel]
[1]63    bind RapptureTooltip <KeyPress> \
[1614]64        [list ::Rappture::Tooltip::tooltip cancel]
[1]65}
66
67itk::usual Tooltip {
68    keep -background -outline -cursor -font
69}
70
71# ----------------------------------------------------------------------
72# CONSTRUCTOR
73# ----------------------------------------------------------------------
74itcl::body Rappture::Tooltip::constructor {args} {
75    wm overrideredirect $itk_component(hull) yes
76    wm withdraw $itk_component(hull)
77
78    component hull configure -borderwidth 1 -background black
79    itk_option remove hull.background hull.borderwidth
80
81    itk_component add icon {
[1614]82        label $itk_interior.icon -anchor n
[1]83    }
84
85    itk_component add text {
[1614]86        label $itk_interior.text -justify left
[1]87    } {
[1614]88        usual
89        keep -wraplength
[1]90    }
91    pack $itk_component(text) -expand yes -fill both -ipadx 4 -ipady 4
92
93    eval itk_initialize $args
94}
95
96# ----------------------------------------------------------------------
[43]97# USAGE: show @<x>,<y>|<widget>+/-<x>,<y>
[1]98#
99# Clients use this to pop up the tooltip on the screen.  The position
[43]100# should be either a <widget> name with an optional offset +/-<x>,<y>
[11]101# (tooltip pops up beneath widget by default), or a specific root
102# window coordinate of the form @x,y.
[1]103#
104# If the -message has the form "@command", then the command is executed
105# now, just before the tooltip is popped up, to build the message
106# on-the-fly.
107# ----------------------------------------------------------------------
108itcl::body Rappture::Tooltip::show {where} {
[11]109    set hull $itk_component(hull)
[3186]110    set _showing 0
111
[43]112    set signx "+"
113    set signy "+"
[11]114
[1]115    if {[regexp {^@([0-9]+),([0-9]+)$} $where match x y]} {
[1614]116        set xpos $x
117        set ypos $y
[43]118    } elseif {[regexp {^(.*)([-+])([0-9]+),([-+]?)([0-9]+)$} $where match win signx x signy y]} {
[1614]119        if {$signy == ""} { set signy $signx }
120        set xpos [expr {[winfo rootx $win] + $x}]
121        set ypos [expr {[winfo rooty $win] + $y}]
[1]122    } elseif {[winfo exists $where]} {
[1614]123        set xpos [expr {[winfo rootx $where]+10}]
124        set ypos [expr {[winfo rooty $where]+[winfo height $where]}]
[1]125    } else {
[1614]126        error "bad position \"$where\": should be widget+x,y, or @x,y"
[1]127    }
128
129    if {[string index $itk_option(-message) 0] == "@"} {
[1614]130        set cmd [string range $itk_option(-message) 1 end]
131        if {[catch $cmd mesg] != 0} {
132            bgerror $mesg
133            return
134        }
[1]135    } else {
[1614]136        set mesg $itk_option(-message)
[1]137    }
138
[26]139    # if there's no message to show, forget it
140    if {[string length $mesg] == 0} {
[1614]141        return
[26]142    }
143
[11]144    # strings can't be too big, or they'll go off screen!
145    set pos 0
[213]146    ::for {set i 0} {$pos >= 0 && $i < 20} {incr i} {
[1614]147        incr pos
148        set pos [string first \n $mesg $pos]
[11]149    }
150    if {$pos > 0} {
[1614]151        set mesg "[string range $mesg 0 $pos]..."
[11]152    }
[413]153    if {[string length $mesg] > 1000} {
[1614]154        set mesg "[string range $mesg 0 1500]..."
[413]155    }
[1]156    $itk_component(text) configure -text $mesg
157
[11]158    #
[413]159    # Make sure the tooltip doesn't go off screen.
[11]160    #
[43]161    update idletasks
162    if {$signx == "+"} {
[1614]163        if {$xpos+[winfo reqwidth $hull] > [winfo screenwidth $hull]} {
164            set xpos [expr {[winfo screenwidth $hull]-[winfo reqwidth $hull]}]
165        }
166        if {$xpos < 0} { set xpos 0 }
[43]167    } else {
[1614]168        if {$xpos-[winfo reqwidth $hull] < 0} {
169            set xpos [expr {[winfo screenwidth $hull]-[winfo reqwidth $hull]}]
170        }
171        set xpos [expr {[winfo screenwidth $hull]-$xpos}]
[11]172    }
[1]173
[43]174    if {$signy == "+"} {
[1614]175        if {$ypos+[winfo reqheight $hull] > [winfo screenheight $hull]} {
176            set ypos [expr {[winfo screenheight $hull]-[winfo reqheight $hull]}]
177        }
178        if {$ypos < 0} { set ypos 0 }
[43]179    } else {
[1614]180        if {$ypos-[winfo reqheight $hull] < 0} {
181            set ypos [expr {[winfo screenheight $hull]-[winfo reqheight $hull]}]
182        }
183        set ypos [expr {[winfo screenheight $hull]-$ypos}]
[11]184    }
185
[413]186    #
187    # Will the tooltip pop up under the mouse pointer?  If so, then
188    # it will just disappear.  Doh!  We should figure out a better
189    # place to pop it up.
190    #
191    set px [winfo pointerx $hull]
192    set py [winfo pointery $hull]
193    if {$px >= $xpos && $px <= $xpos+[winfo reqwidth $hull]
[1614]194          && $py >= $ypos && $py <= $ypos+[winfo reqheight $hull]} {
[413]195
[1614]196        if {$px > [winfo screenwidth $hull]/2} {
197            set signx "-"
198            set xpos [expr {[winfo screenwidth $hull]-$px+4}]
199        } else {
200            set signx "+"
201            set xpos [expr {$px+4}]
202        }
203        if {$py > [winfo screenheight $hull]/2} {
204            set signy "-"
205            set ypos [expr {[winfo screenheight $hull]-$py+4}]
206        } else {
207            set signy "+"
208            set ypos [expr {$py+4}]
209        }
[413]210    }
211
212    #
213    # Finally, put it up.
214    #
[43]215    wm geometry $hull $signx$xpos$signy$ypos
[11]216    update
217
218    wm deiconify $hull
219    raise $hull
[3186]220
221    #
222    # If logging is enabled, grab the start time.  We'll need this
223    # info later during the "hide" step to log activity.
224    #
225    if {$itk_option(-log) ne ""} {
226        set _showing [clock seconds]
227    }
[1]228}
229
230# ----------------------------------------------------------------------
231# USAGE: hide
232#
233# Takes down the tooltip, if it is showing on the screen.
234# ----------------------------------------------------------------------
235itcl::body Rappture::Tooltip::hide {} {
236    wm withdraw $itk_component(hull)
[3186]237
238    #
239    # If logging is enabled and the time is non-zero, then log
240    # the activity on this tooltip.
241    #
242    if {$itk_option(-log) ne "" && $_showing > 0} {
243        set dt [expr {[clock seconds] - $_showing}]
244        if {$dt > 0} {
245            Rappture::Logger::log tooltip -for $itk_option(-log) -time $dt
246        }
247    }
248    set _showing 0
[1]249}
250
251# ----------------------------------------------------------------------
[3186]252# USAGE: for <widget> <text> ?-log <name>?
[1]253#
254# Used to register the tooltip <text> for a particular <widget>.
255# This sets up bindings on the widget so that, when the mouse pointer
256# lingers over the widget, the tooltip pops up automatically after
257# a small delay.  When the mouse pointer leaves the widget or the
258# user clicks on the widget, it cancels the tip.
259#
260# If the <text> has the form "@command", then the command is executed
261# just before the tip pops up to build the message on-the-fly.
[3186]262#
263# The -log option turns logging on/off for this tooltip.  Logging is
264# useful when you want to keep track of whether the user is looking at
265# tooltips and for how long.  If the <name> is specified, then any
266# activity on the tooltip is reported with that name on the log line.
267# If the name is not specified or "", then the activity is not logged.
[1]268# ----------------------------------------------------------------------
[3186]269itcl::body Rappture::Tooltip::for {widget text args} {
270    Rappture::getopts args params {
271        value -log ""
272    }
273    if {[llength $args] > 0} {
274        error "wrong # args: should be \"for widget text ?-log name?\""
275    }
[1]276
[3186]277    set catalog($widget-message) $text
278    set catalog($widget-log) $params(-log)
279
[1]280    set btags [bindtags $widget]
281    set i [lsearch $btags RapptureTooltip]
282    if {$i < 0} {
[1614]283        set i [lsearch $btags [winfo class $widget]]
284        if {$i < 0} {set i 0}
285        set btags [linsert $btags $i RapptureTooltip]
286        bindtags $widget $btags
[1]287    }
288}
289
290# ----------------------------------------------------------------------
[3186]291# USAGE: text <widget> ?<text>? ?-log name?
[11]292#
293# Used to query or set the text used for the tooltip for a widget.
294# This is done automatically when you call the "for" proc, but it
295# is sometimes handy to query or change the text later.
296# ----------------------------------------------------------------------
297itcl::body Rappture::Tooltip::text {widget args} {
298    if {[llength $args] == 0} {
[3186]299        if {[info exists catalog($widget-message)]} {
300            return $catalog($widget-message)
[1614]301        }
302        return ""
[11]303    }
[3186]304
305    # set the text for the tooltip
306    set str [lindex $args 0]
307    set args [lrange $args 1 end]
308
309    Rappture::getopts args params {
310        value -log ""
311    }
312    if {[llength $args] > 0} {
313        error "wrong # args: should be \"text widget ?str? ?-log name?\""
314    }
315
316    set catalog($widget-message) $str
317    set catalog($widget-log) $params(-log)
[11]318}
319
320# ----------------------------------------------------------------------
321# USAGE: tooltip pending <widget> ?@<x>,<y>|+<x>,<y>?
322# USAGE: tooltip show <widget> ?@<x>,<y>|+<x>,<y>?
[1]323# USAGE: tooltip cancel
324#
325# This is invoked automatically whenever the user clicks somewhere
326# inside or outside of the editor.  If the <X>,<Y> coordinate is
327# outside the editor, then we assume the user is done and wants to
328# take the editor down.  Otherwise, we do nothing, and let the entry
329# bindings take over.
330# ----------------------------------------------------------------------
[11]331itcl::body Rappture::Tooltip::tooltip {option args} {
[1]332    switch -- $option {
[1614]333        pending {
334            if {[llength $args] < 1 || [llength $args] > 2} {
335                error "wrong # args: should be \"tooltip pending widget ?@x,y?\""
336            }
337            set widget [lindex $args 0]
338            set loc [lindex $args 1]
[11]339
[3186]340            if {![info exists catalog($widget-message)]} {
[1614]341                return;                        # No tooltip for widget.
342            }
343            if {$pending != ""} {
344                after cancel $pending
345            }
346            set pending [after 750 [itcl::code tooltip show $widget $loc]]
347        }
348        show {
349            if {[llength $args] < 1 || [llength $args] > 2} {
350                error "wrong # args: should be \"tooltip show tag loc\""
351            }
352            set tag [lindex $args 0]
353            set loc [lindex $args 1]
[11]354
[1614]355            # tag name may be .g-axis -- get widget ".g" part
356            set widget $tag
357            if {[regexp {^(\.[^-]+)-[^\.]+$} $widget match wname]} {
358                set widget $wname
359            }
[413]360
[3186]361            if {[winfo exists $widget] && [info exists catalog($tag-message)]} {
362                .rappturetooltip configure \
363                    -message $catalog($tag-message) \
364                    -log $catalog($tag-log)
365
[1614]366                if {[string index $loc 0] == "@"} {
367                    .rappturetooltip show $loc
368                } elseif {[regexp {^[-+]} $loc]} {
369                    .rappturetooltip show $widget$loc
370                } else {
371                    .rappturetooltip show $widget
372                }
373            }
374        }
375        cancel {
376            if {$pending != ""} {
377                after cancel $pending
378                set pending ""
379            }
380            .rappturetooltip hide
381        }
382        default {
383            error "bad option \"$option\": should be show, pending, cancel"
384        }
[1]385    }
386}
387
388# ----------------------------------------------------------------------
389# USAGE: cue <location> <message>
390# USAGE: cue hide
391#
392# Clients use this to show a <message> in a tooltip cue at the
393# specified <location>, which can be a widget name or a root coordinate
394# at @x,y.
395# ----------------------------------------------------------------------
396itcl::body Rappture::Tooltip::cue {option args} {
397    if {"hide" == $option} {
[1614]398        grab release .rappturetoolcue
399        .rappturetoolcue hide
[1]400    } elseif {[regexp {^@[0-9]+,[0-9]+$} $option] || [winfo exists $option]} {
[1614]401        if {[llength $args] != 1} {
402            error "wrong # args: should be \"cue location message\""
403        }
404        set loc $option
405        set mesg [lindex $args 0]
[1]406
[1614]407        .rappturetoolcue configure -message $mesg
408        .rappturetoolcue show $loc
[1]409
[1614]410        #
411        # Add a binding to all widgets so that any keypress will
412        # take this cue down.
413        #
414        set cmd [bind all <KeyPress>]
415        if {![regexp {Rappture::Tooltip::cue} $cmd]} {
416            bind all <KeyPress> "+[list ::Rappture::Tooltip::cue hide]"
417            bind all <KeyPress-Return> "+ "
418        }
[1]419
[1614]420        #
421        # If nobody has the pointer, then grab it.  Otherwise,
422        # we assume the pop-up editor or someone like that has
423        # the grab, so we don't need to impose a grab here.
424        #
425        if {"" == [grab current]} {
426            update
427            while {[catch {grab set -global .rappturetoolcue}]} {
428                after 100
429            }
430        }
[1]431    } else {
[1614]432        error "bad option \"$option\": should be hide, a widget name, or @x,y"
[1]433    }
434}
435
436# ----------------------------------------------------------------------
437# CONFIGURATION OPTION: -icon
438# ----------------------------------------------------------------------
439itcl::configbody Rappture::Tooltip::icon {
440    if {"" == $itk_option(-icon)} {
[1614]441        $itk_component(icon) configure -image ""
442        pack forget $itk_component(icon)
[1]443    } else {
[1614]444        $itk_component(icon) configure -image $itk_option(-icon)
445        pack $itk_component(icon) -before $itk_component(text) \
446            -side left -fill y
[1]447    }
448}
449
450# ----------------------------------------------------------------------
451# CONFIGURATION OPTION: -outline
452# ----------------------------------------------------------------------
453itcl::configbody Rappture::Tooltip::outline {
454    component hull configure -background $itk_option(-outline)
455}
456
[3186]457# ----------------------------------------------------------------------
458# CONFIGURATION OPTION: -log
459# ----------------------------------------------------------------------
460itcl::configbody Rappture::Tooltip::log {
461    # logging options changed -- reset showing time
462    set _showing 0
463}
464
[1]465# create a tooltip widget to show tool tips
466Rappture::Tooltip .rappturetooltip
467
[14]468# any click on any widget takes down the tooltip
469bind all <Leave> [list ::Rappture::Tooltip::tooltip cancel]
470bind all <ButtonPress> [list ::Rappture::Tooltip::tooltip cancel]
471
[1]472# create a tooltip widget to show error cues
473Rappture::Tooltip .rappturetoolcue \
[413]474    -icon [Rappture::icon cue24] \
[1]475    -background black -outline #333333 -foreground white
476
477# when cue is up, it has a grab, and any click brings it down
478bind .rappturetoolcue <ButtonPress> [list ::Rappture::Tooltip::cue hide]
Note: See TracBrowser for help on using the repository browser.