File: widgetip.tcl

package info (click to toggle)
moodss 19.7-1
  • links: PTS
  • area: main
  • in suites: sarge
  • size: 6,136 kB
  • ctags: 3,149
  • sloc: tcl: 49,048; ansic: 187; perl: 178; makefile: 166; sh: 109; python: 65
file content (268 lines) | stat: -rw-r--r-- 12,074 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
# $Id: widgetip.tcl,v 2.18 2005/01/30 19:15:39 jfontain Exp $


class widgetTip {

    variable screenWidth [winfo screenwidth .]
    variable screenHeight [winfo screenheight .]
    variable xOffset 7
    variable yOffset 10

    class topLabel {

        proc topLabel {this parentPath args} composite {
            [new toplevel $parentPath -highlightbackground black -highlightthickness 1] $args
        } {
            composite::manage $this [new label $widget::($this,path) -justify left] label
            composite::complete $this
            pack $composite::($this,label,path)
            wm overrideredirect $widget::($this,path) 1                                             ;# no window manager decorations
        }

        proc ~topLabel {this} {}

        proc options {this} {
            return [list\
                [list -bordercolor Black Black]\
                [list -borderwidth 1 1]\
                [list -background $widget::option(button,background) $widget::option(button,background)]\
                [list -font $widget::option(button,font) $widget::option(button,font)]\
                [list -foreground $widget::option(button,foreground) $widget::option(button,foreground)]\
                [list -text {} {}]\
                [list -wraplength 400]\
            ]
        }

        foreach option {-background -font -foreground -text -wraplength} {
            proc set$option {this value} "\$composite::(\$this,label,path) configure $option \$value"
        }

        proc set-bordercolor {this value} {
            $widget::($this,path) configure -highlightbackground $value
        }

        proc set-borderwidth {this value} {
            $widget::($this,path) configure -highlightthickness $value
        }

    }

