source: trunk/gui/scripts/editor.tcl @ 6300

Last change on this file since 6300 was 6181, checked in by ldelgass, 8 years ago

Add numeric keypad enter binding to Rappture::Editor

File size: 10.7 KB
Line 
1# -*- mode: tcl; indent-tabs-mode: nil -*-
2# ----------------------------------------------------------------------
3#  COMPONENT: editor - pop-up editor for little bits of text
4#
5#  This widget acts as a pop-up editor for small text fields.  It
6#  pops up on top of any text field, accepts edits, and then attempts
7#  to validate and apply changes back to the underlying widget.
8#
9#  This widget uses a number of callbacks to handle communication
10#  with the underlying widget:
11#
12#  -activatecommand .... Should return a key/value list with the
13#                        following elements:
14#                          x ...... root x coordinate for editor
15#                          y ...... root y coordinate for editor
16#                          w ...... width of text being edited
17#                          h ...... height of text being edited
18#                          text ... initial text for the editor
19#
20#  -validatecommand .... Invoked with the new value as an argument.
21#                        Should return 1 if the value is okay, and
22#                        0 otherwise.
23#
24#  -applycommand ....... Invoked with the new value as an argument.
25#                        Should apply the new value to the underlying
26#                        widget.
27#
28# ======================================================================
29#  AUTHOR:  Michael McLennan, Purdue University
30#  Copyright (c) 2004-2012  HUBzero Foundation, LLC
31#
32#  See the file "license.terms" for information on usage and
33#  redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES.
34# ======================================================================
35package require Itk
36
37option add *Editor.background white widgetDefault
38option add *Editor.outline black widgetDefault
39option add *Editor.borderwidth 1 widgetDefault
40option add *Editor.relief flat widgetDefault
41option add *Editor.selectBorderWidth 0 widgetDefault
42
43itcl::class Rappture::Editor {
44    inherit itk::Toplevel
45
46    itk_option define -outline outline Outline ""
47    itk_option define -activatecommand activateCommand ActivateCommand ""
48    itk_option define -validatecommand validateCommand ValidateCommand ""
49    itk_option define -applycommand applyCommand ApplyCommand ""
50
51    constructor {args} { # defined below }
52
53    public method activate {}
54    public method deactivate {args}
55    public method value {newval}
56
57    protected method _click {x y}
58    protected method _resize {}
59    protected variable _loc   ;# array of editor location parameters
60}
61
62itk::usual Editor {
63    keep -cursor -font
64}
65
66# ----------------------------------------------------------------------
67# CONSTRUCTOR
68# ----------------------------------------------------------------------
69itcl::body Rappture::Editor::constructor {args} {
70    wm overrideredirect $itk_component(hull) yes
71    wm withdraw $itk_component(hull)
72
73    itk_option remove hull.background hull.borderwidth
74    component hull configure -borderwidth 1
75
76    itk_component add editor {
77        entry $itk_interior.editor -highlightthickness 0
78    } {
79        usual
80        keep -relief
81        ignore -highlightthickness
82        ignore -highlightcolor
83        ignore -highlightbackground
84    }
85    pack $itk_component(editor) -expand yes -fill both
86
87    bind $itk_component(editor) <KeyPress> \
88        [itcl::code $this _resize]
89    bind $itk_component(editor) <KeyPress-Return> \
90        [itcl::code $this deactivate]
91    bind $itk_component(editor) <KP_Enter> \
92        [itcl::code $this deactivate]
93    bind $itk_component(editor) <KeyPress-Escape> \
94        [itcl::code $this deactivate -abort]
95    bind $itk_component(editor) <ButtonPress> \
96        [itcl::code $this _click %X %Y]
97
98    itk_component add emenu {
99        menu $itk_component(editor).menu -tearoff 0
100    } {
101        usual
102        ignore -tearoff
103        ignore -background -foreground
104    }
105    $itk_component(emenu) add command -label "Cut" -accelerator "^X" \
106        -command [list event generate $itk_component(editor) <<Cut>>]
107    $itk_component(emenu) add command -label "Copy" -accelerator "^C" \
108        -command [list event generate $itk_component(editor) <<Copy>>]
109    $itk_component(emenu) add command -label "Paste" -accelerator "^V" \
110        -command [list event generate $itk_component(editor) <<Paste>>]
111    bind $itk_component(editor) <<PopupMenu>> {
112        tk_popup %W.menu %X %Y
113    }
114
115    eval itk_initialize $args
116}
117
118# ----------------------------------------------------------------------
119# USAGE: activate
120#
121# Clients use this to start the editing process on the underlying
122# widget.  This pops up the editor with the current text from the
123# underlying widget and allows the user to edit the text.  The editor
124# remains up until it is deactivated.
125# ----------------------------------------------------------------------
126itcl::body Rappture::Editor::activate {} {
127    set e $itk_component(editor)
128    if {[winfo ismapped $e]} {
129        return  ;# already mapped -- nothing to do
130    }
131
132    set info ""
133    if {[string length $itk_option(-activatecommand)] > 0} {
134        set status [catch {uplevel #0 $itk_option(-activatecommand)} info]
135        if {$status != 0} {
136            bgerror $info
137            return
138        }
139    }
140
141    #
142    # Pull out the location information from the values passed back
143    # from the activation command.  We must have at least an x,y
144    # coordinate.  If we get width and height too, then use it.
145    # If not, figure out the width and height based on the size
146    # of the string.
147    #
148    array set vals $info
149    if {![info exists vals(x)] || ![info exists vals(y)]} {
150        return
151    }
152    set _loc(x) $vals(x)
153    set _loc(y) $vals(y)
154    set _loc(w) [expr {([info exists vals(w)]) ? $vals(w) : 0}]
155    set _loc(h) [expr {([info exists vals(h)]) ? $vals(h) : 0}]
156
157    $itk_component(editor) delete 0 end
158    if {[info exists vals(text)]} {
159        $itk_component(editor) insert end $vals(text)
160    }
161    $itk_component(editor) select from 0
162    $itk_component(editor) select to end
163
164    _resize
165    wm deiconify $itk_component(hull)
166    raise $itk_component(hull)
167    focus -force $itk_component(editor)
168
169    # try to grab the pointer, and keep trying...
170    update
171    while {[catch {grab set -global $itk_component(editor)}]} {
172        after 100
173    }
174}
175
176# ----------------------------------------------------------------------
177# USAGE: deactivate ?-abort?
178#
179# This is invoked automatically whenever the user presses Enter or
180# Escape in the editor.  Clients can also use it explicitly to
181# deactivate the editor.
182#
183# If the -abort flag is specified, then the editor is taken down
184# without any validation or application of the result.  Otherwise,
185# we validate the contents of the editor and apply the change back
186# to the widget.
187# ----------------------------------------------------------------------
188itcl::body Rappture::Editor::deactivate {args} {
189    # take down any error cue that might be up
190    ::Rappture::Tooltip::cue hide
191
192    if {$args == "-abort"} {
193        grab release $itk_component(editor)
194        wm withdraw $itk_component(hull)
195        return
196    }
197
198    set str [$itk_component(editor) get]
199
200    #
201    # If there's a -validatecommand option, then invoke the code
202    # now to check the new value.
203    #
204    if {[string length $itk_option(-validatecommand)] > 0} {
205        set cmd "uplevel #0 [list $itk_option(-validatecommand) [list $str]]"
206        if {[catch $cmd result]} {
207            bgerror $result
208            set result 1
209        }
210        if {$result == 0} {
211            bell
212            $itk_component(editor) select from 0
213            $itk_component(editor) select to end
214            $itk_component(editor) icursor end
215            focus $itk_component(editor)
216            return
217        }
218    }
219
220    grab release $itk_component(editor)
221    wm withdraw $itk_component(hull)
222
223    #
224    # If there's an -applycommand option, then invoke the code
225    # now to apply the new value.
226    #
227    if {[string length $itk_option(-applycommand)] > 0} {
228        set cmd "uplevel #0 [list $itk_option(-applycommand) [list $str]]"
229        if {[catch $cmd result]} {
230            bgerror $result
231            return
232        }
233    }
234}
235
236# ----------------------------------------------------------------------
237# USAGE: value <newval>
238#
239# Clients use this to suggest a new value, particular when they've
240# caught an error in the editing process.  For example, if the user's
241# value is below the minimum allowed value, a client would call this
242# method to suggest the minimum value.
243# ----------------------------------------------------------------------
244itcl::body Rappture::Editor::value {newval} {
245    $itk_component(editor) delete 0 end
246    $itk_component(editor) insert end $newval
247}
248
249# ----------------------------------------------------------------------
250# USAGE: _click <X> <Y>
251#
252# This is invoked automatically whenever the user clicks somewhere
253# inside or outside of the editor.  If the <X>,<Y> coordinate is
254# outside the editor, then we assume the user is done and wants to
255# take the editor down.  Otherwise, we do nothing, and let the entry
256# bindings take over.
257# ----------------------------------------------------------------------
258itcl::body Rappture::Editor::_click {x y} {
259    if {[winfo containing $x $y] != $itk_component(editor)} {
260        deactivate
261    } else {
262        # make sure the editor has keyboard focus!
263        # it loses focus sometimes during cut/copy/paste operations
264        focus -force $itk_component(editor)
265    }
266}
267
268# ----------------------------------------------------------------------
269# USAGE: _resize
270#
271# Invoked automatically as each key is pressed in the editor.
272# Resizes the editor so that it is just big enough to show all
273# of the text within it.
274# ----------------------------------------------------------------------
275itcl::body Rappture::Editor::_resize {} {
276    set e $itk_component(editor)
277    set str [$e get]
278    set fnt [$e cget -font]
279
280    set w [expr {[font measure $fnt $str]+20}]
281    set w [expr {($w < $_loc(w)) ? $_loc(w) : $w}]
282    if {$w+$_loc(x) >= [winfo screenwidth $e]} {
283        set w [expr {[winfo screenwidth $e]-$_loc(x)}]
284    }
285
286    set h [expr {[font metrics $fnt -linespace]+4}]
287    set h [expr {($h < $_loc(h)) ? $_loc(h) : $h}]
288    if {$h+$_loc(y) >= [winfo screenheight $e]} {
289        set h [expr {[winfo screenheight $e]-$_loc(y)}]
290    }
291    # Temporary fix to prevent Opps. Don't deal with negative dimensions.
292    if { $w <= 0 || $h <= 0 } {
293        wm geometry $itk_component(hull) "+$_loc(x)+$_loc(y)"
294    } else {
295        wm geometry $itk_component(hull) "${w}x${h}+$_loc(x)+$_loc(y)"
296    }
297}
298
299# ----------------------------------------------------------------------
300# CONFIGURATION OPTION: -outline
301# ----------------------------------------------------------------------
302itcl::configbody Rappture::Editor::outline {
303    component hull configure -background $itk_option(-outline)
304}
Note: See TracBrowser for help on using the repository browser.