Mercurial > parpg-source
diff bGrease/mode.py @ 166:a6bbb732b27b
Added .hgeol file to automatically convert line endings.
author | KarstenBock@gmx.net |
---|---|
date | Thu, 12 Jan 2012 18:42:48 +0100 |
parents | ff3e395abf91 |
children |
line wrap: on
line diff
--- a/bGrease/mode.py Thu Dec 15 21:14:13 2011 +0100 +++ b/bGrease/mode.py Thu Jan 12 18:42:48 2012 +0100 @@ -1,391 +1,391 @@ -############################################################################# -# -# Copyright (c) 2010 by Casey Duncan and contributors -# All Rights Reserved. -# -# This software is subject to the provisions of the MIT License -# A copy of the license should accompany this distribution. -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. -# -############################################################################# -""" -Modes manage the state and transition between different application modes. -Typically such modes are presented as different screens that the user can -navigate between, similar to the way a browser navigates web pages. Individual -modes may be things like: - -- Title screen -- Options dialog -- About screen -- In-progress game -- Inventory interface - -The modal framework provides a simple mechanism to ensure that modes are -activated and deactivated properly. An activated mode is running and receives -events. A deactivated mode is paused and does not receive events. - -Modes may be managed as a *last-in-first-out* stack, or as a list, or ring -of modes in sequence, or some combination of all. - -For example usage see: :ref:`the mode section of the tutorial <tut-mode-section>`. -""" - -__version__ = '$Id$' - -import abc - - -class BaseManager(object): - """Mode manager abstract base class. - - The mode manager keeps a stack of modes where a single mode - is active at one time. As modes are pushed on and popped from - the stack, the mode at the top is always active. The current - active mode receives events from the manager's event dispatcher. - """ - - modes = () - """The mode stack sequence. The last mode in the stack is - the current active mode. Read-only. - """ - - @property - def current_mode(self): - """The current active mode or ``None``. Read-only""" - try: - return self.modes[-1] - except IndexError: - return None - - def on_last_mode_pop(self, mode): - """Hook executed when the last mode is popped from the manager. - Implementing this method is optional for subclasses. - - :param mode: The :class:`Mode` object just popped from the manager - """ - - def activate_mode(self, mode): - """Perform actions to activate a node - - :param mode: The :class: 'Mode' object to activate - """ - mode.activate(self) - - def deactivate_mode(self, mode): - """Perform actions to deactivate a node - - :param mode: The :class: 'Mode' object to deactivate - """ - mode.deactivate(self) - - - def push_mode(self, mode): - """Push a mode to the top of the mode stack and make it active - - :param mode: The :class:`Mode` object to make active - """ - current = self.current_mode - if current is not None: - self.deactivate_mode(current) - self.modes.append(mode) - self.activate_mode(mode) - - def pop_mode(self): - """Pop the current mode off the top of the stack and deactivate it. - The mode now at the top of the stack, if any is then activated. - - :param mode: The :class:`Mode` object popped from the stack - """ - mode = self.modes.pop() - mode.deactivate(self) - current = self.current_mode - if current is not None: - self.activate_mode(current) - else: - self.on_last_mode_pop(mode) - return mode - - def swap_modes(self, mode): - """Exchange the specified mode with the mode at the top of the stack. - This is similar to popping the current mode and pushing the specified - one, but without activating the previous mode on the stack or - executing :meth:`on_last_mode_pop()` if there is no previous mode. - - :param mode: The :class:`Mode` object that was deactivated and replaced. - """ - old_mode = self.modes.pop() - self.deactivate_mode(old_mode) - self.modes.append(mode) - self.activate_mode(mode) - return old_mode - - def remove_mode(self, mode): - """Remove the specified mode. If the mode is at the top of the stack, - this is equivilent to :meth:`pop_mode()`. If not, no other modes - are affected. If the mode is not in the manager, do nothing. - - :param mode: The :class:`Mode` object to remove from the manager. - """ - if self.current_mode is mode: - self.pop_mode() - else: - try: - self.modes.remove(mode) - except ValueError: - pass - -class BaseMode(object): - """Application mode very abstract base class - """ - __metaclass__ = abc.ABCMeta - - manager = None - """The :class:`BaseManager` that manages this mode""" - - def __init__(self): - self.active = False - - def on_activate(self): - """Being called when the Mode is activated""" - pass - - def activate(self, mode_manager): - """Activate the mode for the given mode manager, if the mode is already active, - do nothing - - The default implementation schedules time steps at :attr:`step_rate` per - second, sets the :attr:`manager` and sets the :attr:`active` flag to True. - """ - if not self.active: - self.on_activate() - self.manager = mode_manager - self.active = True - - def on_deactivate(self): - """Being called when the Mode is deactivated""" - pass - - def deactivate(self, mode_manager): - """Deactivate the mode, if the mode is not active, do nothing - - The default implementation unschedules time steps for the mode and - sets the :attr:`active` flag to False. - """ - self.on_deactivate() - self.active = False - - -class BaseMulti(BaseMode): - """A mode with multiple submodes. One submode is active at one time. - Submodes can be switched to directly or switched in sequence. If - the Multi is active, then one submode is always active. - - Multis are useful when modes can switch in an order other than - a LIFO stack, such as in "hotseat" multiplayer games, a - "wizard" style ui, or a sequence of slides. - - Note unlike a normal :class:`Mode`, a :class:`Multi` doesn't have it's own - :attr:`clock` and :attr:`step_rate`. The active submode's are used - instead. - """ - active_submode = None - """The currently active submode""" - - def __init__(self, *submodes): - # We do not invoke the superclass __init__ intentionally - self.active = False - self.submodes = list(submodes) - - def add_submode(self, mode, before=None, index=None): - """Add the submode, but do not make it active. - - :param mode: The :class:`Mode` object to add. - - :param before: The existing mode to insert the mode before. - If the mode specified is not a submode, raise - ValueError. - - :param index: The place to insert the mode in the mode list. - Only one of ``before`` or ``index`` may be specified. - - If neither ``before`` or ``index`` are specified, the - mode is appended to the end of the list. - """ - assert before is None or index is None, ( - "Cannot specify both 'before' and 'index' arguments") - if before is not None: - index = self.submodes.index(mode) - if index is not None: - self.submodes.insert(index, mode) - else: - self.submodes.append(mode) - - def remove_submode(self, mode=None): - """Remove the submode. - - :param mode: The submode to remove, if omitted the active submode - is removed. If the mode is not present, do nothing. If the - mode is active, it is deactivated, and the next mode, if any - is activated. If the last mode is removed, the :class:`Multi` - is removed from its manager. - """ - # TODO handle multiple instances of the same subnode - if mode is None: - mode = self.active_submode - elif mode not in self.submodes: - return - next_mode = self.activate_next() - self.submodes.remove(mode) - if next_mode is mode: - if self.manager is not None: - self.manager.remove_mode(self) - self._deactivate_submode() - - def activate_subnode(self, mode, before=None, index=None): - """Activate the specified mode, adding it as a subnode - if it is not already. If the mode is already the active - submode, do nothing. - - :param mode: The mode to activate, and add as necesary. - - :param before: The existing mode to insert the mode before - if it is not already a submode. If the mode specified is not - a submode, raise ValueError. - - :param index: The place to insert the mode in the mode list - if it is not already a submode. Only one of ``before`` or - ``index`` may be specified. - - If the mode is already a submode, the ``before`` and ``index`` - arguments are ignored. - """ - if mode not in self.submodes: - self.add_submode(mode, before, index) - if self.active_submode is not mode: - self._activate_submode(mode) - - def activate_next(self, loop=True): - """Activate the submode after the current submode in order. If there - is no current submode, the first submode is activated. - - Note if there is only one submode, it's active, and `loop` is True - (the default), then this method does nothing and the subnode remains - active. - - :param loop: When :meth:`activate_next` is called - when the last submode is active, a True value for ``loop`` will - cause the first submode to be activated. Otherwise the - :class:`Multi` is removed from its manager. - :type loop: bool - - :return: - The submode that was activated or None if there is no - other submode to activate. - """ - assert self.submodes, "No submode to activate" - next_mode = None - if self.active_submode is None: - next_mode = self.submodes[0] - else: - last_mode = self.active_submode - index = self.submodes.index(last_mode) + 1 - if index < len(self.submodes): - next_mode = self.submodes[index] - elif loop: - next_mode = self.submodes[0] - self._activate_submode(next_mode) - return next_mode - - def activate_previous(self, loop=True): - """Activate the submode before the current submode in order. If there - is no current submode, the last submode is activated. - - Note if there is only one submode, it's active, and `loop` is True - (the default), then this method does nothing and the subnode remains - active. - - :param loop: When :meth:`activate_previous` is called - when the first submode is active, a True value for ``loop`` will - cause the last submode to be activated. Otherwise the - :class:`Multi` is removed from its manager. - :type loop: bool - - :return: - The submode that was activated or None if there is no - other submode to activate. - """ - assert self.submodes, "No submode to activate" - prev_mode = None - if self.active_submode is None: - prev_mode = self.submodes[-1] - else: - last_mode = self.active_submode - index = self.submodes.index(last_mode) - 1 - if loop or index >= 0: - prev_mode = self.submodes[index] - self._activate_submode(prev_mode) - return prev_mode - - def _set_active_submode(self, submode): - self.active_submode = submode - self.step_rate = submode.step_rate - - def _activate_submode(self, submode): - """Activate a submode deactivating any current submode. If the Multi - itself is active, this happens immediately, otherwise the actual - activation is deferred until the Multi is activated. If the submode - is None, the Mulitmode is removed from its manager. - - If submode is already the active submode, do nothing. - """ - if self.active_submode is submode: - return - assert submode in self.submodes, "Unknown submode" - self._deactivate_submode() - self._set_active_submode(submode) - if submode is not None: - if self.active: - self.manager.activate_mode(submode) - else: - if self.manager is not None: - self.manager.remove_mode(self) - - def clear_subnode(self): - """Clear any subnmode data""" - self.active_submode = None - self.step_rate = None - - def _deactivate_submode(self, clear_subnode=True): - """Deactivate the current submode, if any. if `clear_subnode` is - True, `active_submode` is always None when this method returns - """ - if self.active_submode is not None: - if self.active: - self.manager.deactivate_mode(self.active_submode) - if clear_subnode: - self.clear_subnode() - - def activate(self, mode_manager): - """Activate the :class:`Multi` for the specified manager. The - previously active submode of the :class:`Multi` is activated. If there - is no previously active submode, then the first submode is made active. - A :class:`Multi` with no submodes cannot be activated - """ - assert self.submodes, "No submode to activate" - self.manager = mode_manager - if self.active_submode is None: - self._set_active_submode(self.submodes[0]) - else: - self._set_active_submode(self.active_submode) - self.manager.activate_mode(self.active_submode) - super(BaseMulti, self).activate(mode_manager) - - def deactivate(self, mode_manager): - """Deactivate the :class:`Multi` for the specified manager. - The `active_submode`, if any, is deactivated. - """ - self._deactivate_submode(clear_subnode=False) - super(BaseMulti, self).deactivate(mode_manager) - +############################################################################# +# +# Copyright (c) 2010 by Casey Duncan and contributors +# All Rights Reserved. +# +# This software is subject to the provisions of the MIT License +# A copy of the license should accompany this distribution. +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +# +############################################################################# +""" +Modes manage the state and transition between different application modes. +Typically such modes are presented as different screens that the user can +navigate between, similar to the way a browser navigates web pages. Individual +modes may be things like: + +- Title screen +- Options dialog +- About screen +- In-progress game +- Inventory interface + +The modal framework provides a simple mechanism to ensure that modes are +activated and deactivated properly. An activated mode is running and receives +events. A deactivated mode is paused and does not receive events. + +Modes may be managed as a *last-in-first-out* stack, or as a list, or ring +of modes in sequence, or some combination of all. + +For example usage see: :ref:`the mode section of the tutorial <tut-mode-section>`. +""" + +__version__ = '$Id$' + +import abc + + +class BaseManager(object): + """Mode manager abstract base class. + + The mode manager keeps a stack of modes where a single mode + is active at one time. As modes are pushed on and popped from + the stack, the mode at the top is always active. The current + active mode receives events from the manager's event dispatcher. + """ + + modes = () + """The mode stack sequence. The last mode in the stack is + the current active mode. Read-only. + """ + + @property + def current_mode(self): + """The current active mode or ``None``. Read-only""" + try: + return self.modes[-1] + except IndexError: + return None + + def on_last_mode_pop(self, mode): + """Hook executed when the last mode is popped from the manager. + Implementing this method is optional for subclasses. + + :param mode: The :class:`Mode` object just popped from the manager + """ + + def activate_mode(self, mode): + """Perform actions to activate a node + + :param mode: The :class: 'Mode' object to activate + """ + mode.activate(self) + + def deactivate_mode(self, mode): + """Perform actions to deactivate a node + + :param mode: The :class: 'Mode' object to deactivate + """ + mode.deactivate(self) + + + def push_mode(self, mode): + """Push a mode to the top of the mode stack and make it active + + :param mode: The :class:`Mode` object to make active + """ + current = self.current_mode + if current is not None: + self.deactivate_mode(current) + self.modes.append(mode) + self.activate_mode(mode) + + def pop_mode(self): + """Pop the current mode off the top of the stack and deactivate it. + The mode now at the top of the stack, if any is then activated. + + :param mode: The :class:`Mode` object popped from the stack + """ + mode = self.modes.pop() + mode.deactivate(self) + current = self.current_mode + if current is not None: + self.activate_mode(current) + else: + self.on_last_mode_pop(mode) + return mode + + def swap_modes(self, mode): + """Exchange the specified mode with the mode at the top of the stack. + This is similar to popping the current mode and pushing the specified + one, but without activating the previous mode on the stack or + executing :meth:`on_last_mode_pop()` if there is no previous mode. + + :param mode: The :class:`Mode` object that was deactivated and replaced. + """ + old_mode = self.modes.pop() + self.deactivate_mode(old_mode) + self.modes.append(mode) + self.activate_mode(mode) + return old_mode + + def remove_mode(self, mode): + """Remove the specified mode. If the mode is at the top of the stack, + this is equivilent to :meth:`pop_mode()`. If not, no other modes + are affected. If the mode is not in the manager, do nothing. + + :param mode: The :class:`Mode` object to remove from the manager. + """ + if self.current_mode is mode: + self.pop_mode() + else: + try: + self.modes.remove(mode) + except ValueError: + pass + +class BaseMode(object): + """Application mode very abstract base class + """ + __metaclass__ = abc.ABCMeta + + manager = None + """The :class:`BaseManager` that manages this mode""" + + def __init__(self): + self.active = False + + def on_activate(self): + """Being called when the Mode is activated""" + pass + + def activate(self, mode_manager): + """Activate the mode for the given mode manager, if the mode is already active, + do nothing + + The default implementation schedules time steps at :attr:`step_rate` per + second, sets the :attr:`manager` and sets the :attr:`active` flag to True. + """ + if not self.active: + self.on_activate() + self.manager = mode_manager + self.active = True + + def on_deactivate(self): + """Being called when the Mode is deactivated""" + pass + + def deactivate(self, mode_manager): + """Deactivate the mode, if the mode is not active, do nothing + + The default implementation unschedules time steps for the mode and + sets the :attr:`active` flag to False. + """ + self.on_deactivate() + self.active = False + + +class BaseMulti(BaseMode): + """A mode with multiple submodes. One submode is active at one time. + Submodes can be switched to directly or switched in sequence. If + the Multi is active, then one submode is always active. + + Multis are useful when modes can switch in an order other than + a LIFO stack, such as in "hotseat" multiplayer games, a + "wizard" style ui, or a sequence of slides. + + Note unlike a normal :class:`Mode`, a :class:`Multi` doesn't have it's own + :attr:`clock` and :attr:`step_rate`. The active submode's are used + instead. + """ + active_submode = None + """The currently active submode""" + + def __init__(self, *submodes): + # We do not invoke the superclass __init__ intentionally + self.active = False + self.submodes = list(submodes) + + def add_submode(self, mode, before=None, index=None): + """Add the submode, but do not make it active. + + :param mode: The :class:`Mode` object to add. + + :param before: The existing mode to insert the mode before. + If the mode specified is not a submode, raise + ValueError. + + :param index: The place to insert the mode in the mode list. + Only one of ``before`` or ``index`` may be specified. + + If neither ``before`` or ``index`` are specified, the + mode is appended to the end of the list. + """ + assert before is None or index is None, ( + "Cannot specify both 'before' and 'index' arguments") + if before is not None: + index = self.submodes.index(mode) + if index is not None: + self.submodes.insert(index, mode) + else: + self.submodes.append(mode) + + def remove_submode(self, mode=None): + """Remove the submode. + + :param mode: The submode to remove, if omitted the active submode + is removed. If the mode is not present, do nothing. If the + mode is active, it is deactivated, and the next mode, if any + is activated. If the last mode is removed, the :class:`Multi` + is removed from its manager. + """ + # TODO handle multiple instances of the same subnode + if mode is None: + mode = self.active_submode + elif mode not in self.submodes: + return + next_mode = self.activate_next() + self.submodes.remove(mode) + if next_mode is mode: + if self.manager is not None: + self.manager.remove_mode(self) + self._deactivate_submode() + + def activate_subnode(self, mode, before=None, index=None): + """Activate the specified mode, adding it as a subnode + if it is not already. If the mode is already the active + submode, do nothing. + + :param mode: The mode to activate, and add as necesary. + + :param before: The existing mode to insert the mode before + if it is not already a submode. If the mode specified is not + a submode, raise ValueError. + + :param index: The place to insert the mode in the mode list + if it is not already a submode. Only one of ``before`` or + ``index`` may be specified. + + If the mode is already a submode, the ``before`` and ``index`` + arguments are ignored. + """ + if mode not in self.submodes: + self.add_submode(mode, before, index) + if self.active_submode is not mode: + self._activate_submode(mode) + + def activate_next(self, loop=True): + """Activate the submode after the current submode in order. If there + is no current submode, the first submode is activated. + + Note if there is only one submode, it's active, and `loop` is True + (the default), then this method does nothing and the subnode remains + active. + + :param loop: When :meth:`activate_next` is called + when the last submode is active, a True value for ``loop`` will + cause the first submode to be activated. Otherwise the + :class:`Multi` is removed from its manager. + :type loop: bool + + :return: + The submode that was activated or None if there is no + other submode to activate. + """ + assert self.submodes, "No submode to activate" + next_mode = None + if self.active_submode is None: + next_mode = self.submodes[0] + else: + last_mode = self.active_submode + index = self.submodes.index(last_mode) + 1 + if index < len(self.submodes): + next_mode = self.submodes[index] + elif loop: + next_mode = self.submodes[0] + self._activate_submode(next_mode) + return next_mode + + def activate_previous(self, loop=True): + """Activate the submode before the current submode in order. If there + is no current submode, the last submode is activated. + + Note if there is only one submode, it's active, and `loop` is True + (the default), then this method does nothing and the subnode remains + active. + + :param loop: When :meth:`activate_previous` is called + when the first submode is active, a True value for ``loop`` will + cause the last submode to be activated. Otherwise the + :class:`Multi` is removed from its manager. + :type loop: bool + + :return: + The submode that was activated or None if there is no + other submode to activate. + """ + assert self.submodes, "No submode to activate" + prev_mode = None + if self.active_submode is None: + prev_mode = self.submodes[-1] + else: + last_mode = self.active_submode + index = self.submodes.index(last_mode) - 1 + if loop or index >= 0: + prev_mode = self.submodes[index] + self._activate_submode(prev_mode) + return prev_mode + + def _set_active_submode(self, submode): + self.active_submode = submode + self.step_rate = submode.step_rate + + def _activate_submode(self, submode): + """Activate a submode deactivating any current submode. If the Multi + itself is active, this happens immediately, otherwise the actual + activation is deferred until the Multi is activated. If the submode + is None, the Mulitmode is removed from its manager. + + If submode is already the active submode, do nothing. + """ + if self.active_submode is submode: + return + assert submode in self.submodes, "Unknown submode" + self._deactivate_submode() + self._set_active_submode(submode) + if submode is not None: + if self.active: + self.manager.activate_mode(submode) + else: + if self.manager is not None: + self.manager.remove_mode(self) + + def clear_subnode(self): + """Clear any subnmode data""" + self.active_submode = None + self.step_rate = None + + def _deactivate_submode(self, clear_subnode=True): + """Deactivate the current submode, if any. if `clear_subnode` is + True, `active_submode` is always None when this method returns + """ + if self.active_submode is not None: + if self.active: + self.manager.deactivate_mode(self.active_submode) + if clear_subnode: + self.clear_subnode() + + def activate(self, mode_manager): + """Activate the :class:`Multi` for the specified manager. The + previously active submode of the :class:`Multi` is activated. If there + is no previously active submode, then the first submode is made active. + A :class:`Multi` with no submodes cannot be activated + """ + assert self.submodes, "No submode to activate" + self.manager = mode_manager + if self.active_submode is None: + self._set_active_submode(self.submodes[0]) + else: + self._set_active_submode(self.active_submode) + self.manager.activate_mode(self.active_submode) + super(BaseMulti, self).activate(mode_manager) + + def deactivate(self, mode_manager): + """Deactivate the :class:`Multi` for the specified manager. + The `active_submode`, if any, is deactivated. + """ + self._deactivate_submode(clear_subnode=False) + super(BaseMulti, self).deactivate(mode_manager) +