File: canvhand.tcl

package info (click to toggle)
moodss 5.0-2
  • links: PTS
  • area: main
  • in suites: slink
  • size: 1,104 kB
  • ctags: 430
  • sloc: tcl: 12,266; sh: 59; makefile: 43
file content (348 lines) | stat: -rw-r--r-- 18,610 bytes parent folder | download
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
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
# copyright (C) 1997-98 Jean-Luc Fontaine (mailto:jfontain@mygale.org)
# this program is free software: please read the COPYRIGHT file enclosed in this package or use the Help Copyright menu

set rcsId {$Id: canvhand.tcl,v 1.18 1998/07/11 09:56:54 jfontain Exp $}

class canvasWindowManager {

    class handles {     ;# embed widget in a frame with handles for resizing and moving inside a canvas acting as a window manager

        proc handles {this parentPath args} composite {[new frame $parentPath] $args} {
            if {[string compare [winfo class $parentPath] Canvas]!=0} {
                error "parent must be the manager canvas"
            }
            set ($this,item) [$parentPath create window 0 0 -window $widget::($this,path) -anchor nw]
            set ($this,canvas) $parentPath
            composite::complete $this
        }

        proc ~handles {this} {
            [set ($this,canvas)] delete [set ($this,item)] outline                       ;# delete canvas items (eventually outline)
            catch {delete [set ($this,bindings)]}                                                      ;# eventually delete bindings
        }

        proc options {this} {
            return [list\
                [list\
                    -background background Background\
                    $widget::(default,ButtonBackgroundColor) $widget::(default,ButtonBackgroundColor)\
                ]\
                [list -borderwidth borderWidth BorderWidth 3]\
                [list -handlesize handleSize HandleSize 7 7]\
                [list -path path Path {} {}]\
                [list -relief relief Relief ridge]\
                [list -setheight setHeight SetHeight {} {}]\
                [list -setwidth setWidth SetWidth {} {}]\
                [list -setx setX SetX 0 0]\
                [list -sety setY SetY 0 0]\
                [list -static static Static 0]\
            ]
        }

        proc set-handlesize {this value} {
            resize $this [winfo width $widget::($this,path)] [winfo height $widget::($this,path)]             ;# recalculate handles
        }

        proc set-path {this value} {
            if {![winfo exists $value]} {
                error "invalid widget: \"$value\""
            }
            set path $widget::($this,path)
            catch {eval pack forget [pack slaves $path]}                                        ;# eventually forget existing widget
            catch {delete [set ($this,bindings)]}                                             ;# eventually delete existing bindings
            set ($this,bindings) [new bindings $value end]
            bindings::set [set ($this,bindings)] <Visibility>\
                "set canvasWindowManager::handles::($this,partiallyObscured) \[string compare %s VisibilityUnobscured\]"
            raise $value $path
            pack $value -in $path -fill both -expand 1                                                    ;# expand as manager frame
        }

        foreach option {-background -relief -borderwidth} {
            proc set$option {this value} "\$widget::(\$this,path) configure $option \$value"
        }

        proc set-setheight {this value} {
            [set ($this,canvas)] itemconfigure [set ($this,item)] -height $value
        }

        proc set-setwidth {this value} {
            [set ($this,canvas)] itemconfigure [set ($this,item)] -width $value
        }

        proc set-setx {this value} {
            [set ($this,canvas)] coords [set ($this,item)] $value [lindex [[set ($this,canvas)] coords [set ($this,item)]] end]
        }

        proc set-sety {this value} {
            [set ($this,canvas)] coords [set ($this,item)] [lindex [[set ($this,canvas)] coords [set ($this,item)]] 0] $value
        }

