# # Python GUI - Models - Generic # import weakref from GUI import export from GUI.Properties import Properties, overridable_property # List of views for a model is kept separately so that models # can be pickled without fear of accidentally trying to pickle # the user interface. _model_views = weakref.WeakKeyDictionary() # {Model: [object]} class Model(Properties): """A Model represents an application object which can appear in a View. Each Model can have any number of Views attached to it. When a Model is changed, it should notify all of its Views so that they can update themselves. The 'parent' attribute of a Model is treated specially when pickling. If it refers to an object having a 'pickle_as_parent_model' attribute whose value is false, the 'parent' attribute is not pickled. This allows a Model to have a Document as a parent without the Document being pickled along with the Model. """ views = overridable_property('views', "List of objects observing this model. Do not modify directly.") parent = None # Model def __init__(self, parent = None, **kwds): Properties.__init__(self, **kwds) if parent: self.parent = parent def destroy(self): """All views currently observing this model are removed, and their 'model_destroyed' methods, if any, are called with the model as an argument.""" for view in self.views[:]: self.remove_view(view) self._call_if_present(view, 'model_destroyed', self) # # Properties # def get_views(self): views = _model_views.get(self) if views is None: views = [] _model_views[self] = views return views # # Pickling behaviour # def __getstate__(self): state = self.__dict__ parent = self.parent if not getattr(parent, 'pickle_as_parent_model', True): state = state.copy() del state['parent'] return state def __setstate__(self, state): self.__dict__.update(state) # # Adding and removing views # def add_view(self, view): """Add the given object as an observer of this model. The view will typically be a View subclass, but need not be. If the view is not already an observer of this model and defines an 'add_model' method, this method is called with the model as an argument.""" views = self.views if view not in views: views.append(view) self._call_if_present(view, 'add_model', self) def remove_view(self, view): """If the given object is currently an observer of this model, it is removed, and if it defines a 'remove_model' method, this method is called with the model as an argument.""" views = self.views if view in views: views.remove(view) self._call_if_present(view, 'remove_model', self) # # View notification # def notify_views(self, message = 'model_changed', *args, **kwds): """For each observer, if the observer defines a method with the name of the message, call it with the given arguments. Otherwise, if it defines a method called 'model_changed', call it with no arguments. Otherwise, do nothing for that observer.""" for view in self.views: if not self._call_if_present(view, message, self, *args, **kwds): self._call_if_present(view, 'model_changed', self) def _call_if_present(self, obj, method_name, *args, **kwds): method = getattr(obj, method_name, None) if method: method(*args, **kwds) return 1 else: return 0 # # Marking as changed # def changed(self): "Mark the containing Document as changed." parent = self.parent if parent: parent.changed() export(Model)