Merge lp:~mcfletch/simplegc/suggestions into lp:simplegc

Proposed by Mike C. Fletcher
Status: Needs review
Proposed branch: lp:~mcfletch/simplegc/suggestions
Merge into: lp:simplegc
Diff against target: 245 lines (+214/-1)
5 files modified
.bzrignore (+2/-0)
example/helloworldevent.py (+36/-0)
example/helloworldeventcallback.py (+42/-0)
example/slots.py (+131/-0)
sgc/widgets/base_widget.py (+3/-1)
To merge this branch: bzr merge lp:~mcfletch/simplegc/suggestions
Reviewer Review Type Date Requested Status
Sam Bull Disapprove
Review via email: mp+110711@code.launchpad.net

Description of the change

Your use of eval is unnecessary, the builtin function getattr( obj, attr ) performs the same operation. There is also a 3-argument form getattr( obj, attr, default ) which would allow you to code for cases where a widget does not have an expected method.

To post a comment you must log in.
lp:~mcfletch/simplegc/suggestions updated
260. By Mike C. Fletcher

Alter the helloworld.py example to include a basic event processing operation

261. By Mike C. Fletcher

Following tutorial, creating a callback-based version of the clicky demo

262. By Mike C. Fletcher

Ignore the egg-info directory and .eric4project directory

263. By Mike C. Fletcher

Just a worked example of what a multi-callback slot mechanism might look like...

Revision history for this message
Mike C. Fletcher (mcfletch) wrote :

Oh, I also sketched out a sample of a "slot" mechanism that allows multiple callbacks per event, currently just included in the examples directory.

Oh, and there's a couple new samples that follow the tutorial forward from helloworld, as I find it easiest to learn with concrete code. They get a bit too verbose for a tutorial, but maybe you'll find some way to make them useful somehow.

Revision history for this message
Sam Bull (dreamsorcerer) wrote :

> Oh, I also sketched out a sample of a "slot" mechanism that allows multiple
> callbacks per event, currently just included in the examples directory.

OK, in your example, it receives the widget as the implicit self argument, because the object is assigned in the class definition. What I'd really like is the option to assign it afterwards, like:

btn = sgc.Button()
btn.on_click = EventSlot("click")

But, when done like this, it no longer receives the implicit self argument. This is the same reason we don't have a reference to the widget when assigning a callback function like this.

Is there an easy way we can get a reference to the object it is being assigned to? The only thing I can think of, is to use __setattr__() in the widget and pass in self when a callback or event slot is being assigned to a widget.

Revision history for this message
Mike C. Fletcher (mcfletch) wrote :

On 12-06-20 05:56 PM, Sam Bull wrote:
>> Oh, I also sketched out a sample of a "slot" mechanism that allows multiple
>> callbacks per event, currently just included in the examples directory.
> OK, in your example, it receives the widget as the implicit self argument, because the object is assigned in the class definition. What I'd really like is the option to assign it afterwards, like:
>
> btn = sgc.Button()
> btn.on_click = EventSlot("click")
>
> But, when done like this, it no longer receives the implicit self argument. This is the same reason we don't have a reference to the widget when assigning a callback function like this.
>
> Is there an easy way we can get a reference to the object it is being assigned to? The only thing I can think of, is to use __setattr__() in the widget and pass in self when a callback or event slot is being assigned to a widget.
class EventSlot(object):
     def __set__( self, widget, value ):
         ...

That is, a descriptor (such as EventSlot) can decide what to do when you
go to set the value on the object, so it could simply append, or could
replace, or whatever you feel is appropriate. However, that implies
using an EventSlot (or some *other* descriptor) in the class as your
base definition. Which seems to be what you want to avoid.

HTH,
Mike

--
________________________________________________
   Mike C. Fletcher
   Designer, VR Plumber, Coder
   http://www.vrplumber.com
   http://blog.vrplumber.com

Revision history for this message
Sam Bull (dreamsorcerer) wrote :

Yes, so I'm suggesting moving some of that code into the widget. So, something like:

class Simple(object):
  def __setattr__(self, atr, val):
    if atr.startswith("on_"):
      val = lambda: val(self)
    object.__setattr__(self, atr, val)

That should work in making sure callbacks get the widget passed in. Then something slightly more complex might be needed to get the EventSlot thing working in a similar way.

This means in your 'helloworldeventcallback.py' experiment, you would receive the widget as an argument in new() with no code change, solving the issue of not being able to access event properties.

Revision history for this message
Sam Bull (dreamsorcerer) :
review: Disapprove

