1
# Miro - an RSS based video player application
2
# Copyright (C) 2005-2010 Participatory Culture Foundation
4
# This program is free software; you can redistribute it and/or modify
5
# it under the terms of the GNU General Public License as published by
6
# the Free Software Foundation; either version 2 of the License, or
7
# (at your option) any later version.
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
# GNU General Public License for more details.
14
# You should have received a copy of the GNU General Public License
15
# along with this program; if not, write to the Free Software
16
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18
# In addition, as a special exception, the copyright holders give
19
# permission to link the code of portions of this program with the OpenSSL
22
# You must obey the GNU General Public License in all respects for all of
23
# the code used other than OpenSSL. If you modify file(s) with this
24
# exception, you may extend this exception to your version of the file(s),
25
# but you are not obligated to do so. If you do not wish to do so, delete
26
# this exception statement from your version. If you delete this exception
27
# statement from all source files in the program, then also delete it here.
29
"""miro.frontends.widgets.gtk.base -- Base classes for GTK Widgets."""
33
from miro import signals
34
from miro.frontends.widgets.gtk import window
35
from miro.frontends.widgets.gtk import wrappermap
36
from miro.frontends.widgets.gtk.weakconnect import weak_connect
37
from miro.frontends.widgets.gtk import keymap
39
def make_gdk_color(miro_color):
40
def convert_value(value):
41
return int(round(value * 65535))
43
values = tuple(convert_value(c) for c in miro_color)
44
return gtk.gdk.Color(*values)
46
class Widget(signals.SignalEmitter):
47
"""Base class for GTK widgets.
49
The actual GTK Widget is stored in '_widget'.
53
'size-allocated' (widget, width, height): The widget had it's size
56
def __init__(self, *signal_names):
57
signals.SignalEmitter.__init__(self, *signal_names)
58
self.create_signal('size-allocated')
59
self.create_signal('key-press')
61
self.use_custom_style = False
62
self._disabled = False
64
def wrapped_widget_connect(self, signal, method, *user_args):
65
"""Connect to a signal of the widget we're wrapping.
67
We use a weak reference to ensures that we don't have circular
68
references between the wrapped widget and the wrapper widget.
70
return weak_connect(self._widget, signal, method, *user_args)
72
def set_widget(self, widget):
74
wrappermap.add(self._widget, self)
75
self.wrapped_widget_connect('hierarchy_changed',
76
self.on_hierarchy_changed)
77
self.wrapped_widget_connect('size-allocate', self.on_size_allocate)
78
self.wrapped_widget_connect('key-press-event', self.on_key_press)
79
self.use_custom_style_callback = None
81
def on_hierarchy_changed(self, widget, previous_toplevel):
82
toplevel = widget.get_toplevel()
83
if not (toplevel.flags() & gtk.TOPLEVEL):
85
if previous_toplevel != toplevel:
86
if self.use_custom_style_callback:
87
old_window = wrappermap.wrapper(previous_toplevel)
88
old_window.disconnect(self.use_custom_style_callback)
89
if toplevel is not None:
90
window = wrappermap.wrapper(toplevel)
91
callback_id = window.connect('use-custom-style-changed',
92
self.on_use_custom_style_changed)
93
self.use_custom_style_callback = callback_id
95
self.use_custom_style_callback = None
96
if previous_toplevel is None:
97
# Setup our initial state
98
self.on_use_custom_style_changed(window)
100
def on_size_allocate(self, widget, allocation):
101
self.emit('size-allocated', allocation.width, allocation.height)
103
def on_key_press(self, widget, event):
104
key, modifiers = keymap.translate_gtk_event(event)
105
return self.emit('key-press', key, modifiers)
107
def on_use_custom_style_changed(self, window):
108
self.use_custom_style = window.use_custom_style
109
if not self.style_mods:
110
return # no need to do any work here
111
if self.use_custom_style:
112
for (what, state), color in self.style_mods.items():
113
self.do_modify_style(what, state, color)
115
# This should reset the style changes we've made
116
self._widget.modify_style(gtk.RcStyle())
117
self.handle_custom_style_change()
119
def handle_custom_style_change(self):
120
"""Called when the user changes a from a theme where we don't want to
121
use our custom style to one where we do, or vice-versa. The Widget
122
class handles changes that used modify_style(), but subclasses might
123
want to do additional work.
127
def modify_style(self, what, state, color):
128
"""Change the style of our widget. This method checks to see if we
129
think the user's theme is compatible with our stylings, and doesn't
130
change things if not. what is either 'base', 'text', 'bg' or 'fg'
131
depending on which color is to be changed.
133
if self.use_custom_style:
134
self.do_modify_style(what, state, color)
135
self.style_mods[(what, state)] = color
137
def unmodify_style(self, what, state):
138
if (what, state) in self.style_mods:
139
del self.style_mods[(what, state)]
140
default_color = getattr(self.style, what)[state]
141
self.do_modify_style(what, state, default_color)
143
def do_modify_style(self, what, state, color):
145
self._widget.modify_base(state, color)
147
self._widget.modify_text(state, color)
149
self._widget.modify_bg(state, color)
151
self._widget.modify_fg(state, color)
153
raise ValueError("Unknown what in do_modify_style: %s" % what)
155
def get_window(self):
156
gtk_window = self._widget.get_toplevel()
157
return wrappermap.wrapper(gtk_window)
159
def get_size_request(self):
160
return self._widget.size_request()
162
def invalidate_size_request(self):
163
# FIXME - do we need to do anything here?
166
def set_size_request(self, width, height):
167
self._widget.set_size_request(width, height)
169
def relative_position(self, other_widget):
170
return other_widget._widget.translate_coordinates(self._widget, 0, 0)
172
def convert_gtk_color(self, color):
173
return (color.red / 65535.0, color.green / 65535.0,
174
color.blue / 65535.0)
178
return self._widget.allocation.width
179
except AttributeError:
181
width = property(get_width)
183
def get_height(self):
185
return self._widget.allocation.height
186
except AttributeError:
188
height = property(get_height)
190
def queue_redraw(self):
192
self._widget.queue_draw()
194
def forward_signal(self, signal_name, forwarded_signal_name=None):
195
"""Add a callback so that when the GTK widget emits a signal, we emit
196
signal from the wrapper widget.
198
if forwarded_signal_name is None:
199
forwarded_signal_name = signal_name
200
self.wrapped_widget_connect(signal_name, self.do_forward_signal,
201
forwarded_signal_name)
203
def do_forward_signal(self, widget, *args):
204
forwarded_signal_name = args[-1]
206
self.emit(forwarded_signal_name, *args)
208
def make_color(self, miro_color):
209
color = make_gdk_color(miro_color)
210
self._widget.get_colormap().alloc_color(color)
214
self._disabled = False
215
self._widget.set_sensitive(True)
218
self._disabled = True
219
self._widget.set_sensitive(False)
221
def set_disabled(self, disabled):
227
def get_disabled(self):
228
return self._disabled
233
Widget.__init__(self)
236
def add(self, child):
237
if self.child is not None:
238
raise ValueError("Already have a child: %s" % self.child)
239
if child._widget.parent is not None:
240
raise ValueError("%s already has a parent" % child)
242
self.add_child_to_widget()
245
def add_child_to_widget(self):
246
self._widget.add(self.child._widget)
248
def remove_child_from_widget(self):
249
self._widget.get_child().hide() # otherwise gtkmozembed gets confused
250
self._widget.remove(self._widget.get_child())
253
if self.child is not None:
255
self.remove_child_from_widget()
257
def set_child(self, new_child):