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

« back to all changes in this revision

Viewing changes to lib/frontends/widgets/widgetutil.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.widgetutil`` -- Utility functions.
 
30
"""
 
31
 
 
32
import math
 
33
 
 
34
from miro import app
 
35
from miro.frontends.widgets import imagepool
 
36
from miro.plat import resources
 
37
from miro.plat.frontends.widgets import widgetset
 
38
 
 
39
BLACK = (0, 0, 0)
 
40
WHITE = (1, 1, 1)
 
41
 
 
42
PI = math.pi
 
43
 
 
44
def round_rect(context, x, y, width, height, edge_radius):
 
45
    """Specifies path of a rectangle with rounded corners.
 
46
    """
 
47
    edge_radius = min(edge_radius, min(width, height)/2.0)
 
48
    inner_width = width - edge_radius*2
 
49
    inner_height = height - edge_radius*2
 
50
    x_inner1 = x + edge_radius
 
51
    x_inner2 = x + width - edge_radius
 
52
    y_inner1 = y + edge_radius
 
53
    y_inner2 = y + height - edge_radius
 
54
 
 
55
    context.move_to(x+edge_radius, y)
 
56
    context.rel_line_to(inner_width, 0)
 
57
    context.arc(x_inner2, y_inner1, edge_radius, -PI/2, 0)
 
58
    context.rel_line_to(0, inner_height)
 
59
    context.arc(x_inner2, y_inner2, edge_radius, 0, PI/2)
 
60
    context.rel_line_to(-inner_width, 0)
 
61
    context.arc(x_inner1, y_inner2, edge_radius, PI/2, PI)
 
62
    context.rel_line_to(0, -inner_height)
 
63
    context.arc(x_inner1, y_inner1, edge_radius, PI, PI*3/2)
 
64
 
 
65
def round_rect_reverse(context, x, y, width, height, edge_radius):
 
66
    """Specifies path of a rectangle with rounded corners.
 
67
 
 
68
    This specifies the rectangle in a counter-clockwise fashion.
 
69
    """
 
70
    edge_radius = min(edge_radius, min(width, height)/2.0)
 
71
    inner_width = width - edge_radius*2
 
72
    inner_height = height - edge_radius*2
 
73
    x_inner1 = x + edge_radius
 
74
    x_inner2 = x + width - edge_radius
 
75
    y_inner1 = y + edge_radius
 
76
    y_inner2 = y + height - edge_radius
 
77
 
 
78
    context.move_to(x+edge_radius, y)
 
79
    context.arc_negative(x_inner1, y_inner1, edge_radius, PI*3/2, PI)
 
80
    context.rel_line_to(0, inner_height)
 
81
    context.arc_negative(x_inner1, y_inner2, edge_radius, PI, PI/2)
 
82
    context.rel_line_to(inner_width, 0)
 
83
    context.arc_negative(x_inner2, y_inner2, edge_radius, PI/2, 0)
 
84
    context.rel_line_to(0, -inner_height)
 
85
    context.arc_negative(x_inner2, y_inner1, edge_radius, 0, -PI/2)
 
86
    context.rel_line_to(-inner_width, 0)
 
87
 
 
88
def circular_rect(context, x, y, width, height):
 
89
    """Make a path for a rectangle with the left/right side being circles.
 
90
    """
 
91
    radius = height / 2.0
 
92
    inner_width = width - height
 
93
    inner_y = y + radius
 
94
    inner_x1 = x + radius
 
95
    inner_x2 = inner_x1 + inner_width
 
96
 
 
97
    context.move_to(inner_x1, y)
 
98
    context.rel_line_to(inner_width, 0)
 
99
    context.arc(inner_x2, inner_y, radius, -PI/2, PI/2)
 
100
    context.rel_line_to(-inner_width, 0)
 
101
    context.arc(inner_x1, inner_y, radius, PI/2, -PI/2)
 
102
 
 
103
def circular_rect_negative(context, x, y, width, height):
 
104
    """The same path as ``circular_rect()``, but going counter clockwise.
 
105
    """
 
106
    radius = height / 2.0
 
107
    inner_width = width - height
 
108
    inner_y = y + radius
 
109
    inner_x1 = x + radius
 
110
    inner_x2 = inner_x1 + inner_width
 
111
 
 
112
    context.move_to(inner_x1, y)
 
113
    context.arc_negative(inner_x1, inner_y, radius, -PI/2, PI/2)
 
114
    context.rel_line_to(inner_width, 0)
 
115
    context.arc_negative(inner_x2, inner_y, radius, PI/2, -PI/2)
 
116
    context.rel_line_to(-inner_width, 0)
 
117
 
 
118
def draw_rounded_icon(context, icon, x, y, width, height, inset=0, fraction=1.0):
 
119
    """Draw an icon with the corners rounded.
 
120
    
 
121
    x, y, width, height define where the box is.
 
122
 
 
123
    inset creates a margin between where the images is drawn and (x, y, width,
 
124
    height)
 
