Mercurial > code > home > repos > light9
comparison light8/stage.py @ 44:6540879e336e
fixed Stage a lot: ctrl-drag subtracts from selection; ctrl-a/ctrl-A select all/none;
fixed Stage a lot: ctrl-drag subtracts from selection; ctrl-a/ctrl-A select all/none;
rmb adjusts levels
author | drewp |
---|---|
date | Sun, 07 Jul 2002 13:07:31 +0000 |
parents | 115636cca107 |
children | 71489bb71528 |
comparison
equal
deleted
inserted
replaced
43:2cd759c2b3c7 | 44:6540879e336e |
---|---|
34 API: | 34 API: |
35 __init__(parent,**kw) | 35 __init__(parent,**kw) |
36 put pass any canvas options you want | 36 put pass any canvas options you want |
37 | 37 |
38 setimage(stageimage) | 38 setimage(stageimage) |
39 sets image to given filename (ppm, gif, etc) and resizes the canvas to the image size | 39 sets image to given filename (ppm, gif, etc) and resizes the |
40 canvas to the image size | |
40 | 41 |
41 addlight(name, location, aim=None) | 42 addlight(name, location, aim=None) |
42 location and aim are pixel coord tuples. name will be passed back to you in the callback (see below) | 43 location and aim are pixel coord tuples. name will be passed |
43 | 44 back to you in the callback (see below) |
44 setlightchangecb(cb) | 45 |
45 give a function which will be called like this: cb(list_of_light_names, delta) | 46 setsubediting(se) |
47 give a subediting object to receive the 'startlevelchange' and | |
48 'levelchange' messages | |
46 | 49 |
47 | 50 |
48 """ | 51 """ |
49 def __init__(self,parent,**kw): | 52 def __init__(self,parent,**kw): |
50 Canvas.__init__(self,parent,**kw) | 53 Canvas.__init__(self,parent,**kw) |
51 | 54 |
52 self.bind("<ButtonPress-1>", self.leftpress) | 55 self.bind("<ButtonPress>", self.press) |
53 self.bind("<B1-Motion>", self.leftmotion) | 56 self.bind("<Motion>", self.motion) |
54 self.bind("<ButtonRelease-1>", self.leftrelease) | 57 self.bind("<ButtonRelease>", self.release) |
58 self.bind("<Control-Key-a>", lambda ev: self.selectall()) | |
59 self.bind("<Control-Key-A>", lambda ev: self.clearselection()) | |
60 # self.bind("<Control-Shift-Key-a>",self.handlecontrol_a) | |
55 | 61 |
56 self.halo=11 # search radius for clicked items | 62 self.halo=11 # search radius for clicked items |
57 | 63 |
58 self.lmbstate=None # as you perform with LMB, this goes from None to 'pressed','rectangle','levelchange' | 64 self.mode=None # as you perform with the mouse, this goes |
65 # from None to 'pressed','rectangle','levelchange', etc | |
59 | 66 |
60 self.alllights=[] | 67 self.alllights=[] |
61 self.selectedlights=[] | 68 self.selectedlights=[] |
62 self.alllighttags={} # tag: name lookup | 69 self.alllighttags={} # tag: name lookup |
63 | 70 |
78 def updateselectionboxes(self): | 85 def updateselectionboxes(self): |
79 "make selection boxes that match self.selectedlights" | 86 "make selection boxes that match self.selectedlights" |
80 self.delete("selectbox") | 87 self.delete("selectbox") |
81 for l in self.selectedlights: | 88 for l in self.selectedlights: |
82 for c in self.getlightbboxes(l): | 89 for c in self.getlightbboxes(l): |
83 self.create_rectangle(c[0]-2,c[1]-2,c[2]+2,c[3]+2,outline='red',tag="selectbox") | 90 self.create_rectangle(c[0]-2,c[1]-2,c[2]+2,c[3]+2, |
84 | 91 outline='red',tag="selectbox") |
85 def clearselection(self,dyn=0): | 92 |
93 def selectall(self): | |
94 self.selectedlights= self.alllights[:] | |
95 self.updateselectionboxes() | |
96 def clearselection(self): | |
86 self.selectedlights=[] | 97 self.selectedlights=[] |
87 self.updateselectionboxes() | 98 self.updateselectionboxes() |
88 | 99 |
89 def markfordynselection(self): | 100 def markfordynselection(self): |
90 """call this before calls to replacedynselection""" | 101 """call this before calls to replacedynselection""" |
91 self.origselection = self.selectedlights | 102 self.origselection = self.selectedlights[:] |
92 | 103 |
93 def replacedynselection(self,newlightnames): | 104 def replacedynselection(self,newlightnames,subtract=0): |
94 """as a dynamic selection changes, keep calling this function with the | 105 """as a dynamic selection changes, keep calling this function |
95 names of the lights in the dynamic selection. the original selection (at the time | 106 with the names of the lights in the dynamic selection. the |
96 of markfordynselection) will be shown along with any new lights""" | 107 original selection (at the time of markfordynselection) will |
97 self.selectedlights = self.origselection + [l for l in newlightnames if l not in self.origselection] | 108 be shown along with any new lights. if subtract=1, the selection will |
109 be shown MINUS the newlights.""" | |
110 if subtract==0: | |
111 # orig selection plus any newlights that weren't in the orig selection | |
112 self.selectedlights = self.origselection[:] + [l for l in newlightnames if l not in self.origselection] | |
113 else: | |
114 # orig selection lights except those that are in the newlightnames list | |
115 self.selectedlights = [l for l in self.origselection if l not in newlightnames] | |
98 self.updateselectionboxes() | 116 self.updateselectionboxes() |
99 | 117 |
100 def select(self,lightname,select=1,dyn=0): # select=0 for deselect | 118 def select(self,lightname,select=1): # select=0 for deselect |
119 """select or deselect (select=0) a light by name""" | |
101 if select: | 120 if select: |
102 if lightname not in self.selectedlights: | 121 if lightname not in self.selectedlights: |
103 self.selectedlights.append(lightname) | 122 self.selectedlights.append(lightname) |
104 elif lightname in self.selectedlights: | 123 elif lightname in self.selectedlights: |
105 self.selectedlights.remove(lightname) | 124 self.selectedlights.remove(lightname) |
106 | 125 |
107 self.updateselectionboxes() | 126 self.updateselectionboxes() |
108 | 127 |
109 | 128 |
110 # | 129 # |
111 # LMB click or drag | 130 # mouse handling |
112 # | 131 # |
113 def leftpress(self,ev): | 132 def press(self,ev): |
114 | 133 |
115 self.lmbstate='pressed' | 134 self.mode='pressed' |
116 self.lmbstart=(ev.x,ev.y) | 135 self.mousedownpos=(ev.x,ev.y) |
117 print "click at",self.lmbstart | 136 print "click at",self.mousedownpos |
118 | 137 |
138 button=ev.num | |
119 shifted=ev.state & 1 | 139 shifted=ev.state & 1 |
120 control=ev.state & 4 | 140 control=ev.state & 4 |
121 touching=self.findoverlappinglights((ev.x-self.halo,ev.y-self.halo,ev.x+self.halo,ev.y+self.halo)) | 141 touching=self.findoverlappinglights((ev.x-self.halo,ev.y-self.halo, |
142 ev.x+self.halo,ev.y+self.halo)) | |
122 istouching=len(touching)>0 | 143 istouching=len(touching)>0 |
123 | 144 |
124 if not istouching: | 145 if button==1: |
125 # clicked in space | 146 if not istouching: |
126 if not shifted and not control and len(self.selectedlights)>0: | 147 # clicked in space |
127 # either a deselect (if no motion) or a level change (if motion) | 148 if not shifted and not control and len(self.selectedlights)>0: |
128 self.lmbstate = 'deselect-or-level' | 149 # either a deselect (if no motion) or a level change (if motion) |
129 if shifted or control or len(self.selectedlights)==0: | 150 self.mode = 'deselect-or-rectangle' |
130 # with shift/control, add/subtract lights to selection | 151 if shifted or control or len(self.selectedlights)==0: |
131 self.lmbstate='rectangle' | 152 # with shift/control, add/subtract lights to selection |
132 | 153 self.startrectangleselect() |
133 else: | 154 |
134 # clicked a selectable object | 155 else: |
135 # toggle selection | 156 # clicked a selectable object |
136 if touching[0] in self.selectedlights: | 157 # toggle selection |
137 if shifted: | 158 if touching[0] in self.selectedlights: |
138 # deselect | 159 if shifted or control: |
139 self.select(touching[0],0) | 160 # deselect |
140 # and do nothing else | 161 self.select(touching[0],0) |
141 self.lmbstate=None | 162 # and do nothing else |
163 self.mode=None | |
164 if not shifted: | |
165 # select only this light | |
166 self.clearselection() | |
167 self.select(touching[0]) | |
168 # and adjust its level | |
169 self.startlevelchange() | |
170 | |
142 else: | 171 else: |
143 # select only this light | 172 # clicked a light that wasn't selected |
144 self.clearselection() | 173 if not shifted: |
174 self.clearselection() | |
145 self.select(touching[0]) | 175 self.select(touching[0]) |
146 # and adjust its level | 176 # and adjust levels now |
147 self.startlevelchange() | 177 self.startlevelchange() |
148 | 178 |
149 else: | 179 if button==3: |
150 # clicked a light that wasn't selected | 180 self.startlevelchange() |
151 if not shifted: | 181 |
152 self.clearselection() | 182 def motion(self,ev): |
153 self.select(touching[0]) | |
154 # and adjust levels now | |
155 self.startlevelchange() | |
156 | |
157 if self.lmbstate=='rectangle': | |
158 self.markfordynselection() | |
159 def leftmotion(self,ev): | |
160 | 183 |
161 coords=(ev.x,ev.y) | 184 coords=(ev.x,ev.y) |
162 | 185 |
163 shifted=ev.state & 1 | 186 shifted=ev.state & 1 |
164 control=ev.state & 4 | 187 control=ev.state & 4 |
165 | 188 |
166 if self.lmbstate=='deselect-or-level': | 189 if self.mode=='deselect-or-rectangle': |
167 if (coords[0]-self.lmbstart[0])**2+(coords[1]-self.lmbstart[1])**2>self.halo**2: | 190 if (coords[0]-self.mousedownpos[0])**2+(coords[1]-self.mousedownpos[1])**2>self.halo**2: |
168 # they moved enough, it's a level change | 191 # they moved enough, it's a level change |
169 self.startlevelchange() | 192 self.startrectangleselect() |
170 | 193 |
171 if self.lmbstate=='levelchange': | 194 |
172 delta = (self.lmbstart[1]-ev.y) | 195 if self.mode=='levelchange': |
196 delta = (self.mousedownpos[1]-ev.y) | |
173 if self.subeditor: | 197 if self.subeditor: |
174 self.subeditor.levelchange(self.selectedlights,delta) | 198 self.subeditor.levelchange(self.selectedlights,delta) |
175 | 199 |
176 if self.lmbstate=='rectangle': | 200 if self.mode=='rectangle': |
177 sr = self.find_withtag('selectrect') | 201 sr = self.find_withtag('selectrect') |
178 if not sr: | 202 if not sr: |
179 sr=self.create_rectangle( self.lmbstart[0],self.lmbstart[1],coords[0],coords[1], | 203 # make the selection rectangle |
180 outlinestipple='gray50',outline='yellow', | 204 sr=self.create_rectangle( self.mousedownpos[0],self.mousedownpos[1],coords[0],coords[1], |
181 tag='selectrect') | 205 outlinestipple='gray50',outline='yellow',tag='selectrect') |
182 | 206 |
183 # move rectangle with mouse | 207 # move rectangle with mouse |
184 self.coords(sr,*(self.lmbstart+coords)) | 208 self.coords(sr,*(self.mousedownpos+coords)) |
185 | 209 |
186 # redo the dynselection with the new rectangle | 210 # redo the dynselection with the new rectangle |
187 self.replacedynselection([o for o in self.findoverlappinglights((self.lmbstart+coords),1)]) | 211 self.replacedynselection(self.findoverlappinglights((self.mousedownpos+coords),1), |
188 | 212 subtract=control) |
189 # need to handle ctrl | 213 |
190 | 214 def release(self,ev): |
191 def leftrelease(self,ev): | 215 if self.mode: |
192 if self.lmbstate: | 216 if self.mode=='rectangle': |
193 | |
194 if self.lmbstate=='rectangle': | |
195 self.delete('selectrect') | 217 self.delete('selectrect') |
196 | 218 |
197 if self.lmbstate=='deselect-or-level': | 219 if self.mode=='deselect-or-rectangle': |
198 # they didn't move enough to promote the mode to level, so it's a deselect click | 220 # they didn't move enough to promote the mode to level, so it's a deselect click |
199 self.clearselection() | 221 self.clearselection() |
200 | 222 |
201 # all items that were in dynselection join the selection | 223 self.mode=None |
202 # self.incorporatedynselection() | 224 |
225 # | |
226 # | |
227 # | |
203 | 228 |
204 self.lmbstate=None | |
205 def startlevelchange(self): | 229 def startlevelchange(self): |
206 self.lmbstate='levelchange' | 230 """sets mode to levelchange AND notifies subeditor. this |
231 should be done exactly once (per mouse drag), when you first | |
232 decide the mode is levelchange""" | |
233 self.mode='levelchange' | |
207 if self.subeditor: | 234 if self.subeditor: |
208 self.subeditor.startlevelchange() | 235 self.subeditor.startlevelchange() |
209 | 236 def startrectangleselect(self): |
237 """sets mode to rectangle AND checkpoints the current selection""" | |
238 self.mode='rectangle' | |
239 self.markfordynselection() | |
210 # | 240 # |
211 # light names vs. canvas object tags | 241 # light names vs. canvas object tags |
212 # | 242 # |
213 def nametag(self,name): | 243 def nametag(self,name): |
214 "returns a safe version of the name that won't match other names" | 244 "returns a safe version of the name that won't match other names" |
231 self.create_oval(aim[0]-2,aim[1]-2, | 261 self.create_oval(aim[0]-2,aim[1]-2, |
232 aim[0]+2,aim[1]+2, | 262 aim[0]+2,aim[1]+2, |
233 fill='red',tag=tags+" hotspot") | 263 fill='red',tag=tags+" hotspot") |
234 self.create_line(location[0],location[1],aim[0],aim[1],fill='lightblue', | 264 self.create_line(location[0],location[1],aim[0],aim[1],fill='lightblue', |
235 arrow='last',arrowshape="9 15 6",tag='light') | 265 arrow='last',arrowshape="9 15 6",tag='light') |
236 self.create_text(location[0]-1,location[1]+6,anchor='n',text=name,fill='black',tag=tags,**dict([(k,v) for k,v in textstyle.items() if k!='fill'])) | 266 # shadow |
267 self.create_text(location[0]-1,location[1]+6, | |
268 anchor='n',text=name,fill='black', | |
269 tag=tags,**dict([(k,v) for k,v in textstyle.items() if k!='fill'])) | |
270 # text | |
237 self.create_text(location[0],location[1]+5,anchor='n',text=name,tag=tags,**textstyle) | 271 self.create_text(location[0],location[1]+5,anchor='n',text=name,tag=tags,**textstyle) |
272 | |
238 self.alllights.append(name) | 273 self.alllights.append(name) |
239 self.alllighttags[self.nametag(name)]=name | 274 self.alllighttags[self.nametag(name)]=name |
240 | 275 |
241 def getlightbboxes(self,tag): | 276 def getlightbboxes(self,tag): |
242 """returns a list of bboxes for a light with a given name_ tag. the selection | 277 """returns a list of bboxes for a light with a given name_ tag. the selection |
246 if 'hotspot' in self.gettags(o): | 281 if 'hotspot' in self.gettags(o): |
247 bboxes.append(self.bbox(o)) | 282 bboxes.append(self.bbox(o)) |
248 return bboxes | 283 return bboxes |
249 | 284 |
250 def findoverlappinglights(self,box,enclosed=0): | 285 def findoverlappinglights(self,box,enclosed=0): |
251 "returns all the different name_ tags for lights that are within (or enclosed by) the box" | 286 "returns all the different names for lights that are within (or enclosed by) the box" |
252 lights=[] | 287 lights=[] |
253 if enclosed: | 288 if enclosed: |
254 candidates = self.find_enclosed(*box) | 289 candidates = self.find_enclosed(*box) |
255 else: | 290 else: |
256 candidates = self.find_overlapping(*box) | 291 candidates = self.find_overlapping(*box) |
257 | 292 |
258 for o in candidates: | 293 for o in candidates: |
259 for t in self.gettags(o): | 294 for t in self.gettags(o): |
260 if t.startswith("name_"): | 295 if t.startswith("name_"): |
261 n = self.tagtoname(t[5:]) | 296 n = self.tagtoname(t[5:]) |
262 if n and (n not in lights): | 297 if n and (n not in lights): |
305 | 340 |
306 | 341 |
307 | 342 |
308 if __name__=='__main__': | 343 if __name__=='__main__': |
309 root=Tk() | 344 root=Tk() |
345 root.tk_focusFollowsMouse() | |
310 root.wm_geometry("+376+330") | 346 root.wm_geometry("+376+330") |
311 s=Stage(root) | 347 s=Stage(root) |
312 s.setimage('guysanddolls.gif') | 348 s.setimage('guysanddolls.gif') |
313 s.pack() | 349 s.pack() |
314 | 350 |
315 createlights(s) | 351 createlights(s) |
352 | |
353 class subediting_standin: | |
354 def startlevelchange(self): | |
355 print "start lev change" | |
356 def levelchange(self,lights,delta): | |
357 print "change",lights,delta | |
358 s.setsubediting(subediting_standin()) | |
359 | |
316 root.mainloop() | 360 root.mainloop() |
317 | 361 |