1
0
mirror of https://github.com/gryf/pygtktalog.git synced 2025-12-18 12:00:21 +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

49
src/gtkmvc/__init__.py Normal file
View File

@@ -0,0 +1,49 @@
# 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>.
__all__ = ["model", "view", "controller", "observable", "observer"]
__version = (1,2,1)
from model import Model, TreeStoreModel, ListStoreModel, TextBufferModel
from model_mt import ModelMT
from controller import Controller
from view import View
from observer import Observer
import observable
import adapters
def get_version(): return __version
def require(ver):
if isinstance(ver, str): ver = ver.split(".")
ver = tuple(map(int, ver))
if get_version() < ver:
raise AssertionError("gtkmvc required version '%s', found '%s'"\
% (ver, get_version()))
pass
return

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

336
src/gtkmvc/model.py Normal file
View File

@@ -0,0 +1,336 @@
# 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>.
import support.metaclasses
from support.wrappers import ObsWrapperBase
from observable import Signal
class Model (object):
"""
This class is the application model base class. It handles a set
of observable properties which you are interested in showing by
one ore more view - via one or more observers of course. The
mechanism is the following:
1. You are interested in showing a set of model property, that
you can declare in the __properties__ member map.
2. You define one or more observers that observe one or more
properties you registered. When someone changes a property
value the model notifies the changing to each 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
__properties__ = {} # override this
def __init__(self):
object.__init__(self)
self.__observers = []
# keys are properties names, values are methods inside the observer:
self.__value_notifications = {}
self.__instance_notif_before = {}
self.__instance_notif_after = {}
self.__signal_notif = {}
for key in (self.__properties__.keys() + self.__derived_properties__.keys()):
self.register_property(key)
pass
return
def register_property(self, name):
"""Registers an existing property to be monitored, and sets
up notifiers for notifications"""
if not self.__value_notifications.has_key(name):
self.__value_notifications[name] = []
pass
# registers observable wrappers
prop = getattr(self, "_prop_%s" % name)
if isinstance(prop, ObsWrapperBase):
prop.__set_model__(self, name)
if isinstance(prop, Signal):
if not self.__signal_notif.has_key(name):
self.__signal_notif[name] = []
pass
pass
else:
if not self.__instance_notif_before.has_key(name):
self.__instance_notif_before[name] = []
pass
if not self.__instance_notif_after.has_key(name):
self.__instance_notif_after[name] = []
pass
pass
pass
return
def 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)
pass
return
def unregister_observer(self, observer):
if observer not in self.__observers: return
for key in (self.__properties__.keys() + self.__derived_properties__.keys()):
self.__remove_observer_notification(observer, key)
pass
self.__observers.remove(observer)
return
def _reset_property_notification(self, prop_name):
"""Called when it has be done an assignment that changes the
type of a property or the instance of the property has been
changed to a different instance. In this case it must be
unregistered and registered again"""
self.register_property(prop_name)
for observer in self.__observers:
self.__remove_observer_notification(observer, prop_name)
self.__add_observer_notification(observer, prop_name)
pass
return
def __add_observer_notification(self, observer, prop_name):
"""Searches in the observer for any possible listener, and
stores the notification methods to be called later"""
method_name = "property_%s_value_change" % prop_name
if hasattr(observer, method_name):
method = getattr(observer, method_name)
if method not in self.__value_notifications[prop_name]:
list.append(self.__value_notifications[prop_name], method)
pass
pass
# is it a signal?
orig_prop = getattr(self, "_prop_%s" % prop_name)
if isinstance(orig_prop, Signal):
method_name = "property_%s_signal_emit" % prop_name
if hasattr(observer, method_name):
method = getattr(observer, method_name)
if method not in self.__signal_notif[prop_name]:
list.append(self.__signal_notif[prop_name], method)
pass
pass
pass
# is it an instance change notification type?
elif isinstance(orig_prop, ObsWrapperBase):
method_name = "property_%s_before_change" % prop_name
if hasattr(observer, method_name):
method = getattr(observer, method_name)
if method not in self.__instance_notif_before[prop_name]:
list.append(self.__instance_notif_before[prop_name], method)
pass
pass
method_name = "property_%s_after_change" % prop_name
if hasattr(observer, method_name):
method = getattr(observer, method_name)
if method not in self.__instance_notif_after[prop_name]:
list.append(self.__instance_notif_after[prop_name], method)
pass
pass
pass
return
def __remove_observer_notification(self, observer, prop_name):
if self.__value_notifications.has_key(prop_name):
method_name = "property_%s_value_change" % prop_name
if hasattr(observer, method_name):
method = getattr(observer, method_name)
if method in self.__value_notifications[prop_name]:
self.__value_notifications[prop_name].remove(method)
pass
pass
pass
orig_prop = getattr(self, "_prop_%s" % prop_name)
# is it a signal?
if isinstance(orig_prop, Signal):
method_name = "property_%s_signal_emit" % prop_name
if hasattr(observer, method_name):
method = getattr(observer, method_name)
if method in self.__signal_notif[prop_name]:
self.__signal_notif[prop_name].remove(method)
pass
pass
pass
# is it an instance change notification type?
elif isinstance(orig_prop, ObsWrapperBase):
if self.__instance_notif_before.has_key(prop_name):
method_name = "property_%s_before_change" % prop_name
if hasattr(observer, method_name):
method = getattr(observer, method_name)
if method in self.__instance_notif_before[prop_name]:
self.__instance_notif_before[prop_name].remove(method)
pass
pass
pass
if self.__instance_notif_after.has_key(prop_name):
method_name = "property_%s_after_change" % prop_name
if hasattr(observer, method_name):
method = getattr(observer, method_name)
if method in self.__instance_notif_after[prop_name]:
self.__instance_notif_after[prop_name].remove(method)
pass
pass
pass
pass
return
def __notify_observer__(self, observer, method, *args, **kwargs):
"""This can be overridden by derived class in order to call
the method in a different manner (for example, in
multithreading, or a rpc, etc.) This implementation simply
calls the given method with the given arguments"""
return method(*args, **kwargs)
# ---------- Notifiers:
def notify_property_value_change(self, prop_name, old, new):
assert(self.__value_notifications.has_key(prop_name))
for method in self.__value_notifications[prop_name] :
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,
args, kwargs):
assert(self.__instance_notif_before.has_key(prop_name))
for method in self.__instance_notif_before[prop_name] :
self.__notify_observer__(method.im_self, method, self, instance,
meth_name, args, kwargs) # notifies the change
pass
return
def notify_method_after_change(self, prop_name, instance, meth_name,
res, args, kwargs):
assert(self.__instance_notif_after.has_key(prop_name))
for method in self.__instance_notif_after[prop_name] :
self.__notify_observer__(method.im_self, method, self, instance,
meth_name, res, args, kwargs) # notifies the change
pass
return
def notify_signal_emit(self, prop_name, args, kwargs):
assert(self.__signal_notif.has_key(prop_name))
for method in self.__signal_notif[prop_name] :
self.__notify_observer__(method.im_self, method, self,
args, kwargs) # notifies the signal emit
pass
return
pass # end of class Model
# ----------------------------------------------------------------------
import gtk
# ----------------------------------------------------------------------
class TreeStoreModel (Model, gtk.TreeStore):
"""Use this class as base class for your model derived by
gtk.TreeStore"""
__metaclass__ = support.metaclasses.ObservablePropertyGObjectMeta
def __init__(self, column_type, *args):
Model.__init__(self)
gtk.TreeStore.__init__(self, column_type, *args)
return
pass
# ----------------------------------------------------------------------
class ListStoreModel (Model, gtk.ListStore):
"""Use this class as base class for your model derived by
gtk.ListStore"""
__metaclass__ = support.metaclasses.ObservablePropertyGObjectMeta
def __init__(self, column_type, *args):
Model.__init__(self)
gtk.ListStore.__init__(self, column_type, *args)
return
pass
# ----------------------------------------------------------------------
class TextBufferModel (Model, gtk.TextBuffer):
"""Use this class as base class for your model derived by
gtk.TextBuffer"""
__metaclass__ = support.metaclasses.ObservablePropertyGObjectMeta
def __init__(self, table=None):
Model.__init__(self)
gtk.TextBuffer.__init__(self, table)
return
pass

124
src/gtkmvc/model_mt.py Normal file
View File

@@ -0,0 +1,124 @@
# Author: Roberto Cavada <cavada@irst.itc.it>
#
# Copyright (c) 2006 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.model import Model
import support.metaclasses
try: import threading as _threading
except ImportError: import dummy_threading as _threading
import gobject
if hasattr(gobject, "threads_init"): gobject.threads_init()
else: import gtk; gtk.threads_init()
class ModelMT (Model):
"""A base class for models whose observable properties can be
changed by threads different than gtk main thread. Notification is
performed by exploiting the gtk idle loop only if needed,
otherwise the standard notification system (direct method call) is
used. In this model, the observer is expected to run in the gtk
main loop thread."""
__metaclass__ = support.metaclasses.ObservablePropertyMetaMT
def __init__(self):
Model.__init__(self)
self.__observer_threads = {}
self._prop_lock = _threading.Lock()
return
def register_observer(self, observer):
Model.register_observer(self, observer)
self.__observer_threads[observer] = _threading.currentThread()
return
def unregister_observer(self, observer):
Model.unregister_observer(self, observer)
del self.__observer_threads[observer]
return
# ---------- Notifiers:
def __notify_observer__(self, observer, method, *args, **kwargs):
"""This makes a call either through the gtk.idle list or a
direct method call depending whether the caller's thread is
different from the observer's thread"""
assert self.__observer_threads.has_key(observer)
if _threading.currentThread() == self.__observer_threads[observer]:
# standard call
return Model.__notify_observer__(self, observer, method,
*args, **kwargs)
# multi-threading call
gobject.idle_add(self.__idle_callback, observer, method, args, kwargs)
return
def __idle_callback(self, observer, method, args, kwargs):
method(*args, **kwargs)
return False
pass # end of class
import gtk
# ----------------------------------------------------------------------
class TreeStoreModelMT (ModelMT, gtk.TreeStore):
"""Use this class as base class for your model derived by
gtk.TreeStore"""
__metaclass__ = support.metaclasses.ObservablePropertyGObjectMetaMT
def __init__(self, column_type, *args):
ModelMT.__init__(self)
gtk.TreeStore.__init__(self, column_type, *args)
return
pass
# ----------------------------------------------------------------------
class ListStoreModelMT (ModelMT, gtk.ListStore):
"""Use this class as base class for your model derived by
gtk.ListStore"""
__metaclass__ = support.metaclasses.ObservablePropertyGObjectMetaMT
def __init__(self, column_type, *args):
ModelMT.__init__(self)
gtk.ListStore.__init__(self, column_type, *args)
return
pass
# ----------------------------------------------------------------------
class TextBufferModelMT (ModelMT, gtk.TextBuffer):
"""Use this class as base class for your model derived by
gtk.TextBuffer"""
__metaclass__ = support.metaclasses.ObservablePropertyGObjectMetaMT
def __init__(self, table=None):
ModelMT.__init__(self)
gtk.TextBuffer.__init__(self, table)
return
pass

