Use dict-based format strings for numbers (bug 1165)
[gpodder.git] / src / gpodder / gtkui / desktop / preferences.py
blob8e2f14537614971247aac2abb2159cb0a88bee12
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/>.
20 import gtk
21 import pango
22 import threading
24 import gpodder
26 _ = gpodder.gettext
27 N_ = gpodder.ngettext
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)
43 self._config = config
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))
50 def get_index(self):
51 if self._config.do_not_show_new_episodes_dialog:
52 return 0
53 else:
54 for index, row in enumerate(self):
55 if row[self.C_HIDE_DIALOG]:
56 continue
58 if self._config.auto_download == \
59 row[self.C_AUTO_DOWNLOAD]:
60 return index
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)
75 self._config = config
76 self.append((_('None'), 'none'))
77 self.append((_('iPod'), 'ipod'))
78 self.append((_('Filesystem-based'), 'filesystem'))
79 self.append((_('MTP'), 'mtp'))
81 def get_index(self):
82 for index, row in enumerate(self):
83 if self._config.device_type == row[self.C_DEVICE_TYPE]:
84 return index
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)
96 self._config = config
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))
101 def get_index(self):
102 for index, row in enumerate(self):
103 if self._config.on_sync_delete and row[self.C_ON_SYNC_DELETE]:
104 return index
105 if self._config.on_sync_mark_played and row[self.C_ON_SYNC_MARK_PLAYED] \
106 and not self._config.on_sync_delete:
107 return index
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):
116 def new(self):
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)
149 else:
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)
175 else:
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 # Initialize the UI state with configuration settings
198 self.checkbutton_enable.set_active(self._config.mygpo_enabled)
199 self.entry_username.set_text(self._config.mygpo_username)
200 self.entry_password.set_text(self._config.mygpo_password)
201 self.entry_caption.set_text(self._config.mygpo_device_caption)
203 # Disable mygpo sync while the dialog is open
204 self._enable_mygpo = self._config.mygpo_enabled
205 self._config.mygpo_enabled = False
207 def on_dialog_destroy(self, widget):
208 # Re-enable mygpo sync if the user has selected it
209 self._config.mygpo_enabled = self._enable_mygpo
210 # Make sure the device is successfully created/updated
211 self.mygpo_client.create_device()
212 # Flush settings for mygpo client now
213 self.mygpo_client.flush(now=True)
215 if self.callback_finished:
216 self.callback_finished()
218 def on_button_close_clicked(self, widget):
219 self.main_window.destroy()
221 def on_button_advanced_clicked(self, widget):
222 self.main_window.destroy()
223 gPodderConfigEditor(self.parent_window, _config=self._config)
225 def on_combo_audio_player_app_changed(self, widget):
226 index = self.combo_audio_player_app.get_active()
227 self._config.player = self.audio_player_model.get_command(index)
229 def on_combo_video_player_app_changed(self, widget):
230 index = self.combo_video_player_app.get_active()
231 self._config.videoplayer = self.video_player_model.get_command(index)
233 def on_button_audio_player_clicked(self, widget):
234 result = self.show_text_edit_dialog(_('Configure audio player'), \
235 _('Command:'), \
236 self._config.player)
238 if result:
239 self._config.player = result
240 index = self.audio_player_model.get_index(self._config.player)
241 self.combo_audio_player_app.set_active(index)
243 def on_button_video_player_clicked(self, widget):
244 result = self.show_text_edit_dialog(_('Configure video player'), \
245 _('Command:'), \
246 self._config.videoplayer)
248 if result:
249 self._config.videoplayer = result
250 index = self.video_player_model.get_index(self._config.videoplayer)
251 self.combo_video_player_app.set_active(index)
253 def format_update_interval_value(self, scale, value):
254 value = int(value)
255 if value == 0:
256 return _('manual only')
257 else:
258 return util.format_seconds_to_hour_min_sec(self.update_interval_presets[int(value)]*60)
260 def on_update_interval_value_changed(self, range):
261 value = int(range.get_value())
262 self._config.auto_update_feeds = (value > 0)
263 self._config.auto_update_frequency = self.update_interval_presets[value]
265 def on_combo_auto_download_changed(self, widget):
266 index = self.combo_auto_download.get_active()
267 self.auto_download_model.set_index(index)
269 def on_combobox_on_sync_changed(self, widget):
270 index = self.combobox_on_sync.get_active()
271 self.on_sync_model.set_index(index)
273 def on_combobox_device_type_changed(self, widget):
274 index = self.combobox_device_type.get_active()
275 self.device_type_model.set_index(index)
276 if index == 0:
277 self.btn_filesystemMountpoint.set_label('')
278 self.btn_filesystemMountpoint.set_sensitive(False)
279 if index == 1:
280 self.btn_filesystemMountpoint.set_label(self._config.ipod_mount)
281 self.btn_filesystemMountpoint.set_sensitive(True)
282 self._config.connect_gtk_togglebutton('ipod_purge_old_episodes', self.checkbutton_delete_played)
283 if index == 2:
284 self.btn_filesystemMountpoint.set_label(self._config.mp3_player_folder)
285 self.btn_filesystemMountpoint.set_sensitive(True)
286 self._config.connect_gtk_togglebutton('mp3_player_delete_played', self.checkbutton_delete_played)
287 if index == 3:
288 self.btn_filesystemMountpoint.set_label('')
289 self.btn_filesystemMountpoint.set_sensitive(False)
291 children = self.btn_filesystemMountpoint.get_children()
292 if children:
293 label = children.pop()
294 label.set_alignment(0., .5)
296 def on_btn_device_mountpoint_clicked(self, widget):
297 fs = gtk.FileChooserDialog( title = _('Select folder for mount point'), action = gtk.FILE_CHOOSER_ACTION_SELECT_FOLDER)
298 fs.add_button( gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
299 fs.add_button( gtk.STOCK_OPEN, gtk.RESPONSE_OK)
300 fs.set_current_folder(self.btn_filesystemMountpoint.get_label())
301 if fs.run() == gtk.RESPONSE_OK:
302 filename = fs.get_filename()
303 if self._config.device_type == 'filesystem':
304 self._config.mp3_player_folder = filename
305 elif self._config.device_type == 'ipod':
306 self._config.ipod_mount = filename
308 # Request an update of the mountpoint button
309 self.on_combobox_device_type_changed(None)
311 fs.destroy()
313 def format_expiration_value(self, scale, value):
314 value = int(value)
315 if value == 0:
316 return _('manually')
317 else:
318 return N_('after %(count)d day', 'after %(count)d days', value) % {'count':value}
320 def on_expiration_value_changed(self, range):
321 value = int(range.get_value())
323 if value == 0:
324 self.checkbutton_expiration_unplayed.set_active(False)
325 self._config.auto_remove_played_episodes = False
326 self._config.auto_remove_unplayed_episodes = False
327 else:
328 self._config.auto_remove_played_episodes = True
329 self._config.episode_old_age = value
331 self.checkbutton_expiration_unplayed.set_sensitive(value > 0)
333 def on_enabled_toggled(self, widget):
334 # Only update indirectly (see on_dialog_destroy)
335 self._enable_mygpo = widget.get_active()
337 def on_username_changed(self, widget):
338 self._config.mygpo_username = widget.get_text()
340 def on_password_changed(self, widget):
341 self._config.mygpo_password = widget.get_text()
343 def on_device_caption_changed(self, widget):
344 self._config.mygpo_device_caption = widget.get_text()
346 def on_button_overwrite_clicked(self, button):
347 title = _('Replace subscription list on server')
348 message = _('Remote podcasts that have not been added locally will be removed on the server. Continue?')
349 if self.show_confirmation(message, title):
350 def thread_proc():
351 self._config.mygpo_enabled = True
352 self.on_send_full_subscriptions()
353 self._config.mygpo_enabled = False
354 threading.Thread(target=thread_proc).start()