Files @ 891c380afcc1
Branch filter:

Location: light9/light8/stage.py

drewp@bigasterisk.com
move py code under light9, add import test
  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
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
from Tix import *

def printevent(ev):
    for k in dir(ev):
        if not k.startswith('__'):
            print k,getattr(ev,k)
    print ""

textstyle={'font':'arial 9','fill':'white'}

class Stage(Canvas):
    
    """a fancy widget that shows light locations (and optionally their
    aim locations on an image of the stage. you can select or
    multiselect lights and drag them up or down to change their
    brightness.

    ctrl-a is select all,
    ctrl-shift-a or clicking on no light deselects all,
    re-clicking a light with shift key down toggles whether it's in the selection.
    ctrl-drag-rectangle deselects the lights in the rectangle,
    shift-drag-rectangle selects the lights in the rectangle,
    drag-rectangle selects only the lights in the rectangle.

    a light can be selected on its location point, its aim point
    (which may or may not be present), or its name.

    lights should be able to be interactively 'locked', which blocks
    them from being selected. 

    API:
      __init__(parent,**kw)
        put pass any canvas options you want
        
      setimage(stageimage)
        sets image to given filename (ppm, gif, etc) and resizes the
        canvas to the image size

      addlight(name, location, aim=None)
        location and aim are pixel coord tuples. name will be passed
        back to you in the callback (see below)

      setsubediting(se)
        give a subediting object to receive the 'startlevelchange' and
        'levelchange' messages
      

    """
    def __init__(self,parent,**kw):
        Canvas.__init__(self,parent,**kw)

        self.bind("<ButtonPress>", self.press)
        self.bind("<Motion>", self.motion)
        self.bind("<ButtonRelease>", self.release)
        self.bind("<Control-Key-a>", lambda ev: self.selectall())
        self.bind("<Control-Key-A>", lambda ev: self.clearselection())
