From 4dcfaa51e705c05c3d3384c194f2d216b6bd181b Mon Sep 17 00:00:00 2001 From: gryf Date: Wed, 24 Oct 2007 17:29:31 +0000 Subject: [PATCH] * Upgrade pygtkmvc to ne version 1.2.1. --- {gtkmvc => src/gtkmvc}/__init__.py | 7 +- src/gtkmvc/adapters/__init__.py | 25 + src/gtkmvc/adapters/basic.py | 429 ++++++++++++++++++ src/gtkmvc/adapters/containers.py | 217 +++++++++ src/gtkmvc/adapters/default.py | 55 +++ src/gtkmvc/controller.py | 218 +++++++++ {gtkmvc => src/gtkmvc}/model.py | 68 ++- {gtkmvc => src/gtkmvc}/model_mt.py | 3 +- {gtkmvc => src/gtkmvc}/observable.py | 3 +- {gtkmvc => src/gtkmvc}/observer.py | 39 +- {gtkmvc => src/gtkmvc}/support/__init__.py | 3 +- {gtkmvc => src/gtkmvc}/support/decorators.py | 3 +- .../gtkmvc/support/exceptions.py | 26 +- .../gtkmvc}/support/metaclass_base.py | 43 +- {gtkmvc => src/gtkmvc}/support/metaclasses.py | 3 +- src/gtkmvc/support/utils.py | 39 ++ {gtkmvc => src/gtkmvc}/support/wrappers.py | 20 +- {gtkmvc => src/gtkmvc}/view.py | 97 ++-- 18 files changed, 1180 insertions(+), 118 deletions(-) rename {gtkmvc => src/gtkmvc}/__init__.py (92%) create mode 100644 src/gtkmvc/adapters/__init__.py create mode 100644 src/gtkmvc/adapters/basic.py create mode 100644 src/gtkmvc/adapters/containers.py create mode 100644 src/gtkmvc/adapters/default.py create mode 100644 src/gtkmvc/controller.py rename {gtkmvc => src/gtkmvc}/model.py (86%) rename {gtkmvc => src/gtkmvc}/model_mt.py (98%) rename {gtkmvc => src/gtkmvc}/observable.py (96%) rename {gtkmvc => src/gtkmvc}/observer.py (51%) rename {gtkmvc => src/gtkmvc}/support/__init__.py (92%) rename {gtkmvc => src/gtkmvc}/support/decorators.py (95%) rename gtkmvc/controller.py => src/gtkmvc/support/exceptions.py (53%) rename {gtkmvc => src/gtkmvc}/support/metaclass_base.py (87%) rename {gtkmvc => src/gtkmvc}/support/metaclasses.py (97%) create mode 100644 src/gtkmvc/support/utils.py rename {gtkmvc => src/gtkmvc}/support/wrappers.py (90%) rename {gtkmvc => src/gtkmvc}/view.py (71%) diff --git a/gtkmvc/__init__.py b/src/gtkmvc/__init__.py similarity index 92% rename from gtkmvc/__init__.py rename to src/gtkmvc/__init__.py index f7a16fd..fe39d3d 100644 --- a/gtkmvc/__init__.py +++ b/src/gtkmvc/__init__.py @@ -14,7 +14,8 @@ # # 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. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, +# Boston, MA 02110, USA. # # For more information on pygtkmvc see # or email to the author Roberto Cavada . @@ -23,7 +24,7 @@ __all__ = ["model", "view", "controller", "observable", "observer"] -__version = (1,0,1) +__version = (1,2,1) from model import Model, TreeStoreModel, ListStoreModel, TextBufferModel from model_mt import ModelMT @@ -31,7 +32,7 @@ from controller import Controller from view import View from observer import Observer import observable - +import adapters def get_version(): return __version diff --git a/src/gtkmvc/adapters/__init__.py b/src/gtkmvc/adapters/__init__.py new file mode 100644 index 0000000..1dbb3d2 --- /dev/null +++ b/src/gtkmvc/adapters/__init__.py @@ -0,0 +1,25 @@ +# Author: Roberto Cavada +# +# Copyright (c) 2007 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., 51 Franklin Street, Fifth Floor, +# Boston, MA 02110, USA. +# +# For more information on pygtkmvc see +# or email to the author Roberto Cavada . +# Please report bugs to . + +from gtkmvc.adapters.basic import Adapter, UserClassAdapter, RoUserClassAdapter +from gtkmvc.adapters.containers import StaticContainerAdapter diff --git a/src/gtkmvc/adapters/basic.py b/src/gtkmvc/adapters/basic.py new file mode 100644 index 0000000..a24910d --- /dev/null +++ b/src/gtkmvc/adapters/basic.py @@ -0,0 +1,429 @@ +# Author: Roberto Cavada +# +# Copyright (c) 2007 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., 51 Franklin Street, Fifth Floor, +# Boston, MA 02110, USA. +# +# For more information on pygtkmvc see +# or email to the author Roberto Cavada . +# Please report bugs to . + + +import types +import gtk +import time + +from gtkmvc.adapters.default import * +from gtkmvc.observer import Observer + + +# ---------------------------------------------------------------------- +class Adapter (Observer): + + def __init__(self, model, prop_name, + prop_read=None, prop_write=None, + value_error=None): + """ + Creates a new adapter that handles setting of value of a + model single model property when a corresponding widgets set + is changed and viceversa when the property is also + observable. + + This class handles only assignments to properties. For other + kinds of setting (e.g. user-defined classes used as + observable properties, containers, etc.) use other types of + Adapters derived from this class. + + prop_name is the model's property name (as a string). It is + possible to use a dotted notation to identify a property + contained into a hierarchy of models. For example 'a.b.c' + identifies property 'c' into model 'b' inside model 'a', + where model 'a' is an attribute of given top level model. + Last name must be an observable or non-observable attribute, + and previous names (if specified) must all refer to + instances of class Model. First name from the left must be + the name of a model instance inside the given model. + + prop_{write,read} are two optional functions that apply + custom modifications to the value of the property before + setting and reading it. Both take a value and must return a + transformed value whose type must be compatible with the + type of the property. + + value_error can be a function (or a method) to be called + when a ValueError exception occurs while trying to set a + wrong value for the property inside the model. The function + will receive: the adapter, the property name and the value + coming from the widget that offended the model. + """ + + # registration is delayed, as we need to create possible + # listener before: + Observer.__init__(self) + + self._prop_name = prop_name + self._prop_read = prop_read + self._prop_write = prop_write + self._value_error = value_error + self._wid = None + self._wid_info = {} + + # this flag is set when self is changing the property or the + # widget, in order to avoid infinite looping. + self._itsme = False + + self._connect_model(model) + return + + def connect_widget(self, wid, + getter=None, setter=None, + signal=None, arg=None, update=True): + + """ + Called when the widget is instantiated, and the adapter is + ready to connect the widget and the property inside the + observed model. arg is the (optional) argument that will be + passed when connecting the signal. + + getter and setter are the (optional) methods used + for reading and writing the widget's value. When not + specified, default getter and setter will be guessed by + looking at the widget type the adapter will be connected + with. Guessing is carried out by querying information + specified into module 'adapters.default'. + + Finally, if update is false, the widget will not be updated + """ + + if self._wid_info.has_key(wid): + raise ValueError("Widget " + str(wid) + " was already connected") + + wid_type = None + + if None in (getter, setter, signal): + w = search_adapter_info(wid) + if getter is None: getter = w[GETTER] + if setter is None: + setter = w[SETTER] + wid_type = w[WIDTYPE] + pass + + if signal is None: signal = w[SIGNAL] + pass + + # saves information about the widget + self._wid_info[wid] = (getter, setter, wid_type) + + # connects the widget + if signal: + if arg: wid.connect(signal, self._on_wid_changed, arg) + else: wid.connect(signal, self._on_wid_changed) + pass + + self._wid = wid + + # updates the widget: + if update: self.update_widget() + return + + def update_model(self): + """Forces the property to be updated from the value hold by + the widget. This method should be called directly by the + user in very unusual coditions.""" + self._write_property(self._read_widget()) + return + + def update_widget(self): + """Forces the widget to be updated from the property + value. This method should be called directly by the user + when the property is not observable, or in very unusual + coditions.""" + self._write_widget(self._read_property()) + return + + + # ---------------------------------------------------------------------- + # Private methods + # ---------------------------------------------------------------------- + def _connect_model(self, model): + """ + Used internally to connect the property into the model, and + register self as a value observer for that property""" + + parts = self._prop_name.split(".") + if len(parts) > 1: + # identifies the model + models = parts[:-1] + for name in models: + model = getattr(model, name) + if not isinstance(model, gtkmvc.Model): + raise TypeError("Attribute '" + name + + "' was expected to be a Model, but found: " + + str(model)) + pass + prop = parts[-1] + else: prop = parts[0] + + # prop is inside model? + if not hasattr(model, prop): + raise ValueError("Attribute '" + prop + + "' not found in model " + str(model)) + + # is it observable? + if model.has_property(prop): + # we need to create an observing method before registering + self._add_method(self._get_observer_src(prop)) + pass + + self._prop = getattr(model, prop) + self._prop_name = prop + + # registration of model: + self.register_model(model) + return + + + def _get_observer_src(self, prop_name): + """This is the code for an value change observer""" + return """def property_%s_value_change(self, model, old, new): + if self._itsme or old == new: return + self._on_prop_changed()""" % prop_name + + + def _add_method(self, src): + """Private service to add a new method to the instance, + given method code""" + + from gtkmvc.support.utils import get_function_from_source + import new + + func = get_function_from_source(src) + meth = new.instancemethod(func, self, self.__class__) + setattr(self, func.__name__, meth) + return + + def _get_property(self): + """Private method that returns the value currently stored + into the property""" + #return getattr(self.get_model(), self._prop_name) + return self._prop + + def _set_property(self, val): + """Private method that sets the value currently of the property.""" + return setattr(self.get_model(), self._prop_name, val) + + def _read_property(self, *args): + """Returns the (possibly transformed) value that is stored + into the property""" + if self._prop_read: return self._prop_read(self._get_property(*args)) + return self._get_property(*args) + + def _write_property(self, val, *args): + """Sets the value of property. Given val is transformed + accodingly to prop_write function when specified at + construction-time. A try to cast the value to the property + type is given.""" + + # 'finally' would be better here, but not supported in 2.4 :( + try: + totype = type(self._get_property(*args)) + val_prop = self._cast_value(val, totype) + if self._prop_write: val_prop = self._prop_write(val_prop) + + self._itsme = True + self._set_property(val_prop, *args) + + except ValueError: + self._itsme = False + if self._value_error: self._value_error(self, self._prop_name, val) + else: raise + pass + + except: self._itsme = False; raise + + self._itsme = False + return + + def _read_widget(self): + """Returns the value currently stored into the widget, after + transforming it accordingly to possibly specified function.""" + getter = self._wid_info[self._wid][0] + return getter(self._wid) + + def _write_widget(self, val): + """Writes value into the widget. If specified, user setter + is invoked.""" + self._itsme = True + try: + setter = self._wid_info[self._wid][1] + wtype = self._wid_info[self._wid][2] + if wtype is not None: setter(self._wid, self._cast_value(val, wtype)) + else: setter(self._wid, val) + finally: + self._itsme = False + pass + + return + + def _cast_value(self, val, totype): + """Casts given val to given totype. Raises TypeError if not able to cast.""" + t = type(val) + if issubclass(t, totype): return val + if issubclass(totype, types.StringType): return str(val) + if issubclass(t, types.StringType): + if issubclass(totype, types.IntType): + if val: return int(float(val)) + return 0 + if issubclass(totype, types.FloatType): + if val: return float(val) + return 0.0 + pass + + raise TypeError("Not able to cast " + str(t) + " to " + str(totype)) + + + # ---------------------------------------------------------------------- + # Callbacks and observation + # ---------------------------------------------------------------------- + + def _on_wid_changed(self, wid): + """Called when the widget is changed""" + if self._itsme: return + self.update_model() + return + + def _on_prop_changed(self): + """Called by the observation code, when the value in the + observed property is changed""" + if self._itsme: return + self.update_widget() + return + + pass # end of class Adapter + + + +#---------------------------------------------------------------------- +class UserClassAdapter (Adapter): + """ + This class handles the communication between a widget and a + class instance (possibly observable) that is a property inside + the model. The value to be shown is taken and stored by using a + getter and a setter. getter and setter can be: names of user + class methods, bound or unbound methods of the user class, or a + function that will receive the user class instance and possible + arguments whose number depends on whether it is a getter or a + setter.""" + + def __init__(self, model, prop_name, + getter, setter, + prop_read=None, prop_write=None, + value_error=None): + + Adapter.__init__(self, model, prop_name, + prop_read, prop_write, value_error) + + self._getter = self._resolve_to_func(getter) + self._setter = self._resolve_to_func(setter) + return + + # ---------------------------------------------------------------------- + # Private methods + # ---------------------------------------------------------------------- + + def _resolve_to_func(self, what): + """This method resolves whatever is passed: a string, a + bound or unbound method, a function, to make it a + function. This makes internal handling of setter and getter + uniform and easier.""" + if isinstance(what, types.StringType): + what = getattr(Adapter._get_property(self), what) + pass + + # makes it an unbounded function if needed + if type(what) == types.MethodType: what = what.im_func + + if not type(what) == types.FunctionType: raise TypeError("Expected a method name, a method or a function") + return what + + + def _get_observer_src(self, prop_name): + """This is the code for a method after_change observer""" + return """def property_%s_after_change(self, model, \ + instance, meth_name, res, args, kwargs): + if self._itsme: return + self._on_prop_changed(instance, meth_name, res, args, kwargs)""" % prop_name + + + def _on_prop_changed(self, instance, meth_name, res, args, kwargs): + """Called by the observation code, when a modifying method + is called""" + Adapter._on_prop_changed(self) + return + + def _get_property(self, *args): + """Private method that returns the value currently stored + into the property""" + val = self._getter(Adapter._get_property(self), *args) + if self._prop_read: return self._prop_read(val, *args) + return val + + def _set_property(self, val, *args): + """Private method that sets the value currently of the property""" + if self._prop_write: val = self._prop_write(val) + return self._setter(Adapter._get_property(self), val, *args) + + pass # end of class UserClassAdapter +# ---------------------------------------------------------------------- + + + +#---------------------------------------------------------------------- +class RoUserClassAdapter (UserClassAdapter): + """ + This class is for Read-Only user classes. RO classes are those + whose setting methods do not change the instance, but return a + new instance that has been changed accordingly to the setters + semantics. An example is python datetime class, whose replace + method does not change the instance it is invoked on, but + returns a new datetime instance. + + This class is likely to be used very rarely. + """ + + def __init__(self, model, prop_name, + getter, setter, + prop_read=None, prop_write=None, + value_error=None): + + UserClassAdapter.__init__(self, model, prop_name, + getter, setter, + prop_read, prop_write, value_error) + + return + + # ---------------------------------------------------------------------- + # Private methods + # ---------------------------------------------------------------------- + + def _set_property(self, val, *args): + """Private method that sets the value currently of the property""" + val = UserClassAdapter._set_property(self, val, *args) + if val: Adapter._set_property(self, val, *args) + return val + + pass # end of class RoUserClassAdapter +# ---------------------------------------------------------------------- diff --git a/src/gtkmvc/adapters/containers.py b/src/gtkmvc/adapters/containers.py new file mode 100644 index 0000000..73bebdc --- /dev/null +++ b/src/gtkmvc/adapters/containers.py @@ -0,0 +1,217 @@ +# Author: Roberto Cavada +# +# Copyright (c) 2007 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., 51 Franklin Street, Fifth Floor, +# Boston, MA 02110, USA. +# +# For more information on pygtkmvc see +# or email to the author Roberto Cavada . +# Please report bugs to . + + +import types +import gtk + +from gtkmvc.adapters.basic import UserClassAdapter + +from gtkmvc.adapters.default import * +from gtkmvc.observer import Observer +from gtkmvc.support.wrappers import ObsMapWrapper + +# ---------------------------------------------------------------------- +class StaticContainerAdapter (UserClassAdapter): + """ + This class can be used to bound a set of widgets to a property + that is a container, like a tuple, a list or a map, or in + general a class that implements __getitem__ and __setitem__ + methods. + + From the other hand, the set of widgets can be a list provided + by the user, or a container widget like a Box, a notebook, etc. + Widgets will be linked by their position when the property is + list-like, or by their name when the property is map-like. + + This class supports only properties that are static containers, + i.e. those containers that do not change their length + dynamically. If the container grows up in length, no change will + occur in the view-side. + """ + + def __init__(self, model, prop_name, + prop_read=None, prop_write=None, value_error=None): + + UserClassAdapter.__init__(self, model, prop_name, + lambda c,i: c.__getitem__(i), + lambda c,v,i: c.__setitem__(i,v), + prop_read, prop_write, + value_error) + + prop = self._get_property() + if not (hasattr(prop, "__getitem__") and + hasattr(prop, "__setitem__")): + raise TypeError("Property " + self._prop_name + + " is not a valid container") + + + self._prop_is_map = isinstance(prop, types.DictType) or \ + isinstance(prop, ObsMapWrapper) + # contained widgets + self._idx2wid = {} + self._wid2idx = {} + + self._widgets = None + return + + + def connect_widget(self, wid, getters=None, setters=None, + signals=None, arg=None): + """ + Called when the widget is instantiated, and the adapter is + ready to connect the widgets inside it (if a container) or + each widget if wid is a list of widgets. getters and setters + can be None, a function or a list or a map of + functions. signals can be None, a signal name, or a list or + a map of signal names. When maps are used, keys can be + widgets or widget names. The length of the possible lists or + maps must be lesser or equal to the number of widgets that + will be connected. + """ + + if isinstance(wid, gtk.Container): self._widgets = wid.get_children() + elif isinstance(wid, types.ListType) or isinstance(wid, types.TupleType): self._widgets = wid + else: raise TypeError("widget must be either a gtk.Container or a list or tuple") + + # prepares the mappings: + for idx, w in enumerate(self._widgets): + if self._prop_is_map: idx=w.get_name() + self._idx2wid[idx] = w + self._wid2idx[w] = idx + pass + + # prepares the lists for signals + getters = self.__handle_par("getters", getters) + setters = self.__handle_par("setters", setters) + signals = self.__handle_par("signals", signals) + + for wi,ge,se,si in zip(self._widgets, getters, setters, signals): + if type(ge) == types.MethodType: ge = ge.im_func + if type(se) == types.MethodType: se = se.im_func + UserClassAdapter.connect_widget(self, wi, ge, se, si, arg, False) + pass + + self.update_widget() + self._wid = wid + return + + + def update_model(self, idx=None): + """Updates the value of property at given index. If idx is + None, all controlled indices will be updated. This method + should be called directly by the user in very unusual + conditions.""" + if idx is None: + for w in self._widgets: + idx = self._get_idx_from_widget(w) + self._write_property(self._read_widget(idx), idx) + pass + pass + else: self._write_property(self._read_widget(idx), idx) + return + + def update_widget(self, idx=None): + """Forces the widget at given index to be updated from the + property value. If index is not given, all controlled + widgets will be updated. This method should be called + directly by the user when the property is not observable, or + in very unusual conditions.""" + if idx is None: + for w in self._widgets: + idx = self._get_idx_from_widget(w) + self._write_widget(self._read_property(idx), idx) + pass + else: self._write_widget(self._read_property(idx), idx) + return + + # ---------------------------------------------------------------------- + # Private methods + # ---------------------------------------------------------------------- + + def _get_idx_from_widget(self, wid): + """Given a widget, returns the corresponding index for the + model. Returned value can be either an integer or a string""" + return self._wid2idx[wid] + + def _get_widget_from_idx(self, idx): + """Given an index, returns the corresponding widget for the view. + Given index can be either an integer or a string""" + return self._idx2wid[idx] + + + def _read_widget(self, idx): + sav = self._wid + self._wid = self._get_widget_from_idx(idx) + val = UserClassAdapter._read_widget(self) + self._wid = sav + return val + + def _write_widget(self, val, idx): + sav = self._wid + self._wid = self._get_widget_from_idx(idx) + UserClassAdapter._write_widget(self, val) + self._wid = sav + return + + # This is a private service to factorize code of connect_widget + def __handle_par(self, name, par): + if par is None or type(par) in (types.FunctionType, + types.MethodType, types.StringType): + par = [par] * len(self._widgets) + pass + + elif isinstance(par, types.DictType): + val = [] + for w in self._widgets: + if par.has_key(w): val.append(par[w]) + elif par.has_key(w.get_name()): val.append(par[w.get_name()]) + else: val.append(None) + pass + par = val + pass + + elif isinstance(par, types.ListType) or isinstance(par, types.TupleType): + par = list(par) + par.extend([None]*(len(self._widgets)-len(par))) + pass + + else: raise TypeError("Parameter %s has an invalid type (should be None, a sequence or a string)" % name) + + return par + + + # Callbacks: + def _on_wid_changed(self, wid): + """Called when the widget is changed""" + if self._itsme: return + self.update_model(self._get_idx_from_widget(wid)) + return + + def _on_prop_changed(self, instance, meth_name, res, args, kwargs): + """Called by the observation code, we are interested in + __setitem__""" + if not self._itsme and meth_name == "__setitem__": self.update_widget(args[0]) + return + + pass # end of class StaticContainerAdapter diff --git a/src/gtkmvc/adapters/default.py b/src/gtkmvc/adapters/default.py new file mode 100644 index 0000000..43058f4 --- /dev/null +++ b/src/gtkmvc/adapters/default.py @@ -0,0 +1,55 @@ +__all__ = ("search_adapter_info", + "SIGNAL", "GETTER", "SETTER", "WIDTYPE") + +import types +import gtk + +# ---------------------------------------------------------------------- +# This list defines a default behavior for widgets. +# If no particular behaviour has been specified, adapters will +# use information contained into this list to create themself. +# This list is ordered: the earlier a widget occurs, the better it +# will be matched by the search function. +# ---------------------------------------------------------------------- +__def_adapter = [ # class, default signal, getter, setter, value type + (gtk.Entry, "changed", gtk.Entry.get_text, gtk.Entry.set_text, types.StringType), + (gtk.Label, None, gtk.Label.get_text, gtk.Label.set_text, types.StringType), + (gtk.Arrow, None, lambda a: a.get_property("arrow-type"), + lambda a,v: a.set(v,a.get_property("shadow-type")), gtk.ArrowType), + (gtk.ToggleButton, "toggled", gtk.ToggleButton.get_active, gtk.ToggleButton.set_active, types.BooleanType), + (gtk.CheckMenuItem, "toggled", gtk.CheckMenuItem.get_active, gtk.CheckMenuItem.set_active, types.BooleanType), + (gtk.Expander, "activate", lambda w: not w.get_expanded(), gtk.Expander.set_expanded, types.BooleanType), + (gtk.ColorButton, "color-set", gtk.ColorButton.get_color, gtk.ColorButton.set_color, gtk.gdk.Color), + (gtk.ColorSelection, "color-changed", gtk.ColorSelection.get_current_color, gtk.ColorSelection.set_current_color, gtk.gdk.Color), + ] + +if gtk.pygtk_version >= (2,10,0): + # conditionally added support + __def_adapter.append( + (gtk.LinkButton, "clicked", gtk.LinkButton.get_uri, gtk.LinkButton.set_uri, types.StringType)) + pass + + +# constants to access values: +SIGNAL =1 +GETTER =2 +SETTER =3 +WIDTYPE =4 +# ---------------------------------------------------------------------- + + +# To optimize the search +__memoize__ = {} +def search_adapter_info(wid): + """Given a widget returns the default tuple found in __def_adapter""" + t = type(wid) + if __memoize__.has_key(t): return __memoize__[t] + + for w in __def_adapter: + if isinstance(wid, w[0]): + __memoize__[t] = w + return w + pass + + raise TypeError("Adapter type " + str(t) + " not found among supported adapters") + diff --git a/src/gtkmvc/controller.py b/src/gtkmvc/controller.py new file mode 100644 index 0000000..6d75587 --- /dev/null +++ b/src/gtkmvc/controller.py @@ -0,0 +1,218 @@ +# 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., 51 Franklin Street, Fifth Floor, +# Boston, MA 02110, USA. +# +# For more information on pygtkmvc see +# or email to the author Roberto Cavada . +# Please report bugs to . + + +from gtkmvc.observer import Observer +import types + + +class Controller (Observer): + """ + We put all of our gtk signal handlers into a class. This lets + us bind all of them at once, because their names are in the + class dict. This class automatically register its instances as + observers into the corresponding model. Also, when a view is + created, the view calls method register_view, which can be + oveloaded in order to connect signals and perform other specific + operation. A controller possibly handles and contains also a set + of adapters that makes easier to connect widgets and observable + properties into the model. + + parameter spurious controls the way spurious value change + notifications are handled. If True, assignments to observable + properties that do not actually change the value are + notified anyway.""" + + def __init__(self, model, spurious=False): + Observer.__init__(self, model, spurious) + + self.view = None + self.__adapters = [] + return + + def register_view(self, view): + """ + This method is called by the framework when registering a + view. Derived classes can exploit this call to connect + signals manually, create and connect treeview, textview, + etc. + """ + assert(self.view is None) + self.view = view + self.register_adapters() + return + + def register_adapters(self): + """ + This method is called by register_view during view + registration process, when it is time to possibly create all + adapters. model and view can safely be taken from self.model + and self.view. Derived classes can specilize this method. In + this implementation the method does nothing. + """ + assert(self.model is not None) + assert(self.view is not None) + return + + def adapt(self, *args): + """ + Adapts a set of (observable) properties and a set of + widgets, by connecting them. + + This method can be used to simplify the creation of one or + more adapters, by letting the framework do most of the work + needed to connect ('adapt') properties from one hand, and + widgets on the other. + + This method is provided in three flavours: + + 1. An instance of an Adapter class can be created by the + caller and passed as a unique argument. The adapter must + be already fully configured. + + 2. The name of a model's property is passed as a unique + argument. A correponding widget name is searched and if + found, an adapter is created. Name matching is performed + by searching into view's widget names for words that end + with the given property name. Matching is case + insensitive and words can be separated by spaces, + underscore (_) and CapitalizedWords. For example property + 'prop' will match widget 'cb_prop'. If no matches or + multiple matches are found, a ValueError will be raised. + The way names are matched can be customized by deriving + method match_prop_name. + + 3. Two strings can be passed, respectively containing the + name of an observable property in the model, and the name + of a widget in the view. + + Flavour 1 allows for a full control, but flavour 2 and 3 + make easier to create adpaters with basic (default) + behaviour. + + This method can be called into the method register_adapters + which derived classes can specialise. + """ + + # checks arguments + n = len(args) + if n not in (1,2): raise TypeError("adapt() takes 1 or 2 arguments (%d given)" % n) + + if n == 1: #one argument + from gtkmvc.adapters.basic import Adapter + + if isinstance(args[0], Adapter): adapters = (args[0],) + + elif isinstance(args[0], types.StringType): + prop_name = args[0] + names = [] + for k in self.view: + if self.__match_prop_name(prop_name, k): names.append(k) + pass + if len(names) != 1: + raise ValueError("%d widget candidates match property '%s': %s" % \ + (len(names), prop_name, names)) + wid_name = names[0] + adapters = self.__create_adapters__(prop_name, wid_name) + pass + else: raise TypeError("Argument of adapt() must be an Adapter or a string") + + else: # two arguments + if not (isinstance(args[0], types.StringType) and + isinstance(args[1], types.StringType)): + raise TypeError("Arguments of adapt() must be two strings") + + # retrieves both property and widget, and creates an adapter + prop_name, wid_name = args + adapters = self.__create_adapters__(prop_name, wid_name) + pass + + for ad in adapters: self.__adapters.append(ad) + return + + + def __match_prop_name(self, prop_name, wid_name): + """ + Used internally when searching for a suitable widget. To customize + its behaviour, re-implement this method into derived classes + """ + return wid_name.lower().endswith(prop_name.lower()) + + + def __create_adapters__(self, prop_name, wid_name): + """ + Private service that looks at property and widgets types, + and possibly creates one or more (best) fitting adapters + that are returned as a list. + """ + from gtkmvc.adapters.basic import Adapter, RoUserClassAdapter + from gtkmvc.adapters.containers import StaticContainerAdapter + import gtk + + res = [] + + wid = self.view[wid_name] + if wid is None: raise ValueError("Widget '%s' not found" % wid_name) + + # Decides the type of adapters to be created. + if isinstance(wid, gtk.Calendar): + # calendar creates three adapter for year, month and day + ad = RoUserClassAdapter(self.model, prop_name, + lambda d: d.year, lambda d,y: d.replace(year=y)) + ad.connect_widget(wid, lambda c: c.get_date()[0], + lambda c,y: c.select_month(c.get_date()[1], y), + "day-selected") + res.append(ad) # year + + ad = RoUserClassAdapter(self.model, prop_name, + lambda d: d.month, lambda d,m: d.replace(month=m)) + ad.connect_widget(wid, lambda c: c.get_date()[1]+1, + lambda c,m: c.select_month(m-1, c.get_date()[0]), + "day-selected") + res.append(ad) # month + + ad = RoUserClassAdapter(self.model, prop_name, + lambda d: d.day, lambda d,v: d.replace(day=v)) + ad.connect_widget(wid, lambda c: c.get_date()[2], + lambda c,d: c.select_day(d), + "day-selected") + res.append(ad) # day + return res + + + try: # tries with StaticContainerAdapter + ad = StaticContainerAdapter(self.model, prop_name) + ad.connect_widget(wid) + res.append(ad) + + except TypeError: + # falls back to a simple adapter + ad = Adapter(self.model, prop_name) + ad.connect_widget(wid) + res.append(ad) + pass + + return res + + + pass # end of class Controller diff --git a/gtkmvc/model.py b/src/gtkmvc/model.py similarity index 86% rename from gtkmvc/model.py rename to src/gtkmvc/model.py index c2981c2..faedbfd 100644 --- a/gtkmvc/model.py +++ b/src/gtkmvc/model.py @@ -13,8 +13,9 @@ # 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. +# License along with this library; if not, write to the Free +# Software Foundation, Inc., 51 Franklin Street, Fifth Floor, +# Boston, MA 02110, USA. # # For more information on pygtkmvc see # or email to the author Roberto Cavada . @@ -26,23 +27,29 @@ 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 + """ + 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 observer. + + 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 value of the property 'p' you have to + define the method called 'property_p_value_change' 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 @@ -64,8 +71,8 @@ class Model (object): return def register_property(self, name): - """Registers an existing property to be monitored, and sets up - notifiers for notifications""" + """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 @@ -92,11 +99,18 @@ class Model (object): pass return - + + + def has_property(self, name): + """Returns true if given property name refers an observable + property inside self or inside derived classes""" + return self.__properties__.has_key(name) or \ + self.__derived_properties__.has_key(name) + 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) @@ -240,8 +254,12 @@ class Model (object): 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 + obs = method.im_self + # notification occurs checking spuriousness of the observer + if old != new or obs.accepts_spurious_change(): + self.__notify_observer__(obs, method, + self, old, new) # notifies the change + pass pass return diff --git a/gtkmvc/model_mt.py b/src/gtkmvc/model_mt.py similarity index 98% rename from gtkmvc/model_mt.py rename to src/gtkmvc/model_mt.py index 1ce8421..bd705c9 100644 --- a/gtkmvc/model_mt.py +++ b/src/gtkmvc/model_mt.py @@ -14,7 +14,8 @@ # # 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. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, +# Boston, MA 02110, USA. # # For more information on pygtkmvc see # or email to the author Roberto Cavada . diff --git a/gtkmvc/observable.py b/src/gtkmvc/observable.py similarity index 96% rename from gtkmvc/observable.py rename to src/gtkmvc/observable.py index f6ac190..6699f5b 100644 --- a/gtkmvc/observable.py +++ b/src/gtkmvc/observable.py @@ -15,7 +15,8 @@ # # 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. +# 51 Franklin Street, Fifth Floor, +# Boston, MA 02110, USA.ridge, MA 02139, USA. # # For more information on pygtkmvc see # or email to the author . diff --git a/gtkmvc/observer.py b/src/gtkmvc/observer.py similarity index 51% rename from gtkmvc/observer.py rename to src/gtkmvc/observer.py index ac35f16..35d3344 100644 --- a/gtkmvc/observer.py +++ b/src/gtkmvc/observer.py @@ -15,7 +15,8 @@ # # 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. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, +# Boston, MA 02110, USA. # # For more information on pygtkmvc see # or email to the author Roberto Cavada . @@ -26,8 +27,35 @@ class Observer (object): """Use this class as base class of all observers""" - def __init__(self, model=None): + def __init__(self, model=None, spurious=False): + """ + When parameter spurious is set to False + (default value) the observer declares that it is not + interested in receiving value-change notifications when + property's value does not really change. This happens when a + property got assigned to a value that is the same it had + before being assigned. + + A notification was used to be sent to the observer even in + this particular condition, because spurious (non-changing) + assignments were used as signals when signals were not + supported by early version of the framework. The observer + was in charge of deciding what to do with spurious + assignments, by checking if the old and new values were + different at the beginning of the notification code. With + latest version providing new notification types like + signals, this requirement seems to be no longer needed, and + delivering a notification is no longer a sensible + behaviour. + + This is the reason for providing parameter + spurious that changes the previous behaviour + but keeps availability of a possible backward compatible + feature. + """ + self.model = None + self.__accepts_spurious__ = spurious self.register_model(model) return @@ -37,6 +65,13 @@ class Observer (object): if self.model: self.model.register_observer(self) return + def accepts_spurious_change(self): + """ + Returns True if this observer is interested in receiving + spurious value changes. This is queried by the model when + notifying a value change.""" + return self.__accepts_spurious__ + def unregister_model(self): if self.model: self.model.unregister_observer(self) diff --git a/gtkmvc/support/__init__.py b/src/gtkmvc/support/__init__.py similarity index 92% rename from gtkmvc/support/__init__.py rename to src/gtkmvc/support/__init__.py index 86ec566..ff78e23 100644 --- a/gtkmvc/support/__init__.py +++ b/src/gtkmvc/support/__init__.py @@ -14,7 +14,8 @@ # # 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. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, +# Boston, MA 02110, USA. # # For more information on pygtkmvc see # or email to the author Roberto Cavada . diff --git a/gtkmvc/support/decorators.py b/src/gtkmvc/support/decorators.py similarity index 95% rename from gtkmvc/support/decorators.py rename to src/gtkmvc/support/decorators.py index fef7184..0f68f31 100644 --- a/gtkmvc/support/decorators.py +++ b/src/gtkmvc/support/decorators.py @@ -15,7 +15,8 @@ # # 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. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, +# Boston, MA 02110, USA. # # For more information on pygtkmvc see # or email to the author Roberto Cavada . diff --git a/gtkmvc/controller.py b/src/gtkmvc/support/exceptions.py similarity index 53% rename from gtkmvc/controller.py rename to src/gtkmvc/support/exceptions.py index 68b481f..bc2a614 100644 --- a/gtkmvc/controller.py +++ b/src/gtkmvc/support/exceptions.py @@ -14,33 +14,11 @@ # # 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. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, +# Boston, MA 02110, USA. # # For more information on pygtkmvc see # or email to the author Roberto Cavada . # Please report bugs to . -from gtkmvc.observer import Observer - -class Controller (Observer): - """We put all of our gtk signal handlers into a class. This lets us bind - all of them at once, because their names are in the class dict. - This class automatically register its instances as observers into the - corresponding model. - Also, when a view is created, the view calls method register_view, - which can be oveloaded in order to connect signals and perform other - specific operation""" - - def __init__(self, model): - Observer.__init__(self, model) - - self.view = None - return - - def register_view(self, view): - assert(self.view is None) - self.view = view - return - - pass # end of class Controller diff --git a/gtkmvc/support/metaclass_base.py b/src/gtkmvc/support/metaclass_base.py similarity index 87% rename from gtkmvc/support/metaclass_base.py rename to src/gtkmvc/support/metaclass_base.py index 2f7c2b8..771929c 100644 --- a/gtkmvc/support/metaclass_base.py +++ b/src/gtkmvc/support/metaclass_base.py @@ -14,7 +14,8 @@ # # 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. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, +# Boston, MA 02110, USA. # # For more information on pygtkmvc see # or email to the author Roberto Cavada . @@ -26,6 +27,7 @@ import re import types import gtkmvc.support.wrappers as wrappers +from gtkmvc.support.utils import get_function_from_source # ---------------------------------------------------------------------- @@ -53,7 +55,7 @@ class PropertyMeta (type): Customization: The base implementation of getter is to return the value stored in the - variable associate to the property. The setter simply sets its value. + variable associated to the property. The setter simply sets its value. Programmers can override basic behaviour for getters or setters simply by defining their getters and setters (see at the names convention above). The customized function can lie everywhere in the user classes hierarchy. @@ -79,10 +81,10 @@ class PropertyMeta (type): for base in bases: maps = ( getattr(base, '__properties__', {}), getattr(base, '__derived_properties__', {}) ) - for map in maps: - for p in map.keys(): + for _map in maps: + for p in _map.keys(): if not props.has_key(p) and not der_props.has_key(p): - der_props[p] = map[p] + der_props[p] = _map[p] pass pass pass @@ -114,8 +116,8 @@ class PropertyMeta (type): # checks if accessors are already defined: if getter_name not in members_names: src = type(cls).get_getter_source(cls, getter_name, prop_name) - code = type(cls).get_func_code_from_func_src(cls, src) - type(cls).add_method_from_func_code(cls, getter_name, code) + func = get_function_from_source(src) + setattr(cls, getter_name, func) else: cls.__msg__("Warning: Custom member '%s' overloads generated accessor of property '%s'" \ % (getter_name, prop_name), 2) @@ -123,8 +125,8 @@ class PropertyMeta (type): if setter_name not in members_names: src = type(cls).get_setter_source(cls, setter_name, prop_name) - code = type(cls).get_func_code_from_func_src(cls, src) - type(cls).add_method_from_func_code(cls, setter_name, code) + func = get_function_from_source(src) + setattr(cls, setter_name, func) else: cls.__msg__("Warning: Custom member '%s' overloads generated accessor of property '%s'" \ % (setter_name, prop_name), 2) @@ -133,7 +135,7 @@ class PropertyMeta (type): prop = property(getattr(cls, getter_name), getattr(cls, setter_name)) if prop_name in members_names: - cls.__msg__("Warning: automatic property builder is overriding property %s in class %s" \ + cls.__msg__("Warning: automatic property builder overrids property %s in class %s" \ % (prop_name, cls.__name__), 2) pass setattr(cls, prop_name, prop) @@ -187,26 +189,11 @@ class PropertyMeta (type): return res return val - - # Services: - def add_method_from_func_code(cls, meth_name, code): - """Use this to add a code that is a new method for the class""" - func = new.function(code, globals(), meth_name) - meth = new.instancemethod(func, cls, cls.__name__) - setattr(cls, meth_name, func) - return - - def get_func_code_from_func_src(cls, source): - """Public service that provided code object from function source""" - m = re.compile("def\s+(\w+)\s*\(.*\):").match(source) - if m is None: raise BadFuncSource(source) - - func_name = m.group(1) - exec source - code = eval("%s.func_code" % func_name) - return code + # ------------------------------------------------------------ + # Services + # ------------------------------------------------------------ # Override these: def get_getter_source(cls, getter_name, prop_name): diff --git a/gtkmvc/support/metaclasses.py b/src/gtkmvc/support/metaclasses.py similarity index 97% rename from gtkmvc/support/metaclasses.py rename to src/gtkmvc/support/metaclasses.py index 7d5abf5..22091f6 100644 --- a/gtkmvc/support/metaclasses.py +++ b/src/gtkmvc/support/metaclasses.py @@ -14,7 +14,8 @@ # # 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. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, +# Boston, MA 02110, USA. # # For more information on pygtkmvc see # or email to the author Roberto Cavada . diff --git a/src/gtkmvc/support/utils.py b/src/gtkmvc/support/utils.py new file mode 100644 index 0000000..c174de8 --- /dev/null +++ b/src/gtkmvc/support/utils.py @@ -0,0 +1,39 @@ +# Author: Roberto Cavada +# +# Copyright (c) 2007 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., 51 Franklin Street, Fifth Floor, +# Boston, MA 02110, USA. +# +# For more information on pygtkmvc see +# or email to the author Roberto Cavada . +# Please report bugs to . + + + +def get_function_from_source(source): + """Given source code of a function, a function object is + returned""" + + import re + m = re.compile("def\s+(\w+)\s*\(.*\):").match(source) + if m is None: raise ValueError("Given source is not a valid function:\n"+ + source) + name = m.group(1) + + exec source + code = eval("%s.func_code" % name) + import new + return new.function(code, globals(), name) diff --git a/gtkmvc/support/wrappers.py b/src/gtkmvc/support/wrappers.py similarity index 90% rename from gtkmvc/support/wrappers.py rename to src/gtkmvc/support/wrappers.py index 0f362ee..c567a62 100644 --- a/gtkmvc/support/wrappers.py +++ b/src/gtkmvc/support/wrappers.py @@ -15,7 +15,8 @@ # # 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. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, +# Boston, MA 02110, USA. # # For more information on pygtkmvc see # or email to the author Roberto Cavada . @@ -28,6 +29,11 @@ import new # ---------------------------------------------------------------------- class ObsWrapperBase (object): + """ + This class is a base class wrapper for user-defined classes and + containers like lists and maps. + """ + def __init__(self): self.__prop_name = None self.__gtkmvc_model = None @@ -58,6 +64,10 @@ class ObsWrapperBase (object): # ---------------------------------------------------------------------- class ObsWrapper (ObsWrapperBase): + """ + Base class for wrappers, like user-classes and sequences. + """ + def __init__(self, obj, method_names): ObsWrapperBase.__init__(self) @@ -66,7 +76,7 @@ class ObsWrapper (ObsWrapperBase): self.__doc__ = obj.__doc__ for name in method_names: - if hasattr(self._obj, name) and not hasattr(self, name): + if hasattr(self._obj, name): src = self.__get_wrapper_code(name) exec src @@ -87,7 +97,7 @@ class ObsWrapper (ObsWrapperBase): return res""" % {'name' : name} # For all fall backs - def __getattr__(self, name): return self._obj.__getattr__(name) + def __getattr__(self, name): return getattr(self._obj, name) def __repr__(self): return self._obj.__repr__() def __str__(self): return self._obj.__str__() @@ -104,13 +114,13 @@ class ObsSeqWrapper (ObsWrapper): self._notify_method_before(self._obj, "__setitem__", (key,val), {}) res = self._obj.__setitem__(key, val) - self._notify_method_after(self._obj, res, "__setitem__", (key,val), {}) + self._notify_method_after(self._obj, "__setitem__", res, (key,val), {}) return res def __delitem__(self, key): self._notify_method_before(self._obj, "__delitem__", (key,), {}) res = self._obj.__delitem__(key) - self._notify_method_after(self._obj, res, "__delitem__", (key,), {}) + self._notify_method_after(self._obj, "__delitem__", res, (key,), {}) return res diff --git a/gtkmvc/view.py b/src/gtkmvc/view.py similarity index 71% rename from gtkmvc/view.py rename to src/gtkmvc/view.py index 7dbd0ce..220fbd0 100644 --- a/gtkmvc/view.py +++ b/src/gtkmvc/view.py @@ -16,7 +16,8 @@ # # 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. +# Foundation, Inc., 51 Franklin Street, Fifth Floor, +# Boston, MA 02110, USA. # # For more information on pygtkmvc see # or email to the author Roberto Cavada . @@ -37,6 +38,8 @@ class View (object): not given (or None). In that case widgets must be connected manually. glade_top_widget_name can be either a string name or list of names.""" self.manualWidgets = {} + self.autoWidgets = None + self.xmlWidgets = [] # Sets a callback for custom widgets @@ -47,11 +50,12 @@ class View (object): wids = (glade_top_widget_name,) else: wids = glade_top_widget_name # Already a list or tuple + # retrieves XML objects from glade if (glade_filename is not None): for i in range(0,len(wids)): self.xmlWidgets.append(gtk.glade.XML(glade_filename, wids[i])) pass - pass + pass # top widget list or singleton: if (glade_top_widget_name is not None): @@ -63,7 +67,7 @@ class View (object): else: self.m_topWidget = self[wids[0]] else: self.m_topWidget = None - if (glade_filename is not None): self.autoconnect_signals(controller) + if (glade_filename is not None): self.__autoconnect_signals(controller) if (register): controller.register_view(self) if (not parent_view is None): self.set_parent_view(parent_view) return @@ -72,14 +76,20 @@ class View (object): # Returns None if no widget name has been found. def __getitem__(self, key): wid = None - for xml in self.xmlWidgets: - wid = xml.get_widget(key) - if wid is not None: break + + if self.autoWidgets: + if self.autoWidgets.has_key(key): wid = self.autoWidgets[key] + pass + else: + for xml in self.xmlWidgets: + wid = xml.get_widget(key) + if wid is not None: break + pass pass - if (wid is None): + if wid is None: # try with manually-added widgets: - if (self.manualWidgets.has_key(key)): + if self.manualWidgets.has_key(key): wid = self.manualWidgets[key] pass pass @@ -91,20 +101,6 @@ class View (object): if (self.m_topWidget is None): self.m_topWidget = wid return - # performs Controller's signals auto-connection: - def autoconnect_signals(self, controller): - dict = {} - member_names = dir(controller) - for name in member_names: - method = getattr(controller, name) - if (not callable(method)): continue - assert(not dict.has_key(name)) # not already connected! - dict[name] = method - pass - - for xml in self.xmlWidgets: xml.signal_autoconnect(dict) - return - def show(self): ret = True top = self.get_top_widget() @@ -160,11 +156,60 @@ class View (object): pass return - # Finds the right callback for custom widget creation and call it + # Finds the right callback for custom widget creation and calls it + # Returns None if an undefined or invalid handler is found def _custom_widget_create(self, glade, function_name, widget_name, str1, str2, int1, int2): - handler = getattr(self, function_name) - return handler(str1, str2, int1, int2) - + # This code was kindly provided by Allan Douglas + if function_name is not None: + handler = getattr(self, function_name, None) + if handler is not None: return handler(str1, str2, int1, int2) + pass + return None + # implements the iteration protocol + def __iter__(self): + # pre-calculates the auto widgets if needed: + if self.autoWidgets is None: + self.autoWidgets = {} + + for xml in self.xmlWidgets: + for wid in xml.get_widget_prefix(""): + wname = gtk.glade.get_widget_name(wid) + assert not self.autoWidgets.has_key(wname) + self.autoWidgets[wname] = wid + pass + pass + pass + + self.__idx = 0 + self.__max1 = len(self.autoWidgets) + self.__max2 = self.__max1 + len(self.manualWidgets) + return self + + # implements the iteration protocol + def next(self): + if self.__idx >= self.__max2: raise StopIteration() + if self.__idx >= self.__max1: m = self.manualWidgets + else: m = self.autoWidgets + self.__idx += 1 + return m.keys()[self.__idx-1] + + + # performs Controller's signals auto-connection: + def __autoconnect_signals(self, controller): + dic = {} + for name in dir(controller): + method = getattr(controller, name) + if (not callable(method)): continue + assert(not dic.has_key(name)) # not already connected! + dic[name] = method + pass + + for xml in self.xmlWidgets: xml.signal_autoconnect(dic) + return + + + pass # end of class View