        proc set-static {this value} {
            set path $widget::($this,path)
            if {$value} {
                bind $path <Configure> {}
                bind $path <Motion> {}
                bind $path <Enter> {}
                bind $path <Button1-Motion> {}
                bind $path <ButtonPress-1> {}
                bind $path <ButtonRelease-1> "canvasWindowManager::handles::toggleVisibility $this"
                $path configure -cursor arrow        ;# neutral cursor for user feedback since visibility toggling action is allowed
            } else {
                bind $path <Configure> "canvasWindowManager::handles::resize $this %w %h"                    ;# monitor size changes
                bind $path <Motion> "canvasWindowManager::handles::setCursor $this %x %y"
                # when just entering window, no motion event is yet generated
                bind $path <Enter> "canvasWindowManager::handles::setCursor $this %x %y"
                bind $path <Button1-Motion> "canvasWindowManager::handles::buttonMotion $this %x %y"
                bind $path <ButtonPress-1> "canvasWindowManager::handles::buttonPress $this %x %y"
                bind $path <ButtonRelease-1> "canvasWindowManager::handles::buttonRelease $this"
            }
        }

        proc buttonMotion {this x y} {
            set (motion) {}
            updateOutline $this $x $y
        }

        proc buttonPress {this x y} {
            set canvasWindowManager::handles::(xLast) $x                             ;### Tcl BUG: should be set ([xy]Last) $... ###
            set canvasWindowManager::handles::(yLast) $y
            lifoLabel::push $::messenger {}          ;# in case no other string is pushed before button release event pops messenger
            createOutline $this
        }

        proc toggleVisibility {this} {
            if {[set ($this,partiallyObscured)]} {
                raise $widget::($this,path)                                    ;# place on top is partially hidden by another window
            } else {
                lower $widget::($this,path)                     ;# else place below other windows so they get a chance to be visible
            }
            catch {raise $composite::($this,-path) $widget::($this,path)} ;# maintain managed widget (if it exists) just above frame
        }

        proc buttonRelease {this} {
            lifoLabel::pop $::messenger
            if {[info exists (motion)]} {                                                              ;# moving or resizing occured
                updateGeometry $this
                raise $widget::($this,path)                                          ;# always place frame on top after acting on it
                # maintain managed widget (if it exists) just above frame
                catch {raise $composite::($this,-path) $widget::($this,path)}
                unset (motion)
            } else {                                                                                ;# no moving or resizing occured
                toggleVisibility $this
            }
            destroyOutline $this
            unset (xLast) (yLast) (hidden)
        }

        proc resize {this width height} {
            # handle size should not be less than border width because of corners
            set size [maximum $composite::($this,-handlesize) $composite::($this,-borderwidth)]
            # recalculate handles limits
            # mid handle size is 1/3 of side but mid handles disappear when frame gets too small so that it stays movable

            set halfHeight [expr {($height/2)}]
            set ($this,topHandleBottom) [minimum $size $halfHeight]            ;# top corner handle bottom cannot exceed half height
            set ($this,bottomHandleTop) [expr {$height-[set ($this,topHandleBottom)]}]
            # mid handle top cannot be to close to top corner handle bottom
            set ($this,midHandleTop) [maximum [expr {$height/3}] [expr {[set ($this,topHandleBottom)]+$size}]]
            # mid handle bottom limit cannot be greater than bottom corner handle top
            set ($this,midHandleBottom) [minimum [expr {(2*$height)/3}] [expr {[set ($this,bottomHandleTop)]-$size}]]
            # note: mid handle top can be greater than mid handle bottom when handle disappears

            set halfWidth [expr {($width/2)}]
            set ($this,leftHandleRight) [minimum $size $halfWidth]              ;# left corner handle right cannot exceed half width
            set ($this,rightHandleLeft) [expr {$width-[set ($this,leftHandleRight)]}]
            # mid handle left cannot be less than left corner handle right
            set ($this,midHandleLeft) [maximum [expr {$width/3}] [expr {[set ($this,leftHandleRight)]+$size}]]
            # mid handle right limit cannot be greater than right corner handle left
            set ($this,midHandleRight) [minimum [expr {(2*$width)/3}] [expr {[set ($this,rightHandleLeft)]-$size}]]
            # note: mid handle left can be greater than mid handle right when handle disappears
        }

