Maemo 5: Fix window and button layout (Maemo bug 11499)
[gpodder.git] / src / gpodder / gtkui / widgets.py
blobf8ce64babe3d0901b06bd9308a99da81bf31b436
1 #!/usr/bin/python
2 # -*- coding: utf-8 -*-
4 # gPodder - A media aggregator and podcast client
5 # Copyright (c) 2005-2010 Thomas Perl and the gPodder Team
7 # gPodder is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 3 of the License, or
10 # (at your option) any later version.
12 # gPodder is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
23 # widgets.py -- Additional widgets for gPodder
24 # Thomas Perl <thp@gpodder.org> 2009-03-31
27 import gtk
28 import gobject
29 import pango
31 from xml.sax import saxutils
33 class SimpleMessageArea(gtk.HBox):
34 """A simple, yellow message area. Inspired by gedit.
36 Original C source code:
37 http://svn.gnome.org/viewvc/gedit/trunk/gedit/gedit-message-area.c
38 """
39 def __init__(self, message, buttons=()):
40 gtk.HBox.__init__(self, spacing=6)
41 self.set_border_width(6)
42 self.__in_style_set = False
43 self.connect('style-set', self.__style_set)
44 self.connect('expose-event', self.__expose_event)
46 self.__label = gtk.Label()
47 self.__label.set_alignment(0.0, 0.5)
48 self.__label.set_line_wrap(False)
49 self.__label.set_ellipsize(pango.ELLIPSIZE_END)
50 self.__label.set_markup('<b>%s</b>' % saxutils.escape(message))
51 self.pack_start(self.__label, expand=True, fill=True)
53 hbox = gtk.HBox()
54 for button in buttons:
55 hbox.pack_start(button, expand=True, fill=False)
56 self.pack_start(hbox, expand=False, fill=False)
58 def set_markup(self, markup, line_wrap=True, min_width=3, max_width=100):
59 # The longest line should determine the size of the label
60 width_chars = max(len(line) for line in markup.splitlines())
62 # Enforce upper and lower limits for the width
63 width_chars = max(min_width, min(max_width, width_chars))
65 self.__label.set_width_chars(width_chars)
66 self.__label.set_markup(markup)
67 self.__label.set_line_wrap(line_wrap)
69 def __style_set(self, widget, previous_style):
70 if self.__in_style_set:
71 return
73 w = gtk.Window(gtk.WINDOW_POPUP)
74 w.set_name('gtk-tooltip')
75 w.ensure_style()
76 style = w.get_style()
78 self.__in_style_set = True
79 self.set_style(style)
80 self.__label.set_style(style)
81 self.__in_style_set = False
83 w.destroy()
85 self.queue_draw()
87 def __expose_event(self, widget, event):
88 style = widget.get_style()
89 rect = widget.get_allocation()
90 style.paint_flat_box(widget.window, gtk.STATE_NORMAL,
91 gtk.SHADOW_OUT, None, widget, "tooltip",
92 rect.x, rect.y, rect.width, rect.height)
93 return False
95 class NotificationWindow(gtk.Window):
96 """A quick substitution widget for pynotify notifications."""
97 def __init__(self, message, title=None, important=False, widget=None):
98 gtk.Window.__init__(self, gtk.WINDOW_POPUP)
99 self._finished = False
100 message_area = SimpleMessageArea('')
101 arrow = gtk.image_new_from_stock(gtk.STOCK_GO_UP, \
102 gtk.ICON_SIZE_BUTTON)
103 arrow.set_alignment(.5, 0.)
104 arrow.set_padding(6, 0)
105 message_area.pack_start(arrow, False)
106 message_area.reorder_child(arrow, 0)
107 if title is not None:
108 message_area.set_markup('<b>%s</b>\n<small>%s</small>' % (saxutils.escape(title), saxutils.escape(message)))
109 else:
110 message_area.set_markup(saxutils.escape(message))
111 self.add(message_area)
112 self.set_gravity(gtk.gdk.GRAVITY_NORTH_WEST)
113 self.show_all()
114 if widget is not None:
115 _x, _y, ww, hh, _depth = self.window.get_geometry()
116 parent = widget
117 while not isinstance(parent, gtk.Window):
118 parent = parent.get_parent()
119 x, y, _w, _h, _depth = parent.window.get_geometry()
120 rect = widget.allocation
121 w, h = rect.width, rect.height
122 x += rect.x
123 y += rect.y
124 arrow_rect = arrow.allocation
125 if h < hh or w < ww:
126 self.move(x+w/2-arrow_rect.x-arrow_rect.width/2, y+h-5)
127 else:
128 self.move(x+w/2-ww/2, y+h/2-hh/2+20)
129 message_area.remove(arrow)
131 def show_timeout(self, timeout=8000):
132 gobject.timeout_add(timeout, self._hide_and_destroy)
133 self.show_all()
135 def _hide_and_destroy(self):
136 if not self._finished:
137 self.destroy()
138 self._finished = True
139 return False
141 class SpinningProgressIndicator(gtk.Image):
142 # Progress indicator loading inspired by glchess from gnome-games-clutter
143 def __init__(self, size=32):
144 gtk.Image.__init__(self)
146 self._frames = []
147 self._frame_id = 0
149 # Load the progress indicator
150 icon_theme = gtk.icon_theme_get_default()
152 ICON = lambda x: x
153 try:
154 icon = icon_theme.load_icon(ICON('process-working'), size, 0)
155 width, height = icon.get_width(), icon.get_height()
156 if width < size or height < size:
157 size = min(width, height)
158 for row in range(height/size):
159 for column in range(width/size):
160 frame = icon.subpixbuf(column*size, row*size, size, size)
161 self._frames.append(frame)
162 # Remove the first frame (the "idle" icon)
163 if self._frames:
164 self._frames.pop(0)
165 self.step_animation()
166 except:
167 # FIXME: This is not very beautiful :/
168 self.set_from_stock(gtk.STOCK_EXECUTE, gtk.ICON_SIZE_BUTTON)
170 def step_animation(self):
171 if len(self._frames) > 1:
172 self._frame_id += 1
173 if self._frame_id >= len(self._frames):
174 self._frame_id = 0
175 self.set_from_pixbuf(self._frames[self._frame_id])