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