mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-02 06:31:08 +00:00
420 lines
14 KiB
Python
420 lines
14 KiB
Python
# -*- 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
|
|
|
|
# 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)
|
|
|
|
|
|
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 BaseException:
|
|
# 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
|