41
39
from twisted.internet import defer
43
41
from encuentro import platform, data, update
42
from encuentro.config import config, signal
44
43
from encuentro.data import Status
45
44
from encuentro.network import (
46
45
BadCredentialsError,
50
from encuentro.ui import central_panel, wizard, preferences
49
from encuentro.ui import (
52
57
logger = logging.getLogger('encuentro.main')
62
67
u"alguna configuración en el programa."
65
# FIXME: need an About dialog, connected to the proper signals below
66
# title: Encuentro <version> <-- need to receive the version when exec'ed
67
# comments: Simple programa que permite buscar, descargar y ver
68
# contenido del canal Encuentro y otros.
69
# smaller: Copyright 2010-2013 Facundo Batista
70
# url: http://encuentro.taniquetil.com.ar
71
# somewhere (maybe with a button), the license: the content of LICENSE.txt
73
# FIXME: need to put an icon that looks nice in alt-tab, taskbar, unity, etc
75
# FIXME: need to make Encuentro "iconizable"
77
# FIXME: set up a status icon, when the icon is clicked the main window should
78
# appear or disappear, keeping the position and size of the position after
82
class MainUI(QMainWindow):
72
Simple programa que permite buscar, descargar y ver<br/>
73
contenido del canal Encuentro y otros.<br/>
77
<small>Copyright 2010-2013 Facundo Batista</small><br/>
79
<a href="http://encuentro.taniquetil.com.ar">
80
http://encuentro.taniquetil.com.ar
86
class MainUI(remembering.RememberingMainWindow):
85
_config_file = os.path.join(platform.config_dir, 'encuentro.conf')
86
print "Using configuration file:", repr(_config_file)
87
89
_programs_file = os.path.join(platform.data_dir, 'encuentro.data')
89
91
def __init__(self, version, app_quit):
90
92
super(MainUI, self).__init__()
91
93
self.app_quit = app_quit
92
94
self.finished = False
93
# FIXME: size and positions should remain the same between starts
95
self.version = version
96
96
self.setWindowTitle('Encuentro')
98
98
self.programs_data = data.ProgramsData(self, self._programs_file)
99
self.config = self._load_config()
101
101
self.downloaders = {}
102
102
for downtype, dloader_class in all_downloaders.iteritems():
103
self.downloaders[downtype] = dloader_class(self.config)
103
self.downloaders[downtype] = dloader_class()
105
105
# finish all gui stuff
106
106
self.big_panel = central_panel.BigPanel(self)
111
111
# the setting of menubar should be almost in the end, because it may
112
112
# trigger the wizard, which needs big_panel and etc.
113
self.action_play = self.action_download = None
114
self.filter_line = self.filter_cbox = self.needsomething_alert = None
118
if config['autorefresh']:
119
ue = update.UpdateEpisodes(self)
115
122
logger.debug("Main UI started ok")
117
def _save_config(self):
118
"""Save the config to disk."""
119
with open(self._config_file, 'wb') as fh:
120
pickle.dump(self.config, fh)
122
def _load_config(self):
123
"""Load the config from disk."""
124
# get config from file, or defaults
125
if os.path.exists(self._config_file):
126
with open(self._config_file, 'rb') as fh:
127
config = pickle.load(fh)
128
if self.programs_data.reset_config_from_migration:
130
config['password'] = ''
131
config.pop('cols_width', None)
132
config.pop('cols_order', None)
133
config.pop('selected_row', None)
124
def _touch_config(self):
125
"""Do some config processing."""
137
126
# log the config, but without user and pass
138
127
safecfg = config.copy()
139
128
if 'user' in safecfg:
145
134
# we have a default for download dir
146
135
if not config.get('downloaddir'):
147
136
config['downloaddir'] = platform.get_download_dir()
138
# maybe clean some config
139
if self.programs_data.reset_config_from_migration:
141
config['password'] = ''
142
config.pop('cols_width', None)
143
config.pop('cols_order', None)
144
config.pop('selected_row', None)
150
146
def have_config(self):
151
147
"""Return if some config is needed."""
152
return self.config.get('user') and self.config.get('password')
148
return config.get('user') and config.get('password')
154
150
def have_metadata(self):
155
151
"""Return if metadata is needed."""
169
165
action_reload.triggered.connect(self.refresh_episodes)
170
166
menu_appl.addAction(action_reload)
172
# FIXME: set an icon for preferences
173
action_preferences = QAction(u'&Preferencias', self)
168
icon = self.style().standardIcon(QStyle.SP_FileDialogDetailedView)
169
action_preferences = QAction(icon, u'&Preferencias', self)
174
170
action_preferences.triggered.connect(self.open_preferences)
175
171
action_preferences.setToolTip(
176
172
u'Configurar distintos parámetros del programa')
179
175
menu_appl.addSeparator()
181
# FIXME: set an icon for about
182
_act = QAction('&Acerca de', self)
183
# FIXME: connect signal
177
icon = self.style().standardIcon(QStyle.SP_MessageBoxInformation)
178
_act = QAction(icon, '&Acerca de', self)
179
_act.triggered.connect(self.open_about_dialog)
184
180
_act.setToolTip(u'Muestra información de la aplicación')
185
181
menu_appl.addAction(_act)
188
184
_act = QAction(icon, '&Salir', self)
189
185
_act.setShortcut('Ctrl+Q')
190
186
_act.setToolTip(u'Sale de la aplicación')
191
_act.triggered.connect(qApp.quit)
187
_act.triggered.connect(self.on_close)
192
188
menu_appl.addAction(_act)
206
202
self.action_play = QAction(icon, '&Reproducir', self)
207
203
self.action_play.setEnabled(False)
208
204
self.action_play.setToolTip(TTIP_PLAY_D)
209
self.action_play.triggered.connect(self.play_episode)
205
self.action_play.triggered.connect(self.on_play_action)
210
206
menu_prog.addAction(self.action_play)
212
208
# toolbar for buttons
217
213
toolbar.addAction(action_reload)
218
214
toolbar.addAction(action_preferences)
221
# FIXME: see if we can put this toolbar to the extreme
222
# right of the window
223
toolbar = self.addToolBar('')
216
# filter text and button, to the right
218
spacer.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
219
toolbar.addWidget(spacer)
224
220
toolbar.addWidget(QLabel(u"Filtro: "))
225
221
self.filter_line = QLineEdit()
222
self.filter_line.setMaximumWidth(150)
226
223
self.filter_line.textChanged.connect(self.on_filter_changed)
227
224
toolbar.addWidget(self.filter_line)
228
225
self.filter_cbox = QCheckBox(u"Sólo descargados")
229
226
self.filter_cbox.stateChanged.connect(self.on_filter_changed)
230
227
toolbar.addWidget(self.filter_cbox)
229
# if needed, a warning that stuff needs to be configured
232
230
icon = self.style().standardIcon(QStyle.SP_MessageBoxWarning)
233
231
m = u"Necesita configurar algo; haga click aquí para abrir el wizard"
234
232
self.needsomething_alert = QAction(icon, m, self)
235
233
self.needsomething_alert.triggered.connect(self._start_wizard)
236
234
toolbar.addAction(self.needsomething_alert)
237
if not self.config.get('nowizard'):
235
if not config.get('nowizard'):
238
236
self._start_wizard()
239
237
self._review_need_something_indicator()
251
249
cbox = self.filter_cbox.checkState()
252
250
self.episodes_list.set_filter(text, cbox)
252
# after applying filter, nothing is selected, so check buttons
253
# (easiest way to clean them all)
254
self.check_download_play_buttons()
254
256
def _review_need_something_indicator(self):
255
257
"""Hide/show/enable/disable different indicators if need sth."""
256
258
needsomething = bool(not self.have_config() or
263
265
This shutdown con be called at any time, even on init, so we have
264
266
extra precautions about which attributes we have.
266
# self._save_states() FIXME: if we need to save states, the call is here
268
signal.emit('save_state')
268
270
self.finished = True
270
272
programs_data = getattr(self, 'programs_data', None)
313
320
program.state = Status.none
316
def _show_message(self, err_type, text):
323
def show_message(self, err_type, text):
317
324
"""Show different messages to the user."""
318
325
if self.finished:
319
326
logger.debug("Ignoring message: %r", text)
338
345
def refresh_episodes(self, _=None):
339
346
"""Update and refresh episodes."""
340
update.UpdateEpisodes(self)
347
ue = update.UpdateEpisodes(self)
342
350
def download_episode(self, _=None):
343
351
"""Download the episode(s)."""
344
352
items = self.episodes_list.selectedItems()
345
353
for item in items:
346
354
episode = self.programs_data[item.episode_id]
347
self._queue_download(episode)
355
self.queue_download(episode)
349
357
@defer.inlineCallbacks
350
def _queue_download(self, episode):
358
def queue_download(self, episode):
351
359
"""User indicated to download something."""
352
360
logger.debug("Download requested of %s", episode)
353
361
if episode.state != Status.none:
368
377
filename, episode = yield self._episode_download(episode)
369
378
except CancelledError:
370
379
logger.debug("Got a CancelledError!")
371
self.episodes_download.end(error=u"Cancelao")
380
self.episodes_download.end(error=u"Cancelado")
372
381
except BadCredentialsError:
373
382
logger.debug("Bad credentials error!")
374
383
msg = (u"Error con las credenciales: hay que configurar "
375
384
u"usuario y clave correctos")
376
self._show_message('BadCredentialsError', msg)
385
self.show_message('BadCredentialsError', msg)
377
386
self.episodes_download.end(error=msg)
378
387
except Exception, e:
379
388
logger.debug("Unknown download error: %s", e)
380
389
err_type = e.__class__.__name__
381
self._show_message(err_type, str(e))
390
self.show_message(err_type, str(e))
382
391
self.episodes_download.end(error=u"Error: " + str(e))
384
393
logger.debug("Episode downloaded: %s", episode)
404
414
self.episodes_download.progress)
405
415
episode_name = u"%s - %s - %s" % (episode.channel, episode.section,
407
if self.config.get('notification', True) and pynotify is not None:
417
if config.get('notification', True) and pynotify is not None:
408
418
n = pynotify.Notification(u"Descarga finalizada", episode_name)
410
420
defer.returnValue((fname, episode))
413
423
"""Open the preferences dialog."""
414
424
dlg = preferences.PreferencesDialog()
416
# FIXME: el dialogo debería grabar solo cuando lo cierran
426
# after dialog closes, config changed, so review indicators
418
427
self._review_need_something_indicator()
429
def adjust_episode_info(self, episode):
430
"""Adjust the episode info."""
431
self.episodes_list.episode_info.update(episode)
420
433
def check_download_play_buttons(self):
421
434
"""Set both buttons state according to the selected episodes."""
422
435
items = self.episodes_list.selectedItems()
426
437
# 'play' button should be enabled if only one row is selected and
427
438
# its state is 'downloaded'
447
458
self.action_download.setEnabled(download_enabled)
448
459
self.action_download.setToolTip(ttip)
450
def play_episode(self, _=None):
461
def on_play_action(self, _=None):
451
462
"""Play the selected episode."""
452
463
items = self.episodes_list.selectedItems()
453
464
if len(items) != 1:
457
468
episode = self.programs_data[item.episode_id]
458
downloaddir = self.config.get('downloaddir', '')
469
self.play_episode(episode)
471
def play_episode(self, episode):
472
"""Play an episode."""
473
downloaddir = config.get('downloaddir', '')
459
474
filename = os.path.join(downloaddir, episode.filename)
461
476
logger.info("Play requested of %s", episode)
468
483
logger.warning("Aborted playing, file not found: %r", filename)
469
484
msg = (u"No se encontró el archivo para reproducir: " +
471
self._show_message('Error al reproducir', msg)
486
self.show_message('Error al reproducir', msg)
472
487
episode.state = Status.none
473
488
self.episodes_list.set_color(episode)
475
def cancel_download(self):
490
def cancel_download(self, episode):
476
491
"""Cancel the downloading of an episode."""
477
items = self.episodes_list.selectedItems()
479
raise ValueError("Wrong call to cancel_download, with %d "
480
"selections" % len(items))
482
episode = self.programs_data[item.episode_id]
483
492
logger.info("Cancelling download of %s", episode)
484
493
self.episodes_download.cancel()
485
494
downloader = self.downloaders[episode.downtype]
486
495
downloader.cancel()
496
episode.state = Status.none
498
def open_about_dialog(self):
499
"""Show the about dialog."""
500
title = "Encuentro v" + self.version
501
text = ABOUT_TEXT % (self.version,)
502
QMessageBox.about(self, title, text)