69
src/gtkmvc/observable.py Normal file
View File

@@ -0,0 +1,69 @@
# -------------------------------------------------------------------------
# Author: Roberto Cavada <cavada@irst.itc.it>
#
# Copyright (C) 2006 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
# 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>.
# -------------------------------------------------------------------------
from support import decorators
from support.wrappers import ObsWrapperBase
# ----------------------------------------------------------------------
class Observable (ObsWrapperBase):
def __init__(self):
ObsWrapperBase.__init__(self)
return
pass # end of class
@decorators.good_decorator
def observed(func):
"""Use this decorator to make your class methods observable.
Your observer will receive at most two notifications:
- property_<name>_before_change
- property_<name>_after_change
"""
def wrapper(*args, **kwargs):
self = args[0]
assert(isinstance(self, Observable))
self._notify_method_before(self, func.__name__, args, kwargs)
res = func(*args, **kwargs)
self._notify_method_after(self, func.__name__, res, args, kwargs)
return res
return wrapper
# ----------------------------------------------------------------------
class Signal (Observable):
"""Base class for signals properties"""
def __init__(self):
Observable.__init__(self)
return
def emit(self, *args, **kwargs):
return self.__get_model__().notify_signal_emit(
self.__get_prop_name__(), args, kwargs)
pass # end of class

