[#5946] Make swipe navigation previous article iterate over _generated list_. (njsf)
[feedingit] / src / FeedingIt.py
index 1b3f415..88122f6 100644 (file)
 #
 
 # ============================================================================
-# Name        : FeedingIt.py
-# Author      : Yves Marcoz
-# Version     : 0.5.0
-# Description : Simple RSS Reader
+__appname__ = 'FeedingIt'
+__author__  = 'Yves Marcoz'
+__version__ = '0.7.0'
+__description__ = 'A simple RSS Reader for Maemo 5'
 # ============================================================================
 
 import gtk
-import feedparser
+from pango import FontDescription
 import pango
 import hildon
 #import gtkhtml2
-try:
-    import webkit
-    has_webkit=True
-except:
-    import gtkhtml2
-    has_webkit=False
-import time
-import dbus
-import pickle
-from os.path import isfile, isdir
-from os import mkdir
-import sys   
-import urllib2
+#try:
+from webkit import WebView
+#    has_webkit=True
+#except:
+#    import gtkhtml2
+#    has_webkit=False
+from os.path import isfile, isdir, exists
+from os import mkdir, remove, stat
 import gobject
+from aboutdialog import HeAboutDialog
 from portrait import FremantleRotation
-import threading
-import thread
+from threading import Thread, activeCount
 from feedingitdbus import ServerObject
+from updatedbus import UpdateServerObject, get_lock
 from config import Config
+from cgi import escape
 
-from rss import *
+from rss import Listing
 from opml import GetOpmlData, ExportOpmlData
-   
-import socket
+
+from urllib2 import install_opener, build_opener
+
+from socket import setdefaulttimeout
 timeout = 5
-socket.setdefaulttimeout(timeout)
+setdefaulttimeout(timeout)
+del timeout
+
+import xml.sax
+
+LIST_ICON_SIZE = 32
+LIST_ICON_BORDER = 10
+
+USER_AGENT = 'Mozilla/5.0 (compatible; Maemo 5;) %s %s' % (__appname__, __version__)
+ABOUT_ICON = 'feedingit'
+ABOUT_COPYRIGHT = 'Copyright (c) 2010 %s' % __author__
+ABOUT_WEBSITE = 'http://feedingit.marcoz.org/'
+ABOUT_BUGTRACKER = 'https://garage.maemo.org/tracker/?group_id=1202'
+ABOUT_DONATE = None # TODO: Create a donation page + add its URL here
+
+color_style = gtk.rc_get_style_by_paths(gtk.settings_get_default() , 'GtkButton', 'osso-logical-colors', gtk.Button)
+unread_color = color_style.lookup_color('ActiveTextColor')
+read_color = color_style.lookup_color('DefaultTextColor')
+del color_style
 
 CONFIGDIR="/home/user/.feedingit/"
+LOCK = CONFIGDIR + "update.lock"
 
-class AddWidgetWizard(hildon.WizardDialog):
-    
-    def __init__(self, parent, urlIn, titleIn=None):
-        # Create a Notebook
-        self.notebook = gtk.Notebook()
+from re import sub
+from htmlentitydefs import name2codepoint
+
+COLUMN_ICON, COLUMN_MARKUP, COLUMN_KEY = range(3)
+
+FEED_COLUMN_MARKUP, FEED_COLUMN_KEY = range(2)
+
+import style
+
+MARKUP_TEMPLATE= '<span font_desc="%s" foreground="%s">%%s</span>'
+MARKUP_TEMPLATE_ENTRY_UNREAD = '<span font_desc="%s %%s" foreground="%s">%%s</span>'
+MARKUP_TEMPLATE_ENTRY = '<span font_desc="%s italic %%s" foreground="%s">%%s</span>'
+
+# Build the markup template for the Maemo 5 text style
+head_font = style.get_font_desc('SystemFont')
+sub_font = style.get_font_desc('SmallSystemFont')
+
+head_color = style.get_color('ButtonTextColor')
+sub_color = style.get_color('DefaultTextColor')
+active_color = style.get_color('ActiveTextColor')
+
+head = MARKUP_TEMPLATE % (head_font.to_string(), head_color.to_string())
+normal_sub = MARKUP_TEMPLATE % (sub_font.to_string(), sub_color.to_string())
+
+entry_head = MARKUP_TEMPLATE_ENTRY % (head_font.get_family(), head_color.to_string())
+entry_normal_sub = MARKUP_TEMPLATE_ENTRY % (sub_font.get_family(), sub_color.to_string())
+
+active_head = MARKUP_TEMPLATE % (head_font.to_string(), active_color.to_string())
+active_sub = MARKUP_TEMPLATE % (sub_font.to_string(), active_color.to_string())
+
+entry_active_head = MARKUP_TEMPLATE_ENTRY_UNREAD % (head_font.get_family(), active_color.to_string())
+entry_active_sub = MARKUP_TEMPLATE_ENTRY_UNREAD % (sub_font.get_family(), active_color.to_string())
+
+FEED_TEMPLATE = '\n'.join((head, normal_sub))
+FEED_TEMPLATE_UNREAD = '\n'.join((head, active_sub))
+
+ENTRY_TEMPLATE = entry_head
+ENTRY_TEMPLATE_UNREAD = entry_active_head
+
+##
+# Removes HTML or XML character references and entities from a text string.
+#
+# @param text The HTML (or XML) source text.
+# @return The plain text, as a Unicode string, if necessary.
+# http://effbot.org/zone/re-sub.htm#unescape-html
+def unescape(text):
+    def fixup(m):
+        text = m.group(0)
+        if text[:2] == "&#":
+            # character reference
+            try:
+                if text[:3] == "&#x":
+                    return unichr(int(text[3:-1], 16))
+                else:
+                    return unichr(int(text[2:-1]))
+            except ValueError:
+                pass
+        else:
+            # named entity
+            try:
+                text = unichr(name2codepoint[text[1:-1]])
+            except KeyError:
+                pass
+        return text # leave as is
+    return sub("&#?\w+;", fixup, text)
+
+
+class AddWidgetWizard(gtk.Dialog):
+    def __init__(self, parent, urlIn, titleIn=None, isEdit=False):
+        gtk.Dialog.__init__(self)
+        self.set_transient_for(parent)
+
+        if isEdit:
+            self.set_title('Edit RSS feed')
+        else:
+            self.set_title('Add new RSS feed')
+
+        if isEdit:
+            self.btn_add = self.add_button('Save', 2)
+        else:
+            self.btn_add = self.add_button('Add', 2)
+
+        self.set_default_response(2)
 
         self.nameEntry = hildon.Entry(gtk.HILDON_SIZE_AUTO)
-        self.nameEntry.set_placeholder("Enter Feed Name")
-        vbox = gtk.VBox(False,10)
-        label = gtk.Label("Enter Feed Name:")
-        vbox.pack_start(label)
-        vbox.pack_start(self.nameEntry)
+        self.nameEntry.set_placeholder('Feed name')
         if not titleIn == None:
             self.nameEntry.set_text(titleIn)
-        self.notebook.append_page(vbox, None)
-        
+            self.nameEntry.select_region(-1, -1)
+
         self.urlEntry = hildon.Entry(gtk.HILDON_SIZE_AUTO)
-        self.urlEntry.set_placeholder("Enter a URL")
+        self.urlEntry.set_placeholder('Feed URL')
         self.urlEntry.set_text(urlIn)
-        self.urlEntry.select_region(0,-1)
-        
-        vbox = gtk.VBox(False,10)
-        label = gtk.Label("Enter Feed URL:")
-        vbox.pack_start(label)
-        vbox.pack_start(self.urlEntry)
-        self.notebook.append_page(vbox, None)
-
-        labelEnd = gtk.Label("Success")
-        
-        self.notebook.append_page(labelEnd, None)      
-
-        hildon.WizardDialog.__init__(self, parent, "Add Feed", self.notebook)
-   
-        # Set a handler for "switch-page" signal
-        #self.notebook.connect("switch_page", self.on_page_switch, self)
-   
-        # Set a function to decide if user can go to next page
-        self.set_forward_page_func(self.some_page_func)
-   
+        self.urlEntry.select_region(-1, -1)
+        self.urlEntry.set_activates_default(True)
+
+        self.table = gtk.Table(2, 2, False)
+        self.table.set_col_spacings(5)
+        label = gtk.Label('Name:')
+        label.set_alignment(1., .5)
+        self.table.attach(label, 0, 1, 0, 1, gtk.FILL)
+        self.table.attach(self.nameEntry, 1, 2, 0, 1)
+        label = gtk.Label('URL:')
+        label.set_alignment(1., .5)
+        self.table.attach(label, 0, 1, 1, 2, gtk.FILL)
+        self.table.attach(self.urlEntry, 1, 2, 1, 2)
+        self.vbox.pack_start(self.table)
+
         self.show_all()
-        
+
     def getData(self):
         return (self.nameEntry.get_text(), self.urlEntry.get_text())
-        
-    def on_page_switch(self, notebook, page, num, dialog):
-        return True
-   
+
     def some_page_func(self, nb, current, userdata):
         # Validate data for 1st page
         if current == 0:
@@ -115,88 +199,63 @@ class AddWidgetWizard(hildon.WizardDialog):
             return False
         else:
             return True
-
-class GetImage(threading.Thread):
-    def __init__(self, url, stream):
-        threading.Thread.__init__(self)
-        self.url = url
-        self.stream = stream
-    
-    def run(self):
-        f = urllib2.urlopen(self.url)
-        data = f.read()
-        f.close()
-        self.stream.write(data)
-        self.stream.close()
-
-class ImageDownloader():
-    def __init__(self):
-        self.images = []
-        self.downloading = False
-        
-    def queueImage(self, url, stream):
-        self.images.append((url, stream))
-        if not self.downloading:
-            self.downloading = True
-            gobject.timeout_add(50, self.checkQueue)
-        
-    def checkQueue(self):
-        for i in range(4-threading.activeCount()):
-            if len(self.images) > 0:
-                (url, stream) = self.images.pop() 
-                GetImage(url, stream).start()
-        if len(self.images)>0:
-            gobject.timeout_add(200, self.checkQueue)
-        else:
-            self.downloading=False
-            
-    def stopAll(self):
-        self.images = []
         
-        
-class Download(threading.Thread):
+class Download(Thread):
     def __init__(self, listing, key, config):
-        threading.Thread.__init__(self)
+        Thread.__init__(self)
         self.listing = listing
         self.key = key
         self.config = config
         
     def run (self):
-        self.listing.updateFeed(self.key, self.config.getExpiry())
+        (use_proxy, proxy) = self.config.getProxy()
+        key_lock = get_lock(self.key)
+        if key_lock != None:
+            if use_proxy:
+                self.listing.updateFeed(self.key, self.config.getExpiry(), proxy=proxy, imageCache=self.config.getImageCache() )
+            else:
+                self.listing.updateFeed(self.key, self.config.getExpiry(), imageCache=self.config.getImageCache() )
+        del key_lock
 
         
 class DownloadBar(gtk.ProgressBar):
     def __init__(self, parent, listing, listOfKeys, config, single=False):
-        gtk.ProgressBar.__init__(self)
-        self.listOfKeys = listOfKeys[:]
-        self.listing = listing
-        self.total = len(self.listOfKeys)
-        self.config = config
-        self.current = 0
-        self.single = single
-        
-        if self.total>0:
-            #self.progress = gtk.ProgressBar()
-            #self.waitingWindow = hildon.Note("cancel", parent, "Downloading",
-            #                     progressbar=self.progress)
-            self.set_text("Updating...")
-            self.fraction = 0
-            self.set_fraction(self.fraction)
-            self.show_all()
-            # Create a timeout
-            self.timeout_handler_id = gobject.timeout_add(50, self.update_progress_bar)
-            #self.waitingWindow.show_all()
-            #response = self.waitingWindow.run()
-            #self.listOfKeys = []
-            #while threading.activeCount() > 1:
-                # Wait for current downloads to finish
-            #    time.sleep(0.1)
-            #self.waitingWindow.destroy()
+        
+        update_lock = get_lock("update_lock")
+        if update_lock != None:
+            gtk.ProgressBar.__init__(self)
+            self.listOfKeys = listOfKeys[:]
+            self.listing = listing
+            self.total = len(self.listOfKeys)
+            self.config = config
+            self.current = 0
+            self.single = single
+            (use_proxy, proxy) = self.config.getProxy()
+            if use_proxy:
+                opener = build_opener(proxy)
+            else:
+                opener = build_opener()
+
+            opener.addheaders = [('User-agent', USER_AGENT)]
+            install_opener(opener)
+
+            if self.total>0:
+                # In preparation for i18n/l10n
+                def N_(a, b, n):
+                    return (a if n == 1 else b)
+
+                self.set_text(N_('Updating %d feed', 'Updating %d feeds', self.total) % self.total)
+
+                self.fraction = 0
+                self.set_fraction(self.fraction)
+                self.show_all()
+                # Create a timeout
+                self.timeout_handler_id = gobject.timeout_add(50, self.update_progress_bar)
 
     def update_progress_bar(self):
         #self.progress_bar.pulse()
-        if threading.activeCount() < 4:
-            x = threading.activeCount() - 1
+        if activeCount() < 4:
+            x = activeCount() - 1
             k = len(self.listOfKeys)
             fin = self.total - k - x
             fraction = float(fin)/float(self.total) + float(x)/(self.total*2.)
@@ -206,53 +265,63 @@ class DownloadBar(gtk.ProgressBar):
             if len(self.listOfKeys)>0:
                 self.current = self.current+1
                 key = self.listOfKeys.pop()
-                if (not self.listing.getCurrentlyDisplayedFeed() == key) or (self.single == True):
+                #if self.single == True:
                     # Check if the feed is being displayed
-                    download = Download(self.listing, key, self.config)
-                    download.start()
+                download = Download(self.listing, key, self.config)
+                download.start()
                 return True
-            elif threading.activeCount() > 1:
+            elif activeCount() > 1:
                 return True
             else:
                 #self.waitingWindow.destroy()
                 #self.destroy()
+                try:
+                    del self.update_lock
+                except:
+                    pass
                 self.emit("download-done", "success")
                 return False 
         return True
     
     
-class SortList(gtk.Dialog):
-    def __init__(self, parent, listing):
-        gtk.Dialog.__init__(self, "Organizer",  parent)
+class SortList(hildon.StackableWindow):
+    def __init__(self, parent, listing, feedingit, after_closing):
+        hildon.StackableWindow.__init__(self)
+        self.set_transient_for(parent)
+        self.set_title('Subscriptions')
         self.listing = listing
-        
-        self.vbox2 = gtk.VBox(False, 10)
-        
-        button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
-        button.set_label("Move Up")
+        self.feedingit = feedingit
+        self.after_closing = after_closing
+        self.connect('destroy', lambda w: self.after_closing())
+        self.vbox2 = gtk.VBox(False, 2)
+
+        button = hildon.GtkButton(gtk.HILDON_SIZE_FINGER_HEIGHT)
+        button.set_image(gtk.image_new_from_icon_name('keyboard_move_up', gtk.ICON_SIZE_BUTTON))
         button.connect("clicked", self.buttonUp)
         self.vbox2.pack_start(button, expand=False, fill=False)
-        
-        button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
-        button.set_label("Move Down")
+
+        button = hildon.GtkButton(gtk.HILDON_SIZE_FINGER_HEIGHT)
+        button.set_image(gtk.image_new_from_icon_name('keyboard_move_down', gtk.ICON_SIZE_BUTTON))
         button.connect("clicked", self.buttonDown)
         self.vbox2.pack_start(button, expand=False, fill=False)
 
-        button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
-        button.set_label("Add Feed")
+        self.vbox2.pack_start(gtk.Label(), expand=True, fill=False)
+
+        button = hildon.GtkButton(gtk.HILDON_SIZE_FINGER_HEIGHT)
+        button.set_image(gtk.image_new_from_icon_name('general_add', gtk.ICON_SIZE_BUTTON))
         button.connect("clicked", self.buttonAdd)
         self.vbox2.pack_start(button, expand=False, fill=False)
 
-        button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
-        button.set_label("Edit Feed")
+        button = hildon.GtkButton(gtk.HILDON_SIZE_FINGER_HEIGHT)
+        button.set_image(gtk.image_new_from_icon_name('general_information', gtk.ICON_SIZE_BUTTON))
         button.connect("clicked", self.buttonEdit)
         self.vbox2.pack_start(button, expand=False, fill=False)
-        
-        button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
-        button.set_label("Delete")
+
+        button = hildon.GtkButton(gtk.HILDON_SIZE_FINGER_HEIGHT)
+        button.set_image(gtk.image_new_from_icon_name('general_delete', gtk.ICON_SIZE_BUTTON))
         button.connect("clicked", self.buttonDelete)
         self.vbox2.pack_start(button, expand=False, fill=False)
-        
+
         #button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
         #button.set_label("Done")
         #button.connect("clicked", self.buttonDone)
@@ -265,7 +334,20 @@ class SortList(gtk.Dialog):
         self.displayFeeds()
         self.hbox2.pack_end(self.vbox2, expand=False)
         self.set_default_size(-1, 600)
-        self.vbox.pack_start(self.hbox2)
+        self.add(self.hbox2)
+
+        menu = hildon.AppMenu()
+        button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
+        button.set_label("Import from OPML")
+        button.connect("clicked", self.feedingit.button_import_clicked)
+        menu.append(button)
+
+        button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
+        button.set_label("Export to OPML")
+        button.connect("clicked", self.feedingit.button_export_clicked)
+        menu.append(button)
+        self.set_app_menu(menu)
+        menu.show_all()
         
         self.show_all()
         #self.connect("destroy", self.buttonDone)
@@ -285,8 +367,8 @@ class SortList(gtk.Dialog):
         #self.show_all()
 
     def refreshList(self, selected=None, offset=0):
-        rect = self.treeview.get_visible_rect()
-        y = rect.y+rect.height
+        #rect = self.treeview.get_visible_rect()
+        #y = rect.y+rect.height
         self.treestore = gtk.ListStore(gobject.TYPE_STRING, gobject.TYPE_STRING)
         for key in self.listing.getListOfFeeds():
             item = self.treestore.append([self.listing.getFeedTitle(key), key])
@@ -331,21 +413,32 @@ class SortList(gtk.Dialog):
 
     def buttonDelete(self, button):
         key = self.getSelectedItem()
-        if not key == None:
+
+        message = 'Really remove this feed and its entries?'
+        dlg = hildon.hildon_note_new_confirmation(self, message)
+        response = dlg.run()
+        dlg.destroy()
+        if response == gtk.RESPONSE_OK:
             self.listing.removeFeed(key)
-        self.refreshList()
+            self.refreshList()
 
     def buttonEdit(self, button):
         key = self.getSelectedItem()
-        if not key == None:
-            wizard = AddWidgetWizard(self, self.listing.getFeedUrl(key), self.listing.getFeedTitle(key))
+
+        if key == 'ArchivedArticles':
+            message = 'Cannot edit the archived articles feed.'
+            hildon.hildon_banner_show_information(self, '', message)
+            return
+
+        if key is not None:
+            wizard = AddWidgetWizard(self, self.listing.getFeedUrl(key), self.listing.getFeedTitle(key), True)
             ret = wizard.run()
             if ret == 2:
                 (title, url) = wizard.getData()
                 if (not title == '') and (not url == ''):
                     self.listing.editFeed(key, title, url)
+                    self.refreshList()
             wizard.destroy()
-        self.refreshList()
 
     def buttonDone(self, *args):
         self.destroy()
@@ -362,77 +455,106 @@ class SortList(gtk.Dialog):
                
 
 class DisplayArticle(hildon.StackableWindow):
-    def __init__(self, title, text, link, index, key, listing, config):
+    def __init__(self, feed, id, key, config, listing):
         hildon.StackableWindow.__init__(self)
-        self.imageDownloader = ImageDownloader()
+        #self.imageDownloader = ImageDownloader()
+        self.feed = feed
         self.listing=listing
         self.key = key
-        self.index = index
-        self.text = text
-        self.link = link
-        self.set_title(title)
+        self.id = id
+        #self.set_title(feed.getTitle(id))
+        self.set_title(self.listing.getFeedTitle(key))
         self.config = config
-        self.images = []
+        self.set_for_removal = False
         
         # Init the article display
-        if self.config.getWebkitSupport():
-            self.view = webkit.WebView()
+        #if self.config.getWebkitSupport():
+        self.view = WebView()
             #self.view.set_editable(False)
-        else:
-            self.view = gtkhtml2.View()
-            self.document = gtkhtml2.Document()
-            self.view.set_document(self.document)
-            self.document.connect("link_clicked", self._signal_link_clicked)
+        #else:
+        #    import gtkhtml2
+        #    self.view = gtkhtml2.View()
+        #    self.document = gtkhtml2.Document()
+        #    self.view.set_document(self.document)
+        #    self.document.connect("link_clicked", self._signal_link_clicked)
         self.pannable_article = hildon.PannableArea()
         self.pannable_article.add(self.view)
         #self.pannable_article.set_property("mov-mode", hildon.MOVEMENT_MODE_BOTH)
         #self.gestureId = self.pannable_article.connect('horizontal-movement', self.gesture)
 
-        if self.config.getWebkitSupport():
-            self.view.load_string(self.text, "text/html", "utf-8", self.link)
-            self.view.set_zoom_level(float(config.getArtFontSize())/10.)
+        #if self.config.getWebkitSupport():
+        contentLink = self.feed.getContentLink(self.id)
+        self.feed.setEntryRead(self.id)
+        #if key=="ArchivedArticles":
+        self.loadedArticle = False
+        if contentLink.startswith("/home/user/"):
+            self.view.open("file://%s" % contentLink)
+            self.currentUrl = self.feed.getExternalLink(self.id)
         else:
-            if not key == "1295627ef630df9d239abeb0ba631c3f":
-                # Do not download images if the feed is "Archived Articles"
-                self.document.connect("request-url", self._signal_request_url)
-            
-            self.document.clear()
-            self.document.open_stream("text/html")
-            self.document.write_stream(self.text)
-            self.document.close_stream()
+            self.view.load_html_string('This article has not been downloaded yet. Click <a href="%s">here</a> to view online.' % contentLink, contentLink)
+            self.currentUrl = "%s" % contentLink
+        self.view.connect("motion-notify-event", lambda w,ev: True)
+        self.view.connect('load-started', self.load_started)
+        self.view.connect('load-finished', self.load_finished)
+
+        self.view.set_zoom_level(float(config.getArtFontSize())/10.)
         
         menu = hildon.AppMenu()
         # Create a button and add it to the menu
         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
-        button.set_label("Allow Horizontal Scrolling")
+        button.set_label("Allow horizontal scrolling")
         button.connect("clicked", self.horiz_scrolling_button)
         menu.append(button)
         
         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
-        button.set_label("Open in Browser")
-        button.connect("clicked", self._signal_link_clicked, self.link)
+        button.set_label("Open in browser")
+        button.connect("clicked", self.open_in_browser)
         menu.append(button)
         
-        button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
-        button.set_label("Add to Archived Articles")
-        button.connect("clicked", self.archive_button)
+        if key == "ArchivedArticles":
+            button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
+            button.set_label("Remove from archived articles")
+            button.connect("clicked", self.remove_archive_button)
+        else:
+            button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
+            button.set_label("Add to archived articles")
+            button.connect("clicked", self.archive_button)
         menu.append(button)
         
         self.set_app_menu(menu)
         menu.show_all()
         
-        #self.event_box = gtk.EventBox()
-        #self.event_box.add(self.pannable_article)
         self.add(self.pannable_article)
         
-        
         self.pannable_article.show_all()
 
         self.destroyId = self.connect("destroy", self.destroyWindow)
         
+        #self.view.connect('navigation-policy-decision-requested', self.navigation_policy_decision)
+        ## Still using an old version of WebKit, so using navigation-requested signal
+        self.view.connect('navigation-requested', self.navigation_requested)
+        
         self.view.connect("button_press_event", self.button_pressed)
-        self.view.connect("button_release_event", self.button_released)
-        #self.timeout_handler_id = gobject.timeout_add(300, self.reloadArticle)
+        self.gestureId = self.view.connect("button_release_event", self.button_released)
+
+    #def navigation_policy_decision(self, wv, fr, req, action, decision):
+    def navigation_requested(self, wv, fr, req):
+        if self.config.getOpenInExternalBrowser():
+            self.open_in_browser(None, req.get_uri())
+            return True
+        else:
+            return False
+
+    def load_started(self, *widget):
+        hildon.hildon_gtk_window_set_progress_indicator(self, 1)
+        
+    def load_finished(self, *widget):
+        hildon.hildon_gtk_window_set_progress_indicator(self, 0)
+        frame = self.view.get_main_frame()
+        if self.loadedArticle:
+            self.currentUrl = frame.get_uri()
+        else:
+            self.loadedArticle = True
 
     def button_pressed(self, window, event):
         #print event.x, event.y
@@ -442,26 +564,19 @@ class DisplayArticle(hildon.StackableWindow):
         x = self.coords[0] - event.x
         y = self.coords[1] - event.y
         
-        if (abs(y) < 20):
-            if (x > 30):
-                self.emit("article-previous", self.index)
-            elif (x<-30):
-                self.emit("article-next", self.index)   
-        #print x, y
-        #print "Released"
-
-    #def gesture(self, widget, direction, startx, starty):
-    #    if (direction == 3):
-    #        self.emit("article-next", self.index)
-    #    if (direction == 2):
-    #        self.emit("article-previous", self.index)
-        #print startx, starty
-        #self.timeout_handler_id = gobject.timeout_add(200, self.destroyWindow)
+        if (2*abs(y) < abs(x)):
+            if (x > 15):
+                self.emit("article-previous", self.id)
+            elif (x<-15):
+                self.emit("article-next", self.id)   
 
     def destroyWindow(self, *args):
         self.disconnect(self.destroyId)
-        self.emit("article-closed", self.index)
-        self.imageDownloader.stopAll()
+        if self.set_for_removal:
+            self.emit("article-deleted", self.id)
+        else:
+            self.emit("article-closed", self.id)
+        #self.imageDownloader.stopAll()
         self.destroy()
         
     def horiz_scrolling_button(self, *widget):
@@ -470,108 +585,175 @@ class DisplayArticle(hildon.StackableWindow):
         
     def archive_button(self, *widget):
         # Call the listing.addArchivedArticle
-        self.listing.addArchivedArticle(self.key, self.index)
-        
-    #def reloadArticle(self, *widget):
-    #    if threading.activeCount() > 1:
-            # Image thread are still running, come back in a bit
-    #        return True
-    #    else:
-    #        for (stream, imageThread) in self.images:
-    #            imageThread.join()
-    #            stream.write(imageThread.data)
-    #            stream.close()
-    #        return False
-    #    self.show_all()
-
-    def _signal_link_clicked(self, object, link):
-        bus = dbus.SystemBus()
+        self.listing.addArchivedArticle(self.key, self.id)
+        
+    def remove_archive_button(self, *widget):
+        self.set_for_removal = True
+
+    def open_in_browser(self, object, link=None):
+        import dbus
+        bus = dbus.SessionBus()
         proxy = bus.get_object("com.nokia.osso_browser", "/com/nokia/osso_browser/request")
         iface = dbus.Interface(proxy, 'com.nokia.osso_browser')
-        iface.load_url(link)
-
-    def _signal_request_url(self, object, url, stream):
-        #print url
-        self.imageDownloader.queueImage(url, stream)
-        #imageThread = GetImage(url)
-        #imageThread.start()
-        #self.images.append((stream, imageThread))
-
+        if link == None:
+            iface.open_new_window(self.currentUrl)
+        else:
+            iface.open_new_window(link)
 
 class DisplayFeed(hildon.StackableWindow):
-    def __init__(self, listing, feed, title, key, config):
+    def __init__(self, listing, feed, title, key, config, updateDbusHandler):
         hildon.StackableWindow.__init__(self)
         self.listing = listing
         self.feed = feed
         self.feedTitle = title
         self.set_title(title)
         self.key=key
+        self.current = list()
         self.config = config
+        self.updateDbusHandler = updateDbusHandler
         
         self.downloadDialog = False
         
-        self.listing.setCurrentlyDisplayedFeed(self.key)
+        #self.listing.setCurrentlyDisplayedFeed(self.key)
         
         self.disp = False
         
         menu = hildon.AppMenu()
         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
-        button.set_label("Update Feed")
+        button.set_label("Update feed")
         button.connect("clicked", self.button_update_clicked)
         menu.append(button)
         
         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
-        button.set_label("Mark All As Read")
+        button.set_label("Mark all as read")
         button.connect("clicked", self.buttonReadAllClicked)
         menu.append(button)
+        
+        if key=="ArchivedArticles":
+            button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
+            button.set_label("Delete read articles")
+            button.connect("clicked", self.buttonPurgeArticles)
+            menu.append(button)
+        
         self.set_app_menu(menu)
         menu.show_all()
         
         self.displayFeed()
         
+        self.connect('configure-event', self.on_configure_event)
         self.connect("destroy", self.destroyWindow)
-        
+
+    def on_configure_event(self, window, event):
+        if getattr(self, 'markup_renderer', None) is None:
+            return
+
+        # Fix up the column width for wrapping the text when the window is
+        # resized (i.e. orientation changed)
+        self.markup_renderer.set_property('wrap-width', event.width-20)  
+        it = self.feedItems.get_iter_first()
+        while it is not None:
+            markup = self.feedItems.get_value(it, FEED_COLUMN_MARKUP)
+            self.feedItems.set_value(it, FEED_COLUMN_MARKUP, markup)
+            it = self.feedItems.iter_next(it)
+
     def destroyWindow(self, *args):
+        #self.feed.saveUnread(CONFIGDIR)
+        gobject.idle_add(self.feed.saveUnread, CONFIGDIR)
+        self.listing.updateUnread(self.key, self.feed.getNumberOfUnreadItems())
         self.emit("feed-closed", self.key)
         self.destroy()
         #gobject.idle_add(self.feed.saveFeed, CONFIGDIR)
-        self.listing.closeCurrentlyDisplayedFeed()
+        #self.listing.closeCurrentlyDisplayedFeed()
+
+    def fix_title(self, title):
+        return escape(unescape(title).replace("<em>","").replace("</em>","").replace("<nobr>","").replace("</nobr>","").replace("<wbr>",""))
 
     def displayFeed(self):
-        self.vboxFeed = gtk.VBox(False, 10)
         self.pannableFeed = hildon.PannableArea()
-        self.pannableFeed.add_with_viewport(self.vboxFeed)
-        self.pannableFeed.set_property("mov-mode", hildon.MOVEMENT_MODE_BOTH)
-        self.buttons = []
-        for index in range(self.feed.getNumberOfEntries()):
-            button = gtk.Button(self.feed.getTitle(index))
-            button.set_alignment(0,0)
-            label = button.child
-            if self.feed.isEntryRead(index):
-                #label.modify_font(pango.FontDescription("sans 16"))
-                label.modify_font(pango.FontDescription(self.config.getReadFont()))
-            else:
-                #print self.listing.getFont() + " bold"
-                label.modify_font(pango.FontDescription(self.config.getUnreadFont()))
-                #label.modify_font(pango.FontDescription("sans bold 23"))
-                #"sans bold 16"
-            label.set_line_wrap(True)
-            
-            label.set_size_request(self.get_size()[0]-50, -1)
-            button.connect("clicked", self.button_clicked, index)
-            self.buttons.append(button)
-            
-            self.vboxFeed.pack_start(button, expand=False)           
-            index=index+1
+
+        self.pannableFeed.set_property('hscrollbar-policy', gtk.POLICY_NEVER)
+
+        self.feedItems = gtk.ListStore(str, str)
+        #self.feedList = gtk.TreeView(self.feedItems)
+        self.feedList = hildon.GtkTreeView(gtk.HILDON_UI_MODE_NORMAL)
+        selection = self.feedList.get_selection()
+        selection.set_mode(gtk.SELECTION_NONE)
+        #selection.connect("changed", lambda w: True)
+        
+        self.feedList.set_model(self.feedItems)
+        self.feedList.set_grid_lines(gtk.TREE_VIEW_GRID_LINES_HORIZONTAL)
+
+        
+        self.feedList.set_hover_selection(False)
+        #self.feedList.set_property('enable-grid-lines', True)
+        #self.feedList.set_property('hildon-mode', 1)
+        #self.pannableFeed.connect("motion-notify-event", lambda w,ev: True)
+        
+        #self.feedList.connect('row-activated', self.on_feedList_row_activated)
+
+        vbox= gtk.VBox(False, 10)
+        vbox.pack_start(self.feedList)
+        
+        self.pannableFeed.add_with_viewport(vbox)
+
+        self.markup_renderer = gtk.CellRendererText()
+        self.markup_renderer.set_property('wrap-mode', pango.WRAP_WORD_CHAR)
+        self.markup_renderer.set_property('background', "#333333")
+        (width, height) = self.get_size()
+        self.markup_renderer.set_property('wrap-width', width-20)
+        self.markup_renderer.set_property('ypad', 5)
+        self.markup_renderer.set_property('xpad', 5)
+        markup_column = gtk.TreeViewColumn('', self.markup_renderer, \
+                markup=FEED_COLUMN_MARKUP)
+        self.feedList.append_column(markup_column)
+
+        #self.pannableFeed.set_property("mov-mode", hildon.MOVEMENT_MODE_BOTH)
+        hideReadArticles = self.config.getHideReadArticles()
+        hasArticle = False
+        self.current = list()
+        for id in self.feed.getIds():
+            isRead = False
+            try:
+                isRead = self.feed.isEntryRead(id)
+            except:
+                pass
+            if not ( isRead and hideReadArticles ):
+                title = self.fix_title(self.feed.getTitle(id))
+                self.current.append(id)
+                if isRead:
+                    markup = ENTRY_TEMPLATE % (self.config.getFontSize(), title)
+                else:
+                    markup = ENTRY_TEMPLATE_UNREAD % (self.config.getFontSize(), title)
+    
+                self.feedItems.append((markup, id))
+                hasArticle = True
+        if hasArticle:
+            self.feedList.connect('hildon-row-tapped', self.on_feedList_row_activated)
+        else:
+            markup = ENTRY_TEMPLATE % (self.config.getFontSize(), "No Articles To Display")
+            self.feedItems.append((markup, ""))
 
         self.add(self.pannableFeed)
         self.show_all()
-        
+
     def clear(self):
-        self.remove(self.pannableFeed)
+        self.pannableFeed.destroy()
+        #self.remove(self.pannableFeed)
+
+    def on_feedList_row_activated(self, treeview, path): #, column):
+        selection = self.feedList.get_selection()
+        selection.set_mode(gtk.SELECTION_SINGLE)
+        self.feedList.get_selection().select_path(path)
+        model = treeview.get_model()
+        iter = model.get_iter(path)
+        key = model.get_value(iter, FEED_COLUMN_KEY)
+        # Emulate legacy "button_clicked" call via treeview
+        gobject.idle_add(self.button_clicked, treeview, key)
+        #return True
 
     def button_clicked(self, button, index, previous=False, next=False):
-        newDisp = DisplayArticle(self.feedTitle, self.feed.getArticle(index), self.feed.getLink(index), index, self.key, self.listing, self.config)
+        #newDisp = DisplayArticle(self.feedTitle, self.feed.getArticle(index), self.feed.getLink(index), index, self.key, self.listing, self.config)
+        newDisp = DisplayArticle(self.feed, index, self.key, self.config, self.listing)
         stack = hildon.WindowStack.get_default()
         if previous:
             tmp = stack.peek()
@@ -580,53 +762,70 @@ class DisplayFeed(hildon.StackableWindow):
             gobject.timeout_add(200, self.destroyArticle, tmp)
             #print "previous"
             self.disp = newDisp
-            
-            #stack.push(tmp)
-            #if not self.disp == False:
-            #   self.disp.destroyWindow()
         elif next:
-            #print type(self.disp).__name__
-
-                #self.disp.destroyWindow()
-                #stack.pop_and_push(1,newDisp)
-            #else:
-            #    stack.push(newDisp)
-            #self.disp = newDisp
             newDisp.show_all()
             if type(self.disp).__name__ == "DisplayArticle":
                 gobject.timeout_add(200, self.destroyArticle, self.disp)
             self.disp = newDisp
-            #self.disp.show_all()
-            #if not self.disp == False:
-            #    self.disp.destroyWindow()
         else:
             self.disp = newDisp
             self.disp.show_all()
         
         self.ids = []
+        if self.key == "ArchivedArticles":
+            self.ids.append(self.disp.connect("article-deleted", self.onArticleDeleted))
         self.ids.append(self.disp.connect("article-closed", self.onArticleClosed))
         self.ids.append(self.disp.connect("article-next", self.nextArticle))
         self.ids.append(self.disp.connect("article-previous", self.previousArticle))
 
+    def buttonPurgeArticles(self, *widget):
+        self.clear()
+        self.feed.purgeReadArticles()
+        self.feed.saveUnread(CONFIGDIR)
+        self.feed.saveFeed(CONFIGDIR)
+        self.displayFeed()
+
     def destroyArticle(self, handle):
         handle.destroyWindow()
 
+    def mark_item_read(self, key):
+        it = self.feedItems.get_iter_first()
+        while it is not None:
+            k = self.feedItems.get_value(it, FEED_COLUMN_KEY)
+            if k == key:
+                title = self.fix_title(self.feed.getTitle(key))
+                markup = ENTRY_TEMPLATE % (self.config.getFontSize(), title)
+                self.feedItems.set_value(it, FEED_COLUMN_MARKUP, markup)
+                break
+            it = self.feedItems.iter_next(it)
+
     def nextArticle(self, object, index):
-        label = self.buttons[index].child
-        label.modify_font(pango.FontDescription(self.config.getReadFont()))
-        index = (index+1) % self.feed.getNumberOfEntries()
-        self.button_clicked(object, index, next=True)
+        self.mark_item_read(index)
+        id = self.feed.getNextId(index)
+        while id not in self.current and id != index:
+            id = self.feed.getNextId(id)
+        if id != index:
+            self.button_clicked(object, id, next=True)
 
     def previousArticle(self, object, index):
-        label = self.buttons[index].child
-        label.modify_font(pango.FontDescription(self.config.getReadFont()))
-        index = (index-1) % self.feed.getNumberOfEntries()
-        self.button_clicked(object, index, previous=True)
+        self.mark_item_read(index)
+        id = self.feed.getPreviousId(index)
+        while id not in self.current and id != index:
+            id = self.feed.getPreviousId(id)
+        if id != index:
+            self.button_clicked(object, id, previous=True)
 
     def onArticleClosed(self, object, index):
-        label = self.buttons[index].child
-        label.modify_font(pango.FontDescription(self.config.getReadFont()))
-        self.buttons[index].show()
+        selection = self.feedList.get_selection()
+        selection.set_mode(gtk.SELECTION_NONE)
+        self.mark_item_read(index)
+
+    def onArticleDeleted(self, object, index):
+        self.clear()
+        self.feed.removeArticle(index)
+        self.feed.saveUnread(CONFIGDIR)
+        self.feed.saveFeed(CONFIGDIR)
+        self.displayFeed()
 
     def button_update_clicked(self, button):
         #bar = DownloadBar(self, self.listing, [self.key,], self.config ) 
@@ -641,70 +840,91 @@ class DisplayFeed(hildon.StackableWindow):
             
     def onDownloadsDone(self, *widget):
         self.vbox.destroy()
+        self.feed = self.listing.getFeed(self.key)
         self.displayFeed()
-        #self.feed.updateFeed()
-    #    self.clear()
-    #    self.displayFeed()
+        self.updateDbusHandler.ArticleCountUpdated()
         
     def buttonReadAllClicked(self, button):
-        for index in range(self.feed.getNumberOfEntries()):
+        for index in self.feed.getIds():
             self.feed.setEntryRead(index)
-            label = self.buttons[index].child
-            label.modify_font(pango.FontDescription(self.config.getReadFont()))
-            self.buttons[index].show()
+            self.mark_item_read(index)
 
 
 class FeedingIt:
     def __init__(self):
         # Init the windows
         self.window = hildon.StackableWindow()
-        self.window.set_title("FeedingIt")
+        self.window.set_title(__appname__)
         hildon.hildon_gtk_window_set_progress_indicator(self.window, 1)
         self.mainVbox = gtk.VBox(False,10)
-        self.pannableListing = gtk.Label("Loading...")
-        self.mainVbox.pack_start(self.pannableListing)
+        
+        self.introLabel = gtk.Label("Loading...")
+
+        
+        self.mainVbox.pack_start(self.introLabel)
+
         self.window.add(self.mainVbox)
         self.window.show_all()
-        self.config = Config(self.window, CONFIGDIR+"config.ini", has_webkit)
+        self.config = Config(self.window, CONFIGDIR+"config.ini")
         gobject.idle_add(self.createWindow)
         
     def createWindow(self):
+        self.app_lock = get_lock("app_lock")
+        if self.app_lock == None:
+            try:
+                self.stopButton.set_sensitive(True)
+            except:
+                self.stopButton = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
+                self.stopButton.set_text("Stop update","")
+                self.stopButton.connect("clicked", self.stop_running_update)
+                self.mainVbox.pack_end(self.stopButton, expand=False, fill=False)
+                self.window.show_all()
+            self.introLabel.set_label("Update in progress, please wait.")
+            gobject.timeout_add_seconds(3, self.createWindow)
+            return False
+        try:
+            self.stopButton.destroy()
+        except:
+            pass
         self.listing = Listing(CONFIGDIR)
         
         self.downloadDialog = False
-        self.orientation = FremantleRotation("FeedingIt", main_window=self.window)
-        self.orientation.set_mode(self.config.getOrientation())
+        try:
+            self.orientation = FremantleRotation(__appname__, main_window=self.window, app=self)
+            self.orientation.set_mode(self.config.getOrientation())
+        except:
+            print "Could not start rotation manager"
         
         menu = hildon.AppMenu()
         # Create a button and add it to the menu
         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
-        button.set_label("Update All Feeds")
+        button.set_label("Update feeds")
         button.connect("clicked", self.button_update_clicked, "All")
         menu.append(button)
         
         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
-        button.set_label("Mark All As Read")
+        button.set_label("Mark all as read")
         button.connect("clicked", self.button_markAll)
         menu.append(button)
-        
+
         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
-        button.set_label("Organize Feeds")
+        button.set_label("Add new feed")
+        button.connect("clicked", lambda b: self.addFeed())
+        menu.append(button)
+
+        button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
+        button.set_label("Manage subscriptions")
         button.connect("clicked", self.button_organize_clicked)
         menu.append(button)
 
         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
-        button.set_label("Preferences")
+        button.set_label("Settings")
         button.connect("clicked", self.button_preferences_clicked)
         menu.append(button)
        
         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
-        button.set_label("Import Feeds")
-        button.connect("clicked", self.button_import_clicked)
-        menu.append(button)
-        
-        button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
-        button.set_label("Export Feeds")
-        button.connect("clicked", self.button_export_clicked)
+        button.set_label("About")
+        button.connect("clicked", self.button_about_clicked)
         menu.append(button)
         
         self.window.set_app_menu(menu)
@@ -712,18 +932,65 @@ class FeedingIt:
         
         #self.feedWindow = hildon.StackableWindow()
         #self.articleWindow = hildon.StackableWindow()
+        self.introLabel.destroy()
+        self.pannableListing = hildon.PannableArea()
+        self.feedItems = gtk.ListStore(gtk.gdk.Pixbuf, str, str)
+        self.feedList = gtk.TreeView(self.feedItems)
+        self.feedList.connect('row-activated', self.on_feedList_row_activated)
+        self.pannableListing.add(self.feedList)
+
+        icon_renderer = gtk.CellRendererPixbuf()
+        icon_renderer.set_property('width', LIST_ICON_SIZE + 2*LIST_ICON_BORDER)
+        icon_column = gtk.TreeViewColumn('', icon_renderer, \
+                pixbuf=COLUMN_ICON)
+        self.feedList.append_column(icon_column)
+
+        markup_renderer = gtk.CellRendererText()
+        markup_column = gtk.TreeViewColumn('', markup_renderer, \
+                markup=COLUMN_MARKUP)
+        self.feedList.append_column(markup_column)
+        self.mainVbox.pack_start(self.pannableListing)
+        self.mainVbox.show_all()
 
         self.displayListing()
         self.autoupdate = False
         self.checkAutoUpdate()
         hildon.hildon_gtk_window_set_progress_indicator(self.window, 0)
+        gobject.idle_add(self.enableDbus)
+        
+    def stop_running_update(self, button):
+        self.stopButton.set_sensitive(False)
+        import dbus
+        bus=dbus.SessionBus()
+        remote_object = bus.get_object("org.marcoz.feedingit", # Connection name
+                               "/org/marcoz/feedingit/update" # Object's path
+                              )
+        iface = dbus.Interface(remote_object, 'org.marcoz.feedingit')
+        iface.StopUpdate()
+    
+    def enableDbus(self):
+        self.dbusHandler = ServerObject(self)
+        self.updateDbusHandler = UpdateServerObject(self)
 
     def button_markAll(self, button):
         for key in self.listing.getListOfFeeds():
             feed = self.listing.getFeed(key)
-            for index in range(feed.getNumberOfEntries()):
-                feed.setEntryRead(index)
-        self.refreshList()
+            for id in feed.getIds():
+                feed.setEntryRead(id)
+            feed.saveUnread(CONFIGDIR)
+            self.listing.updateUnread(key, feed.getNumberOfUnreadItems())
+        self.displayListing()
+
+    def button_about_clicked(self, button):
+        HeAboutDialog.present(self.window, \
+                __appname__, \
+                ABOUT_ICON, \
+                __version__, \
+                __description__, \
+                ABOUT_COPYRIGHT, \
+                ABOUT_WEBSITE, \
+                ABOUT_BUGTRACKER, \
+                ABOUT_DONATE)
 
     def button_export_clicked(self, button):
         opml = ExportOpmlData(self.window, self.listing)
@@ -746,14 +1013,14 @@ class FeedingIt:
         self.displayListing()
 
     def button_organize_clicked(self, button):
-        org = SortList(self.window, self.listing)
-        org.run()
-        org.destroy()
-        self.listing.saveConfig()
-        self.displayListing()
-        
+        def after_closing():
+            self.listing.saveConfig()
+            self.displayListing()
+        SortList(self.window, self.listing, self, after_closing)
+
     def button_update_clicked(self, button, key):
         if not type(self.downloadDialog).__name__=="DownloadBar":
+            self.updateDbusHandler.UpdateStarted()
             self.downloadDialog = DownloadBar(self.window, self.listing, self.listing.getListOfFeeds(), self.config )
             self.downloadDialog.connect("download-done", self.onDownloadsDone)
             self.mainVbox.pack_end(self.downloadDialog, expand=False, fill=False)
@@ -763,8 +1030,9 @@ class FeedingIt:
     def onDownloadsDone(self, *widget):
         self.downloadDialog.destroy()
         self.downloadDialog = False
-        #self.displayListing()
-        self.refreshList()
+        self.displayListing()
+        self.updateDbusHandler.UpdateFinished()
+        self.updateDbusHandler.ArticleCountUpdated()
 
     def button_preferences_clicked(self, button):
         dialog = self.config.createDialog()
@@ -782,63 +1050,97 @@ class FeedingIt:
             return False
         
     def displayListing(self):
-        try:
-            self.mainVbox.remove(self.pannableListing)
-        except:
-            pass
-        self.vboxListing = gtk.VBox(False,10)
-        self.pannableListing = hildon.PannableArea()
-        self.pannableListing.add_with_viewport(self.vboxListing)
-
-        self.buttons = {}
-        list = self.listing.getListOfFeeds()[:]
-        #list.reverse()
-        for key in list:
-            #button = gtk.Button(item)
-            button = hildon.Button(gtk.HILDON_SIZE_AUTO_WIDTH | gtk.HILDON_SIZE_FINGER_HEIGHT,
-                              hildon.BUTTON_ARRANGEMENT_VERTICAL)
-            button.set_text(self.listing.getFeedTitle(key), self.listing.getFeedUpdateTime(key) + " / " 
-                            + str(self.listing.getFeedNumberOfUnreadItems(key)) + " Unread Items")
-            button.set_alignment(0,0,1,1)
-            button.connect("clicked", self.buttonFeedClicked, self, self.window, key)
-            self.vboxListing.pack_start(button, expand=False)
-            self.buttons[key] = button
-            
-        #if type(self.downloadDialog).__name__=="DownloadBar":
-        #    self.vboxListing.pack_start(self.downloadDialog)
-        self.mainVbox.pack_start(self.pannableListing)
-        self.window.show_all()
+        icon_theme = gtk.icon_theme_get_default()
+        default_pixbuf = icon_theme.load_icon(ABOUT_ICON, LIST_ICON_SIZE, \
+                gtk.ICON_LOOKUP_USE_BUILTIN)
 
-    def refreshList(self):
+        self.feedItems.clear()
+        feedInfo = {}
+        count = 0
         for key in self.listing.getListOfFeeds():
-            if self.buttons.has_key(key):
-                button = self.buttons[key]
-                button.set_text(self.listing.getFeedTitle(key), self.listing.getFeedUpdateTime(key) + " / " 
-                            + str(self.listing.getFeedNumberOfUnreadItems(key)) + " Unread Items")
+            unreadItems = self.listing.getFeedNumberOfUnreadItems(key)
+            if unreadItems > 0 or not self.config.getHideReadFeeds():
+                count=count+1
+                title = self.listing.getFeedTitle(key)
+                updateTime = self.listing.getFeedUpdateTime(key)
+                updateStamp = self.listing.getFeedUpdateStamp(key)
+                subtitle = '%s / %d unread items' % (updateTime, unreadItems)
+                feedInfo[key] = [count, unreadItems, updateStamp, title, subtitle, updateTime];
+
+        order = self.config.getFeedSortOrder();
+        if   order == "Most unread":
+            keyorder = sorted(feedInfo, key = lambda k: feedInfo[k][1], reverse=True)
+        elif order == "Least unread":
+            keyorder = sorted(feedInfo, key = lambda k: feedInfo[k][1])
+        elif order == "Most recent":
+            keyorder = sorted(feedInfo, key = lambda k: feedInfo[k][2], reverse=True)
+        elif order == "Least recent":
+            keyorder = sorted(feedInfo, key = lambda k: feedInfo[k][2])
+        else: # order == "Manual" or invalid value...
+            keyorder = sorted(feedInfo, key = lambda k: feedInfo[k][0])
+        
+        for key in keyorder:
+            unreadItems = feedInfo[key][1]
+            title = xml.sax.saxutils.escape(feedInfo[key][3])
+            subtitle = feedInfo[key][4]
+            updateTime = feedInfo[key][5]
+            if unreadItems:
+                markup = FEED_TEMPLATE_UNREAD % (title, subtitle)
             else:
-                self.displayListing()
-                break
+                markup = FEED_TEMPLATE % (title, subtitle)
+    
+            try:
+                icon_filename = self.listing.getFavicon(key)
+                pixbuf = gtk.gdk.pixbuf_new_from_file_at_size(icon_filename, \
+                                               LIST_ICON_SIZE, LIST_ICON_SIZE)
+            except:
+                pixbuf = default_pixbuf
+    
+            self.feedItems.append((pixbuf, markup, key))
 
-    def buttonFeedClicked(widget, button, self, window, key):
-        disp = DisplayFeed(self.listing, self.listing.getFeed(key), self.listing.getFeedTitle(key), key, self.config)
-        disp.connect("feed-closed", self.onFeedClosed)
+    def on_feedList_row_activated(self, treeview, path, column):
+        model = treeview.get_model()
+        iter = model.get_iter(path)
+        key = model.get_value(iter, COLUMN_KEY)
+        self.openFeed(key)
+            
+    def openFeed(self, key):
+        try:
+            self.feed_lock
+        except:
+            # If feed_lock doesn't exist, we can open the feed, else we do nothing
+            if key != None:
+                self.feed_lock = get_lock(key)
+                self.disp = DisplayFeed(self.listing, self.listing.getFeed(key), \
+                        self.listing.getFeedTitle(key), key, \
+                        self.config, self.updateDbusHandler)
+                self.disp.connect("feed-closed", self.onFeedClosed)
+        
 
     def onFeedClosed(self, object, key):
-        #self.displayListing()
-        self.refreshList()
-        #self.buttons[key].set_text(self.listing.getFeedTitle(key), self.listing.getFeedUpdateTime(key) + " / " 
-        #                    + str(self.listing.getFeedNumberOfUnreadItems(key)) + " Unread Items")
-        #self.buttons[key].show()
+        #self.listing.saveConfig()
+        #del self.feed_lock
+        gobject.idle_add(self.onFeedClosedTimeout)
+        self.displayListing()
+        #self.updateDbusHandler.ArticleCountUpdated()
+        
+    def onFeedClosedTimeout(self):
+        self.listing.saveConfig()
+        del self.feed_lock
+        self.updateDbusHandler.ArticleCountUpdated()
      
     def run(self):
         self.window.connect("destroy", gtk.main_quit)
         gtk.main()
-        for key in self.listing.getListOfFeeds():
-            self.listing.getFeed(key).saveFeed(CONFIGDIR)
         self.listing.saveConfig()
+        del self.app_lock
 
     def prefsClosed(self, *widget):
-        self.orientation.set_mode(self.config.getOrientation())
+        try:
+            self.orientation.set_mode(self.config.getOrientation())
+        except:
+            pass
+        self.displayListing()
         self.checkAutoUpdate()
 
     def checkAutoUpdate(self, *widget):
@@ -861,9 +1163,20 @@ class FeedingIt:
         # Need to check for internet connection
         # If no internet connection, try again in 10 minutes:
         # gobject.timeout_add(int(5*3600000), self.automaticUpdate)
+        #file = open("/home/user/.feedingit/feedingit_widget.log", "a")
+        #from time import localtime, strftime
+        #file.write("App: %s\n" % strftime("%a, %d %b %Y %H:%M:%S +0000", localtime()))
+        #file.close()
         self.button_update_clicked(None, None)
         return True
     
+    def stopUpdate(self):
+        # Not implemented in the app (see update_feeds.py)
+        try:
+            self.downloadDialog.listOfKeys = []
+        except:
+            pass
+    
     def getStatus(self):
         status = ""
         for key in self.listing.getListOfFeeds():
@@ -876,6 +1189,7 @@ class FeedingIt:
 if __name__ == "__main__":
     gobject.signal_new("feed-closed", DisplayFeed, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,))
     gobject.signal_new("article-closed", DisplayArticle, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,))
+    gobject.signal_new("article-deleted", DisplayArticle, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,))
     gobject.signal_new("article-next", DisplayArticle, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,))
     gobject.signal_new("article-previous", DisplayArticle, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,))
     gobject.signal_new("download-done", DownloadBar, gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,))
@@ -885,7 +1199,7 @@ if __name__ == "__main__":
             mkdir(CONFIGDIR)
         except:
             print "Error: Can't create configuration directory"
-            sys.exit(1)
+            from sys import exit
+            exit(1)
     app = FeedingIt()
-    dbusHandler = ServerObject(app)
     app.run()