125
    """
 
126
    context.save()
 
127
    round_rect(context, x + inset, y + inset, width - inset*2, 
 
128
            height - inset*2, 3)
 
129
    context.clip()
 
130
    if icon.width != width or icon.height != height:
 
131
        icon_x = int((width - icon.width) / 2)
 
132
        icon_y = int((height - icon.height) / 2)
 
133
        context.set_color((0, 0, 0), fraction)
 
134
        if fraction < 1.0:
 
135
            if icon_x > 0:
 
136
                context.rectangle(x, y, icon_x, height)
 
137
                context.fill()
 
138
                context.rectangle(x+icon_x+icon.width, y, width-(icon_x+icon.width), height)
 
139
                context.fill()
 
140
            else:
 
141
                context.rectangle(x, y, width, icon_y)
 
142
                context.fill()
 
143
                context.rectangle(x, y+icon_y+icon.height, width, height-(icon_y+icon.height))
 
144
                context.fill()
 
145
        else:
 
146
            context.rectangle(x, y, width, height)
 
147
            context.fill()
 
148
    else:
 
149
        icon_x = icon_y = 0
 
150
    icon.draw(context, x + icon_x, y + icon_y, icon.width, icon.height, fraction)
 
151
    context.restore()
 
152
 
 
153
def align(widget, xalign=0, yalign=0, xscale=0, yscale=0, 
 
154
        top_pad=0, bottom_pad=0, left_pad=0, right_pad=0):
 
155
    """Create an alignment, then add widget to it and return the alignment.
 
156
    """
 
157
    alignment = widgetset.Alignment(xalign, yalign, xscale, yscale)
 
158
    alignment.set_padding(top_pad, bottom_pad, left_pad, right_pad)
 
159
    alignment.add(widget)
 
160
    return alignment
 
161
 
 
162
def align_center(widget, top_pad=0, bottom_pad=0, left_pad=0, right_pad=0):
 
163
    """Wrap a widget in an Alignment that will center it horizontally.
 
164
    """
 
165
    return align(widget, 0.5, 0, 0, 1,
 
166
            top_pad, bottom_pad, left_pad, right_pad)
 
167
 
 
168
def align_right(widget, top_pad=0, bottom_pad=0, left_pad=0, right_pad=0):
 
169
    """Wrap a widget in an Alignment that will align it left.
 
170
    """
 
171
    return align(widget, 1, 0, 0, 1, top_pad, bottom_pad, left_pad, right_pad)
 
172
 
 
173
def align_left(widget, top_pad=0, bottom_pad=0, left_pad=0, right_pad=0):
 
174
    """Wrap a widget in an Alignment that will align it right.
 
175
    """
 
176
    return align(widget, 0, 0, 0, 1, top_pad, bottom_pad, left_pad, right_pad)
 
177
 
 
178
def align_middle(widget, top_pad=0, bottom_pad=0, left_pad=0, right_pad=0):
 
179
    """Wrap a widget in an Alignment that will center it vertically.
 
180
    """
 
181
    return align(widget, 0, 0.5, 1, 0,
 
182
            top_pad, bottom_pad, left_pad, right_pad)
 
183
 
 
184
def align_top(widget, top_pad=0, bottom_pad=0, left_pad=0, right_pad=0):
 
185
    """Wrap a widget in an Alignment that will align to the top.
 
186
    """
 
187
    return align(widget, 0, 0, 1, 0, top_pad, bottom_pad, left_pad, right_pad)
 
188
 
 
189
def align_bottom(widget, top_pad=0, bottom_pad=0, left_pad=0, right_pad=0):
 
190
    """Wrap a widget in an Alignment that will align to the bottom.
 
191
    """
 
192
    return align(widget, 0, 1, 1, 0, top_pad, bottom_pad, left_pad, right_pad)
 
193
 
 
194
def pad(widget, top=0, bottom=0, left=0, right=0):
 
195
    """Wrap a widget in an Alignment that will pad it.
 
196
    """
 
197
    alignment = widgetset.Alignment(xscale=1, yscale=1)
 
198
    alignment.set_padding(top, bottom, left, right)
 
199
    alignment.add(widget)
 
200
    return alignment
 
201
 
 
202
def build_hbox(items, padding=5):
 
203
    """Builds an HBox and packs with the list of widgets.  Padding defaults to
 
204
    5 pixels.
 
205
    """
 
206
    h = widgetset.HBox()
 
207
    map(lambda item: h.pack_start(item, padding=padding), items)
 
208
    return h
 
209
 
 
210
def build_control_line(items, padding=5):
 
211
    max_baseline = -1
 
212
    for item in items:
 
213
        max_baseline = max(max_baseline, item.baseline())
 
214
    padded_items = []
 
215
    for item in items:
 
216
        if item.baseline() == max_baseline:
 
217
            padded_items.append(item)
 
218
        else:
 
219
            pad = int(round(max_baseline - item.baseline()))
 
220
            padded_items.append(align_bottom(item, bottom_pad=pad))
 
221
    return build_hbox(padded_items, padding)
 
222
 
 
223
def make_surface(image_name):
 
224
    path = resources.path("images/%s.png" % image_name)
 
225
    return imagepool.get_surface(path)
 
226
 
 
227
class ThreeImageSurface(object):
 
228
    """Takes a left, center and right image and draws them to an arbitrary
 