Unmerged revisions

263. By Mike C. Fletcher

Just a worked example of what a multi-callback slot mechanism might look like...

262. By Mike C. Fletcher

Ignore the egg-info directory and .eric4project directory

261. By Mike C. Fletcher

Following tutorial, creating a callback-based version of the clicky demo

260. By Mike C. Fletcher

Alter the helloworld.py example to include a basic event processing operation

259. By Mike C. Fletcher

Eliminate use of eval for a simple attribute lookup

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file '.bzrignore'
2--- .bzrignore 2012-05-27 08:21:03 +0000
3+++ .bzrignore 2012-06-18 04:44:18 +0000
4@@ -1,1 +1,3 @@
5 docs/_build/*
6+*.egg-info
7+.eric4project
8
9=== added file 'example/helloworldevent.py'
10--- example/helloworldevent.py 1970-01-01 00:00:00 +0000
11+++ example/helloworldevent.py 2012-06-18 04:44:18 +0000
12@@ -0,0 +1,36 @@
13+import sgc
14+from sgc.locals import *
15+
16+import pygame
17+from pygame.locals import *
18+# We'll use the random module to generate colors
19+import random
20+
21+pygame.display.init()
22+pygame.font.init()
23+
24+screen = sgc.surface.Screen((640,480))
25+
26+clock = pygame.time.Clock()
27+
28+# The color we'll use for the background
29+COLOR = (0,0,0)
30+
31+btn = sgc.Button(label="Clicky", pos=(100, 100))
32+btn.add(0)
33+
34+while True:
35+ time = clock.tick(30)
36+
37+ for event in pygame.event.get():
38+ sgc.event(event)
39+ if event.type == QUIT:
40+ exit()
41+ # now we actually process the event...
42+ elif event.type == GUI:
43+ if event.widget == btn and event.gui_type == 'click':
44+ COLOR = random.randint(0,255),random.randint(0,255),random.randint(0,255)
45+
46+ screen.fill(COLOR)
47+ sgc.update(time)
48+ pygame.display.flip()
49
50=== added file 'example/helloworldeventcallback.py'
51--- example/helloworldeventcallback.py 1970-01-01 00:00:00 +0000
52+++ example/helloworldeventcallback.py 2012-06-18 04:44:18 +0000
53@@ -0,0 +1,42 @@
54+import sgc
55+from sgc.locals import *
56+
57+import pygame
58+from pygame.locals import *
59+# We'll use the random module to generate colors
60+import random
61+
62+pygame.display.init()
63+pygame.font.init()
64+
65+screen = sgc.surface.Screen((640,480))
66+
67+clock = pygame.time.Clock()
68+
69+class Background( object ):
70+ def __init__( self, color, screen ):
71+ self.color = color
72+ self.screen = screen
73+ def new( self ):
74+ # how do I get to the event properties from this callback?
75+ # e.g. how do I know which button sent the event?
76+ self.color = random.randint(0,255),random.randint(0,255),random.randint(0,255)
77+ def draw( self ):
78+ self.screen.fill(self.color)
79+
80+bg = Background( (0,0,0), screen )
81+
82+btn = sgc.Button(label="Clicky", pos=(100, 100))
83+btn.on_click = bg.new
84+btn.add(0)
85+
86+while True:
87+ time = clock.tick(30)
88+
89+ for event in pygame.event.get():
90+ sgc.event(event)
91+ if event.type == QUIT:
92+ exit()
93+ bg.draw()
94+ sgc.update(time)
95+ pygame.display.flip()
96
97=== added file 'example/slots.py'
98--- example/slots.py 1970-01-01 00:00:00 +0000
99+++ example/slots.py 2012-06-18 04:44:18 +0000
100@@ -0,0 +1,131 @@
101+import sgc
102+from sgc.locals import *
103+
104+import pygame
105+from pygame.locals import *
106+# We'll use the random module to generate colors
107+import random
108+
109+import weakref
110+import sgc
111+import pygame
112+import pygame.event
113+
114+class EventSlot( object ):
115+ """Represents an event-generating slot on a Widget"""
116+ def __init__( self, gui_type ):
117+ self.gui_type = gui_type
118+ self.object_key = '_%s_handlers'%(gui_type,)
119+ def __get__( self, widget, typ=None ):
120+ if widget is None:
121+ return self
122+ return BoundSlot( self, widget )
123+ def get( self, widget ):
124+ return getattr( widget, self.object_key, [] )
125+ def set( self, widget, value ):
126+ return setattr( widget, self.object_key, value )
127+ def create_event( self, widget, **params ):
128+ """Create a GUI event from this event-slot"""
129+ base = {'widget':widget,'gui_type': self.gui_type}
130+ base.update( params )
131+ event = pygame.event.Event(GUI, base)
132+ return event
133+
134+def mutates_handlers( func ):
135+ """Decorate methods which mutate the handlers"""
136+ def with_handlers( self, *args, **named ):
137+ handlers = self.slot.get(self.widget)
138+ result = func( self, handlers, *args, **named )
139+ self.slot.set( self.widget, handlers )
140+ return result
141+ with_handlers.__name__ = func.__name__
142+ with_handlers.__doc__ = func.__doc__
143+ with_handlers.__dict__ = func.__dict__
144+ return with_handlers
145+
146+class BoundSlot( object ):
147+ def __init__( self, slot, widget ):
148+ self.slot = slot
149+ self.widget = widget
150+ def propagate_event( self, event ):
151+ # likely need to do some locking to prevent GUI event loops...
152+ for handler in self.slot.get( self.widget )[:]: # copy to prevent alterations to ourselves causing issues...
153+ result = handler( event )
154+ if result:
155+ return result
156+ # do bubbling here...
157+ pygame.event.post(event)
158+ def emit( self, **params ):
159+ """Programatically emit the event, triggering all handlers (if registered)"""
160+ event = self.slot.create_event( self.widget, **params )
161+ self.propagate_event( event )
162+ @mutates_handlers
163+ def append( self, handlers, handler ):
164+ if isinstance( handler, (list,tuple)):
165+ handlers.extend( handler )
166+ else:
167+ handlers.append( handler )
168+ @mutates_handlers
169+ def prepend( self, handlers, handler ):
170+ if isinstance( handler, (list,tuple)):
171+ handlers[:0] = handler
172+ else:
173+ handlers.insert( 0, handler )
174+ @mutates_handlers
175+ def remove( self, handlers, handler ):
176+ while handler in handlers:
177+ try:
178+ handlers.remove( handler )
179+ except ValueError,TypeError:
180+ pass # race condition catch...
181+ def __call__( self ):
182+ self.emit()
183+
184+class MyButton( sgc.Button ):
185+ on_click = EventSlot( 'click' )
186+ def __init__( self, *args, **named ):
187+ for key,value in named.items():
188+ slot = getattr( self, key, None )
189+ if isinstance( slot, BoundSlot ):
190+ slot.append( value )
191+ named.pop( key )
192+ super( MyButton, self ).__init__( *args, **named )
193+
194+pygame.display.init()
195+pygame.font.init()
196+
197+screen = sgc.surface.Screen((640,480))
198+
199+clock = pygame.time.Clock()
200+
201+class Background( object ):
202+ def __init__( self, color, screen ):
203+ self.color = color
204+ self.screen = screen
205+ def new( self, event ):
206+ # how do I get to the event properties from this callback?
207+ # e.g. how do I know which button sent the event?
208+ print 'Clicked on button', event.widget
209+ self.color = random.randint(0,255),random.randint(0,255),random.randint(0,255)
210+ def draw( self ):
211+ self.screen.fill(self.color)
212+
213+bg = Background( (0,0,0), screen )
214+
215+btn = MyButton(label="Clicky", pos=(100, 100), on_click=bg.new)
216+btn.add(0)
217+
218+def generic_logger( event ):
219+ print 'Event', event
220+btn.on_click.append( generic_logger )
221+
222+while True:
223+ time = clock.tick(30)
224+
225+ for event in pygame.event.get():
226+ sgc.event(event)
227+ if event.type == QUIT:
228+ exit()
229+ bg.draw()
230+ sgc.update(time)
231+ pygame.display.flip()
232
233=== modified file 'sgc/widgets/base_widget.py'
234--- sgc/widgets/base_widget.py 2012-06-16 11:01:51 +0000
235+++ sgc/widgets/base_widget.py 2012-06-18 04:44:18 +0000
236@@ -261,7 +261,9 @@
237 if name not in self._images:
238 self._images[name] = create_image(self._extra_images[name])
239 if isinstance(surf, (tuple,list)):
240- f = eval("self._draw_" + name)
241+ # eval is not necessary, use the built-in getattr
242+ attr = '_draw_%s'%( name, )
243+ f = getattr( self, attr )
244 draw = lambda draw, img=self._images[name]: f(draw, img)
245 self._custom_extra.append(draw)
246

Subscribers

People subscribed via source and target branches