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/>.
23 from gi
.repository
import Gdk
, Gtk
, Pango
26 from gpodder
import util
, vimeo
, youtube
27 from gpodder
.gtkui
.desktopfile
import PlayerListModel
28 from gpodder
.gtkui
.interface
.common
import (BuilderWidget
, TreeViewHelper
,
30 from gpodder
.gtkui
.interface
.configeditor
import gPodderConfigEditor
32 logger
= logging
.getLogger(__name__
)
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)
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'))
52 for index
, row
in enumerate(self
):
53 if self
._config
.ui
.gtk
.new_episodes
== row
[self
.C_AUTO_DOWNLOAD
]:
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)
68 self
.append((_('None'), 'none'))
69 self
.append((_('iPod'), 'ipod'))
70 self
.append((_('Filesystem-based'), 'filesystem'))
73 for index
, row
in enumerate(self
):
74 if self
._config
.device_sync
.device_type
== row
[self
.C_DEVICE_TYPE
]:
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)
89 self
.append((_('Do nothing'), False, False))
90 self
.append((_('Mark as played'), False, True))
91 self
.append((_('Delete from gPodder'), True, False))
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
]):
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
):
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))
126 for index
, row
in enumerate(self
):
127 if self
._config
.youtube
.preferred_fmt_id
== row
[self
.C_ID
]:
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))
152 for index
, row
in enumerate(self
):
153 if self
._config
.youtube
.preferred_hls_fmt_id
== row
[self
.C_ID
]:
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
))
172 for index
, row
in enumerate(self
):
173 if self
._config
.vimeo
.fileformat
== row
[self
.C_ID
]:
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))
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
)
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
)
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"):
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):
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()
349 for label
, callback
in result
:
351 name
= "extension." + label
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."
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)
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
)
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():
421 if event
.type == Gdk
.EventType
.BUTTON_RELEASE
and event
.button
== 3:
422 return self
.on_treeview_extension_show_context_menu(treeview
, event
)
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
)
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
)
453 func
= TreeViewHelper
.make_popup_position_func(treeview
)
454 menu
.popup(None, None, func
, None, 3, Gtk
.get_current_event_time())
456 menu
.popup(None, None, None, None, 3, Gtk
.get_current_event_time())
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
)
480 self
.on_extension_enabled(container
.module
)
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
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
:
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'),
544 self
._config
.player
.audio
)
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'),
554 self
._config
.player
.video
)
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
):
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)
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')
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
):
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())
598 self
.checkbutton_expiration_unplayed
.set_active(False)
599 self
._config
.auto
.cleanup
.played
= False
600 self
._config
.auto
.cleanup
.unplayed
= False
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
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
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()
656 label
= children
.pop()
657 label
.set_ellipsize(Pango
.EllipsizeMode
.START
)
658 label
.set_xalign(0.0)
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()
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)
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
)
734 show_message_dialog(fs
, _('The playlists folder must be on the device'))
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()
742 label
= children
.pop()
743 label
.set_ellipsize(Pango
.EllipsizeMode
.START
)
744 label
.set_xalign(0.0)