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