0.4.2-5 Edit feed option, loading screen
[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.window, 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         self.displayListing()
338
339     def buttonDone(self, *args):
340         self.destroy()
341                
342
343 class DisplayArticle(hildon.StackableWindow):
344     def __init__(self, title, text, link, index, key, listing):
345         hildon.StackableWindow.__init__(self)
346         self.imageDownloader = ImageDownloader()
347         self.listing=listing
348         self.key = key
349         self.index = index
350         self.text = text
351         self.link = link
352         self.set_title(title)
353         self.images = []
354         
355         # Init the article display    
356         self.view = gtkhtml2.View()
357         self.pannable_article = hildon.PannableArea()
358         self.pannable_article.add(self.view)
359         #self.pannable_article.set_property("mov-mode", hildon.MOVEMENT_MODE_BOTH)
360         self.gestureId = self.pannable_article.connect('horizontal-movement', self.gesture)
361         self.document = gtkhtml2.Document()
362         self.view.set_document(self.document)
363         
364         self.document.connect("link_clicked", self._signal_link_clicked)
365         if not key == "1295627ef630df9d239abeb0ba631c3f":
366             # Do not download images if the feed is "Archived Articles"
367             self.document.connect("request-url", self._signal_request_url)
368         self.document.clear()
369         self.document.open_stream("text/html")
370         self.document.write_stream(self.text)
371         self.document.close_stream()
372         
373         menu = hildon.AppMenu()
374         # Create a button and add it to the menu
375         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
376         button.set_label("Allow Horizontal Scrolling")
377         button.connect("clicked", self.horiz_scrolling_button)
378         menu.append(button)
379         
380         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
381         button.set_label("Open in Browser")
382         button.connect("clicked", self._signal_link_clicked, self.link)
383         menu.append(button)
384         
385         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
386         button.set_label("Add to Archived Articles")
387         button.connect("clicked", self.archive_button)
388         menu.append(button)
389         
390         self.set_app_menu(menu)
391         menu.show_all()
392         
393         self.add(self.pannable_article)
394         
395         self.pannable_article.show_all()
396
397         self.destroyId = self.connect("destroy", self.destroyWindow)
398         #self.timeout_handler_id = gobject.timeout_add(300, self.reloadArticle)
399
400     def gesture(self, widget, direction, startx, starty):
401         if (direction == 3):
402             self.emit("article-next", self.index)
403         if (direction == 2):
404             self.emit("article-previous", self.index)
405         #self.timeout_handler_id = gobject.timeout_add(200, self.destroyWindow)
406
407     def destroyWindow(self, *args):
408         self.disconnect(self.destroyId)
409         self.emit("article-closed", self.index)
410         self.imageDownloader.stopAll()
411         self.destroy()
412         
413     def horiz_scrolling_button(self, *widget):
414         self.pannable_article.disconnect(self.gestureId)
415         self.pannable_article.set_property("mov-mode", hildon.MOVEMENT_MODE_BOTH)
416         
417     def archive_button(self, *widget):
418         # Call the listing.addArchivedArticle
419         self.listing.addArchivedArticle(self.key, self.index)
420         
421     #def reloadArticle(self, *widget):
422     #    if threading.activeCount() > 1:
423             # Image thread are still running, come back in a bit
424     #        return True
425     #    else:
426     #        for (stream, imageThread) in self.images:
427     #            imageThread.join()
428     #            stream.write(imageThread.data)
429     #            stream.close()
430     #        return False
431     #    self.show_all()
432
433     def _signal_link_clicked(self, object, link):
434         bus = dbus.SystemBus()
435         proxy = bus.get_object("com.nokia.osso_browser", "/com/nokia/osso_browser/request")
436         iface = dbus.Interface(proxy, 'com.nokia.osso_browser')
437         iface.open_new_window(link)
438
439     def _signal_request_url(self, object, url, stream):
440         #print url
441         self.imageDownloader.queueImage(url, stream)
442         #imageThread = GetImage(url)
443         #imageThread.start()
444         #self.images.append((stream, imageThread))
445
446
447 class DisplayFeed(hildon.StackableWindow):
448     def __init__(self, listing, feed, title, key, config):
449         hildon.StackableWindow.__init__(self)
450         self.listing = listing
451         self.feed = feed
452         self.feedTitle = title
453         self.set_title(title)
454         self.key=key
455         self.config = config
456         
457         self.listing.setCurrentlyDisplayedFeed(self.key)
458         
459         self.disp = False
460         
461         #menu = hildon.AppMenu()
462         #button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
463         #button.set_label("Update Feed")
464         #button.connect("clicked", self.button_update_clicked)
465         #menu.append(button)
466         
467         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
468         button.set_label("Mark All As Read")
469         button.connect("clicked", self.buttonReadAllClicked)
470         menu.append(button)
471         self.set_app_menu(menu)
472         menu.show_all()
473         
474         self.displayFeed()
475         
476         self.connect("destroy", self.destroyWindow)
477         
478     def destroyWindow(self, *args):
479         self.emit("feed-closed", self.key)
480         self.destroy()
481         gobject.idle_add(self.feed.saveFeed, CONFIGDIR)
482         self.listing.closeCurrentlyDisplayedFeed()
483
484     def displayFeed(self):
485         self.vboxFeed = gtk.VBox(False, 10)
486         self.pannableFeed = hildon.PannableArea()
487         self.pannableFeed.add_with_viewport(self.vboxFeed)
488         self.pannableFeed.set_property("mov-mode", hildon.MOVEMENT_MODE_BOTH)
489         self.buttons = []
490         for index in range(self.feed.getNumberOfEntries()):
491             button = gtk.Button(self.feed.getTitle(index))
492             button.set_alignment(0,0)
493             label = button.child
494             if self.feed.isEntryRead(index):
495                 #label.modify_font(pango.FontDescription("sans 16"))
496                 label.modify_font(pango.FontDescription(self.config.getReadFont()))
497             else:
498                 #print self.listing.getFont() + " bold"
499                 label.modify_font(pango.FontDescription(self.config.getUnreadFont()))
500                 #label.modify_font(pango.FontDescription("sans bold 23"))
501                 #"sans bold 16"
502             label.set_line_wrap(True)
503             
504             label.set_size_request(self.get_size()[0]-50, -1)
505             button.connect("clicked", self.button_clicked, index)
506             self.buttons.append(button)
507             
508             self.vboxFeed.pack_start(button, expand=False)           
509             index=index+1
510
511         self.add(self.pannableFeed)
512         self.show_all()
513         
514     def clear(self):
515         self.remove(self.pannableFeed)
516
517     def button_clicked(self, button, index, previous=False, next=False):
518         newDisp = DisplayArticle(self.feedTitle, self.feed.getArticle(index), self.feed.getLink(index), index, self.key, self.listing)
519         stack = hildon.WindowStack.get_default()
520         if previous:
521             tmp = stack.peek()
522             stack.pop_and_push(1, newDisp, tmp)
523             newDisp.show()
524             gobject.timeout_add(200, self.destroyArticle, tmp)
525             #print "previous"
526             self.disp = newDisp
527             
528             #stack.push(tmp)
529             #if not self.disp == False:
530             #   self.disp.destroyWindow()
531         elif next:
532             #print type(self.disp).__name__
533
534                 #self.disp.destroyWindow()
535                 #stack.pop_and_push(1,newDisp)
536             #else:
537             #    stack.push(newDisp)
538             #self.disp = newDisp
539             newDisp.show_all()
540             if type(self.disp).__name__ == "DisplayArticle":
541                 gobject.timeout_add(200, self.destroyArticle, self.disp)
542             self.disp = newDisp
543             #self.disp.show_all()
544             #if not self.disp == False:
545             #    self.disp.destroyWindow()
546         else:
547             self.disp = newDisp
548             self.disp.show_all()
549         
550         self.ids = []
551         self.ids.append(self.disp.connect("article-closed", self.onArticleClosed))
552         self.ids.append(self.disp.connect("article-next", self.nextArticle))
553         self.ids.append(self.disp.connect("article-previous", self.previousArticle))
554
555     def destroyArticle(self, handle):
556         handle.destroyWindow()
557
558     def nextArticle(self, object, index):
559         label = self.buttons[index].child
560         label.modify_font(pango.FontDescription(self.config.getReadFont()))
561         index = (index+1) % self.feed.getNumberOfEntries()
562         self.button_clicked(object, index, next=True)
563
564     def previousArticle(self, object, index):
565         label = self.buttons[index].child
566         label.modify_font(pango.FontDescription(self.config.getReadFont()))
567         index = (index-1) % self.feed.getNumberOfEntries()
568         self.button_clicked(object, index, previous=True)
569
570     def onArticleClosed(self, object, index):
571         label = self.buttons[index].child
572         label.modify_font(pango.FontDescription(self.config.getReadFont()))
573         self.buttons[index].show()
574
575     #def button_update_clicked(self, button):
576     #    bar = DownloadBar(self, self.listing, [self.key,], self.config )       
577         #self.feed.updateFeed()
578     #    self.clear()
579     #    self.displayFeed()
580         
581     def buttonReadAllClicked(self, button):
582         for index in range(self.feed.getNumberOfEntries()):
583             self.feed.setEntryRead(index)
584             label = self.buttons[index].child
585             label.modify_font(pango.FontDescription(self.config.getReadFont()))
586             self.buttons[index].show()
587
588
589 class FeedingIt:
590     def __init__(self):
591         # Init the windows
592         self.window = hildon.StackableWindow()
593         self.window.set_title("FeedingIt")
594         hildon.hildon_gtk_window_set_progress_indicator(self.window, 1)
595         self.pannableListing = gtk.Label("Loading...")
596         self.window.add(self.pannableListing)
597         self.window.show_all()
598         self.config = Config(self.window, CONFIGDIR+"config.ini")
599         gobject.idle_add(self.createWindow)
600         
601     def createWindow(self):
602         self.listing = Listing(CONFIGDIR)
603         
604         self.downloadDialog = False
605         self.orientation = FremantleRotation("FeedingIt", main_window=self.window)
606         self.orientation.set_mode(self.config.getOrientation())
607         
608         menu = hildon.AppMenu()
609         # Create a button and add it to the menu
610         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
611         button.set_label("Update All Feeds")
612         button.connect("clicked", self.button_update_clicked, "All")
613         menu.append(button)
614         
615         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
616         button.set_label("Add Feed")
617         button.connect("clicked", self.button_add_clicked)
618         menu.append(button)
619         
620         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
621         button.set_label("Organize Feeds")
622         button.connect("clicked", self.button_organize_clicked)
623         menu.append(button)
624
625         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
626         button.set_label("Preferences")
627         button.connect("clicked", self.button_preferences_clicked)
628         menu.append(button)
629        
630         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
631         button.set_label("Import Feeds")
632         button.connect("clicked", self.button_import_clicked)
633         menu.append(button)
634         
635         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
636         button.set_label("Export Feeds")
637         button.connect("clicked", self.button_export_clicked)
638         menu.append(button)
639         
640         self.window.set_app_menu(menu)
641         menu.show_all()
642         
643         #self.feedWindow = hildon.StackableWindow()
644         #self.articleWindow = hildon.StackableWindow()
645
646         self.displayListing()
647         self.autoupdate = False
648         self.checkAutoUpdate()
649         hildon.hildon_gtk_window_set_progress_indicator(self.window, 0)
650
651
652     def button_export_clicked(self, button):
653         opml = ExportOpmlData(self.window, self.listing)
654         
655     def button_import_clicked(self, button):
656         opml = GetOpmlData(self.window)
657         feeds = opml.getData()
658         for (title, url) in feeds:
659             self.listing.addFeed(title, url)
660         self.displayListing()
661
662     def button_organize_clicked(self, button):
663         org = SortList(self.window, self.listing)
664         org.run()
665         org.destroy()
666         self.listing.saveConfig()
667         self.displayListing()
668         
669     def button_add_clicked(self, button, urlIn="http://"):
670         wizard = AddWidgetWizard(self.window, urlIn)
671         ret = wizard.run()
672         if ret == 2:
673             (title, url) = wizard.getData()
674             if (not title == '') and (not url == ''): 
675                self.listing.addFeed(title, url)
676                    
677         wizard.destroy()
678         self.displayListing()
679         
680     def button_update_clicked(self, button, key):
681         if not type(self.downloadDialog).__name__=="DownloadBar":
682             self.downloadDialog = DownloadBar(self.window, self.listing, self.listing.getListOfFeeds(), self.config )
683             self.downloadDialog.connect("download-done", self.onDownloadsDone)
684             self.vboxListing.pack_start(self.downloadDialog)
685             self.pannableListing.show_all()
686         #self.displayListing()
687
688     def onDownloadsDone(self, *widget):
689         self.downloadDialog.destroy()
690         self.downloadDialog = False
691         self.displayListing()
692
693     def button_preferences_clicked(self, button):
694         dialog = self.config.createDialog()
695         dialog.connect("destroy", self.prefsClosed)
696
697     def show_confirmation_note(self, parent, title):
698         note = hildon.Note("confirmation", parent, "Are you sure you want to delete " + title +"?")
699
700         retcode = gtk.Dialog.run(note)
701         note.destroy()
702         
703         if retcode == gtk.RESPONSE_OK:
704             return True
705         else:
706             return False
707         
708     def displayListing(self):
709         try:
710             self.window.remove(self.pannableListing)
711         except:
712             pass
713         self.vboxListing = gtk.VBox(False,10)
714         self.pannableListing = hildon.PannableArea()
715         self.pannableListing.add_with_viewport(self.vboxListing)
716
717         self.buttons = {}
718         list = self.listing.getListOfFeeds()[:]
719         list.reverse()
720         for key in list:
721             #button = gtk.Button(item)
722             button = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT,
723                               hildon.BUTTON_ARRANGEMENT_VERTICAL)
724             button.set_text(self.listing.getFeedTitle(key), self.listing.getFeedUpdateTime(key) + " / " 
725                             + str(self.listing.getFeedNumberOfUnreadItems(key)) + " Unread Items")
726             button.set_alignment(0,0,1,1)
727             button.connect("clicked", self.buttonFeedClicked, self, self.window, key)
728             self.vboxListing.pack_end(button) #, expand=False)
729             self.buttons[key] = button
730             
731         if type(self.downloadDialog).__name__=="DownloadBar":
732             self.vboxListing.pack_start(self.downloadDialog)
733         self.window.add(self.pannableListing)
734         self.window.show_all()
735
736     def buttonFeedClicked(widget, button, self, window, key):
737         disp = DisplayFeed(self.listing, self.listing.getFeed(key), self.listing.getFeedTitle(key), key, self.config)
738         disp.connect("feed-closed", self.onFeedClosed)
739
740     def onFeedClosed(self, object, key):
741         self.displayListing()
742         #self.buttons[key].set_text(self.listing.getFeedTitle(key), self.listing.getFeedUpdateTime(key) + " / " 
743         #                    + str(self.listing.getFeedNumberOfUnreadItems(key)) + " Unread Items")
744         #self.buttons[key].show()
745      
746     def run(self):
747         self.window.connect("destroy", gtk.main_quit)
748         gtk.main()
749         self.listing.saveConfig()
750
751     def prefsClosed(self, *widget):
752         self.orientation.set_mode(self.config.getOrientation())
753         self.checkAutoUpdate()
754
755     def checkAutoUpdate(self, *widget):
756         if self.config.isAutoUpdateEnabled():
757             if not self.autoupdate:
758                 self.autoupdateId = gobject.timeout_add(int(self.config.getUpdateInterval()*3600000), self.automaticUpdate)
759                 self.autoupdate = True
760         else:
761             if self.autoupdate:
762                 gobject.source_remove(self.autoupdateId)
763                 self.autoupdate = False
764
765     def automaticUpdate(self, *widget):
766         # Need to check for internet connection
767         # If no internet connection, try again in 10 minutes:
768         # gobject.timeout_add(int(5*3600000), self.automaticUpdate)
769         self.button_update_clicked(None, None)
770         return True
771
772 if __name__ == "__main__":
773     gobject.signal_new("feed-closed", DisplayFeed, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,))
774     gobject.signal_new("article-closed", DisplayArticle, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,))
775     gobject.signal_new("article-next", DisplayArticle, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,))
776     gobject.signal_new("article-previous", DisplayArticle, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,))
777     gobject.signal_new("download-done", DownloadBar, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,))
778     gobject.threads_init()
779     if not isdir(CONFIGDIR):
780         try:
781             mkdir(CONFIGDIR)
782         except:
783             print "Error: Can't create configuration directory"
784             sys.exit(1)
785     app = FeedingIt()
786     dbusHandler = ServerObject(app)
787     app.run()