Added read/unread flags for articles
[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.document.connect("ReloadArticle", self.reloadArticle)
129         self.connect("destroy", self.destroyWindow)
130         
131     def destroyWindow(self, *args):
132         self.emit("article-closed", self.index)
133         self.destroy()
134         
135     def reloadArticle(self, widget):
136         self.document.open_stream("text/html")
137         self.document.write_stream(self.text)
138         self.document.close_stream()
139
140     def _signal_link_clicked(self, object, link):
141         webbrowser.open(link)
142
143     def _signal_request_url(self, object, url, stream):
144         f = urllib2.urlopen(url)
145         stream.write(f.read())
146         stream.close()
147
148
149 class DisplayFeed(hildon.StackableWindow):
150     def __init__(self, feed, title, key):
151         hildon.StackableWindow.__init__(self)
152         self.feed = feed
153         self.feedTitle = title
154         self.set_title(title)
155         self.key=key
156         
157         menu = hildon.AppMenu()
158         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
159         button.set_label("Update Feed")
160         button.connect("clicked", self.button_update_clicked)
161         menu.append(button)
162         self.set_app_menu(menu)
163         menu.show_all()
164         
165         self.displayFeed()
166         
167         self.connect("destroy", self.destroyWindow)
168         
169     def destroyWindow(self, *args):
170         for index in range(self.feed.getNumberOfEntries()):
171             self.feed.setEntryRead(index)
172         self.feed.saveFeed()
173         self.emit("feed-closed", self.key)
174         self.destroy()
175
176     def displayFeed(self):
177         self.vboxFeed = gtk.VBox(False, 10)
178         self.pannableFeed = hildon.PannableArea()
179         self.pannableFeed.add_with_viewport(self.vboxFeed)
180         self.pannableFeed.set_property("mov-mode", hildon.MOVEMENT_MODE_BOTH)
181         self.buttons = []
182         for index in range(self.feed.getNumberOfEntries()):
183             button = gtk.Button(self.feed.getTitle(index))
184             button.set_alignment(0,0)
185             label = button.child
186             if self.feed.isEntryRead(index):
187                 label.modify_font(pango.FontDescription("sans 18"))
188             else:
189                 label.modify_font(pango.FontDescription("sans bold 18"))
190             label.set_line_wrap(True)
191             
192             label.set_size_request(self.get_size()[0]-50, -1)
193             button.connect("clicked", self.button_clicked, index)
194             self.buttons.append(button)
195             
196             self.vboxFeed.pack_start(button, expand=False)           
197             index=index+1
198
199         self.add(self.pannableFeed)
200         self.show_all()
201         
202     def clear(self):
203         self.remove(self.pannableFeed)
204         
205     def button_clicked(self, button, index):
206         disp = DisplayArticle(self.feedTitle, self.feed.getArticle(index), index)
207         disp.connect("article-closed", self.onArticleClosed)
208         
209     def onArticleClosed(self, object, index):
210         label = self.buttons[index].child
211         label.modify_font(pango.FontDescription("sans 18"))
212         self.buttons[index].show()
213
214     def button_update_clicked(self, button):
215         self.listing.getFeed(key).updateFeed()
216         self.clear()
217         self.displayFeed(key)
218
219
220 class FeedingIt:
221     def __init__(self):
222         self.listing = Listing()
223         
224         # Init the windows
225         self.window = hildon.StackableWindow()
226         self.window.set_title("FeedingIt")
227         FremantleRotation("FeedingIt", main_window=self.window)
228         menu = hildon.AppMenu()
229         # Create a button and add it to the menu
230         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
231         button.set_label("Update All Feeds")
232         button.connect("clicked", self.button_update_clicked, "All")
233         menu.append(button)
234         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
235         button.set_label("Add Feed")
236         button.connect("clicked", self.button_add_clicked)
237         menu.append(button)
238         
239         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
240         button.set_label("Delete Feed")
241         button.connect("clicked", self.button_delete_clicked)
242         menu.append(button)
243         
244         self.window.set_app_menu(menu)
245         menu.show_all()
246         
247         self.feedWindow = hildon.StackableWindow()
248         self.articleWindow = hildon.StackableWindow()
249
250         self.displayListing() 
251         
252     def button_add_clicked(self, button):
253         wizard = AddWidgetWizard(self.window)
254         ret = wizard.run()
255         if ret == 2:
256             (title, url) = wizard.getData()
257             if (not title == '') and (not url == ''): 
258                self.listing.addFeed(title, url)
259         wizard.destroy()
260         self.displayListing()
261         
262     def button_update_clicked(self, button, key):
263         #hildon.hildon_gtk_window_set_progress_indicator(self.window, 1)
264         if key == "All":
265             self.listing.updateFeeds()
266         else:
267             self.listing.getFeed(key).updateFeed()
268             self.displayFeed(key)            
269         self.displayListing()
270         #hildon.hildon_gtk_window_set_progress_indicator(self.window, 0)
271
272     def button_delete_clicked(self, button):
273         self.pickerDialog = hildon.PickerDialog(self.window)
274         #HildonPickerDialog
275         self.pickerDialog.set_selector(self.create_selector())
276         self.pickerDialog.show_all()
277         
278     def create_selector(self):
279         selector = hildon.TouchSelector(text=True)
280         # Selection multiple
281         #selector.set_column_selection_mode(hildon.TOUCH_SELECTOR_SELECTION_MODE_MULTIPLE)
282         self.mapping = {}
283         selector.connect("changed", self.selection_changed)
284
285         for key in self.listing.getListOfFeeds():
286             title=self.listing.getFeedTitle(key)
287             selector.append_text(title)
288             self.mapping[title]=key
289
290         return selector
291
292     def selection_changed(self, widget, data):
293         current_selection = widget.get_current_text()
294         #print 'Current selection: %s' % current_selection
295         #print "To Delete: %s" % self.mapping[current_selection]
296         self.pickerDialog.destroy()
297         if self.show_confirmation_note(self.window, current_selection):
298             self.listing.removeFeed(self.mapping[current_selection])
299         del self.mapping
300         self.displayListing()
301
302     def show_confirmation_note(self, parent, title):
303         note = hildon.Note("confirmation", parent, "Are you sure you want to delete " + title +"?")
304
305         retcode = gtk.Dialog.run(note)
306         note.destroy()
307         
308         if retcode == gtk.RESPONSE_OK:
309             return True
310         else:
311             return False
312         
313     def displayListing(self):
314         try:
315             self.window.remove(self.pannableListing)
316         except:
317             pass
318         self.vboxListing = gtk.VBox(False,10)
319         self.pannableListing = hildon.PannableArea()
320         self.pannableListing.add_with_viewport(self.vboxListing)
321
322         self.buttons = {}
323         for key in self.listing.getListOfFeeds():
324             #button = gtk.Button(item)
325             button = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT,
326                               hildon.BUTTON_ARRANGEMENT_VERTICAL)
327             button.set_text(self.listing.getFeedTitle(key), self.listing.getFeedUpdateTime(key) + " / " 
328                             + str(self.listing.getFeedNumberOfUnreadItems(key)) + " Unread Items")
329             button.set_alignment(0,0,1,1)
330             button.connect("clicked", self.buttonFeedClicked, self, self.window, key)
331             self.vboxListing.pack_start(button, expand=False)
332             self.buttons[key] = button
333         self.window.add(self.pannableListing)
334         self.window.show_all()
335     
336     def buttonFeedClicked(widget, button, self, window, key):
337         disp = DisplayFeed(self.listing.getFeed(key), self.listing.getFeedTitle(key), key)
338         disp.connect("feed-closed", self.onFeedClosed)
339         
340     def onFeedClosed(self, object, key):
341         self.buttons[key].set_text(self.listing.getFeedTitle(key), self.listing.getFeedUpdateTime(key) + " / " 
342                             + str(self.listing.getFeedNumberOfUnreadItems(key)) + " Unread Items")
343         self.buttons[key].show()
344      
345     def run(self):
346         self.window.connect("destroy", gtk.main_quit)
347         gtk.main()
348
349
350 if __name__ == "__main__":
351     gobject.signal_new("feed-closed", DisplayFeed, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,))
352     gobject.signal_new("article-closed", DisplayArticle, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,))
353     
354     if not isdir(CONFIGDIR):
355         try:
356             mkdir(CONFIGDIR)
357         except:
358             print "Error: Can't create configuration directory"
359             sys.exit(1)
360     app = FeedingIt()
361     app.run()