0.4.3-5 Added GetStatus dbus call, and bugfixes
[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 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("Add Feed")
236         button.connect("clicked", self.buttonAdd)
237         self.vbox2.pack_start(button, expand=False, fill=False)
238
239         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
240         button.set_label("Edit Feed")
241         button.connect("clicked", self.buttonEdit)
242         self.vbox2.pack_start(button, expand=False, fill=False)
243         
244         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
245         button.set_label("Delete")
246         button.connect("clicked", self.buttonDelete)
247         self.vbox2.pack_start(button, expand=False, fill=False)
248         
249         #button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
250         #button.set_label("Done")
251         #button.connect("clicked", self.buttonDone)
252         #self.vbox.pack_start(button)
253         self.hbox2= gtk.HBox(False, 10)
254         self.pannableArea = hildon.PannableArea()
255         self.treestore = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING)
256         self.treeview = gtk.TreeView(self.treestore)
257         self.hbox2.pack_start(self.pannableArea, expand=True)
258         self.displayFeeds()
259         self.hbox2.pack_end(self.vbox2, expand=False)
260         self.set_default_size(-1, 600)
261         self.vbox.pack_start(self.hbox2)
262         
263         self.show_all()
264         #self.connect("destroy", self.buttonDone)
265         
266     def displayFeeds(self):
267         self.treeview.destroy()
268         self.treestore = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING)
269         self.treeview = gtk.TreeView()
270         
271         self.treeview.get_selection().set_mode(gtk.SELECTION_SINGLE)
272         hildon.hildon_gtk_tree_view_set_ui_mode(self.treeview, gtk.HILDON_UI_MODE_EDIT)
273         self.refreshList()
274         self.treeview.append_column(gtk.TreeViewColumn('Feed Name', gtk.CellRendererText(), text = 0))
275
276         self.pannableArea.add(self.treeview)
277
278         #self.show_all()
279
280     def refreshList(self, selected=None, offset=0):
281         rect = self.treeview.get_visible_rect()
282         y = rect.y+rect.height
283         self.treestore = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING)
284         for key in self.listing.getListOfFeeds():
285             item = self.treestore.append([self.listing.getFeedTitle(key), key])
286             if key == selected:
287                 selectedItem = item
288         self.treeview.set_model(self.treestore)
289         if not selected == None:
290             self.treeview.get_selection().select_iter(selectedItem)
291             self.treeview.scroll_to_cell(self.treeview.get_model().get_path(selectedItem))
292         self.pannableArea.show_all()
293
294     def getSelectedItem(self):
295         (model, iter) = self.treeview.get_selection().get_selected()
296         if not iter:
297             return None
298         return model.get_value(iter, 1)
299
300     def findIndex(self, key):
301         after = None
302         before = None
303         found = False
304         for row in self.treestore:
305             if found:
306                 return (before, row.iter)
307             if key == list(row)[0]:
308                 found = True
309             else:
310                 before = row.iter
311         return (before, None)
312
313     def buttonUp(self, button):
314         key  = self.getSelectedItem()
315         if not key == None:
316             self.listing.moveUp(key)
317             self.refreshList(key, -10)
318
319     def buttonDown(self, button):
320         key = self.getSelectedItem()
321         if not key == None:
322             self.listing.moveDown(key)
323             self.refreshList(key, 10)
324
325     def buttonDelete(self, button):
326         key = self.getSelectedItem()
327         if not key == None:
328             self.listing.removeFeed(key)
329         self.refreshList()
330
331     def buttonEdit(self, button):
332         key = self.getSelectedItem()
333         if not key == None:
334             wizard = AddWidgetWizard(self, self.listing.getFeedUrl(key), self.listing.getFeedTitle(key))
335             ret = wizard.run()
336             if ret == 2:
337                 (title, url) = wizard.getData()
338                 if (not title == '') and (not url == ''):
339                     self.listing.editFeed(key, title, url)
340             wizard.destroy()
341         self.refreshList()
342
343     def buttonDone(self, *args):
344         self.destroy()
345         
346     def buttonAdd(self, button, urlIn="http://"):
347         wizard = AddWidgetWizard(self, urlIn)
348         ret = wizard.run()
349         if ret == 2:
350             (title, url) = wizard.getData()
351             if (not title == '') and (not url == ''): 
352                self.listing.addFeed(title, url)
353         wizard.destroy()
354         self.refreshList()
355                
356
357 class DisplayArticle(hildon.StackableWindow):
358     def __init__(self, title, text, link, index, key, listing):
359         hildon.StackableWindow.__init__(self)
360         self.imageDownloader = ImageDownloader()
361         self.listing=listing
362         self.key = key
363         self.index = index
364         self.text = text
365         self.link = link
366         self.set_title(title)
367         self.images = []
368         
369         # Init the article display    
370         self.view = gtkhtml2.View()
371         self.pannable_article = hildon.PannableArea()
372         self.pannable_article.add(self.view)
373         #self.pannable_article.set_property("mov-mode", hildon.MOVEMENT_MODE_BOTH)
374         self.gestureId = self.pannable_article.connect('horizontal-movement', self.gesture)
375         self.document = gtkhtml2.Document()
376         self.view.set_document(self.document)
377         
378         self.document.connect("link_clicked", self._signal_link_clicked)
379         if not key == "1295627ef630df9d239abeb0ba631c3f":
380             # Do not download images if the feed is "Archived Articles"
381             self.document.connect("request-url", self._signal_request_url)
382         self.document.clear()
383         self.document.open_stream("text/html")
384         self.document.write_stream(self.text)
385         self.document.close_stream()
386         
387         menu = hildon.AppMenu()
388         # Create a button and add it to the menu
389         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
390         button.set_label("Allow Horizontal Scrolling")
391         button.connect("clicked", self.horiz_scrolling_button)
392         menu.append(button)
393         
394         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
395         button.set_label("Open in Browser")
396         button.connect("clicked", self._signal_link_clicked, self.link)
397         menu.append(button)
398         
399         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
400         button.set_label("Add to Archived Articles")
401         button.connect("clicked", self.archive_button)
402         menu.append(button)
403         
404         self.set_app_menu(menu)
405         menu.show_all()
406         
407         self.add(self.pannable_article)
408         
409         self.pannable_article.show_all()
410
411         self.destroyId = self.connect("destroy", self.destroyWindow)
412         #self.timeout_handler_id = gobject.timeout_add(300, self.reloadArticle)
413
414     def gesture(self, widget, direction, startx, starty):
415         if (direction == 3):
416             self.emit("article-next", self.index)
417         if (direction == 2):
418             self.emit("article-previous", self.index)
419         #self.timeout_handler_id = gobject.timeout_add(200, self.destroyWindow)
420
421     def destroyWindow(self, *args):
422         self.disconnect(self.destroyId)
423         self.emit("article-closed", self.index)
424         self.imageDownloader.stopAll()
425         self.destroy()
426         
427     def horiz_scrolling_button(self, *widget):
428         self.pannable_article.disconnect(self.gestureId)
429         self.pannable_article.set_property("mov-mode", hildon.MOVEMENT_MODE_BOTH)
430         
431     def archive_button(self, *widget):
432         # Call the listing.addArchivedArticle
433         self.listing.addArchivedArticle(self.key, self.index)
434         
435     #def reloadArticle(self, *widget):
436     #    if threading.activeCount() > 1:
437             # Image thread are still running, come back in a bit
438     #        return True
439     #    else:
440     #        for (stream, imageThread) in self.images:
441     #            imageThread.join()
442     #            stream.write(imageThread.data)
443     #            stream.close()
444     #        return False
445     #    self.show_all()
446
447     def _signal_link_clicked(self, object, link):
448         bus = dbus.SystemBus()
449         proxy = bus.get_object("com.nokia.osso_browser", "/com/nokia/osso_browser/request")
450         iface = dbus.Interface(proxy, 'com.nokia.osso_browser')
451         iface.open_new_window(link)
452
453     def _signal_request_url(self, object, url, stream):
454         #print url
455         self.imageDownloader.queueImage(url, stream)
456         #imageThread = GetImage(url)
457         #imageThread.start()
458         #self.images.append((stream, imageThread))
459
460
461 class DisplayFeed(hildon.StackableWindow):
462     def __init__(self, listing, feed, title, key, config):
463         hildon.StackableWindow.__init__(self)
464         self.listing = listing
465         self.feed = feed
466         self.feedTitle = title
467         self.set_title(title)
468         self.key=key
469         self.config = config
470         
471         self.listing.setCurrentlyDisplayedFeed(self.key)
472         
473         self.disp = False
474         
475         menu = hildon.AppMenu()
476         #button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
477         #button.set_label("Update Feed")
478         #button.connect("clicked", self.button_update_clicked)
479         #menu.append(button)
480         
481         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
482         button.set_label("Mark All As Read")
483         button.connect("clicked", self.buttonReadAllClicked)
484         menu.append(button)
485         self.set_app_menu(menu)
486         menu.show_all()
487         
488         self.displayFeed()
489         
490         self.connect("destroy", self.destroyWindow)
491         
492     def destroyWindow(self, *args):
493         self.emit("feed-closed", self.key)
494         self.destroy()
495         #gobject.idle_add(self.feed.saveFeed, CONFIGDIR)
496         self.listing.closeCurrentlyDisplayedFeed()
497
498     def displayFeed(self):
499         self.vboxFeed = gtk.VBox(False, 10)
500         self.pannableFeed = hildon.PannableArea()
501         self.pannableFeed.add_with_viewport(self.vboxFeed)
502         self.pannableFeed.set_property("mov-mode", hildon.MOVEMENT_MODE_BOTH)
503         self.buttons = []
504         for index in range(self.feed.getNumberOfEntries()):
505             button = gtk.Button(self.feed.getTitle(index))
506             button.set_alignment(0,0)
507             label = button.child
508             if self.feed.isEntryRead(index):
509                 #label.modify_font(pango.FontDescription("sans 16"))
510                 label.modify_font(pango.FontDescription(self.config.getReadFont()))
511             else:
512                 #print self.listing.getFont() + " bold"
513                 label.modify_font(pango.FontDescription(self.config.getUnreadFont()))
514                 #label.modify_font(pango.FontDescription("sans bold 23"))
515                 #"sans bold 16"
516             label.set_line_wrap(True)
517             
518             label.set_size_request(self.get_size()[0]-50, -1)
519             button.connect("clicked", self.button_clicked, index)
520             self.buttons.append(button)
521             
522             self.vboxFeed.pack_start(button, expand=False)           
523             index=index+1
524
525         self.add(self.pannableFeed)
526         self.show_all()
527         
528     def clear(self):
529         self.remove(self.pannableFeed)
530
531     def button_clicked(self, button, index, previous=False, next=False):
532         newDisp = DisplayArticle(self.feedTitle, self.feed.getArticle(index), self.feed.getLink(index), index, self.key, self.listing)
533         stack = hildon.WindowStack.get_default()
534         if previous:
535             tmp = stack.peek()
536             stack.pop_and_push(1, newDisp, tmp)
537             newDisp.show()
538             gobject.timeout_add(200, self.destroyArticle, tmp)
539             #print "previous"
540             self.disp = newDisp
541             
542             #stack.push(tmp)
543             #if not self.disp == False:
544             #   self.disp.destroyWindow()
545         elif next:
546             #print type(self.disp).__name__
547
548                 #self.disp.destroyWindow()
549                 #stack.pop_and_push(1,newDisp)
550             #else:
551             #    stack.push(newDisp)
552             #self.disp = newDisp
553             newDisp.show_all()
554             if type(self.disp).__name__ == "DisplayArticle":
555                 gobject.timeout_add(200, self.destroyArticle, self.disp)
556             self.disp = newDisp
557             #self.disp.show_all()
558             #if not self.disp == False:
559             #    self.disp.destroyWindow()
560         else:
561             self.disp = newDisp
562             self.disp.show_all()
563         
564         self.ids = []
565         self.ids.append(self.disp.connect("article-closed", self.onArticleClosed))
566         self.ids.append(self.disp.connect("article-next", self.nextArticle))
567         self.ids.append(self.disp.connect("article-previous", self.previousArticle))
568
569     def destroyArticle(self, handle):
570         handle.destroyWindow()
571
572     def nextArticle(self, object, index):
573         label = self.buttons[index].child
574         label.modify_font(pango.FontDescription(self.config.getReadFont()))
575         index = (index+1) % self.feed.getNumberOfEntries()
576         self.button_clicked(object, index, next=True)
577
578     def previousArticle(self, object, index):
579         label = self.buttons[index].child
580         label.modify_font(pango.FontDescription(self.config.getReadFont()))
581         index = (index-1) % self.feed.getNumberOfEntries()
582         self.button_clicked(object, index, previous=True)
583
584     def onArticleClosed(self, object, index):
585         label = self.buttons[index].child
586         label.modify_font(pango.FontDescription(self.config.getReadFont()))
587         self.buttons[index].show()
588
589     #def button_update_clicked(self, button):
590     #    bar = DownloadBar(self, self.listing, [self.key,], self.config )       
591         #self.feed.updateFeed()
592     #    self.clear()
593     #    self.displayFeed()
594         
595     def buttonReadAllClicked(self, button):
596         for index in range(self.feed.getNumberOfEntries()):
597             self.feed.setEntryRead(index)
598             label = self.buttons[index].child
599             label.modify_font(pango.FontDescription(self.config.getReadFont()))
600             self.buttons[index].show()
601
602
603 class FeedingIt:
604     def __init__(self):
605         # Init the windows
606         self.window = hildon.StackableWindow()
607         self.window.set_title("FeedingIt")
608         hildon.hildon_gtk_window_set_progress_indicator(self.window, 1)
609         self.mainVbox = gtk.VBox(False,10)
610         self.pannableListing = gtk.Label("Loading...")
611         self.mainVbox.pack_start(self.pannableListing)
612         self.window.add(self.mainVbox)
613         self.window.show_all()
614         self.config = Config(self.window, CONFIGDIR+"config.ini")
615         gobject.idle_add(self.createWindow)
616         
617     def createWindow(self):
618         self.listing = Listing(CONFIGDIR)
619         
620         self.downloadDialog = False
621         self.orientation = FremantleRotation("FeedingIt", main_window=self.window)
622         self.orientation.set_mode(self.config.getOrientation())
623         
624         menu = hildon.AppMenu()
625         # Create a button and add it to the menu
626         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
627         button.set_label("Update All Feeds")
628         button.connect("clicked", self.button_update_clicked, "All")
629         menu.append(button)
630         
631         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
632         button.set_label("Mark All As Read")
633         button.connect("clicked", self.button_markAll)
634         menu.append(button)
635         
636         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
637         button.set_label("Organize Feeds")
638         button.connect("clicked", self.button_organize_clicked)
639         menu.append(button)
640
641         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
642         button.set_label("Preferences")
643         button.connect("clicked", self.button_preferences_clicked)
644         menu.append(button)
645        
646         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
647         button.set_label("Import Feeds")
648         button.connect("clicked", self.button_import_clicked)
649         menu.append(button)
650         
651         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
652         button.set_label("Export Feeds")
653         button.connect("clicked", self.button_export_clicked)
654         menu.append(button)
655         
656         self.window.set_app_menu(menu)
657         menu.show_all()
658         
659         #self.feedWindow = hildon.StackableWindow()
660         #self.articleWindow = hildon.StackableWindow()
661
662         self.displayListing()
663         self.autoupdate = False
664         self.checkAutoUpdate()
665         hildon.hildon_gtk_window_set_progress_indicator(self.window, 0)
666
667
668     def button_markAll(self, button):
669         for key in self.listing.getListOfFeeds():
670             feed = self.listing.getFeed(key)
671             for index in range(feed.getNumberOfEntries()):
672                 feed.setEntryRead(index)
673         self.refreshList()
674
675     def button_export_clicked(self, button):
676         opml = ExportOpmlData(self.window, self.listing)
677         
678     def button_import_clicked(self, button):
679         opml = GetOpmlData(self.window)
680         feeds = opml.getData()
681         for (title, url) in feeds:
682             self.listing.addFeed(title, url)
683         self.displayListing()
684
685     def button_organize_clicked(self, button):
686         org = SortList(self.window, self.listing)
687         org.run()
688         org.destroy()
689         self.listing.saveConfig()
690         self.displayListing()
691         
692     def button_update_clicked(self, button, key):
693         if not type(self.downloadDialog).__name__=="DownloadBar":
694             self.downloadDialog = DownloadBar(self.window, self.listing, self.listing.getListOfFeeds(), self.config )
695             self.downloadDialog.connect("download-done", self.onDownloadsDone)
696             self.mainVbox.pack_end(self.downloadDialog, expand=False, fill=False)
697             self.mainVbox.show_all()
698         #self.displayListing()
699
700     def onDownloadsDone(self, *widget):
701         self.downloadDialog.destroy()
702         self.downloadDialog = False
703         #self.displayListing()
704         self.refreshList()
705
706     def button_preferences_clicked(self, button):
707         dialog = self.config.createDialog()
708         dialog.connect("destroy", self.prefsClosed)
709
710     def show_confirmation_note(self, parent, title):
711         note = hildon.Note("confirmation", parent, "Are you sure you want to delete " + title +"?")
712
713         retcode = gtk.Dialog.run(note)
714         note.destroy()
715         
716         if retcode == gtk.RESPONSE_OK:
717             return True
718         else:
719             return False
720         
721     def displayListing(self):
722         try:
723             self.mainVbox.remove(self.pannableListing)
724         except:
725             pass
726         self.vboxListing = gtk.VBox(False,10)
727         self.pannableListing = hildon.PannableArea()
728         self.pannableListing.add_with_viewport(self.vboxListing)
729
730         self.buttons = {}
731         list = self.listing.getListOfFeeds()[:]
732         list.reverse()
733         for key in list:
734             #button = gtk.Button(item)
735             button = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT,
736                               hildon.BUTTON_ARRANGEMENT_VERTICAL)
737             button.set_text(self.listing.getFeedTitle(key), self.listing.getFeedUpdateTime(key) + " / " 
738                             + str(self.listing.getFeedNumberOfUnreadItems(key)) + " Unread Items")
739             button.set_alignment(0,0,1,1)
740             button.connect("clicked", self.buttonFeedClicked, self, self.window, key)
741             self.vboxListing.pack_start(button, expand=False)
742             self.buttons[key] = button
743             
744         #if type(self.downloadDialog).__name__=="DownloadBar":
745         #    self.vboxListing.pack_start(self.downloadDialog)
746         self.mainVbox.pack_start(self.pannableListing)
747         self.window.show_all()
748
749     def refreshList(self):
750         for key in self.buttons.keys():
751             button = self.buttons[key]
752             button.set_text(self.listing.getFeedTitle(key), self.listing.getFeedUpdateTime(key) + " / " 
753                             + str(self.listing.getFeedNumberOfUnreadItems(key)) + " Unread Items")
754
755     def buttonFeedClicked(widget, button, self, window, key):
756         disp = DisplayFeed(self.listing, self.listing.getFeed(key), self.listing.getFeedTitle(key), key, self.config)
757         disp.connect("feed-closed", self.onFeedClosed)
758
759     def onFeedClosed(self, object, key):
760         #self.displayListing()
761         self.refreshList()
762         #self.buttons[key].set_text(self.listing.getFeedTitle(key), self.listing.getFeedUpdateTime(key) + " / " 
763         #                    + str(self.listing.getFeedNumberOfUnreadItems(key)) + " Unread Items")
764         #self.buttons[key].show()
765      
766     def run(self):
767         self.window.connect("destroy", gtk.main_quit)
768         gtk.main()
769         for key in self.listing.getListOfFeeds():
770             self.listing.getFeed(key).saveFeed(CONFIGDIR)
771         self.listing.saveConfig()
772
773     def prefsClosed(self, *widget):
774         self.orientation.set_mode(self.config.getOrientation())
775         self.checkAutoUpdate()
776
777     def checkAutoUpdate(self, *widget):
778         interval = int(self.config.getUpdateInterval()*3600000)
779         if self.config.isAutoUpdateEnabled():
780             if self.autoupdate == False:
781                 self.autoupdateId = gobject.timeout_add(interval, self.automaticUpdate)
782                 self.autoupdate = interval
783             elif not self.autoupdate == interval:
784                 # If auto-update is enabled, but not at the right frequency
785                 gobject.source_remove(self.autoupdateId)
786                 self.autoupdateId = gobject.timeout_add(interval, self.automaticUpdate)
787                 self.autoupdate = interval
788         else:
789             if not self.autoupdate == False:
790                 gobject.source_remove(self.autoupdateId)
791                 self.autoupdate = False
792
793     def automaticUpdate(self, *widget):
794         # Need to check for internet connection
795         # If no internet connection, try again in 10 minutes:
796         # gobject.timeout_add(int(5*3600000), self.automaticUpdate)
797         self.button_update_clicked(None, None)
798         return True
799     
800     def getStatus(self):
801         status = ""
802         for key in self.listing.getListOfFeeds():
803             if self.listing.getFeedNumberOfUnreadItems(key) > 0:
804                 status += self.listing.getFeedTitle(key) + ": \t" +  str(self.listing.getFeedNumberOfUnreadItems(key)) + " Unread Items\n"
805         if status == "":
806             status = "No unread items"
807         return status
808
809 if __name__ == "__main__":
810     gobject.signal_new("feed-closed", DisplayFeed, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,))
811     gobject.signal_new("article-closed", DisplayArticle, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,))
812     gobject.signal_new("article-next", DisplayArticle, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,))
813     gobject.signal_new("article-previous", DisplayArticle, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,))
814     gobject.signal_new("download-done", DownloadBar, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,))
815     gobject.threads_init()
816     if not isdir(CONFIGDIR):
817         try:
818             mkdir(CONFIGDIR)
819         except:
820             print "Error: Can't create configuration directory"
821             sys.exit(1)
822     app = FeedingIt()
823     dbusHandler = ServerObject(app)
824     app.run()