Update copyright years for 2013
[gpodder.git] / src / gpodder / gtkui / interface / common.py
blobdedd2b8ae25b37506dd30b8a195af706550c20e7
1 # -*- coding: utf-8 -*-
3 # gPodder - A media aggregator and podcast client
4 # Copyright (c) 2005-2013 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/>.
20 import gtk
21 import os
22 import shutil
24 import gpodder
26 _ = gpodder.gettext
28 from gpodder import util
30 from gpodder.gtkui.base import GtkBuilderWidget
33 class BuilderWidget(GtkBuilderWidget):
34 def __init__(self, parent, **kwargs):
35 self._window_iconified = False
36 self._window_visible = False
38 GtkBuilderWidget.__init__(self, gpodder.ui_folders, gpodder.textdomain, **kwargs)
40 # Enable support for tracking iconified state
41 if hasattr(self, 'on_iconify') and hasattr(self, 'on_uniconify'):
42 self.main_window.connect('window-state-event', \
43 self._on_window_state_event_iconified)
45 # Enable support for tracking visibility state
46 self.main_window.connect('visibility-notify-event', \
47 self._on_window_state_event_visibility)
49 if parent is not None:
50 self.main_window.set_transient_for(parent)
52 if hasattr(self, 'center_on_widget'):
53 (x, y) = parent.get_position()
54 a = self.center_on_widget.allocation
55 (x, y) = (x + a.x, y + a.y)
56 (w, h) = (a.width, a.height)
57 (pw, ph) = self.main_window.get_size()
58 self.main_window.move(x + w/2 - pw/2, y + h/2 - ph/2)
60 def _on_window_state_event_visibility(self, widget, event):
61 if event.state & gtk.gdk.VISIBILITY_FULLY_OBSCURED:
62 self._window_visible = False
63 else:
64 self._window_visible = True
66 return False
68 def _on_window_state_event_iconified(self, widget, event):
69 if event.new_window_state & gtk.gdk.WINDOW_STATE_ICONIFIED:
70 if not self._window_iconified:
71 self._window_iconified = True
72 self.on_iconify()
73 else:
74 if self._window_iconified:
75 self._window_iconified = False
76 self.on_uniconify()
78 return False
80 def is_iconified(self):
81 return self._window_iconified
83 def notification(self, message, title=None, important=False, widget=None):
84 util.idle_add(self.show_message, message, title, important, widget)
86 def get_dialog_parent(self):
87 """Return a gtk.Window that should be the parent of dialogs"""
88 return self.main_window
90 def show_message(self, message, title=None, important=False, widget=None):
91 if important:
92 dlg = gtk.MessageDialog(self.main_window, gtk.DIALOG_MODAL, gtk.MESSAGE_INFO, gtk.BUTTONS_OK)
93 if title:
94 dlg.set_title(str(title))
95 dlg.set_markup('<span weight="bold" size="larger">%s</span>\n\n%s' % (title, message))
96 else:
97 dlg.set_markup('<span weight="bold" size="larger">%s</span>' % (message))
98 dlg.run()
99 dlg.destroy()
100 else:
101 gpodder.user_extensions.on_notification_show(title, message)
103 def show_confirmation(self, message, title=None):
104 dlg = gtk.MessageDialog(self.main_window, gtk.DIALOG_MODAL, gtk.MESSAGE_QUESTION, gtk.BUTTONS_YES_NO)
105 if title:
106 dlg.set_title(str(title))
107 dlg.set_markup('<span weight="bold" size="larger">%s</span>\n\n%s' % (title, message))
108 else:
109 dlg.set_markup('<span weight="bold" size="larger">%s</span>' % (message))
110 response = dlg.run()
111 dlg.destroy()
112 return response == gtk.RESPONSE_YES
114 def show_text_edit_dialog(self, title, prompt, text=None, empty=False, \
115 is_url=False, affirmative_text=gtk.STOCK_OK):
116 dialog = gtk.Dialog(title, self.get_dialog_parent(), \
117 gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT)
119 dialog.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
120 dialog.add_button(affirmative_text, gtk.RESPONSE_OK)
122 dialog.set_has_separator(False)
123 dialog.set_default_size(300, -1)
124 dialog.set_default_response(gtk.RESPONSE_OK)
126 text_entry = gtk.Entry()
127 text_entry.set_activates_default(True)
128 if text is not None:
129 text_entry.set_text(text)
130 text_entry.select_region(0, -1)
132 if not empty:
133 def on_text_changed(editable):
134 can_confirm = (editable.get_text() != '')
135 dialog.set_response_sensitive(gtk.RESPONSE_OK, can_confirm)
136 text_entry.connect('changed', on_text_changed)
137 if text is None:
138 dialog.set_response_sensitive(gtk.RESPONSE_OK, False)
140 hbox = gtk.HBox()
141 hbox.set_border_width(10)
142 hbox.set_spacing(10)
143 hbox.pack_start(gtk.Label(prompt), False, False)
144 hbox.pack_start(text_entry, True, True)
145 dialog.vbox.pack_start(hbox, True, True)
147 dialog.show_all()
148 response = dialog.run()
149 result = text_entry.get_text()
150 dialog.destroy()
152 if response == gtk.RESPONSE_OK:
153 return result
154 else:
155 return None
157 def show_login_dialog(self, title, message, username=None, password=None,
158 username_prompt=None, register_callback=None, register_text=None):
159 if username_prompt is None:
160 username_prompt = _('Username')
162 if register_text is None:
163 register_text = _('New user')
165 dialog = gtk.MessageDialog(
166 self.main_window,
167 gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
168 gtk.MESSAGE_QUESTION,
169 gtk.BUTTONS_CANCEL)
170 dialog.add_button(_('Login'), gtk.RESPONSE_OK)
171 dialog.set_image(gtk.image_new_from_stock(gtk.STOCK_DIALOG_AUTHENTICATION, gtk.ICON_SIZE_DIALOG))
172 dialog.set_title(_('Authentication required'))
173 dialog.set_markup('<span weight="bold" size="larger">' + title + '</span>')
174 dialog.format_secondary_markup(message)
175 dialog.set_default_response(gtk.RESPONSE_OK)
177 if register_callback is not None:
178 dialog.add_button(register_text, gtk.RESPONSE_HELP)
180 username_entry = gtk.Entry()
181 password_entry = gtk.Entry()
183 username_entry.connect('activate', lambda w: password_entry.grab_focus())
184 password_entry.set_visibility(False)
185 password_entry.set_activates_default(True)
187 if username is not None:
188 username_entry.set_text(username)
189 if password is not None:
190 password_entry.set_text(password)
192 table = gtk.Table(2, 2)
193 table.set_row_spacings(6)
194 table.set_col_spacings(6)
196 username_label = gtk.Label()
197 username_label.set_markup('<b>' + username_prompt + ':</b>')
198 username_label.set_alignment(0.0, 0.5)
199 table.attach(username_label, 0, 1, 0, 1, gtk.FILL, 0)
200 table.attach(username_entry, 1, 2, 0, 1)
202 password_label = gtk.Label()
203 password_label.set_markup('<b>' + _('Password') + ':</b>')
204 password_label.set_alignment(0.0, 0.5)
205 table.attach(password_label, 0, 1, 1, 2, gtk.FILL, 0)
206 table.attach(password_entry, 1, 2, 1, 2)
208 dialog.vbox.pack_end(table, True, True, 0)
209 dialog.show_all()
210 response = dialog.run()
212 while response == gtk.RESPONSE_HELP:
213 register_callback()
214 response = dialog.run()
216 password_entry.set_visibility(True)
217 username = username_entry.get_text()
218 password = password_entry.get_text()
219 success = (response == gtk.RESPONSE_OK)
221 dialog.destroy()
223 return (success, (username, password))
225 def show_copy_dialog(self, src_filename, dst_filename=None, dst_directory=None, title=_('Select destination')):
226 if dst_filename is None:
227 dst_filename = src_filename
229 if dst_directory is None:
230 dst_directory = os.path.expanduser('~')
232 base, extension = os.path.splitext(src_filename)
234 if not dst_filename.endswith(extension):
235 dst_filename += extension
237 dlg = gtk.FileChooserDialog(title=title, parent=self.main_window, action=gtk.FILE_CHOOSER_ACTION_SAVE)
238 dlg.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
239 dlg.add_button(gtk.STOCK_SAVE, gtk.RESPONSE_OK)
241 dlg.set_do_overwrite_confirmation(True)
242 dlg.set_current_name(os.path.basename(dst_filename))
243 dlg.set_current_folder(dst_directory)
245 result = False
246 folder = dst_directory
247 if dlg.run() == gtk.RESPONSE_OK:
248 result = True
249 dst_filename = dlg.get_filename()
250 folder = dlg.get_current_folder()
251 if not dst_filename.endswith(extension):
252 dst_filename += extension
254 shutil.copyfile(src_filename, dst_filename)
256 dlg.destroy()
257 return (result, folder)
259 class TreeViewHelper(object):
260 """Container for gPodder-specific TreeView attributes."""
261 LAST_TOOLTIP = '_gpodder_last_tooltip'
262 CAN_TOOLTIP = '_gpodder_can_tooltip'
263 ROLE = '_gpodder_role'
264 COLUMNS = '_gpodder_columns'
266 # Enum for the role attribute
267 ROLE_PODCASTS, ROLE_EPISODES, ROLE_DOWNLOADS = range(3)
269 @classmethod
270 def set(cls, treeview, role):
271 setattr(treeview, cls.LAST_TOOLTIP, None)
272 setattr(treeview, cls.CAN_TOOLTIP, True)
273 setattr(treeview, cls.ROLE, role)
275 @staticmethod
276 def make_search_equal_func(gpodder_model):
277 def func(model, column, key, iter):
278 if model is None:
279 return True
280 key = key.lower()
281 for column in gpodder_model.SEARCH_COLUMNS:
282 if key in model.get_value(iter, column).lower():
283 return False
284 return True
285 return func
287 @classmethod
288 def register_column(cls, treeview, column):
289 if not hasattr(treeview, cls.COLUMNS):
290 setattr(treeview, cls.COLUMNS, [])
292 columns = getattr(treeview, cls.COLUMNS)
293 columns.append(column)
295 @classmethod
296 def get_columns(cls, treeview):
297 return getattr(treeview, cls.COLUMNS, [])
299 @staticmethod
300 def make_popup_position_func(widget):
301 def position_func(menu):
302 x, y = widget.window.get_origin()
303 return (x, y, True)
304 return position_func