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