Delete button sensitivity in episode selector (bug 993)
[gpodder.git] / src / gpodder / gtkui / draw.py
blob6e116cedc9945f2717ec1d161cb48a281c130d73
1 # -*- coding: utf-8 -*-
3 # gPodder - A media aggregator and podcast client
4 # Copyright (c) 2005-2010 Thomas Perl and the gPodder Team
6 # gPodder is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 3 of the License, or
9 # (at your option) any later version.
11 # gPodder is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
16 # You should have received a copy of the GNU General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
22 # draw.py -- Draw routines for gPodder-specific graphics
23 # Thomas Perl <thp@perli.net>, 2007-11-25
26 import gpodder
28 import gtk
29 import pango
30 import pangocairo
31 import cairo
32 import StringIO
33 import math
36 class TextExtents(object):
37 def __init__(self, ctx, text):
38 tuple = ctx.text_extents(text)
39 (self.x_bearing, self.y_bearing, self.width, self.height, self.x_advance, self.y_advance) = tuple
42 RRECT_LEFT_SIDE = 1
43 RRECT_RIGHT_SIDE = 2
45 def draw_rounded_rectangle(ctx, x, y, w, h, r=10, left_side_width = None, sides_to_draw=0, close=False):
46 if left_side_width is None:
47 left_side_width = flw/2
49 x = int(x)
50 offset = 0
51 if close: offset = 0.5
53 if sides_to_draw & RRECT_LEFT_SIDE:
54 ctx.move_to(x+int(left_side_width)-offset, y+h)
55 ctx.line_to(x+r, y+h)
56 ctx.curve_to(x, y+h, x, y+h, x, y+h-r)
57 ctx.line_to(x, y+r)
58 ctx.curve_to(x, y, x, y, x+r, y)
59 ctx.line_to(x+int(left_side_width)-offset, y)
60 if close:
61 ctx.line_to(x+int(left_side_width)-offset, y+h)
63 if sides_to_draw & RRECT_RIGHT_SIDE:
64 ctx.move_to(x+int(left_side_width)+offset, y)
65 ctx.line_to(x+w-r, y)
66 ctx.curve_to(x+w, y, x+w, y, x+w, y+r)
67 ctx.line_to(x+w, y+h-r)
68 ctx.curve_to(x+w, y+h, x+w, y+h, x+w-r, y+h)
69 ctx.line_to(x+int(left_side_width)+offset, y+h)
70 if close:
71 ctx.line_to(x+int(left_side_width)+offset, y)
74 def rounded_rectangle(ctx, x, y, width, height, radius=4.):
75 """Simple rounded rectangle algorithmn
77 http://www.cairographics.org/samples/rounded_rectangle/
78 """
79 degrees = math.pi / 180.
80 ctx.new_sub_path()
81 if width > radius:
82 ctx.arc(x + width - radius, y + radius, radius, -90. * degrees, 0)
83 ctx.arc(x + width - radius, y + height - radius, radius, 0, 90. * degrees)
84 ctx.arc(x + radius, y + height - radius, radius, 90. * degrees, 180. * degrees)
85 ctx.arc(x + radius, y + radius, radius, 180. * degrees, 270. * degrees)
86 ctx.close_path()
89 def draw_text_box_centered(ctx, widget, w_width, w_height, text, font_desc=None, add_progress=None):
90 style = widget.rc_get_style()
91 text_color = style.text[gtk.STATE_PRELIGHT]
92 red, green, blue = text_color.red, text_color.green, text_color.blue
93 text_color = [float(x)/65535. for x in (red, green, blue)]
94 text_color.append(.5)
96 if font_desc is None:
97 font_desc = style.font_desc
98 font_desc.set_size(14*pango.SCALE)
100 pango_context = widget.create_pango_context()
101 layout = pango.Layout(pango_context)
102 layout.set_font_description(font_desc)
103 layout.set_text(text)
104 width, height = layout.get_pixel_size()
106 ctx.move_to(w_width/2-width/2, w_height/2-height/2)
107 ctx.set_source_rgba(*text_color)
108 ctx.show_layout(layout)
110 # Draw an optional progress bar below the text (same width)
111 if add_progress is not None:
112 bar_height = 10
113 ctx.set_source_rgba(*text_color)
114 ctx.set_line_width(1.)
115 rounded_rectangle(ctx, w_width/2-width/2-.5, w_height/2+height-.5, width+1, bar_height+1)
116 ctx.stroke()
117 rounded_rectangle(ctx, w_width/2-width/2, w_height/2+height, int(width*add_progress)+.5, bar_height)
118 ctx.fill()
121 def draw_text_pill(left_text, right_text, x=0, y=0, border=2, radius=14, font_desc=None):
122 if gpodder.ui.fremantle:
123 border += 3
124 # Create temporary context to calculate the text size
125 ctx = cairo.Context(cairo.ImageSurface(cairo.FORMAT_ARGB32, 1, 1))
127 # Use GTK+ style of a normal Button
128 widget = gtk.Label()
129 style = widget.rc_get_style()
131 x_border = border*2
133 if font_desc is None:
134 font_desc = style.font_desc
135 font_desc.set_weight(pango.WEIGHT_BOLD)
137 pango_context = widget.create_pango_context()
138 layout_left = pango.Layout(pango_context)
139 layout_left.set_font_description(font_desc)
140 layout_left.set_text(left_text)
141 layout_right = pango.Layout(pango_context)
142 layout_right.set_font_description(font_desc)
143 layout_right.set_text(right_text)
145 width_left, height_left = layout_left.get_pixel_size()
146 width_right, height_right = layout_right.get_pixel_size()
148 text_height = max(height_left, height_right)
150 image_height = int(y+text_height+border*2)
151 image_width = int(x+width_left+width_right+x_border*4)
152 surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, image_width, image_height)
154 ctx = pangocairo.CairoContext(cairo.Context(surface))
156 if left_text == '0':
157 left_text = None
158 if right_text == '0':
159 right_text = None
161 left_side_width = width_left + x_border*2
162 right_side_width = width_right + x_border*2
164 rect_width = left_side_width + right_side_width
165 rect_height = text_height + border*2
166 if left_text is not None:
167 draw_rounded_rectangle(ctx,x,y,rect_width,rect_height,radius, left_side_width, RRECT_LEFT_SIDE, right_text is None)
168 linear = cairo.LinearGradient(x, y, x+left_side_width/2, y+rect_height/2)
169 linear.add_color_stop_rgba(0, .8, .8, .8, .5)
170 linear.add_color_stop_rgba(.4, .8, .8, .8, .7)
171 linear.add_color_stop_rgba(.6, .8, .8, .8, .6)
172 linear.add_color_stop_rgba(.9, .8, .8, .8, .8)
173 linear.add_color_stop_rgba(1, .8, .8, .8, .9)
174 ctx.set_source(linear)
175 ctx.fill()
176 xpos, ypos, width_left, height = x+1, y+1, left_side_width, rect_height-2
177 if right_text is None:
178 width_left -= 2
179 draw_rounded_rectangle(ctx, xpos, ypos, rect_width, height, radius, width_left, RRECT_LEFT_SIDE, right_text is None)
180 ctx.set_source_rgba(1., 1., 1., .3)
181 ctx.set_line_width(1)
182 ctx.stroke()
183 draw_rounded_rectangle(ctx,x,y,rect_width,rect_height,radius, left_side_width, RRECT_LEFT_SIDE, right_text is None)
184 ctx.set_source_rgba(.2, .2, .2, .6)
185 ctx.set_line_width(1)
186 ctx.stroke()
188 ctx.move_to(x+x_border, y+1+border)
189 ctx.set_source_rgba( 0, 0, 0, 1)
190 ctx.show_layout(layout_left)
191 ctx.move_to(x-1+x_border, y+border)
192 ctx.set_source_rgba( 1, 1, 1, 1)
193 ctx.show_layout(layout_left)
195 if right_text is not None:
196 draw_rounded_rectangle(ctx, x, y, rect_width, rect_height, radius, left_side_width, RRECT_RIGHT_SIDE, left_text is None)
197 linear = cairo.LinearGradient(x+left_side_width, y, x+left_side_width+right_side_width/2, y+rect_height)
198 linear.add_color_stop_rgba(0, .2, .2, .2, .9)
199 linear.add_color_stop_rgba(.4, .2, .2, .2, .8)
200 linear.add_color_stop_rgba(.6, .2, .2, .2, .6)
201 linear.add_color_stop_rgba(.9, .2, .2, .2, .7)
202 linear.add_color_stop_rgba(1, .2, .2, .2, .5)
203 ctx.set_source(linear)
204 ctx.fill()
205 xpos, ypos, width, height = x, y+1, rect_width-1, rect_height-2
206 if left_text is None:
207 xpos, width = x+1, rect_width-2
208 draw_rounded_rectangle(ctx, xpos, ypos, width, height, radius, left_side_width, RRECT_RIGHT_SIDE, left_text is None)
209 ctx.set_source_rgba(1., 1., 1., .3)
210 ctx.set_line_width(1)
211 ctx.stroke()
212 draw_rounded_rectangle(ctx, x, y, rect_width, rect_height, radius, left_side_width, RRECT_RIGHT_SIDE, left_text is None)
213 ctx.set_source_rgba(.1, .1, .1, .6)
214 ctx.set_line_width(1)
215 ctx.stroke()
217 ctx.move_to(x+left_side_width+x_border, y+1+border)
218 ctx.set_source_rgba( 0, 0, 0, 1)
219 ctx.show_layout(layout_right)
220 ctx.move_to(x-1+left_side_width+x_border, y+border)
221 ctx.set_source_rgba( 1, 1, 1, 1)
222 ctx.show_layout(layout_right)
224 return surface
227 def draw_pill_pixbuf(left_text, right_text):
228 return cairo_surface_to_pixbuf(draw_text_pill(left_text, right_text))
231 def cairo_surface_to_pixbuf(s):
233 Converts a Cairo surface to a Gtk Pixbuf by
234 encoding it as PNG and using the PixbufLoader.
236 sio = StringIO.StringIO()
237 try:
238 s.write_to_png(sio)
239 except:
240 # Write an empty PNG file to the StringIO, so
241 # in case of an error we have "something" to
242 # load. This happens in PyCairo < 1.1.6, see:
243 # http://webcvs.cairographics.org/pycairo/NEWS?view=markup
244 # Thanks to Chris Arnold for reporting this bug
245 sio.write('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A\n/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9cMEQkqIyxn3RkAAAAZdEVYdENv\nbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAADUlEQVQI12NgYGBgAAAABQABXvMqOgAAAABJ\nRU5ErkJggg==\n'.decode('base64'))
247 pbl = gtk.gdk.PixbufLoader()
248 pbl.write(sio.getvalue())
249 pbl.close()
251 pixbuf = pbl.get_pixbuf()
252 return pixbuf
254 def progressbar_pixbuf(width, height, percentage):
255 COLOR_BG = (.4, .4, .4, .4)
256 COLOR_FG = (.2, .9, .2, 1.)
257 COLOR_FG_HIGH = (1., 1., 1., .5)
258 COLOR_BORDER = (0., 0., 0., 1.)
260 surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height)
261 ctx = cairo.Context(surface)
263 padding = int(float(width)/8.0)
264 bar_width = 2*padding
265 bar_height = height - 2*padding
266 bar_height_fill = bar_height*percentage
268 # Background
269 ctx.rectangle(padding, padding, bar_width, bar_height)
270 ctx.set_source_rgba(*COLOR_BG)
271 ctx.fill()
273 # Foreground
274 ctx.rectangle(padding, padding+bar_height-bar_height_fill, bar_width, bar_height_fill)
275 ctx.set_source_rgba(*COLOR_FG)
276 ctx.fill()
277 ctx.rectangle(padding+bar_width/3, padding+bar_height-bar_height_fill, bar_width/4, bar_height_fill)
278 ctx.set_source_rgba(*COLOR_FG_HIGH)
279 ctx.fill()
281 # Border
282 ctx.rectangle(padding-.5, padding-.5, bar_width+1, bar_height+1)
283 ctx.set_source_rgba(*COLOR_BORDER)
284 ctx.set_line_width(1.)
285 ctx.stroke()
287 return cairo_surface_to_pixbuf(surface)