aboutsummaryrefslogtreecommitdiffstats
path: root/lvc/widgets/gtk/base.py
diff options
context:
space:
mode:
Diffstat (limited to 'lvc/widgets/gtk/base.py')
-rw-r--r--lvc/widgets/gtk/base.py300
1 files changed, 300 insertions, 0 deletions
diff --git a/lvc/widgets/gtk/base.py b/lvc/widgets/gtk/base.py
new file mode 100644
index 0000000..ed6129f
--- /dev/null
+++ b/lvc/widgets/gtk/base.py
@@ -0,0 +1,300 @@
+# @Base: Miro - an RSS based video player application
+# Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011
+# Participatory Culture Foundation
+#
+# 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 2 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, write to the Free Software
+# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+#
+# In addition, as a special exception, the copyright holders give
+# permission to link the code of portions of this program with the OpenSSL
+# library.
+#
+# You must obey the GNU General Public License in all respects for all of
+# the code used other than OpenSSL. If you modify file(s) with this
+# exception, you may extend this exception to your version of the file(s),
+# but you are not obligated to do so. If you do not wish to do so, delete
+# this exception statement from your version. If you delete this exception
+# statement from all source files in the program, then also delete it here.
+
+""".base -- Base classes for GTK Widgets."""
+
+import gtk
+
+from lvc import signals
+import wrappermap
+from .weakconnect import weak_connect
+import keymap
+
+def make_gdk_color(miro_color):
+ def convert_value(value):
+ return int(round(value * 65535))
+
+ values = tuple(convert_value(c) for c in miro_color)
+ return gtk.gdk.Color(*values)
+
+class Widget(signals.SignalEmitter):
+ """Base class for GTK widgets.
+
+ The actual GTK Widget is stored in '_widget'.
+
+ signals:
+
+ 'size-allocated' (widget, width, height): The widget had it's size
+ allocated.
+ """
+ def __init__(self, *signal_names):
+ signals.SignalEmitter.__init__(self, *signal_names)
+ self.create_signal('size-allocated')
+ self.create_signal('key-press')
+ self.create_signal('focus-out')
+ self.style_mods = {}
+ self.use_custom_style = False
+ self._disabled = False
+
+ def wrapped_widget_connect(self, signal, method, *user_args):
+ """Connect to a signal of the widget we're wrapping.
+
+ We use a weak reference to ensures that we don't have circular
+ references between the wrapped widget and the wrapper widget.
+ """
+ return weak_connect(self._widget, signal, method, *user_args)
+
+ def set_widget(self, widget):
+ self._widget = widget
+ wrappermap.add(self._widget, self)
+ if self.should_connect_to_hierarchy_changed():
+ self.wrapped_widget_connect('hierarchy_changed',
+ self.on_hierarchy_changed)
+ self.wrapped_widget_connect('size-allocate', self.on_size_allocate)
+ self.wrapped_widget_connect('key-press-event', self.on_key_press)
+ self.wrapped_widget_connect('focus-out-event', self.on_focus_out)
+ self.use_custom_style_callback = None
+
+ def should_connect_to_hierarchy_changed(self):
+ # GTK creates windows to handle submenus, which messes with our
+ # on_hierarchy_changed callback. We don't care about custom styles
+ # for menus anyways, so just ignore the signal.
+ return not isinstance(self._widget, gtk.MenuItem)
+
+ def set_can_focus(self, allow):
+ """Set if we allow the widget to hold keyboard focus.
+ """
+ if allow:
+ self._widget.set_flags(gtk.CAN_FOCUS)
+ else:
+ self._widget.unset_flags(gtk.CAN_FOCUS)
+
+ def on_hierarchy_changed(self, widget, previous_toplevel):
+ toplevel = widget.get_toplevel()
+ if not (toplevel.flags() & gtk.TOPLEVEL):
+ toplevel = None
+ if previous_toplevel != toplevel:
+ if self.use_custom_style_callback:
+ old_window = wrappermap.wrapper(previous_toplevel)
+ old_window.disconnect(self.use_custom_style_callback)
+ if toplevel is not None:
+ window = wrappermap.wrapper(toplevel)
+ callback_id = window.connect('use-custom-style-changed',
+ self.on_use_custom_style_changed)
+ self.use_custom_style_callback = callback_id
+ else:
+ self.use_custom_style_callback = None
+ if previous_toplevel is None:
+ # Setup our initial state
+ self.on_use_custom_style_changed(window)
+
+ def on_size_allocate(self, widget, allocation):
+ self.emit('size-allocated', allocation.width, allocation.height)
+
+ def on_key_press(self, widget, event):
+ key_modifiers = keymap.translate_gtk_event(event)
+ if key_modifiers:
+ key, modifiers = key_modifiers
+ return self.emit('key-press', key, modifiers)
+
+ def on_focus_out(self, widget, event):
+ self.emit('focus-out')
+
+ def on_use_custom_style_changed(self, window):
+ self.use_custom_style = window.use_custom_style
+ if not self.style_mods:
+ return # no need to do any work here
+ if self.use_custom_style:
+ for (what, state), color in self.style_mods.items():
+ self.do_modify_style(what, state, color)
+ else:
+ # This should reset the style changes we've made
+ self._widget.modify_style(gtk.RcStyle())
+ self.handle_custom_style_change()
+
+ def handle_custom_style_change(self):
+ """Called when the user changes a from a theme where we don't want to
+ use our custom style to one where we do, or vice-versa. The Widget
+ class handles changes that used modify_style(), but subclasses might
+ want to do additional work.
+ """
+ pass
+
+ def modify_style(self, what, state, color):
+ """Change the style of our widget. This method checks to see if we
+ think the user's theme is compatible with our stylings, and doesn't
+ change things if not. what is either 'base', 'text', 'bg' or 'fg'
+ depending on which color is to be changed.
+ """
+ if self.use_custom_style:
+ self.do_modify_style(what, state, color)
+ self.style_mods[(what, state)] = color
+
+ def unmodify_style(self, what, state):
+ if (what, state) in self.style_mods:
+ del self.style_mods[(what, state)]
+ default_color = getattr(self.style, what)[state]
+ self.do_modify_style(what, state, default_color)
+
+ def do_modify_style(self, what, state, color):
+ if what == 'base':
+ self._widget.modify_base(state, color)
+ elif what == 'text':
+ self._widget.modify_text(state, color)
+ elif what == 'bg':
+ self._widget.modify_bg(state, color)
+ elif what == 'fg':
+ self._widget.modify_fg(state, color)
+ else:
+ raise ValueError("Unknown what in do_modify_style: %s" % what)
+
+ def get_window(self):
+ gtk_window = self._widget.get_toplevel()
+ return wrappermap.wrapper(gtk_window)
+
+ def clear_size_request_cache(self):
+ # This is just an OS X hack
+ pass
+
+ def get_size_request(self):
+ return self._widget.size_request()
+
+ def invalidate_size_request(self):
+ self._widget.queue_resize()
+
+ def set_size_request(self, width, height):
+ if not width >= -1 and height >= -1:
+ raise ValueError("invalid dimensions in set_size_request: %s" %
+ repr((width, height)))
+ self._widget.set_size_request(width, height)
+
+ def relative_position(self, other_widget):
+ return other_widget._widget.translate_coordinates(self._widget, 0, 0)
+
+ def convert_gtk_color(self, color):
+ return (color.red / 65535.0, color.green / 65535.0,
+ color.blue / 65535.0)
+
+ def get_width(self):
+ try:
+ return self._widget.allocation.width
+ except AttributeError:
+ return -1
+ width = property(get_width)
+
+ def get_height(self):
+ try:
+ return self._widget.allocation.height
+ except AttributeError:
+ return -1
+ height = property(get_height)
+
+ def queue_redraw(self):
+ if self._widget:
+ self._widget.queue_draw()
+
+ def redraw_now(self):
+ if self._widget:
+ self._widget.queue_draw()
+ self._widget.window.process_updates(True)
+
+ def forward_signal(self, signal_name, forwarded_signal_name=None):
+ """Add a callback so that when the GTK widget emits a signal, we emit
+ signal from the wrapper widget.
+ """
+ if forwarded_signal_name is None:
+ forwarded_signal_name = signal_name
+ self.wrapped_widget_connect(signal_name, self.do_forward_signal,
+ forwarded_signal_name)
+
+ def do_forward_signal(self, widget, *args):
+ forwarded_signal_name = args[-1]
+ args = args[:-1]
+ self.emit(forwarded_signal_name, *args)
+
+ def make_color(self, miro_color):
+ color = make_gdk_color(miro_color)
+ self._widget.get_colormap().alloc_color(color)
+ return color
+
+ def enable(self):
+ self._disabled = False
+ self._widget.set_sensitive(True)
+
+ def disable(self):
+ self._disabled = True
+ self._widget.set_sensitive(False)
+
+ def set_disabled(self, disabled):
+ if disabled:
+ self.disable()
+ else:
+ self.enable()
+
+ def get_disabled(self):
+ return self._disabled
+
+class Bin(Widget):
+ def __init__(self):
+ Widget.__init__(self)
+ self.child = None
+
+ def add(self, child):
+ if self.child is not None:
+ raise ValueError("Already have a child: %s" % self.child)
+ if child._widget.parent is not None:
+ raise ValueError("%s already has a parent" % child)
+ self.child = child
+ self.add_child_to_widget()
+ child._widget.show()
+
+ def add_child_to_widget(self):
+ self._widget.add(self.child._widget)
+
+ def remove_child_from_widget(self):
+ if self._widget.get_child() is not None:
+ # otherwise gtkmozembed gets confused
+ self._widget.get_child().hide()
+ self._widget.remove(self._widget.get_child())
+
+
+ def remove(self):
+ if self.child is not None:
+ self.child = None
+ self.remove_child_from_widget()
+
+ def set_child(self, new_child):
+ self.remove()
+ self.add(new_child)
+
+ def enable(self):
+ self.child.enable()
+
+ def disable(self):
+ self.child.disable()