This commit is contained in:
Akkariin Meiko
2022-03-12 03:16:09 +08:00
Unverified
parent 12b76e0c7a
commit 27c4ec74a1
10075 changed files with 5122287 additions and 1 deletions
@@ -0,0 +1,74 @@
# -*- coding: utf-8; mode: python; -*-
#
# GStreamer Development Utilities
#
# Copyright (C) 2007 René Stadler <mail@renestadler.de>
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the Free
# Software Foundation; either version 3 of the License, or (at your option)
# any later version.
#
# This program 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 General Public License for
# more details.
#
# You should have received a copy of the GNU General Public License along with
# this program. If not, see <http://www.gnu.org/licenses/>.
"""GStreamer Development Utilities Common Data module."""
import gi
from gi.repository import GObject
class Dispatcher (object):
def __call__(self, iterator):
raise NotImplementedError("derived classes must override this method")
def cancel(self):
pass
class DefaultDispatcher (Dispatcher):
def __call__(self, iterator):
for x in iterator:
pass
class GSourceDispatcher (Dispatcher):
def __init__(self):
Dispatcher.__init__(self)
self.source_id = None
def __call__(self, iterator):
if self.source_id is not None:
GObject.source_remove(self.source_id)
def iteration():
r = iterator.__next__()
if not r:
self.source_id = None
return r
self.source_id = GObject.idle_add(
iteration, priority=GObject.PRIORITY_LOW)
def cancel(self):
if self.source_id is None:
return
GObject.source_remove(self.source_id)
self.source_id = None
@@ -0,0 +1,528 @@
# -*- coding: utf-8; mode: python; -*-
#
# GStreamer Development Utilities
#
# Copyright (C) 2007 René Stadler <mail@renestadler.de>
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the Free
# Software Foundation; either version 3 of the License, or (at your option)
# any later version.
#
# This program 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 General Public License for
# more details.
#
# You should have received a copy of the GNU General Public License along with
# this program. If not, see <http://www.gnu.org/licenses/>.
"""GStreamer Development Utilities Common GUI module."""
import os
import logging
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import GObject
from gi.repository import Gtk
from gi.repository import Gdk
from gi.types import GObjectMeta
import GstDebugViewer
from GstDebugViewer.Common import utils
from .generictreemodel import GenericTreeModel
def widget_add_popup_menu(widget, menu, button=3):
def popup_callback(widget, event):
if event.button == button:
menu.popup(
None, None, None, None, event.button, event.get_time())
return False
widget.connect("button-press-event", popup_callback)
class Actions (dict):
def __init__(self):
dict.__init__(self)
self.groups = {}
def __getattr__(self, name):
try:
return self[name]
except KeyError:
if "_" in name:
try:
return self[name.replace("_", "-")]
except KeyError:
pass
raise AttributeError("no action with name %r" % (name,))
def add_group(self, group):
name = group.props.name
if name in self.groups:
raise ValueError("already have a group named %s", name)
self.groups[name] = group
for action in group.list_actions():
self[action.props.name] = action
class Widgets (dict):
def __init__(self, builder):
widgets = (obj for obj in builder.get_objects()
if isinstance(obj, Gtk.Buildable))
# Gtk.Widget.get_name() shadows out the GtkBuildable interface method
# of the same name, hence calling the unbound interface method here:
items = ((Gtk.Buildable.get_name(w), w,) for w in widgets)
dict.__init__(self, items)
def __getattr__(self, name):
try:
return self[name]
except KeyError:
if "_" in name:
try:
return self[name.replace("_", "-")]
except KeyError:
pass
raise AttributeError("no widget with name %r" % (name,))
class WidgetFactory (object):
def __init__(self, directory):
self.directory = directory
def get_builder(self, filename):
builder_filename = os.path.join(self.directory, filename)
builder = Gtk.Builder()
builder.set_translation_domain(GstDebugViewer.GETTEXT_DOMAIN)
builder.add_from_file(builder_filename)
return builder
def make(self, filename, widget_name, autoconnect=None):
builder = self.get_builder(filename)
if autoconnect is not None:
builder.connect_signals(autoconnect)
return Widgets(builder)
def make_one(self, filename, widget_name):
builder = self.get_builder(filename)
return builder.get_object(widget_name)
class UIFactory (object):
def __init__(self, ui_filename, actions=None):
self.filename = ui_filename
if actions:
self.action_groups = actions.groups
else:
self.action_groups = ()
def make(self, extra_actions=None):
ui_manager = Gtk.UIManager()
for action_group in list(self.action_groups.values()):
ui_manager.insert_action_group(action_group, 0)
if extra_actions:
for action_group in extra_actions.groups:
ui_manager.insert_action_group(action_group, 0)
ui_manager.add_ui_from_file(self.filename)
ui_manager.ensure_update()
return ui_manager
class MetaModel (GObjectMeta):
"""Meta class for easy setup of gtk tree models.
Looks for a class attribute named `columns' which must be set to a
sequence of the form name1, type1, name2, type2, ..., where the
names are strings. This metaclass adds the following attributes
to created classes:
cls.column_types = (type1, type2, ...)
cls.column_ids = (0, 1, ...)
cls.name1 = 0
cls.name2 = 1
...
Example: A Gtk.ListStore derived model can use
columns = ("COL_NAME", str, "COL_VALUE", str)
and use this in __init__:
GObject.GObject.__init__ (self, *self.column_types)
Then insert data like this:
self.set (self.append (),
self.COL_NAME, "spam",
self.COL_VALUE, "ham")
"""
def __init__(cls, name, bases, dict):
super(MetaModel, cls).__init__(name, bases, dict)
spec = tuple(cls.columns)
column_names = spec[::2]
column_types = spec[1::2]
column_indices = list(range(len(column_names)))
for col_index, col_name, in zip(column_indices, column_names):
setattr(cls, col_name, col_index)
cls.column_types = column_types
cls.column_ids = tuple(column_indices)
class Manager (object):
"""GUI Manager base class."""
@classmethod
def iter_item_classes(cls):
msg = "%s class does not support manager item class access"
raise NotImplementedError(msg % (cls.__name__,))
@classmethod
def find_item_class(self, **kw):
return self.__find_by_attrs(self.iter_item_classes(), kw)
def iter_items(self):
msg = "%s object does not support manager item access"
raise NotImplementedError(msg % (type(self).__name__,))
def find_item(self, **kw):
return self.__find_by_attrs(self.iter_items(), kw)
@staticmethod
def __find_by_attrs(i, kw):
from operator import attrgetter
if len(kw) != 1:
raise ValueError("need exactly one keyword argument")
attr, value = list(kw.items())[0]
getter = attrgetter(attr)
for item in i:
if getter(item) == value:
return item
else:
raise KeyError("no item such that item.%s == %r" % (attr, value,))
class StateString (object):
"""Descriptor for binding to StateSection classes."""
def __init__(self, option, default=None):
self.option = option
self.default = default
def __get__(self, section, section_class=None):
import configparser
if section is None:
return self
try:
return self.get(section)
except (configparser.NoSectionError,
configparser.NoOptionError,):
return self.get_default(section)
def __set__(self, section, value):
import configparser
self.set(section, value)
def get(self, section):
return section.get(self)
def get_default(self, section):
return self.default
def set(self, section, value):
if value is None:
value = ""
section.set(self, str(value))
class StateBool (StateString):
"""Descriptor for binding to StateSection classes."""
def get(self, section):
return section.state._parser.getboolean(section._name, self.option)
class StateInt (StateString):
"""Descriptor for binding to StateSection classes."""
def get(self, section):
return section.state._parser.getint(section._name, self.option)
class StateInt4 (StateString):
"""Descriptor for binding to StateSection classes. This implements storing
a tuple of 4 integers."""
def get(self, section):
value = StateString.get(self, section)
try:
l = value.split(",")
if len(l) != 4:
return None
else:
return tuple((int(v) for v in l))
except (AttributeError, TypeError, ValueError,):
return None
def set(self, section, value):
if value is None:
svalue = ""
elif len(value) != 4:
raise ValueError("value needs to be a 4-sequence, or None")
else:
svalue = ", ".join((str(v) for v in value))
return StateString.set(self, section, svalue)
class StateItem (StateString):
"""Descriptor for binding to StateSection classes. This implements storing
a class controlled by a Manager class."""
def __init__(self, option, manager_class, default=None):
StateString.__init__(self, option, default=default)
self.manager = manager_class
def get(self, section):
value = SectionString.get(self, section)
if not value:
return None
return self.parse_item(value)
def set(self, section, value):
if value is None:
svalue = ""
else:
svalue = value.name
StateString.set(self, section, svalue)
def parse_item(self, value):
name = value.strip()
try:
return self.manager.find_item_class(name=name)
except KeyError:
return None
class StateItemList (StateItem):
"""Descriptor for binding to StateSection classes. This implements storing
an ordered set of Manager items."""
def get(self, section):
value = StateString.get(self, section)
if not value:
return []
classes = []
for name in value.split(","):
item_class = self.parse_item(name)
if item_class is None:
continue
if not item_class in classes:
classes.append(item_class)
return classes
def get_default(self, section):
default = StateItem.get_default(self, section)
if default is None:
return []
else:
return default
def set(self, section, value):
if value is None:
svalue = ""
else:
svalue = ", ".join((v.name for v in value))
StateString.set(self, section, svalue)
class StateSection (object):
_name = None
def __init__(self, state):
self.state = state
if self._name is None:
raise NotImplementedError(
"subclasses must override the _name attribute")
def get(self, state_string):
return self.state._parser.get(self._name, state_string.option)
def set(self, state_string, value):
import configparser
parser = self.state._parser
try:
parser.set(self._name, state_string.option, value)
except configparser.NoSectionError:
parser.add_section(self._name)
parser.set(self._name, state_string.option, value)
class State (object):
def __init__(self, filename, old_filenames=()):
import configparser
self.sections = {}
self._filename = filename
self._parser = configparser.RawConfigParser()
success = self._parser.read([filename])
if not success:
for old_filename in old_filenames:
success = self._parser.read([old_filename])
if success:
break
def add_section_class(self, section_class):
self.sections[section_class._name] = section_class(self)
def save(self):
with utils.SaveWriteFile(self._filename, "wt") as fp:
self._parser.write(fp)
class WindowState (object):
def __init__(self):
self.logger = logging.getLogger("ui.window-state")
self.is_maximized = False
def attach(self, window, state):
self.window = window
self.state = state
self.window.connect("window-state-event",
self.handle_window_state_event)
geometry = self.state.geometry
if geometry:
self.window.move(*geometry[:2])
self.window.set_default_size(*geometry[2:])
if self.state.maximized:
self.logger.debug("initially maximized")
self.window.maximize()
def detach(self):
window = self.window
self.state.maximized = self.is_maximized
if not self.is_maximized:
position = tuple(window.get_position())
size = tuple(window.get_size())
self.state.geometry = position + size
self.window.disconnect_by_func(self.handle_window_state_event)
self.window = None
def handle_window_state_event(self, window, event):
if not event.changed_mask & Gdk.WindowState.MAXIMIZED:
return
if event.new_window_state & Gdk.WindowState.MAXIMIZED:
self.logger.debug("maximized")
self.is_maximized = True
else:
self.logger.debug("unmaximized")
self.is_maximized = False
@@ -0,0 +1,366 @@
# -*- coding: utf-8; mode: python; -*-
#
# GStreamer Development Utilities
#
# Copyright (C) 2007 René Stadler <mail@renestadler.de>
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the Free
# Software Foundation; either version 3 of the License, or (at your option)
# any later version.
#
# This program 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 General Public License for
# more details.
#
# You should have received a copy of the GNU General Public License along with
# this program. If not, see <http://www.gnu.org/licenses/>.
"""GStreamer Development Utilities Common Main module."""
import sys
import os
import traceback
from operator import attrgetter
import logging
import locale
import gettext
from gettext import gettext as _, ngettext
import gi
from gi.repository import GLib
from gi.repository import GObject
from gi.repository import Gtk
class ExceptionHandler (object):
exc_types = (Exception,)
priority = 50
inherit_fork = True
_handling_exception = False
def __call__(self, exc_type, exc_value, exc_traceback):
raise NotImplementedError(
"derived classes need to override this method")
class DefaultExceptionHandler (ExceptionHandler):
exc_types = (BaseException,)
priority = 0
inherit_fork = True
def __init__(self, excepthook):
ExceptionHandler.__init__(self)
self.excepthook = excepthook
def __call__(self, *exc_info):
return self.excepthook(*exc_info)
class ExitOnInterruptExceptionHandler (ExceptionHandler):
exc_types = (KeyboardInterrupt,)
priority = 100
inherit_fork = False
exit_status = 2
def __call__(self, *args):
print("Interrupt caught, exiting.", file=sys.stderr)
sys.exit(self.exit_status)
class MainLoopWrapper (ExceptionHandler):
priority = 95
inherit_fork = False
def __init__(self, enter, exit):
ExceptionHandler.__init__(self)
self.exc_info = (None,) * 3
self.enter = enter
self.exit = exit
def __call__(self, *exc_info):
self.exc_info = exc_info
self.exit()
def run(self):
ExceptHookManager.register_handler(self)
try:
self.enter()
finally:
ExceptHookManager.unregister_handler(self)
if self.exc_info != (None,) * 3:
# Re-raise unhandled exception that occured while running the loop.
exc_type, exc_value, exc_tb = self.exc_info
raise exc_value
class ExceptHookManagerClass (object):
def __init__(self):
self._in_forked_child = False
self.handlers = []
def setup(self):
if sys.excepthook == self.__excepthook:
raise ValueError("already set up")
hook = sys.excepthook
self.__instrument_excepthook()
self.__instrument_fork()
self.register_handler(DefaultExceptionHandler(hook))
def shutdown(self):
if sys.excepthook != self.__excepthook:
raise ValueError("not set up")
self.__restore_excepthook()
self.__restore_fork()
def __instrument_excepthook(self):
hook = sys.excepthook
self._original_excepthook = hook
sys.excepthook = self.__excepthook
def __restore_excepthook(self):
sys.excepthook = self._original_excepthook
def __instrument_fork(self):
try:
fork = os.fork
except AttributeError:
# System has no fork() system call.
self._original_fork = None
else:
self._original_fork = fork
os.fork = self.__fork
def __restore_fork(self):
if not hasattr(os, "fork"):
return
os.fork = self._original_fork
def entered_forked_child(self):
self._in_forked_child = True
for handler in tuple(self.handlers):
if not handler.inherit_fork:
self.handlers.remove(handler)
def register_handler(self, handler):
if self._in_forked_child and not handler.inherit_fork:
return
self.handlers.append(handler)
def unregister_handler(self, handler):
self.handlers.remove(handler)
def __fork(self):
pid = self._original_fork()
if pid == 0:
# Child process.
self.entered_forked_child()
return pid
def __excepthook(self, exc_type, exc_value, exc_traceback):
for handler in sorted(self.handlers,
key=attrgetter("priority"),
reverse=True):
if handler._handling_exception:
continue
for type_ in handler.exc_types:
if issubclass(exc_type, type_):
break
else:
continue
handler._handling_exception = True
handler(exc_type, exc_value, exc_traceback)
# Not using try...finally on purpose here. If the handler itself
# fails with an exception, this prevents recursing into it again.
handler._handling_exception = False
return
else:
from warnings import warn
warn("ExceptHookManager: unhandled %r" % (exc_value,),
RuntimeWarning,
stacklevel=2)
ExceptHookManager = ExceptHookManagerClass()
class PathsBase (object):
data_dir = None
icon_dir = None
locale_dir = None
@classmethod
def setup_installed(cls, data_prefix):
"""Set up paths for running from a regular installation."""
pass
@classmethod
def setup_devenv(cls, source_dir):
"""Set up paths for running the development environment
(i.e. directly from the source dist)."""
pass
@classmethod
def ensure_setup(cls):
"""If paths are still not set up, try to set from a fallback."""
if cls.data_dir is None:
source_dir = os.path.dirname(
os.path.dirname(os.path.abspath(__file__)))
cls.setup_devenv(source_dir)
def __new__(cls):
raise RuntimeError("do not create instances of this class -- "
"use the class object directly")
class PathsProgramBase (PathsBase):
program_name = None
@classmethod
def setup_installed(cls, data_prefix):
if cls.program_name is None:
raise NotImplementedError(
"derived classes need to set program_name attribute")
cls.data_dir = os.path.join(data_prefix, cls.program_name)
cls.icon_dir = os.path.join(data_prefix, "icons")
cls.locale_dir = os.path.join(data_prefix, "locale")
@classmethod
def setup_devenv(cls, source_dir):
"""Set up paths for running the development environment
(i.e. directly from the source dist)."""
# This is essential: The GUI module needs to find the .glade file.
cls.data_dir = os.path.join(source_dir, "data")
# The locale data might be missing if "setup.py build" wasn't run.
cls.locale_dir = os.path.join(source_dir, "build", "mo")
# Not setting icon_dir. It is not useful since we don't employ the
# needed directory structure in the source dist.
def _init_excepthooks():
ExceptHookManager.setup()
ExceptHookManager.register_handler(ExitOnInterruptExceptionHandler())
def _init_paths(paths):
paths.ensure_setup()
def _init_locale(gettext_domain=None):
if Paths.locale_dir and gettext_domain is not None:
try:
locale.setlocale(locale.LC_ALL, "")
except locale.Error as exc:
from warnings import warn
warn("locale error: %s" % (exc,),
RuntimeWarning,
stacklevel=2)
Paths.locale_dir = None
else:
gettext.bindtextdomain(gettext_domain, Paths.locale_dir)
gettext.textdomain(gettext_domain)
gettext.bind_textdomain_codeset(gettext_domain, "UTF-8")
def _init_logging(level):
if level == "none":
return
mapping = {"debug": logging.DEBUG,
"info": logging.INFO,
"warning": logging.WARNING,
"error": logging.ERROR,
"critical": logging.CRITICAL}
logging.basicConfig(level=mapping[level],
format='%(asctime)s.%(msecs)03d %(levelname)8s %(name)20s: %(message)s',
datefmt='%H:%M:%S')
logger = logging.getLogger("main")
logger.debug("logging at level %s", logging.getLevelName(level))
logger.info("using Python %i.%i.%i %s %i", *sys.version_info)
def _init_log_option(parser):
choices = ["none", "debug", "info", "warning", "error", "critical"]
parser.add_option("--log-level", "-l",
type="choice",
choices=choices,
action="store",
dest="log_level",
default="none",
help=_("Enable logging, possible values: ") + ", ".join(choices))
return parser
def main(main_function, option_parser, gettext_domain=None, paths=None):
# FIXME:
global Paths
Paths = paths
_init_excepthooks()
_init_paths(paths)
_init_locale(gettext_domain)
parser = _init_log_option(option_parser)
options, args = option_parser.parse_args()
_init_logging(options.log_level)
try:
main_function(args)
finally:
logging.shutdown()
@@ -0,0 +1,25 @@
# -*- coding: utf-8; mode: python; -*-
#
# GStreamer Development Utilities
#
# Copyright (C) 2007 René Stadler <mail@renestadler.de>
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the Free
# Software Foundation; either version 3 of the License, or (at your option)
# any later version.
#
# This program 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 General Public License for
# more details.
#
# You should have received a copy of the GNU General Public License along with
# this program. If not, see <http://www.gnu.org/licenses/>.
"""GStreamer Development Utilities Common package."""
from . import Data
from . import GUI
from . import Main
from . import utils
@@ -0,0 +1,420 @@
# -*- 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
@@ -0,0 +1,333 @@
# -*- coding: utf-8; mode: python; -*-
#
# GStreamer Development Utilities
#
# Copyright (C) 2007 René Stadler <mail@renestadler.de>
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the Free
# Software Foundation; either version 3 of the License, or (at your option)
# any later version.
#
# This program 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 General Public License for
# more details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, see <http://www.gnu.org/licenses/>.
"""GStreamer Development Utilities Common utils module."""
import os
import logging
import subprocess as _subprocess
class SingletonMeta (type):
def __init__(cls, name, bases, dict_):
from weakref import WeakValueDictionary
super(SingletonMeta, cls).__init__(name, bases, dict_)
cls._singleton_instances = WeakValueDictionary()
def __call__(cls, *a, **kw):
kw_key = tuple(sorted(kw.items()))
try:
obj = cls._singleton_instances[a + kw_key]
except KeyError:
obj = super(SingletonMeta, cls).__call__(*a, **kw)
cls._singleton_instances[a + kw_key] = obj
return obj
def gettext_cache():
"""Return a callable object that operates like gettext.gettext, but is much
faster when a string is looked up more than once. This is very useful in
loops, where calling gettext.gettext can quickly become a major performance
bottleneck."""
from gettext import gettext
d = {}
def gettext_cache_access(s):
if s not in d:
d[s] = gettext(s)
return d[s]
return gettext_cache_access
class ClassProperty (property):
"Like the property class, but also invokes the getter for class access."
def __init__(self, fget=None, fset=None, fdel=None, doc=None):
property.__init__(self, fget, fset, fdel, doc)
self.__fget = fget
def __get__(self, obj, obj_class=None):
ret = property.__get__(self, obj, obj_class)
if ret == self:
return self.__fget(None)
else:
return ret
class _XDGClass (object):
"""Partial implementation of the XDG Base Directory specification v0.6.
http://standards.freedesktop.org/basedir-spec/basedir-spec-0.6.html"""
def __init__(self):
self._add_base_dir("DATA_HOME", "~/.local/share")
self._add_base_dir("CONFIG_HOME", "~/.config")
self._add_base_dir("CACHE_HOME", "~/.cache")
def _add_base_dir(self, name, default):
dir = os.environ.get("XDG_%s" % (name,))
if not dir:
dir = os.path.expanduser(os.path.join(*default.split("/")))
setattr(self, name, dir)
XDG = _XDGClass()
class SaveWriteFile (object):
def __init__(self, filename, mode="wt"):
from tempfile import mkstemp
self.logger = logging.getLogger("tempfile")
dir = os.path.dirname(filename)
base_name = os.path.basename(filename)
temp_prefix = "%s-tmp" % (base_name,)
if dir:
# Destination dir differs from current directory, ensure that it
# exists:
try:
os.makedirs(dir)
except OSError:
pass
self.clean_stale(dir, temp_prefix)
fd, temp_name = mkstemp(dir=dir, prefix=temp_prefix)
self.target_name = filename
self.temp_name = temp_name
self.real_file = os.fdopen(fd, mode)
def __enter__(self):
return self
def __exit__(self, *exc_args):
if exc_args == (None, None, None,):
self.close()
else:
self.discard()
def __del__(self):
try:
self.discard()
except AttributeError:
# If __init__ failed, self has no real_file attribute.
pass
def __close_real(self):
if self.real_file:
self.real_file.close()
self.real_file = None
def clean_stale(self, dir, temp_prefix):
from time import time
from glob import glob
now = time()
pattern = os.path.join(dir, "%s*" % (temp_prefix,))
for temp_filename in glob(pattern):
mtime = os.stat(temp_filename).st_mtime
if now - mtime > 3600:
self.logger.info("deleting stale temporary file %s",
temp_filename)
try:
os.unlink(temp_filename)
except EnvironmentError as exc:
self.logger.warning("deleting stale temporary file "
"failed: %s", exc)
def tell(self, *a, **kw):
return self.real_file.tell(*a, **kw)
def write(self, *a, **kw):
return self.real_file.write(*a, **kw)
def close(self):
self.__close_real()
if self.temp_name:
try:
os.rename(self.temp_name, self.target_name)
except OSError as exc:
import errno
if exc.errno == errno.EEXIST:
# We are probably on windows.
os.unlink(self.target_name)
os.rename(self.temp_name, self.target_name)
self.temp_name = None
def discard(self):
self.__close_real()
if self.temp_name:
try:
os.unlink(self.temp_name)
except EnvironmentError as exc:
self.logger.warning("deleting temporary file failed: %s", exc)
self.temp_name = None
class TeeWriteFile (object):
# TODO Py2.5: Add context manager methods.
def __init__(self, *file_objects):
self.files = list(file_objects)
def close(self):
for file in self.files:
file.close()
def flush(self):
for file in self.files:
file.flush()
def write(self, string):
for file in self.files:
file.write(string)
def writelines(self, lines):
for file in self.files:
file.writelines(lines)
class FixedPopen (_subprocess.Popen):
def __init__(self, args, **kw):
# Unconditionally specify all descriptors as redirected, to
# work around Python bug #1358527 (which is triggered for
# console-less applications on Windows).
close = []
for name in ("stdin", "stdout", "stderr",):
target = kw.get(name)
if not target:
kw[name] = _subprocess.PIPE
close.append(name)
_subprocess.Popen.__init__(self, args, **kw)
for name in close:
fp = getattr(self, name)
fp.close()
setattr(self, name, None)
class DevhelpError (EnvironmentError):
pass
class DevhelpUnavailableError (DevhelpError):
pass
class DevhelpClient (object):
def available(self):
try:
self.version()
except DevhelpUnavailableError:
return False
else:
return True
def version(self):
return self._invoke("--version")
def search(self, entry):
self._invoke_no_interact("-s", entry)
def _check_os_error(self, exc):
import errno
if exc.errno == errno.ENOENT:
raise DevhelpUnavailableError()
def _invoke(self, *args):
from subprocess import PIPE
try:
proc = FixedPopen(("devhelp",) + args,
stdout=PIPE)
except OSError as exc:
self._check_os_error(exc)
raise
out, err = proc.communicate()
if proc.returncode is not None and proc.returncode != 0:
raise DevhelpError("devhelp exited with status %i"
% (proc.returncode,))
return out
def _invoke_no_interact(self, *args):
from subprocess import PIPE
try:
proc = FixedPopen(("devhelp",) + args)
except OSError as exc:
self._check_os_error(exc)
raise