0
|
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
|
|
11 class Stage(Canvas):
|
|
12
|
|
13 """a fancy widget that shows light locations (and optionally their
|
|
14 aim locations on an image of the stage. you can select or
|
|
15 multiselect lights and drag them up or down to change their
|
|
16 brightness.
|
|
17
|
|
18 ctrl-a is select all,
|
|
19 ctrl-shift-a or clicking on no light deselects all,
|
|
20 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,
|
|
22 shift-drag-rectangle selects the lights in the rectangle,
|
|
23 drag-rectangle selects only the lights in the rectangle.
|
|
24
|
|
25 a light can be selected on its location point, its aim point
|
|
26 (which may or may not be present), or its name.
|
|
27
|
|
28 lights should be able to be interactively 'locked', which blocks
|
|
29 them from being selected.
|
|
30
|
|
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 """
|
|
49 def __init__(self,parent,**kw):
|
|
50 Canvas.__init__(self,parent,**kw)
|
|
51
|
|
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
|
|
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()
|