Merge pull request #1303 from auouymous/setting-to-disable-find-as-you-type
[gpodder.git] / src / gpodder / gtkui / desktop / preferences.py
blobb19108ef83b238ba80057b58eecbc4d85a3d4836
1 # -*- coding: utf-8 -*-
3 # gPodder - A media aggregator and podcast client
4 # Copyright (c) 2005-2018 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 html
21 import logging
22 import urllib.parse
24 from gi.repository import Gdk, Gtk, Pango
26 import gpodder
27 from gpodder import util, vimeo, youtube
28 from gpodder.gtkui.desktopfile import PlayerListModel
29 from gpodder.gtkui.interface.common import (BuilderWidget, TreeViewHelper,
30 show_message_dialog)
31 from gpodder.gtkui.interface.configeditor import gPodderConfigEditor
33 logger = logging.getLogger(__name__)
35 _ = gpodder.gettext
36 N_ = gpodder.ngettext
39 class NewEpisodeActionList(Gtk.ListStore):
40 C_CAPTION, C_AUTO_DOWNLOAD = list(range(2))
42 ACTION_NONE, ACTION_ASK, ACTION_MINIMIZED, ACTION_ALWAYS = list(range(4))
44 def __init__(self, config):
45 Gtk.ListStore.__init__(self, str, str)
46 self._config = config
47 self.append((_('Do nothing'), 'ignore'))
48 self.append((_('Show episode list'), 'show'))
49 self.append((_('Add to download list'), 'queue'))
50 self.append((_('Download immediately'), 'download'))
52 def get_index(self):
53 for index, row in enumerate(self):
54 if self._config.auto_download == row[self.C_AUTO_DOWNLOAD]:
55 return index
57 return 1 # Some sane default
59 def set_index(self, index):
60 self._config.auto_download = self[index][self.C_AUTO_DOWNLOAD]
63 class DeviceTypeActionList(Gtk.ListStore):
64 C_CAPTION, C_DEVICE_TYPE = list(range(2))
66 def __init__(self, config):
67 Gtk.ListStore.__init__(self, str, str)
68 self._config = config
69 self.append((_('None'), 'none'))
70 self.append((_('iPod'), 'ipod'))
71 self.append((_('Filesystem-based'), 'filesystem'))
73 def get_index(self):
74 for index, row in enumerate(self):
75 if self._config.device_sync.device_type == row[self.C_DEVICE_TYPE]:
76 return index
77 return 0 # Some sane default
79 def set_index(self, index):
80 self._config.device_sync.device_type = self[index][self.C_DEVICE_TYPE]
83 class OnSyncActionList(Gtk.ListStore):
84 C_CAPTION, C_ON_SYNC_DELETE, C_ON_SYNC_MARK_PLAYED = list(range(3))
85 ACTION_NONE, ACTION_ASK, ACTION_MINIMIZED, ACTION_ALWAYS = list(range(4))
87 def __init__(self, config):
88 Gtk.ListStore.__init__(self, str, bool, bool)
89 self._config = config
90 self.append((_('Do nothing'), False, False))
91 self.append((_('Mark as played'), False, True))
92 self.append((_('Delete from gPodder'), True, False))
94 def get_index(self):
95 for index, row in enumerate(self):
96 if (self._config.device_sync.after_sync.delete_episodes and
97 row[self.C_ON_SYNC_DELETE]):
98 return index
99 if (self._config.device_sync.after_sync.mark_episodes_played and
100 row[self.C_ON_SYNC_MARK_PLAYED] and not
101 self._config.device_sync.after_sync.delete_episodes):
102 return index
103 return 0 # Some sane default
105 def set_index(self, index):
106 self._config.device_sync.after_sync.delete_episodes = self[index][self.C_ON_SYNC_DELETE]
107 self._config.device_sync.after_sync.mark_episodes_played = self[index][self.C_ON_SYNC_MARK_PLAYED]
110 class YouTubeVideoFormatListModel(Gtk.ListStore):
111 C_CAPTION, C_ID = list(range(2))
113 def __init__(self, config):
114 Gtk.ListStore.__init__(self, str, int)
115 self._config = config
117 if self._config.youtube.preferred_fmt_ids:
118 caption = _('Custom (%(format_ids)s)') % {
119 'format_ids': ', '.join(str(x) for x in self._config.youtube.preferred_fmt_ids),
121 self.append((caption, 0))
123 for id, (fmt_id, path, description) in youtube.formats:
124 self.append((description, id))
126 def get_index(self):
127 for index, row in enumerate(self):
128 if self._config.youtube.preferred_fmt_id == row[self.C_ID]:
129 return index
130 return 0
132 def set_index(self, index):
133 self._config.youtube.preferred_fmt_id = self[index][self.C_ID]
136 class YouTubeVideoHLSFormatListModel(Gtk.ListStore):
137 C_CAPTION, C_ID = list(range(2))
139 def __init__(self, config):
140 Gtk.ListStore.__init__(self, str, int)
141 self._config = config
143 if self._config.youtube.preferred_hls_fmt_ids:
144 caption = _('Custom (%(format_ids)s)') % {
145 'format_ids': ', '.join(str(x) for x in self._config.youtube.preferred_hls_fmt_ids),
147 self.append((caption, 0))
149 for id, (fmt_id, path, description) in youtube.hls_formats:
150 self.append((description, id))
152 def get_index(self):
153 for index, row in enumerate(self):
154 if self._config.youtube.preferred_hls_fmt_id == row[self.C_ID]:
155 return index
156 return 0
158 def set_index(self, index):
159 self._config.youtube.preferred_hls_fmt_id = self[index][self.C_ID]
162 class VimeoVideoFormatListModel(Gtk.ListStore):
163 C_CAPTION, C_ID = list(range(2))
165 def __init__(self, config):
166 Gtk.ListStore.__init__(self, str, str)
167 self._config = config
169 for fileformat, description in vimeo.FORMATS:
170 self.append((description, fileformat))
172 def get_index(self):
173 for index, row in enumerate(self):
174 if self._config.vimeo.fileformat == row[self.C_ID]:
175 return index
176 return 0
178 def set_index(self, index):
179 value = self[index][self.C_ID]
180 if value is not None:
181 self._config.vimeo.fileformat = value
184 class gPodderPreferences(BuilderWidget):
185 C_TOGGLE, C_LABEL, C_EXTENSION, C_SHOW_TOGGLE = list(range(4))
187 def new(self):
188 self.gPodderPreferences.set_transient_for(self.parent_widget)
189 for cb in (self.combo_audio_player_app, self.combo_video_player_app):
190 cellrenderer = Gtk.CellRendererPixbuf()
191 cb.pack_start(cellrenderer, False)
192 cb.add_attribute(cellrenderer, 'pixbuf', PlayerListModel.C_ICON)
193 cellrenderer = Gtk.CellRendererText()
194 cellrenderer.set_property('ellipsize', Pango.EllipsizeMode.END)
195 cb.pack_start(cellrenderer, True)
196 cb.add_attribute(cellrenderer, 'markup', PlayerListModel.C_NAME)
197 cb.set_row_separator_func(PlayerListModel.is_separator)
199 self.audio_player_model = self.user_apps_reader.get_model('audio')
200 self.combo_audio_player_app.set_model(self.audio_player_model)
201 index = self.audio_player_model.get_index(self._config.player.audio)
202 self.combo_audio_player_app.set_active(index)
204 self.video_player_model = self.user_apps_reader.get_model('video')
205 self.combo_video_player_app.set_model(self.video_player_model)
206 index = self.video_player_model.get_index(self._config.player.video)
207 self.combo_video_player_app.set_active(index)
209 self.preferred_youtube_format_model = YouTubeVideoFormatListModel(self._config)
210 self.combobox_preferred_youtube_format.set_model(self.preferred_youtube_format_model)
211 cellrenderer = Gtk.CellRendererText()
212 cellrenderer.set_property('ellipsize', Pango.EllipsizeMode.END)
213 self.combobox_preferred_youtube_format.pack_start(cellrenderer, True)
214 self.combobox_preferred_youtube_format.add_attribute(cellrenderer, 'text', self.preferred_youtube_format_model.C_CAPTION)
215 self.combobox_preferred_youtube_format.set_active(self.preferred_youtube_format_model.get_index())
217 self.preferred_youtube_hls_format_model = YouTubeVideoHLSFormatListModel(self._config)
218 self.combobox_preferred_youtube_hls_format.set_model(self.preferred_youtube_hls_format_model)
219 cellrenderer = Gtk.CellRendererText()
220 cellrenderer.set_property('ellipsize', Pango.EllipsizeMode.END)
221 self.combobox_preferred_youtube_hls_format.pack_start(cellrenderer, True)
222 self.combobox_preferred_youtube_hls_format.add_attribute(cellrenderer, 'text', self.preferred_youtube_hls_format_model.C_CAPTION)
223 self.combobox_preferred_youtube_hls_format.set_active(self.preferred_youtube_hls_format_model.get_index())
225 self.preferred_vimeo_format_model = VimeoVideoFormatListModel(self._config)
226 self.combobox_preferred_vimeo_format.set_model(self.preferred_vimeo_format_model)
227 cellrenderer = Gtk.CellRendererText()
228 cellrenderer.set_property('ellipsize', Pango.EllipsizeMode.END)
229 self.combobox_preferred_vimeo_format.pack_start(cellrenderer, True)
230 self.combobox_preferred_vimeo_format.add_attribute(cellrenderer, 'text', self.preferred_vimeo_format_model.C_CAPTION)
231 self.combobox_preferred_vimeo_format.set_active(self.preferred_vimeo_format_model.get_index())
233 self._config.connect_gtk_togglebutton('podcast_list_view_all',
234 self.checkbutton_show_all_episodes)
235 self._config.connect_gtk_togglebutton('podcast_list_sections',
236 self.checkbutton_podcast_sections)
237 self._config.connect_gtk_togglebutton('ui.gtk.find_as_you_type',
238 self.checkbutton_find_as_you_type)
240 self.update_interval_presets = [0, 10, 30, 60, 2 * 60, 6 * 60, 12 * 60]
241 adjustment_update_interval = self.hscale_update_interval.get_adjustment()
242 adjustment_update_interval.set_upper(len(self.update_interval_presets) - 1)
243 if self._config.auto_update_frequency in self.update_interval_presets:
244 index = self.update_interval_presets.index(self._config.auto_update_frequency)
245 self.hscale_update_interval.set_value(index)
246 else:
247 # Patch in the current "custom" value into the mix
248 self.update_interval_presets.append(self._config.auto_update_frequency)
249 self.update_interval_presets.sort()
251 adjustment_update_interval.set_upper(len(self.update_interval_presets) - 1)
252 index = self.update_interval_presets.index(self._config.auto_update_frequency)
253 self.hscale_update_interval.set_value(index)
255 self._config.connect_gtk_spinbutton('max_episodes_per_feed', self.spinbutton_episode_limit)
257 self.auto_download_model = NewEpisodeActionList(self._config)
258 self.combo_auto_download.set_model(self.auto_download_model)
259 cellrenderer = Gtk.CellRendererText()
260 self.combo_auto_download.pack_start(cellrenderer, True)
261 self.combo_auto_download.add_attribute(cellrenderer, 'text', NewEpisodeActionList.C_CAPTION)
262 self.combo_auto_download.set_active(self.auto_download_model.get_index())
264 self._config.connect_gtk_togglebutton('check_connection',
265 self.checkbutton_check_connection)
267 if self._config.auto_remove_played_episodes:
268 adjustment_expiration = self.hscale_expiration.get_adjustment()
269 if self._config.episode_old_age > adjustment_expiration.get_upper():
270 # Patch the adjustment to include the higher current value
271 adjustment_expiration.set_upper(self._config.episode_old_age)
273 self.hscale_expiration.set_value(self._config.episode_old_age)
274 else:
275 self.hscale_expiration.set_value(0)
277 self._config.connect_gtk_togglebutton('auto_remove_unplayed_episodes',
278 self.checkbutton_expiration_unplayed)
279 self._config.connect_gtk_togglebutton('auto_remove_unfinished_episodes',
280 self.checkbutton_expiration_unfinished)
282 self.device_type_model = DeviceTypeActionList(self._config)
283 self.combobox_device_type.set_model(self.device_type_model)
284 cellrenderer = Gtk.CellRendererText()
285 self.combobox_device_type.pack_start(cellrenderer, True)
286 self.combobox_device_type.add_attribute(cellrenderer, 'text',
287 DeviceTypeActionList.C_CAPTION)
288 self.combobox_device_type.set_active(self.device_type_model.get_index())
290 self.on_sync_model = OnSyncActionList(self._config)
291 self.combobox_on_sync.set_model(self.on_sync_model)
292 cellrenderer = Gtk.CellRendererText()
293 self.combobox_on_sync.pack_start(cellrenderer, True)
294 self.combobox_on_sync.add_attribute(cellrenderer, 'text', OnSyncActionList.C_CAPTION)
295 self.combobox_on_sync.set_active(self.on_sync_model.get_index())
297 self._config.connect_gtk_togglebutton('device_sync.skip_played_episodes',
298 self.checkbutton_skip_played_episodes)
299 self._config.connect_gtk_togglebutton('device_sync.playlists.create',
300 self.checkbutton_create_playlists)
301 self._config.connect_gtk_togglebutton('device_sync.playlists.two_way_sync',
302 self.checkbutton_delete_using_playlists)
303 self._config.connect_gtk_togglebutton('device_sync.delete_deleted_episodes',
304 self.checkbutton_delete_deleted_episodes)
306 # Have to do this before calling set_active on checkbutton_enable
307 self._enable_mygpo = self._config.mygpo.enabled
309 # Initialize the UI state with configuration settings
310 self.checkbutton_enable.set_active(self._config.mygpo.enabled)
311 self.entry_server.set_text(self._config.mygpo.server)
312 self.entry_username.set_text(self._config.mygpo.username)
313 self.entry_password.set_text(self._config.mygpo.password)
314 self.entry_caption.set_text(self._config.mygpo.device.caption)
316 # Disable mygpo sync while the dialog is open
317 self._config.mygpo.enabled = False
319 # Configure the extensions manager GUI
320 self.set_extension_preferences()
322 self._config.connect_gtk_window(self.main_window, 'preferences', True)
324 gpodder.user_extensions.on_ui_object_available('preferences-gtk', self)
326 self.inject_extensions_preferences(init=True)
328 self.prefs_stack.foreach(self._wrap_checkbox_labels)
330 def _wrap_checkbox_labels(self, w, *args):
331 if w.get_name().startswith("no_label_wrap"):
332 return
333 elif isinstance(w, Gtk.CheckButton):
334 label = w.get_child()
335 label.set_line_wrap(True)
336 elif isinstance(w, Gtk.Container):
337 w.foreach(self._wrap_checkbox_labels)
339 def inject_extensions_preferences(self, init=False):
340 if not init:
341 # remove preferences buttons for all extensions
342 for child in self.prefs_stack.get_children():
343 if child.get_name().startswith("extension."):
344 self.prefs_stack.remove(child)
346 # add preferences buttons for all extensions
347 result = gpodder.user_extensions.on_preferences()
348 if result:
349 for label, callback in result:
350 page = callback()
351 name = "extension." + label
352 page.set_name(name)
353 page.foreach(self._wrap_checkbox_labels)
354 self.prefs_stack.add_titled(page, name, label)
356 def _extensions_select_function(self, selection, model, path, path_currently_selected):
357 return model.get_value(model.get_iter(path), self.C_SHOW_TOGGLE)
359 def set_extension_preferences(self):
360 def search_equal_func(model, column, key, it):
361 label = model.get_value(it, self.C_LABEL)
362 if key.lower() in label.lower():
363 # from http://www.pyGtk.org/docs/pygtk/class-gtktreeview.html:
364 # "func should return False to indicate that the row matches
365 # the search criteria."
366 return False
368 return True
369 self.treeviewExtensions.set_search_equal_func(search_equal_func)
371 selection = self.treeviewExtensions.get_selection()
372 selection.set_select_function(self._extensions_select_function)
374 toggle_cell = Gtk.CellRendererToggle()
375 toggle_cell.connect('toggled', self.on_extensions_cell_toggled)
376 toggle_column = Gtk.TreeViewColumn('')
377 toggle_column.pack_start(toggle_cell, True)
378 toggle_column.add_attribute(toggle_cell, 'active', self.C_TOGGLE)
379 toggle_column.add_attribute(toggle_cell, 'visible', self.C_SHOW_TOGGLE)
380 toggle_column.set_property('min-width', 32)
381 self.treeviewExtensions.append_column(toggle_column)
383 name_cell = Gtk.CellRendererText()
384 name_cell.set_property('ellipsize', Pango.EllipsizeMode.END)
385 extension_column = Gtk.TreeViewColumn(_('Name'))
386 extension_column.pack_start(name_cell, True)
387 extension_column.add_attribute(name_cell, 'markup', self.C_LABEL)
388 extension_column.set_expand(True)
389 self.treeviewExtensions.append_column(extension_column)
391 self.extensions_model = Gtk.ListStore(bool, str, object, bool)
393 def key_func(pair):
394 category, container = pair
395 return (category, container.metadata.title)
397 def convert(extensions):
398 for container in extensions:
399 yield (container.metadata.category, container)
401 old_category = None
402 for category, container in sorted(convert(
403 gpodder.user_extensions.get_extensions()), key=key_func):
404 if old_category != category:
405 label = '<span weight="bold">%s</span>' % html.escape(category)
406 self.extensions_model.append((None, label, None, False))
407 old_category = category
409 label = '%s\n<small>%s</small>' % (
410 html.escape(container.metadata.title),
411 html.escape(container.metadata.description))
412 self.extensions_model.append((container.enabled, label, container, True))
414 self.treeviewExtensions.set_model(self.extensions_model)
415 self.treeviewExtensions.columns_autosize()
417 def on_treeview_extension_button_released(self, treeview, event):
418 if event.window != treeview.get_bin_window():
419 return False
421 if event.type == Gdk.EventType.BUTTON_RELEASE and event.button == 3:
422 return self.on_treeview_extension_show_context_menu(treeview, event)
424 return False
426 def on_treeview_extension_show_context_menu(self, treeview, event=None):
427 selection = treeview.get_selection()
428 model, paths = selection.get_selected_rows()
429 container = model.get_value(model.get_iter(paths[0]), self.C_EXTENSION)
431 if not container:
432 return
434 menu = Gtk.Menu()
436 if container.metadata.doc:
437 menu_item = Gtk.MenuItem(_('Documentation'))
438 menu_item.connect('activate', self.open_weblink,
439 container.metadata.doc)
440 menu.append(menu_item)
442 menu_item = Gtk.MenuItem(_('Extension info'))
443 menu_item.connect('activate', self.show_extension_info, model, container)
444 menu.append(menu_item)
446 if container.metadata.payment:
447 menu_item = Gtk.MenuItem(_('Support the author'))
448 menu_item.connect('activate', self.open_weblink, container.metadata.payment)
449 menu.append(menu_item)
451 menu.show_all()
452 if event is None:
453 func = TreeViewHelper.make_popup_position_func(treeview)
454 menu.popup(None, None, func, None, 3, Gtk.get_current_event_time())
455 else:
456 menu.popup(None, None, None, None, 3, Gtk.get_current_event_time())
458 return True
460 def on_extensions_cell_toggled(self, cell, path):
461 model = self.treeviewExtensions.get_model()
462 it = model.get_iter(path)
463 container = model.get_value(it, self.C_EXTENSION)
465 enabled_extensions = list(self._config.extensions.enabled)
466 new_enabled = not model.get_value(it, self.C_TOGGLE)
468 if new_enabled and container.name not in enabled_extensions:
469 enabled_extensions.append(container.name)
470 elif not new_enabled and container.name in enabled_extensions:
471 enabled_extensions.remove(container.name)
473 self._config.extensions.enabled = enabled_extensions
475 now_enabled = (container.name in self._config.extensions.enabled)
477 if new_enabled == now_enabled:
478 model.set_value(it, self.C_TOGGLE, new_enabled)
479 if now_enabled:
480 self.on_extension_enabled(container.module)
481 else:
482 self.on_extension_disabled(container.module)
483 self.inject_extensions_preferences()
484 elif container.error is not None:
485 if hasattr(container.error, 'message'):
486 error_msg = container.error.message
487 else:
488 error_msg = str(container.error)
489 self.show_message(error_msg,
490 _('Extension cannot be activated'), important=True)
491 model.set_value(it, self.C_TOGGLE, False)
493 def show_extension_info(self, w, model, container):
494 if not container or not model:
495 return
497 info = '\n'.join('<b>{}:</b> {}'.format(html.escape(key), html.escape(value))
498 for key, value in container.metadata.get_sorted()
499 if key not in ('title', 'description'))
501 self.show_message_details(container.metadata.title, container.metadata.description, info)
503 def open_weblink(self, w, url):
504 util.open_website(url)
506 def on_dialog_destroy(self, widget):
507 # Re-enable mygpo sync if the user has selected it
508 self._config.mygpo.enabled = self._enable_mygpo
509 # Make sure the device is successfully created/updated
510 self.mygpo_client.create_device()
511 # Flush settings for mygpo client now
512 self.mygpo_client.flush(now=True)
514 def on_button_close_clicked(self, widget):
515 self.main_window.destroy()
517 def on_button_advanced_clicked(self, widget):
518 self.main_window.destroy()
519 gPodderConfigEditor(self.parent_window, _config=self._config)
521 def on_combo_audio_player_app_changed(self, widget):
522 index = self.combo_audio_player_app.get_active()
523 self._config.player.audio = self.audio_player_model.get_command(index)
525 def on_combo_video_player_app_changed(self, widget):
526 index = self.combo_video_player_app.get_active()
527 self._config.player.video = self.video_player_model.get_command(index)
529 def on_combobox_preferred_youtube_format_changed(self, widget):
530 index = self.combobox_preferred_youtube_format.get_active()
531 self.preferred_youtube_format_model.set_index(index)
533 def on_combobox_preferred_youtube_hls_format_changed(self, widget):
534 index = self.combobox_preferred_youtube_hls_format.get_active()
535 self.preferred_youtube_hls_format_model.set_index(index)
537 def on_combobox_preferred_vimeo_format_changed(self, widget):
538 index = self.combobox_preferred_vimeo_format.get_active()
539 self.preferred_vimeo_format_model.set_index(index)
541 def on_button_audio_player_clicked(self, widget):
542 result = self.show_text_edit_dialog(_('Configure audio player'),
543 _('Command:'),
544 self._config.player.audio)
546 if result:
547 self._config.player.audio = result
548 index = self.audio_player_model.get_index(self._config.player.audio)
549 self.combo_audio_player_app.set_active(index)
551 def on_button_video_player_clicked(self, widget):
552 result = self.show_text_edit_dialog(_('Configure video player'),
553 _('Command:'),
554 self._config.player.video)
556 if result:
557 self._config.player.video = result
558 index = self.video_player_model.get_index(self._config.player.video)
559 self.combo_video_player_app.set_active(index)
561 def format_update_interval_value(self, scale, value):
562 value = int(value)
563 ret = None
564 if value == 0:
565 ret = _('manually')
566 elif value > 0 and len(self.update_interval_presets) > value:
567 ret = util.format_seconds_to_hour_min_sec(self.update_interval_presets[value] * 60)
568 else:
569 ret = str(value)
570 # bug in gtk3: value representation (pixels) must be smaller than value for highest value.
571 # this makes sense when formatting e.g. 0 to 1000 where '1000' is the longest
572 # string, but not when '10 minutes' is longer than '12 hours'
573 # so we replace spaces with non breaking spaces otherwise '10 minutes' is displayed as '10'
574 ret = ret.replace(' ', '\xa0')
575 return ret
577 def on_update_interval_value_changed(self, range):
578 value = int(range.get_value())
579 self._config.auto_update_feeds = (value > 0)
580 self._config.auto_update_frequency = self.update_interval_presets[value]
582 def on_combo_auto_download_changed(self, widget):
583 index = self.combo_auto_download.get_active()
584 self.auto_download_model.set_index(index)
586 def format_expiration_value(self, scale, value):
587 value = int(value)
588 if value == 0:
589 return _('manually')
590 else:
591 return N_('after %(count)d day', 'after %(count)d days',
592 value) % {'count': value}
594 def on_expiration_value_changed(self, range):
595 value = int(range.get_value())
597 if value == 0:
598 self.checkbutton_expiration_unplayed.set_active(False)
599 self._config.auto_remove_played_episodes = False
600 self._config.auto_remove_unplayed_episodes = False
601 else:
602 self._config.auto_remove_played_episodes = True
603 self._config.episode_old_age = value
605 self.checkbutton_expiration_unplayed.set_sensitive(value > 0)
606 self.checkbutton_expiration_unfinished.set_sensitive(value > 0)
608 def on_enabled_toggled(self, widget):
609 # Only update indirectly (see on_dialog_destroy)
610 self._enable_mygpo = widget.get_active()
612 def on_server_changed(self, widget):
613 self._config.mygpo.server = widget.get_text()
615 def on_username_changed(self, widget):
616 self._config.mygpo.username = widget.get_text()
618 def on_password_changed(self, widget):
619 self._config.mygpo.password = widget.get_text()
621 def on_device_caption_changed(self, widget):
622 self._config.mygpo.device.caption = widget.get_text()
624 def on_button_overwrite_clicked(self, button):
625 title = _('Replace subscription list on server')
626 message = _('Remote podcasts that have not been added locally will be removed on the server. Continue?')
627 if self.show_confirmation(message, title):
628 @util.run_in_background
629 def thread_proc():
630 self._config.mygpo.enabled = True
631 self.on_send_full_subscriptions()
632 self._config.mygpo.enabled = False
634 def on_combobox_on_sync_changed(self, widget):
635 index = self.combobox_on_sync.get_active()
636 self.on_sync_model.set_index(index)
638 def on_checkbutton_create_playlists_toggled(
639 self, widget, device_type_changed=False):
640 if not widget.get_active():
641 self._config.device_sync.playlists.create = False
642 self.toggle_playlist_interface(False)
643 # need to read value of checkbutton from interface,
644 # rather than value of parameter
645 else:
646 self._config.device_sync.playlists.create = True
647 self.toggle_playlist_interface(True)
649 def toggle_playlist_interface(self, enabled):
650 if enabled and self._config.device_sync.device_type == 'filesystem':
651 self.btn_playlistfolder.set_sensitive(True)
652 self.btn_playlistfolder.set_label(self._config.device_sync.playlists.folder)
653 self.checkbutton_delete_using_playlists.set_sensitive(True)
654 children = self.btn_playlistfolder.get_children()
655 if children:
656 label = children.pop()
657 label.set_ellipsize(Pango.EllipsizeMode.START)
658 label.set_xalign(0.0)
659 else:
660 self.btn_playlistfolder.set_sensitive(False)
661 self.btn_playlistfolder.set_label('')
662 self.checkbutton_delete_using_playlists.set_sensitive(False)
664 def on_combobox_device_type_changed(self, widget):
665 index = self.combobox_device_type.get_active()
666 self.device_type_model.set_index(index)
667 device_type = self._config.device_sync.device_type
668 if device_type == 'none':
669 self.btn_filesystemMountpoint.set_label('')
670 self.btn_filesystemMountpoint.set_sensitive(False)
671 self.checkbutton_create_playlists.set_sensitive(False)
672 self.toggle_playlist_interface(False)
673 self.checkbutton_delete_using_playlists.set_sensitive(False)
674 self.combobox_on_sync.set_sensitive(False)
675 self.checkbutton_skip_played_episodes.set_sensitive(False)
676 elif device_type == 'filesystem':
677 self.btn_filesystemMountpoint.set_label(self._config.device_sync.device_folder or "")
678 self.btn_filesystemMountpoint.set_sensitive(True)
679 self.checkbutton_create_playlists.set_sensitive(True)
680 self.toggle_playlist_interface(self._config.device_sync.playlists.create)
681 self.combobox_on_sync.set_sensitive(True)
682 self.checkbutton_skip_played_episodes.set_sensitive(True)
683 self.checkbutton_delete_deleted_episodes.set_sensitive(True)
684 elif device_type == 'ipod':
685 self.btn_filesystemMountpoint.set_label(self._config.device_sync.device_folder)
686 self.btn_filesystemMountpoint.set_sensitive(True)
687 self.checkbutton_create_playlists.set_sensitive(False)
688 self.toggle_playlist_interface(False)
689 self.checkbutton_delete_using_playlists.set_sensitive(False)
690 self.combobox_on_sync.set_sensitive(False)
691 self.checkbutton_skip_played_episodes.set_sensitive(True)
692 self.checkbutton_delete_deleted_episodes.set_sensitive(True)
694 children = self.btn_filesystemMountpoint.get_children()
695 if children:
696 label = children.pop()
697 label.set_ellipsize(Pango.EllipsizeMode.START)
698 label.set_xalign(0.0)
700 def on_btn_device_mountpoint_clicked(self, widget):
701 fs = Gtk.FileChooserDialog(title=_('Select folder for mount point'),
702 action=Gtk.FileChooserAction.SELECT_FOLDER)
703 fs.set_local_only(False)
704 fs.add_button(_('_Cancel'), Gtk.ResponseType.CANCEL)
705 fs.add_button(_('_Open'), Gtk.ResponseType.OK)
707 fs.set_uri(self.btn_filesystemMountpoint.get_label() or "")
708 if fs.run() == Gtk.ResponseType.OK:
709 if self._config.device_sync.device_type == 'filesystem':
710 self._config.device_sync.device_folder = fs.get_uri()
711 elif self._config.device_sync.device_type == 'ipod':
712 self._config.device_sync.device_folder = fs.get_filename()
713 # Request an update of the mountpoint button
714 self.on_combobox_device_type_changed(None)
716 fs.destroy()
718 def on_btn_playlist_folder_clicked(self, widget):
719 fs = Gtk.FileChooserDialog(title=_('Select folder for playlists'),
720 action=Gtk.FileChooserAction.SELECT_FOLDER)
721 fs.set_local_only(False)
722 fs.add_button(_('_Cancel'), Gtk.ResponseType.CANCEL)
723 fs.add_button(_('_Open'), Gtk.ResponseType.OK)
725 device_folder = util.new_gio_file(self._config.device_sync.device_folder)
726 playlists_folder = device_folder.resolve_relative_path(self._config.device_sync.playlists.folder)
727 fs.set_file(playlists_folder)
729 while fs.run() == Gtk.ResponseType.OK:
730 filename = util.relpath(fs.get_uri(),
731 self._config.device_sync.device_folder)
732 if not filename:
733 show_message_dialog(fs, _('The playlists folder must be on the device'))
734 continue
736 if self._config.device_sync.device_type == 'filesystem':
737 self._config.device_sync.playlists.folder = filename
738 self.btn_playlistfolder.set_label(filename or "")
739 children = self.btn_playlistfolder.get_children()
740 if children:
741 label = children.pop()
742 label.set_ellipsize(Pango.EllipsizeMode.START)
743 label.set_xalign(0.0)
744 break
746 fs.destroy()