File 000-pygtkcompat.diff of Package tryton

diff -U 3 -dHrN -- a/tryton/gui/window/view_form/view/generictreemodel.py b/tryton/gui/window/view_form/view/generictreemodel.py
--- a/tryton/gui/window/view_form/view/generictreemodel.py	1970-01-01 01:00:00.000000000 +0100
+++ b/tryton/gui/window/view_form/view/generictreemodel.py	2021-05-11 12:18:32.392430826 +0200
@@ -0,0 +1,423 @@
+# -*- Mode: Python; py-indent-offset: 4 -*-
+# generictreemodel - GenericTreeModel implementation for pygtk compatibility.
+# Copyright (C) 2013 Simon Feltman
+#
+#   generictreemodel.py: GenericTreeModel implementation for pygtk compatibility
+#
+# 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, see <http://www.gnu.org/licenses/>.
+
+
+# System
+import sys
+import random
+import collections
+import ctypes
+import platform
+
+# GObject
+from gi.repository import GObject
+from gi.repository import Gtk
+
+
+class _CTreeIter(ctypes.Structure):
+    _fields_ = [('stamp', ctypes.c_int),
+                ('user_data', ctypes.c_void_p),
+                ('user_data2', ctypes.c_void_p),
+                ('user_data3', ctypes.c_void_p)]
+
+    @classmethod
+    def from_iter(cls, iter):
+        offset = sys.getsizeof(object())  # size of PyObject_HEAD
+        return ctypes.POINTER(cls).from_address(id(iter) + offset)
+
+
+if platform.python_implementation() == "PyPy":
+    def _get_user_data_as_pyobject(iter):
+        raise NotImplementedError("Not yet supported under PyPy")
+else:
+    def _get_user_data_as_pyobject(iter):
+        citer = _CTreeIter.from_iter(iter)
+        return ctypes.cast(citer.contents.user_data, ctypes.py_object).value
+
+
+def handle_exception(default_return):
+    """Returns a function which can act as a decorator for wrapping exceptions and
+    returning "default_return" upon an exception being thrown.
+
+    This is used to wrap Gtk.TreeModel "do_" method implementations so we can return
+    a proper value from the override upon an exception occurring with client code
+    implemented by the "on_" methods.
+    """
+    def decorator(func):
+        def wrapped_func(*args, **kargs):
+            try:
+                return func(*args, **kargs)
+            except:
+                # Use excepthook directly to avoid any printing to the screen
+                # if someone installed an except hook.
+                sys.excepthook(*sys.exc_info())
+            return default_return
+        return wrapped_func
+    return decorator
+
+
+class GenericTreeModel(GObject.GObject, Gtk.TreeModel):
+    """A base implementation of a Gtk.TreeModel for python.
+
+    The GenericTreeModel eases implementing the Gtk.TreeModel interface in Python.
+    The class can be subclassed to provide a TreeModel implementation which works
+    directly with Python objects instead of iterators.
+
+    All of the on_* methods should be overridden by subclasses to provide the
+    underlying implementation a way to access custom model data. For the purposes of
+    this API, all custom model data supplied or handed back through the overridable
+    API will use the argument names: node, parent, and child in regards to user data
+    python objects.
+
+    The create_tree_iter, set_user_data, invalidate_iters, iter_is_valid methods are
+    available to help manage Gtk.TreeIter objects and their Python object references.
+
+    GenericTreeModel manages a pool of user data nodes that have been used with iters.
+    This pool stores a references to user data nodes as a dictionary value with the
+    key being the integer id of the data. This id is what the Gtk.TreeIter objects
+    use to reference data in the pool.
+    References will be removed from the pool when the model is deleted or explicitly
+    by using the optional "node" argument to the "row_deleted" method when notifying
+    the model of row deletion.
+    """
+
+    leak_references = GObject.Property(default=True, type=bool,
+                                       blurb="If True, strong references to user data attached to iters are "
+                                       "stored in a dictionary pool (default). Otherwise the user data is "
+                                       "stored as a raw pointer to a python object without a reference.")
+
+    #
+    # Methods
+    #
+    def __init__(self):
+        """Initialize. Make sure to call this from derived classes if overridden."""
+        super(GenericTreeModel, self).__init__()
+        self.stamp = 0
+
+        #: Dictionary of (id(user_data): user_data), used when leak-refernces=False
+        self._held_refs = dict()
+
+        # Set initial stamp
+        self.invalidate_iters()
+
+    def iter_depth_first(self):
+        """Depth-first iteration of the entire TreeModel yielding the python nodes."""
+        stack = collections.deque([None])
+        while stack:
+            it = stack.popleft()
+            if it is not None:
+                yield self.get_user_data(it)
+            children = [self.iter_nth_child(it, i) for i in range(self.iter_n_children(it))]
+            stack.extendleft(reversed(children))
+
+    def invalidate_iter(self, iter):
+        """Clear user data and its reference from the iter and this model."""
+        iter.stamp = 0
+        if iter.user_data:
+            if iter.user_data in self._held_refs:
+                del self._held_refs[iter.user_data]
+            iter.user_data = None
+
+    def invalidate_iters(self):
+        """
+        This method invalidates all TreeIter objects associated with this custom tree model
+        and frees their locally pooled references.
+        """
+        self.stamp = random.randint(-2147483648, 2147483647)
+        self._held_refs.clear()
+
+    def iter_is_valid(self, iter):
+        """
+        :Returns:
+            True if the gtk.TreeIter specified by iter is valid for the custom tree model.
+        """
+        return iter.stamp == self.stamp
+
+    def get_user_data(self, iter):
+        """Get the user_data associated with the given TreeIter.
+
+        GenericTreeModel stores arbitrary Python objects mapped to instances of Gtk.TreeIter.
+        This method allows to retrieve the Python object held by the given iterator.
+        """
+        if self.leak_references:
+            return self._held_refs[iter.user_data]
+        else:
+            return _get_user_data_as_pyobject(iter)
+
+    def set_user_data(self, iter, user_data):
+        """Applies user_data and stamp to the given iter.
+
+        If the models "leak_references" property is set, a reference to the
+        user_data is stored with the model to ensure we don't run into bad
+        memory problems with the TreeIter.
+        """
+        iter.user_data = id(user_data)
+
+        if user_data is None:
+            self.invalidate_iter(iter)
+        else:
+            iter.stamp = self.stamp
+            if self.leak_references:
+                self._held_refs[iter.user_data] = user_data
+
+    def create_tree_iter(self, user_data):
+        """Create a Gtk.TreeIter instance with the given user_data specific for this model.
+
+        Use this method to create Gtk.TreeIter instance instead of directly calling
+        Gtk.Treeiter(), this will ensure proper reference managment of wrapped used_data.
+        """
+        iter = Gtk.TreeIter()
+        self.set_user_data(iter, user_data)
+        return iter
+
+    def _create_tree_iter(self, data):
+        """Internal creation of a (bool, TreeIter) pair for returning directly
+        back to the view interfacing with this model."""
+        if data is None:
+            return (False, None)
+        else:
+            it = self.create_tree_iter(data)
+            return (True, it)
+
+    def row_deleted(self, path, node=None):
+        """Notify the model a row has been deleted.
+
+        Use the node parameter to ensure the user_data reference associated
+        with the path is properly freed by this model.
+
+        :Parameters:
+            path : Gtk.TreePath
+                Path to the row that has been deleted.
+            node : object
+                Python object used as the node returned from "on_get_iter". This is
+                optional but ensures the model will not leak references to this object.
+        """
+        super(GenericTreeModel, self).row_deleted(path)
+        node_id = id(node)
+        if node_id in self._held_refs:
+            del self._held_refs[node_id]
+
+    #
+    # GtkTreeModel Interface Implementation
+    #
+    @handle_exception(0)
+    def do_get_flags(self):
+        """Internal method."""
+        return self.on_get_flags()
+
+    @handle_exception(0)
+    def do_get_n_columns(self):
+        """Internal method."""
+        return self.on_get_n_columns()
+
+    @handle_exception(GObject.TYPE_INVALID)
+    def do_get_column_type(self, index):
+        """Internal method."""
+        return self.on_get_column_type(index)
+
+    @handle_exception((False, None))
+    def do_get_iter(self, path):
+        """Internal method."""
+        return self._create_tree_iter(self.on_get_iter(path))
+
+    @handle_exception(False)
+    def do_iter_next(self, iter):
+        """Internal method."""
+        if iter is None:
+            next_data = self.on_iter_next(None)
+        else:
+            next_data = self.on_iter_next(self.get_user_data(iter))
+
+        self.set_user_data(iter, next_data)
+        return next_data is not None
+
+    @handle_exception(None)
+    def do_get_path(self, iter):
+        """Internal method."""
+        path = self.on_get_path(self.get_user_data(iter))
+        if path is None:
+            return None
+        else:
+            return Gtk.TreePath(path)
+
+    @handle_exception(None)
+    def do_get_value(self, iter, column):
+        """Internal method."""
+        return self.on_get_value(self.get_user_data(iter), column)
+
+    @handle_exception((False, None))
+    def do_iter_children(self, parent):
+        """Internal method."""
+        data = self.get_user_data(parent) if parent else None
+        return self._create_tree_iter(self.on_iter_children(data))
+
+    @handle_exception(False)
+    def do_iter_has_child(self, parent):
+        """Internal method."""
+        return self.on_iter_has_child(self.get_user_data(parent))
+
+    @handle_exception(0)
+    def do_iter_n_children(self, iter):
+        """Internal method."""
+        if iter is None:
+            return self.on_iter_n_children(None)
+        return self.on_iter_n_children(self.get_user_data(iter))
+
+    @handle_exception((False, None))
+    def do_iter_nth_child(self, parent, n):
+        """Internal method."""
+        if parent is None:
+            data = self.on_iter_nth_child(None, n)
+        else:
+            data = self.on_iter_nth_child(self.get_user_data(parent), n)
+        return self._create_tree_iter(data)
+
+    @handle_exception((False, None))
+    def do_iter_parent(self, child):
+        """Internal method."""
+        return self._create_tree_iter(self.on_iter_parent(self.get_user_data(child)))
+
+    @handle_exception(None)
+    def do_ref_node(self, iter):
+        self.on_ref_node(self.get_user_data(iter))
+
+    @handle_exception(None)
+    def do_unref_node(self, iter):
+        self.on_unref_node(self.get_user_data(iter))
+
+    #
+    # Python Subclass Overridables
+    #
+    def on_get_flags(self):
+        """Overridable.
+
+        :Returns Gtk.TreeModelFlags:
+            The flags for this model. See: Gtk.TreeModelFlags
+        """
+        raise NotImplementedError
+
+    def on_get_n_columns(self):
+        """Overridable.
+
+        :Returns:
+            The number of columns for this model.
+        """
+        raise NotImplementedError
+
+    def on_get_column_type(self, index):
+        """Overridable.
+
+        :Returns:
+            The column type for the given index.
+        """
+        raise NotImplementedError
+
+    def on_get_iter(self, path):
+        """Overridable.
+
+        :Returns:
+            A python object (node) for the given TreePath.
+        """
+        raise NotImplementedError
+
+    def on_iter_next(self, node):
+        """Overridable.
+
+        :Parameters:
+            node : object
+                Node at current level.
+
+        :Returns:
+            A python object (node) following the given node at the current level.
+        """
+        raise NotImplementedError
+
+    def on_get_path(self, node):
+        """Overridable.
+
+        :Returns:
+            A TreePath for the given node.
+        """
+        raise NotImplementedError
+
+    def on_get_value(self, node, column):
+        """Overridable.
+
+        :Parameters:
+            node : object
+            column : int
+                Column index to get the value from.
+
+        :Returns:
+            The value of the column for the given node."""
+        raise NotImplementedError
+
+    def on_iter_children(self, parent):
+        """Overridable.
+
+        :Returns:
+            The first child of parent or None if parent has no children.
+            If parent is None, return the first node of the model.
+        """
+        raise NotImplementedError
+
+    def on_iter_has_child(self, node):
+        """Overridable.
+
+        :Returns:
+            True if the given node has children.
+        """
+        raise NotImplementedError
+
+    def on_iter_n_children(self, node):
+        """Overridable.
+
+        :Returns:
+            The number of children for the given node. If node is None,
+            return the number of top level nodes.
+        """
+        raise NotImplementedError
+
+    def on_iter_nth_child(self, parent, n):
+        """Overridable.
+
+        :Parameters:
+            parent : object
+            n : int
+                Index of child within parent.
+
+        :Returns:
+            The child for the given parent index starting at 0. If parent None,
+            return the top level node corresponding to "n".
+            If "n" is larger then available nodes, return None.
+        """
+        raise NotImplementedError
+
+    def on_iter_parent(self, child):
+        """Overridable.
+
+        :Returns:
+            The parent node of child or None if child is a top level node."""
+        raise NotImplementedError
+
+    def on_ref_node(self, node):
+        pass
+
+    def on_unref_node(self, node):
+        pass
diff -U 3 -dHrN -- a/tryton/gui/window/view_form/view/list.py b/tryton/gui/window/view_form/view/list.py
--- a/tryton/gui/window/view_form/view/list.py	2021-04-27 09:36:11.000000000 +0200
+++ b/tryton/gui/window/view_form/view/list.py	2021-05-11 12:20:35.783989890 +0200
@@ -7,7 +7,7 @@
 from functools import wraps
 
 from gi.repository import Gdk, GLib, GObject, Gtk
-from pygtkcompat.generictreemodel import GenericTreeModel
+from .generictreemodel import GenericTreeModel
 
 from tryton.config import CONFIG
 from tryton.common.cellrendererbutton import CellRendererButton
openSUSE Build Service is sponsored by