File: canvas_epoints.tcl

package info (click to toggle)
tklib 0.6%2B20190108-1
  • links: PTS, VCS
  • area: main
  • in suites: buster
  • size: 15,008 kB
  • sloc: tcl: 75,757; sh: 5,789; ansic: 792; pascal: 359; makefile: 70; sed: 53; exp: 21
file content (362 lines) | stat: -rw-r--r-- 10,374 bytes parent folder | download | duplicates (5)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
## -*- tcl -*-
# # ## ### ##### ######## ############# #####################

# Canvas Behavior Module. Editing a point cloud.

# - Create    point - B1 (canvas global)
# - Remove    point - B2 (linked to -tag, current item)
# - Drag/Move point - B3 (linked to -tag, current item)
# - Auto-highlight points, to show ability of drag/move.

# Configurable:
# - Tag used to mark/identify the points of this cloud.
#   Default: POINT.
#
# - Callback used to create the item (group) representing the point.
#   Default: Single blue circle of radius 3 with center at point location.
#
# - Callback used to (un)highlight the item (group) of a point.
#   Default: Switch to red color.
#
# - Callback used to record editing activity (add, remove, move point)
#   Default: NONE.

# # ## ### ##### ######## ############# #####################
## Requisites

package require Tcl 8.5
package require Tk
package require snit
package require canvas::drag
package require canvas::highlight
package require canvas::tag

namespace eval ::canvas::edit {
    namespace export points
    namespace ensemble create
}

# # ## ### ##### ######## ############# #####################
## API

