# Author: Roberto Cavada # # Copyright (c) 2005 by Roberto Cavada # # pygtkmvc is free software; you can redistribute it and/or # modify it under the terms of the GNU Lesser General Public # License as published by the Free Software Foundation; either # version 2 of the License, or (at your option) any later version. # # pygtkmvc is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU # Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public # License along with this library; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. # # For more information on pygtkmvc see # or email to the author Roberto Cavada . # Please report bugs to . import support.metaclasses from support.wrappers import ObsWrapperBase from observable import Signal class Model (object): """This class is the application model base class. It handles a set of observable properties which you are interested in showing by one ore more view - via one or more observers of course. The mechanism is the following: 1. You are interested in showing a set of model property, that you can declare in the __properties__ member map. 2. You define one or more observers that observe one or more properties you registered. When someone changes a property value the model notifies the changing to each listening controller. The property-observer[s] association is given by the implicit rule in observers method names: if you want the model notified the changing event of the property 'p' you must define the method called 'property_p_change_notification' in each listening observer class. Notice that tipically 'controllers' implement the observer pattern. The notification method gets the emitting model, the old value for the property and the new one. Properties functionalities are automatically provided by the ObservablePropertyMeta meta-class.""" __metaclass__ = support.metaclasses.ObservablePropertyMeta __properties__ = {} # override this def __init__(self): object.__init__(self) self.__observers = [] # keys are properties names, values are methods inside the observer: self.__value_notifications = {} self.__instance_notif_before = {} self.__instance_notif_after = {} self.__signal_notif = {} for key in (self.__properties__.keys() + self.__derived_properties__.keys()): self.register_property(key) pass return def register_property(self, name): """Registers an existing property to be monitored, and sets up notifiers for notifications""" if not self.__value_notifications.has_key(name): self.__value_notifications[name] = [] pass # registers observable wrappers prop = getattr(self, "_prop_%s" % name) if isinstance(prop, ObsWrapperBase): prop.__set_model__(self, name) if isinstance(prop, Signal): if not self.__signal_notif.has_key(name): self.__signal_notif[name] = [] pass pass else: if not self.__instance_notif_before.has_key(name): self.__instance_notif_before[name] = [] pass if not self.__instance_notif_after.has_key(name): self.__instance_notif_after[name] = [] pass pass pass return def register_observer(self, observer): if observer in self.__observers: return # not already registered self.__observers.append(observer) for key in (self.__properties__.keys() + self.__derived_properties__.keys()): self.__add_observer_notification(observer, key) pass return def unregister_observer(self, observer): if observer not in self.__observers: return for key in (self.__properties__.keys() + self.__derived_properties__.keys()): self.__remove_observer_notification(observer, key) pass self.__observers.remove(observer) return def _reset_property_notification(self, prop_name): """Called when it has done an assignment that changes the type of a property, so it must be unregistered and registered again""" self.register_property(prop_name) for observer in self.__observers: self.__remove_observer_notification(observer, prop_name) self.__add_observer_notification(observer, prop_name) pass return def __add_observer_notification(self, observer, prop_name): """Searches in the observer for any possible listener, and stores the notification methods to be called later""" method_name = "property_%s_value_change" % prop_name if hasattr(observer, method_name): method = getattr(observer, method_name) if method not in self.__value_notifications[prop_name]: list.append(self.__value_notifications[prop_name], method) pass pass # is it a signal? orig_prop = getattr(self, "_prop_%s" % prop_name) if isinstance(orig_prop, Signal): method_name = "property_%s_signal_emit" % prop_name if hasattr(observer, method_name): method = getattr(observer, method_name) if method not in self.__signal_notif[prop_name]: list.append(self.__signal_notif[prop_name], method) pass pass pass # is it an instance change notification type? elif isinstance(orig_prop, ObsWrapperBase): method_name = "property_%s_before_change" % prop_name if hasattr(observer, method_name): method = getattr(observer, method_name) if method not in self.__instance_notif_before[prop_name]: list.append(self.__instance_notif_before[prop_name], method) pass pass method_name = "property_%s_after_change" % prop_name if hasattr(observer, method_name): method = getattr(observer, method_name) if method not in self.__instance_notif_after[prop_name]: list.append(self.__instance_notif_after[prop_name], method) pass pass pass return def __remove_observer_notification(self, observer, prop_name): if self.__value_notifications.has_key(prop_name): method_name = "property_%s_value_change" % prop_name if hasattr(observer, method_name): method = getattr(observer, method_name) if method in self.__value_notifications[prop_name]: self.__value_notifications[prop_name].remove(method) pass pass pass orig_prop = getattr(self, "_prop_%s" % prop_name) # is it a signal? if isinstance(orig_prop, Signal): method_name = "property_%s_signal_emit" % prop_name if hasattr(observer, method_name): method = getattr(observer, method_name) if method in self.__signal_notif[prop_name]: self.__signal_notif[prop_name].remove(method) pass pass pass # is it an instance change notification type? elif isinstance(orig_prop, ObsWrapperBase): if self.__instance_notif_before.has_key(prop_name): method_name = "property_%s_before_change" % prop_name if hasattr(observer, method_name): method = getattr(observer, method_name) if method in self.__instance_notif_before[prop_name]: self.__instance_notif_before[prop_name].remove(method) pass pass pass if self.__instance_notif_after.has_key(prop_name): method_name = "property_%s_after_change" % prop_name if hasattr(observer, method_name): method = getattr(observer, method_name) if method in self.__instance_notif_after[prop_name]: self.__instance_notif_after[prop_name].remove(method) pass pass pass pass return def __notify_observer__(self, observer, method, *args, **kwargs): """This can be overridden by derived class in order to call the method in a different manner (for example, in multithreading, or a rpc, etc.) This implementation simply calls the given method with the given arguments""" return method(*args, **kwargs) # ---------- Notifiers: def notify_property_value_change(self, prop_name, old, new): assert(self.__value_notifications.has_key(prop_name)) for method in self.__value_notifications[prop_name] : self.__notify_observer__(method.im_self, method, self, old, new) # notifies the change pass return def notify_method_before_change(self, prop_name, instance, meth_name, args, kwargs): assert(self.__instance_notif_before.has_key(prop_name)) for method in self.__instance_notif_before[prop_name] : self.__notify_observer__(method.im_self, method, self, instance, meth_name, args, kwargs) # notifies the change pass return def notify_method_after_change(self, prop_name, instance, meth_name, res, args, kwargs): assert(self.__instance_notif_after.has_key(prop_name)) for method in self.__instance_notif_after[prop_name] : self.__notify_observer__(method.im_self, method, self, instance, meth_name, res, args, kwargs) # notifies the change pass return def notify_signal_emit(self, prop_name, args, kwargs): assert(self.__signal_notif.has_key(prop_name)) for method in self.__signal_notif[prop_name] : self.__notify_observer__(method.im_self, method, self, args, kwargs) # notifies the signal emit pass return pass # end of class Model # ---------------------------------------------------------------------- import gtk # ---------------------------------------------------------------------- class TreeStoreModel (Model, gtk.TreeStore): """Use this class as base class for your model derived by gtk.TreeStore""" __metaclass__ = support.metaclasses.ObservablePropertyGObjectMeta def __init__(self, column_type, *args): Model.__init__(self) gtk.TreeStore.__init__(self, column_type, *args) return pass # ---------------------------------------------------------------------- class ListStoreModel (Model, gtk.ListStore): """Use this class as base class for your model derived by gtk.ListStore""" __metaclass__ = support.metaclasses.ObservablePropertyGObjectMeta def __init__(self, column_type, *args): Model.__init__(self) gtk.ListStore.__init__(self, column_type, *args) return pass # ---------------------------------------------------------------------- class TextBufferModel (Model, gtk.TextBuffer): """Use this class as base class for your model derived by gtk.TextBuffer""" __metaclass__ = support.metaclasses.ObservablePropertyGObjectMeta def __init__(self, table=None): Model.__init__(self) gtk.TextBuffer.__init__(self, table) return pass