Fixed bug in unread items
[feedingit] / src / FeedingIt.py
1 #!/usr/bin/env python2.5
2
3
4 # Copyright (c) 2007-2008 INdT.
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Lesser General Public License as published by
7 # the Free Software Foundation, either version 3 of the License, or
8 # (at your option) any later version.
9 #
10 #  This program is distributed in the hope that it will be useful,
11 #  but WITHOUT ANY WARRANTY; without even the implied warranty of
12 #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 #  GNU Lesser General Public License for more details.
14 #
15 #  You should have received a copy of the GNU Lesser General Public License
16 #  along with this program.  If not, see <http://www.gnu.org/licenses/>.
17 #
18
19 # ============================================================================
20 # Name        : FeedingIt.py
21 # Author      : Yves Marcoz
22 # Version     : 0.1.1
23 # Description : PyGtk Example 
24 # ============================================================================
25
26 import gtk
27 import feedparser
28 import pango
29 import hildon
30 import gtkhtml2
31 import time
32 import webbrowser
33 import pickle
34 from os.path import isfile, isdir
35 from os import mkdir
36 import sys   
37 import urllib2
38 import gobject
39 from portrait import FremantleRotation
40
41 from rss import *
42    
43 class AddWidgetWizard(hildon.WizardDialog):
44     
45     def __init__(self, parent):
46         # Create a Notebook
47         self.notebook = gtk.Notebook()
48
49         self.nameEntry = hildon.Entry(gtk.HILDON_SIZE_AUTO)
50         self.nameEntry.set_placeholder("Enter Feed Name")
51         
52         self.urlEntry = hildon.Entry(gtk.HILDON_SIZE_AUTO)
53         
54         self.urlEntry.set_placeholder("Enter a URL")
55             
56         labelEnd = gtk.Label("Success")
57         
58         self.notebook.append_page(self.nameEntry, None)
59         self.notebook.append_page(self.urlEntry, None) 
60         self.notebook.append_page(labelEnd, None)      
61
62         hildon.WizardDialog.__init__(self, parent, "Add Feed", self.notebook)
63    
64         # Set a handler for "switch-page" signal
65         #self.notebook.connect("switch_page", self.on_page_switch, self)
66    
67         # Set a function to decide if user can go to next page
68         self.set_forward_page_func(self.some_page_func)
69    
70         self.show_all()
71         
72     def getData(self):
73         return (self.nameEntry.get_text(), self.urlEntry.get_text())
74         
75     def on_page_switch(self, notebook, page, num, dialog):
76         return True
77    
78     def some_page_func(self, nb, current, userdata):
79         # Validate data for 1st page
80         if current == 0:
81             entry = nb.get_nth_page(current)
82             # Check the name is not null
83             return len(entry.get_text()) != 0
84         elif current == 1:
85             entry = nb.get_nth_page(current)
86             # Check the url is not null, and starts with http
87             return ( (len(entry.get_text()) != 0) and (entry.get_text().lower().startswith("http")) )
88         elif current != 2:
89             return False
90         else:
91             return True
92
93 class DisplayArticle(hildon.StackableWindow):
94     def __init__(self, title, text, index):
95         hildon.StackableWindow.__init__(self)
96         self.index = index
97         self.text = text
98         self.set_title(title)
99
100         # Init the article display    
101         self.view = gtkhtml2.View()
102         self.pannable_article = hildon.PannableArea()
103         self.pannable_article.add(self.view)
104         self.pannable_article.set_property("mov-mode", hildon.MOVEMENT_MODE_BOTH)
105         self.document = gtkhtml2.Document()
106         self.view.set_document(self.document)
107         
108         self.document.clear()
109         self.document.open_stream("text/html")
110         self.document.write_stream(self.text)
111         self.document.close_stream()
112         
113         menu = hildon.AppMenu()
114         # Create a button and add it to the menu
115         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
116         button.set_label("Display Images")
117         button.connect("clicked", self.reloadArticle)
118         menu.append(button)
119         self.set_app_menu(menu)
120         menu.show_all()
121         
122         self.add(self.pannable_article)
123         
124         self.show_all()
125         
126         self.document.connect("link_clicked", self._signal_link_clicked)
127         self.document.connect("request-url", self._signal_request_url)
128         self.connect("destroy", self.destroyWindow)
129         
130     def destroyWindow(self, *args):
131         self.emit("article-closed", self.index)
132         self.destroy()
133         
134     def reloadArticle(self, widget):
135         self.document.open_stream("text/html")
136         self.document.write_stream(self.text)
137         self.document.close_stream()
138
139     def _signal_link_clicked(self, object, link):
140         webbrowser.open(link)
141
142     def _signal_request_url(self, object, url, stream):
143         f = urllib2.urlopen(url)
144         stream.write(f.read())
145         stream.close()
146
147
148 class DisplayFeed(hildon.StackableWindow):
149     def __init__(self, feed, title, key):
150         hildon.StackableWindow.__init__(self)
151         self.feed = feed
152         self.feedTitle = title
153         self.set_title(title)
154         self.key=key
155         
156         menu = hildon.AppMenu()
157         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
158         button.set_label("Update Feed")
159         button.connect("clicked", self.button_update_clicked)
160         menu.append(button)
161         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
162         button.set_label("Mark All As Read")
163         button.connect("clicked", self.buttonReadAllClicked)
164         menu.append(button)
165         self.set_app_menu(menu)
166         menu.show_all()
167         
168         self.displayFeed()
169         
170         self.connect("destroy", self.destroyWindow)
171         
172     def destroyWindow(self, *args):
173         self.feed.saveFeed()
174         self.emit("feed-closed", self.key)
175         self.destroy()
176
177     def displayFeed(self):
178         self.vboxFeed = gtk.VBox(False, 10)
179         self.pannableFeed = hildon.PannableArea()
180         self.pannableFeed.add_with_viewport(self.vboxFeed)
181         self.pannableFeed.set_property("mov-mode", hildon.MOVEMENT_MODE_BOTH)
182         self.buttons = []
183         for index in range(self.feed.getNumberOfEntries()):
184             button = gtk.Button(self.feed.getTitle(index))
185             button.set_alignment(0,0)
186             label = button.child
187             if self.feed.isEntryRead(index):
188                 label.modify_font(pango.FontDescription("sans 16"))
189             else:
190                 label.modify_font(pango.FontDescription("sans bold 16"))
191             label.set_line_wrap(True)
192             
193             label.set_size_request(self.get_size()[0]-50, -1)
194             button.connect("clicked", self.button_clicked, index)
195             self.buttons.append(button)
196             
197             self.vboxFeed.pack_start(button, expand=False)           
198             index=index+1
199
200         self.add(self.pannableFeed)
201         self.show_all()
202         
203     def clear(self):
204         self.remove(self.pannableFeed)
205         
206     def button_clicked(self, button, index):
207         disp = DisplayArticle(self.feedTitle, self.feed.getArticle(index), index)
208         disp.connect("article-closed", self.onArticleClosed)
209         
210     def onArticleClosed(self, object, index):
211         label = self.buttons[index].child
212         label.modify_font(pango.FontDescription("sans 16"))
213         self.buttons[index].show()
214
215     def button_update_clicked(self, button):
216         self.listing.getFeed(key).updateFeed()
217         self.clear()
218         self.displayFeed(key)
219         
220     def buttonReadAllClicked(self, button):
221         for index in range(self.feed.getNumberOfEntries()):
222             self.feed.setEntryRead(index)
223             label = self.buttons[index].child
224             label.modify_font(pango.FontDescription("sans 16"))
225             self.buttons[index].show()
226
227
228 class FeedingIt:
229     def __init__(self):
230         self.listing = Listing()
231         
232         # Init the windows
233         self.window = hildon.StackableWindow()
234         self.window.set_title("FeedingIt")
235         FremantleRotation("FeedingIt", main_window=self.window)
236         menu = hildon.AppMenu()
237         # Create a button and add it to the menu
238         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
239         button.set_label("Update All Feeds")
240         button.connect("clicked", self.button_update_clicked, "All")
241         menu.append(button)
242         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
243         button.set_label("Add Feed")
244         button.connect("clicked", self.button_add_clicked)
245         menu.append(button)
246         
247         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
248         button.set_label("Delete Feed")
249         button.connect("clicked", self.button_delete_clicked)
250         menu.append(button)
251         
252         self.window.set_app_menu(menu)
253         menu.show_all()
254         
255         self.feedWindow = hildon.StackableWindow()
256         self.articleWindow = hildon.StackableWindow()
257
258         self.displayListing() 
259         
260     def button_add_clicked(self, button):
261         wizard = AddWidgetWizard(self.window)
262         ret = wizard.run()
263         if ret == 2:
264             (title, url) = wizard.getData()
265             if (not title == '') and (not url == ''): 
266                self.listing.addFeed(title, url)
267         wizard.destroy()
268         self.displayListing()
269         
270     def button_update_clicked(self, button, key):
271         #hildon.hildon_gtk_window_set_progress_indicator(self.window, 1)
272         if key == "All":
273             self.listing.updateFeeds()
274         else:
275             self.listing.getFeed(key).updateFeed()
276             self.displayFeed(key)            
277         self.displayListing()
278         #hildon.hildon_gtk_window_set_progress_indicator(self.window, 0)
279
280     def button_delete_clicked(self, button):
281         self.pickerDialog = hildon.PickerDialog(self.window)
282         #HildonPickerDialog
283         self.pickerDialog.set_selector(self.create_selector())
284         self.pickerDialog.show_all()
285         
286     def create_selector(self):
287         selector = hildon.TouchSelector(text=True)
288         # Selection multiple
289         #selector.set_column_selection_mode(hildon.TOUCH_SELECTOR_SELECTION_MODE_MULTIPLE)
290         self.mapping = {}
291         selector.connect("changed", self.selection_changed)
292
293         for key in self.listing.getListOfFeeds():
294             title=self.listing.getFeedTitle(key)
295             selector.append_text(title)
296             self.mapping[title]=key
297
298         return selector
299
300     def selection_changed(self, widget, data):
301         current_selection = widget.get_current_text()
302         #print 'Current selection: %s' % current_selection
303         #print "To Delete: %s" % self.mapping[current_selection]
304         self.pickerDialog.destroy()
305         if self.show_confirmation_note(self.window, current_selection):
306             self.listing.removeFeed(self.mapping[current_selection])
307         del self.mapping
308         self.displayListing()
309
310     def show_confirmation_note(self, parent, title):
311         note = hildon.Note("confirmation", parent, "Are you sure you want to delete " + title +"?")
312
313         retcode = gtk.Dialog.run(note)
314         note.destroy()
315         
316         if retcode == gtk.RESPONSE_OK:
317             return True
318         else:
319             return False
320         
321     def displayListing(self):
322         try:
323             self.window.remove(self.pannableListing)
324         except:
325             pass
326         self.vboxListing = gtk.VBox(False,10)
327         self.pannableListing = hildon.PannableArea()
328         self.pannableListing.add_with_viewport(self.vboxListing)
329
330         self.buttons = {}
331         for key in self.listing.getListOfFeeds():
332             #button = gtk.Button(item)
333             button = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT,
334                               hildon.BUTTON_ARRANGEMENT_VERTICAL)
335             button.set_text(self.listing.getFeedTitle(key), self.listing.getFeedUpdateTime(key) + " / " 
336                             + str(self.listing.getFeedNumberOfUnreadItems(key)) + " Unread Items / " + str(self.listing.getFeed(key).getNumberOfEntries()))
337             button.set_alignment(0,0,1,1)
338             button.connect("clicked", self.buttonFeedClicked, self, self.window, key)
339             self.vboxListing.pack_start(button, expand=False)
340             self.buttons[key] = button
341         self.window.add(self.pannableListing)
342         self.window.show_all()
343     
344     def buttonFeedClicked(widget, button, self, window, key):
345         disp = DisplayFeed(self.listing.getFeed(key), self.listing.getFeedTitle(key), key)
346         disp.connect("feed-closed", self.onFeedClosed)
347         
348     def onFeedClosed(self, object, key):
349         self.buttons[key].set_text(self.listing.getFeedTitle(key), self.listing.getFeedUpdateTime(key) + " / " 
350                             + str(self.listing.getFeedNumberOfUnreadItems(key)) + " Unread Items")
351         self.buttons[key].show()
352      
353     def run(self):
354         self.window.connect("destroy", gtk.main_quit)
355         gtk.main()
356
357
358 if __name__ == "__main__":
359     gobject.signal_new("feed-closed", DisplayFeed, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,))
360     gobject.signal_new("article-closed", DisplayArticle, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,))
361     
362     if not isdir(CONFIGDIR):
363         try:
364             mkdir(CONFIGDIR)
365         except:
366             print "Error: Can't create configuration directory"
367             sys.exit(1)
368     app = FeedingIt()
369     app.run()