snit::type ::canvas::edit::points {
    # # ## ### ##### ######## ############# #####################
    ## Life cycle, and configuration

    option -tag           -default POINT -readonly 1 ; # Tag identifying our points
    option -create-cmd    -default {}    -readonly 1 ; # Callback invoked to create new points.
    option -highlight-cmd -default {}    -readonly 1 ; # Callback to highlight a dragged point.
    option -data-cmd      -default {}    -readonly 1 ; # Callback for point edit operations

    constructor {c args} {
	set mycanvas $c
	set options(-create-cmd)    [mymethod DefaultCreate]
	set options(-highlight-cmd) [mymethod DefaultHighlight]

	$self configurelist $args

	# TODO :: Connect this to the option processing to allow me to
	# drop -readonly 1 from their definition. Note that this also
	# requires code to re-tag all the items on the fly.
	$self Bindings Add
	return
    }

    destructor {
	$self Bindings Remove
	return
    }

    # # ## ### ##### ######## ############# #####################
    ## API.

    method disable {} {
	$self Bindings Remove
	return
    }

    method enable {} {
	$self Bindings Add
	return
    }

    method active {} {
	return $myactive
    }

    method add {x y} {
	# Create a point marker programmatically. This enables users
	# to load an editor instance with existing point locations.
	$self Add $mycanvas $x $y
	return
    }

    ###### Destroy an existing point
    method clear {} {
	foreach item [$mycanvas find withtag $options(-tag)] {
	    lappend grouptags [GetId $mycanvas $item]
	}
	foreach grouptag [lsort -unique $grouptags] {
	    $mycanvas delete $grouptag
	    #puts "Remove|$x $y|$grouptag"
	    unset myloc($grouptag)
	    Note remove $grouptag
	}
	return
    }

    # # ## ### ##### ######## ############# #####################
    ## Manage the canvas bindings (point creation and removal,
    ## dragging, highlighting).

    method {Bindings Add} {} {
	if {$myactive} return
	set myactive 1

	canvas::highlight on $mycanvas $options(-tag) [mymethod Highlight]
	canvas::drag      on $mycanvas $options(-tag) [mymethod Drag]

	$mycanvas bind $options(-tag) <2> [mymethod Remove $mycanvas %x %y]
	bind $mycanvas                <1> [mymethod Add    $mycanvas %x %y]

	# NOTES:
	# 1. Is there a way to make 'Add' not canvas global ?
	# 2. If not, is there a way to ensure that 'Add' is not
	# triggered when a 'Remove' is done, even if the events for
	# the 2 actions basically overlap (B1=Add, Shift-B1=Remove,
	# for example) ?
	return
    }

    method {Bindings Remove} {} {
	if {!$myactive} return
	set myactive 0

	canvas::highlight off $mycanvas $options(-tag)
	canvas::drag      off $mycanvas $options(-tag)

	$mycanvas bind $options(-tag) <2> {}
	bind $mycanvas                <1> {}
	return
    }

    # # ## ### ##### ######## ############# #####################
    ## The actions invoked by the bindings managed in the previous
    ## section.

    ###### Place new point
    method Add {c x y} {
	$self CheckCanvas $c
	set grouptag [NewId]
	set items [{*}$options(-create-cmd) $c $x $y]
	# No visual representation of the point, no point. Vetoed.
	if {![llength $items]} return

	Tag $c $items $grouptag
	set myloc($grouptag) [list $x $y]
	#puts "Add|$x $y|$items"
	Note add $grouptag $x $y
	return
    }

    ###### Destroy an existing point
    method Remove {c x y} {
	$self CheckCanvas $c
	set grouptag [GetId $c [$c find withtag current]]
	$c delete $grouptag
	#puts "Remove|$x $y|$grouptag"
	unset myloc($grouptag)
	Note remove $grouptag
	return
    }

    ###### Drag management. On start of a drag ... Identify the group of items to move.
    method {Drag start} {c item} {
	$self CheckCanvas $c
	#puts "Drag Start|$item|"
	set mydragactive 1
	set grouptag [GetId $c $item]
	set mydbox [$c bbox $grouptag]
	Note {move start} $grouptag
	return $grouptag
    }

    ###### Drag management. During a drag ... Move the grouped items.
    method {Drag move} {c grouptag dx dy} {
	$self CheckCanvas $c
	#puts "Drag Move|$grouptag|$dx $dy|"
	$c move $grouptag $dx $dy
	lassign [Delta] px py dx dy
	Note {move delta} $grouptag $px $py $dx $dy
	return $grouptag
    }

    ###### Drag management. After a drag ...
    method {Drag done} {c grouptag} {
	$self CheckCanvas $c
	#puts "Drag Done|$grouptag|"
	set mydragactive 0
	set ok [Note {move done} $grouptag]
	lassign [Delta] px py dx dy
	if {$ok} {
	    # Commit to new location.
	    set myloc($grouptag) [list $px $py]
	} else {
	    # Vetoed. Undo the move.
	    set dx [expr {- $dx}]
	    set dy [expr {- $dy}]
	    $c move $grouptag $dx $dy
	}
	return
    }

    ###### Highlight management ... Start. Note! Not the user callback.
    method {Highlight on} {c item} {
	$self CheckCanvas $c
	return [{*}$options(-highlight-cmd) on $c $item]
    }

    ###### Highlight management ... Done. Vetoed during drag.
    method {Highlight off} {c state} {
	$self CheckCanvas $c
	if {$mydragactive} { return 0 }
	{*}$options(-highlight-cmd) off $c $state
	return 1
    }

    # # ## ### ##### ######## ############# #####################
    ## Class global commands for the actions in the previous section.

    #### Generate notification about changes to the point cloud.

    proc Note {cmd args} {
	upvar 1 options options self self
	if {![llength $options(-data-cmd)]} return
	return [{*}$options(-data-cmd) {*}$cmd $self {*}$args]
    }

    #### Generate a unique tag for a new point.
    #### The tag references editor instance and type

    proc NewId {} {
	upvar 1 mycounter mycounter self self type type
	return P[incr mycounter]/$self/$type
    }

    #### Link both the unique tag for a point marker and the overall
    #### tag identifying the markers managed by an editor to the
    #### canvas items visually representing the marker.

    proc Tag {c items grouptag} {
	upvar 1 options options
	foreach i $items {
	    canvas::tag append $c $i \
		$grouptag \
		$options(-tag)
	}
	return
    }

    #### Retrieve the tag of the point marker from any item which is
    #### part of its visual representation.

    proc GetId {c item} {
	upvar 1 self self type type
	return [lindex [canvas::tag match $c $item */$self/$type] 0]
    }

    #### Compute absolute location and full delta from current and
    #### saved bounding boxes for the items of the point.
    proc Delta {} {
	upvar 1 grouptag grouptag c c
	upvar 1 mydbox obox myloc($grouptag) p

	set nbox [$c bbox $grouptag]
	#puts |$myloc($grouptag)|$mydbox|$nbox|

	lassign $p    px py
	lassign $obox ox oy _ _
	lassign $nbox nx ny _ _

	# Full delta based between old and current location.
	set dx [expr {$nx - $ox}]
	set dy [expr {$ny - $oy}]

	# New absolute location based on the full delta.
	set px [expr {$px + $dx}]
	set py [expr {$py + $dy}]

	return [list $px $py $dx $dy]
    }

    # # ## ### ##### ######## ############# #####################
    ## Instance state

    variable mycanvas     {} ; # Instance command of the canvas widget
			       # the editor works with.
    variable mycounter     0 ; # Counter for NewId to generate
			       # identifiers for point markers.
    variable mydragactive  0 ; # Flag, true when a drag is running, to
			       # veto un-highlighting.
    variable mydbox       {} ; # The bounding box of the items dragged
			       # around, to compute full deltas and
			       # absolute location during the drag.
    variable myactive      0 ; # Flag, true when the editor bindings are
                               # set on the canvas, enabling editing.
    variable myloc -array {} ; # Internal data base mapping from point
			       # id to point location, for the
			       # calculation of absolute coordinates
			       # during dragging.

    # # ## ### ##### ######## ############# #####################
    ## Default implementations for the configurable callbacks to
    ## create and highlight the edited points.

    method DefaultCreate {c x y} {
	$self CheckCanvas $c
	# Create a circle centered on the chosen location, blue filled
	# with black border.
	set w [expr {$x - 3}]
	set n [expr {$y - 3}]
	set e [expr {$x + 3}]
	set s [expr {$y + 3}]
	lappend items [$c create oval $w $n $e $s \
			   -width   1            \
			   -outline black       \
			   -fill    SkyBlue2]
	return $items
    }

    method {DefaultHighlight on} {c item} {
	$self CheckCanvas $c
	# Highlight by refilling the item in red. Save its full state
	# for restoration at the end of the highlight.
	set previous [lindex [$c itemconfigure $item -fill] end]
	$c itemconfigure $item -fill red
	return [list $item $previous]
    }

    method {DefaultHighlight off} {c state} {
	$self CheckCanvas $c
	# To unhighlight get the saved item and state, restore them.
	lassign $state item previous
	$c itemconfigure $item -fill $previous
	return
    }

    method CheckCanvas {c} {
	if {$c eq $mycanvas} return
	return -code error "Canvas mismatch, ours is $mycanvas, called with $c"
    }

    # # ## ### ##### ######## ############# #####################
}

# # ## ### ##### ######## ############# #####################
## Ready

package provide canvas::edit::points 0.1
return

# # ## ### ##### ######## ############# #####################
## Scrap yard.