~ubuntu-branches/ubuntu/natty/miro/natty

« back to all changes in this revision

Viewing changes to lib/frontends/widgets/gtk/base.py

  • Committer: Bazaar Package Importer
  • Author(s): Bryce Harrington
  • Date: 2011-01-22 02:46:33 UTC
  • mfrom: (1.4.10 upstream) (1.7.5 experimental)
  • Revision ID: james.westby@ubuntu.com-20110122024633-kjme8u93y2il5nmf
Tags: 3.5.1-1ubuntu1
* Merge from debian.  Remaining ubuntu changes:
  - Use python 2.7 instead of python 2.6
  - Relax dependency on python-dbus to >= 0.83.0

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Miro - an RSS based video player application
 
2
# Copyright (C) 2005-2010 Participatory Culture Foundation
 
3
#
 
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.
 
8
#
 
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.
 
13
#
 
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
 
17
#
 
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
 
20
# library.
 
21
#
 
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.
 
28
 
 
29
"""miro.frontends.widgets.gtk.base -- Base classes for GTK Widgets."""
 
30
 
 
31
import gtk
 
32
 
 
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
 
38
 
 
39
def make_gdk_color(miro_color):
 
40
    def convert_value(value):
 
41
        return int(round(value * 65535))
 
42
 
 
43
    values = tuple(convert_value(c) for c in miro_color)
 
44
    return gtk.gdk.Color(*values)
 
45
 
 
46
class Widget(signals.SignalEmitter):
 
47
    """Base class for GTK widgets.  
 
48
 
 
49
    The actual GTK Widget is stored in '_widget'.
 
50
 
 
51
    signals:
 
52
 
 
53
        'size-allocated' (widget, width, height): The widget had it's size
 
54
            allocated.
 
55
    """
 
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')
 
60
        self.style_mods = {}
 
61
        self.use_custom_style = False
 
62
        self._disabled = False
 
63
 
 
64
    def wrapped_widget_connect(self, signal, method, *user_args):
 
65
        """Connect to a signal of the widget we're wrapping.
 
66
 
 
67
        We use a weak reference to ensures that we don't have circular
 
68
        references between the wrapped widget and the wrapper widget.
 
69
        """
 
70
        return weak_connect(self._widget, signal, method, *user_args)
 
71
 
 
72
    def set_widget(self, widget):
 
73
        self._widget = 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
 
80
 
 
81
    def on_hierarchy_changed(self, widget, previous_toplevel):
 
82
        toplevel = widget.get_toplevel()
 
83
        if not (toplevel.flags() & gtk.TOPLEVEL):
 
84
            toplevel = None
 
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
 
94
            else:
 
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)
 
99
 
 
100
    def on_size_allocate(self, widget, allocation):
 
101
        self.emit('size-allocated', allocation.width, allocation.height)
 
102
 
 
103
    def on_key_press(self, widget, event):
 
104
        key, modifiers = keymap.translate_gtk_event(event)
 
105
        return self.emit('key-press', key, modifiers)
 
106
 
 
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)
 
114
        else:
 
115
            # This should reset the style changes we've made
 
116
            self._widget.modify_style(gtk.RcStyle())
 
117
        self.handle_custom_style_change()
 
118
 
 
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.
 
124
        """
 
125
        pass
 
126
 
 
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.
 
132
        """
 
133
        if self.use_custom_style:
 
134
            self.do_modify_style(what, state, color)
 
135
        self.style_mods[(what, state)] = color
 
136
 
 
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)
 
142
 
 
143
    def do_modify_style(self, what, state, color):
 
144
        if what == 'base':
 
145
            self._widget.modify_base(state, color)
 
146
        elif what == 'text':
 
147
            self._widget.modify_text(state, color)
 
148
        elif what == 'bg':
 
149
            self._widget.modify_bg(state, color)
 
150
        elif what == 'fg':
 
151
            self._widget.modify_fg(state, color)
 
152
        else:
 
153
            raise ValueError("Unknown what in do_modify_style: %s" % what)
 
154
 
 
155
    def get_window(self):
 
156
        gtk_window = self._widget.get_toplevel()
 
157
        return wrappermap.wrapper(gtk_window)
 
158
 
 
159
    def get_size_request(self):
 
160
        return self._widget.size_request()
 
161
 
 
162
    def invalidate_size_request(self):
 
163
        # FIXME - do we need to do anything here?
 
164
        pass
 
165
 
 
166
    def set_size_request(self, width, height):
 
167
        self._widget.set_size_request(width, height)
 
168
 
 
169
    def relative_position(self, other_widget):
 
170
        return other_widget._widget.translate_coordinates(self._widget, 0, 0)
 
171
 
 
172
    def convert_gtk_color(self, color):
 
173
        return (color.red / 65535.0, color.green / 65535.0, 
 
174
                color.blue / 65535.0)
 
175
 
 
176
    def get_width(self):
 
177
        try:
 
178
            return self._widget.allocation.width
 
179
        except AttributeError:
 
180
            return -1
 
181
    width = property(get_width)
 
182
 
 
183
    def get_height(self):
 
184
        try:
 
185
            return self._widget.allocation.height
 
186
        except AttributeError:
 
187
            return -1
 
188
    height = property(get_height)
 
189
 
 
190
    def queue_redraw(self):
 
191
        if self._widget:
 
192
            self._widget.queue_draw()
 
193
 
 
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.
 
197
        """
 
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)
 
202
 
 
203
    def do_forward_signal(self, widget, *args):
 
204
        forwarded_signal_name = args[-1]
 
205
        args = args[:-1]
 
206
        self.emit(forwarded_signal_name, *args)
 
207
 
 
208
    def make_color(self, miro_color):
 
209
        color = make_gdk_color(miro_color)
 
210
        self._widget.get_colormap().alloc_color(color)
 
211
        return color
 
212
 
 
213
    def enable(self):
 
214
        self._disabled = False
 
215
        self._widget.set_sensitive(True)
 
216
 
 
217
    def disable(self):
 
218
        self._disabled = True
 
219
        self._widget.set_sensitive(False)
 
220
 
 
221
    def set_disabled(self, disabled):
 
222
        if disabled:
 
223
            self.disable()
 
224
        else:
 
225
            self.enable()
 
226
 
 
227
    def get_disabled(self):
 
228
        return self._disabled
 
229
 
 
230
 
 
231
class Bin(Widget):
 
232
    def __init__(self):
 
233
        Widget.__init__(self)
 
234
        self.child = None
 
235
 
 
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)
 
241
        self.child = child
 
242
        self.add_child_to_widget()
 
243
        child._widget.show()
 
244
 
 
245
    def add_child_to_widget(self):
 
246
        self._widget.add(self.child._widget)
 
247
 
 
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())
 
251
 
 
252
    def remove(self):
 
253
        if self.child is not None:
 
254
            self.child = None
 
255
            self.remove_child_from_widget()
 
256
 
 
257
    def set_child(self, new_child):
 
258
        self.remove()
 
259
        self.add(new_child)
 
260
 
 
261
    def enable(self):
 
262
        self.child.enable()
 
263
 
 
264
    def disable(self):
 
265
        self.child.disable()