1 # -*- coding: utf-8 -*-
3 # gPodder - A media aggregator and podcast client
4 # Copyright (c) 2005-2010 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/>.
29 from gpodder
import util
31 from gpodder
.gtkui
.interface
.common
import BuilderWidget
32 from gpodder
.gtkui
.interface
.configeditor
import gPodderConfigEditor
34 from gpodder
.gtkui
.desktopfile
import PlayerListModel
36 class NewEpisodeActionList(gtk
.ListStore
):
37 C_CAPTION
, C_AUTO_DOWNLOAD
, C_HIDE_DIALOG
= range(3)
39 ACTION_NONE
, ACTION_ASK
, ACTION_MINIMIZED
, ACTION_ALWAYS
= range(4)
41 def __init__(self
, config
):
42 gtk
.ListStore
.__init
__(self
, str, str, bool)
44 self
.append((_('Do nothing'), 'never', True))
45 self
.append((_('Show episode list'), 'never', False))
46 self
.append((_('Add to download list'), 'queue', False))
47 self
.append((_('Download if minimized'), 'minimized', False))
48 self
.append((_('Download immediately'), 'always', False))
51 if self
._config
.do_not_show_new_episodes_dialog
:
54 for index
, row
in enumerate(self
):
55 if row
[self
.C_HIDE_DIALOG
]:
58 if self
._config
.auto_download
== \
59 row
[self
.C_AUTO_DOWNLOAD
]:
62 return 1 # Some sane default
64 def set_index(self
, index
):
65 self
._config
.do_not_show_new_episodes_dialog
= self
[index
][self
.C_HIDE_DIALOG
]
66 self
._config
.auto_download
= self
[index
][self
.C_AUTO_DOWNLOAD
]
69 class DeviceTypeActionList(gtk
.ListStore
):
70 C_CAPTION
, C_DEVICE_TYPE
= range(2)
71 ACTION_NONE
, ACTION_ASK
, ACTION_MINIMIZED
, ACTION_ALWAYS
= range(4)
73 def __init__(self
, config
):
74 gtk
.ListStore
.__init
__(self
, str, str)
76 self
.append((_('None'), 'none'))
77 self
.append((_('iPod'), 'ipod'))
78 self
.append((_('Filesystem-based'), 'filesystem'))
79 self
.append((_('MTP'), 'mtp'))
82 for index
, row
in enumerate(self
):
83 if self
._config
.device_type
== row
[self
.C_DEVICE_TYPE
]:
85 return 0 # Some sane default
87 def set_index(self
, index
):
88 self
._config
.device_type
= self
[index
][self
.C_DEVICE_TYPE
]
90 class OnSyncActionList(gtk
.ListStore
):
91 C_CAPTION
, C_ON_SYNC_DELETE
, C_ON_SYNC_MARK_PLAYED
= range(3)
92 ACTION_NONE
, ACTION_ASK
, ACTION_MINIMIZED
, ACTION_ALWAYS
= range(4)
94 def __init__(self
, config
):
95 gtk
.ListStore
.__init
__(self
, str, bool, bool)
97 self
.append((_('Do nothing'), False, False))
98 self
.append((_('Mark it as played'), False, True))
99 self
.append((_('Delete it from gPodder'), True, False))
102 for index
, row
in enumerate(self
):
103 if self
._config
.on_sync_delete
and row
[self
.C_ON_SYNC_DELETE
]:
105 if self
._config
.on_sync_mark_played
and row
[self
.C_ON_SYNC_MARK_PLAYED
] \
106 and not self
._config
.on_sync_delete
:
108 return 0 # Some sane default
110 def set_index(self
, index
):
111 self
._config
.on_sync_delete
= self
[index
][self
.C_ON_SYNC_DELETE
]
112 self
._config
.on_sync_mark_played
= self
[index
][self
.C_ON_SYNC_MARK_PLAYED
]
115 class gPodderPreferences(BuilderWidget
):
117 if not hasattr(self
, 'callback_finished'):
118 self
.callback_finished
= None
120 for cb
in (self
.combo_audio_player_app
, self
.combo_video_player_app
):
121 cellrenderer
= gtk
.CellRendererPixbuf()
122 cb
.pack_start(cellrenderer
, False)
123 cb
.add_attribute(cellrenderer
, 'pixbuf', PlayerListModel
.C_ICON
)
124 cellrenderer
= gtk
.CellRendererText()
125 cellrenderer
.set_property('ellipsize', pango
.ELLIPSIZE_END
)
126 cb
.pack_start(cellrenderer
, True)
127 cb
.add_attribute(cellrenderer
, 'markup', PlayerListModel
.C_NAME
)
128 cb
.set_row_separator_func(PlayerListModel
.is_separator
)
130 self
.audio_player_model
= self
.user_apps_reader
.get_model('audio')
131 self
.combo_audio_player_app
.set_model(self
.audio_player_model
)
132 index
= self
.audio_player_model
.get_index(self
._config
.player
)
133 self
.combo_audio_player_app
.set_active(index
)
135 self
.video_player_model
= self
.user_apps_reader
.get_model('video')
136 self
.combo_video_player_app
.set_model(self
.video_player_model
)
137 index
= self
.video_player_model
.get_index(self
._config
.videoplayer
)
138 self
.combo_video_player_app
.set_active(index
)
140 self
._config
.connect_gtk_togglebutton('enable_notifications', self
.checkbutton_enable_notifications
)
141 self
._config
.connect_gtk_togglebutton('display_tray_icon', self
.checkbutton_show_tray_icon
)
143 self
.update_interval_presets
= [0, 10, 30, 60, 2*60, 6*60, 12*60]
144 adjustment_update_interval
= self
.hscale_update_interval
.get_adjustment()
145 adjustment_update_interval
.upper
= len(self
.update_interval_presets
)-1
146 if self
._config
.auto_update_frequency
in self
.update_interval_presets
:
147 index
= self
.update_interval_presets
.index(self
._config
.auto_update_frequency
)
148 self
.hscale_update_interval
.set_value(index
)
150 # Patch in the current "custom" value into the mix
151 self
.update_interval_presets
.append(self
._config
.auto_update_frequency
)
152 self
.update_interval_presets
.sort()
154 adjustment_update_interval
.upper
= len(self
.update_interval_presets
)-1
155 index
= self
.update_interval_presets
.index(self
._config
.auto_update_frequency
)
156 self
.hscale_update_interval
.set_value(index
)
158 self
._config
.connect_gtk_togglebutton('update_on_startup', self
.checkbutton_update_on_startup
)
159 self
._config
.connect_gtk_spinbutton('max_episodes_per_feed', self
.spinbutton_episode_limit
)
161 self
.auto_download_model
= NewEpisodeActionList(self
._config
)
162 self
.combo_auto_download
.set_model(self
.auto_download_model
)
163 cellrenderer
= gtk
.CellRendererText()
164 self
.combo_auto_download
.pack_start(cellrenderer
, True)
165 self
.combo_auto_download
.add_attribute(cellrenderer
, 'text', NewEpisodeActionList
.C_CAPTION
)
166 self
.combo_auto_download
.set_active(self
.auto_download_model
.get_index())
168 if self
._config
.auto_remove_played_episodes
:
169 adjustment_expiration
= self
.hscale_expiration
.get_adjustment()
170 if self
._config
.episode_old_age
> adjustment_expiration
.get_upper():
171 # Patch the adjustment to include the higher current value
172 adjustment_expiration
.upper
= self
._config
.episode_old_age
174 self
.hscale_expiration
.set_value(self
._config
.episode_old_age
)
176 self
.hscale_expiration
.set_value(0)
178 self
._config
.connect_gtk_togglebutton('auto_remove_unplayed_episodes', self
.checkbutton_expiration_unplayed
)
179 self
._config
.connect_gtk_togglebutton('auto_cleanup_downloads', self
.checkbutton_auto_cleanup_downloads
)
181 self
.device_type_model
= DeviceTypeActionList(self
._config
)
182 self
.combobox_device_type
.set_model(self
.device_type_model
)
183 cellrenderer
= gtk
.CellRendererText()
184 self
.combobox_device_type
.pack_start(cellrenderer
, True)
185 self
.combobox_device_type
.add_attribute(cellrenderer
, 'text', DeviceTypeActionList
.C_CAPTION
)
186 self
.combobox_device_type
.set_active(self
.device_type_model
.get_index())
188 self
.on_sync_model
= OnSyncActionList(self
._config
)
189 self
.combobox_on_sync
.set_model(self
.on_sync_model
)
190 cellrenderer
= gtk
.CellRendererText()
191 self
.combobox_on_sync
.pack_start(cellrenderer
, True)
192 self
.combobox_on_sync
.add_attribute(cellrenderer
, 'text', OnSyncActionList
.C_CAPTION
)
193 self
.combobox_on_sync
.set_active(self
.on_sync_model
.get_index())
195 self
._config
.connect_gtk_togglebutton('only_sync_not_played', self
.checkbutton_only_sync_not_played
)
197 # Have to do this before calling set_active on checkbutton_enable
198 self
._enable
_mygpo
= self
._config
.mygpo_enabled
200 # Initialize the UI state with configuration settings
201 self
.checkbutton_enable
.set_active(self
._config
.mygpo_enabled
)
202 self
.entry_username
.set_text(self
._config
.mygpo_username
)
203 self
.entry_password
.set_text(self
._config
.mygpo_password
)
204 self
.entry_caption
.set_text(self
._config
.mygpo_device_caption
)
206 # Disable mygpo sync while the dialog is open
207 self
._config
.mygpo_enabled
= False
209 def on_dialog_destroy(self
, widget
):
210 # Re-enable mygpo sync if the user has selected it
211 self
._config
.mygpo_enabled
= self
._enable
_mygpo
212 # Make sure the device is successfully created/updated
213 self
.mygpo_client
.create_device()
214 # Flush settings for mygpo client now
215 self
.mygpo_client
.flush(now
=True)
217 if self
.callback_finished
:
218 self
.callback_finished()
220 def on_button_close_clicked(self
, widget
):
221 self
.main_window
.destroy()
223 def on_button_advanced_clicked(self
, widget
):
224 self
.main_window
.destroy()
225 gPodderConfigEditor(self
.parent_window
, _config
=self
._config
)
227 def on_combo_audio_player_app_changed(self
, widget
):
228 index
= self
.combo_audio_player_app
.get_active()
229 self
._config
.player
= self
.audio_player_model
.get_command(index
)
231 def on_combo_video_player_app_changed(self
, widget
):
232 index
= self
.combo_video_player_app
.get_active()
233 self
._config
.videoplayer
= self
.video_player_model
.get_command(index
)
235 def on_button_audio_player_clicked(self
, widget
):
236 result
= self
.show_text_edit_dialog(_('Configure audio player'), \
241 self
._config
.player
= result
242 index
= self
.audio_player_model
.get_index(self
._config
.player
)
243 self
.combo_audio_player_app
.set_active(index
)
245 def on_button_video_player_clicked(self
, widget
):
246 result
= self
.show_text_edit_dialog(_('Configure video player'), \
248 self
._config
.videoplayer
)
251 self
._config
.videoplayer
= result
252 index
= self
.video_player_model
.get_index(self
._config
.videoplayer
)
253 self
.combo_video_player_app
.set_active(index
)
255 def format_update_interval_value(self
, scale
, value
):
259 elif value
> 0 and len(self
.update_interval_presets
) > value
:
260 return util
.format_seconds_to_hour_min_sec(self
.update_interval_presets
[value
]*60)
264 def on_update_interval_value_changed(self
, range):
265 value
= int(range.get_value())
266 self
._config
.auto_update_feeds
= (value
> 0)
267 self
._config
.auto_update_frequency
= self
.update_interval_presets
[value
]
269 def on_combo_auto_download_changed(self
, widget
):
270 index
= self
.combo_auto_download
.get_active()
271 self
.auto_download_model
.set_index(index
)
273 def on_combobox_on_sync_changed(self
, widget
):
274 index
= self
.combobox_on_sync
.get_active()
275 self
.on_sync_model
.set_index(index
)
277 def on_combobox_device_type_changed(self
, widget
):
278 index
= self
.combobox_device_type
.get_active()
279 self
.device_type_model
.set_index(index
)
281 self
.btn_filesystemMountpoint
.set_label('')
282 self
.btn_filesystemMountpoint
.set_sensitive(False)
284 self
.btn_filesystemMountpoint
.set_label(self
._config
.ipod_mount
)
285 self
.btn_filesystemMountpoint
.set_sensitive(True)
286 self
._config
.connect_gtk_togglebutton('ipod_purge_old_episodes', self
.checkbutton_delete_played
)
288 self
.btn_filesystemMountpoint
.set_label(self
._config
.mp3_player_folder
)
289 self
.btn_filesystemMountpoint
.set_sensitive(True)
290 self
._config
.connect_gtk_togglebutton('mp3_player_delete_played', self
.checkbutton_delete_played
)
292 self
.btn_filesystemMountpoint
.set_label('')
293 self
.btn_filesystemMountpoint
.set_sensitive(False)
295 children
= self
.btn_filesystemMountpoint
.get_children()
297 label
= children
.pop()
298 label
.set_alignment(0., .5)
300 def on_btn_device_mountpoint_clicked(self
, widget
):
301 fs
= gtk
.FileChooserDialog( title
= _('Select folder for mount point'), action
= gtk
.FILE_CHOOSER_ACTION_SELECT_FOLDER
)
302 fs
.add_button( gtk
.STOCK_CANCEL
, gtk
.RESPONSE_CANCEL
)
303 fs
.add_button( gtk
.STOCK_OPEN
, gtk
.RESPONSE_OK
)
304 fs
.set_current_folder(self
.btn_filesystemMountpoint
.get_label())
305 if fs
.run() == gtk
.RESPONSE_OK
:
306 filename
= fs
.get_filename()
307 if self
._config
.device_type
== 'filesystem':
308 self
._config
.mp3_player_folder
= filename
309 elif self
._config
.device_type
== 'ipod':
310 self
._config
.ipod_mount
= filename
312 # Request an update of the mountpoint button
313 self
.on_combobox_device_type_changed(None)
317 def format_expiration_value(self
, scale
, value
):
322 return N_('after %(count)d day', 'after %(count)d days', value
) % {'count':value
}
324 def on_expiration_value_changed(self
, range):
325 value
= int(range.get_value())
328 self
.checkbutton_expiration_unplayed
.set_active(False)
329 self
._config
.auto_remove_played_episodes
= False
330 self
._config
.auto_remove_unplayed_episodes
= False
332 self
._config
.auto_remove_played_episodes
= True
333 self
._config
.episode_old_age
= value
335 self
.checkbutton_expiration_unplayed
.set_sensitive(value
> 0)
337 def on_enabled_toggled(self
, widget
):
338 # Only update indirectly (see on_dialog_destroy)
339 self
._enable
_mygpo
= widget
.get_active()
341 def on_username_changed(self
, widget
):
342 self
._config
.mygpo_username
= widget
.get_text()
344 def on_password_changed(self
, widget
):
345 self
._config
.mygpo_password
= widget
.get_text()
347 def on_device_caption_changed(self
, widget
):
348 self
._config
.mygpo_device_caption
= widget
.get_text()
350 def on_button_overwrite_clicked(self
, button
):
351 title
= _('Replace subscription list on server')
352 message
= _('Remote podcasts that have not been added locally will be removed on the server. Continue?')
353 if self
.show_confirmation(message
, title
):
355 self
._config
.mygpo_enabled
= True
356 self
.on_send_full_subscriptions()
357 self
._config
.mygpo_enabled
= False
358 threading
.Thread(target
=thread_proc
).start()