comparison bGrease/mode.py @ 41:ff3e395abf91

Renamed grease to bGrease (Basic Grease) to get rid of conflicts with an already installed grease.
author KarstenBock@gmx.net
date Mon, 05 Sep 2011 15:00:34 +0200
parents grease/mode.py@ce33d344e202
children a6bbb732b27b
comparison
equal deleted inserted replaced
40:2e3ab06a2f47 41:ff3e395abf91
1 #############################################################################
2 #
3 # Copyright (c) 2010 by Casey Duncan and contributors
4 # All Rights Reserved.
5 #
6 # This software is subject to the provisions of the MIT License
7 # A copy of the license should accompany this distribution.
8 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
9 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
10 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
11 #
12 #############################################################################
13 """
14 Modes manage the state and transition between different application modes.
15 Typically such modes are presented as different screens that the user can
16 navigate between, similar to the way a browser navigates web pages. Individual
17 modes may be things like:
18
19 - Title screen
20 - Options dialog
21 - About screen
22 - In-progress game
23 - Inventory interface
24
25 The modal framework provides a simple mechanism to ensure that modes are
26 activated and deactivated properly. An activated mode is running and receives
27 events. A deactivated mode is paused and does not receive events.
28
29 Modes may be managed as a *last-in-first-out* stack, or as a list, or ring
30 of modes in sequence, or some combination of all.
31
32 For example usage see: :ref:`the mode section of the tutorial <tut-mode-section>`.
33 """
34
35 __version__ = '$Id$'
36
37 import abc
38
39
40 class BaseManager(object):
41 """Mode manager abstract base class.
42
43 The mode manager keeps a stack of modes where a single mode
44 is active at one time. As modes are pushed on and popped from
45 the stack, the mode at the top is always active. The current
46 active mode receives events from the manager's event dispatcher.
47 """
48
49 modes = ()
50 """The mode stack sequence. The last mode in the stack is
51 the current active mode. Read-only.
52 """
53
54 @property
55 def current_mode(self):
56 """The current active mode or ``None``. Read-only"""
57 try:
58 return self.modes[-1]
59 except IndexError:
60 return None
61
62 def on_last_mode_pop(self, mode):
63 """Hook executed when the last mode is popped from the manager.
64 Implementing this method is optional for subclasses.
65
66 :param mode: The :class:`Mode` object just popped from the manager
67 """
68
69 def activate_mode(self, mode):
70 """Perform actions to activate a node
71
72 :param mode: The :class: 'Mode' object to activate
73 """
74 mode.activate(self)
75
76 def deactivate_mode(self, mode):
77 """Perform actions to deactivate a node
78
79 :param mode: The :class: 'Mode' object to deactivate
80 """
81 mode.deactivate(self)
82
83
84 def push_mode(self, mode):
85 """Push a mode to the top of the mode stack and make it active
86
87 :param mode: The :class:`Mode` object to make active
88 """
89 current = self.current_mode
90 if current is not None:
91 self.deactivate_mode(current)
92 self.modes.append(mode)
93 self.activate_mode(mode)
94
95 def pop_mode(self):
96 """Pop the current mode off the top of the stack and deactivate it.
97 The mode now at the top of the stack, if any is then activated.
98
99 :param mode: The :class:`Mode` object popped from the stack
100 """
101 mode = self.modes.pop()
102 mode.deactivate(self)
103 current = self.current_mode
104 if current is not None:
105 self.activate_mode(current)
106 else:
107 self.on_last_mode_pop(mode)
108 return mode
109
110 def swap_modes(self, mode):
111 """Exchange the specified mode with the mode at the top of the stack.
112 This is similar to popping the current mode and pushing the specified
113 one, but without activating the previous mode on the stack or
114 executing :meth:`on_last_mode_pop()` if there is no previous mode.
115
116 :param mode: The :class:`Mode` object that was deactivated and replaced.
117 """
118 old_mode = self.modes.pop()
119 self.deactivate_mode(old_mode)
120 self.modes.append(mode)
121 self.activate_mode(mode)
122 return old_mode
123
124 def remove_mode(self, mode):
125 """Remove the specified mode. If the mode is at the top of the stack,
126 this is equivilent to :meth:`pop_mode()`. If not, no other modes
127 are affected. If the mode is not in the manager, do nothing.
128
129 :param mode: The :class:`Mode` object to remove from the manager.
130 """
131 if self.current_mode is mode:
132 self.pop_mode()
133 else:
134 try:
135 self.modes.remove(mode)
136 except ValueError:
137 pass
138
139 class BaseMode(object):
140 """Application mode very abstract base class
141 """
142 __metaclass__ = abc.ABCMeta
143
144 manager = None
145 """The :class:`BaseManager` that manages this mode"""
146
147 def __init__(self):
148 self.active = False
149
150 def on_activate(self):
151 """Being called when the Mode is activated"""
152 pass
153
154 def activate(self, mode_manager):
155 """Activate the mode for the given mode manager, if the mode is already active,
156 do nothing
157
158 The default implementation schedules time steps at :attr:`step_rate` per
159 second, sets the :attr:`manager` and sets the :attr:`active` flag to True.
160 """
161 if not self.active:
162 self.on_activate()
163 self.manager = mode_manager
164 self.active = True
165
166 def on_deactivate(self):
167 """Being called when the Mode is deactivated"""
168 pass
169
170 def deactivate(self, mode_manager):
171 """Deactivate the mode, if the mode is not active, do nothing
172
173 The default implementation unschedules time steps for the mode and
174 sets the :attr:`active` flag to False.
175 """
176 self.on_deactivate()
177 self.active = False
178
179
180 class BaseMulti(BaseMode):
181 """A mode with multiple submodes. One submode is active at one time.
182 Submodes can be switched to directly or switched in sequence. If
183 the Multi is active, then one submode is always active.
184
185 Multis are useful when modes can switch in an order other than
186 a LIFO stack, such as in "hotseat" multiplayer games, a
187 "wizard" style ui, or a sequence of slides.
188
189 Note unlike a normal :class:`Mode`, a :class:`Multi` doesn't have it's own
190 :attr:`clock` and :attr:`step_rate`. The active submode's are used
191 instead.
192 """
193 active_submode = None
194 """The currently active submode"""
195
196 def __init__(self, *submodes):
197 # We do not invoke the superclass __init__ intentionally
198 self.active = False
199 self.submodes = list(submodes)
200
201 def add_submode(self, mode, before=None, index=None):
202 """Add the submode, but do not make it active.
203
204 :param mode: The :class:`Mode` object to add.
205
206 :param before: The existing mode to insert the mode before.
207 If the mode specified is not a submode, raise
208 ValueError.
209
210 :param index: The place to insert the mode in the mode list.
211 Only one of ``before`` or ``index`` may be specified.
212
213 If neither ``before`` or ``index`` are specified, the
214 mode is appended to the end of the list.
215 """
216 assert before is None or index is None, (
217 "Cannot specify both 'before' and 'index' arguments")
218 if before is not None:
219 index = self.submodes.index(mode)
220 if index is not None:
221 self.submodes.insert(index, mode)
222 else:
223 self.submodes.append(mode)
224
225 def remove_submode(self, mode=None):
226 """Remove the submode.
227
228 :param mode: The submode to remove, if omitted the active submode
229 is removed. If the mode is not present, do nothing. If the
230 mode is active, it is deactivated, and the next mode, if any
231 is activated. If the last mode is removed, the :class:`Multi`
232 is removed from its manager.
233 """
234 # TODO handle multiple instances of the same subnode
235 if mode is None:
236 mode = self.active_submode
237 elif mode not in self.submodes:
238 return
239 next_mode = self.activate_next()
240 self.submodes.remove(mode)
241 if next_mode is mode:
242 if self.manager is not None:
243 self.manager.remove_mode(self)
244 self._deactivate_submode()
245
246 def activate_subnode(self, mode, before=None, index=None):
247 """Activate the specified mode, adding it as a subnode
248 if it is not already. If the mode is already the active
249 submode, do nothing.
250
251 :param mode: The mode to activate, and add as necesary.
252
253 :param before: The existing mode to insert the mode before
254 if it is not already a submode. If the mode specified is not
255 a submode, raise ValueError.
256
257 :param index: The place to insert the mode in the mode list
258 if it is not already a submode. Only one of ``before`` or
259 ``index`` may be specified.
260
261 If the mode is already a submode, the ``before`` and ``index``
262 arguments are ignored.
263 """
264 if mode not in self.submodes:
265 self.add_submode(mode, before, index)
266 if self.active_submode is not mode:
267 self._activate_submode(mode)
268
269 def activate_next(self, loop=True):
270 """Activate the submode after the current submode in order. If there
271 is no current submode, the first submode is activated.
272
273 Note if there is only one submode, it's active, and `loop` is True
274 (the default), then this method does nothing and the subnode remains
275 active.
276
277 :param loop: When :meth:`activate_next` is called
278 when the last submode is active, a True value for ``loop`` will
279 cause the first submode to be activated. Otherwise the
280 :class:`Multi` is removed from its manager.
281 :type loop: bool
282
283 :return:
284 The submode that was activated or None if there is no
285 other submode to activate.
286 """
287 assert self.submodes, "No submode to activate"
288 next_mode = None
289 if self.active_submode is None:
290 next_mode = self.submodes[0]
291 else:
292 last_mode = self.active_submode
293 index = self.submodes.index(last_mode) + 1
294 if index < len(self.submodes):
295 next_mode = self.submodes[index]
296 elif loop:
297 next_mode = self.submodes[0]
298 self._activate_submode(next_mode)
299 return next_mode
300
301 def activate_previous(self, loop=True):
302 """Activate the submode before the current submode in order. If there
303 is no current submode, the last submode is activated.
304
305 Note if there is only one submode, it's active, and `loop` is True
306 (the default), then this method does nothing and the subnode remains
307 active.
308
309 :param loop: When :meth:`activate_previous` is called
310 when the first submode is active, a True value for ``loop`` will
311 cause the last submode to be activated. Otherwise the
312 :class:`Multi` is removed from its manager.
313 :type loop: bool
314
315 :return:
316 The submode that was activated or None if there is no
317 other submode to activate.
318 """
319 assert self.submodes, "No submode to activate"
320 prev_mode = None
321 if self.active_submode is None:
322 prev_mode = self.submodes[-1]
323 else:
324 last_mode = self.active_submode
325 index = self.submodes.index(last_mode) - 1
326 if loop or index >= 0:
327 prev_mode = self.submodes[index]
328 self._activate_submode(prev_mode)
329 return prev_mode
330
331 def _set_active_submode(self, submode):
332 self.active_submode = submode
333 self.step_rate = submode.step_rate
334
335 def _activate_submode(self, submode):
336 """Activate a submode deactivating any current submode. If the Multi
337 itself is active, this happens immediately, otherwise the actual
338 activation is deferred until the Multi is activated. If the submode
339 is None, the Mulitmode is removed from its manager.
340
341 If submode is already the active submode, do nothing.
342 """
343 if self.active_submode is submode:
344 return
345 assert submode in self.submodes, "Unknown submode"
346 self._deactivate_submode()
347 self._set_active_submode(submode)
348 if submode is not None:
349 if self.active:
350 self.manager.activate_mode(submode)
351 else:
352 if self.manager is not None:
353 self.manager.remove_mode(self)
354
355 def clear_subnode(self):
356 """Clear any subnmode data"""
357 self.active_submode = None
358 self.step_rate = None
359
360 def _deactivate_submode(self, clear_subnode=True):
361 """Deactivate the current submode, if any. if `clear_subnode` is
362 True, `active_submode` is always None when this method returns
363 """
364 if self.active_submode is not None:
365 if self.active:
366 self.manager.deactivate_mode(self.active_submode)
367 if clear_subnode:
368 self.clear_subnode()
369
370 def activate(self, mode_manager):
371 """Activate the :class:`Multi` for the specified manager. The
372 previously active submode of the :class:`Multi` is activated. If there
373 is no previously active submode, then the first submode is made active.
374 A :class:`Multi` with no submodes cannot be activated
375 """
376 assert self.submodes, "No submode to activate"
377 self.manager = mode_manager
378 if self.active_submode is None:
379 self._set_active_submode(self.submodes[0])
380 else:
381 self._set_active_submode(self.active_submode)
382 self.manager.activate_mode(self.active_submode)
383 super(BaseMulti, self).activate(mode_manager)
384
385 def deactivate(self, mode_manager):
386 """Deactivate the :class:`Multi` for the specified manager.
387 The `active_submode`, if any, is deactivated.
388 """
389 self._deactivate_submode(clear_subnode=False)
390 super(BaseMulti, self).deactivate(mode_manager)
391