        proc setCursor {this x y} {
            if {[info exists (motion)]} {
                return    ;# make sure not to change cursor while moving outline (may happen when pointer passes over manager frame)
            }
            set border $composite::($this,-borderwidth)
            set path $widget::($this,path)
            set cursor fleur                                                                    ;# use moving cursor outside borders
            set direction {}
            if {$x<$border} {
                set side left
                set direction w
            } elseif {$x>=([winfo width $path]-$border)} {
                set side right
                set direction e
            }
            if {[info exists side]} {                                                                        ;# in a vertical border
                if {$y<[set ($this,topHandleBottom)]} {
                    set cursor top_${side}_corner
                    append direction n
                } elseif {$y>[set ($this,bottomHandleTop)]} {
                    set cursor bottom_${side}_corner
                    append direction s
                } elseif {($y>[set ($this,midHandleTop)])&&($y<[set ($this,midHandleBottom)])} {
                    set cursor ${side}_side
                } else {
                    set cursor fleur
                    set direction {}
                }
            } else {
                if {$y<$border} {
                    set side top
                    set direction n
                } elseif {$y>=([winfo height $path]-$border)} {
                    set side bottom
                    set direction s
                }
                if {[info exists side]} {                                                                 ;# in an horizontal border
                    if {$x<[set ($this,leftHandleRight)]} {
                        set cursor ${side}_left_corner
                        append direction w
                    } elseif {$x>[set ($this,rightHandleLeft)]} {
                        set cursor ${side}_right_corner
                        append direction e
                    } elseif {($x>[set ($this,midHandleLeft)])&&($x<[set ($this,midHandleRight)])} {
                        set cursor ${side}_side
                    } else {
                        set cursor fleur
                        set direction {}
                    }
                }
            }
            if {[string compare $cursor [$widget::($this,path) cget -cursor]]!=0} {                ;# update cursor only when needed
                $widget::($this,path) configure -cursor $cursor
                update idletasks                                                ;# make cursor immediately visible for user feedback
            }
            set ($this,direction) $direction
        }

        proc updateOutline {this x y} {                                                 ;# coordinates are relative to manager frame
            lifoLabel::pop $::messenger                                                       ;# remove previous coordinates or size

            if {[set (hidden)]} {                                                              ;# make sure outline is fully visible
                positionOutlineInStackingOrder $this raise
            }
            set canvas [set ($this,canvas)]
            set coordinates [$canvas coords [set ($this,item)]]
            # make sure that pointer stays within canvas boundaries
            set xFrame [lindex $coordinates 0]
            set yFrame [lindex $coordinates 1]
            if {($xFrame+$x)<0} {
                set x [expr {-$xFrame}]                                             ;# use expr to properly handle consecutive signs
            }
            if {($yFrame+$y)<0} {
                set y [expr {-$yFrame}]                                             ;# use expr to properly handle consecutive signs
            }
            set width [winfo width $canvas]
            if {($xFrame+$x)>=$width} {
                set x [expr {$width-$xFrame-1}]
            }
            set height [winfo height $canvas]
            if {($yFrame+$y)>=$height} {
                set y [expr {$height-$yFrame-1}]
            }

            if {[string length [set ($this,direction)]]==0} {                                                ;# moving, not resizing
                $canvas move outline [expr {$x-[set (xLast)]}] [expr {$y-[set (yLast)]}]
                lifoLabel::push $::messenger [$canvas coords outline]                     ;# display new coordinates in message area
                set canvasWindowManager::handles::(xLast) $x                         ;### Tcl BUG: should be set ([xy]Last) $... ###
                set canvasWindowManager::handles::(yLast) $y
                return
            }

            set width [winfo width $widget::($this,path)]
            set height [winfo height $widget::($this,path)]

            switch [set ($this,direction)] {                                                                             ;# resizing
                nw - wn {
                    displayOutline $this [expr {$xFrame+$x}] [expr {$yFrame+$y}] [expr {$width-$x}] [expr {$height-$y}]
                }
                n {
                    displayOutline $this $xFrame [expr {$yFrame+$y}] $width [expr {$height-$y}]
                }
                ne - en {
                    displayOutline $this $xFrame [expr {$yFrame+$y}] $x [expr {$height-$y}]
                }
                e {
                    displayOutline $this $xFrame $yFrame $x $height
                }
                se - es {
                    displayOutline $this $xFrame $yFrame $x $y
                }
                s {
                    displayOutline $this $xFrame $yFrame $width $y
                }
                sw - ws {
                    displayOutline $this [expr {$xFrame+$x}] $yFrame [expr {$width-$x}] $y
                }
                w {
                    displayOutline $this [expr {$xFrame+$x}] $yFrame [expr {$width-$x}] $height
                }
            }
        }

