From 2624bd2b4a465a2d234951dd5b855fe8a0d46e1c Mon Sep 17 00:00:00 2001 From: Simon Feltman Date: Thu, 31 Oct 2013 03:13:53 -0700 Subject: Move gobject sub-module Python files into the main gi package This moves the signalhelper, propertyhelper, and constants Python modules from gi/_gobject into gi. Keep gi/_gobject/__init__.py around because it is still needed to maintain the "_PyGObject_API" exposed by pygobject.h. This allows external modules compiled with prior versions of PyGObject to continue working with newer versions. https://bugzilla.gnome.org/show_bug.cgi?id=712197 --- Makefile.am | 5 +- gi/__init__.py | 19 +- gi/_constants.py | 49 +++++ gi/_gobject/Makefile.am | 5 +- gi/_gobject/__init__.py | 42 +---- gi/_gobject/constants.py | 50 ----- gi/_gobject/propertyhelper.py | 417 ------------------------------------------ gi/_gobject/signalhelper.py | 259 -------------------------- gi/_propertyhelper.py | 417 ++++++++++++++++++++++++++++++++++++++++++ gi/_signalhelper.py | 258 ++++++++++++++++++++++++++ gi/module.py | 16 +- gi/overrides/GObject.py | 7 +- gi/overrides/__init__.py | 2 +- gi/types.py | 41 ++++- pygtkcompat/pygtkcompat.py | 4 +- tests/test_gi.py | 2 +- tests/test_gobject.py | 4 +- tests/test_properties.py | 2 +- tests/test_signal.py | 2 +- 19 files changed, 796 insertions(+), 805 deletions(-) create mode 100644 gi/_constants.py delete mode 100644 gi/_gobject/constants.py delete mode 100644 gi/_gobject/propertyhelper.py delete mode 100644 gi/_gobject/signalhelper.py create mode 100644 gi/_propertyhelper.py create mode 100644 gi/_signalhelper.py diff --git a/Makefile.am b/Makefile.am index ffcdc6a4..1c74126b 100644 --- a/Makefile.am +++ b/Makefile.am @@ -58,7 +58,10 @@ nobase_pyexec_PYTHON = \ gi/module.py \ gi/importer.py \ gi/pygtkcompat.py \ - gi/docstring.py + gi/docstring.py \ + gi/_constants.py \ + gi/_propertyhelper.py \ + gi/_signalhelper.py # if we build in a separate tree, we need to symlink the *.py files from the # source tree; Python does not accept the extensions and modules in different diff --git a/gi/__init__.py b/gi/__init__.py index 0645d448..7c1a279d 100644 --- a/gi/__init__.py +++ b/gi/__init__.py @@ -24,24 +24,27 @@ from __future__ import absolute_import from pkgutil import extend_path __path__ = extend_path(__path__, __name__) +import sys +import os + +# we can't have pygobject 2 loaded at the same time we load the internal _gobject +if 'gobject' in sys.modules: + raise ImportError('When using gi.repository you must not import static ' + 'modules like "gobject". Please change all occurrences ' + 'of "import gobject" to "from gi.repository import GObject".') + +from ._gi import _gobject from ._gi import _API from ._gi import Repository from ._gi import PyGIDeprecationWarning -# Force loading the GObject typelib so we have available the wrappers for -# base classes such as GInitiallyUnowned -import gi._gobject -gi # pyflakes - _API = _API # pyflakes PyGIDeprecationWarning = PyGIDeprecationWarning -import os - _versions = {} _overridesdir = os.path.join(os.path.dirname(__file__), 'overrides') -version_info = gi._gobject.pygobject_version[:] +version_info = _gobject.pygobject_version[:] __version__ = "{0}.{1}.{2}".format(*version_info) diff --git a/gi/_constants.py b/gi/_constants.py new file mode 100644 index 00000000..495c6e1f --- /dev/null +++ b/gi/_constants.py @@ -0,0 +1,49 @@ +# -*- Mode: Python; py-indent-offset: 4 -*- +# pygobject - Python bindings for the GObject library +# Copyright (C) 2006-2007 Johan Dahlin +# +# gi/_constants.py: GObject type constants +# +# This library 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.1 of the License, or (at your option) any later version. +# +# This library 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-1301 +# USA + +from ._gi import _gobject + +TYPE_INVALID = _gobject.TYPE_INVALID +TYPE_NONE = _gobject.type_from_name('void') +TYPE_INTERFACE = _gobject.type_from_name('GInterface') +TYPE_CHAR = _gobject.type_from_name('gchar') +TYPE_UCHAR = _gobject.type_from_name('guchar') +TYPE_BOOLEAN = _gobject.type_from_name('gboolean') +TYPE_INT = _gobject.type_from_name('gint') +TYPE_UINT = _gobject.type_from_name('guint') +TYPE_LONG = _gobject.type_from_name('glong') +TYPE_ULONG = _gobject.type_from_name('gulong') +TYPE_INT64 = _gobject.type_from_name('gint64') +TYPE_UINT64 = _gobject.type_from_name('guint64') +TYPE_ENUM = _gobject.type_from_name('GEnum') +TYPE_FLAGS = _gobject.type_from_name('GFlags') +TYPE_FLOAT = _gobject.type_from_name('gfloat') +TYPE_DOUBLE = _gobject.type_from_name('gdouble') +TYPE_STRING = _gobject.type_from_name('gchararray') +TYPE_POINTER = _gobject.type_from_name('gpointer') +TYPE_BOXED = _gobject.type_from_name('GBoxed') +TYPE_PARAM = _gobject.type_from_name('GParam') +TYPE_OBJECT = _gobject.type_from_name('GObject') +TYPE_PYOBJECT = _gobject.type_from_name('PyObject') +TYPE_GTYPE = _gobject.type_from_name('GType') +TYPE_STRV = _gobject.type_from_name('GStrv') +TYPE_VARIANT = _gobject.type_from_name('GVariant') +TYPE_UNICHAR = TYPE_UINT diff --git a/gi/_gobject/Makefile.am b/gi/_gobject/Makefile.am index b128acc1..37068b87 100644 --- a/gi/_gobject/Makefile.am +++ b/gi/_gobject/Makefile.am @@ -4,10 +4,7 @@ PLATFORM_VERSION = 3.0 pygobjectdir = $(pyexecdir)/gi/_gobject pygobject_PYTHON = \ - __init__.py \ - constants.py \ - propertyhelper.py \ - signalhelper.py + __init__.py # if we build in a separate tree, we need to symlink the *.py files from the # source tree; Python does not accept the extensions and modules in different diff --git a/gi/_gobject/__init__.py b/gi/_gobject/__init__.py index 0bd57425..d559e745 100644 --- a/gi/_gobject/__init__.py +++ b/gi/_gobject/__init__.py @@ -19,44 +19,6 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 # USA -# this can go when things are a little further along - -import sys - -# we can't have pygobject 2 loaded at the same time we load the internal _gobject -if 'gobject' in sys.modules: - raise ImportError('When using gi.repository you must not import static modules like "gobject". Please change all occurrences of "import gobject" to "from gi.repository import GObject".') - +# Needed for compatibility with "pygobject.h" import gi._gi -_gobject = gi._gi._gobject - -from . import propertyhelper -from . import signalhelper - -GObject = _gobject.GObject -GType = _gobject.GType -_PyGObject_API = _gobject._PyGObject_API -pygobject_version = _gobject.pygobject_version - - -class GObjectMeta(type): - "Metaclass for automatically registering GObject classes" - def __init__(cls, name, bases, dict_): - type.__init__(cls, name, bases, dict_) - propertyhelper.install_properties(cls) - signalhelper.install_signals(cls) - cls._type_register(cls.__dict__) - - def _type_register(cls, namespace): - ## don't register the class if already registered - if '__gtype__' in namespace: - return - - # Do not register a new GType for the overrides, as this would sort of - # defeat the purpose of overrides... - if cls.__module__.startswith('gi.overrides.'): - return - - _gobject.type_register(cls, namespace.get('__gtype_name__')) - -_gobject._install_metaclass(GObjectMeta) +_PyGObject_API = gi._gi._gobject._PyGObject_API diff --git a/gi/_gobject/constants.py b/gi/_gobject/constants.py deleted file mode 100644 index 5bb8665c..00000000 --- a/gi/_gobject/constants.py +++ /dev/null @@ -1,50 +0,0 @@ -# -*- Mode: Python; py-indent-offset: 4 -*- -# pygobject - Python bindings for the GObject library -# Copyright (C) 2006-2007 Johan Dahlin -# -# gobject/constants.py: GObject type constants -# -# This library 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.1 of the License, or (at your option) any later version. -# -# This library 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-1301 -# USA - -import gi._gi -_gobject = gi._gi._gobject - -TYPE_INVALID = _gobject.TYPE_INVALID -TYPE_NONE = _gobject.type_from_name('void') -TYPE_INTERFACE = _gobject.type_from_name('GInterface') -TYPE_CHAR = _gobject.type_from_name('gchar') -TYPE_UCHAR = _gobject.type_from_name('guchar') -TYPE_BOOLEAN = _gobject.type_from_name('gboolean') -TYPE_INT = _gobject.type_from_name('gint') -TYPE_UINT = _gobject.type_from_name('guint') -TYPE_LONG = _gobject.type_from_name('glong') -TYPE_ULONG = _gobject.type_from_name('gulong') -TYPE_INT64 = _gobject.type_from_name('gint64') -TYPE_UINT64 = _gobject.type_from_name('guint64') -TYPE_ENUM = _gobject.type_from_name('GEnum') -TYPE_FLAGS = _gobject.type_from_name('GFlags') -TYPE_FLOAT = _gobject.type_from_name('gfloat') -TYPE_DOUBLE = _gobject.type_from_name('gdouble') -TYPE_STRING = _gobject.type_from_name('gchararray') -TYPE_POINTER = _gobject.type_from_name('gpointer') -TYPE_BOXED = _gobject.type_from_name('GBoxed') -TYPE_PARAM = _gobject.type_from_name('GParam') -TYPE_OBJECT = _gobject.type_from_name('GObject') -TYPE_PYOBJECT = _gobject.type_from_name('PyObject') -TYPE_GTYPE = _gobject.type_from_name('GType') -TYPE_STRV = _gobject.type_from_name('GStrv') -TYPE_VARIANT = _gobject.type_from_name('GVariant') -TYPE_UNICHAR = TYPE_UINT diff --git a/gi/_gobject/propertyhelper.py b/gi/_gobject/propertyhelper.py deleted file mode 100644 index 0ee0d373..00000000 --- a/gi/_gobject/propertyhelper.py +++ /dev/null @@ -1,417 +0,0 @@ -# -*- Mode: Python; py-indent-offset: 4 -*- -# pygobject - Python bindings for the GObject library -# Copyright (C) 2007 Johan Dahlin -# -# gobject/propertyhelper.py: GObject property wrapper/helper -# -# This library 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.1 of the License, or (at your option) any later version. -# -# This library 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-1301 -# USA - -import sys - -import gi._gi -_gobject = gi._gi._gobject - -from .constants import \ - TYPE_NONE, TYPE_INTERFACE, TYPE_CHAR, TYPE_UCHAR, \ - TYPE_BOOLEAN, TYPE_INT, TYPE_UINT, TYPE_LONG, \ - TYPE_ULONG, TYPE_INT64, TYPE_UINT64, TYPE_ENUM, TYPE_FLAGS, \ - TYPE_FLOAT, TYPE_DOUBLE, TYPE_STRING, \ - TYPE_POINTER, TYPE_BOXED, TYPE_PARAM, TYPE_OBJECT, \ - TYPE_PYOBJECT, TYPE_GTYPE, TYPE_STRV, TYPE_VARIANT - -G_MAXFLOAT = _gobject.G_MAXFLOAT -G_MAXDOUBLE = _gobject.G_MAXDOUBLE -G_MININT = _gobject.G_MININT -G_MAXINT = _gobject.G_MAXINT -G_MAXUINT = _gobject.G_MAXUINT -G_MINLONG = _gobject.G_MINLONG -G_MAXLONG = _gobject.G_MAXLONG -G_MAXULONG = _gobject.G_MAXULONG - -if sys.version_info >= (3, 0): - _basestring = str - _long = int -else: - _basestring = basestring - _long = long - - -class Property(object): - """ - Creates a new property which in conjunction with GObject subclass will - create a property proxy: - - class MyObject(GObject.GObject): - ... prop = GObject.Property(type=str) - - obj = MyObject() - obj.prop = 'value' - - obj.prop # now is 'value' - - The API is similar to the builtin property: - - class AnotherObject(GObject.GObject): - @GObject.Property - def prop(self): - '''Read only property.''' - return ... - - @GObject.Property(type=int) - def propInt(self): - '''Read-write integer property.''' - return ... - - @propInt.setter - def propInt(self, value): - ... - """ - _type_from_pytype_lookup = { - # Put long_ first in case long_ and int are the same so int clobbers long_ - _long: TYPE_LONG, - int: TYPE_INT, - bool: TYPE_BOOLEAN, - float: TYPE_DOUBLE, - str: TYPE_STRING, - object: TYPE_PYOBJECT, - } - - _min_value_lookup = { - TYPE_UINT: 0, - TYPE_ULONG: 0, - TYPE_UINT64: 0, - # Remember that G_MINFLOAT and G_MINDOUBLE are something different. - TYPE_FLOAT: -G_MAXFLOAT, - TYPE_DOUBLE: -G_MAXDOUBLE, - TYPE_INT: G_MININT, - TYPE_LONG: G_MINLONG, - TYPE_INT64: -2 ** 63, - } - - _max_value_lookup = { - TYPE_UINT: G_MAXUINT, - TYPE_ULONG: G_MAXULONG, - TYPE_INT64: 2 ** 63 - 1, - TYPE_UINT64: 2 ** 64 - 1, - TYPE_FLOAT: G_MAXFLOAT, - TYPE_DOUBLE: G_MAXDOUBLE, - TYPE_INT: G_MAXINT, - TYPE_LONG: G_MAXLONG, - } - - _default_lookup = { - TYPE_INT: 0, - TYPE_UINT: 0, - TYPE_LONG: 0, - TYPE_ULONG: 0, - TYPE_INT64: 0, - TYPE_UINT64: 0, - TYPE_STRING: '', - TYPE_FLOAT: 0.0, - TYPE_DOUBLE: 0.0, - } - - class __metaclass__(type): - def __repr__(self): - return "" - - def __init__(self, getter=None, setter=None, type=None, default=None, - nick='', blurb='', flags=_gobject.PARAM_READWRITE, - minimum=None, maximum=None): - """ - @param getter: getter to get the value of the property - @type getter: callable - @param setter: setter to set the value of the property - @type setter: callable - @param type: type of property - @type type: type - @param default: default value - @param nick: short description - @type nick: string - @param blurb: long description - @type blurb: string - @param flags: parameter flags, one of: - - gobject.PARAM_READABLE - - gobject.PARAM_READWRITE - - gobject.PARAM_WRITABLE - - gobject.PARAM_CONSTRUCT - - gobject.PARAM_CONSTRUCT_ONLY - - gobject.PARAM_LAX_VALIDATION - @keyword minimum: minimum allowed value (int, float, long only) - @keyword maximum: maximum allowed value (int, float, long only) - """ - - self.name = None - - if type is None: - type = object - self.type = self._type_from_python(type) - self.default = self._get_default(default) - self._check_default() - - if not isinstance(nick, _basestring): - raise TypeError("nick must be a string") - self.nick = nick - - if not isinstance(blurb, _basestring): - raise TypeError("blurb must be a string") - self.blurb = blurb - # Always clobber __doc__ with blurb even if blurb is empty because - # we don't want the lengthy Property class documentation showing up - # on instances. - self.__doc__ = blurb - self.flags = flags - - # Call after setting blurb for potential __doc__ usage. - if getter and not setter: - setter = self._readonly_setter - elif setter and not getter: - getter = self._writeonly_getter - elif not setter and not getter: - getter = self._default_getter - setter = self._default_setter - self.getter(getter) - # do not call self.setter() here, as this defines the property name - # already - self.fset = setter - - if minimum is not None: - if minimum < self._get_minimum(): - raise TypeError( - "Minimum for type %s cannot be lower than %d" % - (self.type, self._get_minimum())) - else: - minimum = self._get_minimum() - self.minimum = minimum - if maximum is not None: - if maximum > self._get_maximum(): - raise TypeError( - "Maximum for type %s cannot be higher than %d" % - (self.type, self._get_maximum())) - else: - maximum = self._get_maximum() - self.maximum = maximum - - self._exc = None - - def __repr__(self): - return '' % ( - self.name or '(uninitialized)', - _gobject.type_name(self.type)) - - def __get__(self, instance, klass): - if instance is None: - return self - - self._exc = None - value = instance.get_property(self.name) - if self._exc: - exc = self._exc - self._exc = None - raise exc - - return value - - def __set__(self, instance, value): - if instance is None: - raise TypeError - - self._exc = None - instance.set_property(self.name, value) - if self._exc: - exc = self._exc - self._exc = None - raise exc - - def __call__(self, fget): - """Allows application of the getter along with init arguments.""" - return self.getter(fget) - - def getter(self, fget): - """Set the getter function to fget. For use as a decorator.""" - if fget.__doc__: - # Always clobber docstring and blurb with the getter docstring. - self.blurb = fget.__doc__ - self.__doc__ = fget.__doc__ - self.fget = fget - return self - - def setter(self, fset): - """Set the setter function to fset. For use as a decorator.""" - self.fset = fset - # with a setter decorator, we must ignore the name of the method in - # install_properties, as this does not need to be a valid property name - # and does not define the property name. So set the name here. - if not self.name: - self.name = self.fget.__name__ - return self - - def _type_from_python(self, type_): - if type_ in self._type_from_pytype_lookup: - return self._type_from_pytype_lookup[type_] - elif (isinstance(type_, type) and - issubclass(type_, (_gobject.GObject, - _gobject.GEnum, - _gobject.GFlags, - _gobject.GBoxed, - _gobject.GInterface))): - return type_.__gtype__ - elif type_ in (TYPE_NONE, TYPE_INTERFACE, TYPE_CHAR, TYPE_UCHAR, - TYPE_INT, TYPE_UINT, TYPE_BOOLEAN, TYPE_LONG, - TYPE_ULONG, TYPE_INT64, TYPE_UINT64, - TYPE_FLOAT, TYPE_DOUBLE, TYPE_POINTER, - TYPE_BOXED, TYPE_PARAM, TYPE_OBJECT, TYPE_STRING, - TYPE_PYOBJECT, TYPE_GTYPE, TYPE_STRV, TYPE_VARIANT): - return type_ - else: - raise TypeError("Unsupported type: %r" % (type_,)) - - def _get_default(self, default): - if default is not None: - return default - return self._default_lookup.get(self.type, None) - - def _check_default(self): - ptype = self.type - default = self.default - if (ptype == TYPE_BOOLEAN and (default not in (True, False))): - raise TypeError( - "default must be True or False, not %r" % (default,)) - elif ptype == TYPE_PYOBJECT: - if default is not None: - raise TypeError("object types does not have default values") - elif ptype == TYPE_GTYPE: - if default is not None: - raise TypeError("GType types does not have default values") - elif _gobject.type_is_a(ptype, TYPE_ENUM): - if default is None: - raise TypeError("enum properties needs a default value") - elif not _gobject.type_is_a(default, ptype): - raise TypeError("enum value %s must be an instance of %r" % - (default, ptype)) - elif _gobject.type_is_a(ptype, TYPE_FLAGS): - if not _gobject.type_is_a(default, ptype): - raise TypeError("flags value %s must be an instance of %r" % - (default, ptype)) - elif _gobject.type_is_a(ptype, TYPE_STRV) and default is not None: - if not isinstance(default, list): - raise TypeError("Strv value %s must be a list" % repr(default)) - for val in default: - if type(val) not in (str, bytes): - raise TypeError("Strv value %s must contain only strings" % str(default)) - elif _gobject.type_is_a(ptype, TYPE_VARIANT) and default is not None: - if not hasattr(default, '__gtype__') or not _gobject.type_is_a(default, TYPE_VARIANT): - raise TypeError("variant value %s must be an instance of %r" % - (default, ptype)) - - def _get_minimum(self): - return self._min_value_lookup.get(self.type, None) - - def _get_maximum(self): - return self._max_value_lookup.get(self.type, None) - - # - # Getter and Setter - # - - def _default_setter(self, instance, value): - setattr(instance, '_property_helper_' + self.name, value) - - def _default_getter(self, instance): - return getattr(instance, '_property_helper_' + self.name, self.default) - - def _readonly_setter(self, instance, value): - self._exc = TypeError("%s property of %s is read-only" % ( - self.name, type(instance).__name__)) - - def _writeonly_getter(self, instance): - self._exc = TypeError("%s property of %s is write-only" % ( - self.name, type(instance).__name__)) - - # - # Public API - # - - def get_pspec_args(self): - ptype = self.type - if ptype in (TYPE_INT, TYPE_UINT, TYPE_LONG, TYPE_ULONG, - TYPE_INT64, TYPE_UINT64, TYPE_FLOAT, TYPE_DOUBLE): - args = self.minimum, self.maximum, self.default - elif (ptype == TYPE_STRING or ptype == TYPE_BOOLEAN or - ptype.is_a(TYPE_ENUM) or ptype.is_a(TYPE_FLAGS) or - ptype.is_a(TYPE_VARIANT)): - args = (self.default,) - elif ptype in (TYPE_PYOBJECT, TYPE_GTYPE): - args = () - elif ptype.is_a(TYPE_OBJECT) or ptype.is_a(TYPE_BOXED): - args = () - else: - raise NotImplementedError(ptype) - - return (self.type, self.nick, self.blurb) + args + (self.flags,) - - -def install_properties(cls): - """ - Scans the given class for instances of Property and merges them - into the classes __gproperties__ dict if it exists or adds it if not. - """ - gproperties = cls.__dict__.get('__gproperties__', {}) - - props = [] - for name, prop in cls.__dict__.items(): - if isinstance(prop, Property): # not same as the built-in - # if a property was defined with a decorator, it may already have - # a name; if it was defined with an assignment (prop = Property(...)) - # we set the property's name to the member name - if not prop.name: - prop.name = name - # we will encounter the same property multiple times in case of - # custom setter methods - if prop.name in gproperties: - if gproperties[prop.name] == prop.get_pspec_args(): - continue - raise ValueError('Property %s was already found in __gproperties__' % prop.name) - gproperties[prop.name] = prop.get_pspec_args() - props.append(prop) - - if not props: - return - - cls.__gproperties__ = gproperties - - if 'do_get_property' in cls.__dict__ or 'do_set_property' in cls.__dict__: - for prop in props: - if prop.fget != prop._default_getter or prop.fset != prop._default_setter: - raise TypeError( - "GObject subclass %r defines do_get/set_property" - " and it also uses a property with a custom setter" - " or getter. This is not allowed" % - (cls.__name__,)) - - def obj_get_property(self, pspec): - name = pspec.name.replace('-', '_') - prop = getattr(cls, name, None) - if prop: - return prop.fget(self) - cls.do_get_property = obj_get_property - - def obj_set_property(self, pspec, value): - name = pspec.name.replace('-', '_') - prop = getattr(cls, name, None) - if prop: - prop.fset(self, value) - cls.do_set_property = obj_set_property diff --git a/gi/_gobject/signalhelper.py b/gi/_gobject/signalhelper.py deleted file mode 100644 index 19207cc3..00000000 --- a/gi/_gobject/signalhelper.py +++ /dev/null @@ -1,259 +0,0 @@ -# -*- Mode: Python; py-indent-offset: 4 -*- -# pygobject - Python bindings for the GObject library -# Copyright (C) 2012 Simon Feltman -# -# gobject/signalhelper.py: GObject signal binding decorator object -# -# This library 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.1 of the License, or (at your option) any later version. -# -# This library 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-1301 -# USA - -import sys -import inspect - -import gi._gi -_gobject = gi._gi._gobject - -# Callable went away in python 3.0 and came back in 3.2. -# Use versioning to figure out when to define it, otherwise we have to deal with -# the complexity of using __builtin__ or builtin between python versions to -# check if callable exists which PyFlakes will also complain about. -if (3, 0) <= sys.version_info < (3, 2): - def callable(fn): - return hasattr(fn, '__call__') - - -class Signal(str): - """ - Object which gives a nice API for creating and binding signals. - - Example: - class Spam(GObject.GObject): - velocity = 0 - - @GObject.Signal - def pushed(self): - self.velocity += 1 - - @GObject.Signal(flags=GObject.SignalFlags.RUN_LAST) - def pulled(self): - self.velocity -= 1 - - stomped = GObject.Signal('stomped', arg_types=(int,)) - - @GObject.Signal - def annotated_signal(self, a:int, b:str): - "Python3 annotation support for parameter types. - - def on_pushed(obj): - print(obj) - - spam = Spam() - spam.pushed.connect(on_pushed) - spam.pushed.emit() - """ - class BoundSignal(str): - """ - Temporary binding object which can be used for connecting signals - without specifying the signal name string to connect. - """ - def __new__(cls, name, *args, **kargs): - return str.__new__(cls, name) - - def __init__(self, signal, gobj): - str.__init__(self) - self.signal = signal - self.gobj = gobj - - def __repr__(self): - return 'BoundSignal("%s")' % self - - def __call__(self, *args, **kargs): - """Call the signals closure.""" - return self.signal.func(self.gobj, *args, **kargs) - - def connect(self, callback, *args, **kargs): - """Same as GObject.GObject.connect except there is no need to specify - the signal name.""" - return self.gobj.connect(self, callback, *args, **kargs) - - def connect_detailed(self, callback, detail, *args, **kargs): - """Same as GObject.GObject.connect except there is no need to specify - the signal name. In addition concats "::" to the signal name - when connecting; for use with notifications like "notify" when a property - changes. - """ - return self.gobj.connect(self + '::' + detail, callback, *args, **kargs) - - def disconnect(self, handler_id): - """Same as GObject.GObject.disconnect.""" - self.instance.disconnect(handler_id) - - def emit(self, *args, **kargs): - """Same as GObject.GObject.emit except there is no need to specify - the signal name.""" - return self.gobj.emit(str(self), *args, **kargs) - - def __new__(cls, name='', *args, **kargs): - if callable(name): - name = name.__name__ - return str.__new__(cls, name) - - def __init__(self, name='', func=None, flags=_gobject.SIGNAL_RUN_FIRST, - return_type=None, arg_types=None, doc='', accumulator=None, accu_data=None): - """ - @param name: name of signal or closure method when used as direct decorator. - @type name: string or callable - @param func: closure method. - @type func: callable - @param flags: flags specifying when to run closure - @type flags: GObject.SignalFlags - @param return_type: return type - @type return_type: type - @param arg_types: list of argument types specifying the signals function signature - @type arg_types: None - @param doc: documentation of signal object - @type doc: string - @param accumulator: accumulator method with the signature: - func(ihint, return_accu, handler_return, accu_data) -> boolean - @type accumulator: function - @param accu_data: user data passed to the accumulator - @type accu_data: object - """ - if func and not name: - name = func.__name__ - elif callable(name): - func = name - name = func.__name__ - if func and not doc: - doc = func.__doc__ - - str.__init__(self) - - if func and not (return_type or arg_types): - return_type, arg_types = get_signal_annotations(func) - if arg_types is None: - arg_types = tuple() - - self.func = func - self.flags = flags - self.return_type = return_type - self.arg_types = arg_types - self.__doc__ = doc - self.accumulator = accumulator - self.accu_data = accu_data - - def __get__(self, instance, owner=None): - """Returns a BoundSignal when accessed on an object instance.""" - if instance is None: - return self - return self.BoundSignal(self, instance) - - def __call__(self, obj, *args, **kargs): - """Allows for instantiated Signals to be used as a decorator or calling - of the underlying signal method.""" - - # If obj is a GObject, than we call this signal as a closure otherwise - # it is used as a re-application of a decorator. - if isinstance(obj, _gobject.GObject): - self.func(obj, *args, **kargs) - else: - # If self is already an allocated name, use it otherwise create a new named - # signal using the closure name as the name. - if str(self): - name = str(self) - else: - name = obj.__name__ - # Return a new value of this type since it is based on an immutable string. - return type(self)(name=name, func=obj, flags=self.flags, - return_type=self.return_type, arg_types=self.arg_types, - doc=self.__doc__, accumulator=self.accumulator, accu_data=self.accu_data) - - def copy(self, newName=None): - """Returns a renamed copy of the Signal.""" - if newName is None: - newName = self.name - return type(self)(name=newName, func=self.func, flags=self.flags, - return_type=self.return_type, arg_types=self.arg_types, - doc=self.__doc__, accumulator=self.accumulator, accu_data=self.accu_data) - - def get_signal_args(self): - """Returns a tuple of: (flags, return_type, arg_types, accumulator, accu_data)""" - return (self.flags, self.return_type, self.arg_types, self.accumulator, self.accu_data) - - -class SignalOverride(Signal): - """Specialized sub-class of signal which can be used as a decorator for overriding - existing signals on GObjects. - - Example: - class MyWidget(Gtk.Widget): - @GObject.SignalOverride - def configure_event(self): - pass - """ - def get_signal_args(self): - """Returns the string 'override'.""" - return 'override' - - -def get_signal_annotations(func): - """Attempt pulling python 3 function annotations off of 'func' for - use as a signals type information. Returns an ordered nested tuple - of (return_type, (arg_type1, arg_type2, ...)). If the given function - does not have annotations then (None, tuple()) is returned. - """ - arg_types = tuple() - return_type = None - - if hasattr(func, '__annotations__'): - spec = inspect.getfullargspec(func) - arg_types = tuple(spec.annotations[arg] for arg in spec.args - if arg in spec.annotations) - if 'return' in spec.annotations: - return_type = spec.annotations['return'] - - return return_type, arg_types - - -def install_signals(cls): - """Adds Signal instances on a GObject derived class into the '__gsignals__' - dictionary to be picked up and registered as real GObject signals. - """ - gsignals = cls.__dict__.get('__gsignals__', {}) - newsignals = {} - for name, signal in cls.__dict__.items(): - if isinstance(signal, Signal): - signalName = str(signal) - # Fixup a signal which is unnamed by using the class variable name. - # Since Signal is based on string which immutable, - # we must copy and replace the class variable. - if not signalName: - signalName = name - signal = signal.copy(name) - setattr(cls, name, signal) - if signalName in gsignals: - raise ValueError('Signal "%s" has already been registered.' % name) - newsignals[signalName] = signal - gsignals[signalName] = signal.get_signal_args() - - cls.__gsignals__ = gsignals - - # Setup signal closures by adding the specially named - # method to the class in the form of "do_". - for name, signal in newsignals.items(): - if signal.func is not None: - funcName = 'do_' + name.replace('-', '_') - if not hasattr(cls, funcName): - setattr(cls, funcName, signal.func) diff --git a/gi/_propertyhelper.py b/gi/_propertyhelper.py new file mode 100644 index 00000000..ddc81cc7 --- /dev/null +++ b/gi/_propertyhelper.py @@ -0,0 +1,417 @@ +# -*- Mode: Python; py-indent-offset: 4 -*- +# pygobject - Python bindings for the GObject library +# Copyright (C) 2007 Johan Dahlin +# +# gi/_propertyhelper.py: GObject property wrapper/helper +# +# This library 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.1 of the License, or (at your option) any later version. +# +# This library 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-1301 +# USA + +import sys + +import gi._gi +_gobject = gi._gi._gobject + +from ._constants import \ + TYPE_NONE, TYPE_INTERFACE, TYPE_CHAR, TYPE_UCHAR, \ + TYPE_BOOLEAN, TYPE_INT, TYPE_UINT, TYPE_LONG, \ + TYPE_ULONG, TYPE_INT64, TYPE_UINT64, TYPE_ENUM, TYPE_FLAGS, \ + TYPE_FLOAT, TYPE_DOUBLE, TYPE_STRING, \ + TYPE_POINTER, TYPE_BOXED, TYPE_PARAM, TYPE_OBJECT, \ + TYPE_PYOBJECT, TYPE_GTYPE, TYPE_STRV, TYPE_VARIANT + +G_MAXFLOAT = _gobject.G_MAXFLOAT +G_MAXDOUBLE = _gobject.G_MAXDOUBLE +G_MININT = _gobject.G_MININT +G_MAXINT = _gobject.G_MAXINT +G_MAXUINT = _gobject.G_MAXUINT +G_MINLONG = _gobject.G_MINLONG +G_MAXLONG = _gobject.G_MAXLONG +G_MAXULONG = _gobject.G_MAXULONG + +if sys.version_info >= (3, 0): + _basestring = str + _long = int +else: + _basestring = basestring + _long = long + + +class Property(object): + """ + Creates a new property which in conjunction with GObject subclass will + create a property proxy: + + class MyObject(GObject.GObject): + ... prop = GObject.Property(type=str) + + obj = MyObject() + obj.prop = 'value' + + obj.prop # now is 'value' + + The API is similar to the builtin property: + + class AnotherObject(GObject.GObject): + @GObject.Property + def prop(self): + '''Read only property.''' + return ... + + @GObject.Property(type=int) + def propInt(self): + '''Read-write integer property.''' + return ... + + @propInt.setter + def propInt(self, value): + ... + """ + _type_from_pytype_lookup = { + # Put long_ first in case long_ and int are the same so int clobbers long_ + _long: TYPE_LONG, + int: TYPE_INT, + bool: TYPE_BOOLEAN, + float: TYPE_DOUBLE, + str: TYPE_STRING, + object: TYPE_PYOBJECT, + } + + _min_value_lookup = { + TYPE_UINT: 0, + TYPE_ULONG: 0, + TYPE_UINT64: 0, + # Remember that G_MINFLOAT and G_MINDOUBLE are something different. + TYPE_FLOAT: -G_MAXFLOAT, + TYPE_DOUBLE: -G_MAXDOUBLE, + TYPE_INT: G_MININT, + TYPE_LONG: G_MINLONG, + TYPE_INT64: -2 ** 63, + } + + _max_value_lookup = { + TYPE_UINT: G_MAXUINT, + TYPE_ULONG: G_MAXULONG, + TYPE_INT64: 2 ** 63 - 1, + TYPE_UINT64: 2 ** 64 - 1, + TYPE_FLOAT: G_MAXFLOAT, + TYPE_DOUBLE: G_MAXDOUBLE, + TYPE_INT: G_MAXINT, + TYPE_LONG: G_MAXLONG, + } + + _default_lookup = { + TYPE_INT: 0, + TYPE_UINT: 0, + TYPE_LONG: 0, + TYPE_ULONG: 0, + TYPE_INT64: 0, + TYPE_UINT64: 0, + TYPE_STRING: '', + TYPE_FLOAT: 0.0, + TYPE_DOUBLE: 0.0, + } + + class __metaclass__(type): + def __repr__(self): + return "" + + def __init__(self, getter=None, setter=None, type=None, default=None, + nick='', blurb='', flags=_gobject.PARAM_READWRITE, + minimum=None, maximum=None): + """ + @param getter: getter to get the value of the property + @type getter: callable + @param setter: setter to set the value of the property + @type setter: callable + @param type: type of property + @type type: type + @param default: default value + @param nick: short description + @type nick: string + @param blurb: long description + @type blurb: string + @param flags: parameter flags, one of: + - gobject.PARAM_READABLE + - gobject.PARAM_READWRITE + - gobject.PARAM_WRITABLE + - gobject.PARAM_CONSTRUCT + - gobject.PARAM_CONSTRUCT_ONLY + - gobject.PARAM_LAX_VALIDATION + @keyword minimum: minimum allowed value (int, float, long only) + @keyword maximum: maximum allowed value (int, float, long only) + """ + + self.name = None + + if type is None: + type = object + self.type = self._type_from_python(type) + self.default = self._get_default(default) + self._check_default() + + if not isinstance(nick, _basestring): + raise TypeError("nick must be a string") + self.nick = nick + + if not isinstance(blurb, _basestring): + raise TypeError("blurb must be a string") + self.blurb = blurb + # Always clobber __doc__ with blurb even if blurb is empty because + # we don't want the lengthy Property class documentation showing up + # on instances. + self.__doc__ = blurb + self.flags = flags + + # Call after setting blurb for potential __doc__ usage. + if getter and not setter: + setter = self._readonly_setter + elif setter and not getter: + getter = self._writeonly_getter + elif not setter and not getter: + getter = self._default_getter + setter = self._default_setter + self.getter(getter) + # do not call self.setter() here, as this defines the property name + # already + self.fset = setter + + if minimum is not None: + if minimum < self._get_minimum(): + raise TypeError( + "Minimum for type %s cannot be lower than %d" % + (self.type, self._get_minimum())) + else: + minimum = self._get_minimum() + self.minimum = minimum + if maximum is not None: + if maximum > self._get_maximum(): + raise TypeError( + "Maximum for type %s cannot be higher than %d" % + (self.type, self._get_maximum())) + else: + maximum = self._get_maximum() + self.maximum = maximum + + self._exc = None + + def __repr__(self): + return '' % ( + self.name or '(uninitialized)', + _gobject.type_name(self.type)) + + def __get__(self, instance, klass): + if instance is None: + return self + + self._exc = None + value = instance.get_property(self.name) + if self._exc: + exc = self._exc + self._exc = None + raise exc + + return value + + def __set__(self, instance, value): + if instance is None: + raise TypeError + + self._exc = None + instance.set_property(self.name, value) + if self._exc: + exc = self._exc + self._exc = None + raise exc + + def __call__(self, fget): + """Allows application of the getter along with init arguments.""" + return self.getter(fget) + + def getter(self, fget): + """Set the getter function to fget. For use as a decorator.""" + if fget.__doc__: + # Always clobber docstring and blurb with the getter docstring. + self.blurb = fget.__doc__ + self.__doc__ = fget.__doc__ + self.fget = fget + return self + + def setter(self, fset): + """Set the setter function to fset. For use as a decorator.""" + self.fset = fset + # with a setter decorator, we must ignore the name of the method in + # install_properties, as this does not need to be a valid property name + # and does not define the property name. So set the name here. + if not self.name: + self.name = self.fget.__name__ + return self + + def _type_from_python(self, type_): + if type_ in self._type_from_pytype_lookup: + return self._type_from_pytype_lookup[type_] + elif (isinstance(type_, type) and + issubclass(type_, (_gobject.GObject, + _gobject.GEnum, + _gobject.GFlags, + _gobject.GBoxed, + _gobject.GInterface))): + return type_.__gtype__ + elif type_ in (TYPE_NONE, TYPE_INTERFACE, TYPE_CHAR, TYPE_UCHAR, + TYPE_INT, TYPE_UINT, TYPE_BOOLEAN, TYPE_LONG, + TYPE_ULONG, TYPE_INT64, TYPE_UINT64, + TYPE_FLOAT, TYPE_DOUBLE, TYPE_POINTER, + TYPE_BOXED, TYPE_PARAM, TYPE_OBJECT, TYPE_STRING, + TYPE_PYOBJECT, TYPE_GTYPE, TYPE_STRV, TYPE_VARIANT): + return type_ + else: + raise TypeError("Unsupported type: %r" % (type_,)) + + def _get_default(self, default): + if default is not None: + return default + return self._default_lookup.get(self.type, None) + + def _check_default(self): + ptype = self.type + default = self.default + if (ptype == TYPE_BOOLEAN and (default not in (True, False))): + raise TypeError( + "default must be True or False, not %r" % (default,)) + elif ptype == TYPE_PYOBJECT: + if default is not None: + raise TypeError("object types does not have default values") + elif ptype == TYPE_GTYPE: + if default is not None: + raise TypeError("GType types does not have default values") + elif _gobject.type_is_a(ptype, TYPE_ENUM): + if default is None: + raise TypeError("enum properties needs a default value") + elif not _gobject.type_is_a(default, ptype): + raise TypeError("enum value %s must be an instance of %r" % + (default, ptype)) + elif _gobject.type_is_a(ptype, TYPE_FLAGS): + if not _gobject.type_is_a(default, ptype): + raise TypeError("flags value %s must be an instance of %r" % + (default, ptype)) + elif _gobject.type_is_a(ptype, TYPE_STRV) and default is not None: + if not isinstance(default, list): + raise TypeError("Strv value %s must be a list" % repr(default)) + for val in default: + if type(val) not in (str, bytes): + raise TypeError("Strv value %s must contain only strings" % str(default)) + elif _gobject.type_is_a(ptype, TYPE_VARIANT) and default is not None: + if not hasattr(default, '__gtype__') or not _gobject.type_is_a(default, TYPE_VARIANT): + raise TypeError("variant value %s must be an instance of %r" % + (default, ptype)) + + def _get_minimum(self): + return self._min_value_lookup.get(self.type, None) + + def _get_maximum(self): + return self._max_value_lookup.get(self.type, None) + + # + # Getter and Setter + # + + def _default_setter(self, instance, value): + setattr(instance, '_property_helper_' + self.name, value) + + def _default_getter(self, instance): + return getattr(instance, '_property_helper_' + self.name, self.default) + + def _readonly_setter(self, instance, value): + self._exc = TypeError("%s property of %s is read-only" % ( + self.name, type(instance).__name__)) + + def _writeonly_getter(self, instance): + self._exc = TypeError("%s property of %s is write-only" % ( + self.name, type(instance).__name__)) + + # + # Public API + # + + def get_pspec_args(self): + ptype = self.type + if ptype in (TYPE_INT, TYPE_UINT, TYPE_LONG, TYPE_ULONG, + TYPE_INT64, TYPE_UINT64, TYPE_FLOAT, TYPE_DOUBLE): + args = self.minimum, self.maximum, self.default + elif (ptype == TYPE_STRING or ptype == TYPE_BOOLEAN or + ptype.is_a(TYPE_ENUM) or ptype.is_a(TYPE_FLAGS) or + ptype.is_a(TYPE_VARIANT)): + args = (self.default,) + elif ptype in (TYPE_PYOBJECT, TYPE_GTYPE): + args = () + elif ptype.is_a(TYPE_OBJECT) or ptype.is_a(TYPE_BOXED): + args = () + else: + raise NotImplementedError(ptype) + + return (self.type, self.nick, self.blurb) + args + (self.flags,) + + +def install_properties(cls): + """ + Scans the given class for instances of Property and merges them + into the classes __gproperties__ dict if it exists or adds it if not. + """ + gproperties = cls.__dict__.get('__gproperties__', {}) + + props = [] + for name, prop in cls.__dict__.items(): + if isinstance(prop, Property): # not same as the built-in + # if a property was defined with a decorator, it may already have + # a name; if it was defined with an assignment (prop = Property(...)) + # we set the property's name to the member name + if not prop.name: + prop.name = name + # we will encounter the same property multiple times in case of + # custom setter methods + if prop.name in gproperties: + if gproperties[prop.name] == prop.get_pspec_args(): + continue + raise ValueError('Property %s was already found in __gproperties__' % prop.name) + gproperties[prop.name] = prop.get_pspec_args() + props.append(prop) + + if not props: + return + + cls.__gproperties__ = gproperties + + if 'do_get_property' in cls.__dict__ or 'do_set_property' in cls.__dict__: + for prop in props: + if prop.fget != prop._default_getter or prop.fset != prop._default_setter: + raise TypeError( + "GObject subclass %r defines do_get/set_property" + " and it also uses a property with a custom setter" + " or getter. This is not allowed" % + (cls.__name__,)) + + def obj_get_property(self, pspec): + name = pspec.name.replace('-', '_') + prop = getattr(cls, name, None) + if prop: + return prop.fget(self) + cls.do_get_property = obj_get_property + + def obj_set_property(self, pspec, value): + name = pspec.name.replace('-', '_') + prop = getattr(cls, name, None) + if prop: + prop.fset(self, value) + cls.do_set_property = obj_set_property diff --git a/gi/_signalhelper.py b/gi/_signalhelper.py new file mode 100644 index 00000000..79de3017 --- /dev/null +++ b/gi/_signalhelper.py @@ -0,0 +1,258 @@ +# -*- Mode: Python; py-indent-offset: 4 -*- +# pygobject - Python bindings for the GObject library +# Copyright (C) 2012 Simon Feltman +# +# gi/_signalhelper.py: GObject signal binding decorator object +# +# This library 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.1 of the License, or (at your option) any later version. +# +# This library 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-1301 +# USA + +import sys +import inspect + +from ._gi import _gobject + +# Callable went away in python 3.0 and came back in 3.2. +# Use versioning to figure out when to define it, otherwise we have to deal with +# the complexity of using __builtin__ or builtin between python versions to +# check if callable exists which PyFlakes will also complain about. +if (3, 0) <= sys.version_info < (3, 2): + def callable(fn): + return hasattr(fn, '__call__') + + +class Signal(str): + """ + Object which gives a nice API for creating and binding signals. + + Example: + class Spam(GObject.GObject): + velocity = 0 + + @GObject.Signal + def pushed(self): + self.velocity += 1 + + @GObject.Signal(flags=GObject.SignalFlags.RUN_LAST) + def pulled(self): + self.velocity -= 1 + + stomped = GObject.Signal('stomped', arg_types=(int,)) + + @GObject.Signal + def annotated_signal(self, a:int, b:str): + "Python3 annotation support for parameter types. + + def on_pushed(obj): + print(obj) + + spam = Spam() + spam.pushed.connect(on_pushed) + spam.pushed.emit() + """ + class BoundSignal(str): + """ + Temporary binding object which can be used for connecting signals + without specifying the signal name string to connect. + """ + def __new__(cls, name, *args, **kargs): + return str.__new__(cls, name) + + def __init__(self, signal, gobj): + str.__init__(self) + self.signal = signal + self.gobj = gobj + + def __repr__(self): + return 'BoundSignal("%s")' % self + + def __call__(self, *args, **kargs): + """Call the signals closure.""" + return self.signal.func(self.gobj, *args, **kargs) + + def connect(self, callback, *args, **kargs): + """Same as GObject.GObject.connect except there is no need to specify + the signal name.""" + return self.gobj.connect(self, callback, *args, **kargs) + + def connect_detailed(self, callback, detail, *args, **kargs): + """Same as GObject.GObject.connect except there is no need to specify + the signal name. In addition concats "::" to the signal name + when connecting; for use with notifications like "notify" when a property + changes. + """ + return self.gobj.connect(self + '::' + detail, callback, *args, **kargs) + + def disconnect(self, handler_id): + """Same as GObject.GObject.disconnect.""" + self.instance.disconnect(handler_id) + + def emit(self, *args, **kargs): + """Same as GObject.GObject.emit except there is no need to specify + the signal name.""" + return self.gobj.emit(str(self), *args, **kargs) + + def __new__(cls, name='', *args, **kargs): + if callable(name): + name = name.__name__ + return str.__new__(cls, name) + + def __init__(self, name='', func=None, flags=_gobject.SIGNAL_RUN_FIRST, + return_type=None, arg_types=None, doc='', accumulator=None, accu_data=None): + """ + @param name: name of signal or closure method when used as direct decorator. + @type name: string or callable + @param func: closure method. + @type func: callable + @param flags: flags specifying when to run closure + @type flags: GObject.SignalFlags + @param return_type: return type + @type return_type: type + @param arg_types: list of argument types specifying the signals function signature + @type arg_types: None + @param doc: documentation of signal object + @type doc: string + @param accumulator: accumulator method with the signature: + func(ihint, return_accu, handler_return, accu_data) -> boolean + @type accumulator: function + @param accu_data: user data passed to the accumulator + @type accu_data: object + """ + if func and not name: + name = func.__name__ + elif callable(name): + func = name + name = func.__name__ + if func and not doc: + doc = func.__doc__ + + str.__init__(self) + + if func and not (return_type or arg_types): + return_type, arg_types = get_signal_annotations(func) + if arg_types is None: + arg_types = tuple() + + self.func = func + self.flags = flags + self.return_type = return_type + self.arg_types = arg_types + self.__doc__ = doc + self.accumulator = accumulator + self.accu_data = accu_data + + def __get__(self, instance, owner=None): + """Returns a BoundSignal when accessed on an object instance.""" + if instance is None: + return self + return self.BoundSignal(self, instance) + + def __call__(self, obj, *args, **kargs): + """Allows for instantiated Signals to be used as a decorator or calling + of the underlying signal method.""" + + # If obj is a GObject, than we call this signal as a closure otherwise + # it is used as a re-application of a decorator. + if isinstance(obj, _gobject.GObject): + self.func(obj, *args, **kargs) + else: + # If self is already an allocated name, use it otherwise create a new named + # signal using the closure name as the name. + if str(self): + name = str(self) + else: + name = obj.__name__ + # Return a new value of this type since it is based on an immutable string. + return type(self)(name=name, func=obj, flags=self.flags, + return_type=self.return_type, arg_types=self.arg_types, + doc=self.__doc__, accumulator=self.accumulator, accu_data=self.accu_data) + + def copy(self, newName=None): + """Returns a renamed copy of the Signal.""" + if newName is None: + newName = self.name + return type(self)(name=newName, func=self.func, flags=self.flags, + return_type=self.return_type, arg_types=self.arg_types, + doc=self.__doc__, accumulator=self.accumulator, accu_data=self.accu_data) + + def get_signal_args(self): + """Returns a tuple of: (flags, return_type, arg_types, accumulator, accu_data)""" + return (self.flags, self.return_type, self.arg_types, self.accumulator, self.accu_data) + + +class SignalOverride(Signal): + """Specialized sub-class of signal which can be used as a decorator for overriding + existing signals on GObjects. + + Example: + class MyWidget(Gtk.Widget): + @GObject.SignalOverride + def configure_event(self): + pass + """ + def get_signal_args(self): + """Returns the string 'override'.""" + return 'override' + + +def get_signal_annotations(func): + """Attempt pulling python 3 function annotations off of 'func' for + use as a signals type information. Returns an ordered nested tuple + of (return_type, (arg_type1, arg_type2, ...)). If the given function + does not have annotations then (None, tuple()) is returned. + """ + arg_types = tuple() + return_type = None + + if hasattr(func, '__annotations__'): + spec = inspect.getfullargspec(func) + arg_types = tuple(spec.annotations[arg] for arg in spec.args + if arg in spec.annotations) + if 'return' in spec.annotations: + return_type = spec.annotations['return'] + + return return_type, arg_types + + +def install_signals(cls): + """Adds Signal instances on a GObject derived class into the '__gsignals__' + dictionary to be picked up and registered as real GObject signals. + """ + gsignals = cls.__dict__.get('__gsignals__', {}) + newsignals = {} + for name, signal in cls.__dict__.items(): + if isinstance(signal, Signal): + signalName = str(signal) + # Fixup a signal which is unnamed by using the class variable name. + # Since Signal is based on string which immutable, + # we must copy and replace the class variable. + if not signalName: + signalName = name + signal = signal.copy(name) + setattr(cls, name, signal) + if signalName in gsignals: + raise ValueError('Signal "%s" has already been registered.' % name) + newsignals[signalName] = signal + gsignals[signalName] = signal.get_signal_args() + + cls.__gsignals__ = gsignals + + # Setup signal closures by adding the specially named + # method to the class in the form of "do_". + for name, signal in newsignals.items(): + if signal.func is not None: + funcName = 'do_' + name.replace('-', '_') + if not hasattr(cls, funcName): + setattr(cls, funcName, signal.func) diff --git a/gi/module.py b/gi/module.py index 22ae551b..0032774a 100644 --- a/gi/module.py +++ b/gi/module.py @@ -54,15 +54,15 @@ from ._gi import \ enum_add, \ enum_register_new_gtype_and_add, \ flags_add, \ - flags_register_new_gtype_and_add + flags_register_new_gtype_and_add, \ + _gobject from .types import \ - GObjectMeta, \ + GIObjectMeta, \ StructMeta -import gi._gi -GInterface = gi._gi._gobject.GInterface +GInterface = _gobject.GInterface -from ._gobject.constants import \ +from ._constants import \ TYPE_NONE, \ TYPE_BOXED, \ TYPE_POINTER, \ @@ -183,13 +183,13 @@ class IntrospectionModule(object): interfaces = tuple(interface for interface in get_interfaces_for_object(info) if not issubclass(parent, interface)) bases = (parent,) + interfaces - metaclass = GObjectMeta + metaclass = GIObjectMeta elif isinstance(info, CallbackInfo): bases = (CCallback,) - metaclass = GObjectMeta + metaclass = GIObjectMeta elif isinstance(info, InterfaceInfo): bases = (GInterface,) - metaclass = GObjectMeta + metaclass = GIObjectMeta elif isinstance(info, (StructInfo, UnionInfo)): if g_type.is_a(TYPE_BOXED): bases = (Boxed,) diff --git a/gi/overrides/GObject.py b/gi/overrides/GObject.py index 147a4520..fe172caf 100644 --- a/gi/overrides/GObject.py +++ b/gi/overrides/GObject.py @@ -31,9 +31,10 @@ from gi.overrides import override from gi.repository import GLib from gi import PyGIDeprecationWarning -from gi._gobject import _gobject -from gi._gobject import propertyhelper -from gi._gobject import signalhelper +from gi import _propertyhelper as propertyhelper +from gi import _signalhelper as signalhelper + +_gobject = gi._gi._gobject GObjectModule = gi.module.get_introspection_module('GObject') diff --git a/gi/overrides/__init__.py b/gi/overrides/__init__.py index 9e7a0f1a..2cd1345d 100644 --- a/gi/overrides/__init__.py +++ b/gi/overrides/__init__.py @@ -3,7 +3,7 @@ import warnings from gi import PyGIDeprecationWarning from gi._gi import CallableInfo -from gi._gobject.constants import \ +from gi._constants import \ TYPE_NONE, \ TYPE_INVALID diff --git a/gi/types.py b/gi/types.py index c5b4bd5a..0ccfe27e 100644 --- a/gi/types.py +++ b/gi/types.py @@ -25,8 +25,7 @@ from __future__ import absolute_import import sys import warnings -from . import _gobject -from ._gobject.constants import TYPE_INVALID +from ._constants import TYPE_INVALID from .docstring import generate_doc_string from ._gi import \ @@ -35,13 +34,16 @@ from ._gi import \ StructInfo, \ VFuncInfo, \ register_interface_info, \ - hook_up_vfunc_implementation + hook_up_vfunc_implementation, \ + _gobject -import gi._gi -GInterface = gi._gi._gobject.GInterface +GInterface = _gobject.GInterface StructInfo # pyflakes +from . import _propertyhelper as propertyhelper +from . import _signalhelper as signalhelper + if (3, 0) <= sys.version_info < (3, 3): # callable not available for python 3.0 thru 3.2 def callable(obj): @@ -171,16 +173,39 @@ def find_vfunc_conflict_in_bases(vfunc, bases): return None -class GObjectMeta(_gobject.GObjectMeta, MetaClassHelper): +class GObjectMeta(type): + "Metaclass for automatically registering GObject classes" + def __init__(cls, name, bases, dict_): + type.__init__(cls, name, bases, dict_) + propertyhelper.install_properties(cls) + signalhelper.install_signals(cls) + cls._type_register(cls.__dict__) + + def _type_register(cls, namespace): + ## don't register the class if already registered + if '__gtype__' in namespace: + return + + # Do not register a new GType for the overrides, as this would sort of + # defeat the purpose of overrides... + if cls.__module__.startswith('gi.overrides.'): + return + + _gobject.type_register(cls, namespace.get('__gtype_name__')) + +_gobject._install_metaclass(GObjectMeta) + + +class GIObjectMeta(GObjectMeta, MetaClassHelper): def __init__(cls, name, bases, dict_): - super(GObjectMeta, cls).__init__(name, bases, dict_) + super(GIObjectMeta, cls).__init__(name, bases, dict_) is_gi_defined = False if cls.__module__ == 'gi.repository.' + cls.__info__.get_namespace(): is_gi_defined = True is_python_defined = False - if not is_gi_defined and cls.__module__ != GObjectMeta.__module__: + if not is_gi_defined and cls.__module__ != GIObjectMeta.__module__: is_python_defined = True if is_python_defined: diff --git a/pygtkcompat/pygtkcompat.py b/pygtkcompat/pygtkcompat.py index 67571acd..d5b7b944 100644 --- a/pygtkcompat/pygtkcompat.py +++ b/pygtkcompat/pygtkcompat.py @@ -90,8 +90,8 @@ def enable(): # gobject from gi.repository import GObject sys.modules['gobject'] = GObject - from gi._gobject import propertyhelper - sys.modules['gobject.propertyhelper'] = propertyhelper + from gi import _propertyhelper + sys.modules['gobject.propertyhelper'] = _propertyhelper # gio from gi.repository import Gio diff --git a/tests/test_gi.py b/tests/test_gi.py index 2411a49d..3af086d8 100644 --- a/tests/test_gi.py +++ b/tests/test_gi.py @@ -2406,7 +2406,7 @@ class TestMRO(unittest.TestCase): pass expected = (E, D, B, C, A, GIMarshallingTests.Object, - GObject.Object, GObject.Object.__base__, gi._gobject.GObject, + GObject.Object, GObject.Object.__base__, gi._gi._gobject.GObject, object) self.assertEqual(expected, E.__mro__) diff --git a/tests/test_gobject.py b/tests/test_gobject.py index d49011f6..a88a4bc9 100644 --- a/tests/test_gobject.py +++ b/tests/test_gobject.py @@ -8,7 +8,9 @@ import warnings from gi.repository import GObject, GLib from gi import PyGIDeprecationWarning from gi.module import get_introspection_module -from gi._gobject import _gobject + +import gi +_gobject = gi._gi._gobject import testhelper diff --git a/tests/test_properties.py b/tests/test_properties.py index ef6b8674..d7ceb894 100644 --- a/tests/test_properties.py +++ b/tests/test_properties.py @@ -23,7 +23,7 @@ from gi.repository import Gio from gi.repository import GLib from gi.repository import Regress from gi.repository import GIMarshallingTests -from gi._gobject import propertyhelper +from gi import _propertyhelper as propertyhelper if sys.version_info < (3, 0): TEST_UTF8 = "\xe2\x99\xa5" diff --git a/tests/test_signal.py b/tests/test_signal.py index e90264af..429afc9e 100644 --- a/tests/test_signal.py +++ b/tests/test_signal.py @@ -5,7 +5,7 @@ import unittest import sys from gi.repository import GObject, GLib -from gi._gobject import signalhelper +from gi import _signalhelper as signalhelper import testhelper from compathelper import _long -- cgit v1.2.1