1
0
mirror of https://github.com/gryf/pygtktalog.git synced 2025-12-17 11:30:19 +01:00

* Upgrade pygtkmvc to ne version 1.2.1.

This commit is contained in:
2007-10-24 17:29:31 +00:00
parent 15cbe23161
commit 4dcfaa51e7
18 changed files with 1180 additions and 118 deletions

View File

@@ -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 <http://pygtkmvc.sourceforge.net>
# or email to the author Roberto Cavada <cavada@irst.itc.it>.
@@ -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

View File

@@ -0,0 +1,25 @@
# Author: Roberto Cavada <cavada@irst.itc.it>
#
# 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 <http://pygtkmvc.sourceforge.net>
# or email to the author Roberto Cavada <cavada@irst.itc.it>.
# Please report bugs to <cavada@irst.itc.it>.
from gtkmvc.adapters.basic import Adapter, UserClassAdapter, RoUserClassAdapter
from gtkmvc.adapters.containers import StaticContainerAdapter

View File

@@ -0,0 +1,429 @@
# Author: Roberto Cavada <cavada@irst.itc.it>
#
# 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 <http://pygtkmvc.sourceforge.net>
# or email to the author Roberto Cavada <cavada@irst.itc.it>.
# Please report bugs to <cavada@irst.itc.it>.
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
# ----------------------------------------------------------------------

View File

@@ -0,0 +1,217 @@
# Author: Roberto Cavada <cavada@irst.itc.it>
#
# 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 <http://pygtkmvc.sourceforge.net>
# or email to the author Roberto Cavada <cavada@irst.itc.it>.
# Please report bugs to <cavada@irst.itc.it>.
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

View File

@@ -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")

218
src/gtkmvc/controller.py Normal file
View File

@@ -0,0 +1,218 @@
# Author: Roberto Cavada <cavada@irst.itc.it>
#
# 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 <http://pygtkmvc.sourceforge.net>
# or email to the author Roberto Cavada <cavada@irst.itc.it>.
# Please report bugs to <cavada@irst.itc.it>.
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

View File

@@ -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 <http://pygtkmvc.sourceforge.net>
# or email to the author Roberto Cavada <cavada@irst.itc.it>.
@@ -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
@@ -94,6 +101,13 @@ class Model (object):
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
@@ -240,9 +254,13 @@ 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,
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
def notify_method_before_change(self, prop_name, instance, meth_name,

View File

@@ -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 <http://pygtkmvc.sourceforge.net>
# or email to the author Roberto Cavada <cavada@irst.itc.it>.

View File

@@ -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 <http://pygtkmvc.sourceforge.net>
# or email to the author <cavada@irst.itc.it>.

View File

@@ -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 <http://pygtkmvc.sourceforge.net>
# or email to the author Roberto Cavada <cavada@irst.itc.it>.
@@ -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)

View File

@@ -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 <http://pygtkmvc.sourceforge.net>
# or email to the author Roberto Cavada <cavada@irst.itc.it>.

View File

@@ -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 <http://pygtkmvc.sourceforge.net>
# or email to the author Roberto Cavada <cavada@irst.itc.it>.

View File

@@ -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 <http://pygtkmvc.sourceforge.net>
# or email to the author Roberto Cavada <cavada@irst.itc.it>.
# Please report bugs to <cavada@irst.itc.it>.
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

View File

@@ -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 <http://pygtkmvc.sourceforge.net>
# or email to the author Roberto Cavada <cavada@irst.itc.it>.
@@ -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)
@@ -188,25 +190,10 @@ class PropertyMeta (type):
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):

View File

@@ -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 <http://pygtkmvc.sourceforge.net>
# or email to the author Roberto Cavada <cavada@irst.itc.it>.

View File

@@ -0,0 +1,39 @@
# Author: Roberto Cavada <cavada@irst.itc.it>
#
# 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 <http://pygtkmvc.sourceforge.net>
# or email to the author Roberto Cavada <cavada@irst.itc.it>.
# Please report bugs to <cavada@irst.itc.it>.
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)

View File

@@ -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 <http://pygtkmvc.sourceforge.net>
# or email to the author Roberto Cavada <cavada@irst.itc.it>.
@@ -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

View File

@@ -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 <http://pygtkmvc.sourceforge.net>
# or email to the author Roberto Cavada <cavada@irst.itc.it>.
@@ -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,6 +50,7 @@ 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]))
@@ -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
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 <zalguod at
# users.sourceforge.net>
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