a few small bugfixes for the extension system
[gpodder.git] / src / gpodder / gtkui / desktop / extensions.py
blobf4392c1fde4192648190d5076018ea98b4b77ec5
1 # -*- coding: utf-8 -*-
3 # gPodder - A media aggregator and podcast client
4 # Copyright (c) 2005-2011 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 pango
22 import os.path
24 import gpodder
26 _ = gpodder.gettext
27 N_ = gpodder.ngettext
29 import logging
30 logger = logging.getLogger(__name__)
32 from gpodder.gtkui.interface.common import BuilderWidget
35 class ExtensionTextItem(gtk.VBox):
36 def __init__(self, key, settings, value):
37 gtk.VBox.__init__(self, spacing=6)
38 self.set_border_width(6)
39 self.key = key
41 self.__label = gtk.Label(settings['desc'])
42 self.__label.set_alignment(0, 0)
44 self.__entry = gtk.Entry(max=0)
45 self.__entry.set_visibility(True)
46 self.__entry.set_editable(True)
47 self.__entry.set_text(value)
48 self.pack_start(self.__label, False, False, 0)
49 self.pack_start(self.__entry, False, False, 0)
51 def get_key(self):
52 return self.key
54 def get_value(self):
55 return self.__entry.get_text()
57 def set_value(self, settings, value):
58 self.__entry.set_text(value)
61 class ExtensionCheckBox(gtk.CheckButton):
62 def __init__(self, key, settings, value):
63 gtk.CheckButton.__init__(self)
64 self.set_border_width(6)
65 self.key = key
67 self.set_label(settings['desc'])
68 self.set_active(value)
70 def get_key(self):
71 return self.key
73 def get_value(self):
74 return self.get_active()
76 def set_value(self, settings, value):
77 self.set_active(value)
80 class ExtensionSpinButton(gtk.VBox):
81 def __init__(self, key, settings, value):
82 gtk.VBox.__init__(self, spacing=6)
83 self.set_border_width(6)
84 self.key = key
86 self.__label = gtk.Label(settings['desc'])
87 self.__label.set_alignment(0, 0)
89 self.__spin = gtk.SpinButton(
90 adjustment=gtk.Adjustment(value, 0, 1000, 1),
91 climb_rate=1, digits=2
94 self.pack_start(self.__label, False, False, 0)
95 self.pack_start(self.__spin, False, False, 0)
97 def get_key(self):
98 return self.key
100 def get_value(self):
101 return self.__spin.get_value()
103 def set_value(self, settings, value):
104 self.__spin.set_value(value)
107 class ExtensionMultiChoice(gtk.VBox):
108 def __init__(self, key, settings, value):
109 gtk.VBox.__init__(self, spacing=6)
110 self.set_border_width(6)
111 self.key = key
113 self.__label = gtk.Label(settings['desc'])
114 self.__label.set_alignment(0, 0)
116 self.__treeView = gtk.TreeView(self._get_model(settings, value))
118 toggle_cell = gtk.CellRendererToggle()
119 toggle_cell.connect('toggled', self.value_changed, self.__treeView)
120 toggle_column = gtk.TreeViewColumn('', toggle_cell, active=0)
121 toggle_column.set_clickable(True)
122 self.__treeView.append_column(toggle_column)
124 renderer = gtk.CellRendererText()
125 renderer.set_property('ellipsize', pango.ELLIPSIZE_END)
126 column = gtk.TreeViewColumn(_('Podcast'), renderer, markup=1)
127 column.set_clickable(False)
128 column.set_resizable(True)
129 column.set_expand(True)
130 self.__treeView.append_column(column)
131 self.__treeView.columns_autosize()
133 self.pack_start(self.__label, False, False, 0)
134 self.pack_start(self.__treeView, False, False, 0)
136 def _get_model(self, settings, value):
137 model = gtk.ListStore(bool, str, int)
138 multichoice_list = zip(value, settings['list'])
139 for index, (state, text) in enumerate(multichoice_list):
140 model.append(row=(state, text, index))
141 return model
143 def value_changed(self, cell, path, treeview):
144 model = treeview.get_model()
145 iter = model.get_iter(path)
146 value = model.get_value(iter, 0)
147 model.set_value(iter, 0, not value)
149 def get_key(self):
150 return self.key
152 def get_value(self):
153 model = self.__treeView.get_model()
154 result = []
155 iter = model.get_iter_first()
156 while iter is not None:
157 value = model.get_value(iter, 0)
158 result.append(value)
159 iter = model.iter_next(iter)
160 return result
162 def set_value(self, settings, value):
163 model = self._get_model(settings, value)
164 self.__treeView.set_model(model)
167 class ExtensionRadioGroup(gtk.VBox):
168 def __init__(self, key, settings, value):
169 gtk.VBox.__init__(self, spacing=6)
170 self.set_border_width(6)
171 self.key = key
173 self.__label = gtk.Label(settings['desc'])
174 self.__label.set_alignment(0, 0)
176 self.__radioButtons = []
177 choices = zip(value, settings['list'])
178 group = None
179 for state, label in choices:
180 rb = gtk.RadioButton(group, label, False)
181 rb.set_active(state)
182 group = rb
183 self.__radioButtons.append(rb)
185 self.pack_start(self.__label, False, False, 0)
186 for rb in self.__radioButtons:
187 self.pack_start(rb, False, False, 0)
189 def get_key(self):
190 return self.key
192 def get_value(self):
193 values = []
194 for rb in self.__radioButtons:
195 values.append(rb.get_active())
196 return values
198 def set_value(self, settings, value):
199 choices = zip(value, self.__radioButtons)
200 for state, rb in choices:
201 rb.set_active(state)
204 #TODO: Replace this implementation with a ComboBoxEntry or expandable TreeView
205 class ExtensionComboBoxEntry(ExtensionTextItem):
206 def __init__(self, key, settings, value):
207 value = ';'.join(value)
208 ExtensionTextItem.__init__(self, key, settings, value)
210 def get_value(self):
211 value = super(ExtensionComboBoxEntry, self).get_value()
212 return value.split(';')
214 def set_value(self, settings, value):
215 value = ';'.join(value)
216 super(ExtensionComboBoxEntry, self).set_value(settings, value)
219 class gPodderExtensionPreference(BuilderWidget):
220 widgets = { 'textitem': ExtensionTextItem,
221 'checkbox': ExtensionCheckBox,
222 'spinbutton': ExtensionSpinButton,
223 'multichoice-list': ExtensionMultiChoice,
224 'combobox': ExtensionComboBoxEntry,
225 'radiogroup': ExtensionRadioGroup,
228 def new(self):
229 """Extension Preference Dialog
231 Optional keyword arguments that modify the behaviour of this dialog:
233 - _extenstion_container: ExtensionContainer class for which the preferences should be displayed
234 {'cmd': {
235 'type': 'str',
236 'desc': 'Defines the command line bittorrent program'}
239 self.params = self._extension_container.params
240 self.config = self._extension_container.config
242 self.vbox = gtk.VBox()
243 self.viewport_extensionpref.add(self.vbox)
244 for key, settings in self.params.items():
245 value = getattr(self._extension_container.config, key)
246 widget = self.widgets[settings['type']](key, settings, value)
247 self.vbox.pack_start(widget, False, False, 0)
249 self.gPodderExtensionPreference.show_all()
251 def on_btnRevert_clicked(self, widget):
252 self._extension_container.revert_settings()
253 self.config = self._extension_container.config
255 for w in self.vbox.get_children():
256 key = w.get_key()
257 value = getattr(self._extension_container.config, key)
258 w.set_value(self.params[key], value)
260 def on_btnClose_clicked(self, widget):
261 for w in self.vbox.get_children():
262 key = w.get_key()
263 value = w.get_value()
264 setattr(self._extension_container.config, key, value)
266 self.main_window.destroy()
269 class gPodderExtensionManager(BuilderWidget):
270 C_INDEX, C_TOOLTIP, C_TOGGLE, C_NAME, C_ID, C_EXTENSIONCONTAINER = range(6)
272 def new(self):
273 toggle_cell = gtk.CellRendererToggle()
274 toggle_cell.connect('toggled', self.toggle_cell_handler)
275 toggle_column = gtk.TreeViewColumn('', toggle_cell, active=self.C_TOGGLE)
276 toggle_column.set_clickable(True)
277 self.treeviewExtensions.append_column(toggle_column)
279 renderer = gtk.CellRendererText()
280 renderer.set_property('ellipsize', pango.ELLIPSIZE_END)
281 column = gtk.TreeViewColumn(_('Name'), renderer, markup=self.C_NAME)
282 column.set_clickable(False)
283 column.set_resizable(True)
284 column.set_expand(True)
285 self.treeviewExtensions.append_column(column)
287 renderer = gtk.CellRendererText()
288 column = gtk.TreeViewColumn(_('Extension-ID'), renderer, markup=self.C_ID)
289 column.set_clickable(False)
290 column.set_resizable(True)
291 column.set_expand(False)
292 self.treeviewExtensions.append_column(column)
294 self.treeviewExtensions.set_property('has-tooltip', True)
295 self.treeviewExtensions.connect('query-tooltip', self.treeview_show_tooltip)
297 column_types = [ int, str, bool, str, str, object ]
298 self.model = gtk.ListStore(*column_types)
300 for index, (extension_container, state) in enumerate( gpodder.user_extensions.get_extensions() ):
301 if extension_container.metadata:
302 tooltip = extension_container.metadata['desc']
303 name = extension_container.metadata['name']
304 row = [ index, tooltip, state, name,
305 extension_container.metadata['id'], extension_container
307 self.model.append(row)
309 self.model.set_sort_column_id(self.C_NAME, gtk.SORT_ASCENDING)
310 self.treeviewExtensions.set_model(self.model)
311 self.treeviewExtensions.columns_autosize()
313 self.context_id = self.extension_statusbar.get_context_id('Extension messages')
315 def _set_enabled_extension_in_config(self, model, path):
316 iter = model.get_iter(path)
317 value = model.get_value(iter, self.C_TOGGLE)
318 name = model.get_value(iter, self.C_NAME)
319 extension_id = model.get_value(iter, self.C_ID)
320 extension_container = model.get_value(iter, self.C_EXTENSIONCONTAINER)
321 new_value = not value
323 if new_value and extension_id not in self._config.extensions.enabled:
324 try:
325 extension_container.load_extension()
326 except Exception, e:
327 self.extension_statusbar.push(self.context_id, "Error %s: %s" % (name, e))
328 return
330 self._config.extensions.enabled.append(extension_id)
331 self.extension_statusbar.remove_all(self.context_id)
333 if not new_value and extension_id in self._config.extensions.enabled:
334 self._config.extensions.enabled.remove(extension_id)
336 self._config.schedule_save()
337 self._set_preferences_button(not value)
338 model.set_value(iter, self.C_TOGGLE, not value)
340 def _get_selected_extension_container(self):
341 selection = self.treeviewExtensions.get_selection()
342 model, iter = selection.get_selected()
343 if not iter:
344 return None
346 return model.get_value(iter, self.C_EXTENSIONCONTAINER)
348 def _set_preferences_button(self, value):
349 extension = self._get_selected_extension_container()
351 if extension and extension.params is not None and value:
352 self.btnExtensionPrefs.set_sensitive(True)
353 else:
354 self.btnExtensionPrefs.set_sensitive(False)
356 def on_button_close_clicked(self, widget):
357 # sync enabled/disabled extensions
358 gpodder.user_extensions.get_extensions()
360 # close extension preference window
361 self.main_window.destroy()
363 def on_btnOK_clicked(self, widget):
364 self.on_button_close_clicked(widget)
366 def on_btnExtensionPrefs_clicked(self, widget):
367 gPodderExtensionPreference(self.main_window,
368 _extension_container = self._get_selected_extension_container()
371 def toggle_cell_handler(self, cell, path):
372 model = self.treeviewExtensions.get_model()
373 self._set_enabled_extension_in_config(model, path)
375 def on_row_activated(self, treeview, path, view_column):
376 model = treeview.get_model()
377 self._set_enabled_extension_in_config(model, path)
379 def on_selection_changed(self, treeselection):
380 model, iter = treeselection.get_selected()
381 if not iter:
382 value = False
383 else:
384 value = model.get_value(iter, self.C_TOGGLE)
386 self._set_preferences_button(value)
388 def treeview_show_tooltip(self, treeview, x, y, keyboard_tooltip, tooltip):
389 # TODO: Copied some of the code from src/gpodder/gtkui/desktop/episodeselector.py (gPodderEpisodeSelector.treeview_episodes_query_tooltip)
390 # maybe we should don't duplicate the code and implement this as a function globaly?!
392 # With get_bin_window, we get the window that contains the rows without
393 # the header. The Y coordinate of this window will be the height of the
394 # treeview header. This is the amount we have to subtract from the
395 # event's Y coordinate to get the coordinate to pass to get_path_at_pos
396 (x_bin, y_bin) = treeview.get_bin_window().get_position()
397 y -= x_bin
398 y -= y_bin
399 (path, column, rx, ry) = treeview.get_path_at_pos(x, y) or (None,)*4
401 if column != treeview.get_columns()[1]:
402 return False
404 model = treeview.get_model()
405 iter = model.get_iter(path)
406 description = model.get_value(iter, self.C_TOOLTIP)
407 if description:
408 tooltip.set_text(description)
409 return True
410 else:
411 return False