        proc createOutline {this} {
            # create outline borders (a single frame with no background cannot be used for it hides underlying windows)
            set canvas [set ($this,canvas)]
            foreach side {top bottom left right} {
                set frame $widget::([new frame $canvas -background black],path)                         ;# make sure frame is unique
                # items are static because there can be only 1 outline at a time
                set ($side,item) [$canvas create window 0 0 -window $frame -width 0 -height 0 -anchor nw -tags outline]
            }
            positionOutlineInStackingOrder $this lower                                ;# hide outline for now and make it fit widget
            eval displayOutline $this [$canvas coords [set ($this,item)]]\
                [winfo width $widget::($this,path)] [winfo height $widget::($this,path)]
        }

        proc positionOutlineInStackingOrder {this order} {                                    ;# order must be either raise or lower
            set canvas [set ($this,canvas)]
            foreach side {top bottom left right} {
                $order [$canvas itemcget [set ($side,item)] -window]
            }
            set (hidden) [string compare $order raise]
        }

        proc displayOutline {this x y width height} {                                          ;# coordinates are relative to canvas
            lifoLabel::push $::messenger "$width x $height"                                      ;# display new size in message area
            set minimum [expr {(2*$composite::($this,-borderwidth))+1}]                ;# make sure managed widget is always visible
            set width [maximum $minimum $width]
            set height [maximum $minimum $height]
            set canvas [set ($this,canvas)]
            $canvas coords [set (top,item)] $x $y
            $canvas coords [set (bottom,item)] $x [expr {$y+$height-1}]
            $canvas coords [set (left,item)] $x $y
            $canvas coords [set (right,item)] [expr {$x+$width-1}] $y
            $canvas itemconfigure [set (top,item)] -width $width
            $canvas itemconfigure [set (bottom,item)] -width $width
            $canvas itemconfigure [set (left,item)] -height $height
            $canvas itemconfigure [set (right,item)] -height $height
        }

        proc destroyOutline {this} {
            set canvas [set ($this,canvas)]
            foreach side {top bottom left right} {
                destroy [$canvas itemcget [set ($side,item)] -window]                                          ;# destroy side frame
                unset ($side,item)
            }
            $canvas delete outline                                                                              ;# delete side items
        }

        proc updateGeometry {this} {                ;# update managed widget position and size according to outline current geometry
            set canvas [set ($this,canvas)]
            eval $canvas coords [set ($this,item)] [$canvas coords outline]
            $canvas itemconfigure [set ($this,item)] -width [$canvas itemcget [set (top,item)] -width]\
                -height [$canvas itemcget [set (left,item)] -height]
        }

        proc getGeometry {this} {                                                         ;# return x, y, width and height as a list
            set canvas [set ($this,canvas)]
            return [concat\
                [[set ($this,canvas)] coords [set ($this,item)]]\
                [winfo width $widget::($this,path)] [winfo height $widget::($this,path)]\
            ]
        }

    }

}