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.
|