1# CanvasDraw.tcl --- 2# 3# This file is part of The Coccinella application. It implements the 4# drawings commands associated with the tools. 5# 6# Copyright (c) 2000-2006 Mats Bengtsson 7# 8# This program is free software: you can redistribute it and/or modify 9# it under the terms of the GNU General Public License as published by 10# the Free Software Foundation, either version 3 of the License, or 11# (at your option) any later version. 12# 13# This program is distributed in the hope that it will be useful, 14# but WITHOUT ANY WARRANTY; without even the implied warranty of 15# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16# GNU General Public License for more details. 17# 18# You should have received a copy of the GNU General Public License 19# along with this program. If not, see <http://www.gnu.org/licenses/>. 20# 21# $Id: CanvasDraw.tcl,v 1.24 2008-03-25 08:52:31 matben Exp $ 22 23#-- TAGS ----------------------------------------------------------------------- 24# 25# All items are associated with tags. Each item must have a global unique 26# identifier, utag, so that the item can be identified on the net. 27# The standard items which are drawn and imported images, have two additional 28# tags: 29# std verbatim; this is used for all items made by the standard 30# tools 31# $type line, oval, rectangle, arc, image, polygon corresponding 32# to the items 'type' 33# 34# Other tags: 35# locked used for locked items 36# 37# Temporary tags: 38# _move temporary tag for moving items 39# _ghostrect 40# _selectedwindow 41# _polylines 42 43package provide CanvasDraw 1.0 44 45namespace eval ::CanvasDraw:: {} 46 47#--- The 'move' tool procedures ------------------------------------------------ 48 49# CanvasDraw::InitMoveSelected, DragMoveSelected, FinalMoveSelected -- 50# 51# Moves all selected items. 52 53proc ::CanvasDraw::InitMoveSelected {wcan x y} { 54 variable moveArr 55 56 set selected [$wcan find withtag selected&&!locked] 57 if {[llength $selected] == 0} { 58 return 59 } 60 if {[HitMovableTBBox $wcan $x $y]} { 61 return 62 } 63 $wcan dtag _move 64 $wcan addtag _move withtag selected&&!locked 65 set moveArr(x) $x 66 set moveArr(y) $y 67 set moveArr(x0) $x 68 set moveArr(y0) $y 69 set moveArr(bindType) selected 70 set moveArr(type) selected 71 set moveArr(selected) $selected 72 foreach id $selected { 73 set moveArr(coords0,$id) [$wcan coords $id] 74 } 75} 76 77proc ::CanvasDraw::DragMoveSelected {wcan x y {modifier {}}} { 78 variable moveArr 79 80 set selected [$wcan find withtag _move] 81 if {[llength $selected] == 0} { 82 return 83 } 84 if {![string equal $moveArr(bindType) "selected"]} { 85 return 86 } 87 88 # @@@ These to interfere for 45degree constraints. 89 lassign [ToScroll $wcan _move $moveArr(x) $moveArr(y) $x $y] x y 90 if {[string equal $modifier "shift"]} { 91 lassign [GetConstrainedXY $x $y] x y 92 } 93 set dx [expr {$x - $moveArr(x)}] 94 set dy [expr {$y - $moveArr(y)}] 95 $wcan move _move $dx $dy 96 $wcan move tbbox&&!locked $dx $dy 97 set moveArr(x) $x 98 set moveArr(y) $y 99} 100 101proc ::CanvasDraw::FinalMoveSelected {wcan x y} { 102 variable moveArr 103 104 # Protect this from beeing trigged when moving individual points. 105 set selected [$wcan find withtag _move] 106 if {$selected == {}} { 107 return 108 } 109 if {![info exists moveArr]} { 110 return 111 } 112 if {![string equal $moveArr(bindType) "selected"]} { 113 return 114 } 115 116 # Have moved a bunch of ordinary items. 117 # Need to get the actual, constrained, coordinates and not the mouses. 118 set x $moveArr(x) 119 set y $moveArr(y) 120 set dx [expr {$x - $moveArr(x0)}] 121 set dy [expr {$y - $moveArr(y0)}] 122 set mdx [expr {-1*$dx}] 123 set mdy [expr {-1*$dy}] 124 set cmdList {} 125 set cmdUndoList {} 126 127 foreach id $selected { 128 set utag [::CanvasUtils::GetUtag $wcan $id] 129 130 # Let images use coords instead since more robust if transported. 131 switch -- [$wcan type $id] { 132 image { 133 134 # Find new coords. 135 lassign $moveArr(coords0,$id) x0 y0 136 set x [expr {$x0 + $dx}] 137 set y [expr {$y0 + $dy}] 138 lappend cmdList [list coords $utag $x $y] 139 lappend cmdUndoList \ 140 [concat coords $utag $moveArr(coords0,$id)] 141 } 142 default { 143 lappend cmdList [list move $utag $dx $dy] 144 lappend cmdUndoList [list move $utag $mdx $mdy] 145 } 146 } 147 } 148 set w [winfo toplevel $wcan] 149 set redo [list ::CanvasUtils::CommandList $w $cmdList] 150 set undo [list ::CanvasUtils::CommandList $w $cmdUndoList] 151 eval $redo remote 152 undo::add [::WB::GetUndoToken $wcan] $undo $redo 153 154 ::CanvasFile::SetUnsaved $wcan 155 156 $wcan dtag _move 157 unset -nocomplain moveArr 158} 159 160# CanvasDraw::InitMoveCurrent, DragMoveCurrent, FinalMoveCurrent -- 161# 162# Moves 'current' item. 163 164proc ::CanvasDraw::InitMoveCurrent {wcan x y} { 165 variable moveArr 166 167 set selected [$wcan find withtag selected&&!locked] 168 if {[llength $selected] > 0} { 169 return 170 } 171 set id [$wcan find withtag current] 172 set moveArr(x) $x 173 set moveArr(y) $y 174 set moveArr(x0) $x 175 set moveArr(y0) $y 176 set moveArr(id) $id 177 set moveArr(coords0,$id) [$wcan coords $id] 178 set moveArr(bindType) std 179 set moveArr(type) [$wcan type $id] 180} 181 182proc ::CanvasDraw::DragMoveCurrent {wcan x y {modifier {}}} { 183 variable moveArr 184 185 set selected [$wcan find withtag selected&&!locked] 186 if {[llength $selected] > 0} { 187 return 188 } 189 lassign [ToScroll $wcan $moveArr(id) $moveArr(x) $moveArr(y) $x $y] x y 190 if {[string equal $modifier "shift"]} { 191 lassign [GetConstrainedXY $x $y] x y 192 } 193 set dx [expr {$x - $moveArr(x)}] 194 set dy [expr {$y - $moveArr(y)}] 195 $wcan move $moveArr(id) $dx $dy 196 set moveArr(x) $x 197 set moveArr(y) $y 198} 199 200proc ::CanvasDraw::FinalMoveCurrent {wcan x y} { 201 variable moveArr 202 203 set selected [$wcan find withtag selected&&!locked] 204 if {$selected != {}} { 205 return 206 } 207 if {![info exists moveArr]} { 208 return 209 } 210 211 # Need to get the actual, constrained, coordinates and not the mouses. 212 set x $moveArr(x) 213 set y $moveArr(y) 214 set dx [expr {$x - $moveArr(x0)}] 215 set dy [expr {$y - $moveArr(y0)}] 216 set mdx [expr {-1*$dx}] 217 set mdy [expr {-1*$dy}] 218 set cmdList {} 219 set cmdUndoList {} 220 221 set id $moveArr(id) 222 set utag [::CanvasUtils::GetUtag $wcan $id] 223 224 # Let images use coords instead since more robust if transported. 225 switch -- [$wcan type $id] { 226 image { 227 228 # Find new coords. 229 lassign $moveArr(coords0,$id) x0 y0 230 set x [expr {$x0 + $dx}] 231 set y [expr {$y0 + $dy}] 232 lappend cmdList [list coords $utag $x $y] 233 lappend cmdUndoList \ 234 [concat coords $utag $moveArr(coords0,$id)] 235 } 236 default { 237 lappend cmdList [list move $utag $dx $dy] 238 lappend cmdUndoList [list move $utag $mdx $mdy] 239 } 240 } 241 set w [winfo toplevel $wcan] 242 set redo [list ::CanvasUtils::CommandList $w $cmdList] 243 set undo [list ::CanvasUtils::CommandList $w $cmdUndoList] 244 eval $redo remote 245 undo::add [::WB::GetUndoToken $wcan] $undo $redo 246 247 ::CanvasFile::SetUnsaved $wcan 248 249 unset -nocomplain moveArr 250} 251 252# CanvasDraw::InitMoveRectPoint, DragMoveRectPoint, FinalMoveRectPoint -- 253# 254# For rectangle and oval corner points. 255 256proc ::CanvasDraw::InitMoveRectPoint {wcan x y} { 257 variable moveArr 258 259 if {![HitTBBox $wcan $x $y]} { 260 return 261 } 262 263 # Moving a marker of a selected item, highlight marker. 264 # 'current' must be a marker with tag 'tbbox'. 265 set id [$wcan find withtag current] 266 $wcan addtag hitBbox withtag $id 267 268 # Find associated id for the actual item. Saved in the tags of the marker. 269 if {![regexp {id:([0-9]+)} [$wcan gettags $id] match itemid]} { 270 return 271 } 272 DrawHighlightBox $wcan $itemid $id 273 set itemcoords [$wcan coords $itemid] 274 set utag [::CanvasUtils::GetUtag $wcan $itemid] 275 276 # Get the index of the coordinates that was 'hit'. Then update only 277 # this coordinate when moving. 278 # For rectangle and oval items a list with all four coordinates is used, 279 # but only the hit corner and the diagonally opposite one are kept. 280 281 # Need to reconstruct all four coordinates as: 0---1 282 # | | 283 # 2---3 284 set longcoo [concat \ 285 [lindex $itemcoords 0] [lindex $itemcoords 1] \ 286 [lindex $itemcoords 2] [lindex $itemcoords 1] \ 287 [lindex $itemcoords 0] [lindex $itemcoords 3] \ 288 [lindex $itemcoords 2] [lindex $itemcoords 3]] 289 290 set ind [FindClosestCoordsIndex $x $y $longcoo] 291 set ptind [expr {$ind/2}] 292 293 # Keep only hit corner and the diagonally opposite one. 294 set coords [list [lindex $longcoo $ind] \ 295 [lindex $longcoo [expr {$ind + 1}]]] 296 297 switch -- $ptind { 298 0 { 299 set coo [lappend coords [lindex $longcoo 6] [lindex $longcoo 7]] 300 } 301 1 { 302 set coo [lappend coords [lindex $longcoo 4] [lindex $longcoo 5]] 303 } 304 2 { 305 set coo [lappend coords [lindex $longcoo 2] [lindex $longcoo 3]] 306 } 307 3 { 308 set coo [lappend coords [lindex $longcoo 0] [lindex $longcoo 1]] 309 } 310 } 311 312 set moveArr(x) $x 313 set moveArr(y) $y 314 set moveArr(x0) $x 315 set moveArr(y0) $y 316 set moveArr(id) $id 317 set moveArr(itemid) $itemid 318 set moveArr(utag) $utag 319 set moveArr(coords0) [$wcan coords $id] 320 set moveArr(itemcoords0) $coo 321 set moveArr(undocmd) [concat coords $utag $itemcoords] 322 set moveArr(bindType) tbbox:rect 323 set moveArr(type) [$wcan type $itemid] 324} 325 326proc ::CanvasDraw::DragMoveRectPoint {wcan x y {modifier {}}} { 327 variable moveArr 328 329 if {![info exists moveArr]} { 330 return 331 } 332 if {![string equal $moveArr(bindType) "tbbox:rect"]} { 333 return 334 } 335 lassign [ToScroll $wcan $moveArr(itemid) $moveArr(x) $moveArr(y) $x $y] x y 336 if {[string equal $modifier "shift"]} { 337 lassign [GetConstrainedXY $x $y] x y 338 } 339 set dx [expr {$x - $moveArr(x)}] 340 set dy [expr {$y - $moveArr(y)}] 341 set newcoo [lreplace $moveArr(itemcoords0) 0 1 $x $y] 342 eval $wcan coords $moveArr(itemid) $newcoo 343 $wcan move hitBbox $dx $dy 344 $wcan move lightBbox $dx $dy 345 set moveArr(x) $x 346 set moveArr(y) $y 347} 348 349proc ::CanvasDraw::FinalMoveRectPoint {wcan x y} { 350 variable moveArr 351 352 if {![info exists moveArr]} { 353 return 354 } 355 if {![string equal $moveArr(bindType) "tbbox:rect"]} { 356 return 357 } 358 $wcan delete lightBbox 359 $wcan dtag all hitBbox 360 361 # Move all markers along. 362 $wcan delete id$moveArr(itemid) 363 MarkBbox $wcan 0 $moveArr(itemid) 364 365 set itemid $moveArr(itemid) 366 set utag $moveArr(utag) 367 set utag [::CanvasUtils::GetUtag $wcan $itemid] 368 set cmd [concat coords $utag [$wcan coords $itemid]] 369 370 set w [winfo toplevel $wcan] 371 set redo [list ::CanvasUtils::Command $w $cmd] 372 set undo [list ::CanvasUtils::Command $w $moveArr(undocmd)] 373 eval $redo remote 374 undo::add [::WB::GetUndoToken $wcan] $undo $redo 375 376 ::CanvasFile::SetUnsaved $wcan 377 378 unset -nocomplain moveArr 379} 380 381# CanvasDraw::InitMoveArcPoint, DragMoveArcPoint, FinalMoveArcPoint -- 382# 383# @@@ Pretty buggy! 384 385proc ::CanvasDraw::InitMoveArcPoint {wcan x y} { 386 global kGrad2Rad 387 variable moveArr 388 389 if {![HitTBBox $wcan $x $y]} { 390 return 391 } 392 393 # Moving a marker of a selected item, highlight marker. 394 # 'current' must be a marker with tag 'tbbox'. 395 set id [$wcan find withtag current] 396 $wcan addtag hitBbox withtag $id 397 398 set moveArr(x) $x 399 set moveArr(y) $y 400 set moveArr(x0) $x 401 set moveArr(y0) $y 402 set moveArr(bindType) tbbox:arc 403 set moveArr(type) arc 404 405 # Find associated id for the actual item. Saved in the tags of the marker. 406 if {![regexp {id:([0-9]+)} [$wcan gettags $id] match itemid]} { 407 return 408 } 409 DrawHighlightBox $wcan $itemid $id 410 set itemcoords [$wcan coords $itemid] 411 set utag [::CanvasUtils::GetUtag $wcan $itemid] 412 413 set moveArr(itemid) $itemid 414 set moveArr(coords) $itemcoords 415 set moveArr(utag) $utag 416 417 # Some geometry. We have got the coordinates defining the box. 418 # Find out if we clicked the 'start' or 'extent' "point". 419 # Tricky part: be sure that the branch cut is at +-180 degrees! 420 # 'itemcget' gives angles 0-360, while atan2 gives -180-180. 421 set moveArr(arcX) $x 422 set moveArr(arcY) $y 423 foreach {x1 y1 x2 y2} $itemcoords break 424 set r [expr {abs(($x1 - $x2)/2.0)}] 425 set cx [expr {($x1 + $x2)/2.0}] 426 set cy [expr {($y1 + $y2)/2.0}] 427 set moveArr(arcCX) $cx 428 set moveArr(arcCY) $cy 429 set startAng [$wcan itemcget $itemid -start] 430 431 # Put branch cut at +-180! 432 if {$startAng > 180} { 433 set startAng [expr {$startAng - 360}] 434 } 435 set extentAng [$wcan itemcget $itemid -extent] 436 set xstart [expr {$cx + $r * cos($kGrad2Rad * $startAng)}] 437 set ystart [expr {$cy - $r * sin($kGrad2Rad * $startAng)}] 438 set xfin [expr {$cx + $r * cos($kGrad2Rad * ($startAng + $extentAng))}] 439 set yfin [expr {$cy - $r * sin($kGrad2Rad * ($startAng + $extentAng))}] 440 set dstart [expr {hypot($xstart - $x,$ystart - $y)}] 441 set dfin [expr {hypot($xfin - $x,$yfin - $y)}] 442 set moveArr(arcStart) $startAng 443 set moveArr(arcExtent) $extentAng 444 set moveArr(arcFin) [expr {$startAng + $extentAng}] 445 if {$dstart < $dfin} { 446 set moveArr(arcHit) "start" 447 } else { 448 set moveArr(arcHit) "extent" 449 } 450 set moveArr(undocmd) [concat itemconfigure $utag \ 451 -start $startAng -extent $extentAng] 452} 453 454proc ::CanvasDraw::DragMoveArcPoint {wcan x y {modifier {}}} { 455 global kGrad2Rad kRad2Grad 456 variable moveArr 457 458 lassign [ToScroll $wcan $moveArr(itemid) $moveArr(x) $moveArr(y) $x $y] x y 459 if {[string equal $modifier "shift"]} { 460 lassign [GetConstrainedXY $x $y] x y 461 } 462 set dx [expr {$x - $moveArr(x)}] 463 set dy [expr {$y - $moveArr(y)}] 464 set moveArr(x) $x 465 set moveArr(y) $y 466 467 # Some geometry. We have got the coordinates defining the box. 468 set coords $moveArr(coords) 469 set itemid $moveArr(itemid) 470 471 lassign $coords x1 y1 x2 y2 472 set r [expr {abs(($x1 - $x2)/2.0)}] 473 set cx [expr {($x1 + $x2)/2.0}] 474 set cy [expr {($y1 + $y2)/2.0}] 475 set startAng [$wcan itemcget $itemid -start] 476 set extentAng [$wcan itemcget $itemid -extent] 477 set xstart [expr {$cx + $r * cos($kGrad2Rad * $startAng)}] 478 set ystart [expr {$cy - $r * sin($kGrad2Rad * $startAng)}] 479 set xfin [expr {$cx + $r * cos($kGrad2Rad * ($startAng + $extentAng))}] 480 set yfin [expr {$cy - $r * sin($kGrad2Rad * ($startAng + $extentAng))}] 481 set newAng [expr {$kRad2Grad * atan2($cy - $y,-($cx - $x))}] 482 483 # Dragging the 'extent' point or the 'start' point? 484 if {[string equal $moveArr(arcHit) "extent"]} { 485 set extentAng [expr {$newAng - $moveArr(arcStart)}] 486 487 # Same trick as when drawing it; take care of the branch cut. 488 if {$moveArr(arcExtent) - $extentAng > 180} { 489 set extentAng [expr {$extentAng + 360}] 490 } elseif {$moveArr(arcExtent) - $extentAng < -180} { 491 set extentAng [expr {$extentAng - 360}] 492 } 493 set moveArr(arcExtent) $extentAng 494 495 # Update angle. 496 $wcan itemconfigure $itemid -extent $extentAng 497 498 # Move highlight box. 499 $wcan move hitBbox [expr {$xfin - $moveArr(arcX)}] \ 500 [expr {$yfin - $moveArr(arcY)}] 501 $wcan move lightBbox [expr {$xfin - $moveArr(arcX)}] \ 502 [expr {$yfin - $moveArr(arcY)}] 503 set moveArr(arcX) $xfin 504 set moveArr(arcY) $yfin 505 506 } elseif {[string equal $moveArr(arcHit) "start"]} { 507 508 # Need to update start angle as well as extent angle. 509 set newExtentAng [expr {$moveArr(arcFin) - $newAng}] 510 # Same trick as when drawing it; take care of the branch cut. 511 if {$moveArr(arcExtent) - $newExtentAng > 180} { 512 set newExtentAng [expr {$newExtentAng + 360}] 513 } elseif {$moveArr(arcExtent) - $newExtentAng < -180} { 514 set newExtentAng [expr {$newExtentAng - 360}] 515 } 516 set moveArr(arcExtent) $newExtentAng 517 set moveArr(arcStart) $newAng 518 $wcan itemconfigure $itemid -start $newAng 519 $wcan itemconfigure $itemid -extent $newExtentAng 520 521 # Move highlight box. 522 $wcan move hitBbox [expr {$xstart - $moveArr(arcX)}] \ 523 [expr {$ystart - $moveArr(arcY)}] 524 $wcan move lightBbox [expr {$xstart - $moveArr(arcX)}] \ 525 [expr {$ystart - $moveArr(arcY)}] 526 set moveArr(arcX) $xstart 527 set moveArr(arcY) $ystart 528 } 529} 530 531proc ::CanvasDraw::FinalMoveArcPoint {wcan x y} { 532 variable moveArr 533 534 if {![info exists moveArr]} { 535 return 536 } 537 set id $moveArr(itemid) 538 set w [winfo toplevel $wcan] 539 540 $wcan delete lightBbox 541 $wcan dtag all hitBbox 542 543 # The arc item: update both angles. 544 set utag $moveArr(utag) 545 set cmd [concat itemconfigure $utag -start $moveArr(arcStart) \ 546 -extent $moveArr(arcExtent)] 547 set redo [list ::CanvasUtils::Command $w $cmd] 548 set undo [list ::CanvasUtils::Command $w $moveArr(undocmd)] 549 550 eval $redo remote 551 undo::add [::WB::GetUndoToken $wcan] $undo $redo 552 553 ::CanvasFile::SetUnsaved $wcan 554 555 unset -nocomplain moveArr 556} 557 558# CanvasDraw::InitMovePolyLinePoint, DragMovePolyLinePoint, 559# FinalMovePolyLinePoint -- 560# 561# For moving polygon and line item points. 562 563proc ::CanvasDraw::InitMovePolyLinePoint {wcan x y} { 564 variable moveArr 565 566 if {![HitTBBox $wcan $x $y]} { 567 return 568 } 569 570 # Moving a marker of a selected item, highlight marker. 571 # 'current' must be a marker with tag 'tbbox'. 572 set id [$wcan find withtag current] 573 $wcan addtag hitBbox withtag $id 574 575 set moveArr(x) $x 576 set moveArr(y) $y 577 set moveArr(x0) $x 578 set moveArr(y0) $y 579 580 # Find associated id for the actual item. Saved in the tags of the marker. 581 if {![regexp {id:([0-9]+)} [$wcan gettags $id] match itemid]} { 582 return 583 } 584 DrawHighlightBox $wcan $itemid $id 585 set itemcoords [$wcan coords $itemid] 586 set ind [FindClosestCoordsIndex $x $y $itemcoords] 587 588 set moveArr(itemid) $itemid 589 set moveArr(coords) $itemcoords 590 set moveArr(hitInd) $ind 591 set moveArr(type) [$wcan type $itemid] 592 set moveArr(bindType) tbbox:polyline 593} 594 595proc ::CanvasDraw::DragMovePolyLinePoint {wcan x y {modifier {}}} { 596 variable moveArr 597 598 lassign [ToScroll $wcan $moveArr(itemid) $moveArr(x) $moveArr(y) $x $y] x y 599 if {[string equal $modifier "shift"]} { 600 lassign [GetConstrainedXY $x $y] x y 601 } 602 set dx [expr {$x - $moveArr(x)}] 603 set dy [expr {$y - $moveArr(y)}] 604 set moveArr(x) $x 605 set moveArr(y) $y 606 607 set coords $moveArr(coords) 608 set itemid $moveArr(itemid) 609 610 set ind $moveArr(hitInd) 611 set newcoo [lreplace $coords $ind [expr {$ind + 1}] $x $y] 612 eval $wcan coords $itemid $newcoo 613 $wcan move hitBbox $dx $dy 614 $wcan move lightBbox $dx $dy 615} 616 617proc ::CanvasDraw::FinalMovePolyLinePoint {wcan x y} { 618 variable moveArr 619 620 if {![info exists moveArr]} { 621 return 622 } 623 set itemid $moveArr(itemid) 624 set coords $moveArr(coords) 625 set utag [::CanvasUtils::GetUtag $wcan $itemid] 626 set w [winfo toplevel $wcan] 627 set itemcoo [$wcan coords $itemid] 628 629 $wcan delete lightBbox 630 $wcan dtag all hitBbox 631 632 # If endpoints overlap in line item, make closed polygon. 633 # Find out if closed polygon or open line item. If closed, remove duplicate. 634 635 set len [expr {hypot( \ 636 [lindex $itemcoo end-1] - [lindex $itemcoo 0], \ 637 [lindex $itemcoo end] - [lindex $itemcoo 1] )}] 638 if {[string equal $moveArr(type) "line"] && ($len < 8)} { 639 640 # Make the line segments to a closed polygon. 641 # Get all actual options. 642 set lineopts [::CanvasUtils::GetItemOpts $wcan $itemid] 643 set polycoo [lreplace $itemcoo end-1 end] 644 set cmd1 [list delete $utag] 645 eval $wcan $cmd1 646 647 # Make the closed polygon. Get rid of non-applicable options. 648 set opcmd $lineopts 649 array set opcmdArr $opcmd 650 foreach op {arrow arrowshape capstyle joinstyle tags} { 651 unset -nocomplain opcmdArr(-$op) 652 } 653 set opcmdArr(-outline) black 654 655 # Replace -fill with -outline. 656 set ind [lsearch -exact $lineopts -fill] 657 if {$ind >= 0} { 658 set opcmdArr(-outline) [lindex $lineopts [expr {$ind+1}]] 659 } 660 set utag [::CanvasUtils::NewUtag] 661 set opcmdArr(-fill) {} 662 set opcmdArr(-tags) [list polygon std $utag] 663 set cmd2 [concat create polygon $polycoo [array get opcmdArr]] 664 set polyid [eval $wcan $cmd2] 665 set ucmd1 [list delete $utag] 666 set ucmd2 [concat create line $coords $lineopts] 667 set undo [list ::CanvasUtils::CommandList $w [list $ucmd1 $ucmd2]] 668 set redo [list ::CanvasUtils::CommandList $w [list $cmd1 $cmd2]] 669 670 # Move all markers along. 671 $wcan delete id:$itemid 672 MarkBbox $wcan 0 $polyid 673 } else { 674 set undocmd [concat coords $utag $coords] 675 set cmd [concat coords $utag [$wcan coords $itemid]] 676 set undo [list ::CanvasUtils::Command $w $undocmd] 677 set redo [list ::CanvasUtils::Command $w $cmd] 678 } 679 680 eval $redo remote 681 undo::add [::WB::GetUndoToken $wcan] $undo $redo 682 683 ::CanvasFile::SetUnsaved $wcan 684 685 unset -nocomplain moveArr 686} 687 688# CanvasDraw::InitMoveFrame, DoMoveFrame FinMoveFrame -- 689# 690# Generic and general move functions for framed (window) items. 691 692proc ::CanvasDraw::InitMoveFrame {wcan wframe x y} { 693 global kGrad2Rad 694 variable xDragFrame 695 696 # If frame then make ghost rectangle. 697 # Movies (and windows) do not obey the usual stacking order! 698 set utag [::CanvasUtils::GetUtagFromWindow $wframe] 699 if {$utag eq ""} { 700 return 701 } 702 703 # Fix x and y. 704 set x [$wcan canvasx [expr {[winfo x $wframe] + $x}]] 705 set y [$wcan canvasx [expr {[winfo y $wframe] + $y}]] 706 707 Debug 2 "InitMoveFrame:: wcan=$wcan, wframe=$wframe x=$x, y=$y" 708 709 set xDragFrame(what) "frame" 710 set xDragFrame(baseX) $x 711 set xDragFrame(baseY) $y 712 set xDragFrame(anchorX) $x 713 set xDragFrame(anchorY) $y 714 715 # In some cases we need the anchor point to be an exact item 716 # specific coordinate. 717 718 set xDragFrame(type) [$wcan type current] 719 set xDragFrame(undocmd) [concat coords $utag [$wcan coords $utag]] 720 $wcan addtag _moveframe withtag $utag 721 lassign [$wcan bbox $utag] x1 y1 x2 y2 722 incr x1 -1 723 incr y1 -1 724 incr x2 +1 725 incr y2 +1 726 $wcan create rectangle $x1 $y1 $x2 $y2 -outline gray50 -width 3 \ 727 -stipple gray50 -tags _ghostrect 728 set xDragFrame(doMove) 1 729} 730 731# CanvasDraw::DoMoveFrame -- 732# 733# Moves a ghost rectangle of a framed window. 734 735proc ::CanvasDraw::DoMoveFrame {wcan wframe x y} { 736 variable xDragFrame 737 738 if {![info exists xDragFrame]} { 739 return 740 } 741 742 # Fix x and y. 743 set x [$wcan canvasx [expr {[winfo x $wframe] + $x}]] 744 set y [$wcan canvasx [expr {[winfo y $wframe] + $y}]] 745 lassign [ToScroll $wcan _moveframe $xDragFrame(baseX) $xDragFrame(baseY) $x $y] x y 746 747 # Moving a frame window item (_ghostrect). 748 $wcan move _ghostrect \ 749 [expr {$x - $xDragFrame(baseX)}] [expr {$y - $xDragFrame(baseY)}] 750 751 set xDragFrame(baseX) $x 752 set xDragFrame(baseY) $y 753} 754 755proc ::CanvasDraw::FinMoveFrame {wcan wframe x y} { 756 variable xDragFrame 757 758 Debug 2 "FinMoveFrame info exists xDragFrame=[info exists xDragFrame]" 759 760 if {![info exists xDragFrame]} { 761 return 762 } 763 764 # Need to get the actual, constrained, coordinates and not the mouses. 765 set x $xDragFrame(baseX) 766 set y $xDragFrame(baseY) 767 set id [$wcan find withtag _moveframe] 768 set utag [::CanvasUtils::GetUtag $wcan $id] 769 770 Debug 2 "\t id=$id, utag=$utag, x=$x, y=$y" 771 772 if {$utag eq ""} { 773 return 774 } 775 $wcan move _moveframe [expr {$x - $xDragFrame(anchorX)}] \ 776 [expr {$y - $xDragFrame(anchorY)}] 777 $wcan dtag _moveframe _moveframe 778 set cmd [concat coords $utag [$wcan coords $utag]] 779 780 # Delete the ghost rect or highlighted marker if any. Remove temporary tags. 781 $wcan delete _ghostrect 782 783 # Do send to all connected. 784 set w [winfo toplevel $wcan] 785 set redo [list ::CanvasUtils::Command $w $cmd] 786 if {[info exists xDragFrame(undocmd)]} { 787 set undo [list ::CanvasUtils::Command $w $xDragFrame(undocmd)] 788 } 789 eval $redo remote 790 if {[info exists undo]} { 791 undo::add [::WB::GetUndoToken $wcan] $undo $redo 792 ::CanvasFile::SetUnsaved $wcan 793 } 794 unset -nocomplain xDragFrame 795} 796 797# CanvasDraw::InitMoveWindow -- 798# 799# Generic and general move functions for window items. 800 801proc ::CanvasDraw::InitMoveWindow {wcan win x y} { 802 global kGrad2Rad 803 variable xDragWin 804 805 set utag [::CanvasUtils::GetUtagFromWindow $win] 806 if {$utag eq ""} { 807 return 808 } 809 810 # Fix x and y. 811 set x [$wcan canvasx [expr {[winfo x $win] + $x}]] 812 set y [$wcan canvasx [expr {[winfo y $win] + $y}]] 813 Debug 2 "InitMoveWindow:: wcan=$wcan, win=$win x=$x, y=$y" 814 815 set xDragWin(what) "window" 816 set xDragWin(baseX) $x 817 set xDragWin(baseY) $y 818 set xDragWin(anchorX) $x 819 set xDragWin(anchorY) $y 820 821 # In some cases we need the anchor point to be an exact item 822 # specific coordinate. 823 set xDragWin(type) [$wcan type current] 824 set xDragWin(winbg) [$win cget -bg] 825 set xDragWin(undocmd) [concat coords $utag [$wcan coords $utag]] 826 $win configure -bg gray20 827 $wcan addtag _selectedwindow withtag $utag 828 set xDragWin(doMove) 1 829} 830 831# CanvasDraw::DoMoveWindow -- 832# 833# Moves a ghost rectangle of a framed window. 834 835proc ::CanvasDraw::DoMoveWindow {wcan win x y} { 836 variable xDragWin 837 838 if {![info exists xDragWin]} { 839 return 840 } 841 842 # Fix x and y. 843 set x [$wcan canvasx [expr {[winfo x $win] + $x}]] 844 set y [$wcan canvasx [expr {[winfo y $win] + $y}]] 845 lassign [ToScroll $wcan _selectedwindow $xDragWin(baseX) $xDragWin(baseY) $x $y] x y 846 847 # Moving a frame window item (_selectedwindow). 848 $wcan move _selectedwindow \ 849 [expr {$x - $xDragWin(baseX)}] [expr {$y - $xDragWin(baseY)}] 850 851 set xDragWin(baseX) $x 852 set xDragWin(baseY) $y 853} 854 855# CanvasDraw::FinMoveWindow -- 856# 857# 858 859proc ::CanvasDraw::FinMoveWindow {wcan win x y} { 860 variable xDragWin 861 862 Debug 2 "FinMoveWindow info exists xDragWin=[info exists xDragWin]" 863 864 if {![info exists xDragWin]} { 865 return 866 } 867 868 # Need to get the actual, constrained, coordinates and not the mouses. 869 set x $xDragWin(baseX) 870 set y $xDragWin(baseY) 871 872 set id [$wcan find withtag _selectedwindow] 873 set utag [::CanvasUtils::GetUtag $wcan $id] 874 875 Debug 2 "\t id=$id, utag=$utag, x=$x, y=$y" 876 877 if {$utag eq ""} { 878 return 879 } 880 $wcan dtag _selectedwindow _selectedwindow 881 set cmd [concat coords $utag [$wcan coords $utag]] 882 $win configure -bg $xDragWin(winbg) 883 884 # Do send to all connected. 885 set w [winfo toplevel $wcan] 886 set redo [list ::CanvasUtils::Command $w $cmd] 887 if {[info exists xDragWin(undocmd)]} { 888 set undo [list ::CanvasUtils::Command $w $xDragWin(undocmd)] 889 } 890 eval $redo remote 891 if {[info exists undo]} { 892 undo::add [::WB::GetUndoToken $wcan] $undo $redo 893 ::CanvasFile::SetUnsaved $wcan 894 } 895 unset -nocomplain xDragWin 896} 897 898# CanvasDraw::FinalMoveCurrentGrid -- 899# 900# A way to constrain movements to a grid. 901 902proc ::CanvasDraw::FinalMoveCurrentGrid {wcan x y grid args} { 903 variable moveArr 904 905 Debug 2 "::CanvasDraw::FinalMoveCurrentGrid" 906 907 set selected [$wcan find withtag selected&&!locked] 908 if {$selected != {}} { 909 return 910 } 911 set dx [expr {$x - $moveArr(x0)}] 912 set dy [expr {$y - $moveArr(y0)}] 913 set id $moveArr(id) 914 set utag [::CanvasUtils::GetUtag $wcan $id] 915 if {$utag eq ""} { 916 return 917 } 918 array set argsArr { 919 -anchor nw 920 } 921 array set argsArr $args 922 set w [winfo toplevel $wcan] 923 924 # Extract grid specifiers. 925 foreach {xmin dx nx} [lindex $grid 0] break 926 foreach {ymin dy ny} [lindex $grid 1] break 927 928 # Position of item. 929 foreach {x0 y0 x1 y1} [$wcan bbox $id] break 930 set xc [expr {int(($x0 + $x1)/2)}] 931 set yc [expr {int(($y0 + $y1)/2)}] 932 set width2 [expr {int(($x1 - $x0)/2)}] 933 set height2 [expr {int(($y1 - $y0)/2)}] 934 set ix [expr {round(double($xc - $xmin)/$dx)}] 935 set iy [expr {round(double($yc - $ymin)/$dy)}] 936 937 # Figure out if in the domain of the grid. 938 if {($ix >= 0) && ($ix <= $nx) && ($iy >= 0) && ($iy <= $ny)} { 939 set doGrid 1 940 set newx [expr {$xmin + $ix * $dx}] 941 set newy [expr {$ymin + $iy * $dy}] 942 } else { 943 set doGrid 0 944 set newx [expr {int($x)}] 945 set newy [expr {int($y)}] 946 } 947 948 if {[string equal $moveArr(type) "image"]} { 949 if {$doGrid} { 950 set anchor [$wcan itemcget $id -anchor] 951 952 switch -- $anchor { 953 nw { 954 set offx -$width2 955 set offy -$height2 956 } 957 default { 958 # missing... 959 set offx 0 960 set offy 0 961 } 962 } 963 incr newx $offx 964 incr newy $offy 965 } 966 set cmd [list coords $utag $newx $newy] 967 if {$doGrid} { 968 set redo [list ::CanvasUtils::Command $w $cmd] 969 } else { 970 set redo [list ::CanvasUtils::Command $w $cmd remote] 971 } 972 set undoCmd [concat coords $utag $moveArr(coords0,$id)] 973 } else { 974 975 # Non image items. 976 # If grid then compute distances to be moved: 977 # local item need only move to closest grid, 978 # remote item needs to be moved all the way. 979 if {$doGrid} { 980 set anchor c 981 set cmdlocal [list move $utag [expr {$newx - $xc}] [expr {$newy - $yc}]] 982 set deltax [expr {$newx - $moveArr(x0)}] 983 set deltay [expr {$newy - $moveArr(y0)}] 984 set cmdremote [list move $utag $deltax $deltay] 985 set redo [list ::CanvasUtils::CommandExList $w \ 986 [list [list $cmdlocal local] [list $cmdremote remote]]] 987 set undoCmd [list move $utag [expr {-1*$deltax}] [expr {-1*$deltay}]] 988 } else { 989 set cmd [list move $utag $dx $dy] 990 set redo [list ::CanvasUtils::Command $w $cmd remote] 991 set undoCmd [list move $utag [expr {-1*($x - $moveArr(x0))}] \ 992 [expr {-1*($y - $moveArr(y0))}]] 993 } 994 } 995 996 # Do send to all connected. 997 set undo [list ::CanvasUtils::Command $w $undoCmd] 998 eval $redo 999 undo::add [::WB::GetUndoToken $wcan] $undo $redo 1000 1001 ::CanvasFile::SetUnsaved $wcan 1002 1003 unset -nocomplain moveArr 1004} 1005 1006proc ::CanvasDraw::HitTBBox {wcan x y} { 1007 1008 set hit 0 1009 set d 2 1010 $wcan addtag _tmp overlapping \ 1011 [expr {$x-$d}] [expr {$y-$d}] [expr {$x+$d}] [expr {$y+$d}] 1012 if {[$wcan find withtag tbbox&&_tmp&&!locked] != {}} { 1013 set hit 1 1014 } 1015 $wcan dtag _tmp 1016 return $hit 1017} 1018 1019proc ::CanvasDraw::HitMovableTBBox {wcan x y} { 1020 1021 set hit 0 1022 set d 2 1023 set movable {arc line polygon rectangle oval} 1024 set ids [$wcan find overlapping \ 1025 [expr {$x-$d}] [expr {$y-$d}] [expr {$x+$d}] [expr {$y+$d}]] 1026 foreach id $ids { 1027 set tags [$wcan gettags $id] 1028 if {[lsearch $tags tbbox] >= 0} { 1029 if {[regexp {id:([0-9]+)} $tags match itemid]} { 1030 if {[lsearch $movable [$wcan type $itemid]] >= 0} { 1031 set hit 1 1032 break 1033 } 1034 } 1035 } 1036 } 1037 return $hit 1038} 1039 1040proc ::CanvasDraw::DrawHighlightBox {wcan itemid id} { 1041 1042 # Make a highlightbox at the 'hitBbox' marker. 1043 set bbox [$wcan bbox $id] 1044 set x1 [expr {[lindex $bbox 0] - 1}] 1045 set y1 [expr {[lindex $bbox 1] - 1}] 1046 set x2 [expr {[lindex $bbox 2] + 1}] 1047 set y2 [expr {[lindex $bbox 3] + 1}] 1048 1049 $wcan create rectangle $x1 $y1 $x2 $y2 -outline black -width 1 \ 1050 -tags [list lightBbox id:${itemid}] -fill white 1051} 1052 1053proc ::CanvasDraw::FindClosestCoordsIndex {x y coords} { 1054 1055 set n [llength $coords] 1056 set min 1000000 1057 set ind 0 1058 for {set i 0} {$i < $n} {incr i 2} { 1059 set len [expr {hypot([lindex $coords $i] - $x, \ 1060 [lindex $coords [expr {$i+1}]] - $y)}] 1061 if {$len < $min} { 1062 set ind $i 1063 set min $len 1064 } 1065 } 1066 return $ind 1067} 1068 1069proc ::CanvasDraw::GetConstrainedXY {x y} { 1070 variable moveArr 1071 1072 if {[string match tbbox:* $moveArr(bindType)]} { 1073 if {[string equal $moveArr(type) "arc"]} { 1074 set newco [ConstrainedDrag $x $y $moveArr(arcCX) $moveArr(arcCY)] 1075 } else { 1076 set newco [ConstrainedDrag $x $y $moveArr(x0) $moveArr(y0)] 1077 } 1078 } else { 1079 set newco [ConstrainedDrag $x $y $moveArr(x0) $moveArr(y0)] 1080 } 1081 return $newco 1082} 1083 1084#--- End of the 'move' tool procedures ----------------------------------------- 1085 1086#--- The rectangle, oval, and select from rectangle tool procedures ------------ 1087 1088# CanvasDraw::InitBox -- 1089# 1090# Initializes drawing of a rectangles, ovals, and ghost rectangles. 1091# 1092# Arguments: 1093# wcan the canvas widget. 1094# x,y the mouse coordinates. 1095# type item type (rectangle, oval, ...). 1096# 1097# Results: 1098# none 1099 1100proc ::CanvasDraw::InitBox {wcan x y type} { 1101 1102 variable theBox 1103 1104 set theBox($wcan,anchor) [list $x $y] 1105 set theBox($wcan,x) $x 1106 set theBox($wcan,y) $y 1107 unset -nocomplain theBox($wcan,last) 1108} 1109 1110# CanvasDraw::BoxDrag -- 1111# 1112# Draws rectangles, ovals, and ghost rectangles. 1113# 1114# Arguments: 1115# wcan the canvas widget. 1116# x,y the mouse coordinates. 1117# shift constrain to square or circle. 1118# type item type (rectangle, oval, ...). 1119# mark If not 'mark', then draw ordinary rectangle if 'type' is 1120# rectangle or oval if 'type' is oval. 1121# 1122# Results: 1123# none 1124 1125proc ::CanvasDraw::BoxDrag {wcan x y shift type {mark 0}} { 1126 global prefs 1127 1128 variable theBox 1129 1130 set w [winfo toplevel $wcan] 1131 array set state [::WB::GetStateArray $w] 1132 1133 catch {$wcan delete $theBox($wcan,last)} 1134 1135 # If not set anchor, just return. 1136 if {![info exists theBox($wcan,anchor)]} { 1137 return 1138 } 1139 set boxOrig $theBox($wcan,anchor) 1140 if {!$mark} { 1141 lassign [XYToScroll $wcan $x $y] x y 1142 } 1143 1144 # If 'shift' constrain to square or circle. 1145 if {$shift} { 1146 set box [eval ConstrainedBoxDrag $theBox($wcan,anchor) {$x $y $type}] 1147 set boxOrig [lrange $box 0 1] 1148 set x [lindex $box 2] 1149 set y [lindex $box 3] 1150 } 1151 if {$prefs(haveDash)} { 1152 set extras [list -dash $state(dash)] 1153 } else { 1154 set extras "" 1155 } 1156 1157 # Either mark rectangle or draw rectangle. 1158 if {$mark} { 1159 set theBox($wcan,last) [eval {$wcan create $type} $boxOrig \ 1160 {$x $y -outline gray50 -stipple gray50 -width 2 -tags "markbox" }] 1161 } else { 1162 set tags [list std $type] 1163 if {$state(fill)} { 1164 set theBox($wcan,last) [eval {$wcan create $type} $boxOrig \ 1165 {$x $y -outline $state(fgCol) -fill $state(fgCol) \ 1166 -width $state(penThick) -tags $tags} \ 1167 $extras] 1168 } else { 1169 set theBox($wcan,last) [eval {$wcan create $type} $boxOrig \ 1170 {$x $y -outline $state(fgCol) -width $state(penThick) \ 1171 -tags $tags} $extras] 1172 } 1173 } 1174 set theBox($wcan,x) $x 1175 set theBox($wcan,y) $y 1176} 1177 1178# CanvasDraw::FinalizeBox -- 1179# 1180# Take action when finsished with BoxDrag, mark items, let all other 1181# clients know etc. 1182# 1183# Arguments: 1184# wcan the canvas widget. 1185# x,y the mouse coordinates. 1186# shift constrain to square or circle. 1187# type item type (rectangle, oval, ...). 1188# mark If not 'mark', then draw ordinary rectangle if 'type' is rectangle, 1189# or oval if 'type' is oval. 1190# 1191# Results: 1192# none 1193 1194proc ::CanvasDraw::FinalizeBox {wcan x y shift type {mark 0}} { 1195 global prefs 1196 1197 variable theBox 1198 set w [winfo toplevel $wcan] 1199 array set state [::WB::GetStateArray $w] 1200 1201 # If no theBox($wcan,anchor) defined just return. 1202 if {![info exists theBox($wcan,anchor)]} { 1203 return 1204 } 1205 catch {$wcan delete $theBox($wcan,last)} 1206 lassign $theBox($wcan,anchor) xanch yanch 1207 1208 # Need to get the constrained "mouse point". 1209 set x $theBox($wcan,x) 1210 set y $theBox($wcan,y) 1211 if {($xanch == $x) && ($yanch == $y)} { 1212 set nomove 1 1213 return 1214 } else { 1215 set nomove 0 1216 } 1217 if {$mark} { 1218 set ids [eval {$wcan find overlapping} $theBox($wcan,anchor) {$x $y}] 1219 foreach id $ids { 1220 MarkBbox $wcan 1 $id 1221 } 1222 $wcan delete withtag markbox 1223 } 1224 set extras {} 1225 if {$prefs(haveDash)} { 1226 set extras [list -dash $state(dash)] 1227 } 1228 1229 # Create real objects. 1230 if {!$mark && !$nomove} { 1231 set boxOrig $theBox($wcan,anchor) 1232 if {$mark} { 1233 set utag [::CanvasUtils::NewUtag 0] 1234 } else { 1235 set utag [::CanvasUtils::NewUtag] 1236 } 1237 if {$state(fill)} { 1238 lappend extras -fill $state(fgCol) 1239 } 1240 set tags [list std $type $utag] 1241 set coo [concat $boxOrig $x $y] 1242 set cmd [list create $type $coo -tags $tags -outline $state(fgCol) \ 1243 -width $state(penThick)] 1244 set cmd [concat $cmd $extras] 1245 set undocmd [list delete $utag] 1246 set redo [list ::CanvasUtils::Command $w $cmd] 1247 set undo [list ::CanvasUtils::Command $w $undocmd] 1248 eval $redo 1249 undo::add [::WB::GetUndoToken $wcan] $undo $redo 1250 1251 ::CanvasFile::SetUnsaved $wcan 1252 } 1253 array unset theBox $wcan,* 1254} 1255 1256proc ::CanvasDraw::CancelBox {wcan} { 1257 1258 variable theBox 1259 unset -nocomplain theBox 1260 $wcan delete withtag markbox 1261} 1262 1263# ConstrainedBoxDrag -- 1264# 1265# With the 'shift' key pressed, the rectangle and oval items are contrained 1266# to squares and circles respectively. 1267# 1268# Arguments: 1269# xanch,yanch the anchor coordinates. 1270# x,y the mouse coordinates. 1271# type item type (rectangle, oval, ...). 1272# 1273# Results: 1274# List of the (two) new coordinates for the item. 1275 1276proc ::CanvasDraw::ConstrainedBoxDrag {xanch yanch x y type} { 1277 1278 set deltax [expr {$x - $xanch}] 1279 set deltay [expr {$y - $yanch}] 1280 set prod [expr {$deltax * $deltay}] 1281 if {$type eq "rectangle"} { 1282 set boxOrig [list $xanch $yanch] 1283 if {$prod != 0} { 1284 set sign [expr {$prod / abs($prod)}] 1285 } else { 1286 set sign 1 1287 } 1288 if {[expr {abs($deltax)}] > [expr {abs($deltay)}]} { 1289 set x [expr {$sign * ($y - $yanch) + $xanch}] 1290 } else { 1291 set y [expr {$sign * ($x - $xanch) + $yanch}] 1292 } 1293 1294 # A pure circle is not made with the bounding rectangle model. 1295 # The anchor and the present x, y define the diagonal instead. 1296 } elseif {$type eq "oval"} { 1297 set r [expr {hypot($deltax, $deltay)/2.0}] 1298 set midx [expr {($xanch + $x)/2.0}] 1299 set midy [expr {($yanch + $y)/2.0}] 1300 set boxOrig [list [expr {int($midx - $r)}] [expr {int($midy - $r)}]] 1301 set x [expr {int($midx + $r)}] 1302 set y [expr {int($midy + $r)}] 1303 } 1304 return [concat $boxOrig $x $y] 1305} 1306 1307#--- End of the rectangle, oval, and select from rectangle tool procedures ----- 1308 1309#--- The arc tool procedures --------------------------------------------------- 1310 1311# CanvasDraw::InitArc -- 1312# 1313# First click sets center, second button press sets start point. 1314# 1315# Arguments: 1316# wcan the canvas widget. 1317# x,y the mouse coordinates. 1318# type item type (rectangle, oval, ...). 1319# shift constrain to 45 or 90 degree arcs. 1320# 1321# Results: 1322# none 1323 1324proc ::CanvasDraw::InitArc {wcan x y {shift 0}} { 1325 global kRad2Grad this 1326 1327 variable arcBox 1328 set w [winfo toplevel $wcan] 1329 1330 Debug 2 "InitArc:: wcan=$wcan, x=$x, y=$y, shift=$shift" 1331 1332 if {![info exists arcBox($wcan,setcent)] || $arcBox($wcan,setcent) == 0} { 1333 1334 # First button press. 1335 set arcBox($wcan,center) [list $x $y] 1336 set arcBox($wcan,setcent) 1 1337 # Hack. 1338 if {[string match "mac*" $this(platform)]} { 1339 $wcan create oval [expr {$x - 2}] [expr {$y - 2}] [expr {$x + 3}] [expr {$y + 3}] \ 1340 -outline gray50 -fill {} -tags tcent 1341 $wcan create line [expr {$x - 5}] $y [expr {$x + 5}] $y -fill gray50 -tags tcent 1342 $wcan create line $x [expr {$y - 5}] $x [expr {$y + 5}] -fill gray50 -tags tcent 1343 } else { 1344 $wcan create oval [expr {$x - 3}] [expr {$y - 3}] [expr {$x + 3}] [expr {$y + 3}] \ 1345 -outline gray50 -fill {} -tags tcent 1346 $wcan create line [expr {$x - 5}] $y [expr {$x + 6}] $y -fill gray50 -tags tcent 1347 $wcan create line $x [expr {$y - 5}] $x [expr {$y + 6}] -fill gray50 -tags tcent 1348 } 1349 focus $wcan 1350 bind $wcan <KeyPress-space> { 1351 ::CanvasDraw::ArcCancel %W 1352 } 1353 ::WB::SetStatusMessage $w [mc "Arc tool, click and drag"] 1354 1355 } else { 1356 1357 # If second button press, bind mouse motion. 1358 set cx [lindex $arcBox($wcan,center) 0] 1359 set cy [lindex $arcBox($wcan,center) 1] 1360 if {$shift} { 1361 set newco [ConstrainedDrag $x $y $cx $cy] 1362 foreach {x y} $newco {} 1363 } 1364 set arcBox($wcan,first) [list $x $y] 1365 set arcBox($wcan,startAng) [expr {$kRad2Grad * atan2($cy - $y, -($cx - $x))}] 1366 set arcBox($wcan,extent) {0.0} 1367 set r [expr {hypot($cx - $x, $cy - $y)}] 1368 set x1 [expr {$cx + $r}] 1369 set y1 [expr {$cy + $r}] 1370 set arcBox($wcan,co1) [list $x1 $y1] 1371 set arcBox($wcan,co2) [list [expr {$cx - $r}] [expr {$cy - $r}]] 1372 bind $wcan <B1-Motion> { 1373 ::CanvasDraw::ArcDrag %W [%W canvasx %x] [%W canvasy %y] 1374 } 1375 bind $wcan <Shift-B1-Motion> { 1376 ::CanvasDraw::ArcDrag %W [%W canvasx %x] [%W canvasy %y] 1 1377 } 1378 bind $wcan <ButtonRelease-1> { 1379 ::CanvasDraw::FinalizeArc %W [%W canvasx %x] [%W canvasy %y] 1380 } 1381 } 1382 unset -nocomplain arcBox($wcan,last) 1383} 1384 1385# CanvasDraw::ArcDrag -- 1386# 1387# Draw an arc. 1388# The tricky part is to choose one of the two possible solutions, CW or CCW. 1389# 1390# Arguments: 1391# wcan the canvas widget. 1392# x,y the mouse coordinates. 1393# shift constrain to 45 or 90 degree arcs. 1394# 1395# Results: 1396# none 1397 1398proc ::CanvasDraw::ArcDrag {wcan x y {shift 0}} { 1399 global kRad2Grad prefs 1400 1401 variable arcBox 1402 set w [winfo toplevel $wcan] 1403 array set state [::WB::GetStateArray $w] 1404 1405 # @@@ Remains to constrain to scrollregion. 1406 1407 # If constrained to 90/45 degrees. 1408 if {$shift} { 1409 lassign $arcBox($wcan,center) cx cy 1410 lassign [ConstrainedDrag $x $y $cx $cy] x y 1411 } 1412 1413 # Choose one of two possible solutions, either CW or CCW. 1414 # Make sure that the 'extent' angle is more or less continuous. 1415 1416 set stopAng [expr {$kRad2Grad * \ 1417 atan2([lindex $arcBox($wcan,center) 1] - $y, -([lindex $arcBox($wcan,center) 0] - $x))}] 1418 set extentAng [expr {$stopAng - $arcBox($wcan,startAng)}] 1419 if {[expr {$arcBox($wcan,extent) - $extentAng}] > 180} { 1420 set extentAng [expr {$extentAng + 360}] 1421 } elseif {[expr {$arcBox($wcan,extent) - $extentAng}] < -180} { 1422 set extentAng [expr {$extentAng - 360}] 1423 } 1424 set arcBox($wcan,extent) $extentAng 1425 catch {$wcan delete $arcBox($wcan,last)} 1426 if {$state(fill)} { 1427 set theFill [list -fill $state(fgCol)] 1428 } else { 1429 set theFill [list -fill {}] 1430 } 1431 if {$prefs(haveDash)} { 1432 set extras [list -dash $state(dash)] 1433 } else { 1434 set extras {} 1435 } 1436 set arcBox($wcan,last) [eval {$wcan create arc} $arcBox($wcan,co1) \ 1437 $arcBox($wcan,co2) {-start $arcBox($wcan,startAng) -extent $extentAng \ 1438 -width $state(penThick) -style $state(arcstyle) -outline $state(fgCol) \ 1439 -tags arc} $theFill $extras] 1440} 1441 1442# CanvasDraw::FinalizeArc -- 1443# 1444# Finalize the arc drawing, tell all other clients. 1445# 1446# Arguments: 1447# wcan the canvas widget. 1448# x,y the mouse coordinates. 1449# 1450# Results: 1451# none 1452 1453proc ::CanvasDraw::FinalizeArc {wcan x y} { 1454 global prefs 1455 1456 variable arcBox 1457 set w [winfo toplevel $wcan] 1458 array set state [::WB::GetStateArray $w] 1459 1460 Debug 2 "FinalizeArc:: wcan=$wcan" 1461 1462 ::WB::SetStatusMessage $w [mc "Arc tool, click to set center, spacebar to cancel"] 1463 bind $wcan <B1-Motion> {} 1464 bind $wcan <ButtonRelease-1> {} 1465 bind $wcan <KeyPress-space> {} 1466 catch {$wcan delete tcent} 1467 catch {$wcan delete $arcBox($wcan,last)} 1468 1469 # If extent angle zero, nothing to draw, nothing to send. 1470 if {$arcBox($wcan,extent) eq "0.0"} { 1471 unset -nocomplain arcBox 1472 return 1473 } 1474 1475 # Let all other clients know. 1476 if {$state(fill) == 0} { 1477 set theFill "-fill {}" 1478 } else { 1479 set theFill "-fill $state(fgCol)" 1480 } 1481 if {$prefs(haveDash)} { 1482 set extras [list -dash $state(dash)] 1483 } else { 1484 set extras {} 1485 } 1486 set utag [::CanvasUtils::NewUtag] 1487 set cmd "create arc $arcBox($wcan,co1) \ 1488 $arcBox($wcan,co2) -start $arcBox($wcan,startAng) -extent $arcBox($wcan,extent) \ 1489 -width $state(penThick) -style $state(arcstyle) -outline $state(fgCol) \ 1490 -tags {std arc $utag} $theFill $extras" 1491 set undocmd "delete $utag" 1492 set redo [list ::CanvasUtils::Command $w $cmd] 1493 set undo [list ::CanvasUtils::Command $w $undocmd] 1494 eval $redo 1495 undo::add [::WB::GetUndoToken $wcan] $undo $redo 1496 ::CanvasFile::SetUnsaved $wcan 1497 unset -nocomplain arcBox 1498} 1499 1500# CanvasDraw::ArcCancel -- 1501# 1502# Cancel the arc drawing. 1503# 1504# Arguments: 1505# wcan the canvas widget. 1506# 1507# Results: 1508# none 1509 1510proc ::CanvasDraw::ArcCancel {wcan} { 1511 1512 variable arcBox 1513 set w [winfo toplevel $wcan] 1514 1515 bind $wcan <B1-Motion> {} 1516 bind $wcan <ButtonRelease-1> {} 1517 bind $wcan <KeyPress-space> {} 1518 1519 ::WB::SetStatusMessage $w [mc "Arc tool, click to set center, spacebar to cancel"] 1520 catch {$wcan delete tcent} 1521 catch {$wcan delete $arcBox($wcan,last)} 1522 unset -nocomplain arcBox 1523} 1524 1525#--- End of the arc tool procedures -------------------------------------------- 1526 1527#--- Polygon tool procedures --------------------------------------------------- 1528 1529# CanvasDraw::PolySetPoint -- 1530# 1531# Polygon drawing routines. 1532# 1533# Arguments: 1534# wcan the canvas widget. 1535# x,y the mouse coordinates. 1536# 1537# Results: 1538# none 1539 1540proc ::CanvasDraw::PolySetPoint {wcan x y} { 1541 1542 variable thePoly 1543 1544 if {![info exists thePoly(0)]} { 1545 1546 # First point. 1547 unset -nocomplain thePoly 1548 set thePoly(N) 0 1549 set thePoly(0) [list $x $y] 1550 } elseif {[expr \ 1551 {hypot([lindex $thePoly(0) 0] - $x, [lindex $thePoly(0) 1] - $y)}] < 6} { 1552 1553 # If this point close enough to 'thePoly(0)', close polygon. 1554 PolyDrag $wcan [lindex $thePoly(0) 0] [lindex $thePoly(0) 1] 1555 set thePoly(last) {} 1556 incr thePoly(N) 1557 set thePoly($thePoly(N)) $thePoly(0) 1558 FinalizePoly $wcan [lindex $thePoly(0) 0] [lindex $thePoly(0) 1] 1559 return 1560 } else { 1561 set thePoly(last) {} 1562 incr thePoly(N) 1563 set thePoly($thePoly(N)) $thePoly(xy) 1564 } 1565 1566 # Let the latest line segment follow the mouse movements. 1567 focus $wcan 1568 bind $wcan <Motion> { 1569 ::CanvasDraw::PolyDrag %W [%W canvasx %x] [%W canvasy %y] 1570 } 1571 bind $wcan <Shift-Motion> { 1572 ::CanvasDraw::PolyDrag %W [%W canvasx %x] [%W canvasy %y] 1 1573 } 1574 bind $wcan <KeyPress-space> { 1575 ::CanvasDraw::FinalizePoly %W [%W canvasx %x] [%W canvasy %y] 1576 } 1577} 1578 1579# CanvasDraw::PolyDrag -- 1580# 1581# Polygon drawing routines. 1582# 1583# Arguments: 1584# wcan the canvas widget. 1585# x,y the mouse coordinates. 1586# shift constrain. 1587# 1588# Results: 1589# none 1590 1591proc ::CanvasDraw::PolyDrag {wcan x y {shift 0}} { 1592 global prefs 1593 1594 variable thePoly 1595 set w [winfo toplevel $wcan] 1596 array set state [::WB::GetStateArray $w] 1597 1598 # Move one end point of the latest line segment of the polygon. 1599 # If anchor not set just return. 1600 if {![info exists thePoly(0)]} { 1601 return 1602 } 1603 catch {$wcan delete $thePoly(last)} 1604 1605 lassign [XYToScroll $wcan $x $y] x y 1606 1607 # Vertical or horizontal. 1608 if {$shift} { 1609 lassign $thePoly($thePoly(N)) x0 y0 1610 lassign [ConstrainedDrag $x $y $x0 $y0] x y 1611 } 1612 if {$prefs(haveDash)} { 1613 set extras [list -dash $state(dash)] 1614 } else { 1615 set extras {} 1616 } 1617 1618 # Keep track of last coordinates. Important for 'shift'. 1619 set thePoly(xy) [list $x $y] 1620 set thePoly(last) [eval {$wcan create line} $thePoly($thePoly(N)) \ 1621 {$x $y -tags _polylines -fill $state(fgCol) \ 1622 -width $state(penThick)} $extras] 1623} 1624 1625# CanvasDraw::FinalizePoly -- 1626# 1627# Polygon drawing routines. 1628# 1629# Arguments: 1630# wcan the canvas widget. 1631# x,y the mouse coordinates. 1632# 1633# Results: 1634# none 1635 1636proc ::CanvasDraw::FinalizePoly {wcan x y} { 1637 global prefs 1638 variable thePoly 1639 1640 set w [winfo toplevel $wcan] 1641 array set state [::WB::GetStateArray $w] 1642 1643 bind $wcan <Motion> {} 1644 bind $wcan <KeyPress-space> {} 1645 1646 # If anchor not set just return. 1647 if {![info exists thePoly(0)]} { 1648 return 1649 } 1650 1651 # If too few segment. 1652 if {$thePoly(N) <= 1} { 1653 $wcan delete _polylines 1654 unset -nocomplain thePoly 1655 return 1656 } 1657 1658 # Delete last line segment. 1659 catch {$wcan delete $thePoly(last)} 1660 1661 # Find out if closed polygon or open line item. If closed, remove duplicate. 1662 set isClosed 0 1663 if {[expr \ 1664 {hypot([lindex $thePoly(0) 0] - $x, [lindex $thePoly(0) 1] - $y)}] < 4} { 1665 set isClosed 1 1666 unset thePoly($thePoly(N)) 1667 incr thePoly(N) -1 1668 } 1669 1670 # Transform the set of lines to a polygon (or line) item. 1671 set coords {} 1672 for {set i 0} {$i <= $thePoly(N)} {incr i} { 1673 append coords $thePoly($i) " " 1674 } 1675 $wcan delete _polylines 1676 set extras {} 1677 if {$prefs(haveDash)} { 1678 lappend extras -dash $state(dash) 1679 } 1680 set utag [::CanvasUtils::NewUtag] 1681 if {$isClosed} { 1682 1683 # This is a (closed) polygon. 1684 if {$state(fill)} { 1685 lappend extras -fill $state(fgCol) 1686 } else { 1687 lappend extras -fill {} 1688 } 1689 set tags [list std polygon $utag] 1690 set cmd [list create polygon $coords -tags $tags \ 1691 -outline $state(fgCol) -width $state(penThick) \ 1692 -smooth $state(smooth)] 1693 set cmd [concat $cmd $extras] 1694 } else { 1695 1696 # This is an open line segment. 1697 set tags [list std line $utag] 1698 set cmd [list create line $coords -tags $tags \ 1699 -fill $state(fgCol) -width $state(penThick) \ 1700 -smooth $state(smooth)] 1701 set cmd [concat $cmd $extras] 1702 } 1703 set undocmd [list delete $utag] 1704 set redo [list ::CanvasUtils::Command $w $cmd] 1705 set undo [list ::CanvasUtils::Command $w $undocmd] 1706 eval $redo 1707 undo::add [::WB::GetUndoToken $wcan] $undo $redo 1708 ::CanvasFile::SetUnsaved $wcan 1709 unset -nocomplain thePoly 1710} 1711 1712proc ::CanvasDraw::CancelPoly {wcan} { 1713 variable thePoly 1714 1715 unset -nocomplain thePoly 1716} 1717 1718#--- End of polygon drawing procedures ----------------------------------------- 1719 1720#--- Line and arrow drawing procedures ----------------------------------------- 1721 1722# CanvasDraw::InitLine -- 1723# 1724# Handles drawing of a straight line. Uses global 'theLine' variable 1725# to store anchor point and end point of the line. 1726# 1727# Arguments: 1728# wcan the canvas widget. 1729# x,y the mouse coordinates. 1730# opt 0 for line and arrow for arrow. 1731# 1732# Results: 1733# none 1734 1735proc ::CanvasDraw::InitLine {wcan x y {opt 0}} { 1736 1737 variable theLine 1738 1739 set theLine($wcan,anchor) [list $x $y] 1740 set theLine($wcan,x) $x 1741 set theLine($wcan,y) $y 1742 set theLine($wcan,x0) $x 1743 set theLine($wcan,y0) $y 1744 unset -nocomplain theLine($wcan,last) 1745} 1746 1747# CanvasDraw::LineDrag -- 1748# 1749# Handles drawing of a straight line. Uses global 'theLine' variable 1750# to store anchor point and end point of the line. 1751# 1752# Arguments: 1753# wcan the canvas widget. 1754# x,y the mouse coordinates. 1755# shift constrain the line to be vertical or horizontal. 1756# opt If 'opt'=arrow draw an arrow at the final line end. 1757# 1758# Results: 1759# none 1760 1761proc ::CanvasDraw::LineDrag {wcan x y shift {opt 0}} { 1762 global prefs 1763 1764 variable theLine 1765 set w [winfo toplevel $wcan] 1766 array set state [::WB::GetStateArray $w] 1767 1768 # If anchor not set just return. 1769 if {![info exists theLine($wcan,anchor)]} { 1770 return 1771 } 1772 1773 1774 catch {$wcan delete $theLine($wcan,last)} 1775 if {[string equal $opt "arrow"]} { 1776 set extras [list -arrow last] 1777 } else { 1778 set extras {} 1779 } 1780 if {$prefs(haveDash)} { 1781 lappend extras -dash $state(dash) 1782 } 1783 lassign [XYToScroll $wcan $x $y] x y 1784 1785 # Vertical or horizontal. 1786 if {$shift} { 1787 lassign [ConstrainedDrag $x $y $theLine($wcan,x0) $theLine($wcan,y0)] x y 1788 } 1789 set theLine($wcan,last) [eval {$wcan create line} $theLine($wcan,anchor) \ 1790 {$x $y -tags line -fill $state(fgCol) -width $state(penThick)} $extras] 1791 1792 set theLine($wcan,x) $x 1793 set theLine($wcan,y) $y 1794} 1795 1796# CanvasDraw::FinalizeLine -- 1797# 1798# Handles drawing of a straight line. Uses global 'theLine' variable 1799# to store anchor point and end point of the line. 1800# Lets all other clients know. 1801# 1802# Arguments: 1803# wcan the canvas widget. 1804# x,y the mouse coordinates. 1805# shift constrain the line to be vertical or horizontal. 1806# opt If 'opt'=arrow draw an arrow at the final line end. 1807# 1808# Results: 1809# none 1810 1811proc ::CanvasDraw::FinalizeLine {wcan x y shift {opt 0}} { 1812 global prefs 1813 1814 variable theLine 1815 set w [winfo toplevel $wcan] 1816 array set state [::WB::GetStateArray $w] 1817 1818 # If anchor not set just return. 1819 if {![info exists theLine($wcan,anchor)]} { 1820 return 1821 } 1822 catch {$wcan delete $theLine($wcan,last)} 1823 1824 # If not dragged, zero line, and just return. 1825 if {![info exists theLine($wcan,last)]} { 1826 return 1827 } 1828 if {[string equal $opt "arrow"]} { 1829 set extras [list -arrow last] 1830 } else { 1831 set extras {} 1832 } 1833 if {$prefs(haveDash)} { 1834 lappend extras -dash $state(dash) 1835 } 1836 1837 # Need to get the actual, constrained, coordinates and not the mouses. 1838 set x $theLine($wcan,x) 1839 set y $theLine($wcan,y) 1840 1841 # Vertical or horizontal. 1842 if {$shift} { 1843 lassign [ConstrainedDrag $x $y $theLine($wcan,x0) $theLine($wcan,y0)] x y 1844 } 1845 set utag [::CanvasUtils::NewUtag] 1846 set tags [list std line $utag] 1847 set cmd [list create line $theLine($wcan,x0) $theLine($wcan,y0) $x $y \ 1848 -tags $tags -joinstyle round -fill $state(fgCol) -width $state(penThick)] 1849 set cmd [concat $cmd $extras] 1850 set undocmd [list delete $utag] 1851 set redo [list ::CanvasUtils::Command $w $cmd] 1852 set undo [list ::CanvasUtils::Command $w $undocmd] 1853 eval $redo 1854 undo::add [::WB::GetUndoToken $wcan] $undo $redo 1855 ::CanvasFile::SetUnsaved $wcan 1856 unset -nocomplain theLine 1857} 1858 1859#--- End of line and arrow drawing procedures ---------------------------------- 1860 1861#--- The stroke tool ----------------------------------------------------------- 1862 1863# CanvasDraw::InitStroke -- 1864# 1865# Handles drawing of an arbitrary line. Uses global 'stroke' variable 1866# to store all intermediate points on the line, and stroke(N) to store 1867# the number of such points. If 'thick'=-1, then use 'state(penThick)', 1868# else use the 'thick' argument as line thickness. 1869# 1870# Arguments: 1871# wcan the canvas widget. 1872# x,y the mouse coordinates. 1873# 1874# Results: 1875# none 1876 1877proc ::CanvasDraw::InitStroke {wcan x y} { 1878 1879 variable stroke 1880 1881 unset -nocomplain stroke 1882 set stroke(N) 0 1883 set stroke(0) [list $x $y] 1884} 1885 1886# CanvasDraw::StrokeDrag -- 1887# 1888# Handles drawing of an arbitrary line. Uses global 'stroke' variable 1889# to store all intermediate points on the line, and stroke(N) to store 1890# the number of such points. 1891# 1892# Arguments: 1893# wcan the canvas widget. 1894# x,y the mouse coordinates. 1895# brush (D=0) boolean, 1 for brush, 0 for pen. 1896# 1897# Results: 1898# none 1899 1900proc ::CanvasDraw::StrokeDrag {wcan x y {brush 0}} { 1901 global prefs 1902 1903 variable stroke 1904 set w [winfo toplevel $wcan] 1905 array set state [::WB::GetStateArray $w] 1906 1907 # If stroke not set just return. 1908 if {![info exists stroke(N)]} { 1909 return 1910 } 1911 lassign [XYToScroll $wcan $x $y] x y 1912 set coords $stroke($stroke(N)) 1913 lappend coords $x $y 1914 incr stroke(N) 1915 set stroke($stroke(N)) [list $x $y] 1916 if {$brush} { 1917 set thick $state(brushThick) 1918 } else { 1919 set thick $state(penThick) 1920 } 1921 if {$prefs(haveDash)} { 1922 set extras [list -dash $state(dash)] 1923 } else { 1924 set extras {} 1925 } 1926 eval {$wcan create line} $coords {-tags segments -fill $state(fgCol) \ 1927 -width $thick} $extras 1928} 1929 1930# CanvasDraw::FinalizeStroke -- 1931# 1932# Handles drawing of an arbitrary line. Uses global 'stroke' variable 1933# to store all intermediate points on the line, and stroke(N) to store 1934# the number of such points. 1935# 1936# Arguments: 1937# wcan the canvas widget. 1938# x,y the mouse coordinates. 1939# brush (D=0) boolean, 1 for brush, 0 for pen. 1940# 1941# Results: 1942# none 1943 1944proc ::CanvasDraw::FinalizeStroke {wcan x y {brush 0}} { 1945 global prefs 1946 1947 variable stroke 1948 set w [winfo toplevel $wcan] 1949 array set state [::WB::GetStateArray $w] 1950 1951 Debug 2 "FinalizeStroke::" 1952 1953 # If stroke not set just return. 1954 set coords {} 1955 if {![info exists stroke(N)]} { 1956 return 1957 } 1958 if {$prefs(wb,strokePost)} { 1959 set coords [StrokePostProcess $wcan] 1960 } else { 1961 set coords [StrokeGetCoords $wcan] 1962 } 1963 $wcan delete segments 1964 if {[llength $coords] <= 2} { 1965 return 1966 } 1967 if {$brush} { 1968 set thick $state(brushThick) 1969 } else { 1970 set thick $state(penThick) 1971 } 1972 if {$prefs(haveDash)} { 1973 set extras [list -dash $state(dash)] 1974 } else { 1975 set extras {} 1976 } 1977 if {$prefs(wb,strokePost)} { 1978 set smooth $state(smooth) 1979 } else { 1980 set smooth 0 1981 } 1982 set utag [::CanvasUtils::NewUtag] 1983 set cmd [list create line $coords \ 1984 -tags [list std line $utag] -joinstyle round \ 1985 -smooth $smooth -fill $state(fgCol) -width $thick] 1986 set cmd [concat $cmd $extras] 1987 set undocmd [list delete $utag] 1988 set redo [list ::CanvasUtils::Command $w $cmd] 1989 set undo [list ::CanvasUtils::Command $w $undocmd] 1990 eval $redo 1991 undo::add [::WB::GetUndoToken $wcan] $undo $redo 1992 ::CanvasFile::SetUnsaved $wcan 1993 unset -nocomplain stroke 1994} 1995 1996# CanvasDraw::StrokePostProcess -- 1997# 1998# Reduce the number of coords in the stroke in a smart way that also 1999# smooths it. Always keep first and last. 2000 2001proc ::CanvasDraw::StrokePostProcess {wcan} { 2002 variable stroke 2003 2004 set coords [StrokeGetCoords $wcan] 2005 2006 # Next pass: remove points that are close to each other. 2007 set coords [StripClosePoints $coords 6] 2008 2009 # Next pass: remove points that gives a too small radius or points 2010 # lying on a straight line. 2011 set coords [StripExtremeRadius $coords 6 10000] 2012 return $coords 2013} 2014 2015proc ::CanvasDraw::StrokeGetCoords {wcan} { 2016 variable stroke 2017 2018 set coords $stroke(0) 2019 2020 # First pass: remove duplicates if any. Seems not to be the case! 2021 for {set i 0} {$i <= [expr {$stroke(N) - 1}]} {incr i} { 2022 if {$stroke($i) != $stroke([expr {$i+1}])} { 2023 set coords [concat $coords $stroke([expr {$i+1}])] 2024 } 2025 } 2026 return $coords 2027} 2028 2029#--- End of stroke tool -------------------------------------------------------- 2030 2031#--- The Paint tool ------------------------------------------------------------ 2032 2033# CanvasDraw::DoPaint -- 2034# 2035# Fills item with the foreground color. If 'shift', then transparent. 2036# Tell all other clients. 2037# 2038# Arguments: 2039# wcan the canvas widget. 2040# x,y the mouse coordinates. 2041# shift makes transparent. 2042# 2043# Results: 2044# none 2045 2046proc ::CanvasDraw::DoPaint {wcan x y {shift 0}} { 2047 global prefs kRad2Grad 2048 2049 set w [winfo toplevel $wcan] 2050 array set state [::WB::GetStateArray $w] 2051 2052 Debug 2 "DoPaint:: wcan=$wcan, x=$x, y=$y, shift=$shift" 2053 2054 # Find items overlapping x and y. Doesn't work for transparent items. 2055 #set ids [$wcan find overlapping $x $y $x $y] 2056 # This is perhaps not an efficient solution. 2057 set ids [$wcan find all] 2058 2059 foreach id $ids { 2060 set theType [$wcan type $id] 2061 2062 # Sort out uninteresting items early. 2063 if {![string equal $theType "rectangle"] && \ 2064 ![string equal $theType "oval"] && \ 2065 ![string equal $theType "arc"]} { 2066 continue 2067 } 2068 2069 # Must be in bounding box. 2070 set theBbox [$wcan bbox $id] 2071 2072 if {$x >= [lindex $theBbox 0] && $x <= [lindex $theBbox 2] && \ 2073 $y >= [lindex $theBbox 1] && $y <= [lindex $theBbox 3]} { 2074 # OK, inside! 2075 # Allow privacy. 2076 set theItno [::CanvasUtils::GetUtag $wcan $id] 2077 if {$theItno eq ""} { 2078 continue 2079 } 2080 set cmd "" 2081 if {[string equal $theType "rectangle"]} { 2082 if {$shift == 0} { 2083 set cmd [list itemconfigure $theItno -fill $state(fgCol)] 2084 } elseif {$shift == 1} { 2085 set cmd [list itemconfigure $theItno -fill {}] 2086 } 2087 } elseif {[string equal $theType "oval"]} { 2088 2089 # Use ellipsis equation (1 = x^2/a^2 + y^2/b^2) to find if inside. 2090 set centx [expr {([lindex $theBbox 0] + [lindex $theBbox 2])/2.0}] 2091 set centy [expr {([lindex $theBbox 1] + [lindex $theBbox 3])/2.0}] 2092 set a [expr {abs($centx - [lindex $theBbox 0])}] 2093 set b [expr {abs($centy - [lindex $theBbox 1])}] 2094 if {[expr {($x-$centx)*($x-$centx)/($a*$a) + \ 2095 ($y-$centy)*($y-$centy)/($b*$b)}] <= 1} { 2096 # Inside! 2097 if {$shift == 0} { 2098 set cmd [list itemconfigure $theItno -fill $state(fgCol)] 2099 } elseif {$shift == 1} { 2100 set cmd [list itemconfigure $theItno -fill {}] 2101 } 2102 } 2103 } elseif {[string equal $theType "arc"]} { 2104 set theCoords [$wcan coords $id] 2105 set cx [expr {([lindex $theCoords 0] + [lindex $theCoords 2])/2.0}] 2106 set cy [expr {([lindex $theCoords 1] + [lindex $theCoords 3])/2.0}] 2107 set r [expr {abs([lindex $theCoords 2] - [lindex $theCoords 0])/2.0}] 2108 set rp [expr {hypot($x - $cx, $y - $cy)}] 2109 2110 # Sort out point outside the radius of the arc. 2111 if {$rp > $r} { 2112 continue 2113 } 2114 set phi [expr {$kRad2Grad * atan2(-($y - $cy),$x - $cx)}] 2115 if {$phi < 0} { 2116 set phi [expr {$phi + 360}] 2117 } 2118 set startPhi [$wcan itemcget $id -start] 2119 set extentPhi [$wcan itemcget $id -extent] 2120 if {$extentPhi >= 0} { 2121 set phi1 $startPhi 2122 set phi2 [expr {$startPhi + $extentPhi}] 2123 } else { 2124 set phi1 [expr {$startPhi + $extentPhi}] 2125 set phi2 $startPhi 2126 } 2127 2128 # Put branch cut at 360 degrees. Count CCW. 2129 if {$phi1 > 360} { 2130 set phi1 [expr {$phi1 - 360}] 2131 } elseif {$phi1 < 0} { 2132 set phi1 [expr {$phi1 + 360}] 2133 } 2134 if {$phi2 > 360} { 2135 set phi2 [expr {$phi2 - 360}] 2136 } elseif {$phi2 < 0} { 2137 set phi2 [expr {$phi2 + 360}] 2138 } 2139 set inside 0 2140 2141 # Keep track of if the arc covers the branch cut or not. 2142 if {$phi2 > $phi1} { 2143 if {$phi >= $phi1 && $phi <= $phi2} { 2144 set inside 1 2145 } 2146 } else { 2147 if {$phi >= $phi1 || $phi <= $phi2} { 2148 set inside 1 2149 } 2150 } 2151 if {$inside} { 2152 if {$shift == 0} { 2153 set cmd [list itemconfigure $theItno -fill $state(fgCol)] 2154 } elseif {$shift == 1} { 2155 set cmd [list itemconfigure $theItno -fill {}] 2156 } 2157 } 2158 } 2159 if {$cmd != {}} { 2160 set undocmd [list itemconfigure $theItno \ 2161 -fill [$wcan itemcget $theItno -fill]] 2162 set redo [list ::CanvasUtils::Command $w $cmd] 2163 set undo [list ::CanvasUtils::Command $w $undocmd] 2164 eval $redo 2165 undo::add [::WB::GetUndoToken $wcan] $undo $redo 2166 ::CanvasFile::SetUnsaved $wcan 2167 } 2168 } 2169 } 2170 2171} 2172 2173#--- End of paint tool --------------------------------------------------------- 2174 2175#--- The rotate tool ----------------------------------------------------------- 2176 2177# CanvasDraw::InitRotateItem -- 2178# 2179# Inits a rotate operation. 2180# 2181# Arguments: 2182# wcan the canvas widget. 2183# x,y the mouse coordinates. 2184# 2185# Results: 2186# none 2187 2188proc ::CanvasDraw::InitRotateItem {wcan x y} { 2189 2190 variable rotDrag 2191 2192 # Only one single selected item is allowed to be rotated. 2193 set id [$wcan find withtag selected&&!locked] 2194 if {[llength $id] != 1} { 2195 return 2196 } 2197 set utag [::CanvasUtils::GetUtag $wcan $id] 2198 if {$utag eq ""} { 2199 return 2200 } 2201 2202 # Certain item types cannot be rotated. 2203 set rotDrag(type) [$wcan type $id] 2204 if {[string equal $rotDrag(type) "text"]} { 2205 unset rotDrag 2206 return 2207 } 2208 2209 # Get center of gravity and cache undo command. 2210 if {[string equal $rotDrag(type) "arc"]} { 2211 set colist [$wcan coords $id] 2212 set rotDrag(arcStart) [$wcan itemcget $id -start] 2213 set rotDrag(undocmd) [list itemconfigure $utag -start $rotDrag(arcStart)] 2214 } else { 2215 set colist [$wcan bbox $id] 2216 set rotDrag(undocmd) [concat coords $utag [$wcan coords $utag]] 2217 } 2218 set rotDrag(cgX) [expr {([lindex $colist 0] + [lindex $colist 2])/2.0}] 2219 set rotDrag(cgY) [expr {([lindex $colist 1] + [lindex $colist 3])/2.0}] 2220 set rotDrag(anchorX) $x 2221 set rotDrag(anchorY) $y 2222 set rotDrag(id) $id 2223 set rotDrag(utag) $utag 2224 set rotDrag(lastAng) 0.0 2225 2226 # Save coordinates relative cg. 2227 set theCoords [$wcan coords $id] 2228 set rotDrag(n) [expr {[llength $theCoords]/2}] ;# Number of points. 2229 set i 0 2230 foreach {cx cy} $theCoords { 2231 set rotDrag(x,$i) [expr {$cx - $rotDrag(cgX)}] 2232 set rotDrag(y,$i) [expr {$cy - $rotDrag(cgY)}] 2233 incr i 2234 } 2235 2236 # Observe coordinate system. 2237 set rotDrag(startAng) [expr {atan2($y - $rotDrag(cgY), $x - $rotDrag(cgX))}] 2238 2239 # Keep an invisible fake copy to deal with constraints (scroll region). 2240 set cmdFake [::CanvasUtils::DuplicateItem $wcan $id -fill {} -outline {}] 2241 set rotDrag(idx) [eval $cmdFake] 2242} 2243 2244# CanvasDraw::DoRotateItem -- 2245# 2246# Rotates an item. 2247# 2248# Arguments: 2249# wcan the canvas widget. 2250# x,y the mouse coordinates. 2251# shift constrains rotation. 2252# 2253# Results: 2254# none 2255 2256proc ::CanvasDraw::DoRotateItem {wcan x y {shift 0}} { 2257 global kPI kRad2Grad prefs 2258 2259 variable rotDrag 2260 2261 if {![info exists rotDrag]} { 2262 return 2263 } 2264 set newAng [expr {atan2($y - $rotDrag(cgY), $x - $rotDrag(cgX))}] 2265 set deltaAng [expr {$rotDrag(startAng) - $newAng}] 2266 set angle 0.0 2267 2268 # Certain items are only rotated in 90 degree intervals, other continuously. 2269 switch -- $rotDrag(type) { 2270 arc - line - polygon { 2271 if {$shift} { 2272 if {!$prefs(45)} { 2273 set angle [expr {($kPI/2.0) * round($deltaAng/($kPI/2.0))}] 2274 } elseif {$prefs(45)} { 2275 set angle [expr {($kPI/4.0) * round($deltaAng/($kPI/4.0))}] 2276 } 2277 } else { 2278 set angle $deltaAng 2279 } 2280 } 2281 rectangle - oval { 2282 2283 # Find the rotated angle in steps of 90 degrees. 2284 set angle [expr {($kPI/2.0) * round($deltaAng/($kPI/2.0))}] 2285 } 2286 } 2287 2288 # Find the new coordinates; arc: only start angle. 2289 if {[expr {abs($angle)}] > 1e-4 || \ 2290 [expr {abs($rotDrag(lastAng) - $angle)}] > 1e-4} { 2291 set sinAng [expr {sin($angle)}] 2292 set cosAng [expr {cos($angle)}] 2293 set id $rotDrag(id) 2294 set idx $rotDrag(idx) 2295 if {[string equal $rotDrag(type) "arc"]} { 2296 2297 # Different coordinate system for arcs...and units... 2298 set start [expr {$kRad2Grad * $angle + $rotDrag(arcStart)}] 2299 set cmdReal [list $wcan itemconfigure $id -start $start] 2300 set cmdFake [list $wcan itemconfigure $idx -start $start] 2301 } else { 2302 2303 # Compute new coordinates from the original ones. 2304 set new {} 2305 for {set i 0} {$i < $rotDrag(n)} {incr i} { 2306 lappend new [expr {$rotDrag(cgX) + $cosAng * $rotDrag(x,$i) + \ 2307 $sinAng * $rotDrag(y,$i)}] 2308 lappend new [expr {$rotDrag(cgY) - $sinAng * $rotDrag(x,$i) + \ 2309 $cosAng * $rotDrag(y,$i)}] 2310 } 2311 set cmdReal [list $wcan coords $id $new] 2312 set cmdFake [list $wcan coords $idx $new] 2313 } 2314 eval $cmdFake 2315 set bbox [$wcan bbox $idx] 2316 if {[BboxInsideScroll $wcan $bbox]} { 2317 eval $cmdReal 2318 } 2319 } 2320 set rotDrag(lastAng) $angle 2321} 2322 2323# CanvasDraw::FinalizeRotate -- 2324# 2325# Finalizes the rotation operation. Tells all other clients. 2326# 2327# Arguments: 2328# wcan the canvas widget. 2329# x,y the mouse coordinates. 2330# 2331# Results: 2332# none 2333 2334proc ::CanvasDraw::FinalizeRotate {wcan x y} { 2335 global kRad2Grad 2336 variable rotDrag 2337 2338 if {![info exists rotDrag]} { 2339 return 2340 } 2341 set w [winfo toplevel $wcan] 2342 $wcan delete $rotDrag(idx) 2343 2344 # Move all markers along. 2345 set id $rotDrag(id) 2346 set utag $rotDrag(utag) 2347 $wcan delete id$id 2348 MarkBbox $wcan 0 $id 2349 if {[string equal $rotDrag(type) "arc"]} { 2350 2351 # Get new start angle. 2352 set start [$wcan itemcget $id -start] 2353 set cmd [list itemconfigure $utag -start $start] 2354 } else { 2355 # Or update all coordinates. 2356 set cmd [concat coords $utag [$wcan coords $utag]] 2357 } 2358 set undocmd $rotDrag(undocmd) 2359 set redo [list ::CanvasUtils::Command $w $cmd] 2360 set undo [list ::CanvasUtils::Command $w $undocmd] 2361 ::CanvasUtils::Command $w $cmd remote 2362 undo::add [::WB::GetUndoToken $wcan] $undo $redo 2363 ::CanvasFile::SetUnsaved $wcan 2364 unset -nocomplain rotDrag 2365} 2366 2367#--- End of rotate tool -------------------------------------------------------- 2368 2369namespace eval ::CanvasDraw:: { 2370 2371 variable itemImagesDeleted {} 2372} 2373 2374# CanvasDraw::DeleteCurrent -- 2375# 2376# Bindings to the 'std' tag. 2377 2378proc ::CanvasDraw::DeleteCurrent {wcan} { 2379 2380 set utag [::CanvasUtils::GetUtag $wcan current] 2381 if {$utag ne ""} { 2382 DeleteIds $wcan $utag all 2383 } 2384} 2385 2386proc ::CanvasDraw::DeleteSelected {wcan} { 2387 2388 set ids [$wcan find withtag selected&&!locked] 2389 if {$ids == {}} { 2390 return 2391 } 2392 DeleteIds $wcan $ids all 2393 ::CanvasCmd::DeselectAll $wcan 2394} 2395 2396# CanvasDraw::DeleteIds -- 2397# 2398# 2399 2400proc ::CanvasDraw::DeleteIds {wcan ids where args} { 2401 global prefs this 2402 variable itemImagesDeleted 2403 2404 ::Debug 6 "::CanvasDraw::DeleteIds ids=$ids" 2405 2406 array set argsArr { 2407 -trashunusedimages 1 2408 } 2409 array set argsArr $args 2410 set trashImages $argsArr(-trashunusedimages) 2411 set w [winfo toplevel $wcan] 2412 2413 # List of canvas commands without widget path. 2414 set cmdList {} 2415 2416 # List of complete commands. 2417 set redoCmdList {} 2418 set undoCmdList {} 2419 2420 foreach id $ids { 2421 set utag [::CanvasUtils::GetUtag $wcan $id] 2422 if {$utag eq ""} { 2423 continue 2424 } 2425 set tags [$wcan gettags $id] 2426 set type [$wcan type $id] 2427 set havestd [expr {[lsearch -exact $tags std] < 0 ? 0 : 1}] 2428 2429 # We are only allowed to delete 'std' items. 2430 switch -glob -- $type,$havestd { 2431 image,1 { 2432 set cmd [list delete $utag] 2433 lappend cmdList $cmd 2434 lappend undoCmdList [::CanvasUtils::GetUndoCommand $w $cmd] 2435 if {$trashImages} { 2436 lappend itemImagesDeleted [$wcan itemcget $id -image] 2437 } 2438 } 2439 window,* { 2440 set cmd [list delete $utag] 2441 lappend cmdList $cmd 2442 set win [$wcan itemcget $utag -window] 2443 lappend redoCmdList [list destroy $win] 2444 lappend undoCmdList [::CanvasUtils::GetUndoCommand $w $cmd] 2445 } 2446 *,1 { 2447 set cmd [list delete $utag] 2448 lappend cmdList $cmd 2449 lappend undoCmdList [::CanvasUtils::GetUndoCommand $w $cmd] 2450 } 2451 default { 2452 2453 # A non window item witout 'std' tag. 2454 # Look for any Itcl object with a Delete method. 2455 if {$this(package,Itcl)} { 2456 if {[regexp {object:([^ ]+)} $tags match object]} { 2457 if {![catch { 2458 set objdel [$object Delete $id] 2459 }]} { 2460 if {[llength $objdel] == 2} { 2461 lassign $objdel del undo 2462 if {$del != {}} { 2463 lappend cmdList $del 2464 if {$undo != {}} { 2465 lappend undoCmdList $undo 2466 } 2467 } 2468 } 2469 } 2470 } 2471 } 2472 } 2473 } 2474 } 2475 2476 # Manufacture complete commands. 2477 set canRedo [list ::CanvasUtils::CommandList $w $cmdList $where] 2478 set redo [list ::CanvasDraw::EvalCommandList \ 2479 [concat [list $canRedo] $redoCmdList]] 2480 set undo [list ::CanvasDraw::EvalCommandList $undoCmdList] 2481 2482 eval $redo 2483 undo::add [::WB::GetUndoToken $wcan] $undo $redo 2484 ::CanvasFile::SetUnsaved $wcan 2485 2486 # Garbage collect unused images with 'std' tag. 2487 GarbageUnusedImages 2488} 2489 2490# CanvasDraw::GarbageUnusedImages -- 2491# 2492# Handle image garbage collection for 'std' image items. 2493# Only for deleted ones. Else see Whiteboard.tcl 2494 2495proc ::CanvasDraw::GarbageUnusedImages { } { 2496 variable itemImagesDeleted 2497 2498 # Image garbage collection. TEST! 2499 set ims {} 2500 foreach name [lsort -unique $itemImagesDeleted] { 2501 if {![image inuse $name]} { 2502 lappend ims $name 2503 } 2504 } 2505 eval {image delete} $ims 2506 set itemImagesDeleted {} 2507} 2508 2509proc ::CanvasDraw::AddGarbageImages {name args} { 2510 variable itemImagesDeleted 2511 2512 eval {lappend itemImagesDeleted $name} $args 2513} 2514 2515# CanvasDraw::DeleteFrame -- 2516# 2517# Generic binding for deleting a frame that typically contains 2518# something from a plugin. 2519# Note that this is trigger by the frame's event handler and not the 2520# canvas! 2521# 2522# Arguments: 2523# wcan 2524# wframe the frame widget. 2525# x,y the mouse coordinates. 2526# where "all": erase this canvas and all others. 2527# "remote": erase only client canvases. 2528# "local": erase only own canvas. 2529# 2530# Results: 2531# none 2532 2533proc ::CanvasDraw::DeleteFrame {wcan wframe x y {where all}} { 2534 2535 ::Debug 2 "::CanvasDraw::DeleteFrame wframe=$wframe, x=$x, y=$y" 2536 2537 # Fix x and y (frame to canvas coordinates). 2538 set x [$wcan canvasx [expr {[winfo x $wframe] + $x]}] 2539 set y [$wcan canvasx [expr {[winfo y $wframe] + $y]}] 2540 set w [winfo toplevel $wcan] 2541 set cmdList {} 2542 set canUndoList {} 2543 set undoCmdList {} 2544 2545 set utag [::CanvasUtils::GetUtagFromWindow $wframe] 2546 if {$utag eq ""} { 2547 return 2548 } 2549 2550 # Delete both the window item and the window (with subwindows). 2551 lappend cmdList [list delete $utag] 2552 set extraCmd [list destroy $wframe] 2553 2554 set redo [list ::CanvasUtils::CommandList $w $cmdList $where] 2555 set redo [list ::CanvasDraw::EvalCommandList [list $redo $extraCmd]] 2556 2557 # We need to reconstruct how it was imported. 2558 set undo [::CanvasUtils::GetUndoCommand $w [list delete $utag]] 2559 eval $redo 2560 undo::add [::WB::GetUndoToken $wcan] $undo $redo 2561 ::CanvasFile::SetUnsaved $wcan 2562} 2563 2564# CanvasDraw::DeleteWindow -- 2565# 2566# Generic binding for deleting a window that typically contains 2567# something from a plugin. 2568# 2569# Arguments: 2570# wcan 2571# win the frame widget. 2572# x,y the mouse coordinates. 2573# where "all": erase this canvas and all others. 2574# "remote": erase only client canvases. 2575# "local": erase only own canvas. 2576# 2577# Results: 2578# none 2579 2580proc ::CanvasDraw::DeleteWindow {wcan win x y {where all}} { 2581 2582 ::Debug 2 "::CanvasDraw::DeleteWindow win=$win, x=$x, y=$y" 2583 2584 # Fix x and y (frame to canvas coordinates). 2585 set x [$wcan canvasx [expr {[winfo x $win] + $x]}] 2586 set y [$wcan canvasx [expr {[winfo y $win] + $y]}] 2587 set w [winfo toplevel $wcan] 2588 set cmdList {} 2589 set canUndoList {} 2590 set undoCmdList {} 2591 2592 set utag [::CanvasUtils::GetUtagFromWindow $win] 2593 if {$utag eq ""} { 2594 return 2595 } 2596 2597 # Delete both the window item and the window (with subwindows). 2598 lappend cmdList [list delete $utag] 2599 set extraCmd [list destroy $win] 2600 2601 set redo [list ::CanvasUtils::CommandList $w $cmdList $where] 2602 set redo [list ::CanvasDraw::EvalCommandList [list $redo $extraCmd]] 2603 2604 # We need to reconstruct how it was imported. 2605 set undo [::CanvasUtils::GetUndoCommand $w [list delete $utag]] 2606 eval $redo 2607 undo::add [::WB::GetUndoToken $wcan] $undo $redo 2608 ::CanvasFile::SetUnsaved $wcan 2609} 2610 2611proc ::CanvasDraw::PointButton {wcan x y {modifier {}}} { 2612 2613 if {[string equal $modifier "shift"]} { 2614 MarkBbox $wcan 1 2615 } else { 2616 MarkBbox $wcan 0 2617 } 2618} 2619 2620# CanvasDraw::MarkBbox -- 2621# 2622# Administrates a selection, drawing, ui etc. 2623# 2624# Arguments: 2625# wcan the canvas widget. 2626# shift If 'shift', then just select item, else deselect all 2627# other first. 2628# which can either be "current", another tag, or an id. 2629# 2630# Results: 2631# none 2632 2633proc ::CanvasDraw::MarkBbox {wcan shift {which current}} { 2634 global prefs kGrad2Rad 2635 2636 Debug 4 "MarkBbox:: wcan=$wcan, shift=$shift, which=$which" 2637 2638 set w [winfo toplevel $wcan] 2639 2640 # If no shift key, deselect all. 2641 if {$shift == 0} { 2642 ::CanvasCmd::DeselectAll $wcan 2643 } 2644 set id [$wcan find withtag $which] 2645 if {$id eq ""} { 2646 return 2647 } 2648 set utag [::CanvasUtils::GetUtag $wcan $which] 2649 if {$utag eq ""} { 2650 return 2651 } 2652 if {[lsearch [$wcan gettags $id] "std"] < 0} { 2653 return 2654 } 2655 2656 # If already selected, and shift clicked, deselect. 2657 if {$shift == 1} { 2658 if {[IsSelected $wcan $id]} { 2659 $wcan delete tbbox&&id:${id} 2660 $wcan dtag $id selected 2661 return 2662 } 2663 } 2664 SelectItem $wcan $which 2665 focus $wcan 2666 2667 # Testing.. 2668 selection own -command [list ::CanvasDraw::LostSelection $w] $wcan 2669} 2670 2671proc ::CanvasDraw::SelectItem {wcan which} { 2672 2673 # Add tag 'selected' to the selected item. Indicate to which item id 2674 # a marker belongs with adding a tag 'id$id'. 2675 set type [$wcan type $which] 2676 $wcan addtag "selected" withtag $which 2677 set id [$wcan find withtag $which] 2678 if {[::CanvasUtils::IsLocked $wcan $id]} { 2679 set tmark [list tbbox $type id:${id} locked] 2680 } else { 2681 set tmark [list tbbox $type id:${id}] 2682 } 2683 DrawItemSelection $wcan $which $tmark 2684} 2685 2686proc ::CanvasDraw::DeselectItem {wcan which} { 2687 2688 set id [$wcan find withtag $which] 2689 $wcan delete tbbox&&id:${id} 2690 $wcan dtag $id selected 2691} 2692 2693proc ::CanvasDraw::DeleteSelection {wcan which} { 2694 2695 set id [$wcan find withtag $which] 2696 $wcan delete tbbox&&id:${id} 2697 $wcan dtag $id selected 2698} 2699 2700proc ::CanvasDraw::IsSelected {wcan which} { 2701 2702 return [expr {[lsearch [$wcan gettags $which] "selected"] < 0 ? 0 : 1}] 2703} 2704 2705proc ::CanvasDraw::AnySelected {wcan} { 2706 2707 return [expr {[$wcan find withtag "selected"] eq "" ? 0 : 1}] 2708} 2709 2710# CanvasDraw::DrawItemSelection -- 2711# 2712# Does the actual drawing of any selection. 2713 2714proc ::CanvasDraw::DrawItemSelection {wcan which tmark} { 2715 global prefs kGrad2Rad 2716 2717 set type [$wcan type $which] 2718 set bbox [$wcan bbox $which] 2719 set id [$wcan find withtag $which] 2720 2721 set w [winfo toplevel $wcan] 2722 set a [option get $w aSelect {}] 2723 if {[::CanvasUtils::IsLocked $wcan $id]} { 2724 set fg [option get $w fgSelectLocked {}] 2725 } else { 2726 set fg [option get $w fgSelectNormal {}] 2727 } 2728 2729 # If mark the bounding box. Also for all "regular" shapes. 2730 2731 if {$prefs(bboxOrCoords) || ($type eq "oval") || ($type eq "text") \ 2732 || ($type eq "rectangle") || ($type eq "image")} { 2733 2734 foreach {x1 y1 x2 y2} $bbox break 2735 $wcan create rectangle [expr {$x1-$a}] [expr {$y1-$a}] [expr {$x1+$a}] [expr {$y1+$a}] \ 2736 -tags $tmark -fill white -outline $fg 2737 $wcan create rectangle [expr {$x1-$a}] [expr {$y2-$a}] [expr {$x1+$a}] [expr {$y2+$a}] \ 2738 -tags $tmark -fill white -outline $fg 2739 $wcan create rectangle [expr {$x2-$a}] [expr {$y1-$a}] [expr {$x2+$a}] [expr {$y1+$a}] \ 2740 -tags $tmark -fill white -outline $fg 2741 $wcan create rectangle [expr {$x2-$a}] [expr {$y2-$a}] [expr {$x2+$a}] [expr {$y2+$a}] \ 2742 -tags $tmark -fill white -outline $fg 2743 } else { 2744 2745 set coords [$wcan coords $which] 2746 if {[string length $coords] == 0} { 2747 return 2748 } 2749 set n [llength $coords] 2750 2751 # For an arc item, mark start and stop endpoints. 2752 # Beware, mixes of two coordinate systems, y <-> -y. 2753 if {[string equal $type "arc"]} { 2754 if {$n != 4} { 2755 return 2756 } 2757 foreach {x1 y1 x2 y2} $coords break 2758 set r [expr {abs(($x1 - $x2)/2.0)}] 2759 set cx [expr {($x1 + $x2)/2.0}] 2760 set cy [expr {($y1 + $y2)/2.0}] 2761 set startAng [$wcan itemcget $id -start] 2762 set extentAng [$wcan itemcget $id -extent] 2763 set xstart [expr {$cx + $r * cos($kGrad2Rad * $startAng)}] 2764 set ystart [expr {$cy - $r * sin($kGrad2Rad * $startAng)}] 2765 set xfin [expr {$cx + $r * cos($kGrad2Rad * ($startAng + $extentAng))}] 2766 set yfin [expr {$cy - $r * sin($kGrad2Rad * ($startAng + $extentAng))}] 2767 $wcan create rectangle [expr {$xstart-$a}] [expr {$ystart-$a}] \ 2768 [expr {$xstart+$a}] [expr {$ystart+$a}] -tags $tmark -fill white \ 2769 -outline $fg 2770 $wcan create rectangle [expr {$xfin-$a}] [expr {$yfin-$a}] \ 2771 [expr {$xfin+$a}] [expr {$yfin+$a}] -tags $tmark -fill white \ 2772 -outline $fg 2773 2774 } else { 2775 2776 # Mark each coordinate. {x0 y0 x1 y1 ... } 2777 foreach {x y} $coords { 2778 $wcan create rectangle [expr {$x-$a}] [expr {$y-$a}] [expr {$x+$a}] [expr {$y+$a}] \ 2779 -tags $tmark -fill white -outline $fg 2780 } 2781 } 2782 } 2783} 2784 2785# CanvasDraw::LostSelection -- 2786# 2787# Lost selection to other window. Deselect only if same toplevel. 2788 2789proc ::CanvasDraw::LostSelection {w} { 2790 2791 if {$w == [selection own]} { 2792 #::CanvasCmd::DeselectAll $wcan 2793 } 2794} 2795 2796proc ::CanvasDraw::SyncMarks {wcan} { 2797 2798 $wcan delete withtag tbbox 2799 foreach id [$wcan find withtag "selected"] { 2800 MarkBbox $wcan 1 $id 2801 } 2802} 2803 2804#--- Various assistant procedures ---------------------------------------------- 2805 2806# CanvasDraw::ToScroll -- 2807# 2808# Confine movement to the canvas scrollregion. 2809# 2810# Arguments: 2811# wcan the canvas widget. 2812# tag 2813# x0,y0 present "mouse point" 2814# x,y the mouse coordinates. 2815# type item type (rectangle, oval, ...). 2816# 2817# Results: 2818# none 2819 2820proc ::CanvasDraw::ToScroll {wcan tag x0 y0 x y} { 2821 2822 # @@@ In order to speed up things we could get this at init move and 2823 # update it ourselves. 2824 set bbox [$wcan bbox $tag] 2825 set scroll [$wcan cget -scrollregion] 2826 set inset [$wcan cget -highlightthickness] 2827 lassign $bbox X0 Y0 X1 Y1 2828 lassign $scroll XS0 YS0 XS1 YS1 2829 2830 set dx [expr {$x - $x0}] 2831 set dy [expr {$y - $y0}] 2832 2833 if {$dx < 0} { 2834 if {($X0 < 0) || ([expr {$dx + $X0}] < 0)} { 2835 set x [expr {$x0 - $X0}] 2836 } 2837 } elseif {$dx > 0} { 2838 if {($X1 > $XS1) || ([expr {$dx + $X1}] > $XS1)} { 2839 set x [expr {$x0 + $XS1 - $X1}] 2840 } 2841 } 2842 if {$dy < 0} { 2843 if {($Y0 < 0) || ([expr {$dy + $Y0}] < 0)} { 2844 set y [expr {$y0 - $Y0}] 2845 } 2846 } elseif {$dy > 0} { 2847 if {($Y1 > $YS1) || ([expr {$dy + $Y1}] > $YS1)} { 2848 set y [expr {$y0 + $YS1 - $Y1}] 2849 } 2850 } 2851 return [list $x $y] 2852} 2853 2854proc ::CanvasDraw::XYToScroll {wcan x y} { 2855 2856 set scroll [$wcan cget -scrollregion] 2857 lassign $scroll X0 Y0 X1 Y1 2858 set x [expr {$x < $X0 ? $X0 : $x}] 2859 set y [expr {$y < $Y0 ? $Y0 : $y}] 2860 set x [expr {$x > $X1 ? $X1 : $x}] 2861 set y [expr {$y > $Y1 ? $Y1 : $y}] 2862 return [list $x $y] 2863} 2864 2865proc ::CanvasDraw::ItemInsideScroll {wcan tag} { 2866 2867 return [BboxInsideScroll $wcan [$wcan bbox $tag]] 2868} 2869 2870proc ::CanvasDraw::BboxInsideScroll {wcan bbox} { 2871 2872 set scroll [$wcan cget -scrollregion] 2873 set inset [$wcan cget -highlightthickness] 2874 lassign $bbox X0 Y0 X1 Y1 2875 lassign $scroll XS0 YS0 XS1 YS1 2876 2877 if {$X0 < $XS0} { 2878 return 0 2879 } elseif {$X1 > $XS1} { 2880 return 0 2881 } elseif {$Y0 < $XS0} { 2882 return 0 2883 } elseif {$Y1 > $YS1} { 2884 return 0 2885 } else { 2886 return 1 2887 } 2888} 2889 2890proc ::CanvasDraw::ResizeBbox {bbox add} { 2891 2892 lassign $bbox X0 Y0 X1 Y1 2893 return [list \ 2894 [expr {$X0-$add}] [expr {$Y0-$add}] \ 2895 [expr {$X1+$add}] [expr {$Y1+$add}]] 2896} 2897 2898# CanvasDraw::ConstrainedDrag -- 2899# 2900# Compute new x and y coordinates constrained to 90 or 45 degree 2901# intervals. 2902# 2903# Arguments: 2904# xanch,yanch the anchor coordinates. 2905# x,y the mouse coordinates. 2906# 2907# Results: 2908# List of new x and y coordinates. 2909 2910proc ::CanvasDraw::ConstrainedDrag {x y xanch yanch} { 2911 global prefs kTan225 kTan675 2912 2913 # Constrain movements to 90 degree intervals. 2914 if {!$prefs(45)} { 2915 if {[expr {abs($x - $xanch)}] > [expr {abs($y - $yanch)}]} { 2916 set y $yanch 2917 } else { 2918 set x $xanch 2919 } 2920 return [list $x $y] 2921 } else { 2922 2923 # 45 degree intervals. 2924 set deltax [expr {int($x - $xanch)}] 2925 set deltay [expr {int($y - $yanch)}] 2926 if {[expr {abs($deltay/($deltax + 0.5))}] <= $kTan225} { 2927 2928 # constrain to x-axis. 2929 set y $yanch 2930 return [list $x $y] 2931 } elseif {[expr {abs($deltay/($deltax + 0.5))}] >= $kTan675} { 2932 2933 # constrain to y-axis. 2934 set x $xanch 2935 return [list $x $y] 2936 } else { 2937 2938 # Do the same analysis in the coordinate system rotated 45 degree CCW. 2939 set deltaxprim [expr {1./sqrt(2.0) * ($deltax + $deltay)}] 2940 set deltayprim [expr {1./sqrt(2.0) * (-$deltax + $deltay)}] 2941 if {[expr {abs($deltayprim/($deltaxprim + 0.5))}] <= $kTan225} { 2942 2943 # constrain to x'-axis. 2944 set x [expr {$xanch + ($deltax + $deltay)/2.0}] 2945 set y [expr {$yanch + $x - $xanch}] 2946 } else { 2947 2948 # constrain to y'-axis. 2949 set y [expr {$yanch + (-$deltax + $deltay)/2.0}] 2950 set x [expr {$xanch - $y + $yanch}] 2951 } 2952 return [list $x $y] 2953 } 2954 } 2955} 2956 2957# CanvasDraw::MakeSpeechBubble, SpeechBubbleCmd -- 2958# 2959# Makes and draws a speech bubble for a text item. 2960 2961proc ::CanvasDraw::MakeSpeechBubble {wcan id} { 2962 2963 set w [winfo toplevel $wcan] 2964 set bbox [$wcan bbox $id] 2965 set utagtext [::CanvasUtils::GetUtag $wcan $id] 2966 foreach {utag redocmd} [::CanvasDraw::SpeechBubbleCmd $wcan $bbox] break 2967 set undocmd [list delete $utag] 2968 set cmdLower [list lower $utag $utagtext] 2969 2970 set redo [list ::CanvasUtils::CommandList $w [list $redocmd $cmdLower]] 2971 set undo [list ::CanvasUtils::Command $w $undocmd] 2972 eval $redo 2973 undo::add [::WB::GetUndoToken $wcan] $undo $redo 2974 ::CanvasFile::SetUnsaved $wcan 2975} 2976 2977proc ::CanvasDraw::SpeechBubbleCmd {wcan bbox args} { 2978 2979 set a 8 2980 set b 12 2981 set c 40 2982 set d 20 2983 foreach {left top right bottom} $bbox break 2984 set midw [expr {($right+$left)/2.0}] 2985 set midh [expr {($bottom+$top)/2.0}] 2986 set coords [list \ 2987 [expr {$left-$a}] [expr {$top-$a}] \ 2988 $midw [expr {$top-$b}] \ 2989 [expr {$right+$a}] [expr {$top-$a}] \ 2990 [expr {$right+$b}] $midh \ 2991 [expr {$right+$a}] [expr {$bottom+$a}] \ 2992 [expr {$right+$a}] [expr {$bottom+$c}] \ 2993 [expr {$right+$a}] [expr {$bottom+$c}] \ 2994 [expr {$right-$d+10}] [expr {$bottom+$a}] \ 2995 [expr {$right-$d}] [expr {$bottom+$a}] \ 2996 $midw [expr {$bottom+$b}] \ 2997 [expr {$left-$a}] [expr {$bottom+$a}] \ 2998 [expr {$left-$b}] $midh \ 2999 ] 3000 array set optsArr {-outline black -fill white -smooth 1 -splinesteps 10} 3001 array set optsArr $args 3002 set utag [::CanvasUtils::NewUtag] 3003 set cmd "create polygon $coords -tags {std polygon $utag} [array get optsArr]" 3004 return [list $utag $cmd] 3005} 3006 3007# CanvasDraw::StripClosePoints -- 3008# 3009# Removes points that are closer than 'd'. 3010# 3011# Arguments: 3012# coords list of coordinates {x0 y0 x1 y1 ...} 3013# dmax maximum allowed distance 3014# 3015# Results: 3016# list of new coordinates 3017 3018proc ::CanvasDraw::StripClosePoints {coords dmax} { 3019 3020 set len [llength $coords] 3021 if {$len < 6} { 3022 return $coords 3023 } 3024 set tmp [lrange $coords 0 1] 3025 for {set i1 0; set i2 2} {$i2 < $len} { } { 3026 foreach {x1 y1} [lrange $coords $i1 [expr {$i1+1}]] break 3027 foreach {x2 y2} [lrange $coords $i2 [expr {$i2+1}]] break 3028 set d [expr {hypot($x2-$x1, $y2-$y1)}] 3029 3030 if {$i2 < [expr {$len - 2}]} { 3031 3032 # To accept or not to accept. 3033 if {$d < $dmax} { 3034 incr i2 2 3035 } else { 3036 lappend tmp $x2 $y2 3037 set i1 $i2 3038 incr i2 2 3039 } 3040 } else { 3041 3042 # Last point. 3043 if {$d < $dmax} { 3044 set tmp [lreplace $tmp end-1 end $x2 $y2] 3045 } else { 3046 lappend tmp $x2 $y2 3047 } 3048 incr i2 2 3049 } 3050 } 3051 return $tmp 3052} 3053 3054proc ::CanvasDraw::GetDistList {coords} { 3055 3056 set dlist {} 3057 set len [llength $coords] 3058 for {set i1 0; set i2 2} {$i2 < $len} {incr i1 2; incr i2 2} { 3059 foreach {x1 y1} [lrange $coords $i1 [expr {$i1+1}]] break 3060 foreach {x2 y2} [lrange $coords $i2 [expr {$i2+1}]] break 3061 lappend dlist [expr {hypot($x2-$x1, $y2-$y1)}] 3062 } 3063 return $dlist 3064} 3065 3066# CanvasDraw::StripExtremeRadius -- 3067# 3068# Strip points that form triplets with radius outside 'rmin' and 'rmax'. 3069# 3070# Arguments: 3071# coords list of coordinates {x0 y0 x1 y1 ...} 3072# rmin 3073# rmax 3074# 3075# Results: 3076# list of new coordinates 3077 3078proc ::CanvasDraw::StripExtremeRadius {coords rmin rmax} { 3079 3080 set len [llength $coords] 3081 if {$len < 8} { 3082 return $coords 3083 } 3084 set tmp [lrange $coords 0 1] 3085 for {set i1 0; set i2 2; set i3 4} {$i3 < $len} { } { 3086 foreach {x1 y1} [lrange $coords $i1 [expr {$i1+1}]] break 3087 foreach {x2 y2} [lrange $coords $i2 [expr {$i2+1}]] break 3088 foreach {x3 y3} [lrange $coords $i3 [expr {$i3+1}]] break 3089 set r [ThreePointRadius [list $x1 $y1 $x2 $y2 $x3 $y3]] 3090 3091 if {$i2 < [expr {$len - 4}]} { 3092 3093 # To accept or not to accept. 3094 if {($r > $rmax) || ($r < $rmin)} { 3095 incr i2 2 3096 incr i3 2 3097 } else { 3098 lappend tmp $x2 $y2 3099 set i1 $i2 3100 set i2 $i3 3101 incr i3 2 3102 } 3103 } else { 3104 3105 # Last point. 3106 set tmp [concat $tmp [lrange $coords end-1 end]] 3107 incr i3 2 3108 } 3109 } 3110 return $tmp 3111} 3112 3113proc ::CanvasDraw::GetRadiusList {coords} { 3114 3115 set rlist {} 3116 set imax [expr {[llength $coords] - 4}] 3117 for {set i 0} {$i < $imax} {incr i 2} { 3118 lappend rlist [ThreePointRadius \ 3119 [lrange $coords $i [expr {$i + 5}]]] 3120 } 3121 return $rlist 3122} 3123 3124# CanvasDraw::ThreePointRadius -- 3125# 3126# Computes the radius of a circle that goes through three nonidentical 3127# points. 3128# 3129# Arguments: 3130# p list {x1 y1 x2 y2 x3 y3} of three points 3131# 3132# Results: 3133# radius 3134 3135proc ::CanvasDraw::ThreePointRadius {p} { 3136 3137 foreach {x1 y1 x2 y2 x3 y3} $p break 3138 set a [expr {$x1 - $x2}] 3139 set b [expr {$y1 - $y2}] 3140 set c [expr {$x1 - $x3}] 3141 set d [expr {$y1 - $y3}] 3142 set e [expr {0.5 * ($x1*$x1 + $y1*$y1 - ($x2*$x2 + $y2*$y2))}] 3143 set f [expr {0.5 * ($x1*$x1 + $y1*$y1 - ($x3*$x3 + $y3*$y3))}] 3144 set det [expr {$a*$d - $b*$c}] 3145 if {[expr {abs($det)}] < 1e-16} { 3146 3147 # Straight line. 3148 return 1e+16 3149 } 3150 set rx [expr {($d*$e - $b*$f)/$det}] 3151 set ry [expr {($a*$f - $c*$e)/$det}] 3152 set dx [expr {$rx - $x1}] 3153 set dy [expr {$ry - $y1}] 3154 return [expr {sqrt($dx*$dx + $dy*$dy)}] 3155} 3156 3157# CanvasDraw::EvalCommandList -- 3158# 3159# A utility function to evaluate more than a single command. 3160# Useful for the undo/redo implementation. 3161 3162proc ::CanvasDraw::EvalCommandList {cmdList} { 3163 3164 foreach cmd $cmdList { 3165 eval $cmd 3166 } 3167} 3168 3169#------------------------------------------------------------------------------- 3170