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

Last change on this file since 95 was 43, checked in by mmc, 19 years ago
  • Added a new Rappture.result() function to the Python library. This makes it easy to finalize results. See examples/graph for example use.
  • Added examples/graph to illustrate a simple tool with a string and two numbers for input.
  • Fixed the XY graph to show crosshairs and pop-up info when you mouse over particular points.
  • Fixed Rappture::exec so that it doesn't add stray newlines when a program has lots (more than 8k) of output.
  • Fixed the analyzer to recognize the <tool><analyzer> tag. When set to "last", this automatically clears the last result. Handy for programs like SPICE, where you don't compare much, but keep running one new simulation after another.
  • Fixed <string> entries to enable the Simulate button after each editing keystroke.
File size: 13.1 KB
Line 
1# ----------------------------------------------------------------------
2#  COMPONENT: tooltip - help information that pops up beneath a widget
3#
4#  This file provides support for tooltips, which are little bits
5#  of help information that pop up beneath a widget.
6#
7#  Tooltips can be registered for various widgets as follows:
8#
9#    Rappture::Tooltip::for .w "Some help text."
10#    Rappture::Tooltip::for .x.y "Some more help text."
11#
12#  Tooltips can also be popped up as an error cue beneath a widget
13#  with bad information:
14#
15#    Rappture::Tooltip::cue .w "Bad data in this widget."
16#
17# ======================================================================
18#  AUTHOR:  Michael McLennan, Purdue University
19#  Copyright (c) 2004-2005
20#  Purdue Research Foundation, West Lafayette, IN
21# ======================================================================
22package require Itk
23
24option add *Tooltip.background white widgetDefault
25option add *Tooltip.outline black widgetDefault
26option add *Tooltip.borderwidth 1 widgetDefault
27option add *Tooltip.font -*-helvetica-medium-r-normal-*-*-120-* widgetDefault
28option add *Tooltip.wrapLength 4i widgetDefault
29
30itcl::class Rappture::Tooltip {
31    inherit itk::Toplevel
32
33    itk_option define -outline outline Outline ""
34    itk_option define -icon icon Icon ""
35    itk_option define -message message Message ""
36
37    constructor {args} { # defined below }
38
39    public method show {where}
40    public method hide {}
41
42    public proc for {widget args}
43    public proc text {widget args}
44    private common catalog    ;# maps widget => message
45
46    public proc tooltip {option args}
47    private common pending "" ;# after ID for pending "tooltip show"
48
49    public proc cue {option args}
50
51    bind RapptureTooltip <Enter> \
52        [list ::Rappture::Tooltip::tooltip pending %W]
53    bind RapptureTooltip <Leave> \
54        [list ::Rappture::Tooltip::tooltip cancel]
55    bind RapptureTooltip <ButtonPress> \
56        [list ::Rappture::Tooltip::tooltip cancel]
57    bind RapptureTooltip <KeyPress> \
58        [list ::Rappture::Tooltip::tooltip cancel]
59
60    private common icons
61    set dir [file dirname [info script]]
62    set icons(cue) [image create photo -file [file join $dir images cue24.gif]]
63}
64
65itk::usual Tooltip {
66    keep -background -outline -cursor -font
67}
68
69# ----------------------------------------------------------------------
70# CONSTRUCTOR
71# ----------------------------------------------------------------------
72itcl::body Rappture::Tooltip::constructor {args} {
73    wm overrideredirect $itk_component(hull) yes
74    wm withdraw $itk_component(hull)
75
76    component hull configure -borderwidth 1 -background black
77    itk_option remove hull.background hull.borderwidth
78
79    itk_component add icon {
80        label $itk_interior.icon -anchor n
81    }
82
83    itk_component add text {
84        label $itk_interior.text -justify left
85    } {
86        usual
87        keep -wraplength
88    }
89    pack $itk_component(text) -expand yes -fill both -ipadx 4 -ipady 4
90
91    eval itk_initialize $args
92}
93
94# ----------------------------------------------------------------------
95# USAGE: show @<x>,<y>|<widget>+/-<x>,<y>
96#
97# Clients use this to pop up the tooltip on the screen.  The position
98# should be either a <widget> name with an optional offset +/-<x>,<y>
99# (tooltip pops up beneath widget by default), or a specific root
100# window coordinate of the form @x,y.
101#
102# If the -message has the form "@command", then the command is executed
103# now, just before the tooltip is popped up, to build the message
104# on-the-fly.
105# ----------------------------------------------------------------------
106itcl::body Rappture::Tooltip::show {where} {
107    set hull $itk_component(hull)
108    set signx "+"
109    set signy "+"
110
111    if {[regexp {^@([0-9]+),([0-9]+)$} $where match x y]} {
112        set xpos $x
113        set ypos $y
114    } elseif {[regexp {^(.*)([-+])([0-9]+),([-+]?)([0-9]+)$} $where match win signx x signy y]} {
115        if {$signy == ""} { set signy $signx }
116        set xpos [expr {[winfo rootx $win] + $x}]
117        set ypos [expr {[winfo rooty $win] + $y}]
118    } elseif {[winfo exists $where]} {
119        set xpos [expr {[winfo rootx $where]+10}]
120        set ypos [expr {[winfo rooty $where]+[winfo height $where]}]
121    } else {
122        error "bad position \"$where\": should be widget+x,y, or @x,y"
123    }
124
125    if {[string index $itk_option(-message) 0] == "@"} {
126        set cmd [string range $itk_option(-message) 1 end]
127        if {[catch $cmd mesg] != 0} {
128            bgerror $mesg
129            return
130        }
131    } else {
132        set mesg $itk_option(-message)
133    }
134
135    # if there's no message to show, forget it
136    if {[string length $mesg] == 0} {
137        return
138    }
139
140    # strings can't be too big, or they'll go off screen!
141    if {[string length $mesg] > 1000} {
142        set mesg "[string range $mesg 0 1000]..."
143    }
144    set pos 0
145    ::for {set i 0} {$pos >= 0 && $i < 5} {incr i} {
146        incr pos
147        set pos [string first \n $mesg $pos]
148    }
149    if {$pos > 0} {
150        set mesg "[string range $mesg 0 $pos]..."
151    }
152    $itk_component(text) configure -text $mesg
153
154    #
155    # Make sure the tooltip doesn't go off screen.  Then, put it up.
156    #
157    update idletasks
158    if {$signx == "+"} {
159        if {$xpos+[winfo reqwidth $hull] > [winfo screenwidth $hull]} {
160            set xpos [expr {[winfo screenwidth $hull]-[winfo reqwidth $hull]}]
161        }
162        if {$xpos < 0} { set xpos 0 }
163    } else {
164        if {$xpos-[winfo reqwidth $hull] < 0} {
165            set xpos [expr {[winfo screenwidth $hull]-[winfo reqwidth $hull]}]
166        }
167        set xpos [expr {[winfo screenwidth $hull]-$xpos}]
168    }
169
170    if {$signy == "+"} {
171        if {$ypos+[winfo reqheight $hull] > [winfo screenheight $hull]} {
172            set ypos [expr {[winfo screenheight $hull]-[winfo reqheight $hull]}]
173        }
174        if {$ypos < 0} { set ypos 0 }
175    } else {
176        if {$ypos-[winfo reqheight $hull] < 0} {
177            set ypos [expr {[winfo screenheight $hull]-[winfo reqheight $hull]}]
178        }
179        set ypos [expr {[winfo screenheight $hull]-$ypos}]
180    }
181
182    wm geometry $hull $signx$xpos$signy$ypos
183    update
184
185    wm deiconify $hull
186    raise $hull
187}
188
189# ----------------------------------------------------------------------
190# USAGE: hide
191#
192# Takes down the tooltip, if it is showing on the screen.
193# ----------------------------------------------------------------------
194itcl::body Rappture::Tooltip::hide {} {
195    wm withdraw $itk_component(hull)
196}
197
198# ----------------------------------------------------------------------
199# USAGE: for <widget> <text>
200#
201# Used to register the tooltip <text> for a particular <widget>.
202# This sets up bindings on the widget so that, when the mouse pointer
203# lingers over the widget, the tooltip pops up automatically after
204# a small delay.  When the mouse pointer leaves the widget or the
205# user clicks on the widget, it cancels the tip.
206#
207# If the <text> has the form "@command", then the command is executed
208# just before the tip pops up to build the message on-the-fly.
209# ----------------------------------------------------------------------
210itcl::body Rappture::Tooltip::for {widget text} {
211    set catalog($widget) $text
212
213    set btags [bindtags $widget]
214    set i [lsearch $btags RapptureTooltip]
215    if {$i < 0} {
216        set i [lsearch $btags [winfo class $widget]]
217        if {$i < 0} {set i 0}
218        set btags [linsert $btags $i RapptureTooltip]
219        bindtags $widget $btags
220    }
221}
222
223# ----------------------------------------------------------------------
224# USAGE: text <widget> ?<text>?
225#
226# Used to query or set the text used for the tooltip for a widget.
227# This is done automatically when you call the "for" proc, but it
228# is sometimes handy to query or change the text later.
229# ----------------------------------------------------------------------
230itcl::body Rappture::Tooltip::text {widget args} {
231    if {[llength $args] == 0} {
232        if {[info exists catalog($widget)]} {
233            return $catalog($widget)
234        }
235        return ""
236    } elseif {[llength $args] == 1} {
237        set str [lindex $args 0]
238        set catalog($widget) $str
239    } else {
240        error "wrong # args: should be \"text widget ?str?\""
241    }
242}
243
244# ----------------------------------------------------------------------
245# USAGE: tooltip pending <widget> ?@<x>,<y>|+<x>,<y>?
246# USAGE: tooltip show <widget> ?@<x>,<y>|+<x>,<y>?
247# USAGE: tooltip cancel
248#
249# This is invoked automatically whenever the user clicks somewhere
250# inside or outside of the editor.  If the <X>,<Y> coordinate is
251# outside the editor, then we assume the user is done and wants to
252# take the editor down.  Otherwise, we do nothing, and let the entry
253# bindings take over.
254# ----------------------------------------------------------------------
255itcl::body Rappture::Tooltip::tooltip {option args} {
256    switch -- $option {
257        pending {
258            if {[llength $args] < 1 || [llength $args] > 2} {
259                error "wrong # args: should be \"tooltip pending widget ?@x,y?\""
260            }
261            set widget [lindex $args 0]
262            set loc [lindex $args 1]
263
264            if {![info exists catalog($widget)]} {
265                error "can't find tooltip for $widget"
266            }
267            if {$pending != ""} {
268                after cancel $pending
269            }
270            set pending [after 1500 [itcl::code tooltip show $widget $loc]]
271        }
272        show {
273            if {[llength $args] < 1 || [llength $args] > 2} {
274                error "wrong # args: should be \"tooltip pending widget ?@x,y?\""
275            }
276            set widget [lindex $args 0]
277            set loc [lindex $args 1]
278
279            if {[winfo exists $widget]} {
280                .rappturetooltip configure -message $catalog($widget)
281                if {[string index $loc 0] == "@"} {
282                    .rappturetooltip show $loc
283                } elseif {[regexp {^[-+]} $loc]} {
284                    .rappturetooltip show $widget$loc
285                } else {
286                    .rappturetooltip show $widget
287                }
288            }
289        }
290        cancel {
291            if {$pending != ""} {
292                after cancel $pending
293                set pending ""
294            }
295            .rappturetooltip hide
296        }
297        default {
298            error "bad option \"$option\": should be show, pending, cancel"
299        }
300    }
301}
302
303# ----------------------------------------------------------------------
304# USAGE: cue <location> <message>
305# USAGE: cue hide
306#
307# Clients use this to show a <message> in a tooltip cue at the
308# specified <location>, which can be a widget name or a root coordinate
309# at @x,y.
310# ----------------------------------------------------------------------
311itcl::body Rappture::Tooltip::cue {option args} {
312    if {"hide" == $option} {
313        grab release .rappturetoolcue
314        .rappturetoolcue hide
315    } elseif {[regexp {^@[0-9]+,[0-9]+$} $option] || [winfo exists $option]} {
316        if {[llength $args] != 1} {
317            error "wrong # args: should be \"cue location message\""
318        }
319        set loc $option
320        set mesg [lindex $args 0]
321
322        .rappturetoolcue configure -message $mesg
323        .rappturetoolcue show $loc
324
325        #
326        # Add a binding to all widgets so that any keypress will
327        # take this cue down.
328        #
329        set cmd [bind all <KeyPress>]
330        if {![regexp {Rappture::Tooltip::cue} $cmd]} {
331            bind all <KeyPress> "+[list ::Rappture::Tooltip::cue hide]"
332            bind all <KeyPress-Return> "+ "
333        }
334
335        #
336        # If nobody has the pointer, then grab it.  Otherwise,
337        # we assume the pop-up editor or someone like that has
338        # the grab, so we don't need to impose a grab here.
339        #
340        if {"" == [grab current]} {
341            update
342            while {[catch {grab set -global .rappturetoolcue}]} {
343                after 100
344            }
345        }
346    } else {
347        error "bad option \"$option\": should be hide, a widget name, or @x,y"
348    }
349}
350
351# ----------------------------------------------------------------------
352# CONFIGURATION OPTION: -icon
353# ----------------------------------------------------------------------
354itcl::configbody Rappture::Tooltip::icon {
355    if {"" == $itk_option(-icon)} {
356        $itk_component(icon) configure -image ""
357        pack forget $itk_component(icon)
358    } else {
359        $itk_component(icon) configure -image $itk_option(-icon)
360        pack $itk_component(icon) -before $itk_component(text) \
361            -side left -fill y
362    }
363}
364
365# ----------------------------------------------------------------------
366# CONFIGURATION OPTION: -outline
367# ----------------------------------------------------------------------
368itcl::configbody Rappture::Tooltip::outline {
369    component hull configure -background $itk_option(-outline)
370}
371
372# create a tooltip widget to show tool tips
373Rappture::Tooltip .rappturetooltip
374
375# any click on any widget takes down the tooltip
376bind all <Leave> [list ::Rappture::Tooltip::tooltip cancel]
377bind all <ButtonPress> [list ::Rappture::Tooltip::tooltip cancel]
378
379# create a tooltip widget to show error cues
380Rappture::Tooltip .rappturetoolcue \
381    -icon $Rappture::Tooltip::icons(cue) \
382    -background black -outline #333333 -foreground white
383
384# when cue is up, it has a grab, and any click brings it down
385bind .rappturetoolcue <ButtonPress> [list ::Rappture::Tooltip::cue hide]
Note: See TracBrowser for help on using the repository browser.