# -*- coding: utf-8; mode: python; -*- # # GStreamer Development Utilities # # Copyright (C) 2007 René Stadler # # 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 . """GStreamer Development Utilities Common GUI module.""" import os import logging import gi 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, 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 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 = 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 = 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