0.4.5-1
[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.4.3
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("Downloading")
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 has_webkit:
379             self.view = webkit.WebView()
380             #self.view.set_editable(False)
381         else:
382             self.view = gtkhtml2.View()
383             self.document = gtkhtml2.Document()
384             self.view.set_document(self.document)
385             self.document.connect("link_clicked", self._signal_link_clicked)
386         self.pannable_article = hildon.PannableArea()
387         self.pannable_article.add(self.view)
388         #self.pannable_article.set_property("mov-mode", hildon.MOVEMENT_MODE_BOTH)
389         #self.gestureId = self.pannable_article.connect('horizontal-movement', self.gesture)
390
391         if has_webkit:
392             self.view.load_string(self.text, "text/html", "utf-8", self.link)
393             self.view.set_zoom_level(float(config.getArtFontSize())/10.)
394         else:
395             if not key == "1295627ef630df9d239abeb0ba631c3f":
396                 # Do not download images if the feed is "Archived Articles"
397                 self.document.connect("request-url", self._signal_request_url)
398             
399             self.document.clear()
400             self.document.open_stream("text/html")
401             self.document.write_stream(self.text)
402             self.document.close_stream()
403         
404         menu = hildon.AppMenu()
405         # Create a button and add it to the menu
406         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
407         button.set_label("Allow Horizontal Scrolling")
408         button.connect("clicked", self.horiz_scrolling_button)
409         menu.append(button)
410         
411         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
412         button.set_label("Open in Browser")
413         button.connect("clicked", self._signal_link_clicked, self.link)
414         menu.append(button)
415         
416         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
417         button.set_label("Add to Archived Articles")
418         button.connect("clicked", self.archive_button)
419         menu.append(button)
420         
421         self.set_app_menu(menu)
422         menu.show_all()
423         
424         #self.event_box = gtk.EventBox()
425         #self.event_box.add(self.pannable_article)
426         self.add(self.pannable_article)
427         
428         
429         self.pannable_article.show_all()
430
431         self.destroyId = self.connect("destroy", self.destroyWindow)
432         
433         self.view.connect("button_press_event", self.button_pressed)
434         self.view.connect("button_release_event", self.button_released)
435         #self.timeout_handler_id = gobject.timeout_add(300, self.reloadArticle)
436
437     def button_pressed(self, window, event):
438         #print event.x, event.y
439         self.coords = (event.x, event.y)
440         
441     def button_released(self, window, event):
442         x = self.coords[0] - event.x
443         y = self.coords[1] - event.y
444         
445         if (abs(y) < 20):
446             if (x > 30):
447                 self.emit("article-previous", self.index)
448             elif (x<-30):
449                 self.emit("article-next", self.index)   
450         #print x, y
451         #print "Released"
452
453     #def gesture(self, widget, direction, startx, starty):
454     #    if (direction == 3):
455     #        self.emit("article-next", self.index)
456     #    if (direction == 2):
457     #        self.emit("article-previous", self.index)
458         #print startx, starty
459         #self.timeout_handler_id = gobject.timeout_add(200, self.destroyWindow)
460
461     def destroyWindow(self, *args):
462         self.disconnect(self.destroyId)
463         self.emit("article-closed", self.index)
464         self.imageDownloader.stopAll()
465         self.destroy()
466         
467     def horiz_scrolling_button(self, *widget):
468         self.pannable_article.disconnect(self.gestureId)
469         self.pannable_article.set_property("mov-mode", hildon.MOVEMENT_MODE_BOTH)
470         
471     def archive_button(self, *widget):
472         # Call the listing.addArchivedArticle
473         self.listing.addArchivedArticle(self.key, self.index)
474         
475     #def reloadArticle(self, *widget):
476     #    if threading.activeCount() > 1:
477             # Image thread are still running, come back in a bit
478     #        return True
479     #    else:
480     #        for (stream, imageThread) in self.images:
481     #            imageThread.join()
482     #            stream.write(imageThread.data)
483     #            stream.close()
484     #        return False
485     #    self.show_all()
486
487     def _signal_link_clicked(self, object, link):
488         bus = dbus.SystemBus()
489         proxy = bus.get_object("com.nokia.osso_browser", "/com/nokia/osso_browser/request")
490         iface = dbus.Interface(proxy, 'com.nokia.osso_browser')
491         iface.load_url(link)
492
493     def _signal_request_url(self, object, url, stream):
494         #print url
495         self.imageDownloader.queueImage(url, stream)
496         #imageThread = GetImage(url)
497         #imageThread.start()
498         #self.images.append((stream, imageThread))
499
500
501 class DisplayFeed(hildon.StackableWindow):
502     def __init__(self, listing, feed, title, key, config):
503         hildon.StackableWindow.__init__(self)
504         self.listing = listing
505         self.feed = feed
506         self.feedTitle = title
507         self.set_title(title)
508         self.key=key
509         self.config = config
510         
511         self.downloadDialog = False
512         
513         self.listing.setCurrentlyDisplayedFeed(self.key)
514         
515         self.disp = False
516         
517         menu = hildon.AppMenu()
518         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
519         button.set_label("Update Feed")
520         button.connect("clicked", self.button_update_clicked)
521         menu.append(button)
522         
523         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
524         button.set_label("Mark All As Read")
525         button.connect("clicked", self.buttonReadAllClicked)
526         menu.append(button)
527         self.set_app_menu(menu)
528         menu.show_all()
529         
530         self.displayFeed()
531         
532         self.connect("destroy", self.destroyWindow)
533         
534     def destroyWindow(self, *args):
535         self.emit("feed-closed", self.key)
536         self.destroy()
537         #gobject.idle_add(self.feed.saveFeed, CONFIGDIR)
538         self.listing.closeCurrentlyDisplayedFeed()
539
540     def displayFeed(self):
541         self.vboxFeed = gtk.VBox(False, 10)
542         self.pannableFeed = hildon.PannableArea()
543         self.pannableFeed.add_with_viewport(self.vboxFeed)
544         self.pannableFeed.set_property("mov-mode", hildon.MOVEMENT_MODE_BOTH)
545         self.buttons = []
546         for index in range(self.feed.getNumberOfEntries()):
547             button = gtk.Button(self.feed.getTitle(index))
548             button.set_alignment(0,0)
549             label = button.child
550             if self.feed.isEntryRead(index):
551                 #label.modify_font(pango.FontDescription("sans 16"))
552                 label.modify_font(pango.FontDescription(self.config.getReadFont()))
553             else:
554                 #print self.listing.getFont() + " bold"
555                 label.modify_font(pango.FontDescription(self.config.getUnreadFont()))
556                 #label.modify_font(pango.FontDescription("sans bold 23"))
557                 #"sans bold 16"
558             label.set_line_wrap(True)
559             
560             label.set_size_request(self.get_size()[0]-50, -1)
561             button.connect("clicked", self.button_clicked, index)
562             self.buttons.append(button)
563             
564             self.vboxFeed.pack_start(button, expand=False)           
565             index=index+1
566
567         self.add(self.pannableFeed)
568         self.show_all()
569         
570     def clear(self):
571         self.remove(self.pannableFeed)
572
573     def button_clicked(self, button, index, previous=False, next=False):
574         newDisp = DisplayArticle(self.feedTitle, self.feed.getArticle(index), self.feed.getLink(index), index, self.key, self.listing, self.config)
575         stack = hildon.WindowStack.get_default()
576         if previous:
577             tmp = stack.peek()
578             stack.pop_and_push(1, newDisp, tmp)
579             newDisp.show()
580             gobject.timeout_add(200, self.destroyArticle, tmp)
581             #print "previous"
582             self.disp = newDisp
583             
584             #stack.push(tmp)
585             #if not self.disp == False:
586             #   self.disp.destroyWindow()
587         elif next:
588             #print type(self.disp).__name__
589
590                 #self.disp.destroyWindow()
591                 #stack.pop_and_push(1,newDisp)
592             #else:
593             #    stack.push(newDisp)
594             #self.disp = newDisp
595             newDisp.show_all()
596             if type(self.disp).__name__ == "DisplayArticle":
597                 gobject.timeout_add(200, self.destroyArticle, self.disp)
598             self.disp = newDisp
599             #self.disp.show_all()
600             #if not self.disp == False:
601             #    self.disp.destroyWindow()
602         else:
603             self.disp = newDisp
604             self.disp.show_all()
605         
606         self.ids = []
607         self.ids.append(self.disp.connect("article-closed", self.onArticleClosed))
608         self.ids.append(self.disp.connect("article-next", self.nextArticle))
609         self.ids.append(self.disp.connect("article-previous", self.previousArticle))
610
611     def destroyArticle(self, handle):
612         handle.destroyWindow()
613
614     def nextArticle(self, object, index):
615         label = self.buttons[index].child
616         label.modify_font(pango.FontDescription(self.config.getReadFont()))
617         index = (index+1) % self.feed.getNumberOfEntries()
618         self.button_clicked(object, index, next=True)
619
620     def previousArticle(self, object, index):
621         label = self.buttons[index].child
622         label.modify_font(pango.FontDescription(self.config.getReadFont()))
623         index = (index-1) % self.feed.getNumberOfEntries()
624         self.button_clicked(object, index, previous=True)
625
626     def onArticleClosed(self, object, index):
627         label = self.buttons[index].child
628         label.modify_font(pango.FontDescription(self.config.getReadFont()))
629         self.buttons[index].show()
630
631     def button_update_clicked(self, button):
632         #bar = DownloadBar(self, self.listing, [self.key,], self.config ) 
633         if not type(self.downloadDialog).__name__=="DownloadBar":
634             self.pannableFeed.destroy()
635             self.vbox = gtk.VBox(False, 10)
636             self.downloadDialog = DownloadBar(self.window, self.listing, [self.key,], self.config, single=True )
637             self.downloadDialog.connect("download-done", self.onDownloadsDone)
638             self.vbox.pack_start(self.downloadDialog, expand=False, fill=False)
639             self.add(self.vbox)
640             self.show_all()
641             
642     def onDownloadsDone(self, *widget):
643         self.vbox.destroy()
644         self.displayFeed()
645         #self.feed.updateFeed()
646     #    self.clear()
647     #    self.displayFeed()
648         
649     def buttonReadAllClicked(self, button):
650         for index in range(self.feed.getNumberOfEntries()):
651             self.feed.setEntryRead(index)
652             label = self.buttons[index].child
653             label.modify_font(pango.FontDescription(self.config.getReadFont()))
654             self.buttons[index].show()
655
656
657 class FeedingIt:
658     def __init__(self):
659         # Init the windows
660         self.window = hildon.StackableWindow()
661         self.window.set_title("FeedingIt")
662         hildon.hildon_gtk_window_set_progress_indicator(self.window, 1)
663         self.mainVbox = gtk.VBox(False,10)
664         self.pannableListing = gtk.Label("Loading...")
665         self.mainVbox.pack_start(self.pannableListing)
666         self.window.add(self.mainVbox)
667         self.window.show_all()
668         self.config = Config(self.window, CONFIGDIR+"config.ini")
669         gobject.idle_add(self.createWindow)
670         
671     def createWindow(self):
672         self.listing = Listing(CONFIGDIR)
673         
674         self.downloadDialog = False
675         #self.orientation = FremantleRotation("FeedingIt", main_window=self.window)
676         #self.orientation.set_mode(self.config.getOrientation())
677         
678         menu = hildon.AppMenu()
679         # Create a button and add it to the menu
680         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
681         button.set_label("Update All Feeds")
682         button.connect("clicked", self.button_update_clicked, "All")
683         menu.append(button)
684         
685         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
686         button.set_label("Mark All As Read")
687         button.connect("clicked", self.button_markAll)
688         menu.append(button)
689         
690         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
691         button.set_label("Organize Feeds")
692         button.connect("clicked", self.button_organize_clicked)
693         menu.append(button)
694
695         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
696         button.set_label("Preferences")
697         button.connect("clicked", self.button_preferences_clicked)
698         menu.append(button)
699        
700         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
701         button.set_label("Import Feeds")
702         button.connect("clicked", self.button_import_clicked)
703         menu.append(button)
704         
705         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
706         button.set_label("Export Feeds")
707         button.connect("clicked", self.button_export_clicked)
708         menu.append(button)
709         
710         self.window.set_app_menu(menu)
711         menu.show_all()
712         
713         #self.feedWindow = hildon.StackableWindow()
714         #self.articleWindow = hildon.StackableWindow()
715
716         self.displayListing()
717         self.autoupdate = False
718         self.checkAutoUpdate()
719         hildon.hildon_gtk_window_set_progress_indicator(self.window, 0)
720
721     def button_markAll(self, button):
722         for key in self.listing.getListOfFeeds():
723             feed = self.listing.getFeed(key)
724             for index in range(feed.getNumberOfEntries()):
725                 feed.setEntryRead(index)
726         self.refreshList()
727
728     def button_export_clicked(self, button):
729         opml = ExportOpmlData(self.window, self.listing)
730         
731     def button_import_clicked(self, button):
732         opml = GetOpmlData(self.window)
733         feeds = opml.getData()
734         for (title, url) in feeds:
735             self.listing.addFeed(title, url)
736         self.displayListing()
737
738     def addFeed(self, urlIn="http://"):
739         wizard = AddWidgetWizard(self.window, urlIn)
740         ret = wizard.run()
741         if ret == 2:
742             (title, url) = wizard.getData()
743             if (not title == '') and (not url == ''): 
744                self.listing.addFeed(title, url)
745         wizard.destroy()
746         self.displayListing()
747
748     def button_organize_clicked(self, button):
749         org = SortList(self.window, self.listing)
750         org.run()
751         org.destroy()
752         self.listing.saveConfig()
753         self.displayListing()
754         
755     def button_update_clicked(self, button, key):
756         if not type(self.downloadDialog).__name__=="DownloadBar":
757             self.downloadDialog = DownloadBar(self.window, self.listing, self.listing.getListOfFeeds(), self.config )
758             self.downloadDialog.connect("download-done", self.onDownloadsDone)
759             self.mainVbox.pack_end(self.downloadDialog, expand=False, fill=False)
760             self.mainVbox.show_all()
761         #self.displayListing()
762
763     def onDownloadsDone(self, *widget):
764         self.downloadDialog.destroy()
765         self.downloadDialog = False
766         #self.displayListing()
767         self.refreshList()
768
769     def button_preferences_clicked(self, button):
770         dialog = self.config.createDialog()
771         dialog.connect("destroy", self.prefsClosed)
772
773     def show_confirmation_note(self, parent, title):
774         note = hildon.Note("confirmation", parent, "Are you sure you want to delete " + title +"?")
775
776         retcode = gtk.Dialog.run(note)
777         note.destroy()
778         
779         if retcode == gtk.RESPONSE_OK:
780             return True
781         else:
782             return False
783         
784     def displayListing(self):
785         try:
786             self.mainVbox.remove(self.pannableListing)
787         except:
788             pass
789         self.vboxListing = gtk.VBox(False,10)
790         self.pannableListing = hildon.PannableArea()
791         self.pannableListing.add_with_viewport(self.vboxListing)
792
793         self.buttons = {}
794         list = self.listing.getListOfFeeds()[:]
795         #list.reverse()
796         for key in list:
797             #button = gtk.Button(item)
798             button = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT,
799                               hildon.BUTTON_ARRANGEMENT_VERTICAL)
800             button.set_text(self.listing.getFeedTitle(key), self.listing.getFeedUpdateTime(key) + " / " 
801                             + str(self.listing.getFeedNumberOfUnreadItems(key)) + " Unread Items")
802             button.set_alignment(0,0,1,1)
803             button.connect("clicked", self.buttonFeedClicked, self, self.window, key)
804             self.vboxListing.pack_start(button, expand=False)
805             self.buttons[key] = button
806             
807         #if type(self.downloadDialog).__name__=="DownloadBar":
808         #    self.vboxListing.pack_start(self.downloadDialog)
809         self.mainVbox.pack_start(self.pannableListing)
810         self.window.show_all()
811
812     def refreshList(self):
813         for key in self.listing.getListOfFeeds():
814             if self.buttons.has_key(key):
815                 button = self.buttons[key]
816                 button.set_text(self.listing.getFeedTitle(key), self.listing.getFeedUpdateTime(key) + " / " 
817                             + str(self.listing.getFeedNumberOfUnreadItems(key)) + " Unread Items")
818             else:
819                 self.displayListing()
820                 break
821
822     def buttonFeedClicked(widget, button, self, window, key):
823         disp = DisplayFeed(self.listing, self.listing.getFeed(key), self.listing.getFeedTitle(key), key, self.config)
824         disp.connect("feed-closed", self.onFeedClosed)
825
826     def onFeedClosed(self, object, key):
827         #self.displayListing()
828         self.refreshList()
829         #self.buttons[key].set_text(self.listing.getFeedTitle(key), self.listing.getFeedUpdateTime(key) + " / " 
830         #                    + str(self.listing.getFeedNumberOfUnreadItems(key)) + " Unread Items")
831         #self.buttons[key].show()
832      
833     def run(self):
834         self.window.connect("destroy", gtk.main_quit)
835         gtk.main()
836         for key in self.listing.getListOfFeeds():
837             self.listing.getFeed(key).saveFeed(CONFIGDIR)
838         self.listing.saveConfig()
839
840     def prefsClosed(self, *widget):
841         self.orientation.set_mode(self.config.getOrientation())
842         self.checkAutoUpdate()
843
844     def checkAutoUpdate(self, *widget):
845         interval = int(self.config.getUpdateInterval()*3600000)
846         if self.config.isAutoUpdateEnabled():
847             if self.autoupdate == False:
848                 self.autoupdateId = gobject.timeout_add(interval, self.automaticUpdate)
849                 self.autoupdate = interval
850             elif not self.autoupdate == interval:
851                 # If auto-update is enabled, but not at the right frequency
852                 gobject.source_remove(self.autoupdateId)
853                 self.autoupdateId = gobject.timeout_add(interval, self.automaticUpdate)
854                 self.autoupdate = interval
855         else:
856             if not self.autoupdate == False:
857                 gobject.source_remove(self.autoupdateId)
858                 self.autoupdate = False
859
860     def automaticUpdate(self, *widget):
861         # Need to check for internet connection
862         # If no internet connection, try again in 10 minutes:
863         # gobject.timeout_add(int(5*3600000), self.automaticUpdate)
864         self.button_update_clicked(None, None)
865         return True
866     
867     def getStatus(self):
868         status = ""
869         for key in self.listing.getListOfFeeds():
870             if self.listing.getFeedNumberOfUnreadItems(key) > 0:
871                 status += self.listing.getFeedTitle(key) + ": \t" +  str(self.listing.getFeedNumberOfUnreadItems(key)) + " Unread Items\n"
872         if status == "":
873             status = "No unread items"
874         return status
875
876 if __name__ == "__main__":
877     gobject.signal_new("feed-closed", DisplayFeed, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,))
878     gobject.signal_new("article-closed", DisplayArticle, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,))
879     gobject.signal_new("article-next", DisplayArticle, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,))
880     gobject.signal_new("article-previous", DisplayArticle, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,))
881     gobject.signal_new("download-done", DownloadBar, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,))
882     gobject.threads_init()
883     if not isdir(CONFIGDIR):
884         try:
885             mkdir(CONFIGDIR)
886         except:
887             print "Error: Can't create configuration directory"
888             sys.exit(1)
889     app = FeedingIt()
890     dbusHandler = ServerObject(app)
891     app.run()