Mercurial > code > home > repos > light9
comparison light8/stage.py @ 5:a76f775bb635
wrote some docstring
author | drewp |
---|---|
date | Sun, 07 Jul 2002 01:24:00 +0000 |
parents | 45b12307c695 |
children | 119369e60da1 |
comparison
equal
deleted
inserted
replaced
4:f974a462133f | 5:a76f775bb635 |
---|---|
1 from Tix import * | |
2 | |
3 def printevent(ev): | |
4 for k in dir(ev): | |
5 if not k.startswith('__'): | |
6 print k,getattr(ev,k) | |
7 print "" | |
8 | |
9 textstyle={'font':'arial 9','fill':'white'} | |
10 | 1 |
11 class Stage(Canvas): | 2 class Stage(Canvas): |
12 | 3 |
13 """a fancy widget that shows light locations (and optionally their | 4 """a fancy widget that shows light locations (and optionally their |
14 aim locations on an image of the stage. you can select or | 5 aim locations on an image of the stage. you can select or |
20 re-clicking a light with shift key down toggles whether it's in the selection. | 11 re-clicking a light with shift key down toggles whether it's in the selection. |
21 ctrl-drag-rectangle deselects the lights in the rectangle, | 12 ctrl-drag-rectangle deselects the lights in the rectangle, |
22 shift-drag-rectangle selects the lights in the rectangle, | 13 shift-drag-rectangle selects the lights in the rectangle, |
23 drag-rectangle selects only the lights in the rectangle. | 14 drag-rectangle selects only the lights in the rectangle. |
24 | 15 |
25 a light can be selected on its location point, its aim point | 16 a light can be selected on its location point, it's aim point |
26 (which may or may not be present), or its name. | 17 (which may or may not be present), or its name. |
27 | 18 |
28 lights should be able to be interactively 'locked', which blocks | 19 lights should be able to be interactively 'locked', which blocks |
29 them from being selected. | 20 them from being selected. |
30 | 21 |
31 API: | |
32 __init__(parent,**kw) | |
33 put pass any canvas options you want | |
34 | |
35 setimage(stageimage) | |
36 sets image to given filename (ppm, gif, etc) and resizes the | |
37 canvas to the image size | |
38 | |
39 addlight(name, location, aim=None) | |
40 location and aim are pixel coord tuples. name will be passed | |
41 back to you in the callback (see below) | |
42 | |
43 setsubediting(se) | |
44 give a subediting object to receive the 'startlevelchange' and | |
45 'levelchange' messages | |
46 | |
47 | |
48 """ | 22 """ |
49 def __init__(self,parent,**kw): | 23 def __init__(self,parent,**kw): |
50 Canvas.__init__(self,parent,**kw) | 24 Canvas.__init__(self,parent,**kw) |
51 | 25 if 'stageimage' in kw: |
52 self.bind("<ButtonPress>", self.press) | |
53 self.bind("<Motion>", self.motion) | |
54 self.bind("<ButtonRelease>", self.release) | |
55 self.bind("<Control-Key-a>", lambda ev: self.selectall()) | |
56 self.bind("<Control-Key-A>", lambda ev: self.clearselection()) | |
57 # self.bind("<Control-Shift-Key-a>",self.handlecontrol_a) | |
58 | |
59 self.halo=11 # search radius for clicked items | |
60 | |
61 self.mode=None # as you perform with the mouse, this goes | |
62 # from None to 'pressed','rectangle','levelchange', etc | |
63 | |
64 self.alllights=[] | |
65 self.selectedlights=[] | |
66 self.alllighttags={} # tag: name lookup | |
67 | |
68 self.subeditor = None | |
69 | |
70 | |
71 def setimage(self,stageimage): | |
72 img = Image('photo',file=stageimage) | |
73 self.img=img # can't lose this! | |
74 # print img.width() | |
75 # self.create_image(0,0,anchor='nw',image=img) | |
76 # self.config(width=img.width(),height=img.height()) | |
77 | |
78 # we had the picture removed for good luck, but we remember | |
79 # what the dimensions formerly was | |
80 self.config(width=821,height=681, bg="grey40") | |
81 | |
82 | |
83 def setsubediting(self,subeditor): | |
84 self.subeditor = subeditor | |
85 | |
86 # (17:00:06) drewp: if yes, then self.itemconfigure(tagOrId, text=...) | |
87 def updatelightlevel(self, name, level): | |
88 tag = self.nametag(name) | |
89 self.itemconfigure("level_%s" % tag, text=level) | |
90 | |
91 # | |
92 # selection management | |
93 # | |
94 def updateselectionboxes(self): | |
95 "make selection boxes that match self.selectedlights" | |
96 self.delete("selectbox") | |
97 for l in self.selectedlights: | |
98 for c in self.getlightbboxes(l): | |
99 self.create_rectangle(c[0]-2,c[1]-2,c[2]+2,c[3]+2, | |
100 outline='red',tag="selectbox") | |
101 | |
102 def selectall(self): | |
103 self.selectedlights= self.alllights[:] | |
104 self.updateselectionboxes() | |
105 def clearselection(self): | |
106 self.selectedlights=[] | |
107 self.updateselectionboxes() | |
108 | |
109 def markfordynselection(self): | |
110 """call this before calls to replacedynselection""" | |
111 self.origselection = self.selectedlights[:] | |
112 | |
113 def replacedynselection(self,newlightnames,subtract=0): | |
114 """as a dynamic selection changes, keep calling this function | |
115 with the names of the lights in the dynamic selection. the | |
116 original selection (at the time of markfordynselection) will | |
117 be shown along with any new lights. if subtract=1, the selection will | |
118 be shown MINUS the newlights.""" | |
119 if subtract==0: | |
120 # orig selection plus any newlights that weren't in the orig | |
121 # selection | |
122 self.selectedlights = self.origselection[:] + \ | |
123 [l for l in newlightnames if l not in self.origselection] | |
124 else: | |
125 # orig selection lights except those that are in the newlightnames | |
126 # list | |
127 self.selectedlights = [l for l in self.origselection | |
128 if l not in newlightnames] | |
129 self.updateselectionboxes() | |
130 | |
131 def select(self,lightname,select=1): # select=0 for deselect | |
132 """select or deselect (select=0) a light by name""" | |
133 if select: | |
134 if lightname not in self.selectedlights: | |
135 self.selectedlights.append(lightname) | |
136 elif lightname in self.selectedlights: | |
137 self.selectedlights.remove(lightname) | |
138 | |
139 self.updateselectionboxes() | |
140 | |
141 # | |
142 # mouse handling | |
143 # | |
144 def press(self,ev): | |
145 | |
146 self.mode='pressed' | |
147 self.mousedownpos=(ev.x,ev.y) | |
148 print "click at",self.mousedownpos | |
149 | |
150 button=ev.num | |
151 shifted=ev.state & 1 | |
152 control=ev.state & 4 | |
153 touching=self.findoverlappinglights((ev.x-self.halo,ev.y-self.halo, | |
154 ev.x+self.halo,ev.y+self.halo)) | |
155 istouching=len(touching)>0 | |
156 | |
157 if button==1: | |
158 if not istouching: | |
159 # clicked in space | |
160 if not shifted and not control and len(self.selectedlights)>0: | |
161 # either a deselect (if no motion) or a level change (if motion) | |
162 self.mode = 'deselect-or-rectangle' | |
163 if shifted or control or len(self.selectedlights)==0: | |
164 # with shift/control, add/subtract lights to selection | |
165 self.startrectangleselect() | |
166 | |
167 else: | |
168 # clicked a selectable object | |
169 # toggle selection | |
170 if touching[0] in self.selectedlights: | |
171 if shifted or control: | |
172 # deselect | |
173 self.select(touching[0],0) | |
174 # and do nothing else | |
175 self.mode=None | |
176 if not shifted: | |
177 # select only this light | |
178 self.clearselection() | |
179 self.select(touching[0]) | |
180 # and adjust its level | |
181 self.startlevelchange() | |
182 | |
183 else: | |
184 # clicked a light that wasn't selected | |
185 if not shifted: | |
186 self.clearselection() | |
187 self.select(touching[0]) | |
188 # and adjust levels now | |
189 self.startlevelchange() | |
190 | |
191 if button==3: | |
192 self.startlevelchange() | |
193 | |
194 def motion(self,ev): | |
195 | |
196 coords=(ev.x,ev.y) | |
197 | |
198 shifted=ev.state & 1 | |
199 control=ev.state & 4 | |
200 | |
201 if self.mode=='deselect-or-rectangle': | |
202 if (coords[0]-self.mousedownpos[0])**2+(coords[1]-self.mousedownpos[1])**2>self.halo**2: | |
203 if not shifted and not control: | |
204 self.clearselection() | |
205 # they moved enough, it's a level change | |
206 self.startrectangleselect() | |
207 | |
208 | |
209 if self.mode=='levelchange': | |
210 delta = 1.5 * (self.mousedownpos[1]-ev.y) | |
211 if self.subeditor: | |
212 self.subeditor.levelchange(self.selectedlights,delta) | |
213 | |
214 if self.mode=='rectangle': | |
215 sr = self.find_withtag('selectrect') | |
216 if not sr: | |
217 # make the selection rectangle | |
218 sr=self.create_rectangle( self.mousedownpos[0], | |
219 self.mousedownpos[1],coords[0],coords[1], | |
220 outlinestipple='gray50',outline='yellow',tag='selectrect') | |
221 | |
222 # move rectangle with mouse | |
223 self.coords(sr,*(self.mousedownpos+coords)) | |
224 | |
225 # redo the dynselection with the new rectangle | |
226 self.replacedynselection(self.findoverlappinglights((self.mousedownpos+coords),1), | |
227 subtract=control) | |
228 | |
229 def release(self,ev): | |
230 if self.mode: | |
231 if self.mode=='rectangle': | |
232 self.delete('selectrect') | |
233 | |
234 if self.mode=='deselect-or-rectangle': | |
235 # they didn't move enough to promote the mode to level, so | |
236 # it's a deselect click | |
237 self.clearselection() | |
238 | 26 |
239 self.mode=None | |
240 | |
241 # | |
242 # subedit type things (correct me if i'm wrong) | |
243 # | |
244 | |
245 def startlevelchange(self): | |
246 """sets mode to levelchange AND notifies subeditor. this | |
247 should be done exactly once (per mouse drag), when you first | |
248 decide the mode is levelchange""" | |
249 self.mode='levelchange' | |
250 if self.subeditor: | |
251 self.subeditor.startlevelchange() | |
252 def startrectangleselect(self): | |
253 """sets mode to rectangle AND checkpoints the current selection""" | |
254 self.mode='rectangle' | |
255 self.markfordynselection() | |
256 # | |
257 # light names vs. canvas object tags | |
258 # | |
259 def nametag(self,name): | |
260 "returns a safe version of the name that won't match other names" | |
261 return name.replace(" ","__") | |
262 | |
263 def tagtoname(self,tag): | |
264 "finds the real light name for a tag written by nametag()" | |
265 return self.alllighttags[tag] | |
266 | |
267 # | |
268 # light methods | |
269 # | |
270 def addlight(self,name,location,aim=None): | |
271 tags='light selectable name_%s' % self.nametag(name) | |
272 | |
273 self.create_oval(location[0]-2,location[1]-2, | |
274 location[0]+2,location[1]+2, | |
275 fill='red',tag=tags+" hotspot") | |
276 if aim: | |
277 self.create_oval(aim[0]-2,aim[1]-2, | |
278 aim[0]+2,aim[1]+2, | |
279 fill='blue',tag=tags+" hotspot") | |
280 self.create_line(location[0],location[1],aim[0],aim[1], | |
281 fill='lightblue', arrow='last',arrowshape="9 15 6",tag='light') | |
282 | |
283 # shadow | |
284 self.create_text(location[0]-1,location[1]+6, | |
285 anchor='n',text=name,fill='black', | |
286 tag=tags,**dict([(k,v) | |
287 for k,v in textstyle.items() if k!='fill'])) | |
288 # text | |
289 self.create_text(location[0],location[1]+5,anchor='n',text=name, | |
290 tag=tags,**textstyle) | |
291 | |
292 # level | |
293 self.create_text(location[0]-2,location[1]+13,anchor='n', | |
294 text='0', # will be changed later | |
295 tag='level_%s' % self.nametag(name),**textstyle) | |
296 | |
297 self.alllights.append(name) | |
298 self.alllighttags[self.nametag(name)]=name | |
299 | |
300 def getlightbboxes(self,tag): | |
301 """returns a list of bboxes for a light with a given name_ tag. the | |
302 selection mechanism draws around these bboxes to show that a light is | |
303 selected""" | |
304 bboxes=[] | |
305 for o in self.find_withtag("name_%s" % self.nametag(tag)): | |
306 if 'hotspot' in self.gettags(o): | |
307 bboxes.append(self.bbox(o)) | |
308 return bboxes | |
309 | |
310 def findoverlappinglights(self,box,enclosed=0): | |
311 "returns all the different names for lights that are within (or enclosed by) the box" | |
312 lights=[] | |
313 if enclosed: | |
314 candidates = self.find_enclosed(*box) | |
315 else: | |
316 candidates = self.find_overlapping(*box) | |
317 | |
318 for o in candidates: | |
319 for t in self.gettags(o): | |
320 if t.startswith("name_"): | |
321 n = self.tagtoname(t[5:]) | |
322 if n and (n not in lights): | |
323 lights.append(n) | |
324 return lights | |
325 | |
326 | |
327 def createlights(s): | |
328 s.setimage('guysanddolls.gif') | |
329 s.addlight('desk1',(46, 659), aim=(210, 381)) | |
330 s.addlight('marry1',(78, 661), aim=(398, 428)) | |
331 s.addlight('b13',(110, 661)) | |
332 s.addlight('hotbox1',(147, 657), aim=(402, 327)) | |
333 s.addlight('edge',(179, 651), aim=(116, 441)) | |
334 s.addlight('phone',(214, 652), aim=(651, 417)) | |
335 s.addlight('cuba1',(315, 656), aim=(559, 407)) | |
336 s.addlight('b22',(347, 661), aim=(247, 458)) | |
337 s.addlight('b23',(379, 661)) | |
338 s.addlight('b24',(417, 661)) | |
339 s.addlight('b25',(455, 658), aim=(520, 466)) | |
340 s.addlight('desk2',(490, 655), aim=(237, 375)) | |
341 s.addlight('rock',(571, 655), aim=(286, 304)) | |
342 s.addlight('b32',(606, 650)) | |
343 s.addlight('hotbox2',(637, 650), aim=(433, 337)) | |
344 s.addlight('b34',(671, 651)) | |
345 s.addlight('marry2',(703, 651), aim=(429, 426)) | |
346 s.addlight('cuba2',(733, 652), aim=(602, 408)) | |
347 | |
348 s.addlight('sidefill1',(115, 473),aim=(228, 423)) | |
349 s.addlight('sidefill2',(617, 475),aim=(526, 425)) | |
350 | |
351 s.addlight('cycright',(485, 164),(483, 109)) | |
352 s.addlight('cycleft',(330, 154),(333, 108)) | |
353 | |
354 s.addlight('upfill1',(275, 325),(262, 237)) | |
355 s.addlight('upfill2',(333, 326),(330, 229)) | |
356 s.addlight('upfill3',(473, 325),(454, 226)) | |
357 s.addlight('upfill4',(541, 325),(528, 223)) | |
358 | |
359 s.addlight('god',(369,549)) | |
360 | |
361 s.addlight('patio1',(42, 560),(12, 512)) | |
362 s.addlight('patio2',(675, 553),(793, 514)) | |
363 | |
364 s.addlight('hotback',(413, 476),(414, 396)) | |
365 | |
366 s.addlight('main 2',(120, 563) ,aim=(241, 472)) | |
367 s.addlight('main 3',(162, 562) ,aim=(140, 425)) | |
368 s.addlight('main 4',(208, 560) ,aim=(342, 423)) | |
369 s.addlight('main 5',(259, 558) ,aim=(433, 450)) | |
370 s.addlight('main 7',(494, 551) ,aim=(420, 458)) | |
371 s.addlight('main 8',(528, 554) ,aim=(503, 477)) | |
372 s.addlight('main 9',(559, 554) ,aim=(544, 479)) | |
373 s.addlight('main 10',(597, 556),aim=(339, 444)) | |
374 s.addlight('main 11',(636, 556),aim=(449, 409)) | |
375 | |
376 s.addlight('side r', (785, 609)) | |
377 s.addlight('side l', (8, 613)) | |
378 | |
379 s.addlight('cafe1',(174, 476)) | |
380 s.addlight('cafe2',(584, 475)) | |
381 s.addlight('dream',(329, 477),aim=(267, 309)) | |
382 s.addlight('xmas',(555, 56),aim=(438, 284)) | |
383 | |
384 for y,col in ((300,'red'),(333,'blue'),(364,'gree'),(407,'oran')): | |
385 for i in range(1,5): | |
386 s.addlight('%s%s' % (col,i),(726+10*i,y)) | |
387 | |
388 if __name__=='__main__': | |
389 root=Tk() | |
390 root.tk_focusFollowsMouse() | |
391 root.wm_geometry("+376+330") | |
392 s=Stage(root) | |
393 s.setimage('guysanddolls.gif') | |
394 s.pack() | |
395 | |
396 createlights(s) | |
397 | |
398 class subediting_standin: | |
399 def startlevelchange(self): | |
400 print "start lev change" | |
401 def levelchange(self,lights,delta): | |
402 print "change",lights,delta | |
403 s.setsubediting(subediting_standin()) | |
404 | |
405 root.mainloop() |