Add setting to toggle sync filesize comparisons.
[gpodder.git] / src / gpodder / gtkui / desktop / preferences.py
blob360b3c9306e9931144b9921e79eec5578842f1a8
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
23 from gi.repository import Gdk, Gtk, Pango
25 import gpodder
26 from gpodder import util, vimeo, youtube
27 from gpodder.gtkui.desktopfile import PlayerListModel
28 from gpodder.gtkui.interface.common import (BuilderWidget, TreeViewHelper,
29 show_message_dialog)
30 from gpodder.gtkui.interface.configeditor import gPodderConfigEditor
32 logger = logging.getLogger(__name__)
34 _ = gpodder.gettext
35 N_ = gpodder.ngettext
38 class NewEpisodeActionList(Gtk.ListStore):
39 C_CAPTION, C_AUTO_DOWNLOAD = list(range(2))
41 ACTION_NONE, ACTION_ASK, ACTION_MINIMIZED, ACTION_ALWAYS = list(range(4))
43 def __init__(self, config):
44 Gtk.ListStore.__init__(self, str, str)
45 self._config = config
46 self.append((_('Do nothing'), 'ignore'))
47 self.append((_('Show episode list'), 'show'))
48 self.append((_('Add to download list'), 'queue'))
49 self.append((_('Download immediately'), 'download'))
51 def get_index(self):
52 for index, row in enumerate(self):
53 if self._config.ui.gtk.new_episodes == row[self.C_AUTO_DOWNLOAD]:
54 return index
56 return 1 # Some sane default
58 def set_index(self, index):
59 self._config.ui.gtk.new_episodes = self[index][self.C_AUTO_DOWNLOAD]
62 class DeviceTypeActionList(Gtk.ListStore):
63 C_CAPTION, C_DEVICE_TYPE = list(range(2))
65 def __init__(self, config):
66 Gtk.ListStore.__init__(self, str, str)
67 self._config = config
68 self.append((_('None'), 'none'))
69 self.append((_('iPod'), 'ipod'))
70 self.append((_('Filesystem-based'), 'filesystem'))
72 def get_index(self):
73 for index, row in enumerate(self):
74 if self._config.device_sync.device_type == row[self.C_DEVICE_TYPE]:
75 return index
76 return 0 # Some sane default
78 def set_index(self, index):
79 self._config.device_sync.device_type = self[index][self.C_DEVICE_TYPE]
82 class OnSyncActionList(Gtk.ListStore):
83 C_CAPTION, C_ON_SYNC_DELETE, C_ON_SYNC_MARK_PLAYED = list(range(3))
84 ACTION_NONE, ACTION_ASK, ACTION_MINIMIZED, ACTION_ALWAYS = list(range(4))
86 def __init__(self, config):
87 Gtk.ListStore.__init__(self, str, bool, bool)
88 self._config = config
89 self.append((_('Do nothing'), False, False))
90 self.append((_('Mark as played'), False, True))
91 self.append((_('Delete from gPodder'), True, False))
93 def get_index(self):
94 for index, row in enumerate(self):
95 if (self._config.device_sync.after_sync.delete_episodes
96 and row[self.C_ON_SYNC_DELETE]):
97 return index
98 if (self._config.device_sync.after_sync.mark_episodes_played
99 and row[self.C_ON_SYNC_MARK_PLAYED] and not
100 self._config.device_sync.after_sync.delete_episodes):
101 return index
102 return 0 # Some sane default
104 def set_index(self, index):
105 self._config.device_sync.after_sync.delete_episodes = self[index][self.C_ON_SYNC_DELETE]
106 self._config.device_sync.after_sync.mark_episodes_played = self[index][self.C_ON_SYNC_MARK_PLAYED]
109 class YouTubeVideoFormatListModel(Gtk.ListStore):
110 C_CAPTION, C_ID = list(range(2))
112 def __init__(self, config):
113 Gtk.ListStore.__init__(self, str, int)
114 self._config = config
116 if self._config.youtube.preferred_fmt_ids:
117 caption = _('Custom (%(format_ids)s)') % {
118 'format_ids': ', '.join(str(x) for x in self._config.youtube.preferred_fmt_ids),
120 self.append((caption, 0))
122 for id, (fmt_id, path, description) in youtube.formats:
123 self.append((description, id))
125 def get_index(self):
126 for index, row in enumerate(self):
127 if self._config.youtube.preferred_fmt_id == row[self.C_ID]:
128 return index
129 return 0
131 def set_index(self, index):
132 self._config.youtube.preferred_fmt_id = self[index][self.C_ID]
135 class YouTubeVideoHLSFormatListModel(Gtk.ListStore):
136 C_CAPTION, C_ID = list(range(2))
138 def __init__(self, config):
139 Gtk.ListStore.__init__(self, str, int)
140 self._config = config
142 if self._config.youtube.preferred_hls_fmt_ids:
143 caption = _('Custom (%(format_ids)s)') % {
144 'format_ids': ', '.join(str(x) for x in self._config.youtube.preferred_hls_fmt_ids),
146 self.append((caption, 0))
148 for id, (fmt_id, path, description) in youtube.hls_formats:
149 self.append((description, id))
151 def get_index(self):
152 for index, row in enumerate(self):
153 if self._config.youtube.preferred_hls_fmt_id == row[self.C_ID]:
154 return index
155 return 0
157 def set_index(self, index):
158 self._config.youtube.preferred_hls_fmt_id = self[index][self.C_ID]
161 class VimeoVideoFormatListModel(Gtk.ListStore):
162 C_CAPTION, C_ID = list(range(2))
164 def __init__(self, config):
165 Gtk.ListStore.__init__(self, str, str)
166 self._config = config
168 for fileformat, description in vimeo.FORMATS:
169 self.append((description, fileformat))
171 def get_index(self):
172 for index, row in enumerate(self):
173 if self._config.vimeo.fileformat == row[self.C_ID]:
174 return index
175 return 0
177 def set_index(self, index):
178 value = self[index][self.C_ID]
179 if value is not None:
180 self._config.vimeo.fileformat = value
183 class gPodderPreferences(BuilderWidget):
184 C_TOGGLE, C_LABEL, C_EXTENSION, C_SHOW_TOGGLE = list(range(4))
186 def new(self):
187 self.gPodderPreferences.set_transient_for(self.parent_widget)
188 for cb in (self.combo_audio_player_app, self.combo_video_player_app):
189 cellrenderer = Gtk.CellRendererPixbuf()
190 cb.pack_start(cellrenderer, False)
191 cb.add_attribute(cellrenderer, 'pixbuf', PlayerListModel.C_ICON)
192 cellrenderer = Gtk.CellRendererText()
193 cellrenderer.set_property('ellipsize', Pango.EllipsizeMode.END)
194 cb.pack_start(cellrenderer, True)
195 cb.add_attribute(cellrenderer, 'markup', PlayerListModel.C_NAME)
196 cb.set_row_separator_func(PlayerListModel.is_separator)
198 self.audio_player_model = self.user_apps_reader.get_model('audio')
199 self.combo_audio_player_app.set_model(self.audio_player_model)
200 index = self.audio_player_model.get_index(self._config.player.audio)
201 self.combo_audio_player_app.set_active(index)
203 self.video_player_model = self.user_apps_reader.get_model('video')
204 self.combo_video_player_app.set_model(self.video_player_model)
205 index = self.video_player_model.get_index(self._config.player.video)
206 self.combo_video_player_app.set_active(index)
208 self.preferred_youtube_format_model = YouTubeVideoFormatListModel(self._config)
209 self.combobox_preferred_youtube_format.set_model(self.preferred_youtube_format_model)
210 cellrenderer = Gtk.CellRendererText()
211 cellrenderer.set_property('ellipsize', Pango.EllipsizeMode.END)
212 self.combobox_preferred_youtube_format.pack_start(cellrenderer, True)
213 self.combobox_preferred_youtube_format.add_attribute(cellrenderer, 'text', self.preferred_youtube_format_model.C_CAPTION)
214 self.combobox_preferred_youtube_format.set_active(self.preferred_youtube_format_model.get_index())
216 self.preferred_youtube_hls_format_model = YouTubeVideoHLSFormatListModel(self._config)
217 self.combobox_preferred_youtube_hls_format.set_model(self.preferred_youtube_hls_format_model)
218 cellrenderer = Gtk.CellRendererText()
219 cellrenderer.set_property('ellipsize', Pango.EllipsizeMode.END)
220 self.combobox_preferred_youtube_hls_format.pack_start(cellrenderer, True)
221 self.combobox_preferred_youtube_hls_format.add_attribute(cellrenderer, 'text', self.preferred_youtube_hls_format_model.C_CAPTION)
222 self.combobox_preferred_youtube_hls_format.set_active(self.preferred_youtube_hls_format_model.get_index())
224 self.preferred_vimeo_format_model = VimeoVideoFormatListModel(self._config)
225 self.combobox_preferred_vimeo_format.set_model(self.preferred_vimeo_format_model)
226 cellrenderer = Gtk.CellRendererText()
227 cellrenderer.set_property('ellipsize', Pango.EllipsizeMode.END)
228 self.combobox_preferred_vimeo_format.pack_start(cellrenderer, True)
229 self.combobox_preferred_vimeo_format.add_attribute(cellrenderer, 'text', self.preferred_vimeo_format_model.C_CAPTION)
230 self.combobox_preferred_vimeo_format.set_active(self.preferred_vimeo_format_model.get_index())
232 self._config.connect_gtk_togglebutton('ui.gtk.find_as_you_type',
233 self.checkbutton_find_as_you_type)
235 self.update_interval_presets = [0, 10, 30, 60, 2 * 60, 6 * 60, 12 * 60]
236 adjustment_update_interval = self.hscale_update_interval.get_adjustment()
237 adjustment_update_interval.set_upper(len(self.update_interval_presets) - 1)
238 if self._config.auto.update.frequency in self.update_interval_presets:
239 index = self.update_interval_presets.index(self._config.auto.update.frequency)
240 self.hscale_update_interval.set_value(index)
241 else:
242 # Patch in the current "custom" value into the mix
243 self.update_interval_presets.append(self._config.auto.update.frequency)
244 self.update_interval_presets.sort()
246 adjustment_update_interval.set_upper(len(self.update_interval_presets) - 1)
247 index = self.update_interval_presets.index(self._config.auto.update.frequency)
248 self.hscale_update_interval.set_value(index)
250 self._config.connect_gtk_spinbutton('limit.episodes', self.spinbutton_episode_limit)
252 self._config.connect_gtk_togglebutton('ui.gtk.only_added_are_new',
253 self.checkbutton_only_added_are_new)
255 self.auto_download_model = NewEpisodeActionList(self._config)
256 self.combo_auto_download.set_model(self.auto_download_model)
257 cellrenderer = Gtk.CellRendererText()
258 self.combo_auto_download.pack_start(cellrenderer, True)
259 self.combo_auto_download.add_attribute(cellrenderer, 'text', NewEpisodeActionList.C_CAPTION)
260 self.combo_auto_download.set_active(self.auto_download_model.get_index())
262 self._config.connect_gtk_togglebutton('check_connection',
263 self.checkbutton_check_connection)
265 if self._config.auto.cleanup.played:
266 adjustment_expiration = self.hscale_expiration.get_adjustment()
267 if self._config.auto.cleanup.days > adjustment_expiration.get_upper():
268 # Patch the adjustment to include the higher current value
269 adjustment_expiration.set_upper(self._config.auto.cleanup.days)
271 self.hscale_expiration.set_value(self._config.auto.cleanup.days)
272 else:
273 self.hscale_expiration.set_value(0)
275 self._config.connect_gtk_togglebutton('auto.cleanup.unplayed',
276 self.checkbutton_expiration_unplayed)
277 self._config.connect_gtk_togglebutton('auto.cleanup.unfinished',
278 self.checkbutton_expiration_unfinished)
280 self.device_type_model = DeviceTypeActionList(self._config)
281 self.combobox_device_type.set_model(self.device_type_model)
282 cellrenderer = Gtk.CellRendererText()
283 self.combobox_device_type.pack_start(cellrenderer, True)
284 self.combobox_device_type.add_attribute(cellrenderer, 'text',
285 DeviceTypeActionList.C_CAPTION)
286 self.combobox_device_type.set_active(self.device_type_model.get_index())
288 self.on_sync_model = OnSyncActionList(self._config)
289 self.combobox_on_sync.set_model(self.on_sync_model)
290 cellrenderer = Gtk.CellRendererText()
291 self.combobox_on_sync.pack_start(cellrenderer, True)
292 self.combobox_on_sync.add_attribute(cellrenderer, 'text', OnSyncActionList.C_CAPTION)
293 self.combobox_on_sync.set_active(self.on_sync_model.get_index())
295 self._config.connect_gtk_togglebutton('device_sync.skip_played_episodes',
296 self.checkbutton_skip_played_episodes)
297 self._config.connect_gtk_togglebutton('device_sync.playlists.create',
298 self.checkbutton_create_playlists)
299 self._config.connect_gtk_togglebutton('device_sync.playlists.two_way_sync',
300 self.checkbutton_delete_using_playlists)
301 self._config.connect_gtk_togglebutton('device_sync.delete_deleted_episodes',
302 self.checkbutton_delete_deleted_episodes)
303 self._config.connect_gtk_togglebutton('device_sync.compare_episode_filesize',
304 self.checkbutton_compare_episode_filesize)
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.enabled = (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.cleanup.played = False
600 self._config.auto.cleanup.unplayed = False
601 else:
602 self._config.auto.cleanup.played = True
603 self._config.auto.cleanup.days = 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)
693 self.checkbutton_compare_episode_filesize.set_sensitive(True)
695 children = self.btn_filesystemMountpoint.get_children()
696 if children:
697 label = children.pop()
698 label.set_ellipsize(Pango.EllipsizeMode.START)
699 label.set_xalign(0.0)
701 def on_btn_device_mountpoint_clicked(self, widget):
702 fs = Gtk.FileChooserDialog(title=_('Select folder for mount point'),
703 action=Gtk.FileChooserAction.SELECT_FOLDER)
704 fs.set_local_only(False)
705 fs.add_button(_('_Cancel'), Gtk.ResponseType.CANCEL)
706 fs.add_button(_('_Open'), Gtk.ResponseType.OK)
708 fs.set_uri(self.btn_filesystemMountpoint.get_label() or "")
709 if fs.run() == Gtk.ResponseType.OK:
710 if self._config.device_sync.device_type == 'filesystem':
711 self._config.device_sync.device_folder = fs.get_uri()
712 elif self._config.device_sync.device_type == 'ipod':
713 self._config.device_sync.device_folder = fs.get_filename()
714 # Request an update of the mountpoint button
715 self.on_combobox_device_type_changed(None)
717 fs.destroy()
719 def on_btn_playlist_folder_clicked(self, widget):
720 fs = Gtk.FileChooserDialog(title=_('Select folder for playlists'),
721 action=Gtk.FileChooserAction.SELECT_FOLDER)
722 fs.set_local_only(False)
723 fs.add_button(_('_Cancel'), Gtk.ResponseType.CANCEL)
724 fs.add_button(_('_Open'), Gtk.ResponseType.OK)
726 device_folder = util.new_gio_file(self._config.device_sync.device_folder)
727 playlists_folder = device_folder.resolve_relative_path(self._config.device_sync.playlists.folder)
728 fs.set_file(playlists_folder)
730 while fs.run() == Gtk.ResponseType.OK:
731 filename = util.relpath(fs.get_uri(),
732 self._config.device_sync.device_folder)
733 if not filename:
734 show_message_dialog(fs, _('The playlists folder must be on the device'))
735 continue
737 if self._config.device_sync.device_type == 'filesystem':
738 self._config.device_sync.playlists.folder = filename
739 self.btn_playlistfolder.set_label(filename or "")
740 children = self.btn_playlistfolder.get_children()
741 if children:
742 label = children.pop()
743 label.set_ellipsize(Pango.EllipsizeMode.START)
744 label.set_xalign(0.0)
745 break
747 fs.destroy()