allow using a notification to inform the user about new items on a feed,
[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
6 from Components.ActionMap import ActionMap
7 from Components.Label import Label
8 from Components.ScrollLabel import ScrollLabel
9
10 from RSSList import RSSFeedList, RSSEntryList
11
12 class RSSBaseView(Screen):
13         """Base Screen for all Screens used in SimpleRSS"""
14
15         def __init__(self, session, poller, parent=None):
16                 Screen.__init__(self, session)
17                 self.rssPoller = poller
18                 self.pollDialog = None
19                 self.parent = parent
20
21         def errorPolling(self, errmsg = ""):
22                 # An error occured while polling
23                 self.session.open(
24                         MessageBox,
25                         _("Error while parsing Feed, this usually means there is something wrong with it."),
26                         type = MessageBox.TYPE_ERROR,
27                         timeout = 3
28                 )
29
30                 # Don't show "we're updating"-dialog any longer
31                 if self.pollDialog:
32                         self.pollDialog.close()
33                         self.pollDialog = None
34
35         def singleUpdate(self, feedid, errback = None):
36                 # Don't do anything if we have no poller
37                 if self.rssPoller is None:
38                         return
39
40                 # Default errorback to self.errorPolling
41                 # If an empty errorback is wanted the Screen needs to provide it
42                 if errback is None:
43                         errback = self.errorPolling
44
45                 # Tell Poller to poll
46                 self.rssPoller.singlePoll(feedid, callback=True, errorback=errback)
47
48                 # Open Dialog and save locally
49                 self.pollDialog = self.session.open(
50                         MessageBox,
51                         _("Update is being done in Background.\nContents will automatically be updated when it's done."),
52                         type = MessageBox.TYPE_INFO,
53                         timeout = 5
54                 )
55
56         def selectEnclosure(self, enclosures):
57                 # Empty List
58                 if enclosures is None:
59                         return
60
61                 from Components.Scanner import openList
62
63                 if not openList(self.session, enclosures):
64                         self.session.open(
65                                 MessageBox,
66                                 _("Found no Enclosure we can display."),
67                                 type = MessageBox.TYPE_INFO, 
68                                 timeout = 5
69                         )
70
71 class RSSEntryView(RSSBaseView):
72         """Shows a RSS Item"""
73
74         skin = """
75                 <screen position="100,100" size="460,420" title="Simple RSS Reader" >
76                         <widget name="info" position="0,0" size="460, 20" halign="right" font="Regular; 18" />
77                         <widget name="content" position="0,20" size="460,400" font="Regular; 22" />
78                 </screen>"""
79
80         def __init__(self, session, data, feedTitle="", cur_idx=None, entries=None, parent=None):
81                 RSSBaseView.__init__(self, session, None, parent)
82
83                 self.data = data
84                 self.feedTitle = feedTitle
85                 self.cur_idx = cur_idx
86                 self.entries = entries
87
88                 if cur_idx is not None and entries is not None:
89                         self["info"] = Label(_("Entry %s/%s") % (cur_idx+1, entries))
90                 else:
91                         self["info"] = Label()
92
93                 if data is not None:
94                         self["content"] = ScrollLabel("\n\n".join([data[0], data[2], " ".join([str(len(data[3])), "Enclosures"])]))
95                 else:
96                         self["content"] = ScrollLabel()
97
98                 self["actions"] = ActionMap([ "OkCancelActions", "ChannelSelectBaseActions", "ColorActions", "DirectionActions" ],
99                 {
100                         "cancel": self.close,
101                         "ok": self.selectEnclosure,
102                         "yellow": self.selectEnclosure,
103                         "up": self.up,
104                         "down": self.down,
105                         "right": self.next,
106                         "left": self.previous,
107                         "nextBouquet": self.nextFeed,
108                         "prevBouquet": self.previousFeed,
109                 })
110
111                 self.onLayoutFinish.append(self.setConditionalTitle)
112
113         def setConditionalTitle(self):
114                 self.setTitle(': '.join(["Simple RSS Reader", self.feedTitle]))
115
116         def up(self):
117                 self["content"].pageUp()
118
119         def down(self):
120                 self["content"].pageDown()
121
122         def next(self):
123                 if self.parent is not None:
124                         (self.data, self.cur_idx, self.entries) = self.parent.nextEntry()
125                         self.setContent()
126
127         def previous(self):
128                 if self.parent is not None:
129                         (self.data, self.cur_idx, self.entries) = self.parent.previousEntry()
130                         self.setContent()
131
132         def nextFeed(self):
133                 # Show next Feed
134                 if self.parent is not None:
135                         result = self.parent.next()
136                         self.feedTitle = result[0]
137                         self.entries = len(result[1])
138                         if self.entries:
139                                 self.cur_idx = 0
140                                 self.data = result[1][0]
141                         else:
142                                 self.cur_idx = None
143                                 self.data = None
144                         self.setConditionalTitle()
145                         self.setContent()
146
147         def previousFeed(self):
148                 # Show previous Feed
149                 if self.parent is not None:
150                         result = self.parent.previous()
151                         self.feedTitle = result[0]
152                         self.entries = len(result[1])
153                         if self.entries:
154                                 self.cur_idx = 0
155                                 self.data = result[1][0]
156                         else:
157                                 self.cur_idx = None
158                                 self.data = None
159                         self.setConditionalTitle()
160                         self.setContent()
161
162         def setContent(self):
163                 if self.cur_idx is not None and self.entries is not None:
164                         self["info"].setText(_("Entry %s/%s") % (self.cur_idx+1, self.entries))
165                 else:
166                         self["info"].setText("")
167                 if self.data is not None:
168                         self["content"].setText("\n\n".join([self.data[0], self.data[2], " ".join([str(len(self.data[3])), _("Enclosures")])]))
169                 else:
170                         self["content"].setText(_("No such Item."))
171
172         def selectEnclosure(self):
173                 if self.data is not None:
174                         RSSBaseView.selectEnclosure(self, self.data[3])
175
176 class RSSFeedView(RSSBaseView):
177         """Shows a RSS-Feed"""
178
179         skin = """
180                 <screen position="100,100" size="460,415" title="Simple RSS Reader" >
181                         <widget name="info" position="0,0" size="460,20" halign="right" font="Regular; 18" />
182                         <widget name="content" position="0,20" size="460,300" scrollbarMode="showOnDemand" />
183                         <widget name="summary" position="0,320" size="460,95" font="Regular;16" />
184                 </screen>"""
185
186         def __init__(self, session, feed=None, newItems=False, parent=None, rssPoller=None,id=None):
187                 RSSBaseView.__init__(self, session, rssPoller, parent)
188
189                 self.feed = feed
190                 self.newItems = newItems
191                 self.id = id
192
193                 self["content"] = RSSEntryList(self.feed.history)
194                 self["summary"] = Label()
195                 self["info"] = Label()
196
197                 if not newItems:
198                         self["actions"] = ActionMap([ "OkCancelActions", "ChannelSelectBaseActions", "MenuActions", "ColorActions" ], 
199                         {
200                                 "ok": self.showCurrentEntry,
201                                 "cancel": self.close,
202                                 "nextBouquet": self.next,
203                                 "prevBouquet": self.previous,
204                                 "menu": self.menu,
205                                 "yellow": self.selectEnclosure,
206                         })
207                         self.onLayoutFinish.append(self.__show)
208                         self.onClose.append(self.__close)
209
210                         self.timer = None
211                 else:
212                         self["actions"] = ActionMap([ "OkCancelActions" ], 
213                         {
214                                 "cancel": self.close,
215                         })
216
217                         self.timer = eTimer()
218                         self.timer.callback.append(self.timerTick)
219                         self.onExecBegin.append(self.startTimer)
220
221                 self["content"].connectSelChanged(self.updateInfo)
222                 self.onLayoutFinish.extend([self.updateInfo, self.setConditionalTitle])
223
224         def startTimer(self):
225                 self.timer.startLongTimer(5)
226
227         def timerTick(self):
228                 self.timer.callback.remove(self.timerTick)
229
230                 self.close()
231
232         def __show(self):
233                 self.rssPoller.addCallback(self.pollCallback)
234
235         def __close(self):
236                 if self.timer is not None:
237                         self.timer.callback.remove(self.timerTick)
238                         self.timer = None
239                 self.rssPoller.removeCallback(self.pollCallback)
240
241         def pollCallback(self, id = None):
242                 print "[SimpleRSS] SimpleRSSFeed called back"
243
244                 if id is None or id+1 == self.id:
245                         # TODO: do we really need this?
246                         current_entry = self["content"].getCurrent()
247                         self["content"].moveToEntry(current_entry)
248
249                         self["content"].invalidate()
250                         self.setConditionalTitle()
251                         self.updateInfo()
252
253         def setConditionalTitle(self):
254                 self.setTitle(': '.join(["Simple RSS Reader", self.feed.title]))
255
256         def updateInfo(self):
257                 current_entry = self["content"].getCurrent()
258                 if current_entry:
259                         self["summary"].setText(current_entry[2])
260
261                         cur_idx = self["content"].getSelectedIndex()
262                         self["info"].setText(_("Entry %s/%s") % (cur_idx+1, len(self.feed.history)))
263                 else:
264                         self["summary"].setText(_("Feed is empty."))
265                         self["info"].setText("")
266
267         def menu(self):
268                 if self.id > 0:
269                         self.singleUpdate(self.id-1)
270
271         def nextEntry(self):
272                 self["content"].down()
273                 return (self["content"].getCurrent(), self["content"].getSelectedIndex(), len(self.feed.history))
274
275         def previousEntry(self):
276                 self["content"].up()
277                 return (self["content"].getCurrent(), self["content"].getSelectedIndex(), len(self.feed.history))
278
279         # TODO: Fix moving back to previously marked entry (same goes for self.previous)
280         def next(self):
281                 # Show next Feed
282                 if self.parent is not None:
283                         (self.feed, self.id) = self.parent.nextFeed()
284                         #current_entry = self["content"].getCurrent()
285                         self["content"].l.setList(self.feed.history) # Update list
286                         self["content"].moveToIndex(0)
287                         #self["content"].moveToEntry(current_entry)
288                         self.updateInfo() # In case entry is no longer in history
289                         self.setConditionalTitle() # Update title
290                         return (self.feed.title, self.feed.history, self.id)
291                 return (self.feed.title, self.feed.history, self.id)
292
293         def previous(self):
294                 # Show previous Feed
295                 if self.parent is not None:
296                         (self.feed, self.id) = self.parent.previousFeed()
297                         #current_entry = self["content"].getCurrent()
298                         self["content"].l.setList(self.feed.history) # Update list
299                         self["content"].moveToIndex(0)
300                         #self["content"].moveToEntry(current_entry)
301                         self.updateInfo() # In case entry is no longer in history
302                         self.setConditionalTitle() # Update title
303                         return (self.feed.title, self.feed.history, self.id)
304                 return (self.feed.title, self.feed.history, self.id)
305
306         def checkEmpty(self):
307                 if self.id > 0 and not len(self.feed.history):
308                         self.singleUpdate(self.id-1)
309
310         def showCurrentEntry(self):
311                 current_entry = self["content"].getCurrent()
312                 if current_entry is None: # empty list
313                         return
314
315                 self.session.openWithCallback(
316                         self.updateInfo,
317                         RSSEntryView,
318                         current_entry,
319                         cur_idx=self["content"].getSelectedIndex(),
320                         entries=len(self.feed.history),
321                         feedTitle=self.feed.title,
322                         parent=self
323                 )
324
325         def selectEnclosure(self):
326                 current_entry = self["content"].getCurrent()
327                 if current_entry is None: # empty list
328                         return
329
330                 RSSBaseView.selectEnclosure(self, current_entry[3])
331
332 class RSSOverview(RSSBaseView):
333         """Shows an Overview over all RSS-Feeds known to rssPoller"""
334
335         skin = """
336                 <screen position="100,100" size="460,415" title="Simple RSS Reader" >
337                         <widget name="info" position="0,0" size="460,20" halign="right" font="Regular; 18" />
338                         <widget name="content" position="0,20" size="460,300" scrollbarMode="showOnDemand" />
339                         <widget name="summary" position="0,320" size="460,95" font="Regular;16" />
340                 </screen>"""
341
342         def __init__(self, session, poller):
343                 RSSBaseView.__init__(self, session, poller)
344
345                 self["actions"] = ActionMap([ "OkCancelActions", "MenuActions", "ColorActions" ], 
346                 {
347                         "ok": self.showCurrentEntry,
348                         "cancel": self.close,
349                         "menu": self.menu,
350                         "yellow": self.selectEnclosure,
351                 })
352
353                 self.fillFeeds()
354
355                 # We always have at least "New Items"-Feed
356                 self["content"] = RSSFeedList(self.feeds)
357                 self["summary"] = Label(' '.join([str(len(self.feeds[0][0].history)), _("Entries")]))
358                 self["info"] = Label(_("Feed %s/%s") % (1, len(self.feeds)))
359
360                 self["content"].connectSelChanged(self.updateInfo)
361                 self.onLayoutFinish.append(self.__show)
362                 self.onClose.append(self.__close)
363
364         def __show(self):
365                 self.rssPoller.addCallback(self.pollCallback)
366
367         def __close(self):
368                 self.rssPoller.removeCallback(self.pollCallback)
369
370         def fillFeeds(self):
371                 # Feedlist contains our virtual Feed and all real ones
372                 self.feeds = [(self.rssPoller.newItemFeed,)]
373                 self.feeds.extend([(feed,) for feed in self.rssPoller.feeds])
374
375         def pollCallback(self, id = None):
376                 print "[SimpleRSS] SimpleRSS called back"
377                 self.updateInfo()
378                 self["content"].invalidate()
379
380         def updateInfo(self):
381                 current_entry = self["content"].getCurrent()
382                 self["summary"].setText(' '.join([str(len(current_entry.history)), _("Entries")]))
383                 self["info"].setText(_("Feed %s/%s") % (self["content"].getSelectedIndex()+1, len(self.feeds)))
384
385         def menu(self):
386                 from Screens.ChoiceBox import ChoiceBox
387
388                 cur_idx = self["content"].getSelectedIndex()
389                 if cur_idx > 0:
390                         possible_actions = [
391                                 (_("Update Feed"), "update"),
392                                 (_("Setup"), "setup"),
393                                 (_("Close"), "close")
394                         ]
395                 else:
396                         possible_actions = [
397                                 (_("Setup"), "setup"),
398                                 (_("Close"), "close")
399                         ]
400
401                 self.session.openWithCallback(
402                         self.menuChoice,
403                         ChoiceBox,
404                         _("What to do?"),
405                         possible_actions
406                 )
407
408         def menuChoice(self, result):
409                 if result:
410                         if result[1] == "update":
411                                 cur_idx = self["content"].getSelectedIndex()
412                                 if cur_idx > 0:
413                                         self.singleUpdate(cur_idx-1)
414                         elif result[1] == "setup":
415                                 from RSSSetup import RSSSetup
416
417                                 self.session.openWithCallback(
418                                         self.refresh,
419                                         RSSSetup,
420                                         rssPoller=self.rssPoller
421                                 )
422                         elif result[1] == "close":
423                                 self.close()
424
425         def refresh(self):
426                 current_entry = self["content"].getCurrent()
427
428                 self.fillFeeds()
429                 self["content"].l.setList(self.feeds)
430
431                 self["content"].moveToEntry(current_entry)
432                 self.updateInfo()
433
434         def nextFeed(self):
435                 self["content"].up()
436                 return (self["content"].getCurrent(), self["content"].getSelectedIndex())
437
438         def previousFeed(self):
439                 self["content"].down()
440                 return (self["content"].getCurrent(), self["content"].getSelectedIndex())
441
442         def showCurrentEntry(self):
443                 current_entry = self["content"].getCurrent()
444                 self.session.openWithCallback(
445                         self.updateInfo,
446                         RSSFeedView,
447                         feed=current_entry,
448                         parent=self,
449                         rssPoller=self.rssPoller,
450                         id=self["content"].getSelectedIndex()
451                 )
452
453         def selectEnclosure(self):
454                 # Build a list of all enclosures in this feed
455                 enclosures = []
456                 for entry in self["content"].getCurrent().history:
457                                 enclosures.extend(entry[3])
458                 RSSBaseView.selectEnclosure(self, enclosures)