91
src/gtkmvc/observer.py Normal file
View File

@@ -0,0 +1,91 @@
# -------------------------------------------------------------------------
# Author: Roberto Cavada <cavada@irst.itc.it>
#
# Copyright (C) 2006 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>.
# -------------------------------------------------------------------------
class Observer (object):
"""Use this class as base class of all observers"""
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
def register_model(self, model):
self.unregister_model()
self.model = model
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)
self.model = None
pass
return
def __del__(self):
self.unregister_model()
return
def get_model(self): return self.model
pass # end of class

View File

@@ -0,0 +1,25 @@
# 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>.
__all__ = ["metaclass_base", "metaclasses", "wrappers", "decorators"]

View File

@@ -0,0 +1,44 @@
# -------------------------------------------------------------------------
# Author: Roberto Cavada <cavada@irst.itc.it>
#
# Copyright (C) 2006 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>.
# -------------------------------------------------------------------------
# This file contains decorators to be used (privately) by other parts
# of the framework
def good_decorator(decorator):
"""This decorator makes decorators behave well wrt to decorated
functions names, doc, etc."""
def new_decorator(f):
g = decorator(f)
g.__name__ = f.__name__
g.__doc__ = f.__doc__
g.__dict__.update(f.__dict__)
return g
new_decorator.__name__ = decorator.__name__
new_decorator.__doc__ = decorator.__doc__
new_decorator.__dict__.update(decorator.__dict__)
return new_decorator

