aboutsummaryrefslogtreecommitdiffstats
path: root/mvc/widgets/gtk
diff options
context:
space:
mode:
Diffstat (limited to 'mvc/widgets/gtk')
-rw-r--r--mvc/widgets/gtk/__init__.py65
-rw-r--r--mvc/widgets/gtk/base.py300
-rw-r--r--mvc/widgets/gtk/const.py44
-rw-r--r--mvc/widgets/gtk/contextmenu.py31
-rw-r--r--mvc/widgets/gtk/controls.py337
-rw-r--r--mvc/widgets/gtk/customcontrols.py517
-rw-r--r--mvc/widgets/gtk/drawing.py268
-rw-r--r--mvc/widgets/gtk/gtkmenus.py404
-rw-r--r--mvc/widgets/gtk/keymap.py94
-rw-r--r--mvc/widgets/gtk/layout.py227
-rw-r--r--mvc/widgets/gtk/layoutmanager.py550
-rw-r--r--mvc/widgets/gtk/simple.py313
-rw-r--r--mvc/widgets/gtk/tableview.py1557
-rw-r--r--mvc/widgets/gtk/tableviewcells.py249
-rw-r--r--mvc/widgets/gtk/weakconnect.py56
-rw-r--r--mvc/widgets/gtk/widgets.py47
-rw-r--r--mvc/widgets/gtk/widgetset.py63
-rw-r--r--mvc/widgets/gtk/window.py708
-rw-r--r--mvc/widgets/gtk/wrappermap.py50
19 files changed, 0 insertions, 5880 deletions
diff --git a/mvc/widgets/gtk/__init__.py b/mvc/widgets/gtk/__init__.py
deleted file mode 100644
index 8e58700..0000000
--- a/mvc/widgets/gtk/__init__.py
+++ /dev/null
@@ -1,65 +0,0 @@
-import os
-import sys
-import gtk
-import gobject
-
-def initialize(app):
- from gtkmenus import MainWindowMenuBar
- app.menubar = MainWindowMenuBar()
- app.startup()
- app.run()
-
-def attach_menubar():
- from mvc.widgets import app
- app.widgetapp.vbox.pack_start(app.widgetapp.menubar)
-
-def mainloop_start():
- gobject.threads_init()
- gtk.main()
-
-def mainloop_stop():
- gtk.main_quit()
-
-def idle_add(callback, periodic=None):
- if periodic is not None and periodic < 0:
- raise ValueError('periodic cannot be negative')
- def wrapper():
- callback()
- return periodic is not None
- delay = periodic
- if delay is not None:
- delay *= 1000 # milliseconds
- else:
- delay = 0
- return gobject.timeout_add(delay, wrapper)
-
-def idle_remove(id_):
- gobject.source_remove(id_)
-
-def check_kde():
- return os.environ.get("KDE_FULL_SESSION", None) != None
-
-def open_file_linux(filename):
- if check_kde():
- os.spawnlp(os.P_NOWAIT, "kfmclient", "kfmclient", # kfmclient is part of konqueror
- "exec", "file://" + filename)
- else:
- os.spawnlp(os.P_NOWAIT, "gnome-open", "gnome-open", filename)
-
-def reveal_file(filename):
- if hasattr(os, 'startfile'): # Windows
- os.startfile(os.path.dirname(filename))
- else:
- open_file_linux(filename)
-
-def get_conversion_directory_windows():
- from mvc.windows import specialfolders
- return specialfolders.base_movies_directory
-
-def get_conversion_directory_linux():
- return os.path.expanduser('~')
-
-if sys.platform == 'win32':
- get_conversion_directory = get_conversion_directory_windows
-else:
- get_conversion_directory = get_conversion_directory_linux
diff --git a/mvc/widgets/gtk/base.py b/mvc/widgets/gtk/base.py
deleted file mode 100644
index e02db3f..0000000
--- a/mvc/widgets/gtk/base.py
+++ /dev/null
@@ -1,300 +0,0 @@
-# @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 mvc 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()
diff --git a/mvc/widgets/gtk/const.py b/mvc/widgets/gtk/const.py
deleted file mode 100644
index 5e9ec05..0000000
--- a/mvc/widgets/gtk/const.py
+++ /dev/null
@@ -1,44 +0,0 @@
-# @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.
-
-""".const -- Constants."""
-
-import gtk
-
-DRAG_ACTION_NONE = 0
-DRAG_ACTION_COPY = gtk.gdk.ACTION_COPY
-DRAG_ACTION_MOVE = gtk.gdk.ACTION_MOVE
-DRAG_ACTION_LINK = gtk.gdk.ACTION_LINK
-DRAG_ACTION_ALL = DRAG_ACTION_COPY | DRAG_ACTION_MOVE | DRAG_ACTION_LINK
-
-ITEM_TITLE_FONT = "Helvetica"
-ITEM_DESC_FONT = "Helvetica"
-ITEM_INFO_FONT = "Lucida Grande"
-
-TOOLBAR_GRAY = (0.2, 0.2, 0.2)
diff --git a/mvc/widgets/gtk/contextmenu.py b/mvc/widgets/gtk/contextmenu.py
deleted file mode 100644
index cd5b6ba..0000000
--- a/mvc/widgets/gtk/contextmenu.py
+++ /dev/null
@@ -1,31 +0,0 @@
-import gtk
-
-from .base import Widget
-
-class ContextMenu(Widget):
-
- def __init__(self, options):
- super(ContextMenu, self).__init__()
- self.set_widget(gtk.Menu())
- for i, item_info in enumerate(options):
- if item_info is None:
- # separator
- item = gtk.SeparatorMenuItem()
- else:
- label, callback = item_info
- item = gtk.MenuItem(label)
- if isinstance(callback, list):
- submenu = ContextMenu(callback)
- item.set_submenu(submenu._widget)
- elif callback is not None:
- item.connect('activate', self.on_activate, callback, i)
- else:
- item.set_sensitive(False)
- self._widget.append(item)
- item.show()
-
- def popup(self):
- self._widget.popup(None, None, None, 0, 0)
-
- def on_activate(self, widget, callback, i):
- callback(self, i)
diff --git a/mvc/widgets/gtk/controls.py b/mvc/widgets/gtk/controls.py
deleted file mode 100644
index 4367c1f..0000000
--- a/mvc/widgets/gtk/controls.py
+++ /dev/null
@@ -1,337 +0,0 @@
-# @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.
-
-""".controls -- Control Widgets."""
-
-import gtk
-import pango
-
-from mvc.widgets import widgetconst
-import layout
-from .base import Widget
-from .simple import Label
-
-class BinBaselineCalculator(object):
- """Mixin class that defines the baseline method for gtk.Bin subclasses,
- where the child is the label that we are trying to get the baseline for.
- """
-
- def baseline(self):
- my_size = self._widget.size_request()
- child_size = self._widget.child.size_request()
- ypad = (my_size[1] - child_size[1]) / 2
-
- pango_context = self._widget.get_pango_context()
- metrics = pango_context.get_metrics(self._widget.style.font_desc)
- return pango.PIXELS(metrics.get_descent()) + ypad
-
-class TextEntry(Widget):
- entry_class = gtk.Entry
- def __init__(self, initial_text=None):
- Widget.__init__(self)
- self.create_signal('activate')
- self.create_signal('changed')
- self.create_signal('validate')
- self.set_widget(self.entry_class())
- self.forward_signal('activate')
- self.forward_signal('changed')
- if initial_text is not None:
- self.set_text(initial_text)
-
- def focus(self):
- self._widget.grab_focus()
-
- def start_editing(self, text):
- self.set_text(text)
- self.focus()
- self._widget.emit('move-cursor', gtk.MOVEMENT_BUFFER_ENDS, 1, False)
-
- def set_text(self, text):
- self._widget.set_text(text)
-
- def get_text(self):
- return self._widget.get_text().decode('utf-8')
-
- def set_max_length(self, chars):
- self._widget.set_max_length(chars)
-
- def set_width(self, chars):
- self._widget.set_width_chars(chars)
-
- def set_invisible(self, setting):
- self._widget.props.visibility = not setting
-
- def set_activates_default(self, setting):
- self._widget.set_activates_default(setting)
-
- def baseline(self):
- layout_height = pango.PIXELS(self._widget.get_layout().get_size()[1])
- ypad = (self._widget.size_request()[1] - layout_height) / 2
- pango_context = self._widget.get_pango_context()
- metrics = pango_context.get_metrics(self._widget.style.font_desc)
- return pango.PIXELS(metrics.get_descent()) + ypad
-
-
-class NumberEntry(TextEntry):
- def __init__(self, initial_text=None):
- TextEntry.__init__(self, initial_text)
- self._widget.connect('changed', self.validate)
- self.previous_text = initial_text or ""
-
- def validate(self, entry):
- text = self.get_text()
- if text.isdigit() or not text:
- self.previous_text = text
- else:
- self._widget.set_text(self.previous_text)
-
-class SecureTextEntry(TextEntry):
- def __init__(self, initial_text=None):
- TextEntry.__init__(self, initial_text)
- self.set_invisible(True)
-
-class MultilineTextEntry(Widget):
- entry_class = gtk.TextView
- def __init__(self, initial_text=None, border=False):
- Widget.__init__(self)
- self.set_widget(self.entry_class())
- if initial_text is not None:
- self.set_text(initial_text)
- self._widget.set_wrap_mode(gtk.WRAP_WORD)
- self._widget.set_accepts_tab(False)
- self.border = border
-
- def focus(self):
- self._widget.grab_focus()
-
- def set_text(self, text):
- self._widget.get_buffer().set_text(text)
-
- def get_text(self):
- buffer_ = self._widget.get_buffer()
- return buffer_.get_text(*(buffer_.get_bounds())).decode('utf-8')
-
- def baseline(self):
- # FIXME
- layout_height = pango.PIXELS(self._widget.get_layout().get_size()[1])
- ypad = (self._widget.size_request()[1] - layout_height) / 2
- pango_context = self._widget.get_pango_context()
- metrics = pango_context.get_metrics(self._widget.style.font_desc)
- return pango.PIXELS(metrics.get_descent()) + ypad
-
- def set_editable(self, editable):
- self._widget.set_editable(editable)
-
-class Checkbox(Widget, BinBaselineCalculator):
- """Widget that the user can toggle on or off."""
-
- def __init__(self, text=None, bold=False, color=None):
- Widget.__init__(self)
- BinBaselineCalculator.__init__(self)
- if text is None:
- text = ''
- self.set_widget(gtk.CheckButton())
- self.label = Label(text, color=color)
- self._widget.add(self.label._widget)
- self.label._widget.show()
- self.create_signal('toggled')
- self.forward_signal('toggled')
- if bold:
- self.label.set_bold(True)
-
- def get_checked(self):
- return self._widget.get_active()
-
- def set_checked(self, value):
- self._widget.set_active(value)
-
- def set_size(self, scale_factor):
- self.label.set_size(scale_factor)
-
- def get_text_padding(self):
- """
- Returns the amount of space the checkbox takes up before the label.
- """
- indicator_size = self._widget.style_get_property('indicator-size')
- indicator_spacing = self._widget.style_get_property(
- 'indicator-spacing')
- focus_width = self._widget.style_get_property('focus-line-width')
- focus_padding = self._widget.style_get_property('focus-padding')
- return (indicator_size + 3 * indicator_spacing + 2 * (focus_width +
- focus_padding))
-
-class RadioButtonGroup(Widget, BinBaselineCalculator):
- """RadioButtonGroup.
-
- Create the group, then create a bunch of RadioButtons passing in the group.
-
- NB: GTK has built-in radio button grouping functionality, and we should
- be using that but we need this widget for portable code. We create
- a dummy GTK radio button and make this the "root" button which gets
- inherited by all buttons in this radio button group.
- """
- def __init__(self):
- Widget.__init__(self)
- BinBaselineCalculator.__init__(self)
- self.set_widget(gtk.RadioButton(label=""))
- self._widget.set_active(False)
- self._buttons = []
-
- def add_button(self, button):
- self._buttons.append(button)
-
- def get_buttons(self):
- return self._buttons
-
- def get_selected(self):
- for mem in self._buttons:
- if mem.get_selected():
- return mem
-
- def set_selected(self, button):
- for mem in self._buttons:
- if mem is button:
- mem._widget.set_active(True)
- else:
- mem._widget.set_active(False)
-
-class RadioButton(Widget, BinBaselineCalculator):
- """RadioButton."""
- def __init__(self, label, group=None, color=None):
- Widget.__init__(self)
- BinBaselineCalculator.__init__(self)
- if group:
- self.group = group
- else:
- self.group = RadioButtonGroup()
- self.set_widget(gtk.RadioButton(group=self.group._widget))
- self.label = Label(label, color=color)
- self._widget.add(self.label._widget)
- self.label._widget.show()
- self.create_signal('clicked')
- self.forward_signal('clicked')
-
- group.add_button(self)
-
- def set_size(self, size):
- self.label.set_size(size)
-
- def get_group(self):
- return self.group
-
- def get_selected(self):
- return self._widget.get_active()
-
- def set_selected(self):
- self.group.set_selected(self)
-
-class Button(Widget, BinBaselineCalculator):
- def __init__(self, text, style='normal', width=None):
- Widget.__init__(self)
- BinBaselineCalculator.__init__(self)
- # We just ignore style here, GTK users expect their own buttons.
- self.set_widget(gtk.Button())
- self.create_signal('clicked')
- self.forward_signal('clicked')
- self.label = Label(text)
- # only honor width if its bigger than the width we need to display the
- # label (#18994)
- if width and width > self.label.get_width():
- alignment = layout.Alignment(0.5, 0.5, 0, 0)
- alignment.set_size_request(width, -1)
- alignment.add(self.label)
- self._widget.add(alignment._widget)
- else:
- self._widget.add(self.label._widget)
- self.label._widget.show()
-
- def set_text(self, title):
- self.label.set_text(title)
-
- def set_bold(self, bold):
- self.label.set_bold(bold)
-
- def set_size(self, scale_factor):
- self.label.set_size(scale_factor)
-
- def set_color(self, color):
- self.label.set_color(color)
-
-class OptionMenu(Widget):
- def __init__(self, options):
- Widget.__init__(self)
- self.create_signal('changed')
-
- self.set_widget(gtk.ComboBox(gtk.ListStore(str, str)))
- self.cell = gtk.CellRendererText()
- self._widget.pack_start(self.cell, True)
- self._widget.add_attribute(self.cell, 'text', 0)
- if options:
- for option, value in options:
- self._widget.get_model().append((option, value))
- self._widget.set_active(0)
- self.options = options
- self.wrapped_widget_connect('changed', self.on_changed)
-
- def baseline(self):
- my_size = self._widget.size_request()
- child_size = self._widget.child.size_request()
- ypad = self.cell.props.ypad + (my_size[1] - child_size[1]) / 2
-
- pango_context = self._widget.get_pango_context()
- metrics = pango_context.get_metrics(self._widget.style.font_desc)
- return pango.PIXELS(metrics.get_descent()) + ypad
-
- def set_bold(self, bold):
- if bold:
- self.cell.props.weight = pango.WEIGHT_BOLD
- else:
- self.cell.props.weight = pango.WEIGHT_NORMAL
-
- def set_size(self, size):
- if size == widgetconst.SIZE_NORMAL:
- self.cell.props.scale = 1
- else:
- self.cell.props.scale = 0.75
-
- def set_color(self, color):
- self.cell.props.foreground_gdk = self.make_color(color)
-
- def set_selected(self, index):
- self._widget.set_active(index)
-
- def get_selected(self):
- return self._widget.get_active()
-
- def on_changed(self, widget):
- index = widget.get_active()
- self.emit('changed', index)
-
- def set_width(self, width):
- self._widget.set_property('width-request', width)
diff --git a/mvc/widgets/gtk/customcontrols.py b/mvc/widgets/gtk/customcontrols.py
deleted file mode 100644
index 070cebd..0000000
--- a/mvc/widgets/gtk/customcontrols.py
+++ /dev/null
@@ -1,517 +0,0 @@
-# @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.
-
-""".controls -- Contains the ControlBox and
-CustomControl classes. These handle the custom buttons/sliders used during
-playback.
-"""
-
-from __future__ import division
-import math
-
-import gtk
-import gobject
-
-import wrappermap
-from .base import Widget
-from .simple import Label, Image
-from .drawing import (CustomDrawingMixin, Drawable,
- ImageSurface)
-from mvc.widgets import widgetconst
-
-class CustomControlMixin(CustomDrawingMixin):
- def do_expose_event(self, event):
- CustomDrawingMixin.do_expose_event(self, event)
- if self.is_focus():
- style = self.get_style()
- style.paint_focus(self.window, self.state,
- event.area, self, None, self.allocation.x,
- self.allocation.y, self.allocation.width,
- self.allocation.height)
-
-class CustomButtonWidget(CustomControlMixin, gtk.Button):
- def draw(self, wrapper, context):
- if self.is_active():
- wrapper.state = 'pressed'
- elif self.state == gtk.STATE_PRELIGHT:
- wrapper.state = 'hover'
- else:
- wrapper.state = 'normal'
- wrapper.draw(context, wrapper.layout_manager)
- self.set_focus_on_click(False)
-
- def is_active(self):
- return self.state == gtk.STATE_ACTIVE
-
-class ContinuousCustomButtonWidget(CustomButtonWidget):
- def is_active(self):
- return (self.state == gtk.STATE_ACTIVE or
- wrappermap.wrapper(self).button_down)
-
-class DragableCustomButtonWidget(CustomButtonWidget):
- def __init__(self):
- CustomButtonWidget.__init__(self)
- self.button_press_x = None
- self.set_events(self.get_events() | gtk.gdk.POINTER_MOTION_MASK)
-
- def do_button_press_event(self, event):
- self.button_press_x = event.x
- self.last_drag_event = None
- gtk.Button.do_button_press_event(self, event)
-
- def do_button_release_event(self, event):
- self.button_press_x = None
- gtk.Button.do_button_release_event(self, event)
-
- def do_motion_notify_event(self, event):
- DRAG_THRESHOLD = 15
- if self.button_press_x is None:
- # button not down
- return
- if (self.last_drag_event != 'right' and
- event.x > self.button_press_x + DRAG_THRESHOLD):
- wrappermap.wrapper(self).emit('dragged-right')
- self.last_drag_event = 'right'
- elif (self.last_drag_event != 'left' and
- event.x < self.button_press_x - DRAG_THRESHOLD):
- wrappermap.wrapper(self).emit('dragged-left')
- self.last_drag_event = 'left'
-
- def do_clicked(self):
- # only emit clicked if we didn't emit dragged-left or dragged-right
- if self.last_drag_event is None:
- wrappermap.wrapper(self).emit('clicked')
-
-class _DragInfo(object):
- """Info about the start of a drag.
-
- Attributes:
-
- - button: button that started the drag
- - start_pos: position of the slider
- - click_pos: position of the click
-
- Note that start_pos and click_pos will be different if the user clicks
- inside the slider.
- """
-
- def __init__(self, button, start_pos, click_pos):
- self.button = button
- self.start_pos = start_pos
- self.click_pos = click_pos
-
-class CustomScaleMixin(CustomControlMixin):
- def __init__(self):
- CustomControlMixin.__init__(self)
- self.drag_info = None
- self.min = self.max = 0.0
-
- def get_range(self):
- return self.min, self.max
-
- def set_range(self, min, max):
- self.min = float(min)
- self.max = float(max)
- gtk.Range.set_range(self, min, max)
-
- def is_continuous(self):
- return wrappermap.wrapper(self).is_continuous()
-
- def is_horizontal(self):
- # this comes from a mixin
- pass
-
- def gtk_scale_class(self):
- if self.is_horizontal():
- return gtk.HScale
- else:
- return gtk.VScale
-
- def get_slider_pos(self, value=None):
- if value is None:
- value = self.get_value()
- if self.is_horizontal():
- size = self.allocation.width
- else:
- size = self.allocation.height
- ratio = (float(value) - self.min) / (self.max - self.min)
- start_pos = self.slider_size() / 2.0
- return start_pos + ratio * (size - self.slider_size())
-
- def slider_size(self):
- return wrappermap.wrapper(self).slider_size()
-
- def _event_pos(self, event):
- """Get the position of an event.
-
- If we are horizontal, this will be the x coordinate. If we are
- vertical, the y.
- """
- if self.is_horizontal():
- return event.x
- else:
- return event.y
-
- def do_button_press_event(self, event):
- if self.drag_info is not None:
- return
- current_pos = self.get_slider_pos()
- event_pos = self._event_pos(event)
- pos_difference = abs(current_pos - event_pos)
- # only move the slider if the click was outside its boundaries
- # (#18840)
- if pos_difference > self.slider_size() / 2.0:
- self.move_slider(event_pos)
- current_pos = event_pos
- self.drag_info = _DragInfo(event.button, current_pos, event_pos)
- self.grab_focus()
- wrappermap.wrapper(self).emit('pressed')
-
- def do_motion_notify_event(self, event):
- if self.drag_info is not None:
- event_pos = self._event_pos(event)
- delta = event_pos - self.drag_info.click_pos
- self.move_slider(self.drag_info.start_pos + delta)
-
- def move_slider(self, new_pos):
- """Move the slider so that it's centered on new_pos."""
- if self.is_horizontal():
- size = self.allocation.width
- else:
- size = self.allocation.height
-
- slider_size = self.slider_size()
- new_pos -= slider_size / 2
- size -= slider_size
- ratio = max(0, min(1, float(new_pos) / size))
- self.set_value(ratio * (self.max - self.min))
-
- wrappermap.wrapper(self).emit('moved', self.get_value())
- if self.is_continuous():
- wrappermap.wrapper(self).emit('changed', self.get_value())
-
- def handle_drag_out_of_bounds(self):
- if not self.is_continuous():
- self.set_value(self.start_value)
-
- def do_button_release_event(self, event):
- if self.drag_info is None or event.button != self.drag_info.button:
- return
- self.drag_info = None
- if (self.is_continuous and
- (0 <= event.x < self.allocation.width) and
- (0 <= event.y < self.allocation.height)):
- wrappermap.wrapper(self).emit('changed', self.get_value())
- wrappermap.wrapper(self).emit('released')
-
- def do_scroll_event(self, event):
- wrapper = wrappermap.wrapper(self)
- if self.is_horizontal():
- if event.direction == gtk.gdk.SCROLL_UP:
- event.direction = gtk.gdk.SCROLL_DOWN
- elif event.direction == gtk.gdk.SCROLL_DOWN:
- event.direction = gtk.gdk.SCROLL_UP
- if (wrapper._scroll_step is not None and
- event.direction in (gtk.gdk.SCROLL_UP, gtk.gdk.SCROLL_DOWN)):
- # handle the scroll ourself
- if event.direction == gtk.gdk.SCROLL_DOWN:
- delta = wrapper._scroll_step
- else:
- delta = -wrapper._scroll_step
- self.set_value(self.get_value() + delta)
- else:
- # let GTK handle the scroll
- self.gtk_scale_class().do_scroll_event(self, event)
- # Treat mouse scrolls as if the user clicked on the new position
- wrapper.emit('pressed')
- wrapper.emit('changed', self.get_value())
- wrapper.emit('released')
-
- def do_move_slider(self, scroll):
- if self.is_horizontal():
- if scroll == gtk.SCROLL_STEP_UP:
- scroll = gtk.SCROLL_STEP_DOWN
- elif scroll == gtk.SCROLL_STEP_DOWN:
- scroll = gtk.SCROLL_STEP_UP
- elif scroll == gtk.SCROLL_PAGE_UP:
- scroll = gtk.SCROLL_PAGE_DOWN
- elif scroll == gtk.SCROLL_PAGE_DOWN:
- scroll = gtk.SCROLL_PAGE_UP
- elif scroll == gtk.SCROLL_START:
- scroll = gtk.SCROLL_END
- elif scroll == gtk.SCROLL_END:
- scroll = gtk.SCROLL_START
- return self.gtk_scale_class().do_move_slider(self, scroll)
-
-class CustomHScaleWidget(CustomScaleMixin, gtk.HScale):
- def __init__(self):
- CustomScaleMixin.__init__(self)
- gtk.HScale.__init__(self)
-
- def is_horizontal(self):
- return True
-
-class CustomVScaleWidget(CustomScaleMixin, gtk.VScale):
- def __init__(self):
- CustomScaleMixin.__init__(self)
- gtk.VScale.__init__(self)
-
- def is_horizontal(self):
- return False
-
-gobject.type_register(CustomButtonWidget)
-gobject.type_register(ContinuousCustomButtonWidget)
-gobject.type_register(DragableCustomButtonWidget)
-gobject.type_register(CustomHScaleWidget)
-gobject.type_register(CustomVScaleWidget)
-
-class CustomControlBase(Drawable, Widget):
- def __init__(self):
- Widget.__init__(self)
- Drawable.__init__(self)
- self._gtk_cursor = None
- self._entry_handlers = None
-
- def _connect_enter_notify_handlers(self):
- if self._entry_handlers is None:
- self._entry_handlers = [
- self.wrapped_widget_connect('enter-notify-event',
- self.on_enter_notify),
- self.wrapped_widget_connect('leave-notify-event',
- self.on_leave_notify),
- self.wrapped_widget_connect('button-release-event',
- self.on_click)
- ]
-
- def _disconnect_enter_notify_handlers(self):
- if self._entry_handlers is not None:
- for handle in self._entry_handlers:
- self._widget.disconnect(handle)
- self._entry_handlers = None
-
- def set_cursor(self, cursor):
- if cursor == widgetconst.CURSOR_NORMAL:
- self._gtk_cursor = None
- self._disconnect_enter_notify_handlers()
- elif cursor == widgetconst.CURSOR_POINTING_HAND:
- self._gtk_cursor = gtk.gdk.Cursor(gtk.gdk.HAND2)
- self._connect_enter_notify_handlers()
- else:
- raise ValueError("Unknown cursor: %s" % cursor)
-
- def on_enter_notify(self, widget, event):
- self._widget.window.set_cursor(self._gtk_cursor)
-
- def on_leave_notify(self, widget, event):
- if self._widget.window:
- self._widget.window.set_cursor(None)
-
- def on_click(self, widget, event):
- self.emit('clicked')
- return True
-
-class CustomButton(CustomControlBase):
- def __init__(self):
- """Create a new CustomButton. active_image will be displayed while
- the button is pressed. The image must have the same size.
- """
- CustomControlBase.__init__(self)
- self.set_widget(CustomButtonWidget())
- self.create_signal('clicked')
- self.forward_signal('clicked')
-
-class DragableCustomButton(CustomControlBase):
- def __init__(self):
- CustomControlBase.__init__(self)
- self.set_widget(DragableCustomButtonWidget())
- self.create_signal('clicked')
- self.create_signal('dragged-left')
- self.create_signal('dragged-right')
-
-class CustomSlider(CustomControlBase):
- def __init__(self):
- CustomControlBase.__init__(self)
- self.create_signal('pressed')
- self.create_signal('released')
- self.create_signal('changed')
- self.create_signal('moved')
- self._scroll_step = None
- if self.is_horizontal():
- self.set_widget(CustomHScaleWidget())
- else:
- self.set_widget(CustomVScaleWidget())
- self.wrapped_widget_connect('move-slider', self.on_slider_move)
-
- def on_slider_move(self, widget, scrolltype):
- self.emit('changed', widget.get_value())
- self.emit('moved', widget.get_value())
-
- def get_value(self):
- return self._widget.get_value()
-
- def set_value(self, value):
- self._widget.set_value(value)
-
- def get_range(self):
- return self._widget.get_range()
-
- def get_slider_pos(self, value=None):
- """Get the position for the slider for our current value.
-
- This will return position that the slider should be centered on to
- display the value. It will be the x coordinate if is_horizontal() is
- True and the y coordinate otherwise.
-
- This method takes into acount the size of the slider when calculating
- the position. The slider position will start at (slider_size / 2) and
- will end (slider_size / 2) px before the end of the widget.
-
- :param value: value to get the position for. Defaults to the current
- value
- """
- return self._widget.get_slider_pos(value)
-
- def set_range(self, min_value, max_value):
- self._widget.set_range(min_value, max_value)
- # set_digits controls the precision of the scale by limiting changes
- # to a certain number of digits. If the range is [0, 1], this code
- # will give us 4 digits of precision, which seems reasonable.
- range = max_value - min_value
- self._widget.set_digits(int(round(math.log10(10000.0 / range))))
-
- def set_increments(self, small_step, big_step, scroll_step=None):
- """Set the increments to scroll.
-
- :param small_step: scroll amount for up/down
- :param big_step: scroll amount for page up/page down.
- :param scroll_step: scroll amount for mouse wheel, or None to make
- this 2 times the small step
- """
- self._widget.set_increments(small_step, big_step)
- self._scroll_step = scroll_step
-
-def to_miro_volume(value):
- """Convert from 0 to 1.0 to 0.0 to MAX_VOLUME.
- """
- if value == 0:
- return 0.0
- return value * widgetconst.MAX_VOLUME
-
-def to_gtk_volume(value):
- """Convert from 0.0 to MAX_VOLUME to 0 to 1.0.
- """
- if value > 0.0:
- value = (value / widgetconst.MAX_VOLUME)
- return value
-
-if hasattr(gtk.VolumeButton, "get_popup"):
- # FIXME - Miro on Windows has an old version of gtk (2.16) and
- # doesn't have the get_popup method. Once we upgrade and
- # fix that, we can take out the hasattr check.
-
- class VolumeMuter(Label):
- """Empty space that has a clicked signal so it can be dropped
- in place of the VolumeMuter.
- """
- def __init__(self):
- Label.__init__(self)
- self.create_signal("clicked")
-
- class VolumeSlider(Widget):
- """VolumeSlider that uses the gtk.VolumeButton().
- """
- def __init__(self):
- Widget.__init__(self)
- self.set_widget(gtk.VolumeButton())
- self.wrapped_widget_connect('value-changed', self.on_value_changed)
- self._widget.get_popup().connect("hide", self.on_hide)
- self.create_signal('changed')
- self.create_signal('released')
-
- def on_value_changed(self, *args):
- value = self.get_value()
- self.emit('changed', value)
-
- def on_hide(self, *args):
- self.emit('released')
-
- def get_value(self):
- value = self._widget.get_property('value')
- return to_miro_volume(value)
-
- def set_value(self, value):
- value = to_gtk_volume(value)
- self._widget.set_property('value', value)
-
-class ClickableImageButton(CustomButton):
- """Image that can send clicked events. If max_width and/or max_height are
- specified, resizes the image proportionally such that all constraints are
- met.
- """
- def __init__(self, image_path, max_width=None, max_height=None):
- CustomButton.__init__(self)
- self.max_width = max_width
- self.max_height = max_height
- self.image = None
- self._width, self._height = None, None
- if image_path:
- self.set_path(image_path)
- self.set_cursor(widgetconst.CURSOR_POINTING_HAND)
-
- def set_path(self, path):
- image = Image(path)
- if self.max_width:
- image = image.resize_for_space(self.max_width, self.max_height)
- self.image = ImageSurface(image)
- self._width, self._height = image.width, image.height
-
- def size_request(self, layout):
- w = self._width
- h = self._height
- if not w:
- w = self.max_width
- if not h:
- h = self.max_height
- return w, h
-
- def draw(self, context, layout):
- if self.image:
- self.image.draw(context, 0, 0, self._width, self._height)
- w = self._width
- h = self._height
- if not w:
- w = self.max_width
- if not h:
- h = self.max_height
- w = min(context.width, w)
- h = min(context.height, h)
- context.rectangle(0, 0, w, h)
- context.set_color((0, 0, 0)) # black
- context.set_line_width(1)
- context.stroke()
diff --git a/mvc/widgets/gtk/drawing.py b/mvc/widgets/gtk/drawing.py
deleted file mode 100644
index 5888851..0000000
--- a/mvc/widgets/gtk/drawing.py
+++ /dev/null
@@ -1,268 +0,0 @@
-# @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.
-
-""".drawing -- Contains classes used to draw on
-widgets.
-"""
-
-import cairo
-import gobject
-import gtk
-
-import wrappermap
-from .base import Widget, Bin
-from .layoutmanager import LayoutManager
-
-def css_to_color(css_string):
- parts = (css_string[1:3], css_string[3:5], css_string[5:7])
- return tuple((int(value, 16) / 255.0) for value in parts)
-
-class ImageSurface:
- def __init__(self, image):
- format = cairo.FORMAT_RGB24
- if image.pixbuf.get_has_alpha():
- format = cairo.FORMAT_ARGB32
- self.image = cairo.ImageSurface(
- format, int(image.width), int(image.height))
- context = cairo.Context(self.image)
- gdkcontext = gtk.gdk.CairoContext(context)
- gdkcontext.set_source_pixbuf(image.pixbuf, 0, 0)
- gdkcontext.paint()
- self.pattern = cairo.SurfacePattern(self.image)
- self.pattern.set_extend(cairo.EXTEND_REPEAT)
- self.width = image.width
- self.height = image.height
-
- def get_size(self):
- return self.width, self.height
-
- def _align_pattern(self, x, y):
- """Line up our image pattern so that it's top-left corner is x, y."""
- m = cairo.Matrix()
- m.translate(-x, -y)
- self.pattern.set_matrix(m)
-
- def draw(self, context, x, y, width, height, fraction=1.0):
- self._align_pattern(x, y)
- cairo_context = context.context
- cairo_context.save()
- cairo_context.set_source(self.pattern)
- cairo_context.new_path()
- cairo_context.rectangle(x, y, width, height)
- if fraction >= 1.0:
- cairo_context.fill()
- else:
- cairo_context.clip()
- cairo_context.paint_with_alpha(fraction)
- cairo_context.restore()
-
- def draw_rect(self, context, dest_x, dest_y, source_x, source_y,
- width, height, fraction=1.0):
-
- self._align_pattern(dest_x-source_x, dest_y-source_y)
- cairo_context = context.context
- cairo_context.save()
- cairo_context.set_source(self.pattern)
- cairo_context.new_path()
- cairo_context.rectangle(dest_x, dest_y, width, height)
- if fraction >= 1.0:
- cairo_context.fill()
- else:
- cairo_context.clip()
- cairo_context.paint_with_alpha(fraction)
- cairo_context.restore()
-
-class DrawingStyle(object):
- def __init__(self, widget, use_base_color=False, state=None):
- if state is None:
- state = widget._widget.state
- self.use_custom_style = widget.use_custom_style
- self.style = widget._widget.style
- self.text_color = widget.convert_gtk_color(self.style.text[state])
- if use_base_color:
- self.bg_color = widget.convert_gtk_color(self.style.base[state])
- else:
- self.bg_color = widget.convert_gtk_color(self.style.bg[state])
-
-class DrawingContext(object):
- """DrawingContext. This basically just wraps a Cairo context and adds a
- couple convenience methods.
- """
-
- def __init__(self, window, drawing_area, expose_area):
- self.window = window
- self.context = window.cairo_create()
- self.context.rectangle(expose_area.x, expose_area.y,
- expose_area.width, expose_area.height)
- self.context.clip()
- self.width = drawing_area.width
- self.height = drawing_area.height
- self.context.translate(drawing_area.x, drawing_area.y)
-
- def __getattr__(self, name):
- return getattr(self.context, name)
-
- def set_color(self, (red, green, blue), alpha=1.0):
- self.context.set_source_rgba(red, green, blue, alpha)
-
- def set_shadow(self, color, opacity, offset, blur_radius):
- pass
-
- def gradient_fill(self, gradient):
- old_source = self.context.get_source()
- self.context.set_source(gradient.pattern)
- self.context.fill()
- self.context.set_source(old_source)
-
- def gradient_fill_preserve(self, gradient):
- old_source = self.context.get_source()
- self.context.set_source(gradient.pattern)
- self.context.fill_preserve()
- self.context.set_source(old_source)
-
-class Gradient(object):
- def __init__(self, x1, y1, x2, y2):
- self.pattern = cairo.LinearGradient(x1, y1, x2, y2)
-
- def set_start_color(self, (red, green, blue)):
- self.pattern.add_color_stop_rgb(0, red, green, blue)
-
- def set_end_color(self, (red, green, blue)):
- self.pattern.add_color_stop_rgb(1, red, green, blue)
-
-class CustomDrawingMixin(object):
- def do_expose_event(self, event):
- wrapper = wrappermap.wrapper(self)
- if self.flags() & gtk.NO_WINDOW:
- drawing_area = self.allocation
- else:
- drawing_area = gtk.gdk.Rectangle(0, 0,
- self.allocation.width, self.allocation.height)
- context = DrawingContext(event.window, drawing_area, event.area)
- context.style = DrawingStyle(wrapper)
- if self.flags() & gtk.CAN_FOCUS:
- focus_space = (self.style_get_property('focus-padding') +
- self.style_get_property('focus-line-width'))
- if not wrapper.squish_width:
- context.width -= focus_space * 2
- translate_x = focus_space
- else:
- translate_x = 0
- if not wrapper.squish_height:
- context.height -= focus_space * 2
- translate_y = focus_space
- else:
- translate_y = 0
- context.translate(translate_x, translate_y)
- wrapper.layout_manager.update_cairo_context(context.context)
- self.draw(wrapper, context)
-
- def draw(self, wrapper, context):
- wrapper.layout_manager.reset()
- wrapper.draw(context, wrapper.layout_manager)
-
- def do_size_request(self, requesition):
- wrapper = wrappermap.wrapper(self)
- width, height = wrapper.size_request(wrapper.layout_manager)
- requesition.width = width
- requesition.height = height
- if self.flags() & gtk.CAN_FOCUS:
- focus_space = (self.style_get_property('focus-padding') +
- self.style_get_property('focus-line-width'))
- if not wrapper.squish_width:
- requesition.width += focus_space * 2
- if not wrapper.squish_height:
- requesition.height += focus_space * 2
-
-class MiroDrawingArea(CustomDrawingMixin, gtk.Widget):
- def __init__(self):
- gtk.Widget.__init__(self)
- CustomDrawingMixin.__init__(self)
- self.set_flags(gtk.NO_WINDOW)
-
-class BackgroundWidget(CustomDrawingMixin, gtk.Bin):
- def do_size_request(self, requesition):
- CustomDrawingMixin.do_size_request(self, requesition)
- if self.get_child():
- child_width, child_height = self.get_child().size_request()
- requesition.width = max(child_width, requesition.width)
- requesition.height = max(child_height, requesition.height)
-
- def do_expose_event(self, event):
- CustomDrawingMixin.do_expose_event(self, event)
- if self.get_child():
- self.propagate_expose(self.get_child(), event)
-
- def do_size_allocate(self, allocation):
- gtk.Bin.do_size_allocate(self, allocation)
- if self.get_child():
- self.get_child().size_allocate(allocation)
-
-gobject.type_register(MiroDrawingArea)
-gobject.type_register(BackgroundWidget)
-
-class Drawable:
- def __init__(self):
- self.squish_width = self.squish_height = False
-
- def set_squish_width(self, setting):
- self.squish_width = setting
-
- def set_squish_height(self, setting):
- self.squish_height = setting
-
- def set_widget(self, drawing_widget):
- if self.is_opaque() and 0:
- box = gtk.EventBox()
- box.add(drawing_widget)
- Widget.set_widget(self, box)
- else:
- Widget.set_widget(self, drawing_widget)
- self.layout_manager = LayoutManager(self._widget)
-
- def size_request(self, layout_manager):
- return 0, 0
-
- def draw(self, context, layout_manager):
- pass
-
- def is_opaque(self):
- return False
-
-class DrawingArea(Drawable, Widget):
- def __init__(self):
- Widget.__init__(self)
- Drawable.__init__(self)
- self.set_widget(MiroDrawingArea())
-
-class Background(Drawable, Bin):
- def __init__(self):
- Bin.__init__(self)
- Drawable.__init__(self)
- self.set_widget(BackgroundWidget())
diff --git a/mvc/widgets/gtk/gtkmenus.py b/mvc/widgets/gtk/gtkmenus.py
deleted file mode 100644
index 926ba15..0000000
--- a/mvc/widgets/gtk/gtkmenus.py
+++ /dev/null
@@ -1,404 +0,0 @@
-# @Base: Miro - an RSS based video player application
-# Copyright (C) 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.
-
-"""gtkmenus.py -- Manage menu layout."""
-
-import gtk
-
-from mvc.widgets import app
-
-import base
-import keymap
-import wrappermap
-
-def _setup_accel(widget, name, shortcut=None):
- """Setup accelerators for a menu item.
-
- This method sets an accel path for the widget and optionally connects a
- shortcut to that accel path.
- """
- # The GTK docs say that we should set the path using this form:
- # <Window-Name>/Menu/Submenu/MenuItem
- # ...but this is hard to do because we don't yet know what window/menu
- # this menu item is going to be added to. gtk.Action and gtk.ActionGroup
- # don't follow the above suggestion, so we don't need to either.
- path = "<MiroActions>/MenuBar/%s" % name
- widget.set_accel_path(path)
- if shortcut is not None:
- accel_string = keymap.get_accel_string(shortcut)
- key, mods = gtk.accelerator_parse(accel_string)
- if gtk.accel_map_lookup_entry(path) is None:
- gtk.accel_map_add_entry(path, key, mods)
- else:
- gtk.accel_map_change_entry(path, key, mods, True)
-
-# map menu names to GTK stock ids.
-_STOCK_IDS = {
- "SaveItem": gtk.STOCK_SAVE,
- "CopyItemURL": gtk.STOCK_COPY,
- "RemoveItems": gtk.STOCK_REMOVE,
- "StopItem": gtk.STOCK_MEDIA_STOP,
- "NextItem": gtk.STOCK_MEDIA_NEXT,
- "PreviousItem": gtk.STOCK_MEDIA_PREVIOUS,
- "PlayPauseItem": gtk.STOCK_MEDIA_PLAY,
- "Open": gtk.STOCK_OPEN,
- "EditPreferences": gtk.STOCK_PREFERENCES,
- "Quit": gtk.STOCK_QUIT,
- "Help": gtk.STOCK_HELP,
- "About": gtk.STOCK_ABOUT,
- "Translate": gtk.STOCK_EDIT
-}
-try:
- _STOCK_IDS['Fullscreen'] = gtk.STOCK_FULLSCREEN
-except AttributeError:
- # fullscreen not available on all GTK versions
- pass
-
-class MenuItemBase(base.Widget):
- """Base class for MenuItem and Separator."""
-
- def show(self):
- """Show this menu item."""
- self._widget.show()
-
- def hide(self):
- """Hide and disable this menu item."""
- self._widget.hide()
-
- def remove_from_parent(self):
- """Remove this menu item from it's parent Menu."""
- parent_menu = self._widget.get_parent()
- if parent_menu is None:
- return
- parent_menu_item = parent_menu.get_attach_widget()
- if parent_menu_item is None:
- return
- parent_menu_item.remove(self._widget)
-
- def _set_accel_group(self, accel_group):
- # menu items don't care about the accel group, their parent Menu
- # handles it for them
- pass
-
-class MenuItem(MenuItemBase):
- """Single item in the menu that can be clicked
-
- :param label: The label it has (must be internationalized)
- :param name: String identifier for this item
- :param shortcut: Shortcut object to use
-
- Signals:
- - activate: menu item was clicked
-
- Example:
-
- >>> MenuItem(_("Preferences"), "EditPreferences")
- >>> MenuItem(_("Cu_t"), "ClipboardCut", Shortcut("x", MOD))
- >>> MenuItem(_("_Update Podcasts and Library"), "UpdatePodcasts",
- ... (Shortcut("r", MOD), Shortcut(F5)))
- >>> MenuItem(_("_Play"), "PlayPauseItem",
- ... play=_("_Play"), pause=_("_Pause"))
- """
-
- def __init__(self, label, name, shortcut=None):
- MenuItemBase.__init__(self)
- self.name = name
- self.set_widget(self.make_widget(label))
- self.activate_id = self.wrapped_widget_connect('activate',
- self._on_activate)
- self._widget.show()
- self.create_signal('activate')
- _setup_accel(self._widget, self.name, shortcut)
-
- def _on_activate(self, menu_item):
- self.emit('activate')
- gtk_menubar = self._find_menubar()
- if gtk_menubar is not None:
- try:
- menubar = wrappermap.wrapper(gtk_menubar)
- except KeyError:
- logging.exception('menubar activate: '
- 'no wrapper for gtbbk.MenuBar')
- else:
- menubar.emit('activate', self.name)
-
- def _find_menubar(self):
- """Find the MenuBar that this menu item is attached to."""
- menu_item = self._widget
- while True:
- parent_menu = menu_item.get_parent()
- if isinstance(parent_menu, gtk.MenuBar):
- return parent_menu
- elif parent_menu is None:
- return None
- menu_item = parent_menu.get_attach_widget()
- if menu_item is None:
- return None
-
- def make_widget(self, label):
- """Create the menu item to use for this widget.
-
- Subclasses will probably want to override this.
- """
- if self.name in _STOCK_IDS:
- mi = gtk.ImageMenuItem(stock_id=_STOCK_IDS[self.name])
- mi.set_label(label)
- return mi
- else:
- return gtk.MenuItem(label)
-
- def set_label(self, new_label):
- self._widget.set_label(new_label)
-
- def get_label(self):
- self._widget.get_label()
-
-class CheckMenuItem(MenuItem):
- """MenuItem that toggles on/off"""
-
- def make_widget(self, label):
- return gtk.CheckMenuItem(label)
-
- def set_state(self, active):
- # prevent the activate signal from fireing in response to us manually
- # changing a value
- self._widget.handler_block(self.activate_id)
- if active is not None:
- self._widget.set_inconsistent(False)
- self._widget.set_active(active)
- else:
- self._widget.set_inconsistent(True)
- self._widget.set_active(False)
- self._widget.handler_unblock(self.activate_id)
-
- def get_state(self):
- return self._widget.get_active()
-
-class RadioMenuItem(CheckMenuItem):
- """MenuItem that toggles on/off and is grouped with other RadioMenuItems.
- """
-
- def make_widget(self, label):
- widget = gtk.RadioMenuItem()
- widget.set_label(label)
- return widget
-
- def set_group(self, group_item):
- self._widget.set_group(group_item._widget)
-
- def remove_from_group(self):
- """Remove this RadioMenuItem from its current group."""
- self._widget.set_group(None)
-
- def _on_activate(self, menu_item):
- # GTK sends the activate signal for both the radio button that's
- # toggled on and the one that gets turned off. Just emit our signal
- # for the active radio button.
- if self.get_state():
- MenuItem._on_activate(self, menu_item)
-
-class Separator(MenuItemBase):
- """Separator item for menus"""
-
- def __init__(self):
- MenuItemBase.__init__(self)
- self.set_widget(gtk.SeparatorMenuItem())
- self._widget.show()
- # Set name to be None just so that it has a similar API to other menu
- # items.
- self.name = None
-
-class MenuShell(base.Widget):
- """Common code shared between Menu and MenuBar.
-
- Subclasses must define a _menu attribute that's a gtk.MenuShell subclass.
- """
-
- def __init__(self):
- base.Widget.__init__(self)
- self._accel_group = None
- self.children = []
-
- def append(self, menu_item):
- """Add a menu item to the end of this menu."""
- self.children.append(menu_item)
- menu_item._set_accel_group(self._accel_group)
- self._menu.append(menu_item._widget)
-
- def insert(self, index, menu_item):
- """Insert a menu item in the middle of this menu."""
- self.children.insert(index, menu_item)
- menu_item._set_accel_group(self._accel_group)
- self._menu.insert(menu_item._widget, index)
-
- def remove(self, menu_item):
- """Remove a child menu item.
-
- :raises ValueError: menu_item is not a child of this menu
- """
- self.children.remove(menu_item)
- self._menu.remove(menu_item._widget)
- menu_item._set_accel_group(None)
-
- def index(self, name):
- """Get the position of a menu item in this list.
-
- :param name: name of the menu
- :returns: index of the menu item, or -1 if not found.
- """
- for i, menu_item in enumerate(self.children):
- if menu_item.name == name:
- return i
- return -1
-
- def get_children(self):
- """Get the child menu items in order."""
- return list(self.children)
-
- def find(self, name):
- """Search for a menu or menu item
-
- This method recursively searches the entire menu structure for a Menu
- or MenuItem object with a given name.
-
- :raises KeyError: name not found
- """
- found = self._find(name)
- if found is None:
- raise KeyError(name)
- else:
- return found
-
- def _find(self, name):
- """Low-level helper-method for find().
-
- :returns: found menu item or None.
- """
- for menu_item in self.get_children():
- if menu_item.name == name:
- return menu_item
- if isinstance(menu_item, MenuShell):
- submenu_find = menu_item._find(name)
- if submenu_find is not None:
- return submenu_find
- return None
-
-class Menu(MenuShell):
- """A Menu holds a list of MenuItems and Menus.
-
- Example:
- >>> Menu(_("P_layback"), "Playback", [
- ... MenuItem(_("_Foo"), "Foo"),
- ... MenuItem(_("_Bar"), "Bar")
- ... ])
- >>> Menu("", "toplevel", [
- ... Menu(_("_File"), "File", [ ... ])
- ... ])
- """
-
- def __init__(self, label, name, child_items):
- MenuShell.__init__(self)
- self.set_widget(gtk.MenuItem(label))
- self._widget.show()
- self.name = name
- # set up _menu for the MenuShell code
- self._menu = gtk.Menu()
- _setup_accel(self._menu, self.name)
- self._widget.set_submenu(self._menu)
- for item in child_items:
- self.append(item)
-
- def show(self):
- """Show this menu."""
- self._widget.show()
-
- def hide(self):
- """Hide this menu."""
- self._widget.hide()
-
- def _set_accel_group(self, accel_group):
- """Set the accel group for this widget.
-
- Accel groups get created by the MenuBar. Whenever a menu or menu item
- is added to that menu bar, the parent calls _set_accel_group() to give
- the accel group to the child.
- """
- if accel_group == self._accel_group:
- return
- self._menu.set_accel_group(accel_group)
- self._accel_group = accel_group
- for child in self.children:
- child._set_accel_group(accel_group)
-
-class MenuBar(MenuShell):
- """Displays a list of Menu items.
-
- Signals:
-
- - activate(menu_bar, name): a menu item was activated
- """
-
- def __init__(self):
- """Create a new MenuBar
-
- :param name: string id to use for our action group
- """
- MenuShell.__init__(self)
- self.create_signal('activate')
- self.set_widget(gtk.MenuBar())
- self._widget.show()
- self._accel_group = gtk.AccelGroup()
- # set up _menu for the MenuShell code
- self._menu = self._widget
-
- def get_accel_group(self):
- return self._accel_group
-
-class MainWindowMenuBar(MenuBar):
- """MenuBar for the main window.
-
- This gets installed into app.widgetapp.menubar on GTK.
- """
- def add_initial_menus(self, menus):
- """Add the initial set of menus.
-
- We modify the menu structure slightly for GTK.
- """
- for menu in menus:
- self.append(menu)
- self._modify_initial_menus()
-
- def _modify_initial_menus(self):
- """Update the portable root menu with GTK-specific stuff."""
- # on linux, we don't have a CheckVersion option because
- # we update with the package system.
- #this_platform = app.config.get(prefs.APP_PLATFORM)
- #if this_platform == 'linux':
- # self.find("CheckVersion").remove_from_parent()
- #app.video_renderer.setup_subtitle_encoding_menu()
diff --git a/mvc/widgets/gtk/keymap.py b/mvc/widgets/gtk/keymap.py
deleted file mode 100644
index cf341ff..0000000
--- a/mvc/widgets/gtk/keymap.py
+++ /dev/null
@@ -1,94 +0,0 @@
-# @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.
-
-"""keymap.py -- Map portable key values to GTK ones.
-"""
-
-import gtk
-
-from mvc.widgets import keyboard
-
-menubar_mod_map = {
- keyboard.MOD: '<Ctrl>',
- keyboard.CTRL: '<Ctrl>',
- keyboard.ALT: '<Alt>',
- keyboard.SHIFT: '<Shift>',
-}
-
-menubar_key_map = {
- keyboard.RIGHT_ARROW: 'Right',
- keyboard.LEFT_ARROW: 'Left',
- keyboard.UP_ARROW: 'Up',
- keyboard.DOWN_ARROW: 'Down',
- keyboard.SPACE: 'space',
- keyboard.ENTER: 'Return',
- keyboard.DELETE: 'Delete',
- keyboard.BKSPACE: 'BackSpace',
- keyboard.ESCAPE: 'Escape',
- '>': 'greater',
- '<': 'less'
-}
-for i in range(1, 13):
- name = 'F%d' % i
- menubar_key_map[getattr(keyboard, name)] = name
-
-# These are reversed versions of menubar_key_map and menubar_mod_map
-gtk_key_map = dict((i[1], i[0]) for i in menubar_key_map.items())
-
-def get_accel_string(shortcut):
- mod_str = ''.join(menubar_mod_map[mod] for mod in shortcut.modifiers)
- key_str = menubar_key_map.get(shortcut.shortcut, shortcut.shortcut)
- return mod_str + key_str
-
-def translate_gtk_modifiers(event):
- """Convert a keypress event to a set of modifiers from the shortcut
- module.
- """
- modifiers = set()
- if event.state & gtk.gdk.CONTROL_MASK:
- modifiers.add(keyboard.CTRL)
- if event.state & gtk.gdk.MOD1_MASK:
- modifiers.add(keyboard.ALT)
- if event.state & gtk.gdk.SHIFT_MASK:
- modifiers.add(keyboard.SHIFT)
- return modifiers
-
-def translate_gtk_event(event):
- """Convert a GTK key event into the tuple (key, modifiers) where
- key and modifiers are from the shortcut module.
- """
- gtk_keyval = gtk.gdk.keyval_name(event.keyval)
- if gtk_keyval == None:
- return None
- if len(gtk_keyval) == 1:
- key = gtk_keyval
- else:
- key = gtk_key_map.get(gtk_keyval)
- modifiers = translate_gtk_modifiers(event)
- return key, modifiers
diff --git a/mvc/widgets/gtk/layout.py b/mvc/widgets/gtk/layout.py
deleted file mode 100644
index d887fcb..0000000
--- a/mvc/widgets/gtk/layout.py
+++ /dev/null
@@ -1,227 +0,0 @@
-# @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.
-
-""".layout -- Layout widgets. """
-
-import gtk
-
-from mvc.utils import Matrix
-from .base import Widget, Bin
-
-class Box(Widget):
- def __init__(self, spacing=0):
- Widget.__init__(self)
- self.children = set()
- self.set_widget(self.WIDGET_CLASS(spacing=spacing))
-
- def pack_start(self, widget, expand=False, padding=0):
- self._widget.pack_start(widget._widget, expand, fill=True,
- padding=padding)
- widget._widget.show()
- self.children.add(widget)
-
- def pack_end(self, widget, expand=False, padding=0):
- self._widget.pack_end(widget._widget, expand, fill=True,
- padding=padding)
- widget._widget.show()
- self.children.add(widget)
-
- def remove(self, widget):
- widget._widget.hide() # otherwise gtkmozembed gets confused
- self._widget.remove(widget._widget)
- self.children.remove(widget)
-
- def enable(self):
- for mem in self.children:
- mem.enable()
-
- def disable(self):
- for mem in self.children:
- mem.disable()
-
-class HBox(Box):
- WIDGET_CLASS = gtk.HBox
-
-class VBox(Box):
- WIDGET_CLASS = gtk.VBox
-
-class Alignment(Bin):
- def __init__(self, xalign=0, yalign=0, xscale=0, yscale=0,
- top_pad=0, bottom_pad=0, left_pad=0, right_pad=0):
- Bin.__init__(self)
- self.set_widget(gtk.Alignment(xalign, yalign, xscale, yscale))
- self.set_padding(top_pad, bottom_pad, left_pad, right_pad)
-
- def set(self, xalign=0, yalign=0, xscale=0, yscale=0):
- self._widget.set(xalign, yalign, xscale, yscale)
-
- def set_padding(self, top_pad=0, bottom_pad=0, left_pad=0, right_pad=0):
- self._widget.set_padding(top_pad, bottom_pad, left_pad, right_pad)
-
-class DetachedWindowHolder(Alignment):
- def __init__(self):
- Alignment.__init__(self, xscale=1, yscale=1)
-
-class Splitter(Widget):
- def __init__(self):
- """Create a new splitter."""
- Widget.__init__(self)
- self.set_widget(gtk.HPaned())
-
- def set_left(self, widget):
- """Set the left child widget."""
- self.left = widget
- self._widget.pack1(widget._widget, resize=False, shrink=False)
- widget._widget.show()
-
- def set_right(self, widget):
- """Set the right child widget. """
- self.right = widget
- self._widget.pack2(widget._widget, resize=True, shrink=False)
- widget._widget.show()
-
- def remove_left(self):
- """Remove the left child widget."""
- if self.left is not None:
- self.left._widget.hide() # otherwise gtkmozembed gets confused
- self._widget.remove(self.left._widget)
- self.left = None
-
- def remove_right(self):
- """Remove the right child widget."""
- if self.right is not None:
- self.right._widget.hide() # otherwise gtkmozembed gets confused
- self._widget.remove(self.right._widget)
- self.right = None
-
- def set_left_width(self, width):
- self._widget.set_position(width)
-
- def get_left_width(self):
- return self._widget.get_position()
-
- def set_right_width(self, width):
- self._widget.set_position(self.width - width)
- # We should take into account the width of the bar, but this seems
- # good enough.
-
-class Table(Widget):
- """Lays out widgets in a table. It works very similar to the GTK Table
- widget, or an HTML table.
- """
- def __init__(self, columns, rows):
- Widget.__init__(self)
- self.set_widget(gtk.Table(rows, columns, homogeneous=False))
- self.children = Matrix(columns, rows)
-
- def pack(self, widget, column, row, column_span=1, row_span=1):
- """Add a widget to the table.
- """
- self.children[column, row] = widget
- self._widget.attach(widget._widget, column, column + column_span,
- row, row + row_span)
- widget._widget.show()
-
- def remove(self, widget):
- widget._widget.hide() # otherwise gtkmozembed gets confused
- self.children.remove(widget)
- self._widget.remove(widget._widget)
-
- def set_column_spacing(self, spacing):
- self._widget.set_col_spacings(spacing)
-
- def set_row_spacing(self, spacing):
- self._widget.set_row_spacings(spacing)
-
- def enable(self, row=None, column=None):
- if row != None and column != None:
- if self.children[column, row]:
- self.children[column, row].enable()
- elif row != None:
- for mem in self.children.row(row):
- if mem: mem.enable()
- elif column != None:
- for mem in self.children.column(column):
- if mem: mem.enable()
- else:
- for mem in self.children:
- if mem: mem.enable()
-
- def disable(self, row=None, column=None):
- if row != None and column != None:
- if self.children[column, row]:
- self.children[column, row].disable()
- elif row != None:
- for mem in self.children.row(row):
- if mem: mem.disable()
- elif column != None:
- for mem in self.children.column(column):
- if mem: mem.disable()
- else:
- for mem in self.children:
- if mem: mem.disable()
-
-class TabContainer(Widget):
- def __init__(self, xalign=0, yalign=0, xscale=0, yscale=0,
- top_pad=0, bottom_pad=0, left_pad=0, right_pad=0):
- Widget.__init__(self)
- self.set_widget(gtk.Notebook())
- self._widget.set_tab_pos(gtk.POS_TOP)
- self.children = []
- self._page_to_select = None
- self.wrapped_widget_connect('realize', self._on_realize)
-
- def _on_realize(self, widget):
- if self._page_to_select is not None:
- self._widget.set_current_page(self._page_to_select)
- self._page_to_select = None
-
- def append_tab(self, child_widget, text, image=None):
- if image is not None:
- label_widget = gtk.VBox(spacing=2)
- image_widget = gtk.Image()
- image_widget.set_from_pixbuf(image.pixbuf)
- label_widget.pack_start(image_widget)
- label_widget.pack_start(gtk.Label(text))
- label_widget.show_all()
- else:
- label_widget = gtk.Label(text)
-
- # switch from a center align to a top align
- child_widget.set(0, 0, 1, 0)
- child_widget.set_padding(10, 10, 10, 10)
-
- self._widget.append_page(child_widget._widget, label_widget)
- self.children.append(child_widget)
-
- def select_tab(self, index):
- if self._widget.flags() & gtk.REALIZED:
- self._widget.set_current_page(index)
- else:
- self._page_to_select = index
diff --git a/mvc/widgets/gtk/layoutmanager.py b/mvc/widgets/gtk/layoutmanager.py
deleted file mode 100644
index fb60049..0000000
--- a/mvc/widgets/gtk/layoutmanager.py
+++ /dev/null
@@ -1,550 +0,0 @@
-# @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.
-
-"""drawing.py -- Contains the LayoutManager class. LayoutManager is
-handles laying out complex objects for the custom drawing code like
-text blocks and buttons.
-"""
-
-import math
-
-import cairo
-import gtk
-import pango
-
-from mvc import utils
-
-use_native_buttons = False # not implemented in MVC
-
-class FontCache(utils.Cache):
- def get(self, context, description, scale_factor, bold, italic):
- key = (context, description, scale_factor, bold, italic)
- return utils.Cache.get(self, key)
-
- def create_new_value(self, key, invalidator=None):
- (context, description, scale_factor, bold, italic) = key
- return Font(context, description, scale_factor, bold, italic)
-
-_font_cache = FontCache(512)
-
-class LayoutManager(object):
- def __init__(self, widget):
- self.pango_context = widget.get_pango_context()
- self.update_style(widget.style)
- self.update_direction(widget.get_direction())
- widget.connect('style-set', self.on_style_set)
- widget.connect('direction-changed', self.on_direction_changed)
- self.widget = widget
- self.reset()
-
- def reset(self):
- self.current_font = self.font(1.0)
- self.text_color = (0, 0, 0)
- self.text_shadow = None
-
- def on_style_set(self, widget, previous_style):
- old_font_desc = self.style_font_desc
- self.update_style(widget.style)
- if self.style_font_desc != old_font_desc:
- # bug #17423 font changed, so the widget's width might have changed
- widget.queue_resize()
-
- def on_direction_changed(self, widget, previous_direction):
- self.update_direction(widget.get_direction())
-
- def update_style(self, style):
- self.style_font_desc = style.font_desc
- self.style = style
-
- def update_direction(self, direction):
- if direction == gtk.TEXT_DIR_RTL:
- self.pango_context.set_base_dir(pango.DIRECTION_RTL)
- else:
- self.pango_context.set_base_dir(pango.DIRECTION_LTR)
-
- def font(self, scale_factor, bold=False, italic=False, family=None):
- return _font_cache.get(self.pango_context, self.style_font_desc,
- scale_factor, bold, italic)
-
- def set_font(self, scale_factor, bold=False, italic=False, family=None):
- self.current_font = self.font(scale_factor, bold, italic)
-
- def set_text_color(self, color):
- self.text_color = color
-
- def set_text_shadow(self, shadow):
- self.text_shadow = shadow
-
- def textbox(self, text, underline=False):
- textbox = TextBox(self.pango_context, self.current_font,
- self.text_color, self.text_shadow)
- textbox.set_text(text, underline=underline)
- return textbox
-
- def button(self, text, pressed=False, disabled=False, style='normal'):
- if style == 'webby':
- return StyledButton(text, self.pango_context, self.current_font,
- pressed, disabled)
- elif use_native_buttons:
- return NativeButton(text, self.pango_context, self.current_font,
- pressed, self.style, self.widget)
- else:
- return StyledButton(text, self.pango_context, self.current_font,
- pressed)
-
- def update_cairo_context(self, cairo_context):
- cairo_context.update_context(self.pango_context)
-
-class Font(object):
- def __init__(self, context, style_font_desc, scale, bold, italic):
- self.context = context
- self.description = style_font_desc.copy()
- self.description.set_size(int(scale * style_font_desc.get_size()))
- if bold:
- self.description.set_weight(pango.WEIGHT_BOLD)
- if italic:
- self.description.set_style(pango.STYLE_ITALIC)
- self.font_metrics = None
-
- def get_font_metrics(self):
- if self.font_metrics is None:
- self.font_metrics = self.context.get_metrics(self.description)
- return self.font_metrics
-
- def ascent(self):
- return pango.PIXELS(self.get_font_metrics().get_ascent())
-
- def descent(self):
- return pango.PIXELS(self.get_font_metrics().get_descent())
-
- def line_height(self):
- metrics = self.get_font_metrics()
- # the +1: some glyphs can be slightly taller than ascent+descent
- # (#17329)
- return (pango.PIXELS(metrics.get_ascent()) +
- pango.PIXELS(metrics.get_descent()) + 1)
-
-class TextBox(object):
- def __init__(self, context, font, color, shadow):
- self.layout = pango.Layout(context)
- self.layout.set_wrap(pango.WRAP_WORD_CHAR)
- self.font = font
- self.color = color
- self.layout.set_font_description(font.description.copy())
- self.width = self.height = None
- self.shadow = shadow
-
- def set_text(self, text, font=None, color=None, underline=False):
- self.text_chunks = []
- self.attributes = []
- self.text_length = 0
- self.underlines = []
- self.append_text(text, font, color, underline)
-
- def append_text(self, text, font=None, color=None, underline=False):
- if text == None:
- text = u""
- startpos = self.text_length
- self.text_chunks.append(text)
- endpos = self.text_length = self.text_length + len(text)
- if font is not None:
- attr = pango.AttrFontDesc(font.description, startpos, endpos)
- self.attributes.append(attr)
- if underline:
- self.underlines.append((startpos, endpos))
- if color:
- def convert(value):
- return int(round(value * 65535))
- attr = pango.AttrForeground(convert(color[0]), convert(color[1]),
- convert(color[2]), startpos, endpos)
- self.attributes.append(attr)
- self.text_set = False
-
- def set_width(self, width):
- if width is not None:
- self.layout.set_width(int(width * pango.SCALE))
- else:
- self.layout.set_width(-1)
- self.width = width
-
- def set_height(self, height):
- # if height is not None:
- # # not sure why set_height isn't in the python bindings, but it
- # # isn't
- # pygtkhacks.set_pango_layout_height(self.layout,
- # int(height * pango.SCALE))
- self.height = height
-
- def set_wrap_style(self, wrap):
- if wrap == 'word':
- self.layout.set_wrap(pango.WRAP_WORD_CHAR)
- elif wrap == 'char' or wrap == 'truncated-char':
- self.layout.set_wrap(pango.WRAP_CHAR)
- else:
- raise ValueError("Unknown wrap value: %s" % wrap)
- if wrap == 'truncated-char':
- self.layout.set_ellipsize(pango.ELLIPSIZE_END)
- else:
- self.layout.set_ellipsize(pango.ELLIPSIZE_NONE)
-
- def set_alignment(self, align):
- if align == 'left':
- self.layout.set_alignment(pango.ALIGN_LEFT)
- elif align == 'right':
- self.layout.set_alignment(pango.ALIGN_RIGHT)
- elif align == 'center':
- self.layout.set_alignment(pango.ALIGN_CENTER)
- else:
- raise ValueError("Unknown align value: %s" % align)
-
- def ensure_layout(self):
- if not self.text_set:
- text = ''.join(self.text_chunks)
- if len(text) > 100:
- text = text[:self._calc_text_cutoff()]
- self.layout.set_text(text)
- attr_list = pango.AttrList()
- for attr in self.attributes:
- attr_list.insert(attr)
- self.layout.set_attributes(attr_list)
- self.text_set = True
-
- def _calc_text_cutoff(self):
- """This method is a bit of a hack... GTK slows down if we pass too
- much text to the layout. Even text that falls below our height has a
- performance penalty. Try not to have too much more than is necessary.
- """
- if None in (self.width, self.height):
- return -1
-
- chars_per_line = (self.width * pango.SCALE //
- self.font.get_font_metrics().get_approximate_char_width())
- lines_available = self.height // self.font.line_height()
- # overestimate these because it's better to have too many characters
- # than too little.
- return int(chars_per_line * lines_available * 1.2)
-
- def line_count(self):
- self.ensure_layout()
- return self.layout.get_line_count()
-
- def get_size(self):
- self.ensure_layout()
- return self.layout.get_pixel_size()
-
- def char_at(self, x, y):
- self.ensure_layout()
- x *= pango.SCALE
- y *= pango.SCALE
- width, height = self.layout.get_size()
- if 0 <= x < width and 0 <= y < height:
- index, leading = self.layout.xy_to_index(x, y)
- # xy_to_index returns the nearest character, but that
- # doesn't mean the user actually clicked on it. Double
- # check that (x, y) is actually inside that char's
- # bounding box
- char_x, char_y, char_w, char_h = self.layout.index_to_pos(index)
- if char_w > 0: # the glyph is LTR
- left = char_x
- right = char_x + char_w
- else: # the glyph is RTL
- left = char_x + char_w
- right = char_x
- if left <= x < right:
- return index
- return None
-
-
- def draw(self, context, x, y, width, height):
- self.set_width(width)
- self.set_height(height)
- self.ensure_layout()
- cairo_context = context.context
- cairo_context.save()
- underline_drawer = UnderlineDrawer(self.underlines)
- if self.shadow:
- # draw shadow first so that it's underneath the regular text
- # FIXME: we don't use the blur_radius setting
- cairo_context.set_source_rgba(self.shadow.color[0],
- self.shadow.color[1], self.shadow.color[2],
- self.shadow.opacity)
- self._draw_layout(context, x + self.shadow.offset[0],
- y + self.shadow.offset[1], width, height,
- underline_drawer)
- cairo_context.set_source_rgb(*self.color)
- self._draw_layout(context, x, y, width, height, underline_drawer)
- cairo_context.restore()
- cairo_context.new_path()
-
- def _draw_layout(self, context, x, y, width, height, underline_drawer):
- line_height = 0
- alignment = self.layout.get_alignment()
- for i in xrange(self.layout.get_line_count()):
- line = self.layout.get_line_readonly(i)
- extents = line.get_pixel_extents()[1]
- next_line_height = line_height + extents[3]
- if next_line_height > height:
- break
- if alignment == pango.ALIGN_CENTER:
- line_x = max(x, x + (width - extents[2]) / 2.0)
- elif alignment == pango.ALIGN_RIGHT:
- line_x = max(x, x + width - extents[2])
- else:
- line_x = x
- baseline = y + line_height + pango.ASCENT(extents)
- context.move_to(line_x, baseline)
- context.context.show_layout_line(line)
- underline_drawer.draw(context, line_x, baseline, line)
- line_height = next_line_height
-
-class UnderlineDrawer(object):
- """Class to draw our own underlines because cairo's don't look
- that great at small fonts. We make sure that the underline is
- always drawn at a pixel boundary and that there always is space
- between the text and the baseline.
-
- This class makes a couple assumptions that might not be that
- great. It assumes that the correct underline size is 1 pixel and
- that the text color doesn't change in the middle of an underline.
- """
- def __init__(self, underlines):
- self.underline_iter = iter(underlines)
- self.finished = False
- self.next_underline()
-
- def next_underline(self):
- try:
- self.startpos, self.endpos = self.underline_iter.next()
- except StopIteration:
- self.finished = True
- else:
- # endpos is the char to stop underlining at
- self.endpos -= 1
-
- def draw(self, context, x, baseline, line):
- baseline = round(baseline) + 0.5
- context.set_line_width(1)
- while not self.finished and line.start_index <= self.startpos:
- startpos = max(line.start_index, self.startpos)
- endpos = min(self.endpos, line.start_index + line.length)
- x1 = x + pango.PIXELS(line.index_to_x(startpos, 0))
- x2 = x + pango.PIXELS(line.index_to_x(endpos, 1))
- context.move_to(x1, baseline + 1)
- context.line_to(x2, baseline + 1)
- context.stroke()
- if endpos < self.endpos:
- break
- else:
- self.next_underline()
-
-class NativeButton(object):
- ICON_PAD = 4
-
- def __init__(self, text, context, font, pressed, style, widget):
- self.layout = pango.Layout(context)
- self.font = font
- self.pressed = pressed
- self.layout.set_font_description(font.description.copy())
- self.layout.set_text(text)
- self.pad_x = style.xthickness + 11
- self.pad_y = style.ythickness + 1
- self.style = style
- self.widget = widget
- # The above code assumes an "inner-border" style property of
- # 1. PyGTK doesn't seem to support Border objects very well,
- # so can't get it from the widget style.
- self.min_width = 0
- self.icon = None
-
- def set_min_width(self, width):
- self.min_width = width
-
- def set_icon(self, icon):
- self.icon = icon
-
- def get_size(self):
- width, height = self.layout.get_pixel_size()
- if self.icon:
- width += self.icon.width + self.ICON_PAD
- height = max(height, self.icon.height)
- width += self.pad_x * 2
- height += self.pad_y * 2
- return max(self.min_width, width), height
-
- def draw(self, context, x, y, width, height):
- text_width, text_height = self.layout.get_pixel_size()
- if self.icon:
- inner_width = text_width + self.icon.width + self.ICON_PAD
- # calculate the icon position x and y are still in cairo
- # coordinates
- icon_x = x + (width - inner_width) / 2.0
- icon_y = y + (height - self.icon.height) / 2.0
- text_x = icon_x + self.icon.width + self.ICON_PAD
- else:
- text_x = x + (width - text_width) / 2.0
- text_y = y + (height - text_height) / 2.0
-
- x, y = context.context.user_to_device(x, y)
- text_x, text_y = context.context.user_to_device(text_x, text_y)
- # Hmm, maybe we should somehow support floating point numbers
- # here, but I don't know how to.
- x, y, width, height = (int(f) for f in (x, y, width, height))
- context.context.get_target().flush()
- self.draw_box(context.window, x, y, width, height)
- self.draw_text(context.window, text_x, text_y)
- if self.icon:
- self.icon.draw(context, icon_x, icon_y, self.icon.width,
- self.icon.height)
-
- def draw_box(self, window, x, y, width, height):
- if self.pressed:
- shadow = gtk.SHADOW_IN
- state = gtk.STATE_ACTIVE
- else:
- shadow = gtk.SHADOW_OUT
- state = gtk.STATE_NORMAL
- if 'QtCurveStyle' in str(self.style):
- # This is a horrible hack for the libqtcurve library. See
- # http://bugzilla.pculture.org/show_bug.cgi?id=10380 for
- # details
- widget = window.get_user_data()
- else:
- widget = self.widget
-
- self.style.paint_box(window, state, shadow, None, widget, "button",
- int(x), int(y), int(width), int(height))
-
- def draw_text(self, window, x, y):
- if self.pressed:
- state = gtk.STATE_ACTIVE
- else:
- state = gtk.STATE_NORMAL
- self.style.paint_layout(window, state, True, None, None, None,
- int(x), int(y), self.layout)
-
-class StyledButton(object):
- PAD_HORIZONTAL = 4
- PAD_VERTICAL = 3
- TOP_COLOR = (1, 1, 1)
- BOTTOM_COLOR = (0.86, 0.86, 0.86)
- LINE_COLOR_TOP = (0.71, 0.71, 0.71)
- LINE_COLOR_BOTTOM = (0.45, 0.45, 0.45)
- TEXT_COLOR = (0.184, 0.184, 0.184)
- DISABLED_COLOR = (0.86, 0.86, 0.86)
- DISABLED_TEXT_COLOR = (0.5, 0.5, 0.5)
- ICON_PAD = 8
-
- def __init__(self, text, context, font, pressed, disabled=False):
- self.layout = pango.Layout(context)
- self.font = font
- self.layout.set_font_description(font.description.copy())
- self.layout.set_text(text)
- self.min_width = 0
- self.pressed = pressed
- self.disabled = disabled
- self.icon = None
-
- def set_icon(self, icon):
- self.icon = icon
-
- def set_min_width(self, width):
- self.min_width = width
-
- def get_size(self):
- width, height = self.layout.get_pixel_size()
- if self.icon:
- width += self.icon.width + self.ICON_PAD
- height = max(height, self.icon.height)
- height += self.PAD_VERTICAL * 2
- if height % 2 == 1:
- # make height even so that the radius of our circle is
- # whole
- height += 1
- width += self.PAD_HORIZONTAL * 2 + height
- return max(self.min_width, width), height
-
- def draw_path(self, context, x, y, width, height, radius):
- inner_width = width - radius * 2
- context.move_to(x + radius, y)
- context.rel_line_to(inner_width, 0)
- context.arc(x + width - radius, y+radius, radius, -math.pi/2,
- math.pi/2)
- context.rel_line_to(-inner_width, 0)
- context.arc(x + radius, y+radius, radius, math.pi/2, -math.pi/2)
-
- def draw_button(self, context, x, y, width, height, radius):
- context.context.save()
- self.draw_path(context, x, y, width, height, radius)
- if self.disabled:
- end_color = self.DISABLED_COLOR
- start_color = self.DISABLED_COLOR
- elif self.pressed:
- end_color = self.TOP_COLOR
- start_color = self.BOTTOM_COLOR
- else:
- context.set_line_width(1)
- start_color = self.TOP_COLOR
- end_color = self.BOTTOM_COLOR
- gradient = cairo.LinearGradient(x, y, x, y + height)
- gradient.add_color_stop_rgb(0, *start_color)
- gradient.add_color_stop_rgb(1, *end_color)
- context.context.set_source(gradient)
- context.fill()
- context.set_line_width(1)
- self.draw_path(context, x+0.5, y+0.5, width, height, radius)
- gradient = cairo.LinearGradient(x, y, x, y + height)
- gradient.add_color_stop_rgb(0, *self.LINE_COLOR_TOP)
- gradient.add_color_stop_rgb(1, *self.LINE_COLOR_BOTTOM)
- context.context.set_source(gradient)
- context.stroke()
- context.context.restore()
-
- def draw(self, context, x, y, width, height):
- radius = height / 2
- self.draw_button(context, x, y, width, height, radius)
-
- text_width, text_height = self.layout.get_pixel_size()
- # draw the text in the center of the button
- text_x = x + (width - text_width) / 2
- text_y = y + (height - text_height) / 2
- if self.icon:
- icon_x = text_x - (self.icon.width + self.ICON_PAD) / 2
- text_x += (self.icon.width + self.ICON_PAD) / 2
- icon_y = y + (height - self.icon.height) / 2
- self.icon.draw(context, icon_x, icon_y, self.icon.width,
- self.icon.height)
- self.draw_text(context, text_x, text_y, width, height, radius)
-
- def draw_text(self, context, x, y, width, height, radius):
- if self.disabled:
- context.set_color(self.DISABLED_TEXT_COLOR)
- else:
- context.set_color(self.TEXT_COLOR)
- context.move_to(x, y)
- context.context.show_layout(self.layout)
diff --git a/mvc/widgets/gtk/simple.py b/mvc/widgets/gtk/simple.py
deleted file mode 100644
index f0921e0..0000000
--- a/mvc/widgets/gtk/simple.py
+++ /dev/null
@@ -1,313 +0,0 @@
-# @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.
-
-"""simple.py -- Collection of simple widgets."""
-
-import gtk
-import gobject
-import pango
-
-from mvc.widgets import widgetconst
-from .base import Widget, Bin
-
-class Image(object):
- def __init__(self, path):
- try:
- self._set_pixbuf(gtk.gdk.pixbuf_new_from_file(path))
- except gobject.GError, ge:
- raise ValueError("%s" % ge)
- self.width = self.pixbuf.get_width()
- self.height = self.pixbuf.get_height()
-
- def _set_pixbuf(self, pixbuf):
- self.pixbuf = pixbuf
- self.width = self.pixbuf.get_width()
- self.height = self.pixbuf.get_height()
-
- def resize(self, width, height):
- width = int(round(width))
- height = int(round(height))
- resized_pixbuf = self.pixbuf.scale_simple(width, height,
- gtk.gdk.INTERP_BILINEAR)
- return TransformedImage(resized_pixbuf)
-
- def resize_for_space(self, width, height):
- """Returns an image scaled to fit into the specified space at the
- correct height/width ratio.
- """
- ratio = min(1.0 * width / self.width, 1.0 * height / self.height)
- return self.resize(ratio * self.width, ratio * self.height)
-
- def crop_and_scale(self, src_x, src_y, src_width, src_height, dest_width,
- dest_height):
- """Crop an image then scale it.
-
- The image will be cropped to the rectangle (src_x, src_y, src_width,
- src_height), that rectangle will be scaled to a new Image with tisez
- (dest_width, dest_height)
- """
- dest = gtk.gdk.Pixbuf(self.pixbuf.get_colorspace(),
- self.pixbuf.get_has_alpha(),
- self.pixbuf.get_bits_per_sample(), dest_width, dest_height)
-
- scale_x = dest_width / float(src_width)
- scale_y = dest_height / float(src_height)
-
- self.pixbuf.scale(dest, 0, 0, dest_width, dest_height,
- -src_x * scale_x, -src_y * scale_y, scale_x, scale_y,
- gtk.gdk.INTERP_BILINEAR)
- return TransformedImage(dest)
-
-class TransformedImage(Image):
- def __init__(self, pixbuf):
- # XXX intentionally not calling direct super's __init__; we should do
- # this differently
- self._set_pixbuf(pixbuf)
-
-class ImageDisplay(Widget):
- def __init__(self, image=None):
- Widget.__init__(self)
- self.set_widget(gtk.Image())
- self.set_image(image)
-
- def set_image(self, image):
- self.image = image
- if image is not None:
- self._widget.set_from_pixbuf(image.pixbuf)
- else:
- self._widget.clear()
-
-class AnimatedImageDisplay(Widget):
- def __init__(self, path):
- Widget.__init__(self)
- self.set_widget(gtk.Image())
- self._animation = gtk.gdk.PixbufAnimation(path)
- # Set to animate before we are shown and stop animating after
- # we disappear.
- self._widget.connect('map', lambda w: self._set_animate(True))
- self._widget.connect('unmap-event',
- lambda w, a: self._set_animate(False))
-
- def _set_animate(self, enabled):
- if enabled:
- self._widget.set_from_animation(self._animation)
- else:
- self._widget.clear()
-
-class Label(Widget):
- """Widget that displays simple text."""
- def __init__(self, text="", color=None):
- Widget.__init__(self)
- self.set_widget(gtk.Label())
- if text:
- self.set_text(text)
- self.attr_list = pango.AttrList()
- self.font_description = self._widget.style.font_desc.copy()
- self.scale_factor = 1.0
- if color is not None:
- self.set_color(color)
- self.wrapped_widget_connect('style-set', self.on_style_set)
-
- def set_bold(self, bold):
- if bold:
- weight = pango.WEIGHT_BOLD
- else:
- weight = pango.WEIGHT_NORMAL
- self.font_description.set_weight(weight)
- self.set_attr(pango.AttrFontDesc(self.font_description))
-
- def set_size(self, size):
- if size == widgetconst.SIZE_NORMAL:
- self.scale_factor = 1
- elif size == widgetconst.SIZE_SMALL:
- self.scale_factor = 0.75
- else:
- self.scale_factor = size
- baseline = self._widget.style.font_desc.get_size()
- self.font_description.set_size(int(baseline * self.scale_factor))
- self.set_attr(pango.AttrFontDesc(self.font_description))
-
- def get_preferred_width(self):
- return self._widget.size_request()[0]
-
- def on_style_set(self, widget, old_style):
- self.set_size(self.scale_factor)
-
- def set_wrap(self, wrap):
- self._widget.set_line_wrap(wrap)
-
- def set_alignment(self, alignment):
- # default to left.
- gtkalignment = gtk.JUSTIFY_LEFT
- if alignment == widgetconst.TEXT_JUSTIFY_LEFT:
- gtkalignment = gtk.JUSTIFY_LEFT
- elif alignment == widgetconst.TEXT_JUSTIFY_RIGHT:
- gtkalignment = gtk.JUSTIFY_RIGHT
- elif alignment == widgetconst.TEXT_JUSTIFY_CENTER:
- gtkalignment = gtk.JUSTIFY_CENTER
- self._widget.set_justify(gtkalignment)
-
- def get_alignment(self):
- return self._widget.get_justify()
-
- def get_width(self):
- return self._widget.get_layout().get_pixel_size()[0]
-
- def set_text(self, text):
- self._widget.set_text(text)
-
- def get_text(self):
- return self._widget.get_text().decode('utf-8')
-
- def set_selectable(self, val):
- self._widget.set_selectable(val)
-
- def set_attr(self, attr):
- attr.end_index = 65535
- self.attr_list.change(attr)
- self._widget.set_attributes(self.attr_list)
-
- def set_color(self, color):
- color_as_int = (int(65535 * c) for c in color)
- self.set_attr(pango.AttrForeground(*color_as_int))
-
- def baseline(self):
- pango_context = self._widget.get_pango_context()
- metrics = pango_context.get_metrics(self.font_description)
- return pango.PIXELS(metrics.get_descent())
-
- def hide(self):
- self._widget.hide()
-
- def show(self):
- self._widget.show()
-
-class Scroller(Bin):
- def __init__(self, horizontal, vertical):
- Bin.__init__(self)
- self.set_widget(gtk.ScrolledWindow())
- if horizontal:
- h_policy = gtk.POLICY_AUTOMATIC
- else:
- h_policy = gtk.POLICY_NEVER
- if vertical:
- v_policy = gtk.POLICY_AUTOMATIC
- else:
- v_policy = gtk.POLICY_NEVER
- self._widget.set_policy(h_policy, v_policy)
-
- def set_has_borders(self, has_border):
- pass
-
- def set_background_color(self, color):
- pass
-
- def add_child_to_widget(self):
- if (isinstance(self.child._widget, gtk.TreeView) or
- isinstance(self.child._widget, gtk.TextView)):
- # child has native scroller
- self._widget.add(self.child._widget)
- else:
- self._widget.add_with_viewport(self.child._widget)
- self._widget.get_child().set_shadow_type(gtk.SHADOW_NONE)
- if isinstance(self.child._widget, gtk.TextView):
- self._widget.set_shadow_type(gtk.SHADOW_IN)
- else:
- self._widget.set_shadow_type(gtk.SHADOW_NONE)
-
- def prepare_for_dark_content(self):
- # this is just a hack for cocoa
- pass
-
-
-class SolidBackground(Bin):
- def __init__(self, color=None):
- Bin.__init__(self)
- self.set_widget(gtk.EventBox())
- if color is not None:
- self.set_background_color(color)
-
- def set_background_color(self, color):
- self.modify_style('base', gtk.STATE_NORMAL, self.make_color(color))
- self.modify_style('bg', gtk.STATE_NORMAL, self.make_color(color))
-
-class Expander(Bin):
- def __init__(self, child=None):
- Bin.__init__(self)
- self.set_widget(gtk.Expander())
- if child is not None:
- self.add(child)
- self.label = None
- # This is a complete hack. GTK expanders have a transparent
- # background most of the time, except when they are prelighted. So we
- # just set the background to white there because that's what should
- # happen in the item list.
- self.modify_style('bg', gtk.STATE_PRELIGHT,
- gtk.gdk.color_parse('white'))
-
- def set_spacing(self, spacing):
- self._widget.set_spacing(spacing)
-
- def set_label(self, widget):
- self.label = widget
- self._widget.set_label_widget(widget._widget)
- widget._widget.show()
-
- def set_expanded(self, expanded):
- self._widget.set_expanded(expanded)
-
-class ProgressBar(Widget):
- def __init__(self):
- Widget.__init__(self)
- self.set_widget(gtk.ProgressBar())
- self._timer = None
-
- def set_progress(self, fraction):
- self._widget.set_fraction(fraction)
-
- def start_pulsing(self):
- if self._timer is None:
- self._timer = gobject.timeout_add(100, self._do_pulse)
-
- def stop_pulsing(self):
- if self._timer:
- gobject.source_remove(self._timer)
- self._timer = None
-
- def _do_pulse(self):
- self._widget.pulse()
- return True
-
-class HLine(Widget):
- """A horizontal separator. Not to be confused with HSeparator, which is is
- a DrawingArea, not a Widget.
- """
- def __init__(self):
- Widget.__init__(self)
- self.set_widget(gtk.HSeparator())
diff --git a/mvc/widgets/gtk/tableview.py b/mvc/widgets/gtk/tableview.py
deleted file mode 100644
index 930270c..0000000
--- a/mvc/widgets/gtk/tableview.py
+++ /dev/null
@@ -1,1557 +0,0 @@
-# @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.
-
-"""tableview.py -- Wrapper for the GTKTreeView widget. It's used for the tab
-list and the item list (AKA almost all of the miro).
-"""
-
-import logging
-
-import itertools
-import gobject
-import gtk
-from collections import namedtuple
-
-# These are probably wrong, and are placeholders for now, until custom headers
-# are also implemented for GTK.
-CUSTOM_HEADER_HEIGHT = 25
-HEADER_HEIGHT = 25
-
-from mvc import signals
-from mvc.errors import (WidgetActionError, WidgetDomainError,
- WidgetRangeError, WidgetNotReadyError)
-from mvc.widgets.tableselection import SelectionOwnerMixin
-from mvc.widgets.tablescroll import ScrollbarOwnerMixin
-import drawing
-import wrappermap
-from .base import Widget
-from .simple import Image
-from .layoutmanager import LayoutManager
-from .weakconnect import weak_connect
-from .tableviewcells import GTKCustomCellRenderer
-
-
-PathInfo = namedtuple('PathInfo', 'path column x y')
-Rect = namedtuple('Rect', 'x y width height')
-_album_view_gtkrc_installed = False
-
-def _install_album_view_gtkrc():
- """Hack for styling GTKTreeView for the album view widget.
-
- We do a couple things:
- - Remove the focus ring
- - Remove any separator space.
-
- We do this so that we don't draw a box through the album view column for
- selected rows.
- """
- global _album_view_gtkrc_installed
- if _album_view_gtkrc_installed:
- return
- rc_string = ('style "album-view-style"\n'
- '{ \n'
- ' GtkTreeView::vertical-separator = 0\n'
- ' GtkTreeView::horizontal-separator = 0\n'
- ' GtkWidget::focus-line-width = 0 \n'
- '}\n'
- 'widget "*.miro-album-view" style "album-view-style"\n')
- gtk.rc_parse_string(rc_string)
- _album_view_gtkrc_installed = True
-
-def rect_contains_point(rect, x, y):
- return ((rect.x <= x < rect.x + rect.width) and
- (rect.y <= y < rect.y + rect.height))
-
-class TreeViewScrolling(object):
- def __init__(self):
- self.scrollbars = []
- self.scroll_positions = None, None
- self.restoring_scroll = None
- self.connect('parent-set', self.on_parent_set)
- self.scroller = None
- # hack necessary because of our weird widget hierarchy (GTK doesn't deal
- # well with the Scroller's widget not being the direct parent of the
- # TableView's widget.)
- self._coords_working = False
-
- def scroll_range_changed(self):
- """Faux-signal; this should all be integrated into
- GTKScrollbarOwnerMixin, making this unnecessary.
- """
-
- @property
- def manually_scrolled(self):
- """Return whether the view has been scrolled explicitly by the user
- since the last time it was set automatically.
- """
- auto_pos = self.scroll_positions[1]
- if auto_pos is None:
- # if we don't have any position yet, user can't have manually
- # scrolled
- return False
- real_pos = self.scrollbars[1].get_value()
- return abs(auto_pos - real_pos) > 5 # allowing some fuzziness
-
- @property
- def position_set(self):
- """Return whether the scroll position has been set in any way."""
- return any(x is not None for x in self.scroll_positions)
-
- def on_parent_set(self, widget, old_parent):
- """We have parent window now; we need to control its scrollbars."""
- self.set_scroller(widget.get_parent())
-
- def set_scroller(self, window):
- """Take control of the scrollbars of window."""
- if not isinstance(window, gtk.ScrolledWindow):
- return
- self.scroller = window
- scrollbars = tuple(bar.get_adjustment()
- for bar in (window.get_hscrollbar(), window.get_vscrollbar()))
- self.scrollbars = scrollbars
- for i, bar in enumerate(scrollbars):
- weak_connect(bar, 'changed', self.on_scroll_range_changed, i)
- if self.restoring_scroll:
- self.set_scroll_position(self.restoring_scroll)
-
- def on_scroll_range_changed(self, adjustment, bar):
- """The scrollbar might have a range now. Set its initial position if
- we haven't already.
- """
- self._coords_working = True
- if self.restoring_scroll:
- self.set_scroll_position(self.restoring_scroll)
- # our wrapper handles the same thing for iters
- self.scroll_range_changed()
-
- def set_scroll_position(self, scroll_position):
- """Restore the scrollbars to a remembered state."""
- try:
- self.scroll_positions = tuple(self._clip_pos(adj, x)
- for adj, x in zip(self.scrollbars, scroll_position))
- except WidgetActionError, error:
- logging.debug("can't scroll yet: %s", error.reason)
- # try again later
- self.restoring_scroll = scroll_position
- else:
- for adj, pos in zip(self.scrollbars, self.scroll_positions):
- adj.set_value(pos)
- self.restoring_scroll = None
-
- def _clip_pos(self, adj, pos):
- lower = adj.get_lower()
- upper = adj.get_upper() - adj.get_page_size()
- # currently, StandardView gets an upper of 2.0 when it's not ready
- # FIXME: don't count on that
- if pos > upper and upper < 5:
- raise WidgetRangeError("scrollable area", pos, lower, upper)
- return min(max(pos, lower), upper)
-
- def get_path_rect(self, path):
- """Return the Rect for the given item, in tree coords."""
- if not self._coords_working:
- # part of solution to #17405; widget_to_tree_coords tends to return
- # y=8 before the first scroll-range-changed signal. ugh.
- raise WidgetNotReadyError('_coords_working')
- rect = self.get_background_area(path, self.get_columns()[0])
- x, y = self.widget_to_tree_coords(rect.x, rect.y)
- return Rect(x, y, rect.width, rect.height)
-
- @property
- def _scrollbars(self):
- if not self.scrollbars:
- raise WidgetNotReadyError
- return self.scrollbars
-
- def scroll_ancestor(self, newly_selected, down):
- # Try to figure out what just became selected. If multiple things
- # somehow became selected, select the outermost one
- if len(newly_selected) == 0:
- raise WidgetActionError("need at an item to scroll to")
- if down:
- path_to_show = max(newly_selected)
- else:
- path_to_show = min(newly_selected)
-
- if not self.scrollbars:
- return
- vadjustment = self.scrollbars[1]
-
- rect = self.get_background_area(path_to_show, self.get_columns()[0])
- _, top = self.translate_coordinates(self.scroller, 0, rect.y)
- top += vadjustment.value
- bottom = top + rect.height
- if down:
- if bottom > vadjustment.value + vadjustment.page_size:
- bottom_value = min(bottom, vadjustment.upper)
- vadjustment.set_value(bottom_value - vadjustment.page_size)
- else:
- if top < vadjustment.value:
- vadjustment.set_value(max(vadjustment.lower, top))
-
-class MiroTreeView(gtk.TreeView, TreeViewScrolling):
- """Extends the GTK TreeView widget to help implement TableView
- https://develop.participatoryculture.org/index.php/WidgetAPITableView"""
- # Add a tiny bit of padding so that the user can drag feeds below
- # the table, i.e. to the bottom row, as a top-level
- PAD_BOTTOM = 3
- def __init__(self):
- gtk.TreeView.__init__(self)
- TreeViewScrolling.__init__(self)
- self.height_without_pad_bottom = -1
- self.set_enable_search(False)
- self.horizontal_separator = self.style_get_property("horizontal-separator")
- self.expander_size = self.style_get_property("expander-size")
- self.group_lines_enabled = False
- self.group_line_color = (0, 0, 0)
- self.group_line_width = 1
-
- def do_size_request(self, req):
- gtk.TreeView.do_size_request(self, req)
- self.height_without_pad_bottom = req.height
- req.height += self.PAD_BOTTOM
-
- def do_move_cursor(self, step, count):
- if step == gtk.MOVEMENT_VISUAL_POSITIONS:
- # GTK is asking us to move left/right. Since our TableViews don't
- # support this, return False to let the key press propagate. See
- # #15646 for more info.
- return False
- if isinstance(self.get_parent(), gtk.ScrolledWindow):
- # If our parent is a ScrolledWindow, let GTK take care of this
- handled = gtk.TreeView.do_move_cursor(self, step, count)
- return handled
- else:
- # Otherwise, we have to search up the widget tree for a
- # ScrolledWindow to take care of it
- selection = self.get_selection()
- model, start_selection = selection.get_selected_rows()
- gtk.TreeView.do_move_cursor(self, step, count)
-
- model, end_selection = selection.get_selected_rows()
- newly_selected = set(end_selection) - set(start_selection)
- down = (count > 0)
-
- try:
- self.scroll_ancestor(newly_selected, down)
- except WidgetActionError:
- # not possible
- return False
- return True
-
- def get_position_info(self, x, y):
- """Wrapper for get_path_at_pos that converts the path_info to a named
- tuple and handles rounding the coordinates.
- """
- path_info = self.get_path_at_pos(int(round(x)), int(round(y)))
- if path_info:
- return PathInfo(*path_info)
-
-gobject.type_register(MiroTreeView)
-
-class HotspotTracker(object):
- """Handles tracking hotspots.
- https://develop.participatoryculture.org/index.php/WidgetAPITableView"""
-
- def __init__(self, treeview, event):
- self.treeview = treeview
- self.treeview_wrapper = wrappermap.wrapper(treeview)
- self.hit = False
- self.button = event.button
- path_info = treeview.get_position_info(event.x, event.y)
- if path_info is None:
- return
- self.path, self.column, background_x, background_y = path_info
- # We always pack 1 renderer for each column
- gtk_renderer = self.column.get_cell_renderers()[0]
- if not isinstance(gtk_renderer, GTKCustomCellRenderer):
- return
- self.renderer = wrappermap.wrapper(gtk_renderer)
- self.attr_map = self.treeview_wrapper.attr_map_for_column[self.column]
- if not rect_contains_point(self.calc_cell_area(), event.x, event.y):
- # Mouse is in the padding around the actual cell area
- return
- self.update_position(event)
- self.iter = treeview.get_model().get_iter(self.path)
- self.name = self.calc_hotspot()
- if self.name is not None:
- self.hit = True
-
- def is_for_context_menu(self):
- return self.name == "#show-context-menu"
-
- def calc_cell_area(self):
- cell_area = self.treeview.get_cell_area(self.path, self.column)
- xpad = self.renderer._renderer.props.xpad
- ypad = self.renderer._renderer.props.ypad
- cell_area.x += xpad
- cell_area.y += ypad
- cell_area.width -= xpad * 2
- cell_area.height -= ypad * 2
- return cell_area
-
- def update_position(self, event):
- self.x, self.y = int(event.x), int(event.y)
-
- def calc_cell_state(self):
- if self.treeview.get_selection().path_is_selected(self.path):
- if self.treeview.flags() & gtk.HAS_FOCUS:
- return gtk.STATE_SELECTED
- else:
- return gtk.STATE_ACTIVE
- else:
- return gtk.STATE_NORMAL
-
- def calc_hotspot(self):
- cell_area = self.calc_cell_area()
- if rect_contains_point(cell_area, self.x, self.y):
- model = self.treeview.get_model()
- self.renderer.cell_data_func(self.column, self.renderer._renderer,
- model, self.iter, self.attr_map)
- style = drawing.DrawingStyle(self.treeview_wrapper,
- use_base_color=True, state=self.calc_cell_state())
- x = self.x - cell_area.x
- y = self.y - cell_area.y
- return self.renderer.hotspot_test(style,
- self.treeview_wrapper.layout_manager,
- x, y, cell_area.width, cell_area.height)
- else:
- return None
-
- def update_hit(self):
- if self.is_for_context_menu():
- return # we always keep hit = True for this one
- old_hit = self.hit
- self.hit = (self.calc_hotspot() == self.name)
- if self.hit != old_hit:
- self.redraw_cell()
-
- def redraw_cell(self):
- # Check that the treeview is still around. We might have switched
- # views in response to a hotspot being clicked.
- if self.treeview.flags() & gtk.REALIZED:
- cell_area = self.treeview.get_cell_area(self.path, self.column)
- x, y = self.treeview.tree_to_widget_coords(cell_area.x,
- cell_area.y)
- self.treeview.queue_draw_area(x, y,
- cell_area.width, cell_area.height)
-
-class TableColumn(signals.SignalEmitter):
- """A single column of a TableView.
-
- Signals:
-
- clicked (table_column) -- The header for this column was clicked.
- """
- # GTK hard-codes 4px of padding for each column
- FIXED_PADDING = 4
- def __init__(self, title, renderer, header=None, **attrs):
- # header widget not used yet in GTK (#15800)
- signals.SignalEmitter.__init__(self)
- self.create_signal('clicked')
- self._column = gtk.TreeViewColumn(title, renderer._renderer)
- self._column.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
- self._column.set_clickable(True)
- self.attrs = attrs
- renderer.setup_attributes(self._column, attrs)
- self.renderer = renderer
- weak_connect(self._column, 'clicked', self._header_clicked)
- self.do_horizontal_padding = True
-
- def set_right_aligned(self, right_aligned):
- """Horizontal alignment of the header label."""
- if right_aligned:
- self._column.set_alignment(1.0)
- else:
- self._column.set_alignment(0.0)
-
- def set_min_width(self, width):
- self._column.props.min_width = width + TableColumn.FIXED_PADDING
-
- def set_max_width(self, width):
- self._column.props.max_width = width
-
- def set_width(self, width):
- self._column.set_fixed_width(width + TableColumn.FIXED_PADDING)
-
- def get_width(self):
- return self._column.get_width()
-
- def _header_clicked(self, tablecolumn):
- self.emit('clicked')
-
- def set_resizable(self, resizable):
- """Set if the user can resize the column."""
- self._column.set_resizable(resizable)
-
- def set_do_horizontal_padding(self, horizontal_padding):
- self.do_horizontal_padding = False
-
- def set_sort_indicator_visible(self, visible):
- """Show/Hide the sort indicator for this column."""
- self._column.set_sort_indicator(visible)
-
- def get_sort_indicator_visible(self):
- return self._column.get_sort_indicator()
-
- def set_sort_order(self, ascending):
- """Display a sort indicator on the column header. Ascending can be
- either True or False which affects the direction of the indicator.
- """
- if ascending:
- self._column.set_sort_order(gtk.SORT_ASCENDING)
- else:
- self._column.set_sort_order(gtk.SORT_DESCENDING)
-
- def get_sort_order_ascending(self):
- """Returns if the sort indicator is displaying that the sort is
- ascending.
- """
- return self._column.get_sort_order() == gtk.SORT_ASCENDING
-
-class GTKSelectionOwnerMixin(SelectionOwnerMixin):
- """GTK-specific methods for selection management.
-
- This subclass should not define any behavior. Methods that cannot be
- completed in this widget state should raise WidgetActionError.
- """
- def __init__(self):
- SelectionOwnerMixin.__init__(self)
- self.selection = self._widget.get_selection()
- weak_connect(self.selection, 'changed', self.on_selection_changed)
-
- def _set_allow_multiple_select(self, allow):
- if allow:
- mode = gtk.SELECTION_MULTIPLE
- else:
- mode = gtk.SELECTION_SINGLE
- self.selection.set_mode(mode)
-
- def _get_allow_multiple_select(self):
- return self.selection.get_mode() == gtk.SELECTION_MULTIPLE
-
- def _get_selected_iters(self):
- iters = []
- def collect(treemodel, path, iter_):
- iters.append(iter_)
- self.selection.selected_foreach(collect)
- return iters
-
- def _get_selected_iter(self):
- model, iter_ = self.selection.get_selected()
- return iter_
-
- @property
- def num_rows_selected(self):
- return self.selection.count_selected_rows()
-
- def _is_selected(self, iter_):
- return self.selection.iter_is_selected(iter_)
-
- def _select(self, iter_):
- self.selection.select_iter(iter_)
-
- def _unselect(self, iter_):
- self.selection.unselect_iter(iter_)
-
- def _unselect_all(self):
- self.selection.unselect_all()
-
- def _iter_to_string(self, iter_):
- return self._model.get_string_from_iter(iter_)
-
- def _iter_from_string(self, string):
- try:
- return self._model.get_iter_from_string(string)
- except ValueError:
- raise WidgetDomainError(
- "model iters", string, "%s other iters" % len(self.model))
-
- def select_path(self, path):
- self.selection.select_path(path)
-
- def _validate_iter(self, iter_):
- if self.get_path(iter_) is None:
- raise WidgetDomainError(
- "model iters", iter_, "%s other iters" % len(self.model))
- real_model = self._widget.get_model()
- if not real_model:
- raise WidgetActionError("no model")
- elif real_model != self._model:
- raise WidgetActionError("wrong model?")
-
- def get_cursor(self):
- """Return the path of the 'focused' item."""
- path, column = self._widget.get_cursor()
- return path
-
- def set_cursor(self, path):
- """Set the path of the 'focused' item."""
- if path is None:
- # XXX: is there a way to clear the cursor?
- return
- path_as_string = ':'.join(str(component) for component in path)
- with self.preserving_selection(): # set_cursor() messes up the selection
- self._widget.set_cursor(path_as_string)
-
-class DNDHandlerMixin(object):
- """TableView row DnD.
-
- Depends on arbitrary TableView methods; otherwise self-contained except:
- on_button_press: may call start_drag
- on_button_release: may unset drag_button_down
- on_motion_notify: may call potential_drag_motion
- """
- def __init__(self):
- self.drag_button_down = False
- self.drag_data = {}
- self.drag_source = self.drag_dest = None
- self.drag_start_x, self.drag_start_y = None, None
- self.wrapped_widget_connect('drag-data-get', self.on_drag_data_get)
- self.wrapped_widget_connect('drag-end', self.on_drag_end)
- self.wrapped_widget_connect('drag-motion', self.on_drag_motion)
- self.wrapped_widget_connect('drag-leave', self.on_drag_leave)
- self.wrapped_widget_connect('drag-drop', self.on_drag_drop)
- self.wrapped_widget_connect('drag-data-received',
- self.on_drag_data_received)
- self.wrapped_widget_connect('unrealize', self.on_drag_unrealize)
-
- def set_drag_source(self, drag_source):
- self.drag_source = drag_source
- # XXX: the following note no longer seems accurate:
- # No need to call enable_model_drag_source() here, we handle it
- # ourselves in on_motion_notify()
-
- def set_drag_dest(self, drag_dest):
- """Set the drop handler."""
- self.drag_dest = drag_dest
- if drag_dest is not None:
- targets = self._gtk_target_list(drag_dest.allowed_types())
- self._widget.enable_model_drag_dest(targets,
- drag_dest.allowed_actions())
- self._widget.drag_dest_set(0, targets,
- drag_dest.allowed_actions())
- else:
- self._widget.unset_rows_drag_dest()
- self._widget.drag_dest_unset()
-
- def start_drag(self, treeview, event, path_info):
- """Check whether the event is a drag event; return whether handled
- here.
- """
- if event.state & (gtk.gdk.CONTROL_MASK | gtk.gdk.SHIFT_MASK):
- return False
- model, row_paths = treeview.get_selection().get_selected_rows()
-
- if path_info.path not in row_paths:
- # something outside the selection is being dragged.
- # make it the new selection.
- self.unselect_all(signal=False)
- self.select_path(path_info.path)
- row_paths = [path_info.path]
- rows = self.model.get_rows(row_paths)
- self.drag_data = rows and self.drag_source.begin_drag(self, rows)
- self.drag_button_down = bool(self.drag_data)
- if self.drag_button_down:
- self.drag_start_x = int(event.x)
- self.drag_start_y = int(event.y)
-
- if len(row_paths) > 1 and path_info.path in row_paths:
- # handle multiple selection. If the current row is already
- # selected, stop propagating the signal. We will only change
- # the selection if the user doesn't start a DnD operation.
- # This makes it more natural for the user to drag a block of
- # selected items.
- renderer = path_info.column.get_cell_renderers()[0]
- if (not self._x_coord_in_expander(treeview, path_info)
- and not isinstance(renderer, GTKCheckboxCellRenderer)):
- self.delaying_press = True
- # grab keyboard focus since we handled the event
- self.focus()
- return True
-
- def on_drag_data_get(self, treeview, context, selection, info, timestamp):
- for typ, data in self.drag_data.items():
- selection.set(typ, 8, repr(data))
-
- def on_drag_end(self, treeview, context):
- self.drag_data = {}
-
- def find_type(self, drag_context):
- return self._widget.drag_dest_find_target(drag_context,
- self._widget.drag_dest_get_target_list())
-
- def calc_positions(self, x, y):
- """Given x and y coordinates, generate a list of drop positions to
- try. The values are tuples in the form of (parent_path, position,
- gtk_path, gtk_position), where parent_path and position is the
- position to send to the Miro code, and gtk_path and gtk_position is an
- equivalent position to send to the GTK code if the drag_dest validates
- the drop.
- """
- model = self._model
- try:
- gtk_path, gtk_position = self._widget.get_dest_row_at_pos(x, y)
- except TypeError:
- # Below the last row
- yield (None, len(model), None, None)
- return
-
- iter_ = model.get_iter(gtk_path)
- if gtk_position in (gtk.TREE_VIEW_DROP_INTO_OR_BEFORE,
- gtk.TREE_VIEW_DROP_INTO_OR_AFTER):
- yield (iter_, -1, gtk_path, gtk_position)
-
- if hasattr(model, 'iter_is_valid'):
- # tablist has this; item list does not
- assert model.iter_is_valid(iter_)
- parent_iter = model.iter_parent(iter_)
- position = gtk_path[-1]
- if gtk_position in (gtk.TREE_VIEW_DROP_BEFORE,
- gtk.TREE_VIEW_DROP_INTO_OR_BEFORE):
- # gtk gave us a "before" position, no need to change it
- yield (parent_iter, position, gtk_path, gtk.TREE_VIEW_DROP_BEFORE)
- else:
- # gtk gave us an "after" position, translate that to before the
- # next row for miro.
- if (self._widget.row_expanded(gtk_path) and
- model.iter_has_child(iter_)):
- child_path = gtk_path + (0,)
- yield (iter_, 0, child_path, gtk.TREE_VIEW_DROP_BEFORE)
- else:
- yield (parent_iter, position+1, gtk_path,
- gtk.TREE_VIEW_DROP_AFTER)
-
- def on_drag_motion(self, treeview, drag_context, x, y, timestamp):
- if not self.drag_dest:
- return True
- type = self.find_type(drag_context)
- if type == "NONE":
- drag_context.drag_status(0, timestamp)
- return True
- drop_action = 0
- for pos_info in self.calc_positions(x, y):
- drop_action = self.drag_dest.validate_drop(self, self.model, type,
- drag_context.actions, pos_info[0], pos_info[1])
- if isinstance(drop_action, (list, tuple)):
- drop_action, iter = drop_action
- path = self.model.get_path(iter)
- pos = gtk.TREE_VIEW_DROP_INTO_OR_BEFORE
- else:
- path, pos = pos_info[2:4]
-
- if drop_action:
- self.set_drag_dest_row(path, pos)
- break
- else:
- self.unset_drag_dest_row()
- drag_context.drag_status(drop_action, timestamp)
- return True
-
- def set_drag_dest_row(self, path, position):
- self._widget.set_drag_dest_row(path, position)
-
- def unset_drag_dest_row(self):
- self._widget.unset_drag_dest_row()
-
- def on_drag_leave(self, treeview, drag_context, timestamp):
- treeview.unset_drag_dest_row()
-
- def on_drag_drop(self, treeview, drag_context, x, y, timestamp):
- # prevent the default handler
- treeview.emit_stop_by_name('drag-drop')
- target = self.find_type(drag_context)
- if target == "NONE":
- return False
- treeview.drag_get_data(drag_context, target, timestamp)
- treeview.unset_drag_dest_row()
-
- def on_drag_data_received(self,
- treeview, drag_context, x, y, selection, info, timestamp):
- # prevent the default handler
- treeview.emit_stop_by_name('drag-data-received')
- if not self.drag_dest:
- return
- type = self.find_type(drag_context)
- if type == "NONE":
- return
- if selection.data is None:
- return
- drop_action = 0
- for pos_info in self.calc_positions(x, y):
- drop_action = self.drag_dest.validate_drop(self, self.model, type,
- drag_context.actions, pos_info[0], pos_info[1])
- if drop_action:
- self.drag_dest.accept_drop(self, self.model, type,
- drag_context.actions, pos_info[0], pos_info[1],
- eval(selection.data))
- return True
- return False
-
- def on_drag_unrealize(self, treeview):
- self.drag_button_down = False
-
- def potential_drag_motion(self, treeview, event):
- """A motion event has occurred and did not hit a hotspot; start a drag
- if applicable.
- """
- if (self.drag_data and self.drag_button_down and
- treeview.drag_check_threshold(self.drag_start_x,
- self.drag_start_y, int(event.x), int(event.y))):
- self.delaying_press = False
- treeview.drag_begin(self._gtk_target_list(self.drag_data.keys()),
- self.drag_source.allowed_actions(), 1, event)
-
- @staticmethod
- def _gtk_target_list(types):
- count = itertools.count()
- return [(type, gtk.TARGET_SAME_APP, count.next()) for type in types]
-
-class HotspotTrackingMixin(object):
- def __init__(self):
- self.hotspot_tracker = None
- self.create_signal('hotspot-clicked')
- self._hotspot_callback_handles = []
- self._connect_hotspot_signals()
- self.wrapped_widget_connect('unrealize', self.on_hotspot_unrealize)
-
- def _connect_hotspot_signals(self):
- SIGNALS = {
- 'row-inserted': self.on_row_inserted,
- 'row-deleted': self.on_row_deleted,
- 'row-changed': self.on_row_changed,
- }
- self._hotspot_callback_handles.extend(
- weak_connect(self._model, signal, handler)
- for signal, handler in SIGNALS.iteritems())
-
- def _disconnect_hotspot_signals(self):
- for handle in self._hotspot_callback_handles:
- self._model.disconnect(handle)
- self._hotspot_callback_handles = []
-
- def on_row_inserted(self, model, path, iter_):
- if self.hotspot_tracker:
- self.hotspot_tracker.redraw_cell()
- self.hotspot_tracker = None
-
- def on_row_deleted(self, model, path):
- if self.hotspot_tracker:
- self.hotspot_tracker.redraw_cell()
- self.hotspot_tracker = None
-
- def on_row_changed(self, model, path, iter_):
- if self.hotspot_tracker:
- self.hotspot_tracker.update_hit()
-
- def handle_hotspot_hit(self, treeview, event):
- """Check whether the event is a hotspot event; return whether handled
- here.
- """
- if self.hotspot_tracker:
- return
- hotspot_tracker = HotspotTracker(treeview, event)
- if hotspot_tracker.hit:
- self.hotspot_tracker = hotspot_tracker
- hotspot_tracker.redraw_cell()
- if hotspot_tracker.is_for_context_menu():
- menu = self._popup_context_menu(self.hotspot_tracker.path, event)
- if menu:
- menu.connect('selection-done',
- self._on_hotspot_context_menu_selection_done)
- # grab keyboard focus since we handled the event
- self.focus()
- return True
-
- def _on_hotspot_context_menu_selection_done(self, menu):
- # context menu is closed, we won't get the button-release-event in
- # this case, but we can unset hotspot tracker here.
- if self.hotspot_tracker:
- self.hotspot_tracker.redraw_cell()
- self.hotspot_tracker = None
-
- def on_hotspot_unrealize(self, treeview):
- self.hotspot_tracker = None
-
- def release_on_hotspot(self, event):
- """A button_release occurred; return whether it has been handled as a
- hotspot hit.
- """
- hotspot_tracker = self.hotspot_tracker
- if hotspot_tracker and event.button == hotspot_tracker.button:
- hotspot_tracker.update_position(event)
- hotspot_tracker.update_hit()
- if (hotspot_tracker.hit and
- not hotspot_tracker.is_for_context_menu()):
- self.emit('hotspot-clicked', hotspot_tracker.name,
- hotspot_tracker.iter)
- hotspot_tracker.redraw_cell()
- self.hotspot_tracker = None
- return True
-
- def hotspot_model_changed(self):
- """A bulk change has ended; reconnect signals and update hotspots."""
- self._connect_hotspot_signals()
- if self.hotspot_tracker:
- self.hotspot_tracker.redraw_cell()
- self.hotspot_tracker.update_hit()
-
-class ColumnOwnerMixin(object):
- """Keeps track of the table's columns - including the list of columns, and
- properties that we set for a table but need to apply to each column.
-
- This manages:
- columns
- attr_map_for_column
- gtk_column_to_wrapper
- for use throughout tableview.
- """
- def __init__(self):
- self._columns_draggable = False
- self._renderer_xpad = self._renderer_ypad = 0
- self.columns = []
- self.attr_map_for_column = {}
- self.gtk_column_to_wrapper = {}
- self.create_signal('reallocate-columns') # not emitted on GTK
-
- def remove_column(self, index):
- """Remove a column from the display and forget it from the column lists.
- """
- column = self.columns.pop(index)
- del self.attr_map_for_column[column._column]
- del self.gtk_column_to_wrapper[column._column]
- self._widget.remove_column(column._column)
-
- def get_columns(self):
- """Returns the current columns, in order, by title."""
- # FIXME: this should probably return column objects, and really should
- # not be keeping track of columns by title at all
- titles = [column.get_title().decode('utf-8')
- for column in self._widget.get_columns()]
- return titles
-
- def add_column(self, column):
- """Append a column to this table; setup all necessary mappings, and
- setup the new column's properties to match the table's settings.
- """
- self.model.check_new_column(column)
- self._widget.append_column(column._column)
- self.columns.append(column)
- self.attr_map_for_column[column._column] = column.attrs
- self.gtk_column_to_wrapper[column._column] = column
- self.setup_new_column(column)
-
- def setup_new_column(self, column):
- """Apply properties that we keep track of at the table level to a
- newly-created column.
- """
- if self.background_color:
- column.renderer._renderer.set_property('cell-background-gdk',
- self.background_color)
- column._column.set_reorderable(self._columns_draggable)
- if column.do_horizontal_padding:
- column.renderer._renderer.set_property('xpad', self._renderer_xpad)
- column.renderer._renderer.set_property('ypad', self._renderer_ypad)
-
- def set_column_spacing(self, space):
- """Set the amount of space between columns."""
- self._renderer_xpad = space / 2
- for column in self.columns:
- if column.do_horizontal_padding:
- column.renderer._renderer.set_property('xpad',
- self._renderer_xpad)
-
- def set_row_spacing(self, space):
- """Set the amount of space between columns."""
- self._renderer_ypad = space / 2
- for column in self.columns:
- column.renderer._renderer.set_property('ypad', self._renderer_ypad)
-
- def set_columns_draggable(self, setting):
- """Set the draggability of existing and future columns."""
- self._columns_draggable = setting
- for column in self.columns:
- column._column.set_reorderable(setting)
-
- def set_column_background_color(self):
- """Set the background color of existing columns to the table's
- background_color.
- """
- for column in self.columns:
- column.renderer._renderer.set_property('cell-background-gdk',
- self.background_color)
-
- def set_auto_resizes(self, setting):
- # FIXME: to be implemented.
- # At this point, GTK somehow does the right thing anyway in terms of
- # auto-resizing. I'm not sure exactly what's happening, but I believe
- # that if the column widths don't add up to the total width,
- # gtk.TreeView allocates extra width for the last column. This works
- # well enough for the tab list and item list, since there's only one
- # column.
- pass
-
-class HoverTrackingMixin(object):
- """Handle mouse hover events - tooltips for some cells and hover events for
- renderers which support them.
- """
- def __init__(self):
- self.hover_info = None
- self.hover_pos = None
- if hasattr(self, 'get_tooltip'):
- # this should probably be something like self.set_tooltip_source
- self._widget.set_property('has-tooltip', True)
- self.wrapped_widget_connect('query-tooltip', self.on_tooltip)
- self._last_tooltip_place = None
-
- def on_tooltip(self, treeview, x, y, keyboard_mode, tooltip):
- # x, y are relative to the entire widget, but we want them to be
- # relative to our bin window. The bin window doesn't include things
- # like the column headers.
- origin = treeview.window.get_origin()
- bin_origin = treeview.get_bin_window().get_origin()
- x += origin[0] - bin_origin[0]
- y += origin[1] - bin_origin[1]
- path_info = treeview.get_position_info(x, y)
- if path_info is None:
- self._last_tooltip_place = None
- return False
- if (self._last_tooltip_place is not None and
- path_info[:2] != self._last_tooltip_place):
- # the default GTK behavior is to keep the tooltip in the same
- # position, but this is looks bad when we move to a different row.
- # So return False once to stop this.
- self._last_tooltip_place = None
- return False
- self._last_tooltip_place = path_info[:2]
- iter_ = treeview.get_model().get_iter(path_info.path)
- column = self.gtk_column_to_wrapper[path_info.column]
- text = self.get_tooltip(iter_, column)
- if text is None:
- return False
- pygtkhacks.set_tooltip_text(tooltip, text)
- return True
-
- def _update_hover(self, treeview, event):
- old_hover_info, old_hover_pos = self.hover_info, self.hover_pos
- path_info = treeview.get_position_info(event.x, event.y)
- if (path_info and
- self.gtk_column_to_wrapper[path_info.column].renderer.want_hover):
- self.hover_info = path_info.path, path_info.column
- self.hover_pos = path_info.x, path_info.y
- else:
- self.hover_info = None
- self.hover_pos = None
- if (old_hover_info != self.hover_info or
- old_hover_pos != self.hover_pos):
- if (old_hover_info != self.hover_info and
- old_hover_info is not None):
- self._redraw_cell(treeview, *old_hover_info)
- if self.hover_info is not None:
- self._redraw_cell(treeview, *self.hover_info)
-
-class GTKScrollbarOwnerMixin(ScrollbarOwnerMixin):
- # XXX this is half a wrapper for TreeViewScrolling. A lot of things will
- # become much simpler when we integrate TVS into this
- def __init__(self):
- ScrollbarOwnerMixin.__init__(self)
- # super uses this for postponed scroll_to_iter
- # it's a faux-signal from our _widget; this hack is only necessary until
- # we integrate TVS
- self._widget.scroll_range_changed = (lambda *a:
- self.emit('scroll-range-changed'))
-
- def set_scroller(self, scroller):
- """Set the Scroller object for this widget, if its ScrolledWindow is
- not a direct ancestor of the object. Standard View needs this.
- """
- self._widget.set_scroller(scroller._widget)
-
- def _set_scroll_position(self, scroll_pos):
- self._widget.set_scroll_position(scroll_pos)
-
- def _get_item_area(self, iter_):
- return self._widget.get_path_rect(self.get_path(iter_))
-
- @property
- def _manually_scrolled(self):
- return self._widget.manually_scrolled
-
- @property
- def _position_set(self):
- return self._widget.position_set
-
- def _get_visible_area(self):
- """Return the Rect of the visible area, in tree coords.
-
- get_visible_rect gets this wrong for StandardView, always returning an
- origin of (0, 0) - this is because our ScrolledWindow is not our direct
- parent.
- """
- bars = self._widget._scrollbars
- x, y = (int(adj.get_value()) for adj in bars)
- width, height = (int(adj.get_page_size()) for adj in bars)
- if height == 0:
- # this happens even after _widget._coords_working
- raise WidgetNotReadyError('visible height')
- return Rect(x, y, width, height)
-
- def _get_scroll_position(self):
- """Get the current position of both scrollbars, to restore later."""
- try:
- return tuple(int(bar.get_value()) for bar in self._widget._scrollbars)
- except WidgetNotReadyError:
- return None
-
-class TableView(Widget, GTKSelectionOwnerMixin, DNDHandlerMixin,
- HotspotTrackingMixin, ColumnOwnerMixin, HoverTrackingMixin,
- GTKScrollbarOwnerMixin):
- """https://develop.participatoryculture.org/index.php/WidgetAPITableView"""
-
- draws_selection = True
-
- def __init__(self, model, custom_headers=False):
- Widget.__init__(self)
- self.set_widget(MiroTreeView())
- self.model = model
- self.model.add_to_tableview(self._widget)
- self._model = self._widget.get_model()
- wrappermap.add(self._model, model)
- self._setup_colors()
- self.background_color = None
- self.context_menu_callback = None
- self.in_bulk_change = False
- self.delaying_press = False
- self._use_custom_headers = False
- self.layout_manager = LayoutManager(self._widget)
- self.height_changed = None # 17178 hack
- self._connect_signals()
- # setting up mixins after general TableView init
- GTKSelectionOwnerMixin.__init__(self)
- DNDHandlerMixin.__init__(self)
- HotspotTrackingMixin.__init__(self)
- ColumnOwnerMixin.__init__(self)
- HoverTrackingMixin.__init__(self)
- GTKScrollbarOwnerMixin.__init__(self)
- if custom_headers:
- self._enable_custom_headers()
-
- # FIXME: should implement set_model() and make None a special case.
- def unset_model(self):
- """Disconnect our model from this table view.
-
- This should be called when you want to destroy a TableView and
- there's a new TableView sharing its model.
- """
- self._widget.set_model(None)
- self.model = None
-
- def _connect_signals(self):
- self.create_signal('row-expanded')
- self.create_signal('row-collapsed')
- self.create_signal('row-clicked')
- self.create_signal('row-activated')
- self.wrapped_widget_connect('row-activated', self.on_row_activated)
- self.wrapped_widget_connect('row-expanded', self.on_row_expanded)
- self.wrapped_widget_connect('row-collapsed', self.on_row_collapsed)
- self.wrapped_widget_connect('button-press-event', self.on_button_press)
- self.wrapped_widget_connect('button-release-event',
- self.on_button_release)
- self.wrapped_widget_connect('motion-notify-event',
- self.on_motion_notify)
-
- def set_gradient_highlight(self, gradient):
- # This is just an OS X thing.
- pass
-
- def set_background_color(self, color):
- self.background_color = self.make_color(color)
- self.modify_style('base', gtk.STATE_NORMAL, self.background_color)
- if not self.draws_selection:
- self.modify_style('base', gtk.STATE_SELECTED,
- self.background_color)
- self.modify_style('base', gtk.STATE_ACTIVE, self.background_color)
- if self.use_custom_style:
- self.set_column_background_color()
-
- def set_group_lines_enabled(self, enabled):
- """Enable/Disable group lines.
-
- This only has an effect if our model is an InfoListModel and it has a
- grouping set.
-
- If group lines are enabled, we will draw a line below the last item in
- the group. Use set_group_line_style() to change the look of the line.
- """
- self._widget.group_lines_enabled = enabled
- self.queue_redraw()
-
- def set_group_line_style(self, color, width):
- self._widget.group_line_color = color
- self._widget.group_line_width = width
- self.queue_redraw()
-
- def handle_custom_style_change(self):
- if self.background_color is not None:
- if self.use_custom_style:
- self.set_column_background_color()
- else:
- for column in self.columns:
- column.renderer._renderer.set_property(
- 'cell-background-set', False)
-
- def set_alternate_row_backgrounds(self, setting):
- self._widget.set_rules_hint(setting)
-
- def set_grid_lines(self, horizontal, vertical):
- if horizontal and vertical:
- setting = gtk.TREE_VIEW_GRID_LINES_BOTH
- elif horizontal:
- setting = gtk.TREE_VIEW_GRID_LINES_HORIZONTAL
- elif vertical:
- setting = gtk.TREE_VIEW_GRID_LINES_VERTICAL
- else:
- setting = gtk.TREE_VIEW_GRID_LINES_NONE
- self._widget.set_grid_lines(setting)
-
- def width_for_columns(self, total_width):
- """Given the width allocated for the TableView, return how much of that
- is available to column contents. Note that this depends on the number of
- columns.
- """
- column_spacing = TableColumn.FIXED_PADDING * len(self.columns)
- return total_width - column_spacing
-
- def enable_album_view_focus_hack(self):
- _install_album_view_gtkrc()
- self._widget.set_name("miro-album-view")
-
- def focus(self):
- self._widget.grab_focus()
-
- def _enable_custom_headers(self):
- # NB: this is currently not used because the GTK tableview does not
- # support custom headers.
- self._use_custom_headers = True
-
- def set_show_headers(self, show):
- self._widget.set_headers_visible(show)
- self._widget.set_headers_clickable(show)
-
- def _setup_colors(self):
- style = self._widget.style
- if not self.draws_selection:
- # if we don't want to draw selection, make the selected/active
- # colors the same as the normal ones
- self.modify_style('base', gtk.STATE_SELECTED,
- style.base[gtk.STATE_NORMAL])
- self.modify_style('base', gtk.STATE_ACTIVE,
- style.base[gtk.STATE_NORMAL])
-
- def set_search_column(self, model_index):
- self._widget.set_search_column(model_index)
-
- def set_fixed_height(self, fixed_height):
- self._widget.set_fixed_height_mode(fixed_height)
-
- def set_row_expanded(self, iter_, expanded):
- """Expand or collapse the row specified by iter_. Succeeds or raises
- WidgetActionError. Causes row-expanded or row-collapsed to be emitted
- when successful.
- """
- path = self.get_path(iter_)
- if expanded:
- self._widget.expand_row(path, False)
- else:
- self._widget.collapse_row(path)
- if bool(self._widget.row_expanded(path)) != bool(expanded):
- raise WidgetActionError("cannot expand the given item - it "
- "probably has no children.")
-
- def is_row_expanded(self, iter_):
- path = self.get_path(iter_)
- return self._widget.row_expanded(path)
-
- def set_context_menu_callback(self, callback):
- self.context_menu_callback = callback
-
- # GTK is really good and it is safe to operate on table even when
- # cells may be constantly changing in flux.
- def set_volatile(self, volatile):
- return
-
- def on_row_expanded(self, _widget, iter_, path):
- self.emit('row-expanded', iter_, path)
-
- def on_row_collapsed(self, _widget, iter_, path):
- self.emit('row-collapsed', iter_, path)
-
- def on_button_press(self, treeview, event):
- """Handle a mouse button press"""
- if event.type == gtk.gdk._2BUTTON_PRESS:
- # already handled as row-activated
- return False
-
- path_info = treeview.get_position_info(event.x, event.y)
- if not path_info:
- # no item was clicked, so it's not going to be a hotspot, drag, or
- # context menu
- return False
- if event.type == gtk.gdk.BUTTON_PRESS:
- # single click; emit the event but keep on running so we can handle
- # stuff like drag and drop.
- if not self._x_coord_in_expander(treeview, path_info):
- iter_ = treeview.get_model().get_iter(path_info.path)
- self.emit('row-clicked', iter_)
-
- if (event.button == 1 and self.handle_hotspot_hit(treeview, event)):
- return True
- if event.window != treeview.get_bin_window():
- # click is outside the content area, don't try to handle this.
- # In particular, our DnD code messes up resizing table columns.
- return False
- if (event.button == 1 and self.drag_source and
- not self._x_coord_in_expander(treeview, path_info)):
- return self.start_drag(treeview, event, path_info)
- elif event.button == 3 and self.context_menu_callback:
- self.show_context_menu(treeview, event, path_info)
- return True
-
- # FALLTHROUGH
- return False
-
- def show_context_menu(self, treeview, event, path_info):
- """Pop up a context menu for the given click event (which is a
- right-click on a row).
- """
- # hack for album view
- if (treeview.group_lines_enabled and
- path_info.column == treeview.get_columns()[0]):
- self._select_all_rows_in_group(treeview, path_info.path)
- self._popup_context_menu(path_info.path, event)
- # grab keyboard focus since we handled the event
- self.focus()
-
- def _select_all_rows_in_group(self, treeview, path):
- """Select all items in the group """
-
- # FIXME: this is very tightly coupled with the portable code.
-
- infolist = self.model
- gtk_model = treeview.get_model()
- if (not isinstance(infolist, InfoListModel) or
- infolist.get_grouping() is None):
- return
- it = gtk_model.get_iter(path)
- info, attrs, group_info = infolist.row_for_iter(it)
- start_row = path[0] - group_info[0]
- total_rows = group_info[1]
-
- with self._ignoring_changes():
- self.unselect_all()
- for row in xrange(start_row, start_row + total_rows):
- self.select_path((row,))
- self.emit('selection-changed')
-
- def _popup_context_menu(self, path, event):
- if not self.selection.path_is_selected(path):
- self.unselect_all(signal=False)
- self.select_path(path)
- menu = self.make_context_menu()
- if menu:
- menu.popup(None, None, None, event.button, event.time)
- return menu
- else:
- return None
-
- # XXX treeview.get_cell_area handles what we're trying to use this for
- def _x_coord_in_expander(self, treeview, path_info):
- """Calculate if an x coordinate is over the expander triangle
-
- :param treeview: Gtk.TreeView
- :param path_info: PathInfo(
- tree path for the cell,
- Gtk.TreeColumn,
- x coordinate relative to column's cell area,
- y coordinate relative to column's cell area (ignored),
- )
- """
- if path_info.column != treeview.get_expander_column():
- return False
- model = treeview.get_model()
- if not model.iter_has_child(model.get_iter(path_info.path)):
- return False
- # GTK allocateds an extra 4px to the right of the expanders. This
- # seems to be hardcoded as EXPANDER_EXTRA_PADDING in the source code.
- total_exander_size = treeview.expander_size + 4
- # include horizontal_separator
- # XXX: should this value be included in total_exander_size ?
- offset = treeview.horizontal_separator / 2
- # allocate space for expanders for parent nodes
- expander_start = total_exander_size * (len(path_info.path) - 1) + offset
- expander_end = expander_start + total_exander_size + offset
- return expander_start <= path_info.x < expander_end
-
- def on_row_activated(self, treeview, path, view_column):
- iter_ = treeview.get_model().get_iter(path)
- self.emit('row-activated', iter_)
-
- def make_context_menu(self):
- def gen_menu(menu_items):
- menu = gtk.Menu()
- for menu_item_info in menu_items:
- if menu_item_info is None:
- item = gtk.SeparatorMenuItem()
- else:
- label, callback = menu_item_info
-
- if isinstance(label, tuple) and len(label) == 2:
- text_label, icon_path = label
- pixbuf = gtk.gdk.pixbuf_new_from_file(icon_path)
- image = gtk.Image()
- image.set_from_pixbuf(pixbuf)
- item = gtk.ImageMenuItem(text_label)
- item.set_image(image)
- else:
- item = gtk.MenuItem(label)
-
- if callback is None:
- item.set_sensitive(False)
- elif isinstance(callback, list):
- item.set_submenu(gen_menu(callback))
- else:
- item.connect('activate', self.on_context_menu_activate,
- callback)
- menu.append(item)
- item.show()
- return menu
-
- items = self.context_menu_callback(self)
- if items:
- return gen_menu(items)
- else:
- return None
-
- def on_context_menu_activate(self, item, callback):
- callback()
-
- def on_button_release(self, treeview, event):
- if self.release_on_hotspot(event):
- return True
- if event.button == 1:
- self.drag_button_down = False
-
- if self.delaying_press:
- # if dragging did not happen, unselect other rows and
- # select current row
- path_info = treeview.get_position_info(event.x, event.y)
- if path_info is not None:
- self.unselect_all(signal=False)
- self.select_path(path_info.path)
- self.delaying_press = False
-
- def _redraw_cell(self, treeview, path, column):
- cell_area = treeview.get_cell_area(path, column)
- x, y = treeview.convert_bin_window_to_widget_coords(cell_area.x,
- cell_area.y)
- treeview.queue_draw_area(x, y, cell_area.width, cell_area.height)
-
- def on_motion_notify(self, treeview, event):
- self._update_hover(treeview, event)
-
- if self.hotspot_tracker:
- self.hotspot_tracker.update_position(event)
- self.hotspot_tracker.update_hit()
- return True
-
- self.potential_drag_motion(treeview, event)
- return None # XXX: used to fall through; not sure what retval does here
-
- def start_bulk_change(self):
- self._widget.freeze_child_notify()
- self._widget.set_model(None)
- self._disconnect_hotspot_signals()
- self.in_bulk_change = True
-
- def model_changed(self):
- if self.in_bulk_change:
- self._widget.set_model(self._model)
- self._widget.thaw_child_notify()
- self.hotspot_model_changed()
- self.in_bulk_change = False
-
- def get_path(self, iter_):
- """Always use this rather than the model's get_path directly -
- if the iter isn't valid, a GTK assertion causes us to exit
- without warning; this wrapper changes that to a much more useful
- AssertionError. Example related bug: #17362.
- """
- assert self.model.iter_is_valid(iter_)
- return self._model.get_path(iter_)
-
-class TableModel(object):
- """https://develop.participatoryculture.org/index.php/WidgetAPITableView"""
- MODEL_CLASS = gtk.ListStore
-
- def __init__(self, *column_types):
- self._model = self.MODEL_CLASS(*self.map_types(column_types))
- self._column_types = column_types
- if 'image' in self._column_types:
- self.convert_row_for_gtk = self.convert_row_for_gtk_slow
- self.convert_value_for_gtk = self.convert_value_for_gtk_slow
- else:
- self.convert_row_for_gtk = self.convert_row_for_gtk_fast
- self.convert_value_for_gtk = self.convert_value_for_gtk_fast
-
- def add_to_tableview(self, widget):
- widget.set_model(self._model)
-
- def map_types(self, miro_column_types):
- type_map = {
- 'boolean': bool,
- 'numeric': float,
- 'integer': int,
- 'text': str,
- 'image': gtk.gdk.Pixbuf,
- 'datetime': object,
- 'object': object,
- }
- try:
- return [type_map[type] for type in miro_column_types]
- except KeyError, e:
- raise ValueError("Unknown column type: %s" % e[0])
-
- # If we store image data, we need to do some work to convert row data to
- # send to GTK
- def convert_value_for_gtk_slow(self, column_value):
- if isinstance(column_value, Image):
- return column_value.pixbuf
- else:
- return column_value
-
- def convert_row_for_gtk_slow(self, column_values):
- return tuple(self.convert_value_for_gtk(c) for c in column_values)
-
- def check_new_column(self, column):
- for value in column.attrs.values():
- if not isinstance(value, int):
- msg = "Attribute values must be integers, not %r" % value
- raise TypeError(msg)
- if value < 0 or value >= len(self._column_types):
- raise ValueError("Attribute index out of range: %s" % value)
-
- # If we don't store image data, we can don't need to do any work to
- # convert row data to gtk
- def convert_value_for_gtk_fast(self, value):
- return value
-
- def convert_row_for_gtk_fast(self, column_values):
- return column_values
-
- def append(self, *column_values):
- return self._model.append(self.convert_row_for_gtk(column_values))
-
- def update_value(self, iter_, index, value):
- assert self._model.iter_is_valid(iter_)
- self._model.set(iter_, index, self.convert_value_for_gtk(value))
-
- def update(self, iter_, *column_values):
- self._model[iter_] = self.convert_value_for_gtk(column_values)
-
- def remove(self, iter_):
- if self._model.remove(iter_):
- return iter_
- else:
- return None
-
- def insert_before(self, iter_, *column_values):
- row = self.convert_row_for_gtk(column_values)
- return self._model.insert_before(iter_, row)
-
- def first_iter(self):
- return self._model.get_iter_first()
-
- def next_iter(self, iter_):
- return self._model.iter_next(iter_)
-
- def nth_iter(self, index):
- assert index >= 0
- return self._model.iter_nth_child(None, index)
-
- def __iter__(self):
- return iter(self._model)
-
- def __len__(self):
- return len(self._model)
-
- def __getitem__(self, iter_):
- return self._model[iter_]
-
- def get_rows(self, row_paths):
- return [self._model[path] for path in row_paths]
-
- def get_path(self, iter_):
- return self._model.get_path(iter_)
-
- def iter_is_valid(self, iter_):
- return self._model.iter_is_valid(iter_)
-
-class TreeTableModel(TableModel):
- """https://develop.participatoryculture.org/index.php/WidgetAPITableView"""
- MODEL_CLASS = gtk.TreeStore
-
- def append(self, *column_values):
- return self._model.append(None, self.convert_row_for_gtk(
- column_values))
-
- def insert_before(self, iter_, *column_values):
- parent = self.parent_iter(iter_)
- row = self.convert_row_for_gtk(column_values)
- return self._model.insert_before(parent, iter_, row)
-
- def append_child(self, iter_, *column_values):
- return self._model.append(iter_, self.convert_row_for_gtk(
- column_values))
-
- def child_iter(self, iter_):
- return self._model.iter_children(iter_)
-
- def nth_child_iter(self, iter_, index):
- assert index >= 0
- return self._model.iter_nth_child(iter_, index)
-
- def has_child(self, iter_):
- return self._model.iter_has_child(iter_)
-
- def children_count(self, iter_):
- return self._model.iter_n_children(iter_)
-
- def parent_iter(self, iter_):
- assert self._model.iter_is_valid(iter_)
- return self._model.iter_parent(iter_)
diff --git a/mvc/widgets/gtk/tableviewcells.py b/mvc/widgets/gtk/tableviewcells.py
deleted file mode 100644
index 33ac6f8..0000000
--- a/mvc/widgets/gtk/tableviewcells.py
+++ /dev/null
@@ -1,249 +0,0 @@
-# @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.
-
-"""tableviewcells.py - Cell renderers for TableView."""
-
-import gobject
-import gtk
-import pango
-
-from mvc import signals
-from mvc.widgets import widgetconst
-import drawing
-import wrappermap
-from .base import make_gdk_color
-
-class CellRenderer(object):
- """Simple Cell Renderer
- https://develop.participatoryculture.org/index.php/WidgetAPITableView"""
- def __init__(self):
- self._renderer = gtk.CellRendererText()
- self.want_hover = False
-
- def setup_attributes(self, column, attr_map):
- column.add_attribute(self._renderer, 'text', attr_map['value'])
-
- def set_align(self, align):
- if align == 'left':
- self._renderer.props.xalign = 0.0
- elif align == 'center':
- self._renderer.props.xalign = 0.5
- elif align == 'right':
- self._renderer.props.xalign = 1.0
- else:
- raise ValueError("unknown alignment: %s" % align)
-
- def set_color(self, color):
- self._renderer.props.foreground_gdk = make_gdk_color(color)
-
- def set_bold(self, bold):
- font_desc = self._renderer.props.font_desc
- if bold:
- font_desc.set_weight(pango.WEIGHT_BOLD)
- else:
- font_desc.set_weight(pango.WEIGHT_NORMAL)
- self._renderer.props.font_desc = font_desc
-
- def set_text_size(self, size):
- if size == widgetconst.SIZE_NORMAL:
- self._renderer.props.scale = 1.0
- elif size == widgetconst.SIZE_SMALL:
- # FIXME: on 3.5 we just ignored the call. Always setting scale to
- # 1.0 basically replicates that behavior, but should we actually
- # try to implement the semantics of SIZE_SMALL?
- self._renderer.props.scale = 1.0
- else:
- raise ValueError("unknown size: %s" % size)
-
- def set_font_scale(self, scale_factor):
- self._renderer.props.scale = scale_factor
-
-class ImageCellRenderer(object):
- """Cell Renderer for images
- https://develop.participatoryculture.org/index.php/WidgetAPITableView"""
- def __init__(self):
- self._renderer = gtk.CellRendererPixbuf()
- self.want_hover = False
-
- def setup_attributes(self, column, attr_map):
- column.add_attribute(self._renderer, 'pixbuf', attr_map['image'])
-
-class GTKCheckboxCellRenderer(gtk.CellRendererToggle):
- def do_activate(self, event, treeview, path, background_area, cell_area,
- flags):
- iter = treeview.get_model().get_iter(path)
- self.set_active(not self.get_active())
- wrappermap.wrapper(self).emit('clicked', iter)
-
-gobject.type_register(GTKCheckboxCellRenderer)
-
-class CheckboxCellRenderer(signals.SignalEmitter):
- """Cell Renderer for booleans
- https://develop.participatoryculture.org/index.php/WidgetAPITableView"""
- def __init__(self):
- signals.SignalEmitter.__init__(self)
- self.create_signal("clicked")
- self._renderer = GTKCheckboxCellRenderer()
- wrappermap.add(self._renderer, self)
- self.want_hover = False
-
- def set_control_size(self, size):
- pass
-
- def setup_attributes(self, column, attr_map):
- column.add_attribute(self._renderer, 'active', attr_map['value'])
-
-class GTKCustomCellRenderer(gtk.GenericCellRenderer):
- """Handles the GTK hide of CustomCellRenderer
- https://develop.participatoryculture.org/index.php/WidgetAPITableView"""
-
- def on_get_size(self, widget, cell_area=None):
- wrapper = wrappermap.wrapper(self)
- widget_wrapper = wrappermap.wrapper(widget)
- style = drawing.DrawingStyle(widget_wrapper, use_base_color=True)
- # NOTE: CustomCellRenderer.cell_data_func() sets up its attributes
- # from the model itself, so we don't have to worry about setting them
- # here.
- width, height = wrapper.get_size(style, widget_wrapper.layout_manager)
- x_offset = self.props.xpad
- y_offset = self.props.ypad
- width += self.props.xpad * 2
- height += self.props.ypad * 2
- if cell_area:
- x_offset += cell_area.x
- y_offset += cell_area.x
- extra_width = max(0, cell_area.width - width)
- extra_height = max(0, cell_area.height - height)
- x_offset += int(round(self.props.xalign * extra_width))
- y_offset += int(round(self.props.yalign * extra_height))
- return x_offset, y_offset, width, height
-
- def on_render(self, window, widget, background_area, cell_area, expose_area,
- flags):
- widget_wrapper = wrappermap.wrapper(widget)
- cell_wrapper = wrappermap.wrapper(self)
-
- selected = (flags & gtk.CELL_RENDERER_SELECTED)
- if selected:
- if widget.flags() & gtk.HAS_FOCUS:
- state = gtk.STATE_SELECTED
- else:
- state = gtk.STATE_ACTIVE
- else:
- state = gtk.STATE_NORMAL
- if cell_wrapper.IGNORE_PADDING:
- area = background_area
- else:
- xpad = self.props.xpad
- ypad = self.props.ypad
- area = gtk.gdk.Rectangle(cell_area.x + xpad, cell_area.y + ypad,
- cell_area.width - xpad * 2, cell_area.height - ypad * 2)
- context = drawing.DrawingContext(window, area, expose_area)
- if (selected and not widget_wrapper.draws_selection and
- widget_wrapper.use_custom_style):
- # Draw the base color as our background. This erases the gradient
- # that GTK draws for selected items.
- window.draw_rectangle(widget.style.base_gc[state], True,
- background_area.x, background_area.y,
- background_area.width, background_area.height)
- context.style = drawing.DrawingStyle(widget_wrapper,
- use_base_color=True, state=state)
- widget_wrapper.layout_manager.update_cairo_context(context.context)
- hotspot_tracker = widget_wrapper.hotspot_tracker
- if (hotspot_tracker and hotspot_tracker.hit and
- hotspot_tracker.column == self.column and
- hotspot_tracker.path == self.path):
- hotspot = hotspot_tracker.name
- else:
- hotspot = None
- if (self.path, self.column) == widget_wrapper.hover_info:
- hover = widget_wrapper.hover_pos
- hover = (hover[0] - xpad, hover[1] - ypad)
- else:
- hover = None
- # NOTE: CustomCellRenderer.cell_data_func() sets up its attributes
- # from the model itself, so we don't have to worry about setting them
- # here.
- widget_wrapper.layout_manager.reset()
- cell_wrapper.render(context, widget_wrapper.layout_manager, selected,
- hotspot, hover)
-
- def on_activate(self, event, widget, path, background_area, cell_area,
- flags):
- pass
-
- def on_start_editing(self, event, widget, path, background_area,
- cell_area, flags):
- pass
-gobject.type_register(GTKCustomCellRenderer)
-
-class CustomCellRenderer(object):
- """Customizable Cell Renderer
- https://develop.participatoryculture.org/index.php/WidgetAPITableView"""
-
- IGNORE_PADDING = False
-
- def __init__(self):
- self._renderer = GTKCustomCellRenderer()
- self.want_hover = False
- wrappermap.add(self._renderer, self)
-
- def setup_attributes(self, column, attr_map):
- column.set_cell_data_func(self._renderer, self.cell_data_func,
- attr_map)
-
- def cell_data_func(self, column, cell, model, iter, attr_map):
- cell.column = column
- cell.path = model.get_path(iter)
- row = model[iter]
- # Set attributes on self instead cell This works because cell is just
- # going to turn around and call our methods to do the rendering.
- for name, index in attr_map.items():
- setattr(self, name, row[index])
-
- def hotspot_test(self, style, layout, x, y, width, height):
- return None
-
-class InfoListRenderer(CustomCellRenderer):
- """Custom Renderer for InfoListModels
- https://develop.participatoryculture.org/index.php/WidgetAPITableView"""
-
- def cell_data_func(self, column, cell, model, iter, attr_map):
- self.info, self.attrs, self.group_info = \
- wrappermap.wrapper(model).row_for_iter(iter)
- cell.column = column
- cell.path = model.get_path(iter)
-
-class InfoListRendererText(CellRenderer):
- """Renderer for InfoListModels that only display text
- https://develop.participatoryculture.org/index.php/WidgetAPITableView"""
-
- def setup_attributes(self, column, attr_map):
- infolist.gtk.setup_text_cell_data_func(column, self._renderer,
- self.get_value)
diff --git a/mvc/widgets/gtk/weakconnect.py b/mvc/widgets/gtk/weakconnect.py
deleted file mode 100644
index b8b9526..0000000
--- a/mvc/widgets/gtk/weakconnect.py
+++ /dev/null
@@ -1,56 +0,0 @@
-# @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.
-
-"""weakconnect.py -- Connect to a signal of a GObject using a weak method
-reference. This means that this connection will not keep the object alive.
-This is a good thing because it prevents circular references between wrapper
-widgets and the wrapped GTK widget.
-"""
-
-from mvc import signals
-
-class WeakSignalHandler(object):
- def __init__(self, method):
- self.method = signals.WeakMethodReference(method)
-
- def connect(self, obj, signal, *user_args):
- self.user_args = user_args
- self.signal_handle = obj.connect(signal, self.handle_callback)
- return self.signal_handle
-
- def handle_callback(self, obj, *args):
- real_method = self.method()
- if real_method is not None:
- return real_method(obj, *(args + self.user_args))
- else:
- obj.disconnect(self.signal_handle)
-
-def weak_connect(gobject, signal, method, *user_args):
- handler = WeakSignalHandler(method)
- return handler.connect(gobject, signal, *user_args)
diff --git a/mvc/widgets/gtk/widgets.py b/mvc/widgets/gtk/widgets.py
deleted file mode 100644
index 6c4280d..0000000
--- a/mvc/widgets/gtk/widgets.py
+++ /dev/null
@@ -1,47 +0,0 @@
-# @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.
-
-""".widgets -- Contains portable implementations of
-the GTK Widgets. These are shared between the windows port and the x11 port.
-"""
-
-import gtk
-
-# Just use the GDK Rectangle class
-class Rect(gtk.gdk.Rectangle):
- @classmethod
- def from_string(cls, rect_string):
- x, y, width, height = [int(i) for i in rect_string.split(',')]
- return Rect(x, y, width, height)
-
- def __str__(self):
- return "%d,%d,%d,%d" % (self.x, self.y, self.width, self.height)
-
- def get_width(self):
- return self.width
diff --git a/mvc/widgets/gtk/widgetset.py b/mvc/widgets/gtk/widgetset.py
deleted file mode 100644
index c63855c..0000000
--- a/mvc/widgets/gtk/widgetset.py
+++ /dev/null
@@ -1,63 +0,0 @@
-# @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.
-
-from .base import Widget, Bin
-from .const import *
-from .controls import TextEntry, NumberEntry, \
- SecureTextEntry, MultilineTextEntry, Checkbox, RadioButton, \
- RadioButtonGroup, OptionMenu, Button
-from .customcontrols import (
- CustomButton, DragableCustomButton, CustomSlider,
- ClickableImageButton)
-# VolumeSlider and VolumeMuter aren't defined if gtk.VolumeButton
-# doesn't have get_popup.
-try:
- from .customcontrols import (
- VolumeSlider, VolumeMuter)
-except ImportError:
- pass
-from .contextmenu import ContextMenu
-from .drawing import ImageSurface, DrawingContext, \
- DrawingArea, Background, Gradient
-from .layout import HBox, VBox, Alignment, \
- Splitter, Table, TabContainer, DetachedWindowHolder
-from .window import Window, MainWindow, Dialog, \
- FileOpenDialog, FileSaveDialog, DirectorySelectDialog, AboutDialog, \
- AlertDialog, DialogWindow
-from .tableview import (TableView, TableModel,
- TableColumn, TreeTableModel, CUSTOM_HEADER_HEIGHT)
-from .tableviewcells import (CellRenderer,
- ImageCellRenderer, CheckboxCellRenderer, CustomCellRenderer,
- InfoListRenderer, InfoListRendererText)
-from .simple import (Image, ImageDisplay,
- AnimatedImageDisplay, Label, Scroller, Expander, SolidBackground,
- ProgressBar, HLine)
-from .widgets import Rect
-from .gtkmenus import (MenuItem, RadioMenuItem, CheckMenuItem, Separator,
- Menu, MenuBar, MainWindowMenuBar)
diff --git a/mvc/widgets/gtk/window.py b/mvc/widgets/gtk/window.py
deleted file mode 100644
index 3859a1a..0000000
--- a/mvc/widgets/gtk/window.py
+++ /dev/null
@@ -1,708 +0,0 @@
-# @Base: Miro - an RSS based video player application
-# Copyright (C) 2005, 2006, 2007, 2008, 2009, 2010, 2011
-# Jesus Eduardo (Heckyel) | 2017
-#
-# 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.
-
-""".window -- GTK Window widget."""
-
-import gobject
-import gtk
-import os
-
-from mvc import resources
-from mvc import signals
-
-import keymap
-import layout
-import widgets
-import wrappermap
-
-# keeps the objects alive until destroy() is called
-alive_windows = set()
-running_dialogs = set()
-
-class WrappedWindow(gtk.Window):
- def do_map(self):
- gtk.Window.do_map(self)
- wrappermap.wrapper(self).emit('show')
-
- def do_unmap(self):
- gtk.Window.do_unmap(self)
- wrappermap.wrapper(self).emit('hide')
- def do_focus_in_event(self, event):
- gtk.Window.do_focus_in_event(self, event)
- wrappermap.wrapper(self).emit('active-change')
- def do_focus_out_event(self, event):
- gtk.Window.do_focus_out_event(self, event)
- wrappermap.wrapper(self).emit('active-change')
-
- def do_key_press_event(self, event):
- if self.activate_key(event): # event activated a menu item
- return
-
- if self.propagate_key_event(event): # event handled by widget
- return
-
- ret = keymap.translate_gtk_event(event)
- if ret is not None:
- key, modifiers = ret
- rv = wrappermap.wrapper(self).emit('key-press', key, modifiers)
- if not rv:
- gtk.Window.do_key_press_event(self, event)
-
- def _get_focused_wrapper(self):
- """Get the wrapper of the widget with keyboard focus"""
- focused = self.get_focus()
- # some of our widgets created children for their use
- # (GtkSearchTextEntry). If we don't find a wrapper for
- # focused, try it's parents
- while focused is not None:
- try:
- wrapper = wrappermap.wrapper(focused)
- except KeyError:
- focused = focused.get_parent()
- else:
- return wrapper
- return None
-
- def change_focus_using_wrapper(self, direction):
- my_wrapper = wrappermap.wrapper(self)
- focused_wrapper = self._get_focused_wrapper()
- if direction == gtk.DIR_TAB_FORWARD:
- to_focus = my_wrapper.get_next_tab_focus(focused_wrapper, True)
- elif direction == gtk.DIR_TAB_BACKWARD:
- to_focus = my_wrapper.get_next_tab_focus(focused_wrapper, False)
- else:
- return False
- if to_focus is not None:
- to_focus.focus()
- return True
- return False
-
- def do_focus(self, direction):
- if not self.change_focus_using_wrapper(direction):
- gtk.Window.do_focus(self, direction)
-
-gobject.type_register(WrappedWindow)
-
-class WindowBase(signals.SignalEmitter):
- def __init__(self):
- signals.SignalEmitter.__init__(self)
- self.create_signal('use-custom-style-changed')
- self.create_signal('key-press')
- self.create_signal('show')
- self.create_signal('hide')
-
- def set_window(self, window):
- self._window = window
- window.connect('style-set', self.on_style_set)
- wrappermap.add(window, self)
- self.calc_use_custom_style()
-
- def on_style_set(self, widget, old_style):
- old_use_custom_style = self.use_custom_style
- self.calc_use_custom_style()
- if old_use_custom_style != self.use_custom_style:
- self.emit('use-custom-style-changed')
-
- def calc_use_custom_style(self):
- if self._window is not None:
- base = self._window.style.base[gtk.STATE_NORMAL]
- # Decide if we should use a custom style. Right now the
- # formula is the base color is a very light shade of
- # gray/white (lighter than #f0f0f0).
- self.use_custom_style = ((base.red == base.green == base.blue) and
- base.red >= 61680)
-
-
-class Window(WindowBase):
- """The main Libre window. """
-
- def __init__(self, title, rect=None):
- """Create the Libre Main Window. Title is the name to give the
- window, rect specifies the position it should have on screen.
- """
- WindowBase.__init__(self)
- self.set_window(self._make_gtk_window())
- self._window.set_title(title)
- self.setup_icon()
- if rect:
- self._window.set_default_size(rect.width, rect.height)
- self._window.set_default_size(rect.width, rect.height)
- self._window.set_gravity(gtk.gdk.GRAVITY_CENTER)
- self._window.move(rect.x, rect.y)
-
- self.create_signal('active-change')
- self.create_signal('will-close')
- self.create_signal('did-move')
- self.create_signal('file-drag-motion')
- self.create_signal('file-drag-received')
- self.create_signal('file-drag-leave')
- self.create_signal('on-shown')
- self.drag_signals = []
- alive_windows.add(self)
-
- self._window.connect('delete-event', self.on_delete_window)
- self._window.connect('map-event', lambda w, a: self.emit('on-shown'))
- # XXX: Define MVCWindow/MiroWindow style not hard code this
- self._window.set_resizable(False)
-
- def setup_icon(self):
- icon_pixbuf = gtk.gdk.pixbuf_new_from_file(
- resources.image_path("mvc-logo.png"))
- self._window.set_icon(icon_pixbuf)
-
-
- def accept_file_drag(self, val):
- if not val:
- self._window.drag_dest_set(0, [], 0)
- for handle in self.drag_signals:
- self.disconnect(handle)
- self.drag_signals = []
- else:
- self._window.drag_dest_set(
- gtk.DEST_DEFAULT_MOTION | gtk.DEST_DEFAULT_DROP,
- [('text/uri-list', 0, 0)],
- gtk.gdk.ACTION_COPY)
- for signal, callback in (
- ('drag-motion', self.on_drag_motion),
- ('drag-data-received', self.on_drag_data_received),
- ('drag-leave', self.on_drag_leave)):
- self.drag_signals.append(
- self._window.connect(signal, callback))
-
- def on_drag_motion(self, widget, context, x, y, time):
- self.emit('file-drag-motion')
-
- def on_drag_data_received(self, widget, context, x, y, selection_data,
- info, time):
- self.emit('file-drag-received', selection_data.get_uris())
-
- def on_drag_leave(self, widget, context, time):
- self.emit('file-drag-leave')
-
- def on_delete_window(self, widget, event):
- # when the user clicks on the X in the corner of the window we
- # want that to close the window, but also trigger our
- # will-close signal and all that machinery unless the window
- # is currently hidden--then we don't do anything.
- if not self._window.window.is_visible():
- return
- self.close()
- return True
-
- def _make_gtk_window(self):
- return WrappedWindow()
-
- def set_title(self, title):
- self._window.set_title(title)
-
- def get_title(self):
- self._window.get_title()
-
- def center(self):
- self._window.set_position(gtk.WIN_POS_CENTER)
-
- def show(self):
- if self not in alive_windows:
- raise ValueError("Window destroyed")
- self._window.show()
-
- def close(self):
- if hasattr(self, "_closing"):
- return
- self._closing = True
- # Keep a reference to the widget in case will-close signal handler
- # calls destroy()
- old_window = self._window
- self.emit('will-close')
- old_window.hide()
- del self._closing
-
- def destroy(self):
- self.close()
- self._window = None
- alive_windows.discard(self)
-
- def is_active(self):
- return self._window.is_active()
-
- def is_visible(self):
- return self._window.props.visible
-
- def get_next_tab_focus(self, current, is_forward):
- return None
-
- def set_content_widget(self, widget):
- """Set the widget that will be drawn in the content area for this
- window.
-
- It will be allocated the entire area of the widget, except the
- space needed for the titlebar, frame and other decorations.
- When the window is resized, content should also be resized.
- """
- self._add_content_widget(widget)
- widget._widget.show()
- self.content_widget = widget
-
- def _add_content_widget(self, widget):
- self._window.add(widget._widget)
-
- def get_content_widget(self, widget):
- """Get the current content widget."""
- return self.content_widget
-
- def get_frame(self):
- pos = self._window.get_position()
- size = self._window.get_size()
- return widgets.Rect(pos[0], pos[1], size[0], size[1])
-
- def set_frame(self, x=None, y=None, width=None, height=None):
- if x is not None or y is not None:
- pos = self._window.get_position()
- x = x if x is not None else pos[0]
- y = y if y is not None else pos[1]
- self._window.move(x, y)
-
- if width is not None or height is not None:
- size = self._window.get_size()
- width = width if width is not None else size[0]
- height = height if height is not None else size[1]
- self._window.resize(width, height)
-
- def get_monitor_geometry(self):
- """Returns a Rect of the geometry of the monitor that this
- window is currently on.
-
- :returns: Rect
- """
- gtkwindow = self._window
- gdkwindow = gtkwindow.window
- screen = gtkwindow.get_screen()
-
- monitor = screen.get_monitor_at_window(gdkwindow)
- return screen.get_monitor_geometry(monitor)
-
- def check_position_and_fix(self):
- """This pulls the geometry of the monitor of the screen this
- window is on as well as the position of the window.
-
- It then makes sure that the position y is greater than the
- monitor geometry y. This makes sure that the titlebar of
- the window is showing.
- """
- gtkwindow = self._window
- gdkwindow = gtkwindow.window
- monitor_geom = self.get_monitor_geometry()
-
- frame_extents = gdkwindow.get_frame_extents()
- position = gtkwindow.get_position()
-
- # if the frame is not visible, then we move the window so that
- # it is
- if frame_extents.y < monitor_geom.y:
- gtkwindow.move(position[0],
- monitor_geom.y + (position[1] - frame_extents.y))
-
-
-
-class DialogWindow(Window):
- def __init__(self, title, rect=None):
- Window.__init__(self, title, rect)
- self._window.set_resizable(False)
-
-class MainWindow(Window):
- def __init__(self, title, rect):
- Window.__init__(self, title, rect)
- self.vbox = gtk.VBox()
- self._window.add(self.vbox)
- self.vbox.show()
- self._add_app_menubar()
- self.create_signal('save-dimensions')
- self.create_signal('save-maximized')
- self._window.connect('key-release-event', self.on_key_release)
- self._window.connect('window-state-event', self.on_window_state_event)
- self._window.connect('configure-event', self.on_configure_event)
-
- def _make_gtk_window(self):
- return WrappedWindow()
-
- def on_delete_window(self, widget, event):
- return True
-
- def on_configure_event(self, widget, event):
- (x, y) = self._window.get_position()
- (width, height) = self._window.get_size()
- self.emit('save-dimensions', x, y, width, height)
-
- def on_window_state_event(self, widget, event):
- maximized = bool(
- event.new_window_state & gtk.gdk.WINDOW_STATE_MAXIMIZED)
- self.emit('save-maximized', maximized)
-
- def on_key_release(self, widget, event):
- if app.playback_manager.is_playing:
- if gtk.gdk.keyval_name(event.keyval) in ('Right', 'Left',
- 'Up', 'Down'):
- return True
-
- def _add_app_menubar(self):
- self.menubar = app.widgetapp.menubar
- self.vbox.pack_start(self.menubar._widget, expand=False)
- self.connect_menu_keyboard_shortcuts()
-
- def _add_content_widget(self, widget):
- self.vbox.pack_start(widget._widget, expand=True)
-
-
-class DialogBase(WindowBase):
- def set_transient_for(self, window):
- self._window.set_transient_for(window._window)
-
- def run(self):
- running_dialogs.add(self)
- try:
- return self._run()
- finally:
- running_dialogs.remove(self)
- self._window = None
-
- def _run(self):
- """Run the dialog. Must be implemented by subclasses."""
- raise NotImplementedError()
-
- def destroy(self):
- if self._window is not None:
- self._window.response(gtk.RESPONSE_NONE)
- # don't set self._window to None yet. We will unset it when we
- # return from the _run() method
-
-class Dialog(DialogBase):
- def __init__(self, title, description=None):
- """Create a dialog."""
- DialogBase.__init__(self)
- self.create_signal('open')
- self.create_signal('close')
- self.set_window(gtk.Dialog(title))
- self._window.set_default_size(425, -1)
- self.extra_widget = None
- self.buttons_to_add = []
- wrappermap.add(self._window, self)
- self.description = description
-
- def build_content(self):
- packing_vbox = layout.VBox(spacing=20)
- packing_vbox._widget.set_border_width(6)
- if self.description is not None:
- label = gtk.Label(self.description)
- label.set_line_wrap(True)
- label.set_size_request(390, -1)
- label.set_selectable(True)
- packing_vbox._widget.pack_start(label)
- if self.extra_widget:
- packing_vbox._widget.pack_start(self.extra_widget._widget)
- return packing_vbox
-
- def add_button(self, text):
- from mvc.widgets import dialogs
- _stock = {
- dialogs.BUTTON_OK.text: gtk.STOCK_OK,
- dialogs.BUTTON_CANCEL.text: gtk.STOCK_CANCEL,
- dialogs.BUTTON_YES.text: gtk.STOCK_YES,
- dialogs.BUTTON_NO.text: gtk.STOCK_NO,
- dialogs.BUTTON_QUIT.text: gtk.STOCK_QUIT,
- dialogs.BUTTON_REMOVE.text: gtk.STOCK_REMOVE,
- dialogs.BUTTON_DELETE.text: gtk.STOCK_DELETE,
- }
- if text in _stock:
- # store both the text and the stock ID
- text = _stock[text], text
- self.buttons_to_add.append(text)
-
- def pack_buttons(self):
- # There's a couple tricky things here:
- # 1) We need to add them in the reversed order we got them, since GTK
- # lays them out left-to-right
- #
- # 2) We can't use 0 as a response-id. GTK only reserves positive
- # response_ids for the user.
- response_id = len(self.buttons_to_add)
- for text in reversed(self.buttons_to_add):
- label = None
- if isinstance(text, tuple): # stock ID, text
- text, label = text
- button = self._window.add_button(text, response_id)
- if label is not None:
- button.set_label(label)
- response_id -= 1
- self.buttons_to_add = []
- self._window.set_default_response(1)
-
- def _run(self):
- self.pack_buttons()
- packing_vbox = self.build_content()
- self._window.vbox.pack_start(packing_vbox._widget, True, True)
- self._window.show_all()
- response = self._window.run()
- self._window.hide()
- if response == gtk.RESPONSE_DELETE_EVENT:
- return -1
- else:
- return response - 1 # response IDs started at 1
-
- def set_extra_widget(self, widget):
- self.extra_widget = widget
-
- def get_extra_widget(self):
- return self.extra_widget
-
-class FileDialogBase(DialogBase):
- def _run(self):
- ret = self._window.run()
- self._window.hide()
- if ret == gtk.RESPONSE_OK:
- self._files = self._window.get_filenames()
- return 0
-
-class FileOpenDialog(FileDialogBase):
- def __init__(self, title):
- FileDialogBase.__init__(self)
- self._files = None
- fcd = gtk.FileChooserDialog(title,
- action=gtk.FILE_CHOOSER_ACTION_OPEN,
- buttons=(gtk.STOCK_CANCEL,
- gtk.RESPONSE_CANCEL,
- gtk.STOCK_OPEN,
- gtk.RESPONSE_OK))
-
- self.set_window(fcd)
-
- def set_filename(self, text):
- self._window.set_filename(text)
-
- def set_select_multiple(self, value):
- self._window.set_select_multiple(value)
-
- def add_filters(self, filters):
- for name, ext_list in filters:
- f = gtk.FileFilter()
- f.set_name(name)
- for mem in ext_list:
- f.add_pattern('*.%s' % mem)
- self._window.add_filter(f)
-
- f = gtk.FileFilter()
- f.set_name(_('All files'))
- f.add_pattern('*')
- self._window.add_filter(f)
-
- def get_filenames(self):
- return [unicode(f) for f in self._files]
-
- def get_filename(self):
- if self._files is None:
- # clicked Cancel
- return None
- else:
- return unicode(self._files[0])
-
- # provide a common interface for file chooser dialogs
- get_path = get_filename
- def set_path(self, path):
- # set_filename puts the whole path in the filename field
- self._window.set_current_folder(os.path.dirname(path))
- self._window.set_current_name(os.path.basename(path))
-
-class FileSaveDialog(FileDialogBase):
- def __init__(self, title):
- FileDialogBase.__init__(self)
- self._files = None
- fcd = gtk.FileChooserDialog(title,
- action=gtk.FILE_CHOOSER_ACTION_SAVE,
- buttons=(gtk.STOCK_CANCEL,
- gtk.RESPONSE_CANCEL,
- gtk.STOCK_SAVE,
- gtk.RESPONSE_OK))
- self.set_window(fcd)
-
- def set_filename(self, text):
- self._window.set_current_name(text)
-
- def get_filename(self):
- if self._files is None:
- # clicked Cancel
- return None
- else:
- return unicode(self._files[0])
-
- # provide a common interface for file chooser dialogs
- get_path = get_filename
- def set_path(self, path):
- # set_filename puts the whole path in the filename field
- self._window.set_current_folder(os.path.dirname(path))
- self._window.set_current_name(os.path.basename(path))
-
-class DirectorySelectDialog(FileDialogBase):
- def __init__(self, title):
- FileDialogBase.__init__(self)
- self._files = None
- choose_str = 'Choose'
- fcd = gtk.FileChooserDialog(
- title,
- action=gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER,
- buttons=(gtk.STOCK_CANCEL,
- gtk.RESPONSE_CANCEL,
- choose_str, gtk.RESPONSE_OK))
- self.set_window(fcd)
-
- def set_directory(self, text):
- self._window.set_filename(text)
-
- def get_directory(self):
- if self._files is None:
- # clicked Cancel
- return None
- else:
- return unicode(self._files[0])
-
- # provide a common interface for file chooser dialogs
- get_path = get_directory
- set_path = set_directory
-
-class AboutDialog(Dialog):
- def __init__(self):
- Dialog.__init__(self, "Libre Video Converter")
-# _("About %(appname)s",
-# {'appname': app.config.get(prefs.SHORT_APP_NAME)}))
-# self.add_button(_("Close"))
- self.add_button("Close")
- self._window.set_has_separator(False)
-
- def build_content(self):
- packing_vbox = layout.VBox(spacing=20)
- #icon_pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(
- # resources.share_path('icons/hicolor/128x128/apps/miro.png'),
- # 48, 48)
- #packing_vbox._widget.pack_start(gtk.image_new_from_pixbuf(icon_pixbuf))
- #if app.config.get(prefs.APP_REVISION_NUM):
- # version = "%s (%s)" % (
- # app.config.get(prefs.APP_VERSION),
- # app.config.get(prefs.APP_REVISION_NUM))
- #else:
- # version = "%s" % app.config.get(prefs.APP_VERSION)
- version = '3.0'
- #name_label = gtk.Label(
- # '<span size="xx-large" weight="bold">%s %s</span>' % (
- # app.config.get(prefs.SHORT_APP_NAME), version))
- name_label = gtk.Label(
- '<span size="xx-large" weight="bold">%s %s</span>' % (
- 'Libre Video Converter', version))
- name_label.set_use_markup(True)
- packing_vbox._widget.pack_start(name_label)
- copyright_text = 'Copyright (c) Jesus Eduardo (Heckyel) | 2017'
- copyright_label = gtk.Label('<small>%s</small>' % copyright_text)
- copyright_label.set_use_markup(True)
- copyright_label.set_justify(gtk.JUSTIFY_CENTER)
- packing_vbox._widget.pack_start(copyright_label)
-
- # FIXME - make the project url clickable
- #packing_vbox._widget.pack_start(
- # gtk.Label(app.config.get(prefs.PROJECT_URL)))
-
- #contributor_label = gtk.Label(
- # _("Thank you to all the people who contributed to %(appname)s "
- # "%(version)s:",
- # {"appname": app.config.get(prefs.SHORT_APP_NAME),
- # "version": app.config.get(prefs.APP_VERSION)}))
- #contributor_label.set_justify(gtk.JUSTIFY_CENTER)
- #packing_vbox._widget.pack_start(contributor_label)
-
- # get contributors, remove newlines and wrap it
- #contributors = open(resources.path('CREDITS'), 'r').readlines()
- #contributors = [c[2:].strip()
- # for c in contributors if c.startswith("* ")]
- #contributors = ", ".join(contributors)
-
- # show contributors
- #contrib_buffer = gtk.TextBuffer()
- #contrib_buffer.set_text(contributors)
-
- #contrib_view = gtk.TextView(contrib_buffer)
- #contrib_view.set_editable(False)
- #contrib_view.set_cursor_visible(False)
- #contrib_view.set_wrap_mode(gtk.WRAP_WORD)
- #contrib_window = gtk.ScrolledWindow()
- #contrib_window.set_policy(gtk.POLICY_NEVER, gtk.POLICY_ALWAYS)
- #contrib_window.add(contrib_view)
- #contrib_window.set_size_request(-1, 100)
- #packing_vbox._widget.pack_start(contrib_window)
-
- # FIXME - make the project url clickable
- #donate_label = gtk.Label(
- # _("To help fund continued %(appname)s development, visit the "
- # "donation page at:",
- # {"appname": app.config.get(prefs.SHORT_APP_NAME)}))
- #donate_label.set_justify(gtk.JUSTIFY_CENTER)
- #packing_vbox._widget.pack_start(donate_label)
-
- #packing_vbox._widget.pack_start(
- # gtk.Label(app.config.get(prefs.DONATE_URL)))
- return packing_vbox
-
- def on_contrib_link_event(self, texttag, widget, event, iter_):
- if event.type == gtk.gdk.BUTTON_PRESS:
- resources.open_url('https://notabug.org/heckyel/librevideoconverter')
-
-type_map = {
- 0: gtk.MESSAGE_WARNING,
- 1: gtk.MESSAGE_INFO,
- 2: gtk.MESSAGE_ERROR
-}
-
-class AlertDialog(DialogBase):
- def __init__(self, title, description, alert_type):
- DialogBase.__init__(self)
- message_type = type_map.get(alert_type, gtk.MESSAGE_INFO)
- self.set_window(gtk.MessageDialog(type=message_type,
- message_format=description))
- self._window.set_title(title)
- self.description = description
-
- def add_button(self, text):
- self._window.add_button(_stock.get(text, text), 1)
- self._window.set_default_response(1)
-
- def _run(self):
- self._window.set_modal(False)
- self._window.show_all()
- response = self._window.run()
- self._window.hide()
- if response == gtk.RESPONSE_DELETE_EVENT:
- return -1
- else:
- # response IDs start at 1
- return response - 1
diff --git a/mvc/widgets/gtk/wrappermap.py b/mvc/widgets/gtk/wrappermap.py
deleted file mode 100644
index c2b2aad..0000000
--- a/mvc/widgets/gtk/wrappermap.py
+++ /dev/null
@@ -1,50 +0,0 @@
-# @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.
-
-""".wrappermap -- Map GTK Widgets to the Libre Widget
-that wraps them.
-"""
-
-import weakref
-
-# Maps gtk windows -> wrapper objects. We use a weak references to prevent
-# circular references between the GTK widget and it's wrapper. (Keeping a
-# reference to the GTK widget is fine, since if the wrapper is alive, the GTK
-# widget should be).
-widget_mapping = weakref.WeakValueDictionary()
-
-def wrapper(gtk_widget):
- """Find the wrapper widget for a GTK widget."""
- try:
- return widget_mapping[gtk_widget]
- except KeyError:
- raise KeyError("Widget wrapper no longer exists")
-
-def add(gtk_widget, wrapper):
- widget_mapping[gtk_widget] = wrapper