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