View File

@@ -0,0 +1,24 @@
# 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>.

View File

@@ -0,0 +1,240 @@
# 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>.
import new
import re
import types
import gtkmvc.support.wrappers as wrappers
from gtkmvc.support.utils import get_function_from_source
# ----------------------------------------------------------------------
VERBOSE_LEVEL = 5
class PropertyMeta (type):
"""This is a meta-class that provides auto-property support.
The idea is to allow programmers to define some properties which
will be automatically connected to auto-generated code which handles
access to those properties.
How can you use this meta-class?
First, '__metaclass__ = PropertyMeta' must be class member of the class
you want to make the automatic properties handling.
Second, '__properties__' must be a map containing the properties names
as keys, values will be initial values for properties.
That's all: after the instantiation, your class will contain all properties
you named inside '__properties__'. Each of them will be also associated
to a couple of automatically-generated functions which get and set the
property value inside a generated member variable.
About names: suppose the property is called 'x'. The generated variable
(which keeps the real value of the property x) is called _prop_x.
The getter is called get_prop_x(self), and the setter is called
'set_prop_x(self, value)'.
Customization:
The base implementation of getter is to return the value stored in the
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.
Every overrided function will not be generated by the metaclass.
To supply your own methods is good for few methods, but can result in a
very unconfortable way for many methods. In this case you can extend
the meta-class, and override methods get_[gs]etter_source with your
implementation (this can be probably made better).
An example is provided in meta-class PropertyMetaVerbose below.
"""
def __init__(cls, name, bases, dict):
"""class constructor"""
properties = {}
type.__init__(cls, name, bases, dict)
props = getattr(cls, '__properties__', {})
setattr(cls, '__derived_properties__', {})
der_props = getattr(cls, '__derived_properties__')
# Calculates derived properties:
for base in bases:
maps = ( getattr(base, '__properties__', {}),
getattr(base, '__derived_properties__', {}) )
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]
pass
pass
pass
pass
# Generates code for all properties (but not for derived props):
props = getattr(cls, '__properties__', {})
for prop in props.keys():
type(cls).__create_prop_accessors__(cls, prop, props[prop])
pass
return
def __msg__(cls, msg, level):
"""if level is less or equal to VERBOSE_LEVEL, ths message will
be printed"""
if level <= VERBOSE_LEVEL: print msg
return
def __create_prop_accessors__(cls, prop_name, default_val):
"""Private method that creates getter and setter, and the
corresponding property"""
getter_name = "get_prop_%s" % prop_name
setter_name = "set_prop_%s" % prop_name
members_names = cls.__dict__.keys()
# checks if accessors are already defined:
if getter_name not in members_names:
src = type(cls).get_getter_source(cls, getter_name, prop_name)
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)
pass
if setter_name not in members_names:
src = type(cls).get_setter_source(cls, setter_name, prop_name)
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)
pass
prop = property(getattr(cls, getter_name), getattr(cls, setter_name))
if prop_name in members_names:
cls.__msg__("Warning: automatic property builder overrids property %s in class %s" \
% (prop_name, cls.__name__), 2)
pass
setattr(cls, prop_name, prop)
varname = "_prop_%s" % prop_name
if not varname in members_names: cls.__create_property(varname, default_val)
else: cls.__msg__("Warning: automatic property builder found a possible clashing for variable %s inside class %s" \
% (varname, cls.__name__), 2)
return
def __create_property(cls, name, default_val):
setattr(cls, name, cls.create_value(name, default_val))
return
def check_value_change(cls, old, new):
"""Checks whether the value of the property changed in type
or if the instance has been changed to a different instance.
If true, a call to model._reset_property_notification should
be called in order to re-register the new property instance
or type"""
return type(old) != type(new) or \
isinstance(old, wrappers.ObsWrapperBase) and (old != new)
def create_value(cls, prop_name, val, model=None):
"""This is used to create a value to be assigned to a
property. Depending on the type of the value, different values
are created and returned. For example, for a list, a
ListWrapper is created to wrap it, and returned for the
assignment. model is different from model when the value is
changed (a model exists). Otherwise, during property creation
model is None"""
if isinstance(val, tuple):
# this might be a class instance to be wrapped
if len(val) == 3 and \
isinstance(val[1], val[0]) and \
(isinstance(val[2], tuple) or isinstance(val[2], list)):
res = wrappers.ObsUserClassWrapper(val[1], val[2])
if model: res.__set_model__(model, prop_name)
return res
pass
elif isinstance(val, list):
res = wrappers.ObsListWrapper(val)
if model: res.__set_model__(model, prop_name)
return res
elif isinstance(val, dict):
res = wrappers.ObsMapWrapper(val)
if model: res.__set_model__(model, prop_name)
return res
return val
# ------------------------------------------------------------
# Services
# ------------------------------------------------------------
# Override these:
def get_getter_source(cls, getter_name, prop_name):
"""This must be overrided if you need a different implementation.
Simply the generated implementation returns the variable name
_prop_name"""
return "def %s(self): return self._prop_%s" % (getter_name, prop_name)
def get_setter_source(cls, setter_name, prop_name):
"""This must be overrided if you need a different implementation.
Simply the generated implementation sets the variable _prop_name"""
return "def %s(self, val): self._prop_%s = val" \
% (setter_name, prop_name)
pass # end of class
# ----------------------------------------------------------------------
# What follows underneath is a set of examples of usage
## class PropertyMetaVerbose (PropertyMeta):
## """An example of customization"""
## def get_getter_source(cls, getter_name, prop_name):
## return "def %s(self): print 'Calling %s!'; return self._prop_%s" \
## % (getter_name, getter_name, prop_name)
## def get_setter_source(cls, setter_name, prop_name):
## return "def %s(self, val): print 'Calling %s!'; self._prop_%s = val;" \
## % (setter_name, setter_name, prop_name)
## pass #end of class
# ----------------------------------------------------------------------
## class User:
## """An example of usage"""
## __metaclass__ = PropertyMetaVerbose
## __properties__ = {'x':10, 'y':20}
## def __init__(self):
## print self.x # x is 10
## self.x = self.y + 10 # x is now 30
## return
## pass
# ----------------------------------------------------------------------