    if {![info exists (label)]} {
        set (label) [new topLabel . -font $widget::option(entry,font) -background #FFFFDF]
        set (path) $widget::($(label),path)
        wm withdraw $(path)
        # handle button and key presses as global events for some child widgets (such as entries) do not pass them to their parent
        bind all <ButtonPress> {widgetTip::globalEvent %W}
        bind all <KeyPress> {widgetTip::globalEvent %W}
        set (xLast) -1
        set (yLast) -1
    }

    proc widgetTip {this args} switched {$args} {
        switched::complete $this
        setupBindings $this
    }

    proc ~widgetTip {this} {
        catch {after cancel $($this,event)}
        if {!$switched::($this,-ephemeral)} {            ;# avoid infinite loop since ephemeral tip deletes self when first disabled
            disable $this
        }
        if {[info exists ($this,bindings)]} {                                                              ;# remove bindings if any
            delete $($this,bindings)
        }
        set path $switched::($this,-path)
        set tag $switched::($this,-itemortag)
        if {([string length $path] > 0) && ([string length $tag] > 0)} {                                   ;# remove canvas bindings
            array set match [list <Enter> "widgetTip::enable $this" <Leave> "widgetTip::disable $this"]
            foreach sequence [array names match] {
                set script {}
                foreach line [split [$path bind $tag $sequence] \n] {
                    if {![string equal [string trim $line] $match($sequence)]} {
                        if {[string length $script] > 0} {append script \n}
                        append script $line
                    }
                }
                $path bind $tag $sequence $script                                                 ;# restore original binding script
            }
        }
    }

    proc options {this} {
        return [list\
            [list -ephemeral 0 0]\
            [list -font $widget::option(entry,font) $widget::option(entry,font)]\
            [list -itemortag {} {}]\
            [list -path {} {}]\
            [list -rectangle {} {}]\
            [list -state normal normal]\
            [list -text {} {}]\
        ]
    }

    proc set-ephemeral {this value} {
        if {$switched::($this,complete)} {
            error {option -ephemeral cannot be set dynamically}
        }
    }

    proc set-itemortag {this value} {                     ;# implies that tip cannot be deleted before the canvas that it applies to
        if {$switched::($this,complete)} {
            error {option -itemortag cannot be set dynamically}
        }
        if {[string length $switched::($this,-rectangle)] > 0} {
            error {-itemortag and -rectangle options are incompatible}
        }
        if {([string length $switched::($this,-path)] > 0) && [catch {$switched::($this,-path) type $value} message]} {
            error "$switched::($this,-path) is not a canvas, $value not a valid item or tag, ...: $message"
        }
    }

    proc set-path {this value} {
        if {$switched::($this,complete)} {
            error {option -path cannot be set dynamically}
        }
        if {![winfo exists $value]} {
            error "invalid widget: \"$value\""
        }
        if {([string length $switched::($this,-itemortag)] > 0) && [catch {$value type $switched::($this,-itemortag)} message]} {
            error "$value is not a canvas, $switched::($this,-itemortag) not a valid item or tag, ...: $message"
        }
    }

    proc set-rectangle {this value} {
        if {[string length $switched::($this,-itemortag)] > 0} {
            error {-itemortag and -rectangle options are incompatible}
        }
        set error 0
        if {[llength $value] != 4} {
            set error 1
        } else {
            foreach item $value {
                if {![string is integer -strict $item]} {set error 1; break}
            }
        }
        if {$error} {
            error {-rectangle option must be a list of 4 integers}
        }
        foreach [list ($this,left) ($this,top) ($this,right) ($this,bottom)] $value {}
        setupBindings $this
        if {[string length $switched::($this,-path)] > 0} {                   ;# generate an artificial motion event for correctness
            set path $switched::($this,-path)
            after idle widgetTip::motion $this [expr {[winfo pointerx $path] - [winfo rootx $path]}]\
                [expr {[winfo pointery $path] - [winfo rooty $path]}]                 ;# wait after object is completely constructed
        }
    }

    proc set-state {this value} {
        switch $value {
            disabled {disable $this}
            normal {}
            default {error "bad state value \"$value\": must be normal or disabled"}
        }
    }

    proc setupBindings {this} {                                                                    ;# invoked right after completion
        if {[string length $switched::($this,-itemortag)] == 0} {
            if {![info exists ($this,bindings)]} {                    ;# may be invoked several times when setting -rectangle option
                set ($this,bindings) [new bindings $switched::($this,-path) 0]
            }
            if {[string length $switched::($this,-rectangle)] > 0} {
                bindings::set $($this,bindings) <Enter> {}                              ;# possibly reset existing binding for -path
                bindings::set $($this,bindings) <Leave> "widgetTip::disable $this; catch {unset widgetTip::($this,in)}"
                bindings::set $($this,bindings) <Motion> "widgetTip::motion $this %x %y"
            } else {
                bindings::set $($this,bindings) <Enter> "widgetTip::enable $this"
                bindings::set $($this,bindings) <Leave> "widgetTip::disable $this"
            }
        } else {
            $switched::($this,-path) bind $switched::($this,-itemortag) <Enter> "+ widgetTip::enable $this"
            $switched::($this,-path) bind $switched::($this,-itemortag) <Leave> "+ widgetTip::disable $this"
        }
    }

    proc set-font {this value} {}                                                  ;# nothing to do, data is saved at switched level
    proc set-text {this value} {
        if {[info exists (active)] && ($(active) == $this)} {
            widget::configure $(label) -text $value                                                              ;# update tip label
        }
    }

    proc globalEvent {widget} {
        if {![catch {string first $switched::($(active),-path) $widget} value] && ($value == 0)} {
            disable $(active)                        ;# hide if active widget exists and is a descendant of the active target widget
        }
    }

    proc show {this x y} {                                                                             ;# pointer screen coordinates
        variable screenWidth
        variable screenHeight
        variable xOffset
        variable yOffset

        set path $(path)
        widget::configure $(label) -font $switched::($this,-font) -text $switched::($this,-text)                 ;# update tip label
        update idletasks                                                                              ;# make sure sizes are correct
        set size [winfo reqwidth $path]
        set delta [expr {$screenWidth - $x - $xOffset - $size}]
        if {$delta < 0} {                  ;# widget tip right edge would be pass screen: position widget right edge left of pointer
            incr x -$xOffset
            incr x -$size
        } else {
            incr x $xOffset
        }
        set size [winfo reqheight $path]
        set delta [expr {$screenHeight - $y - $yOffset - $size}]
        if {$delta < 0} {                  ;# widget tip bottom edge would be pass screen: position widget bottom edge above pointer
            incr y -$yOffset
            incr y -$size
        } else {
            incr y $yOffset
        }
        showTopLevel $path +$x+$y
        update idletasks
        raise $path
    }

    proc enable {this} {
        if {[catch {classof $this}]} return                                                              ;# has been deleted already
        if {[string equal $switched::($this,-state) disabled] || ([string length $switched::($this,-text)] == 0)} {
            return                                                                                             ;# nothing to display
        }
        set x [winfo pointerx $(path)]
        set y [winfo pointery $(path)]
        if {($x == $(xLast)) && ($y == $(yLast))} {
            catch {after cancel $($this,event)}
            show $this $x $y
        } else {
            set (xLast) $x
            set (yLast) $y
            set ($this,event) [after 300 "widgetTip::enable $this"]                                                          ;# poll
        }
        set (active) $this                                                                                 ;# remember active object
    }

    proc disable {this} {
        # event and active tip may no longer exist when the pointer leaves after a click (for example)
        catch {after cancel $($this,event)}
        catch {unset (active)}
        wm withdraw $(path)
        if {$switched::($this,-ephemeral)} {after idle "if {!\[catch {classof $this}\]} {delete $this}"}
    }

    proc motion {this x y} {
        if {[catch {classof $this}]} return                                                              ;# has been deleted already
        if {($x < $($this,left)) || ($y < $($this,top)) || ($x > $($this,right)) || ($y > $($this,bottom))} {    ;# out of rectangle
            if {[info exists ($this,in)]} {                                                         ;# just crossed rectangle border
                unset ($this,in)
                disable $this
            }
        } else {                                                                                                     ;# in rectangle
            if {![info exists ($this,in)]} {                                                        ;# just crossed rectangle border
                set ($this,in) {}
                enable $this
            }
        }
    }

}