source: branches/blt4/gui/scripts/tooltip.tcl @ 1695

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