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)]\
]
}
}
}
|