229
    width.  If the width is greater than the combined width of the 3 images,
 
230
    then the center image will be tiled to compensate.
 
231
 
 
232
    Example:
 
233
 
 
234
    >>> timelinebar = ThreeImageSurface("timelinebar")
 
235
 
 
236
    This creates a ``ThreeImageSurface`` using the images 
 
237
    ``images/timelinebar_left.png``, ``images/timelinebar_center.png``, and
 
238
    ``images/timelinebar_right.png``.
 
239
 
 
240
    Example:
 
241
 
 
242
    >>> timelinebar = ThreeImageSurface()
 
243
    >>> img_left = make_surface("timelinebar_left")
 
244
    >>> img_center = make_surface("timelinebar_center")
 
245
    >>> img_right = make_surface("timelinebar_right")
 
246
    >>> timelinebar.set_images(img_left, img_center, img_right)
 
247
 
 
248
    This does the same thing, but allows you to explicitly set which images
 
249
    get used.
 
250
    """
 
251
    def __init__(self, basename=None):
 
252
        self.left = self.center = self.right = None
 
253
        self.height = 0
 
254
        if basename is not None:
 
255
            left = make_surface(basename + '_left')
 
256
            center = make_surface(basename + '_center')
 
257
            right = make_surface(basename + '_right')
 
258
            self.set_images(left, center, right)
 
259
 
 
260
    def set_images(self, left, center, right):
 
261
        """Sets the left, center and right images to use.
 
262
        """
 
263
        self.left = left
 
264
        self.center = center
 
265
        self.right = right
 
266
        if not (self.left.height == self.center.height == self.right.height):
 
267
            raise ValueError("Images aren't the same height")
 
268
        self.height = self.left.height
 
269
 
 
270
    def draw(self, context, x, y, width, fraction=1.0):
 
271
        left_width = min(self.left.width, width)
 
272
        self.left.draw(context, x, y, left_width, self.height, fraction)
 
273
        self.draw_right(context, x + left_width, y, width - left_width, fraction)
 
274
 
 
275
    def draw_right(self, context, x, y, width, fraction=1.0):
 
276
        # draws only the right two images
 
277
 
 
278
        right_width = min(self.right.width, width)
 
279
        center_width = int(width - right_width)
 
280
 
 
281
        self.center.draw(context, x, y, center_width, self.height, fraction)
 
282
        self.right.draw(context, x + center_width, y, right_width, self.height, fraction)
 
283
 
 
284
class HideableWidget(widgetset.VBox):
 
285
    """Creates a widget that can be hidden and shown.
 
286
 
 
287
    Example:
 
288
 
 
289
    >>> lab = Label(_("Error!"))
 
290
    >>> hidden_lab = HideableWidget(lab)
 
291
 
 
292
    Then when we want to hide it, we do:
 
293
 
 
294
    >>> hidden_lab.hide()
 
295
 
 
296
    and when we want to show it again, we do:
 
297
 
 
298
    >>> hidden_lab.show()
 
299
    """
 
300
    def __init__(self, child):
 
301
        widgetset.VBox.__init__(self)
 
302
        self._child = child
 
303
        self.shown = False
 
304
 
 
305
    def child(self):
 
306
        """Returns the child widget.
 
307
        """
 
308
        return self._child
 
309
 
 
310
    def show(self):
 
311
        """Shows the child widget.
 
312
        """
 
313
        if not self.shown:
 
314
            self.pack_start(self._child)
 
315
            self.shown = True
 
316
 
 
317
    def hide(self):
 
318
        """Hides the child widget.
 
319
        """
 
320
        if self.shown:
 
321
            self.remove(self._child)
 
322
            self.shown = False
 
323
 
 
324
class Shadow(object):
 
325
    """Encapsulates all parameters required to draw shadows.
 
326
    """
 
327
    def __init__(self, color, opacity, offset, blur_radius):
 
328
        self.color = color
 
329
        self.opacity = opacity
 
330
        self.offset = offset
 
331
        self.blur_radius = blur_radius
 
332
 
 
333
def get_feed_info(feed_id):
 
334
    """Returns the :class:`FeedInfo` object for a given ``feed_id`` 
 
335
    regardless of whether it's an audio or video feed.
 
336
    """
 
337
    tablist = app.tab_list_manager.which_tablist_has_id(feed_id)
 
338
    return tablist.get_info(feed_id)
 
339
 
 
340
def feed_exists(feed_id):
 
341
    """Returns true or false as to whether a :class:`Feed` with id ``feed_id`` 
 
342
    exists.
 
343
    """
 
344
    try:
 
345
        get_feed_info(feed_id)
 
346
        return True
 
347
    except (ValueError, KeyError):
 
348
        pass
 
349
    return False