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:
49
src/gtkmvc/__init__.py
Normal file
49
src/gtkmvc/__init__.py
Normal 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
|
||||
|
||||
|
||||
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
|
||||
336
src/gtkmvc/model.py
Normal file
336
src/gtkmvc/model.py
Normal 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
124
src/gtkmvc/model_mt.py
Normal 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
69
src/gtkmvc/observable.py
Normal 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
91
src/gtkmvc/observer.py
Normal 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
|
||||
|
||||
|
||||
|
||||
25
src/gtkmvc/support/__init__.py
Normal file
25
src/gtkmvc/support/__init__.py
Normal 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"]
|
||||
44
src/gtkmvc/support/decorators.py
Normal file
44
src/gtkmvc/support/decorators.py
Normal 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
|
||||
24
src/gtkmvc/support/exceptions.py
Normal file
24
src/gtkmvc/support/exceptions.py
Normal 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>.
|
||||
|
||||
|
||||
240
src/gtkmvc/support/metaclass_base.py
Normal file
240
src/gtkmvc/support/metaclass_base.py
Normal 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
|
||||
# ----------------------------------------------------------------------
|
||||
82
src/gtkmvc/support/metaclasses.py
Normal file
82
src/gtkmvc/support/metaclasses.py
Normal 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
|
||||
|
||||
|
||||
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)
|
||||
162
src/gtkmvc/support/wrappers.py
Normal file
162
src/gtkmvc/support/wrappers.py
Normal 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
215
src/gtkmvc/view.py
Normal 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
|
||||
Reference in New Issue
Block a user