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:
@@ -14,7 +14,8 @@
|
|||||||
#
|
#
|
||||||
# You should have received a copy of the GNU Lesser General Public
|
# You should have received a copy of the GNU Lesser General Public
|
||||||
# License along with this library; if not, write to the Free Software
|
# 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>
|
# For more information on pygtkmvc see <http://pygtkmvc.sourceforge.net>
|
||||||
# or email to the author Roberto Cavada <cavada@irst.itc.it>.
|
# or email to the author Roberto Cavada <cavada@irst.itc.it>.
|
||||||
@@ -23,7 +24,7 @@
|
|||||||
|
|
||||||
__all__ = ["model", "view", "controller", "observable", "observer"]
|
__all__ = ["model", "view", "controller", "observable", "observer"]
|
||||||
|
|
||||||
__version = (1,0,1)
|
__version = (1,2,1)
|
||||||
|
|
||||||
from model import Model, TreeStoreModel, ListStoreModel, TextBufferModel
|
from model import Model, TreeStoreModel, ListStoreModel, TextBufferModel
|
||||||
from model_mt import ModelMT
|
from model_mt import ModelMT
|
||||||
@@ -31,7 +32,7 @@ from controller import Controller
|
|||||||
from view import View
|
from view import View
|
||||||
from observer import Observer
|
from observer import Observer
|
||||||
import observable
|
import observable
|
||||||
|
import adapters
|
||||||
|
|
||||||
def get_version(): return __version
|
def get_version(): return __version
|
||||||
|
|
||||||
25
src/gtkmvc/adapters/__init__.py
Normal file
25
src/gtkmvc/adapters/__init__.py
Normal 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
|
||||||
429
src/gtkmvc/adapters/basic.py
Normal file
429
src/gtkmvc/adapters/basic.py
Normal 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
|
||||||
|
# ----------------------------------------------------------------------
|
||||||
217
src/gtkmvc/adapters/containers.py
Normal file
217
src/gtkmvc/adapters/containers.py
Normal 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
|
||||||
55
src/gtkmvc/adapters/default.py
Normal file
55
src/gtkmvc/adapters/default.py
Normal 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
218
src/gtkmvc/controller.py
Normal 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
|
||||||
@@ -13,8 +13,9 @@
|
|||||||
# Lesser General Public License for more details.
|
# Lesser General Public License for more details.
|
||||||
#
|
#
|
||||||
# You should have received a copy of the GNU Lesser General Public
|
# You should have received a copy of the GNU Lesser General Public
|
||||||
# License along with this library; if not, write to the Free Software
|
# License along with this library; if not, write to the Free
|
||||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
# Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
|
||||||
|
# Boston, MA 02110, USA.
|
||||||
#
|
#
|
||||||
# For more information on pygtkmvc see <http://pygtkmvc.sourceforge.net>
|
# For more information on pygtkmvc see <http://pygtkmvc.sourceforge.net>
|
||||||
# or email to the author Roberto Cavada <cavada@irst.itc.it>.
|
# or email to the author Roberto Cavada <cavada@irst.itc.it>.
|
||||||
@@ -26,23 +27,29 @@ from observable import Signal
|
|||||||
|
|
||||||
|
|
||||||
class Model (object):
|
class Model (object):
|
||||||
"""This class is the application model base class.
|
"""
|
||||||
It handles a set of observable properties which you are interested
|
This class is the application model base class. It handles a set
|
||||||
in showing by one ore more view - via one or more observers of course.
|
of observable properties which you are interested in showing by
|
||||||
The mechanism is the following:
|
one ore more view - via one or more observers of course. The
|
||||||
1. You are interested in showing a set of model property, that you can
|
mechanism is the following:
|
||||||
declare in the __properties__ member map.
|
|
||||||
2. You define one or more observers that observe one or more properties
|
1. You are interested in showing a set of model property, that
|
||||||
you registered. When someone changes a property value the model notifies
|
you can declare in the __properties__ member map.
|
||||||
the changing to each listening controller. The property-observer[s]
|
|
||||||
association is given by the implicit rule in observers method names: if
|
2. You define one or more observers that observe one or more
|
||||||
you want the model notified the changing event of the property 'p'
|
properties you registered. When someone changes a property
|
||||||
you must define the method called 'property_p_change_notification' in
|
value the model notifies the changing to each observer.
|
||||||
each listening observer class.
|
|
||||||
Notice that tipically 'controllers' implement the observer pattern.
|
The property-observer[s] association is given by the implicit
|
||||||
The notification method gets the
|
rule in observers method names: if you want the model notified
|
||||||
emitting model, the old value for the property and the new one.
|
the changing event of the value of the property 'p' you have to
|
||||||
Properties functionalities are automatically provided by the
|
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."""
|
ObservablePropertyMeta meta-class."""
|
||||||
|
|
||||||
__metaclass__ = support.metaclasses.ObservablePropertyMeta
|
__metaclass__ = support.metaclasses.ObservablePropertyMeta
|
||||||
@@ -64,8 +71,8 @@ class Model (object):
|
|||||||
return
|
return
|
||||||
|
|
||||||
def register_property(self, name):
|
def register_property(self, name):
|
||||||
"""Registers an existing property to be monitored, and sets up
|
"""Registers an existing property to be monitored, and sets
|
||||||
notifiers for notifications"""
|
up notifiers for notifications"""
|
||||||
if not self.__value_notifications.has_key(name):
|
if not self.__value_notifications.has_key(name):
|
||||||
self.__value_notifications[name] = []
|
self.__value_notifications[name] = []
|
||||||
pass
|
pass
|
||||||
@@ -94,6 +101,13 @@ class Model (object):
|
|||||||
return
|
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):
|
def register_observer(self, observer):
|
||||||
if observer in self.__observers: return # not already registered
|
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):
|
def notify_property_value_change(self, prop_name, old, new):
|
||||||
assert(self.__value_notifications.has_key(prop_name))
|
assert(self.__value_notifications.has_key(prop_name))
|
||||||
for method in self.__value_notifications[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
|
self, old, new) # notifies the change
|
||||||
pass
|
pass
|
||||||
|
pass
|
||||||
return
|
return
|
||||||
|
|
||||||
def notify_method_before_change(self, prop_name, instance, meth_name,
|
def notify_method_before_change(self, prop_name, instance, meth_name,
|
||||||
@@ -14,7 +14,8 @@
|
|||||||
#
|
#
|
||||||
# You should have received a copy of the GNU Lesser General Public
|
# You should have received a copy of the GNU Lesser General Public
|
||||||
# License along with this library; if not, write to the Free Software
|
# 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>
|
# For more information on pygtkmvc see <http://pygtkmvc.sourceforge.net>
|
||||||
# or email to the author Roberto Cavada <cavada@irst.itc.it>.
|
# or email to the author Roberto Cavada <cavada@irst.itc.it>.
|
||||||
@@ -15,7 +15,8 @@
|
|||||||
#
|
#
|
||||||
# You should have received a copy of the GNU Lesser General Public
|
# You should have received a copy of the GNU Lesser General Public
|
||||||
# License along with this library; if not, write to the Free Software
|
# 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>
|
# For more information on pygtkmvc see <http://pygtkmvc.sourceforge.net>
|
||||||
# or email to the author <cavada@irst.itc.it>.
|
# or email to the author <cavada@irst.itc.it>.
|
||||||
@@ -15,7 +15,8 @@
|
|||||||
#
|
#
|
||||||
# You should have received a copy of the GNU Lesser General Public
|
# You should have received a copy of the GNU Lesser General Public
|
||||||
# License along with this library; if not, write to the Free Software
|
# 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>
|
# For more information on pygtkmvc see <http://pygtkmvc.sourceforge.net>
|
||||||
# or email to the author Roberto Cavada <cavada@irst.itc.it>.
|
# or email to the author Roberto Cavada <cavada@irst.itc.it>.
|
||||||
@@ -26,8 +27,35 @@
|
|||||||
class Observer (object):
|
class Observer (object):
|
||||||
"""Use this class as base class of all observers"""
|
"""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.model = None
|
||||||
|
self.__accepts_spurious__ = spurious
|
||||||
self.register_model(model)
|
self.register_model(model)
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -37,6 +65,13 @@ class Observer (object):
|
|||||||
if self.model: self.model.register_observer(self)
|
if self.model: self.model.register_observer(self)
|
||||||
return
|
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):
|
def unregister_model(self):
|
||||||
if self.model:
|
if self.model:
|
||||||
self.model.unregister_observer(self)
|
self.model.unregister_observer(self)
|
||||||
@@ -14,7 +14,8 @@
|
|||||||
#
|
#
|
||||||
# You should have received a copy of the GNU Lesser General Public
|
# You should have received a copy of the GNU Lesser General Public
|
||||||
# License along with this library; if not, write to the Free Software
|
# 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>
|
# For more information on pygtkmvc see <http://pygtkmvc.sourceforge.net>
|
||||||
# or email to the author Roberto Cavada <cavada@irst.itc.it>.
|
# or email to the author Roberto Cavada <cavada@irst.itc.it>.
|
||||||
@@ -15,7 +15,8 @@
|
|||||||
#
|
#
|
||||||
# You should have received a copy of the GNU Lesser General Public
|
# You should have received a copy of the GNU Lesser General Public
|
||||||
# License along with this library; if not, write to the Free Software
|
# 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>
|
# For more information on pygtkmvc see <http://pygtkmvc.sourceforge.net>
|
||||||
# or email to the author Roberto Cavada <cavada@irst.itc.it>.
|
# or email to the author Roberto Cavada <cavada@irst.itc.it>.
|
||||||
@@ -14,33 +14,11 @@
|
|||||||
#
|
#
|
||||||
# You should have received a copy of the GNU Lesser General Public
|
# You should have received a copy of the GNU Lesser General Public
|
||||||
# License along with this library; if not, write to the Free Software
|
# 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>
|
# For more information on pygtkmvc see <http://pygtkmvc.sourceforge.net>
|
||||||
# or email to the author Roberto Cavada <cavada@irst.itc.it>.
|
# or email to the author Roberto Cavada <cavada@irst.itc.it>.
|
||||||
# Please report bugs to <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
|
|
||||||
@@ -14,7 +14,8 @@
|
|||||||
#
|
#
|
||||||
# You should have received a copy of the GNU Lesser General Public
|
# You should have received a copy of the GNU Lesser General Public
|
||||||
# License along with this library; if not, write to the Free Software
|
# 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>
|
# For more information on pygtkmvc see <http://pygtkmvc.sourceforge.net>
|
||||||
# or email to the author Roberto Cavada <cavada@irst.itc.it>.
|
# or email to the author Roberto Cavada <cavada@irst.itc.it>.
|
||||||
@@ -26,6 +27,7 @@ import re
|
|||||||
import types
|
import types
|
||||||
|
|
||||||
import gtkmvc.support.wrappers as wrappers
|
import gtkmvc.support.wrappers as wrappers
|
||||||
|
from gtkmvc.support.utils import get_function_from_source
|
||||||
|
|
||||||
|
|
||||||
# ----------------------------------------------------------------------
|
# ----------------------------------------------------------------------
|
||||||
@@ -53,7 +55,7 @@ class PropertyMeta (type):
|
|||||||
|
|
||||||
Customization:
|
Customization:
|
||||||
The base implementation of getter is to return the value stored in the
|
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
|
Programmers can override basic behaviour for getters or setters simply by
|
||||||
defining their getters and setters (see at the names convention above).
|
defining their getters and setters (see at the names convention above).
|
||||||
The customized function can lie everywhere in the user classes hierarchy.
|
The customized function can lie everywhere in the user classes hierarchy.
|
||||||
@@ -79,10 +81,10 @@ class PropertyMeta (type):
|
|||||||
for base in bases:
|
for base in bases:
|
||||||
maps = ( getattr(base, '__properties__', {}),
|
maps = ( getattr(base, '__properties__', {}),
|
||||||
getattr(base, '__derived_properties__', {}) )
|
getattr(base, '__derived_properties__', {}) )
|
||||||
for map in maps:
|
for _map in maps:
|
||||||
for p in map.keys():
|
for p in _map.keys():
|
||||||
if not props.has_key(p) and not der_props.has_key(p):
|
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
|
pass
|
||||||
pass
|
pass
|
||||||
@@ -114,8 +116,8 @@ class PropertyMeta (type):
|
|||||||
# checks if accessors are already defined:
|
# checks if accessors are already defined:
|
||||||
if getter_name not in members_names:
|
if getter_name not in members_names:
|
||||||
src = type(cls).get_getter_source(cls, getter_name, prop_name)
|
src = type(cls).get_getter_source(cls, getter_name, prop_name)
|
||||||
code = type(cls).get_func_code_from_func_src(cls, src)
|
func = get_function_from_source(src)
|
||||||
type(cls).add_method_from_func_code(cls, getter_name, code)
|
setattr(cls, getter_name, func)
|
||||||
else:
|
else:
|
||||||
cls.__msg__("Warning: Custom member '%s' overloads generated accessor of property '%s'" \
|
cls.__msg__("Warning: Custom member '%s' overloads generated accessor of property '%s'" \
|
||||||
% (getter_name, prop_name), 2)
|
% (getter_name, prop_name), 2)
|
||||||
@@ -123,8 +125,8 @@ class PropertyMeta (type):
|
|||||||
|
|
||||||
if setter_name not in members_names:
|
if setter_name not in members_names:
|
||||||
src = type(cls).get_setter_source(cls, setter_name, prop_name)
|
src = type(cls).get_setter_source(cls, setter_name, prop_name)
|
||||||
code = type(cls).get_func_code_from_func_src(cls, src)
|
func = get_function_from_source(src)
|
||||||
type(cls).add_method_from_func_code(cls, setter_name, code)
|
setattr(cls, setter_name, func)
|
||||||
else:
|
else:
|
||||||
cls.__msg__("Warning: Custom member '%s' overloads generated accessor of property '%s'" \
|
cls.__msg__("Warning: Custom member '%s' overloads generated accessor of property '%s'" \
|
||||||
% (setter_name, prop_name), 2)
|
% (setter_name, prop_name), 2)
|
||||||
@@ -133,7 +135,7 @@ class PropertyMeta (type):
|
|||||||
prop = property(getattr(cls, getter_name), getattr(cls, setter_name))
|
prop = property(getattr(cls, getter_name), getattr(cls, setter_name))
|
||||||
|
|
||||||
if prop_name in members_names:
|
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)
|
% (prop_name, cls.__name__), 2)
|
||||||
pass
|
pass
|
||||||
setattr(cls, prop_name, prop)
|
setattr(cls, prop_name, prop)
|
||||||
@@ -188,25 +190,10 @@ class PropertyMeta (type):
|
|||||||
|
|
||||||
return val
|
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:
|
# Override these:
|
||||||
def get_getter_source(cls, getter_name, prop_name):
|
def get_getter_source(cls, getter_name, prop_name):
|
||||||
@@ -14,7 +14,8 @@
|
|||||||
#
|
#
|
||||||
# You should have received a copy of the GNU Lesser General Public
|
# You should have received a copy of the GNU Lesser General Public
|
||||||
# License along with this library; if not, write to the Free Software
|
# 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>
|
# For more information on pygtkmvc see <http://pygtkmvc.sourceforge.net>
|
||||||
# or email to the author Roberto Cavada <cavada@irst.itc.it>.
|
# or email to the author Roberto Cavada <cavada@irst.itc.it>.
|
||||||
39
src/gtkmvc/support/utils.py
Normal file
39
src/gtkmvc/support/utils.py
Normal 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)
|
||||||
@@ -15,7 +15,8 @@
|
|||||||
#
|
#
|
||||||
# You should have received a copy of the GNU Lesser General Public
|
# You should have received a copy of the GNU Lesser General Public
|
||||||
# License along with this library; if not, write to the Free Software
|
# 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>
|
# For more information on pygtkmvc see <http://pygtkmvc.sourceforge.net>
|
||||||
# or email to the author Roberto Cavada <cavada@irst.itc.it>.
|
# or email to the author Roberto Cavada <cavada@irst.itc.it>.
|
||||||
@@ -28,6 +29,11 @@ import new
|
|||||||
|
|
||||||
# ----------------------------------------------------------------------
|
# ----------------------------------------------------------------------
|
||||||
class ObsWrapperBase (object):
|
class ObsWrapperBase (object):
|
||||||
|
"""
|
||||||
|
This class is a base class wrapper for user-defined classes and
|
||||||
|
containers like lists and maps.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.__prop_name = None
|
self.__prop_name = None
|
||||||
self.__gtkmvc_model = None
|
self.__gtkmvc_model = None
|
||||||
@@ -58,6 +64,10 @@ class ObsWrapperBase (object):
|
|||||||
|
|
||||||
# ----------------------------------------------------------------------
|
# ----------------------------------------------------------------------
|
||||||
class ObsWrapper (ObsWrapperBase):
|
class ObsWrapper (ObsWrapperBase):
|
||||||
|
"""
|
||||||
|
Base class for wrappers, like user-classes and sequences.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
def __init__(self, obj, method_names):
|
def __init__(self, obj, method_names):
|
||||||
ObsWrapperBase.__init__(self)
|
ObsWrapperBase.__init__(self)
|
||||||
@@ -66,7 +76,7 @@ class ObsWrapper (ObsWrapperBase):
|
|||||||
self.__doc__ = obj.__doc__
|
self.__doc__ = obj.__doc__
|
||||||
|
|
||||||
for name in method_names:
|
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)
|
src = self.__get_wrapper_code(name)
|
||||||
exec src
|
exec src
|
||||||
|
|
||||||
@@ -87,7 +97,7 @@ class ObsWrapper (ObsWrapperBase):
|
|||||||
return res""" % {'name' : name}
|
return res""" % {'name' : name}
|
||||||
|
|
||||||
# For all fall backs
|
# 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 __repr__(self): return self._obj.__repr__()
|
||||||
def __str__(self): return self._obj.__str__()
|
def __str__(self): return self._obj.__str__()
|
||||||
|
|
||||||
@@ -104,13 +114,13 @@ class ObsSeqWrapper (ObsWrapper):
|
|||||||
|
|
||||||
self._notify_method_before(self._obj, "__setitem__", (key,val), {})
|
self._notify_method_before(self._obj, "__setitem__", (key,val), {})
|
||||||
res = 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
|
return res
|
||||||
|
|
||||||
def __delitem__(self, key):
|
def __delitem__(self, key):
|
||||||
self._notify_method_before(self._obj, "__delitem__", (key,), {})
|
self._notify_method_before(self._obj, "__delitem__", (key,), {})
|
||||||
res = 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
|
return res
|
||||||
|
|
||||||
|
|
||||||
@@ -16,7 +16,8 @@
|
|||||||
#
|
#
|
||||||
# You should have received a copy of the GNU Lesser General Public
|
# You should have received a copy of the GNU Lesser General Public
|
||||||
# License along with this library; if not, write to the Free Software
|
# 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>
|
# For more information on pygtkmvc see <http://pygtkmvc.sourceforge.net>
|
||||||
# or email to the author Roberto Cavada <cavada@irst.itc.it>.
|
# 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.
|
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."""
|
glade_top_widget_name can be either a string name or list of names."""
|
||||||
self.manualWidgets = {}
|
self.manualWidgets = {}
|
||||||
|
self.autoWidgets = None
|
||||||
|
|
||||||
self.xmlWidgets = []
|
self.xmlWidgets = []
|
||||||
|
|
||||||
# Sets a callback for custom widgets
|
# Sets a callback for custom widgets
|
||||||
@@ -47,6 +50,7 @@ class View (object):
|
|||||||
wids = (glade_top_widget_name,)
|
wids = (glade_top_widget_name,)
|
||||||
else: wids = glade_top_widget_name # Already a list or tuple
|
else: wids = glade_top_widget_name # Already a list or tuple
|
||||||
|
|
||||||
|
# retrieves XML objects from glade
|
||||||
if (glade_filename is not None):
|
if (glade_filename is not None):
|
||||||
for i in range(0,len(wids)):
|
for i in range(0,len(wids)):
|
||||||
self.xmlWidgets.append(gtk.glade.XML(glade_filename, wids[i]))
|
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 = self[wids[0]]
|
||||||
else: self.m_topWidget = None
|
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 (register): controller.register_view(self)
|
||||||
if (not parent_view is None): self.set_parent_view(parent_view)
|
if (not parent_view is None): self.set_parent_view(parent_view)
|
||||||
return
|
return
|
||||||
@@ -72,14 +76,20 @@ class View (object):
|
|||||||
# Returns None if no widget name has been found.
|
# Returns None if no widget name has been found.
|
||||||
def __getitem__(self, key):
|
def __getitem__(self, key):
|
||||||
wid = None
|
wid = None
|
||||||
|
|
||||||
|
if self.autoWidgets:
|
||||||
|
if self.autoWidgets.has_key(key): wid = self.autoWidgets[key]
|
||||||
|
pass
|
||||||
|
else:
|
||||||
for xml in self.xmlWidgets:
|
for xml in self.xmlWidgets:
|
||||||
wid = xml.get_widget(key)
|
wid = xml.get_widget(key)
|
||||||
if wid is not None: break
|
if wid is not None: break
|
||||||
pass
|
pass
|
||||||
|
pass
|
||||||
|
|
||||||
if (wid is None):
|
if wid is None:
|
||||||
# try with manually-added widgets:
|
# try with manually-added widgets:
|
||||||
if (self.manualWidgets.has_key(key)):
|
if self.manualWidgets.has_key(key):
|
||||||
wid = self.manualWidgets[key]
|
wid = self.manualWidgets[key]
|
||||||
pass
|
pass
|
||||||
pass
|
pass
|
||||||
@@ -91,20 +101,6 @@ class View (object):
|
|||||||
if (self.m_topWidget is None): self.m_topWidget = wid
|
if (self.m_topWidget is None): self.m_topWidget = wid
|
||||||
return
|
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):
|
def show(self):
|
||||||
ret = True
|
ret = True
|
||||||
top = self.get_top_widget()
|
top = self.get_top_widget()
|
||||||
@@ -160,11 +156,60 @@ class View (object):
|
|||||||
pass
|
pass
|
||||||
return
|
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,
|
def _custom_widget_create(self, glade, function_name, widget_name,
|
||||||
str1, str2, int1, int2):
|
str1, str2, int1, int2):
|
||||||
handler = getattr(self, function_name)
|
# This code was kindly provided by Allan Douglas <zalguod at
|
||||||
return handler(str1, str2, int1, int2)
|
# 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
|
pass # end of class View
|
||||||
Reference in New Issue
Block a user