View File

@@ -0,0 +1,82 @@
# 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 metaclass_base import PropertyMeta
import types
class ObservablePropertyMeta (PropertyMeta):
"""Classes instantiated by this meta-class must provide a method named
notify_property_change(self, prop_name, old, new)"""
def __init__(cls, name, bases, dict):
PropertyMeta.__init__(cls, name, bases, dict)
return
def get_setter_source(cls, setter_name, prop_name):
return """def %(setter)s(self, val):
old = self._prop_%(prop)s
new = type(self).create_value('%(prop)s', val, self)
self._prop_%(prop)s = new
if type(self).check_value_change(old, new): self._reset_property_notification('%(prop)s')
self.notify_property_value_change('%(prop)s', old, val)
return
""" % {'setter':setter_name, 'prop':prop_name}
pass #end of class
class ObservablePropertyMetaMT (ObservablePropertyMeta):
"""This class provides multithreading support for accesing
properties, through a locking mechanism. It is assumed a lock is
owned by the class that uses it. A Lock object called _prop_lock
is assumed to be a member of the using class. see for example class
ModelMT"""
def __init__(cls, name, bases, dict):
ObservablePropertyMeta.__init__(cls, name, bases, dict)
return
def get_setter_source(cls, setter_name, prop_name):
return """def %(setter)s(self, val):
old = self._prop_%(prop)s
new = type(self).create_value('%(prop)s', val, self)
self._prop_lock.acquire()
self._prop_%(prop)s = new
self._prop_lock.release()
if type(self).check_value_change(old, new): self._reset_property_notification('%(prop)s')
self.notify_property_value_change('%(prop)s', old, val)
return
""" % {'setter':setter_name, 'prop':prop_name}
pass #end of class
try:
from gobject import GObjectMeta
class ObservablePropertyGObjectMeta (ObservablePropertyMeta, GObjectMeta): pass
class ObservablePropertyGObjectMetaMT (ObservablePropertyMetaMT, GObjectMeta): pass
except:
class ObservablePropertyGObjectMeta (ObservablePropertyMeta): pass
class ObservablePropertyGObjectMetaMT (ObservablePropertyMetaMT): pass
pass

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

