Maemo 5: Fix window and button layout (Maemo bug 11499)
[gpodder.git] / src / gpodder / gtkui / draw.py
blob83783f1616da7337fa9321d75265499a8408cd99
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 assert left_side_width is not None
48 x = int(x)
49 offset = 0
50 if close: offset = 0.5
52 if sides_to_draw & RRECT_LEFT_SIDE:
53 ctx.move_to(x+int(left_side_width)-offset, y+h)
54 ctx.line_to(x+r, y+h)
55 ctx.curve_to(x, y+h, x, y+h, x, y+h-r)
56 ctx.line_to(x, y+r)
57 ctx.curve_to(x, y, x, y, x+r, y)
58 ctx.line_to(x+int(left_side_width)-offset, y)
59 if close:
60 ctx.line_to(x+int(left_side_width)-offset, y+h)
62 if sides_to_draw & RRECT_RIGHT_SIDE:
63 ctx.move_to(x+int(left_side_width)+offset, y)
64 ctx.line_to(x+w-r, y)
65 ctx.curve_to(x+w, y, x+w, y, x+w, y+r)
66 ctx.line_to(x+w, y+h-r)
67 ctx.curve_to(x+w, y+h, x+w, y+h, x+w-r, y+h)
68 ctx.line_to(x+int(left_side_width)+offset, y+h)
69 if close:
70 ctx.line_to(x+int(left_side_width)+offset, y)
73 def rounded_rectangle(ctx, x, y, width, height, radius=4.):
74 """Simple rounded rectangle algorithmn
76 http://www.cairographics.org/samples/rounded_rectangle/
77 """
78 degrees = math.pi / 180.
79 ctx.new_sub_path()
80 if width > radius:
81 ctx.arc(x + width - radius, y + radius, radius, -90. * degrees, 0)
82 ctx.arc(x + width - radius, y + height - radius, radius, 0, 90. * degrees)
83 ctx.arc(x + radius, y + height - radius, radius, 90. * degrees, 180. * degrees)
84 ctx.arc(x + radius, y + radius, radius, 180. * degrees, 270. * degrees)
85 ctx.close_path()
88 def draw_text_box_centered(ctx, widget, w_width, w_height, text, font_desc=None, add_progress=None):
89 style = widget.rc_get_style()
90 text_color = style.text[gtk.STATE_PRELIGHT]
91 red, green, blue = text_color.red, text_color.green, text_color.blue
92 text_color = [float(x)/65535. for x in (red, green, blue)]
93 text_color.append(.5)
95 if font_desc is None:
96 font_desc = style.font_desc
97 font_desc.set_size(14*pango.SCALE)
99 pango_context = widget.create_pango_context()
100 layout = pango.Layout(pango_context)
101 layout.set_font_description(font_desc)
102 layout.set_text(text)
103 width, height = layout.get_pixel_size()
105 ctx.move_to(w_width/2-width/2, w_height/2-height/2)
106 ctx.set_source_rgba(*text_color)
107 ctx.show_layout(layout)
109 # Draw an optional progress bar below the text (same width)
110 if add_progress is not None:
111 bar_height = 10
112 ctx.set_source_rgba(*text_color)
113 ctx.set_line_width(1.)
114 rounded_rectangle(ctx, w_width/2-width/2-.5, w_height/2+height-.5, width+1, bar_height+1)
115 ctx.stroke()
116 rounded_rectangle(ctx, w_width/2-width/2, w_height/2+height, int(width*add_progress)+.5, bar_height)
117 ctx.fill()
120 def draw_text_pill(left_text, right_text, x=0, y=0, border=2, radius=14, font_desc=None):
121 if gpodder.ui.fremantle:
122 border += 3
123 # Create temporary context to calculate the text size
124 ctx = cairo.Context(cairo.ImageSurface(cairo.FORMAT_ARGB32, 1, 1))
126 # Use GTK+ style of a normal Button
127 widget = gtk.Label()
128 style = widget.rc_get_style()
130 x_border = border*2
132 if font_desc is None:
133 font_desc = style.font_desc
134 font_desc.set_weight(pango.WEIGHT_BOLD)
136 pango_context = widget.create_pango_context()
137 layout_left = pango.Layout(pango_context)
138 layout_left.set_font_description(font_desc)
139 layout_left.set_text(left_text)
140 layout_right = pango.Layout(pango_context)
141 layout_right.set_font_description(font_desc)
142 layout_right.set_text(right_text)
144 width_left, height_left = layout_left.get_pixel_size()
145 width_right, height_right = layout_right.get_pixel_size()
147 text_height = max(height_left, height_right)
149 image_height = int(y+text_height+border*2)
150 image_width = int(x+width_left+width_right+x_border*4)
151 surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, image_width, image_height)
153 ctx = pangocairo.CairoContext(cairo.Context(surface))
155 if left_text == '0':
156 left_text = None
157 if right_text == '0':
158 right_text = None
160 left_side_width = width_left + x_border*2
161 right_side_width = width_right + x_border*2
163 rect_width = left_side_width + right_side_width
164 rect_height = text_height + border*2
165 if left_text is not None:
166 draw_rounded_rectangle(ctx,x,y,rect_width,rect_height,radius, left_side_width, RRECT_LEFT_SIDE, right_text is None)
167 linear = cairo.LinearGradient(x, y, x+left_side_width/2, y+rect_height/2)
168 linear.add_color_stop_rgba(0, .8, .8, .8, .5)
169 linear.add_color_stop_rgba(.4, .8, .8, .8, .7)
170 linear.add_color_stop_rgba(.6, .8, .8, .8, .6)
171 linear.add_color_stop_rgba(.9, .8, .8, .8, .8)
172 linear.add_color_stop_rgba(1, .8, .8, .8, .9)
173 ctx.set_source(linear)
174 ctx.fill()
175 xpos, ypos, width_left, height = x+1, y+1, left_side_width, rect_height-2
176 if right_text is None:
177 width_left -= 2
178 draw_rounded_rectangle(ctx, xpos, ypos, rect_width, height, radius, width_left, RRECT_LEFT_SIDE, right_text is None)
179 ctx.set_source_rgba(1., 1., 1., .3)
180 ctx.set_line_width(1)
181 ctx.stroke()
182 draw_rounded_rectangle(ctx,x,y,rect_width,rect_height,radius, left_side_width, RRECT_LEFT_SIDE, right_text is None)
183 ctx.set_source_rgba(.2, .2, .2, .6)
184 ctx.set_line_width(1)
185 ctx.stroke()
187 ctx.move_to(x+x_border, y+1+border)
188 ctx.set_source_rgba( 0, 0, 0, 1)
189 ctx.show_layout(layout_left)
190 ctx.move_to(x-1+x_border, y+border)
191 ctx.set_source_rgba( 1, 1, 1, 1)
192 ctx.show_layout(layout_left)
194 if right_text is not None:
195 draw_rounded_rectangle(ctx, x, y, rect_width, rect_height, radius, left_side_width, RRECT_RIGHT_SIDE, left_text is None)
196 linear = cairo.LinearGradient(x+left_side_width, y, x+left_side_width+right_side_width/2, y+rect_height)
197 linear.add_color_stop_rgba(0, .2, .2, .2, .9)
198 linear.add_color_stop_rgba(.4, .2, .2, .2, .8)
199 linear.add_color_stop_rgba(.6, .2, .2, .2, .6)
200 linear.add_color_stop_rgba(.9, .2, .2, .2, .7)
201 linear.add_color_stop_rgba(1, .2, .2, .2, .5)
202 ctx.set_source(linear)
203 ctx.fill()
204 xpos, ypos, width, height = x, y+1, rect_width-1, rect_height-2
205 if left_text is None:
206 xpos, width = x+1, rect_width-2
207 draw_rounded_rectangle(ctx, xpos, ypos, width, height, radius, left_side_width, RRECT_RIGHT_SIDE, left_text is None)
208 ctx.set_source_rgba(1., 1., 1., .3)
209 ctx.set_line_width(1)
210 ctx.stroke()
211 draw_rounded_rectangle(ctx, x, y, rect_width, rect_height, radius, left_side_width, RRECT_RIGHT_SIDE, left_text is None)
212 ctx.set_source_rgba(.1, .1, .1, .6)
213 ctx.set_line_width(1)
214 ctx.stroke()
216 ctx.move_to(x+left_side_width+x_border, y+1+border)
217 ctx.set_source_rgba( 0, 0, 0, 1)
218 ctx.show_layout(layout_right)
219 ctx.move_to(x-1+left_side_width+x_border, y+border)
220 ctx.set_source_rgba( 1, 1, 1, 1)
221 ctx.show_layout(layout_right)
223 return surface
226 def draw_pill_pixbuf(left_text, right_text):
227 return cairo_surface_to_pixbuf(draw_text_pill(left_text, right_text))
230 def cairo_surface_to_pixbuf(s):
232 Converts a Cairo surface to a Gtk Pixbuf by
233 encoding it as PNG and using the PixbufLoader.
235 sio = StringIO.StringIO()
236 try:
237 s.write_to_png(sio)
238 except:
239 # Write an empty PNG file to the StringIO, so
240 # in case of an error we have "something" to
241 # load. This happens in PyCairo < 1.1.6, see:
242 # http://webcvs.cairographics.org/pycairo/NEWS?view=markup
243 # Thanks to Chris Arnold for reporting this bug
244 sio.write('iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAAZiS0dEAP8A\n/wD/oL2nkwAAAAlwSFlzAAALEwAACxMBAJqcGAAAAAd0SU1FB9cMEQkqIyxn3RkAAAAZdEVYdENv\nbW1lbnQAQ3JlYXRlZCB3aXRoIEdJTVBXgQ4XAAAADUlEQVQI12NgYGBgAAAABQABXvMqOgAAAABJ\nRU5ErkJggg==\n'.decode('base64'))
246 pbl = gtk.gdk.PixbufLoader()
247 pbl.write(sio.getvalue())
248 pbl.close()
250 pixbuf = pbl.get_pixbuf()
251 return pixbuf
253 def progressbar_pixbuf(width, height, percentage):
254 COLOR_BG = (.4, .4, .4, .4)
255 COLOR_FG = (.2, .9, .2, 1.)
256 COLOR_FG_HIGH = (1., 1., 1., .5)
257 COLOR_BORDER = (0., 0., 0., 1.)
259 surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height)
260 ctx = cairo.Context(surface)
262 padding = int(float(width)/8.0)
263 bar_width = 2*padding
264 bar_height = height - 2*padding
265 bar_height_fill = bar_height*percentage
267 # Background
268 ctx.rectangle(padding, padding, bar_width, bar_height)
269 ctx.set_source_rgba(*COLOR_BG)
270 ctx.fill()
272 # Foreground
273 ctx.rectangle(padding, padding+bar_height-bar_height_fill, bar_width, bar_height_fill)
274 ctx.set_source_rgba(*COLOR_FG)
275 ctx.fill()
276 ctx.rectangle(padding+bar_width/3, padding+bar_height-bar_height_fill, bar_width/4, bar_height_fill)
277 ctx.set_source_rgba(*COLOR_FG_HIGH)
278 ctx.fill()
280 # Border
281 ctx.rectangle(padding-.5, padding-.5, bar_width+1, bar_height+1)
282 ctx.set_source_rgba(*COLOR_BORDER)
283 ctx.set_line_width(1.)
284 ctx.stroke()
286 return cairo_surface_to_pixbuf(surface)