0.5.1-0: added image caching for webkit Archived 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.5.0
23 # Description : Simple RSS Reader
24 # ============================================================================
25
26 import gtk
27 import feedparser
28 import pango
29 import hildon
30 #import gtkhtml2
31 try:
32     import webkit
33     has_webkit=True
34 except:
35     import gtkhtml2
36     has_webkit=False
37 import time
38 import dbus
39 import pickle
40 from os.path import isfile, isdir
41 from os import mkdir
42 import sys   
43 import urllib2
44 import gobject
45 from portrait import FremantleRotation
46 import threading
47 import thread
48 from feedingitdbus import ServerObject
49 from config import Config
50
51 from rss import *
52 from opml import GetOpmlData, ExportOpmlData
53    
54 import socket
55 timeout = 5
56 socket.setdefaulttimeout(timeout)
57
58 CONFIGDIR="/home/user/.feedingit/"
59
60 class AddWidgetWizard(hildon.WizardDialog):
61     
62     def __init__(self, parent, urlIn, titleIn=None):
63         # Create a Notebook
64         self.notebook = gtk.Notebook()
65
66         self.nameEntry = hildon.Entry(gtk.HILDON_SIZE_AUTO)
67         self.nameEntry.set_placeholder("Enter Feed Name")
68         vbox = gtk.VBox(False,10)
69         label = gtk.Label("Enter Feed Name:")
70         vbox.pack_start(label)
71         vbox.pack_start(self.nameEntry)
72         if not titleIn == None:
73             self.nameEntry.set_text(titleIn)
74         self.notebook.append_page(vbox, None)
75         
76         self.urlEntry = hildon.Entry(gtk.HILDON_SIZE_AUTO)
77         self.urlEntry.set_placeholder("Enter a URL")
78         self.urlEntry.set_text(urlIn)
79         self.urlEntry.select_region(0,-1)
80         
81         vbox = gtk.VBox(False,10)
82         label = gtk.Label("Enter Feed URL:")
83         vbox.pack_start(label)
84         vbox.pack_start(self.urlEntry)
85         self.notebook.append_page(vbox, None)
86
87         labelEnd = gtk.Label("Success")
88         
89         self.notebook.append_page(labelEnd, None)      
90
91         hildon.WizardDialog.__init__(self, parent, "Add Feed", self.notebook)
92    
93         # Set a handler for "switch-page" signal
94         #self.notebook.connect("switch_page", self.on_page_switch, self)
95    
96         # Set a function to decide if user can go to next page
97         self.set_forward_page_func(self.some_page_func)
98    
99         self.show_all()
100         
101     def getData(self):
102         return (self.nameEntry.get_text(), self.urlEntry.get_text())
103         
104     def on_page_switch(self, notebook, page, num, dialog):
105         return True
106    
107     def some_page_func(self, nb, current, userdata):
108         # Validate data for 1st page
109         if current == 0:
110             return len(self.nameEntry.get_text()) != 0
111         elif current == 1:
112             # Check the url is not null, and starts with http
113             return ( (len(self.urlEntry.get_text()) != 0) and (self.urlEntry.get_text().lower().startswith("http")) )
114         elif current != 2:
115             return False
116         else:
117             return True
118
119 class GetImage(threading.Thread):
120     def __init__(self, url, stream):
121         threading.Thread.__init__(self)
122         self.url = url
123         self.stream = stream
124     
125     def run(self):
126         f = urllib2.urlopen(self.url)
127         data = f.read()
128         f.close()
129         self.stream.write(data)
130         self.stream.close()
131
132 class ImageDownloader():
133     def __init__(self):
134         self.images = []
135         self.downloading = False
136         
137     def queueImage(self, url, stream):
138         self.images.append((url, stream))
139         if not self.downloading:
140             self.downloading = True
141             gobject.timeout_add(50, self.checkQueue)
142         
143     def checkQueue(self):
144         for i in range(4-threading.activeCount()):
145             if len(self.images) > 0:
146                 (url, stream) = self.images.pop() 
147                 GetImage(url, stream).start()
148         if len(self.images)>0:
149             gobject.timeout_add(200, self.checkQueue)
150         else:
151             self.downloading=False
152             
153     def stopAll(self):
154         self.images = []
155         
156         
157 class Download(threading.Thread):
158     def __init__(self, listing, key, config):
159         threading.Thread.__init__(self)
160         self.listing = listing
161         self.key = key
162         self.config = config
163         
164     def run (self):
165         self.listing.updateFeed(self.key, self.config.getExpiry())
166
167         
168 class DownloadBar(gtk.ProgressBar):
169     def __init__(self, parent, listing, listOfKeys, config, single=False):
170         gtk.ProgressBar.__init__(self)
171         self.listOfKeys = listOfKeys[:]
172         self.listing = listing
173         self.total = len(self.listOfKeys)
174         self.config = config
175         self.current = 0
176         self.single = single
177         
178         if self.total>0:
179             #self.progress = gtk.ProgressBar()
180             #self.waitingWindow = hildon.Note("cancel", parent, "Downloading",
181             #                     progressbar=self.progress)
182             self.set_text("Updating...")
183             self.fraction = 0
184             self.set_fraction(self.fraction)
185             self.show_all()
186             # Create a timeout
187             self.timeout_handler_id = gobject.timeout_add(50, self.update_progress_bar)
188             #self.waitingWindow.show_all()
189             #response = self.waitingWindow.run()
190             #self.listOfKeys = []
191             #while threading.activeCount() > 1:
192                 # Wait for current downloads to finish
193             #    time.sleep(0.1)
194             #self.waitingWindow.destroy()
195
196     def update_progress_bar(self):
197         #self.progress_bar.pulse()
198         if threading.activeCount() < 4:
199             x = threading.activeCount() - 1
200             k = len(self.listOfKeys)
201             fin = self.total - k - x
202             fraction = float(fin)/float(self.total) + float(x)/(self.total*2.)
203             #print x, k, fin, fraction
204             self.set_fraction(fraction)
205
206             if len(self.listOfKeys)>0:
207                 self.current = self.current+1
208                 key = self.listOfKeys.pop()
209                 if (not self.listing.getCurrentlyDisplayedFeed() == key) or (self.single == True):
210                     # Check if the feed is being displayed
211                     download = Download(self.listing, key, self.config)
212                     download.start()
213                 return True
214             elif threading.activeCount() > 1:
215                 return True
216             else:
217                 #self.waitingWindow.destroy()
218                 #self.destroy()
219                 self.emit("download-done", "success")
220                 return False 
221         return True
222     
223     
224 class SortList(gtk.Dialog):
225     def __init__(self, parent, listing):
226         gtk.Dialog.__init__(self, "Organizer",  parent)
227         self.listing = listing
228         
229         self.vbox2 = gtk.VBox(False, 10)
230         
231         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
232         button.set_label("Move Up")
233         button.connect("clicked", self.buttonUp)
234         self.vbox2.pack_start(button, expand=False, fill=False)
235         
236         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
237         button.set_label("Move Down")
238         button.connect("clicked", self.buttonDown)
239         self.vbox2.pack_start(button, expand=False, fill=False)
240
241         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
242         button.set_label("Add Feed")
243         button.connect("clicked", self.buttonAdd)
244         self.vbox2.pack_start(button, expand=False, fill=False)
245
246         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
247         button.set_label("Edit Feed")
248         button.connect("clicked", self.buttonEdit)
249         self.vbox2.pack_start(button, expand=False, fill=False)
250         
251         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
252         button.set_label("Delete")
253         button.connect("clicked", self.buttonDelete)
254         self.vbox2.pack_start(button, expand=False, fill=False)
255         
256         #button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
257         #button.set_label("Done")
258         #button.connect("clicked", self.buttonDone)
259         #self.vbox.pack_start(button)
260         self.hbox2= gtk.HBox(False, 10)
261         self.pannableArea = hildon.PannableArea()
262         self.treestore = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING)
263         self.treeview = gtk.TreeView(self.treestore)
264         self.hbox2.pack_start(self.pannableArea, expand=True)
265         self.displayFeeds()
266         self.hbox2.pack_end(self.vbox2, expand=False)
267         self.set_default_size(-1, 600)
268         self.vbox.pack_start(self.hbox2)
269         
270         self.show_all()
271         #self.connect("destroy", self.buttonDone)
272         
273     def displayFeeds(self):
274         self.treeview.destroy()
275         self.treestore = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING)
276         self.treeview = gtk.TreeView()
277         
278         self.treeview.get_selection().set_mode(gtk.SELECTION_SINGLE)
279         hildon.hildon_gtk_tree_view_set_ui_mode(self.treeview, gtk.HILDON_UI_MODE_EDIT)
280         self.refreshList()
281         self.treeview.append_column(gtk.TreeViewColumn('Feed Name', gtk.CellRendererText(), text = 0))
282
283         self.pannableArea.add(self.treeview)
284
285         #self.show_all()
286
287     def refreshList(self, selected=None, offset=0):
288         rect = self.treeview.get_visible_rect()
289         y = rect.y+rect.height
290         self.treestore = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING)
291         for key in self.listing.getListOfFeeds():
292             item = self.treestore.append([self.listing.getFeedTitle(key), key])
293             if key == selected:
294                 selectedItem = item
295         self.treeview.set_model(self.treestore)
296         if not selected == None:
297             self.treeview.get_selection().select_iter(selectedItem)
298             self.treeview.scroll_to_cell(self.treeview.get_model().get_path(selectedItem))
299         self.pannableArea.show_all()
300
301     def getSelectedItem(self):
302         (model, iter) = self.treeview.get_selection().get_selected()
303         if not iter:
304             return None
305         return model.get_value(iter, 1)
306
307     def findIndex(self, key):
308         after = None
309         before = None
310         found = False
311         for row in self.treestore:
312             if found:
313                 return (before, row.iter)
314             if key == list(row)[0]:
315                 found = True
316             else:
317                 before = row.iter
318         return (before, None)
319
320     def buttonUp(self, button):
321         key  = self.getSelectedItem()
322         if not key == None:
323             self.listing.moveUp(key)
324             self.refreshList(key, -10)
325
326     def buttonDown(self, button):
327         key = self.getSelectedItem()
328         if not key == None:
329             self.listing.moveDown(key)
330             self.refreshList(key, 10)
331
332     def buttonDelete(self, button):
333         key = self.getSelectedItem()
334         if not key == None:
335             self.listing.removeFeed(key)
336         self.refreshList()
337
338     def buttonEdit(self, button):
339         key = self.getSelectedItem()
340         if not key == None:
341             wizard = AddWidgetWizard(self, self.listing.getFeedUrl(key), self.listing.getFeedTitle(key))
342             ret = wizard.run()
343             if ret == 2:
344                 (title, url) = wizard.getData()
345                 if (not title == '') and (not url == ''):
346                     self.listing.editFeed(key, title, url)
347             wizard.destroy()
348         self.refreshList()
349
350     def buttonDone(self, *args):
351         self.destroy()
352         
353     def buttonAdd(self, button, urlIn="http://"):
354         wizard = AddWidgetWizard(self, urlIn)
355         ret = wizard.run()
356         if ret == 2:
357             (title, url) = wizard.getData()
358             if (not title == '') and (not url == ''): 
359                self.listing.addFeed(title, url)
360         wizard.destroy()
361         self.refreshList()
362                
363
364 class DisplayArticle(hildon.StackableWindow):
365     def __init__(self, title, text, link, index, key, listing, config):
366         hildon.StackableWindow.__init__(self)
367         self.imageDownloader = ImageDownloader()
368         self.listing=listing
369         self.key = key
370         self.index = index
371         self.text = text
372         self.link = link
373         self.set_title(title)
374         self.config = config
375         self.images = []
376         
377         # Init the article display
378         if self.config.getWebkitSupport():
379             self.view = webkit.WebView()
380             #self.view.set_editable(False)
381         else:
382             import gtkhtml2
383             self.view = gtkhtml2.View()
384             self.document = gtkhtml2.Document()
385             self.view.set_document(self.document)
386             self.document.connect("link_clicked", self._signal_link_clicked)
387         self.pannable_article = hildon.PannableArea()
388         self.pannable_article.add(self.view)
389         #self.pannable_article.set_property("mov-mode", hildon.MOVEMENT_MODE_BOTH)
390         #self.gestureId = self.pannable_article.connect('horizontal-movement', self.gesture)
391
392         if self.config.getWebkitSupport():
393             if key=="ArchivedArticles":
394                 self.view.open("file://" + self.link)
395             else:
396                 self.view.load_html_string(self.text, self.link) # "text/html", "utf-8", self.link)
397             self.view.set_zoom_level(float(config.getArtFontSize())/10.)
398         else:
399             if not key == "ArchivedArticles":
400                 # Do not download images if the feed is "Archived Articles"
401                 self.document.connect("request-url", self._signal_request_url)
402             
403             self.document.clear()
404             self.document.open_stream("text/html")
405             self.document.write_stream(self.text)
406             self.document.close_stream()
407         
408         menu = hildon.AppMenu()
409         # Create a button and add it to the menu
410         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
411         button.set_label("Allow Horizontal Scrolling")
412         button.connect("clicked", self.horiz_scrolling_button)
413         menu.append(button)
414         
415         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
416         button.set_label("Open in Browser")
417         button.connect("clicked", self._signal_link_clicked, self.link)
418         menu.append(button)
419         
420         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
421         button.set_label("Add to Archived Articles")
422         button.connect("clicked", self.archive_button)
423         menu.append(button)
424         
425         self.set_app_menu(menu)
426         menu.show_all()
427         
428         #self.event_box = gtk.EventBox()
429         #self.event_box.add(self.pannable_article)
430         self.add(self.pannable_article)
431         
432         
433         self.pannable_article.show_all()
434
435         self.destroyId = self.connect("destroy", self.destroyWindow)
436         
437         self.view.connect("button_press_event", self.button_pressed)
438         self.view.connect("button_release_event", self.button_released)
439         #self.timeout_handler_id = gobject.timeout_add(300, self.reloadArticle)
440
441     def button_pressed(self, window, event):
442         #print event.x, event.y
443         self.coords = (event.x, event.y)
444         
445     def button_released(self, window, event):
446         x = self.coords[0] - event.x
447         y = self.coords[1] - event.y
448         
449         if (abs(y) < 20):
450             if (x > 30):
451                 self.emit("article-previous", self.index)
452             elif (x<-30):
453                 self.emit("article-next", self.index)   
454         #print x, y
455         #print "Released"
456
457     #def gesture(self, widget, direction, startx, starty):
458     #    if (direction == 3):
459     #        self.emit("article-next", self.index)
460     #    if (direction == 2):
461     #        self.emit("article-previous", self.index)
462         #print startx, starty
463         #self.timeout_handler_id = gobject.timeout_add(200, self.destroyWindow)
464
465     def destroyWindow(self, *args):
466         self.disconnect(self.destroyId)
467         self.emit("article-closed", self.index)
468         self.imageDownloader.stopAll()
469         self.destroy()
470         
471     def horiz_scrolling_button(self, *widget):
472         self.pannable_article.disconnect(self.gestureId)
473         self.pannable_article.set_property("mov-mode", hildon.MOVEMENT_MODE_BOTH)
474         
475     def archive_button(self, *widget):
476         # Call the listing.addArchivedArticle
477         self.listing.addArchivedArticle(self.key, self.index)
478         
479     #def reloadArticle(self, *widget):
480     #    if threading.activeCount() > 1:
481             # Image thread are still running, come back in a bit
482     #        return True
483     #    else:
484     #        for (stream, imageThread) in self.images:
485     #            imageThread.join()
486     #            stream.write(imageThread.data)
487     #            stream.close()
488     #        return False
489     #    self.show_all()
490
491     def _signal_link_clicked(self, object, link):
492         bus = dbus.SystemBus()
493         proxy = bus.get_object("com.nokia.osso_browser", "/com/nokia/osso_browser/request")
494         iface = dbus.Interface(proxy, 'com.nokia.osso_browser')
495         iface.load_url(link)
496
497     def _signal_request_url(self, object, url, stream):
498         #print url
499         self.imageDownloader.queueImage(url, stream)
500         #imageThread = GetImage(url)
501         #imageThread.start()
502         #self.images.append((stream, imageThread))
503
504
505 class DisplayFeed(hildon.StackableWindow):
506     def __init__(self, listing, feed, title, key, config):
507         hildon.StackableWindow.__init__(self)
508         self.listing = listing
509         self.feed = feed
510         self.feedTitle = title
511         self.set_title(title)
512         self.key=key
513         self.config = config
514         
515         self.downloadDialog = False
516         
517         self.listing.setCurrentlyDisplayedFeed(self.key)
518         
519         self.disp = False
520         
521         menu = hildon.AppMenu()
522         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
523         button.set_label("Update Feed")
524         button.connect("clicked", self.button_update_clicked)
525         menu.append(button)
526         
527         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
528         button.set_label("Mark All As Read")
529         button.connect("clicked", self.buttonReadAllClicked)
530         menu.append(button)
531         self.set_app_menu(menu)
532         menu.show_all()
533         
534         self.displayFeed()
535         
536         self.connect("destroy", self.destroyWindow)
537         
538     def destroyWindow(self, *args):
539         self.feed.saveUnread(CONFIGDIR)
540         self.listing.updateUnread(self.key, self.feed.getNumberOfUnreadItems())
541         self.emit("feed-closed", self.key)
542         self.destroy()
543         #gobject.idle_add(self.feed.saveFeed, CONFIGDIR)
544         self.listing.closeCurrentlyDisplayedFeed()
545
546     def displayFeed(self):
547         self.vboxFeed = gtk.VBox(False, 10)
548         self.pannableFeed = hildon.PannableArea()
549         self.pannableFeed.add_with_viewport(self.vboxFeed)
550         self.pannableFeed.set_property("mov-mode", hildon.MOVEMENT_MODE_BOTH)
551         self.buttons = {}
552         for id in self.feed.getIds():
553             button = gtk.Button(self.feed.getTitle(id))
554             button.set_alignment(0,0)
555             label = button.child
556             if self.feed.isEntryRead(id):
557                 #label.modify_font(pango.FontDescription("sans 16"))
558                 label.modify_font(pango.FontDescription(self.config.getReadFont()))
559             else:
560                 #print self.listing.getFont() + " bold"
561                 label.modify_font(pango.FontDescription(self.config.getUnreadFont()))
562                 #label.modify_font(pango.FontDescription("sans bold 23"))
563                 #"sans bold 16"
564             label.set_line_wrap(True)
565             
566             label.set_size_request(self.get_size()[0]-50, -1)
567             button.connect("clicked", self.button_clicked, id)
568             self.buttons[id] = button
569             
570             self.vboxFeed.pack_start(button, expand=False)
571
572         self.add(self.pannableFeed)
573         self.show_all()
574         
575     def clear(self):
576         self.remove(self.pannableFeed)
577
578     def button_clicked(self, button, index, previous=False, next=False):
579         newDisp = DisplayArticle(self.feedTitle, self.feed.getArticle(index), self.feed.getLink(index), index, self.key, self.listing, self.config)
580         stack = hildon.WindowStack.get_default()
581         if previous:
582             tmp = stack.peek()
583             stack.pop_and_push(1, newDisp, tmp)
584             newDisp.show()
585             gobject.timeout_add(200, self.destroyArticle, tmp)
586             #print "previous"
587             self.disp = newDisp
588             
589             #stack.push(tmp)
590             #if not self.disp == False:
591             #   self.disp.destroyWindow()
592         elif next:
593             #print type(self.disp).__name__
594
595                 #self.disp.destroyWindow()
596                 #stack.pop_and_push(1,newDisp)
597             #else:
598             #    stack.push(newDisp)
599             #self.disp = newDisp
600             newDisp.show_all()
601             if type(self.disp).__name__ == "DisplayArticle":
602                 gobject.timeout_add(200, self.destroyArticle, self.disp)
603             self.disp = newDisp
604             #self.disp.show_all()
605             #if not self.disp == False:
606             #    self.disp.destroyWindow()
607         else:
608             self.disp = newDisp
609             self.disp.show_all()
610         
611         self.ids = []
612         self.ids.append(self.disp.connect("article-closed", self.onArticleClosed))
613         self.ids.append(self.disp.connect("article-next", self.nextArticle))
614         self.ids.append(self.disp.connect("article-previous", self.previousArticle))
615
616     def destroyArticle(self, handle):
617         handle.destroyWindow()
618
619     def nextArticle(self, object, index):
620         label = self.buttons[index].child
621         label.modify_font(pango.FontDescription(self.config.getReadFont()))
622         id = self.feed.getNextId(index)
623         self.button_clicked(object, id, next=True)
624
625     def previousArticle(self, object, index):
626         label = self.buttons[index].child
627         label.modify_font(pango.FontDescription(self.config.getReadFont()))
628         id = self.feed.getPreviousId(index)
629         self.button_clicked(object, id, previous=True)
630
631     def onArticleClosed(self, object, index):
632         label = self.buttons[index].child
633         label.modify_font(pango.FontDescription(self.config.getReadFont()))
634         self.buttons[index].show()
635
636     def button_update_clicked(self, button):
637         #bar = DownloadBar(self, self.listing, [self.key,], self.config ) 
638         if not type(self.downloadDialog).__name__=="DownloadBar":
639             self.pannableFeed.destroy()
640             self.vbox = gtk.VBox(False, 10)
641             self.downloadDialog = DownloadBar(self.window, self.listing, [self.key,], self.config, single=True )
642             self.downloadDialog.connect("download-done", self.onDownloadsDone)
643             self.vbox.pack_start(self.downloadDialog, expand=False, fill=False)
644             self.add(self.vbox)
645             self.show_all()
646             
647     def onDownloadsDone(self, *widget):
648         self.vbox.destroy()
649         self.feed = self.listing.getFeed(self.key)
650         self.displayFeed()
651         #self.feed.updateFeed()
652     #    self.clear()
653     #    self.displayFeed()
654         
655     def buttonReadAllClicked(self, button):
656         for index in self.feed.getIds():
657             self.feed.setEntryRead(index)
658             label = self.buttons[index].child
659             label.modify_font(pango.FontDescription(self.config.getReadFont()))
660             self.buttons[index].show()
661
662
663 class FeedingIt:
664     def __init__(self):
665         # Init the windows
666         self.window = hildon.StackableWindow()
667         self.window.set_title("FeedingIt")
668         hildon.hildon_gtk_window_set_progress_indicator(self.window, 1)
669         self.mainVbox = gtk.VBox(False,10)
670         self.pannableListing = gtk.Label("Loading...")
671         self.mainVbox.pack_start(self.pannableListing)
672         self.window.add(self.mainVbox)
673         self.window.show_all()
674         self.config = Config(self.window, CONFIGDIR+"config.ini", has_webkit)
675         gobject.idle_add(self.createWindow)
676         
677     def createWindow(self):
678         self.listing = Listing(CONFIGDIR)
679         
680         self.downloadDialog = False
681         self.orientation = FremantleRotation("FeedingIt", main_window=self.window)
682         self.orientation.set_mode(self.config.getOrientation())
683         
684         menu = hildon.AppMenu()
685         # Create a button and add it to the menu
686         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
687         button.set_label("Update All Feeds")
688         button.connect("clicked", self.button_update_clicked, "All")
689         menu.append(button)
690         
691         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
692         button.set_label("Mark All As Read")
693         button.connect("clicked", self.button_markAll)
694         menu.append(button)
695         
696         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
697         button.set_label("Organize Feeds")
698         button.connect("clicked", self.button_organize_clicked)
699         menu.append(button)
700
701         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
702         button.set_label("Preferences")
703         button.connect("clicked", self.button_preferences_clicked)
704         menu.append(button)
705        
706         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
707         button.set_label("Import Feeds")
708         button.connect("clicked", self.button_import_clicked)
709         menu.append(button)
710         
711         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
712         button.set_label("Export Feeds")
713         button.connect("clicked", self.button_export_clicked)
714         menu.append(button)
715         
716         self.window.set_app_menu(menu)
717         menu.show_all()
718         
719         #self.feedWindow = hildon.StackableWindow()
720         #self.articleWindow = hildon.StackableWindow()
721
722         self.displayListing()
723         self.autoupdate = False
724         self.checkAutoUpdate()
725         hildon.hildon_gtk_window_set_progress_indicator(self.window, 0)
726
727     def button_markAll(self, button):
728         for key in self.listing.getListOfFeeds():
729             feed = self.listing.getFeed(key)
730             for index in range(feed.getNumberOfEntries()):
731                 feed.setEntryRead(index)
732         self.refreshList()
733
734     def button_export_clicked(self, button):
735         opml = ExportOpmlData(self.window, self.listing)
736         
737     def button_import_clicked(self, button):
738         opml = GetOpmlData(self.window)
739         feeds = opml.getData()
740         for (title, url) in feeds:
741             self.listing.addFeed(title, url)
742         self.displayListing()
743
744     def addFeed(self, urlIn="http://"):
745         wizard = AddWidgetWizard(self.window, urlIn)
746         ret = wizard.run()
747         if ret == 2:
748             (title, url) = wizard.getData()
749             if (not title == '') and (not url == ''): 
750                self.listing.addFeed(title, url)
751         wizard.destroy()
752         self.displayListing()
753
754     def button_organize_clicked(self, button):
755         org = SortList(self.window, self.listing)
756         org.run()
757         org.destroy()
758         self.listing.saveConfig()
759         self.displayListing()
760         
761     def button_update_clicked(self, button, key):
762         if not type(self.downloadDialog).__name__=="DownloadBar":
763             self.downloadDialog = DownloadBar(self.window, self.listing, self.listing.getListOfFeeds(), self.config )
764             self.downloadDialog.connect("download-done", self.onDownloadsDone)
765             self.mainVbox.pack_end(self.downloadDialog, expand=False, fill=False)
766             self.mainVbox.show_all()
767         #self.displayListing()
768
769     def onDownloadsDone(self, *widget):
770         self.downloadDialog.destroy()
771         self.downloadDialog = False
772         #self.displayListing()
773         self.refreshList()
774
775     def button_preferences_clicked(self, button):
776         dialog = self.config.createDialog()
777         dialog.connect("destroy", self.prefsClosed)
778
779     def show_confirmation_note(self, parent, title):
780         note = hildon.Note("confirmation", parent, "Are you sure you want to delete " + title +"?")
781
782         retcode = gtk.Dialog.run(note)
783         note.destroy()
784         
785         if retcode == gtk.RESPONSE_OK:
786             return True
787         else:
788             return False
789         
790     def displayListing(self):
791         try:
792             self.mainVbox.remove(self.pannableListing)
793         except:
794             pass
795         self.vboxListing = gtk.VBox(False,10)
796         self.pannableListing = hildon.PannableArea()
797         self.pannableListing.add_with_viewport(self.vboxListing)
798
799         self.buttons = {}
800         list = self.listing.getListOfFeeds()[:]
801         #list.reverse()
802         for key in list:
803             #button = gtk.Button(item)
804             button = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT,
805                               hildon.BUTTON_ARRANGEMENT_VERTICAL)
806             button.set_text(self.listing.getFeedTitle(key), self.listing.getFeedUpdateTime(key) + " / " 
807                             + str(self.listing.getFeedNumberOfUnreadItems(key)) + " Unread Items")
808             button.set_alignment(0,0,1,1)
809             button.connect("clicked", self.buttonFeedClicked, self, self.window, key)
810             self.vboxListing.pack_start(button, expand=False)
811             self.buttons[key] = button
812             
813         #if type(self.downloadDialog).__name__=="DownloadBar":
814         #    self.vboxListing.pack_start(self.downloadDialog)
815         self.mainVbox.pack_start(self.pannableListing)
816         self.window.show_all()
817
818     def refreshList(self):
819         for key in self.listing.getListOfFeeds():
820             if self.buttons.has_key(key):
821                 button = self.buttons[key]
822                 button.set_text(self.listing.getFeedTitle(key), self.listing.getFeedUpdateTime(key) + " / " 
823                             + str(self.listing.getFeedNumberOfUnreadItems(key)) + " Unread Items")
824             else:
825                 self.displayListing()
826                 break
827
828     def buttonFeedClicked(widget, button, self, window, key):
829         disp = DisplayFeed(self.listing, self.listing.getFeed(key), self.listing.getFeedTitle(key), key, self.config)
830         disp.connect("feed-closed", self.onFeedClosed)
831
832     def onFeedClosed(self, object, key):
833         #self.displayListing()
834         self.listing.saveConfig()
835         self.refreshList()
836         #self.buttons[key].set_text(self.listing.getFeedTitle(key), self.listing.getFeedUpdateTime(key) + " / " 
837         #                    + str(self.listing.getFeedNumberOfUnreadItems(key)) + " Unread Items")
838         #self.buttons[key].show()
839      
840     def run(self):
841         self.window.connect("destroy", gtk.main_quit)
842         gtk.main()
843         #for key in self.listing.getListOfFeeds():
844         #    self.listing.getFeed(key).saveFeed(CONFIGDIR)
845         self.listing.saveConfig()
846
847     def prefsClosed(self, *widget):
848         self.orientation.set_mode(self.config.getOrientation())
849         self.checkAutoUpdate()
850
851     def checkAutoUpdate(self, *widget):
852         interval = int(self.config.getUpdateInterval()*3600000)
853         if self.config.isAutoUpdateEnabled():
854             if self.autoupdate == False:
855                 self.autoupdateId = gobject.timeout_add(interval, self.automaticUpdate)
856                 self.autoupdate = interval
857             elif not self.autoupdate == interval:
858                 # If auto-update is enabled, but not at the right frequency
859                 gobject.source_remove(self.autoupdateId)
860                 self.autoupdateId = gobject.timeout_add(interval, self.automaticUpdate)
861                 self.autoupdate = interval
862         else:
863             if not self.autoupdate == False:
864                 gobject.source_remove(self.autoupdateId)
865                 self.autoupdate = False
866
867     def automaticUpdate(self, *widget):
868         # Need to check for internet connection
869         # If no internet connection, try again in 10 minutes:
870         # gobject.timeout_add(int(5*3600000), self.automaticUpdate)
871         self.button_update_clicked(None, None)
872         return True
873     
874     def getStatus(self):
875         status = ""
876         for key in self.listing.getListOfFeeds():
877             if self.listing.getFeedNumberOfUnreadItems(key) > 0:
878                 status += self.listing.getFeedTitle(key) + ": \t" +  str(self.listing.getFeedNumberOfUnreadItems(key)) + " Unread Items\n"
879         if status == "":
880             status = "No unread items"
881         return status
882
883 if __name__ == "__main__":
884     gobject.signal_new("feed-closed", DisplayFeed, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,))
885     gobject.signal_new("article-closed", DisplayArticle, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,))
886     gobject.signal_new("article-next", DisplayArticle, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,))
887     gobject.signal_new("article-previous", DisplayArticle, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,))
888     gobject.signal_new("download-done", DownloadBar, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,))
889     gobject.threads_init()
890     if not isdir(CONFIGDIR):
891         try:
892             mkdir(CONFIGDIR)
893         except:
894             print "Error: Can't create configuration directory"
895             sys.exit(1)
896     app = FeedingIt()
897     dbusHandler = ServerObject(app)
898     app.run()