@@ -0,0 +1,162 @@
# -------------------------------------------------------------------------
# Author: Roberto Cavada <cavada@irst.itc.it>
#
# Copyright (C) 2006 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 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
return
def __set_model__(self, model, prop_name):
self.__prop_name = prop_name
self.__gtkmvc_model = model
return
def __get_prop_name__(self): return self.__prop_name
def __get_model__(self): return self.__gtkmvc_model
def _notify_method_before(self, instance, name, args, kwargs):
self.__get_model__().notify_method_before_change(self.__prop_name,
instance,
name, args, kwargs)
return
def _notify_method_after(self, instance, name, res_val, args, kwargs):
self.__get_model__().notify_method_after_change(self.__prop_name,
instance,
name, res_val, args, kwargs)
return
pass
# ----------------------------------------------------------------------
class ObsWrapper (ObsWrapperBase):
"""
Base class for wrappers, like user-classes and sequences.
"""
def __init__(self, obj, method_names):
ObsWrapperBase.__init__(self)
self._obj = obj
self.__doc__ = obj.__doc__
for name in method_names:
if hasattr(self._obj, name):
src = self.__get_wrapper_code(name)
exec src
code = eval("%s.func_code" % name)
func = new.function(code, globals())
meth = new.instancemethod(func, self, type(self).__name__)
setattr(self, name, meth)
pass
pass
return
def __get_wrapper_code(self, name):
return """def %(name)s(self, *args, **kwargs):
self._notify_method_before(self._obj, "%(name)s", args, kwargs)
res = self._obj.%(name)s(*args, **kwargs)
self._notify_method_after(self._obj, "%(name)s", res, args, kwargs)
return res""" % {'name' : name}
# For all fall backs
def __getattr__(self, name): return getattr(self._obj, name)
def __repr__(self): return self._obj.__repr__()
def __str__(self): return self._obj.__str__()
pass #end of class
# ----------------------------------------------------------------------
class ObsSeqWrapper (ObsWrapper):
def __init__(self, obj, method_names):
ObsWrapper.__init__(self, obj, method_names)
return
def __setitem__(self, key, val):
self._notify_method_before(self._obj, "__setitem__", (key,val), {})
res = self._obj.__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, "__delitem__", res, (key,), {})
return res
def __getitem__(self, key):
return self._obj.__getitem__(key)
pass #end of class
# ----------------------------------------------------------------------
class ObsMapWrapper (ObsSeqWrapper):
def __init__(self, m):
methods = ("clear", "pop", "popitem", "update",
"setdefault")
ObsSeqWrapper.__init__(self, m, methods)
return
pass #end of class
# ----------------------------------------------------------------------
class ObsListWrapper (ObsSeqWrapper):
def __init__(self, l):
methods = ("append", "extend", "insert",
"pop", "remove", "reverse", "sort")
ObsSeqWrapper.__init__(self, l, methods)
return
pass #end of class
# ----------------------------------------------------------------------
class ObsUserClassWrapper (ObsWrapper):
def __init__(self, user_class_instance, obs_method_names):
ObsWrapper.__init__(self, user_class_instance, obs_method_names)
return
pass #end of class

