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()