File: canvas_drag.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 (278 lines) | stat: -rw-r--r-- 7,344 bytes parent folder | download | duplicates (6)
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
## -*- tcl -*-
# ### ### ### ######### ######### #########

# Canvas Behavior Module. Dragging items and groups of items.

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

package require Tcl 8.5
package require Tk

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

namespace eval ::canvas::drag {
    namespace export \
	item group on off
    namespace ensemble create
}

proc ::canvas::drag::item {c tag args} {
    # Set up dragging of single items identified by the <tag>
    on $c $tag [namespace code Item1] {*}$args
    return
}

proc ::canvas::drag::group {c tag cmdprefix args} {
    # Set up dragging a group of items, with each group's drag
    # handle(s) identified by <tag>, and the <cmdprefix> taking the
    # handle item which triggered the drag and returning a tag which
    # identifies the whole group to move.

    on $c $tag [namespace code [list ItemGroup $cmdprefix]] {*}$args
    return
}

proc ::canvas::drag::on {c tag cmdprefix args} {
    # Setting up a general drag, with the drag handles identified by
    # <tag> and <cmdprefix> providing start/move methods invoked to
    # initialize and perform the drag. The cmdprefix is fully
    # responsible for how the dragging of a particular handle is
    # handled.

    variable attached

    # Process options (-event)
    set events [dict get [Options {*}$args] event]

    # Save the (canvas, tag) combination for use by 'off'.
    set k [list $c $tag]
    set attached($k) $events

    # Install the bindings doing the drag
    lassign $events trigger motion untrigger
    $c bind $tag $trigger   [namespace code [list Start $c $cmdprefix %x %y]]
    $c bind $tag $motion    [namespace code [list Move  $c $cmdprefix %x %y]]
    $c bind $tag $untrigger [namespace code [list Done  $c $cmdprefix %x %y]]
    return
}

proc ::canvas::drag::off {c tag} {
    # Remove a drag identified by canvas and tag.

    variable attached

    # Find and remove the bindings for this particular canvas,tag
    # combination.
    set k [list $c $tag]
    foreach event $attached($k) {
	$c bind $tag $event {}
    }

    # Update our database
    unset attached($k)
    return
}

# ### ### ### ######### ######### #########
## Option processing.

proc ::canvas::drag::Options {args} {
    # Button 3 is default for dragging.
    set config [list event [Validate 3]]

    foreach {option value} $args {
	switch -exact -- $option {
	    -event {
		dict set config event [Validate $value]
	    }
	    default {
		return -code error "Unknown option \"$option\", expected -event"
	    }
	}
    }

    return $config
}

# ### ### ### ######### ######### #########
## Event parsing and transformation

proc ::canvas::drag::Validate {event} {
    # Assumes that events are specified in the forms
    # <modifier>-<button> and <button>, where <modifier> is in the set
    # {Control, Shift, Alt, ... } and <button> a number. Returns
    # button-press and related motion event, or throws an error.

    set xevent [split $event -]
    if {[llength $xevent] > 2} {
	return -code error "Bad event \"$event\""
    } elseif {[llength $xevent] == 2} {
	lassign $xevent modifier button

	set trigger   <${event}>
	set motion    <${modifier}-B${button}-Motion>
	set untrigger <${modifier}-ButtonRelease-${button}>

    } else {
	lassign $xevent button
	set modifier {}

	set trigger   <${button}>
	set motion    <B${button}-Motion>
	set untrigger <ButtonRelease-${button}>
    }

    return [list $trigger $motion $untrigger]
}

# ### ### ### ######### ######### #########
## Drag execution.

proc ::canvas::drag::Start {c cmdprefix x y} {
    # Start a drag operation.
    variable attached
    variable active
    variable clientdata
    variable lastx
    variable lasty

    # Clear drag state
    unset -nocomplain active clientdata lastx lasty

    # Get item under mouse, if any.
    set item [$c find withtag current]
    if {$item eq {}} return

    # Initialize the drag state, run the command to initialize
    # anything external to us. We remember the current location to
    # enable the delta calculations in 'Move'.

    set active     $cmdprefix
    set lastx      [$c canvasx $x]
    set lasty      [$c canvasy $y]
    set clientdata [{*}$active start $c $item]
    return
}

proc ::canvas::drag::Move {c cmdprefix x y} {
    # Check for active drag.
    variable active
    if {![info exists active]} return

    # Import remainder of the drag state
    variable clientdata
    variable lastx
    variable lasty

    # Get current location and compute delta.
    set x [$c canvasx $x]
    set y [$c canvasy $y]

    set dx [expr {$x - $lastx}]
    set dy [expr {$y - $lasty}]

    # Let the command process the movement as it sees fit.
    # This may include updated client data.
    set clientdata [{*}$active move $c $clientdata $dx $dy]

    # Save the new location , for the next movement and delta.
    set lastx $x
    set lasty $y
    return
}

proc ::canvas::drag::Done {c cmdprefix x y} {
    # Check for active drag.
    variable active
    if {![info exists active]} return

    # Import remainder of the drag state
    variable clientdata

    # Let the command process the end of the drag operation as it sees
    # fit.
    {*}$active done $c $clientdata
    return
}

# ### ### ### ######### ######### #########
## Convenience. Dragging a single item.

# This is trivial. We remember the item to be dragged, and forward
# move requests directly to the canvas.

namespace eval ::canvas::drag::Item1 {
    namespace export start move done
    namespace ensemble create
}

proc ::canvas::drag::Item1::start {c item} {
    return $item
}

proc ::canvas::drag::Item1::move {c item dx dy} {
    $c move $item $dx $dy
    return $item
}

proc ::canvas::drag::Item1::done {c item} {
    return
}

# ### ### ### ######### ######### #########
## Convenience. Dragging an item group.

# Also mostly trivial. The move requests are still simply forwarded to
# the canvas, using the tag identifying the group. The main point is
# during start, using the external callback to transform the handle
# item into the group tag.

proc ::canvas::drag::ItemGroup {cmd method c args} {
    return [ItemGroup::$method $cmd $c {*}$args]
}

namespace eval ::canvas::drag::ItemGroup {}

proc ::canvas::drag::ItemGroup::start {cmd c item} {
    return [{*}$cmd start $c $item]
}

proc ::canvas::drag::ItemGroup::move {cmd c grouptag dx dy} {
    $c move $grouptag $dx $dy
    return $grouptag
}

proc ::canvas::drag::ItemGroup::done {cmd c grouptag} {
    {*}$cmd done $c $grouptag
    return
}

# ### ### ### ######### ######### #########
## State.

namespace eval ::canvas::drag {
    # Database of canvas,tag combinations with active bindings
    # (allowing their removal, see --> 'off'). Value are the
    # events which have bindings.

    variable  attached
    array set attached {}

    # State of a drag in progress

    variable  active     ; # command prefix to invoke for 'start' / 'move'.
    variable  clientdata ; # Result of invoking 'start', data for 'move'.
    variable  lastx      ; # x coord of last position the drag was at.
    variable  lasty      ; # y coord of last position the drag was at.
}

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

package provide canvas::drag 0.1
return

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