# -*- 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 pygtk pygtk.require ("2.0") del pygtk import gobject import gtk import GstDebugViewer from GstDebugViewer.Common import utils 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 (gobject.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__: gtk.ListStore.__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): # TODO Py2.5: Use 'with' statement. fp = utils.SaveWriteFile (self._filename, "wt") try: self._parser.write (fp) except: fp.discard () else: fp.close () 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 & gtk.gdk.WINDOW_STATE_MAXIMIZED: return if event.new_window_state & gtk.gdk.WINDOW_STATE_MAXIMIZED: self.logger.debug ("maximized") self.is_maximized = True else: self.logger.debug ("unmaximized") self.is_maximized = False