Fix screen sizes
[enigma2-plugins.git] / simplerss / src / RSSScreens.py
1 from enigma import eTimer
2
3 from Screens.Screen import Screen
4 from Screens.MessageBox import MessageBox
5 from Screens.ChoiceBox import ChoiceBox
6 from Components.ActionMap import ActionMap
7 from Components.Label import Label
8 from Components.ScrollLabel import ScrollLabel
9 from Components.Pixmap import Pixmap
10
11 from RSSList import RSSList
12 from RSSSetup import RSSSetup
13
14 class PictureView(Screen):
15         """Downloads a Picture, shows it and delete the temporary file"""
16
17         skin = """
18                 <screen position="100,100" size="460,400" title="Simple RSS Reader" >
19                         <widget name="content" position="0,0" size="460,400" alphatest="on"/>
20                 </screen>"""
21
22         filename = '/tmp/simplerss_enclosure'
23
24         def __init__(self, session, url):
25                 Screen.__init__(self, session)
26
27                 self.url = url
28
29                 self["actions"] = ActionMap([ "OkCancelActions" ], 
30                 {
31                         "ok": self.close,
32                         "cancel": self.close,
33                 })
34
35                 self["content"] = Pixmap()
36
37                 self.onLayoutFinish.append(self.fetchFile)
38
39         def fetchFile(self):
40                 # Fetch file
41                 from twisted.web.client import downloadPage
42                 downloadPage(self.url, self.filename).addCallback(self.gotFile).addErrback(self.error)
43
44         def gotFile(self, data = ""):
45                 # Determine Aspect
46                 from Components.AVSwitch import AVSwitch
47                 aspect = AVSwitch().getAspectRatioSetting()/2
48
49                 # Load Picture
50                 from enigma import loadPic
51                 ptr = loadPic(self.filename, 460, 400, aspect)
52
53                 # Show Picture
54                 self["content"].instance.setPixmap(ptr)
55
56                 # Remove Temporary File
57                 from os import unlink
58                 unlink(self.filename)
59
60         def error(self):
61                 self.session.open(
62                         MessageBox,
63                         "Error while loading Picture.",
64                         type = MessageBox.TYPE_ERROR,
65                         timeout = 3
66                 )
67                 self.close()
68
69 class RSSBaseView(Screen):
70         """Base Screen for all Screens used in SimpleRSS"""
71
72         def __init__(self, session, poller):
73                 Screen.__init__(self, session)
74                 self.rssPoller = poller
75                 self.pollDialog = None
76
77         def errorPolling(self, errmsg = ""):
78                 # Hide Dialog if shown
79                 if self.pollDialog:
80                         self.pollDialog.close()
81                         self.pollDialog = None
82
83                 # TODO: fix error not showing when dialog was just hid (work around by using a timer?)
84                 self.session.open(
85                         MessageBox,
86                         "Error while parsing Feed, this usually means there is something wrong with it.",
87                         type = MessageBox.TYPE_ERROR,
88                         timeout = 3
89                 )
90
91         def singleUpdate(self, feedid, errback = None):
92                 # Default errorback to self.errorPolling
93                 # If an empty errorback is wanted the Screen needs to provide it
94                 if errback is None:
95                         errback = self.errorPolling
96
97                 # Tell Poller to poll
98                 self.rssPoller.singlePoll(feedid, callback=True, errorback=errback)
99
100                 # Open Dialog and save locally
101                 self.pollDialog = self.session.open(
102                         MessageBox,
103                         "Update is being done in Background.\nContents will automatically be updated when it's done.",
104                         type = MessageBox.TYPE_INFO,
105                         timeout = 5
106                 )
107
108         def selectEnclosure(self, enclosures):
109                 # Empty List
110                 if enclosures is None:
111                         return
112
113                 count = len(enclosures)
114                 # Select stream in ChoiceBox if more than one present
115                 if count > 1:
116                         self.session.openWithCallback(
117                                 self.enclosureSelected,
118                                 ChoiceBox,
119                                 "Select enclosure to play",
120                                 [(x[0][x[0].rfind("/")+1:].replace('%20', ' ').replace('%5F', '_').replace('%2D', '-'), x) for x in enclosures]
121                         )
122                 # Play if one present
123                 elif count:
124                         self.enclosureSelected((None, enclosures[0]))
125
126         def enclosureSelected(self, enclosure):
127                 if enclosure:
128                         (url, type) = enclosure[1]
129
130                         print "[SimpleRSS] Trying to play back enclosure: url=%s, type=%s" % (url, type)
131
132                         if type in ["video/mpeg", "audio/mpeg"]:
133                                 from enigma import eServiceReference
134                                 from Screens.MediaPlayer import MediaPlayer
135
136                                 mp = self.session.open(MediaPlayer)
137                                 ref = eServiceReference(4097, 0, url)
138
139                                 mp.switchToPlayList()
140                                 mp.playlist.addFile(ref)
141                                 mp.playlist.updateList()
142
143                                 mp.playServiceRefEntry(ref)
144                         elif type in ["image/jpeg", "image/png", "image/gif", "image/bmp"]:
145                                 self.session.open(PictureView, url)
146
147 class RSSEntryView(RSSBaseView):
148         """Shows a RSS Item"""
149         skin = """
150                 <screen position="100,100" size="460,420" title="Simple RSS Reader" >
151                         <widget name="info" position="0,0" size="460, 20" halign="right" font="Regular; 18" />
152                         <widget name="content" position="0,20" size="460,420" font="Regular; 22" />
153                 </screen>"""
154
155         def __init__(self, session, data, feedTitle="", cur_idx=None, entries=None, nextEntryCB=None, previousEntryCB=None, nextFeedCB=None, previousFeedCB=None):
156                 RSSBaseView.__init__(self, session, None)
157
158                 self.data = data
159                 self.feedTitle = feedTitle
160                 self.nextEntryCB = nextEntryCB
161                 self.previousEntryCB = previousEntryCB
162                 self.nextFeedCB = nextFeedCB
163                 self.previousFeedCB = previousFeedCB
164                 self.cur_idx = cur_idx
165                 self.entries = entries
166
167                 if cur_idx is not None and entries is not None:
168                         self["info"] = Label("Entry %s/%s" % (cur_idx+1, entries))
169                 else:
170                         self["info"] = Label()
171
172                 if data is not None:
173                         self["content"] = ScrollLabel("\n\n".join([data[0], data[2], " ".join([str(len(data[3])), "Enclosures"])]))
174                 else:
175                         self["content"] = ScrollLabel()
176
177                 self["actions"] = ActionMap([ "OkCancelActions", "ChannelSelectBaseActions", "ColorActions", "DirectionActions" ],
178                 {
179                         "cancel": self.close,
180                         "ok": self.selectEnclosure,
181                         "yellow": self.selectEnclosure,
182                         "up": self.up,
183                         "down": self.down,
184                         "right": self.next,
185                         "left": self.previous,
186                         "nextBouquet": self.nextFeed,
187                         "prevBouquet": self.previousFeed,
188                 })
189
190                 self.onLayoutFinish.append(self.setConditionalTitle)
191
192         def setConditionalTitle(self):
193                 self.setTitle(': '.join(["Simple RSS Reader", self.feedTitle]))
194
195         def up(self):
196                 self["content"].pageUp()
197
198         def down(self):
199                 self["content"].pageDown()
200
201         def next(self):
202                 if self.nextEntryCB is not None:
203                         (self.data, self.cur_idx, self.entries) = self.nextEntryCB()
204                         self.setContent()
205
206         def previous(self):
207                 if self.previousEntryCB is not None:
208                         (self.data, self.cur_idx, self.entries) = self.previousEntryCB()
209                         self.setContent()
210
211         def nextFeed(self):
212                 # Show next Feed
213                 if self.nextFeedCB is not None:
214                         result = self.nextFeedCB()
215                         self.feedTitle = result[0]
216                         self.entries = len(result[1])
217                         if self.entries:
218                                 self.cur_idx = 0
219                                 self.data = result[1][0]
220                         else:
221                                 self.cur_idx = None
222                                 self.data = None
223                         self.setConditionalTitle()
224                         self.setContent()
225
226         def previousFeed(self):
227                 # Show previous Feed
228                 if self.previousFeedCB is not None:
229                         result = self.previousFeedCB()
230                         self.feedTitle = result[0]
231                         self.entries = len(result[1])
232                         if self.entries:
233                                 self.cur_idx = 0
234                                 self.data = result[1][0]
235                         else:
236                                 self.cur_idx = None
237                                 self.data = None
238                         self.setConditionalTitle()
239                         self.setContent()
240
241         def setContent(self):
242                 if self.cur_idx is not None and self.entries is not None:
243                         self["info"].setText("Entry %s/%s" % (self.cur_idx+1, self.entries))
244                 else:
245                         self["info"].setText("")
246                 if self.data is not None:
247                         self["content"].setText("\n\n".join([self.data[0], self.data[2], " ".join([str(len(self.data[3])), "Enclosures"])]))
248                 else:
249                         self["content"].setText("No such Item.")
250
251         def selectEnclosure(self):
252                 if self.data is not None:
253                         RSSBaseView.selectEnclosure(self, self.data[3])
254
255 class RSSFeedView(RSSBaseView):
256         """Shows a RSS-Feed"""
257         skin = """
258                 <screen position="100,100" size="460,415" title="Simple RSS Reader" >
259                         <widget name="info" position="0,0" size="460,20" halign="right" font="Regular; 18" />
260                         <widget name="content" position="0,20" size="460,300" scrollbarMode="showOnDemand" />
261                         <widget name="summary" position="0,320" size="460,95" font="Regular;16" />
262                 </screen>"""
263
264         def __init__(self, session, data, feedTitle = "", newItems=False, nextFeedCB=None, previousFeedCB=None, rssPoller=None, id = None):
265                 RSSBaseView.__init__(self, session, rssPoller)
266
267                 self.data = data
268                 self.feedTitle = feedTitle
269                 self.newItems = newItems
270                 self.id = id
271                 self.nextFeedCB=nextFeedCB
272                 self.previousFeedCB=previousFeedCB
273
274                 self["content"] = RSSList(data)
275                 self["summary"] = Label()
276                 self["info"] = Label()
277
278                 if not newItems:
279                         self["actions"] = ActionMap([ "OkCancelActions", "ChannelSelectBaseActions", "MenuActions", "ColorActions" ], 
280                         {
281                                 "ok": self.showCurrentEntry,
282                                 "cancel": self.close,
283                                 "nextBouquet": self.next,
284                                 "prevBouquet": self.previous,
285                                 "menu": self.menu,
286                                 "yellow": self.selectEnclosure,
287                         })
288                         self.onShown.append(self.__show)
289                         self.onClose.append(self.__close)
290
291                         self.timer = None
292                 else:
293                         self["actions"] = ActionMap([ "OkCancelActions" ], 
294                         {
295                                 "cancel": self.close,
296                         })
297
298                         self.timer = eTimer()
299                         self.timer.timeout.get().append(self.timerTick)
300                         self.onExecBegin.append(self.startTimer)
301
302                 self["content"].connectSelChanged(self.updateInfo)
303                 self.onLayoutFinish.extend([self.updateInfo, self.setConditionalTitle])
304
305         def startTimer(self):
306                 self.timer.startLongTimer(5)
307
308         def timerTick(self):
309                 self.close()
310
311         def __show(self):
312                 self.rssPoller.addCallback(self.pollCallback)
313
314         def __close(self):
315                 if self.timer is not None:
316                         self.timer.timeout.get().remove(self.timerTick)
317                         self.timer = None
318                 self.rssPoller.removeCallback(self.pollCallback)
319
320         def pollCallback(self, id = None):
321                 print "[SimpleRSS] SimpleRSSFeed called back"
322                 current_entry = self["content"].getCurrentEntry()
323
324                 if id is not None and self.id == id+1:
325                         print "[SimpleRSS] pollCallback recieved local feed", self.id
326                         self.feedTitle = self.rssPoller.feeds[id].title
327                         self.data = self.rssPoller.feeds[id].history
328                 elif self.id == 0:
329                         print "[SimpleRSS] pollCallback recieved all or non-local feed, updating active view (new_items)"
330                         self.data = self.rssPoller.new_items
331                 else:
332                         print "[SimpleRSS] pollCallback recieved all or non-local feed, updating", self.id
333                         self.feedTitle = self.rssPoller.feeds[self.id-1].title
334                         self.data = self.rssPoller.feeds[self-id-1].history
335
336                 self["content"].l.setList(self.data)
337                 self["content"].moveToEntry(current_entry)
338
339                 self.setConditionalTitle()
340                 self.updateInfo()
341
342         def setConditionalTitle(self):
343                 if not self.newItems:
344                         self.setTitle(': '.join(["Simple RSS Reader", self.feedTitle]))
345                 else:
346                         self.setTitle("Simple RSS Reader: New Items")
347
348         def updateInfo(self):
349                 current_entry = self["content"].getCurrentEntry()
350                 if current_entry:
351                         self["summary"].setText(current_entry[2])
352
353                         cur_idx = self["content"].getCurrentIndex()
354                         self["info"].setText("Entry %s/%s" % (cur_idx+1, len(self.data)))
355                 else:
356                         self["summary"].setText("Feed is empty.")
357                         self["info"].setText("")
358
359         def menu(self):
360                 if self.id > 0:
361                         self.singleUpdate(self.id-1)
362
363         def nextEntryCB(self):
364                 self["content"].moveDown()
365                 return (self["content"].getCurrentEntry(), self["content"].getCurrentIndex(), len(self.data))
366
367         def previousEntryCB(self):
368                 self["content"].moveUp()
369                 return (self["content"].getCurrentEntry(), self["content"].getCurrentIndex(), len(self.data))
370
371         # TODO: Fix moving back to previously marked entry (same goes for self.previous)
372         def next(self):
373                 # Show next Feed
374                 if self.nextFeedCB is not None:
375                         result = self.nextFeedCB()
376                         (self.feedTitle, self.data, self.id) = result
377                         #current_entry = self["content"].getCurrentEntry()
378                         self["content"].l.setList(self.data) # Update list
379                         self["content"].moveToIndex(0)
380                         #self["content"].moveToEntry(current_entry)
381                         self.updateInfo() # In case entry is no longer in history
382                         self.setConditionalTitle() # Update title
383                         return result
384                 return (self.feedTitle, self.data, self.id)
385
386         def previous(self):
387                 # Show previous Feed
388                 if self.previousFeedCB is not None:
389                         result = self.previousFeedCB()
390                         (self.feedTitle, self.data, self.id) = result
391                         #current_entry = self["content"].getCurrentEntry()
392                         self["content"].l.setList(self.data) # Update list
393                         self["content"].moveToIndex(0)
394                         #self["content"].moveToEntry(current_entry)
395                         self.updateInfo() # In case entry is no longer in history
396                         self.setConditionalTitle() # Update title
397                         return result
398                 return (self.feedTitle, self.data, self.id)
399
400         def checkEmpty(self):
401                 if self.id > 0 and not len(self.data):
402                         self.singleUpdate(self.id-1)
403
404         def showCurrentEntry(self):
405                 current_entry = self["content"].getCurrentEntry()
406                 if current_entry is None: # empty list
407                         return
408
409                 self.session.openWithCallback(
410                         self.updateInfo,
411                         RSSEntryView,
412                         current_entry,
413                         cur_idx=self["content"].getCurrentIndex(),
414                         entries=len(self.data),
415                         feedTitle=self.feedTitle,
416                         nextEntryCB=self.nextEntryCB,
417                         previousEntryCB=self.previousEntryCB,
418                         nextFeedCB=self.next,
419                         previousFeedCB=self.previous
420                 )
421
422         def selectEnclosure(self):
423                 current_entry = self["content"].getCurrentEntry()
424                 if current_entry is None: # empty list
425                         return
426
427                 RSSBaseView.selectEnclosure(self, current_entry[3])
428
429 class RSSOverview(RSSBaseView):
430         """Shows an Overview over all RSS-Feeds known to rssPoller"""
431         skin = """
432                 <screen position="100,100" size="460,415" title="Simple RSS Reader" >
433                         <widget name="info" position="0,0" size="460,20" halign="right" font="Regular; 18" />
434                         <widget name="content" position="0,20" size="460,300" scrollbarMode="showOnDemand" />
435                         <widget name="summary" position="0,320" size="460,95" font="Regular;16" />
436                 </screen>"""
437
438         def __init__(self, session, poller):
439                 RSSBaseView.__init__(self, session, poller)
440
441                 self["actions"] = ActionMap([ "OkCancelActions", "MenuActions", "ColorActions" ], 
442                 {
443                         "ok": self.showCurrentEntry,
444                         "cancel": self.close,
445                         "menu": self.menu,
446                         "yellow": self.selectEnclosure,
447                 })
448
449                 self.fillFeeds()
450
451                 # We always have at least "New Items"-Feed
452                 self["content"] = RSSList(self.feeds)
453                 self["summary"] = Label(self.feeds[0][2])
454                 self["info"] = Label("Feed 1/%s" % len(self.feeds))
455
456                 self["content"].connectSelChanged(self.updateInfo)
457                 self.onShown.append(self.__show)
458                 self.onClose.append(self.__close)
459
460         def __show(self):
461                 self.rssPoller.addCallback(self.pollCallback)
462
463         def __close(self):
464                 self.rssPoller.removeCallback(self.pollCallback)
465
466         def fillFeeds(self):
467                 self.feeds = [(
468                         "New Items",
469                         "New Items since last Auto-Update",
470                         ' '.join([str(len(self.rssPoller.new_items)), "Entries"]),
471                         self.rssPoller.new_items
472                 )]
473                 self.feeds.extend([
474                         (
475                                 feed.title,
476                                 feed.description,
477                                 ' '.join([str(len(feed.history)), "Entries"]),
478                                 feed.history
479                         )
480                                 for feed in self.rssPoller.feeds
481                 ])
482
483         def pollCallback(self, id = None):
484                 print "[SimpleRSS] SimpleRSS called back"
485                 current_entry = self["content"].getCurrentEntry()
486
487                 if id is not None:
488                         print "[SimpleRSS] pollCallback updating feed", id
489                         self.feeds[id+1] = (
490                                 self.rssPoller.feeds[id].title,
491                                 self.rssPoller.feeds[id].description,
492                                 ' '.join([str(len(self.rssPoller.feeds[id].history)), "Entries"]),
493                                 self.rssPoller.feeds[id].history
494                         )
495                 else:
496                         print "[SimpleRSS] pollCallback updating all feeds"
497                         self.fillFeeds()
498
499                 self["content"].l.setList(self.feeds)
500                 self["content"].moveToEntry(current_entry)
501
502                 self.updateInfo()
503
504         def updateInfo(self):
505                 current_entry = self["content"].getCurrentEntry()
506                 if current_entry:
507                         self["summary"].setText(current_entry[2])
508                         self["info"].setText("Feed %s/%s" % (self["content"].getCurrentIndex()+1, len(self.feeds)))
509                 # Should never happen
510                 else:
511                         self["summary"].setText("")
512                         self["info"].setText("")
513
514         def menu(self):
515                 cur_idx = self["content"].getCurrentIndex()
516                 if cur_idx > 0:
517                         possible_actions = [
518                                 (_("Update Feed"), "update"),
519                                 (_("Setup"), "setup"),
520                                 (_("Close"), "close")
521                         ]
522                 else:
523                         possible_actions = [
524                                 (_("Setup"), "setup"),
525                                 (_("Close"), "close")
526                         ]
527                 self.session.openWithCallback(
528                         self.menuChoice,
529                         ChoiceBox,
530                         "What to do?",
531                         possible_actions
532                 )
533
534         def menuChoice(self, result):
535                 if result:
536                         if result[1] == "update":
537                                 cur_idx = self["content"].getCurrentIndex()
538                                 if cur_idx > 0:
539                                         self.singleUpdate(cur_idx-1)
540                         elif result[1] == "setup":
541                                 self.session.openWithCallback(self.refresh, RSSSetup, rssPoller=self.rssPoller)
542                         elif result[1] == "close":
543                                 self.close()
544
545         def refresh(self):
546                 current_entry = self["content"].getCurrentEntry()
547
548                 self.fillFeeds()
549                 self["content"].l.setList(self.feeds)
550
551                 self["content"].moveToEntry(current_entry)
552                 self.updateInfo()
553
554         def nextFeedCB(self):
555                 self["content"].moveUp()
556                 current_entry = self["content"].getCurrentEntry()
557                 return (current_entry[0], current_entry[3], self["content"].getCurrentIndex())
558
559         def previousFeedCB(self):
560                 self["content"].moveDown()
561                 current_entry = self["content"].getCurrentEntry()
562                 return (current_entry[0], current_entry[3], self["content"].getCurrentIndex())
563
564         def showCurrentEntry(self):
565                 current_entry = self["content"].getCurrentEntry()
566                 if current_entry is None: # empty list
567                         return
568
569                 self.session.openWithCallback(
570                         self.refresh,
571                         RSSFeedView,
572                         current_entry[3],
573                         feedTitle=current_entry[0],
574                         nextFeedCB=self.nextFeedCB,
575                         previousFeedCB=self.previousFeedCB,
576                         rssPoller=self.rssPoller,
577                         id=self["content"].getCurrentIndex()
578                 )
579
580         def selectEnclosure(self):
581                 current_entry = self["content"].getCurrentEntry()
582                 if current_entry is None: # empty list
583                         return
584
585                 # Build a list of all enclosures in this feed
586                 enclosures = []
587                 for entry in current_entry[3]:
588                                 enclosures.extend(entry[3])
589                 RSSBaseView.selectEnclosure(self, enclosures)