215
src/gtkmvc/view.py Normal file
View File

@@ -0,0 +1,215 @@
# Author: Roberto Cavada <cavada@irst.itc.it>
# Modified by: Guillaume Libersat <glibersat AT linux62.org>
#
# Copyright (c) 2005 by Roberto Cavada
# Copyright (c) 2007 by Guillaume Libersat
#
# 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 gtk.glade
from controller import Controller
import types
class View (object):
def __init__(self, controller, glade_filename=None,
glade_top_widget_name=None, parent_view=None, register=True):
"""If register is False you *must* call 'controller.register_view(self)'
from the derived class constructor (i.e. registration is delayed)
If filename is not given (or None) all following parameters must be
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
gtk.glade.set_custom_handler(self._custom_widget_create)
if (( type(glade_top_widget_name) == types.StringType)
or (glade_top_widget_name is None) ):
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
# top widget list or singleton:
if (glade_top_widget_name is not None):
if len(wids) > 1:
self.m_topWidget = []
for i in range(0, len(wids)):
self.m_topWidget.append(self[wids[i]])
pass
else: self.m_topWidget = self[wids[0]]
else: self.m_topWidget = None
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
# Gives us the ability to do: view['widget_name'].action()
# 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:
# try with manually-added widgets:
if self.manualWidgets.has_key(key):
wid = self.manualWidgets[key]
pass
pass
return wid
# You can also add a single widget:
def __setitem__(self, key, wid):
self.manualWidgets[key] = wid
if (self.m_topWidget is None): self.m_topWidget = wid
return
def show(self):
ret = True
top = self.get_top_widget()
if type(top) in (types.ListType, types.TupleType):
for t in top:
if t is not None: ret = ret and t.show()
pass
elif (top is not None): ret = top.show_all()
else: ret = False
return ret
def hide(self):
top = self.get_top_widget()
if type(top) in (types.ListType, types.TupleType):
for t in top:
if t is not None: t.hide_all()
pass
elif top is not None: top.hide_all()
return
# Returns the top-level widget, or a list of top widgets
def get_top_widget(self):
return self.m_topWidget
# Set parent view:
def set_parent_view(self, parent_view):
top = self.get_top_widget()
if type(top) in (types.ListType, types.TupleType):
for t in top:
if t is not None:
t.set_transient_for(parent_view.get_top_widget())
pass
pass
elif (top is not None):
top.set_transient_for(parent_view.get_top_widget())
pass
return
# Set the transient for the view:
def set_transient(self, transient_view):
top = self.get_top_widget()
if type(top) in (types.ListType, types.TupleType):
for t in top:
if t is not None:
transient_view.get_top_widget().set_transient_for(t)
pass
pass
elif (top is not None):
transient_view.get_top_widget().set_transient_for(top)
pass
return
# 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):
# 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