#        self.bind("<Control-Shift-Key-a>",self.handlecontrol_a)
        
        self.halo=11 # search radius for clicked items

        self.mode=None # as you perform with the mouse, this goes
                       # from None to 'pressed','rectangle','levelchange', etc

        self.alllights=[]
        self.selectedlights=[]
        self.alllighttags={} # tag: name lookup

        self.subeditor = None


    def setimage(self,stageimage):
        img = Image('photo',file=stageimage)
        self.img=img # can't lose this!
        # print img.width()
        self.create_image(0,0,anchor='nw',image=img)
        self.config(width=img.width(),height=img.height())

        # we had the picture removed for good luck, but we remember
        # what the dimensions formerly was
        self.config(width=821,height=681, bg="grey40")


    def setsubediting(self,subeditor):
        self.subeditor = subeditor

    # (17:00:06) drewp: if yes, then self.itemconfigure(tagOrId, text=...)
    def updatelightlevel(self, name, level):
        tag = self.nametag(name)
        self.itemconfigure("level_%s" % tag, text=level)

    #
    # selection management
    #
    def updateselectionboxes(self):
        "make selection boxes that match self.selectedlights"
        self.delete("selectbox")
        for l in self.selectedlights:
            for c in self.getlightbboxes(l):
               self.create_rectangle(c[0]-2,c[1]-2,c[2]+2,c[3]+2,
                                     outline='red',tag="selectbox")

    def selectall(self):
        self.selectedlights= self.alllights[:]
        self.updateselectionboxes()
    def clearselection(self):
        self.selectedlights=[]
        self.updateselectionboxes()

    def markfordynselection(self):
        """call this before calls to replacedynselection"""
        self.origselection = self.selectedlights[:]

    def replacedynselection(self,newlightnames,subtract=0):
        """as a dynamic selection changes, keep calling this function
        with the names of the lights in the dynamic selection. the
        original selection (at the time of markfordynselection) will
        be shown along with any new lights. if subtract=1, the selection will
        be shown MINUS the newlights."""
        if subtract==0:
            # orig selection plus any newlights that weren't in the orig 
            # selection
            self.selectedlights = self.origselection[:] + \
                [l for l in newlightnames if l not in self.origselection]
        else:
            # orig selection lights except those that are in the newlightnames 
            # list
            self.selectedlights = [l for l in self.origselection 
                if l not in newlightnames]
        self.updateselectionboxes()

    def select(self,lightname,select=1): # select=0 for deselect
        """select or deselect (select=0) a light by name"""
        if select:
            if lightname not in self.selectedlights:
                self.selectedlights.append(lightname)
        elif lightname in self.selectedlights:
            self.selectedlights.remove(lightname)

        self.updateselectionboxes()
                
    #
    # mouse handling
    #
    def press(self,ev):
        
        self.mode='pressed'
        self.mousedownpos=(ev.x,ev.y)
        print "click at",self.mousedownpos

        button=ev.num
        shifted=ev.state & 1
        control=ev.state & 4
        touching=self.findoverlappinglights((ev.x-self.halo,ev.y-self.halo,
                                             ev.x+self.halo,ev.y+self.halo))
        istouching=len(touching)>0

        if button==1:
            if not istouching:
                # clicked in space
                if not shifted and not control and len(self.selectedlights)>0:
                    # either a deselect (if no motion) or a level change (if motion)
                    self.mode = 'deselect-or-rectangle'
                if shifted or control or len(self.selectedlights)==0:
                    # with shift/control, add/subtract lights to selection
                    self.startrectangleselect()

            else:
                # clicked a selectable object
                # toggle selection
                if touching[0] in self.selectedlights:
                    if shifted or control:
                        # deselect
                        self.select(touching[0],0)
                        # and do nothing else
                        self.mode=None
                    if not shifted:
                        # select only this light
                        self.clearselection()
                        self.select(touching[0])
                        # and adjust its level
                        self.startlevelchange()

                else:
                    # clicked a light that wasn't selected
                    if not shifted:
                        self.clearselection()
                    self.select(touching[0])
                    # and adjust levels now
                    self.startlevelchange()

        if button==3:
            self.startlevelchange()

    def motion(self,ev):

        coords=(ev.x,ev.y)

        shifted=ev.state & 1
        control=ev.state & 4

        if self.mode=='deselect-or-rectangle':
            if (coords[0]-self.mousedownpos[0])**2+(coords[1]-self.mousedownpos[1])**2>self.halo**2:
                if not shifted and not control:
                    self.clearselection()
                # they moved enough, it's a level change
                self.startrectangleselect()
                

        if self.mode=='levelchange':
            delta = 1.5 * (self.mousedownpos[1]-ev.y)
            if self.subeditor:
                self.subeditor.levelchange(self.selectedlights,delta)

        if self.mode=='rectangle':
            sr = self.find_withtag('selectrect')
            if not sr:
                # make the selection rectangle
                sr=self.create_rectangle( self.mousedownpos[0],
                    self.mousedownpos[1],coords[0],coords[1],
                    outlinestipple='gray50',outline='yellow',tag='selectrect')

            # move rectangle with mouse
            self.coords(sr,*(self.mousedownpos+coords))

            # redo the dynselection with the new rectangle
            self.replacedynselection(self.findoverlappinglights((self.mousedownpos+coords),1),
                                     subtract=control)

    def release(self,ev):
        if self.mode:
            if self.mode=='rectangle':
                self.delete('selectrect')

            if self.mode=='deselect-or-rectangle':
                # they didn't move enough to promote the mode to level, so 
                # it's a deselect click
                self.clearselection()
            
            self.mode=None

    #
    # subedit type things (correct me if i'm wrong)
    #
            
    def startlevelchange(self):
        """sets mode to levelchange AND notifies subeditor. this
        should be done exactly once (per mouse drag), when you first
        decide the mode is levelchange"""
        self.mode='levelchange'
        if self.subeditor:
            self.subeditor.startlevelchange()
    def startrectangleselect(self):
        """sets mode to rectangle AND checkpoints the current selection"""
        self.mode='rectangle'
        self.markfordynselection()
    #
    # light names vs. canvas object tags
    #
    def nametag(self,name):
        "returns a safe version of the name that won't match other names"
        return name.replace(" ","__")

    def tagtoname(self,tag):
        "finds the real light name for a tag written by nametag()"
        return self.alllighttags[tag]

    #
    # light methods
    #
    def addlight(self,name,location,aim=None):
        tags='light selectable name_%s' % self.nametag(name)
        
        self.create_oval(location[0]-2,location[1]-2,
                         location[0]+2,location[1]+2,
                         fill='red',tag=tags+" hotspot")
        if aim:
            self.create_oval(aim[0]-2,aim[1]-2,
                             aim[0]+2,aim[1]+2,
                             fill='blue',tag=tags+" hotspot")
            self.create_line(location[0],location[1],aim[0],aim[1],
                fill='lightblue', arrow='last',arrowshape="9 15 6",tag='light')

        # shadow
        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']))
        # text
        self.create_text(location[0],location[1]+5,anchor='n',text=name,
            tag=tags,**textstyle)

        # level
        self.create_text(location[0]-2,location[1]+13,anchor='n',
            text='0', # will be changed later
            tag='level_%s' % self.nametag(name),**textstyle)
        
        self.alllights.append(name)
        self.alllighttags[self.nametag(name)]=name

    def getlightbboxes(self,tag):
        """returns a list of bboxes for a light with a given name_ tag. the 
        selection mechanism draws around these bboxes to show that a light is 
        selected"""
        bboxes=[]
        for o in self.find_withtag("name_%s" % self.nametag(tag)):
            if 'hotspot' in self.gettags(o):
                bboxes.append(self.bbox(o))
        return bboxes

    def findoverlappinglights(self,box,enclosed=0):
        "returns all the different names for lights that are within (or enclosed by) the box"
        lights=[]
        if enclosed:
            candidates = self.find_enclosed(*box)
        else:
            candidates = self.find_overlapping(*box)

        for o in candidates:
            for t in self.gettags(o):
                if t.startswith("name_"):
                    n = self.tagtoname(t[5:])
                    if n and (n not in lights):
                        lights.append(n)
        return lights


def createlights(s):
    s.setimage('guysanddolls.gif')
    s.addlight('desk1',(46, 659),    aim=(210, 381))
    s.addlight('marry1',(78, 661),   aim=(398, 428))
    s.addlight('b13',(110, 661))   
    s.addlight('hotbox1',(147, 657), aim=(402, 327))
    s.addlight('edge',(179, 651),    aim=(116, 441))
    s.addlight('phone',(214, 652),   aim=(651, 417))
    s.addlight('cuba1',(315, 656),   aim=(559, 407))
    s.addlight('b22',(347, 661),     aim=(247, 458))
    s.addlight('b23',(379, 661))  
    s.addlight('b24',(417, 661))  
    s.addlight('b25',(455, 658),     aim=(520, 466))
    s.addlight('desk2',(490, 655),   aim=(237, 375))
    s.addlight('rock',(571, 655),    aim=(286, 304))
    s.addlight('b32',(606, 650))  
    s.addlight('hotbox2',(637, 650), aim=(433, 337))
    s.addlight('b34',(671, 651))   
    s.addlight('marry2',(703, 651),  aim=(429, 426))
    s.addlight('cuba2',(733, 652),   aim=(602, 408))

    s.addlight('sidefill1',(115, 473),aim=(228, 423))
    s.addlight('sidefill2',(617, 475),aim=(526, 425))

    s.addlight('cycright',(485, 164),(483, 109))
    s.addlight('cycleft',(330, 154),(333, 108))

    s.addlight('upfill1',(275, 325),(262, 237))
    s.addlight('upfill2',(333, 326),(330, 229))
    s.addlight('upfill3',(473, 325),(454, 226))
    s.addlight('upfill4',(541, 325),(528, 223))

    s.addlight('god',(369,549))

    s.addlight('patio1',(42, 560),(12, 512))
    s.addlight('patio2',(675, 553),(793, 514))

    s.addlight('hotback',(413, 476),(414, 396))

    s.addlight('main 2',(120, 563) ,aim=(241, 472))
    s.addlight('main 3',(162, 562) ,aim=(140, 425))
    s.addlight('main 4',(208, 560) ,aim=(342, 423))
    s.addlight('main 5',(259, 558) ,aim=(433, 450))
    s.addlight('main 7',(494, 551) ,aim=(420, 458))
    s.addlight('main 8',(528, 554) ,aim=(503, 477))
    s.addlight('main 9',(559, 554) ,aim=(544, 479))
    s.addlight('main 10',(597, 556),aim=(339, 444))
    s.addlight('main 11',(636, 556),aim=(449, 409))

    s.addlight('side r', (785, 609))
    s.addlight('side l', (8, 613))

    s.addlight('cafe1',(174, 476))
    s.addlight('cafe2',(584, 475))
    s.addlight('dream',(329, 477),aim=(267, 309))
    s.addlight('xmas',(555, 56),aim=(438, 284))

    for y,col in ((300,'red'),(333,'blue'),(364,'gree'),(407,'oran')):
        for i in range(1,5):
            s.addlight('%s%s' % (col,i),(726+10*i,y))

if __name__=='__main__':
    root=Tk()
    root.tk_focusFollowsMouse()
    root.wm_geometry("+376+330")
    s=Stage(root)
    s.setimage('guysanddolls.gif')
    s.pack()

    createlights(s)

    class subediting_standin:
        def startlevelchange(self):
            print "start lev change"
        def levelchange(self,lights,delta):
            print "change",lights,delta
    s.setsubediting(subediting_standin())
    
    root.mainloop()