Merge branch 'master' of https://git.maemo.org/projects/feedingit
[feedingit] / src / FeedingIt.py
index 2e53a52..a48251f 100644 (file)
@@ -2,6 +2,7 @@
 
 # 
 # Copyright (c) 2007-2008 INdT.
+# Copyright (c) 2011 Neal H. Walfield
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU Lesser General Public License as published by
 # the Free Software Foundation, either version 3 of the License, or
@@ -19,7 +20,7 @@
 # ============================================================================
 __appname__ = 'FeedingIt'
 __author__  = 'Yves Marcoz'
-__version__ = '0.8.0'
+__version__ = '0.9.1~woodchuck'
 __description__ = 'A simple RSS Reader for Maemo 5'
 # ============================================================================
 
@@ -35,20 +36,24 @@ from webkit import WebView
 #    import gtkhtml2
 #    has_webkit=False
 from os.path import isfile, isdir, exists
-from os import mkdir, remove, stat
+from os import mkdir, remove, stat, environ
 import gobject
 from aboutdialog import HeAboutDialog
 from portrait import FremantleRotation
 from threading import Thread, activeCount
 from feedingitdbus import ServerObject
-from updatedbus import UpdateServerObject, get_lock
 from config import Config
 from cgi import escape
+import weakref
+import dbus
+import debugging
+import logging
+logger = logging.getLogger(__name__)
 
 from rss_sqlite import Listing
 from opml import GetOpmlData, ExportOpmlData
 
-from urllib2 import install_opener, build_opener
+import mainthread
 
 from socket import setdefaulttimeout
 timeout = 5
@@ -92,10 +97,18 @@ MARKUP_TEMPLATE_ENTRY = '<span font_desc="%s italic %%s" foreground="%s">%%s</sp
 head_font = style.get_font_desc('SystemFont')
 sub_font = style.get_font_desc('SmallSystemFont')
 
-head_color = style.get_color('ButtonTextColor')
+#head_color = style.get_color('ButtonTextColor')
+head_color = style.get_color('DefaultTextColor')
 sub_color = style.get_color('DefaultTextColor')
 active_color = style.get_color('ActiveTextColor')
 
+bg_color = style.get_color('DefaultBackgroundColor').to_string()
+c1=hex(min(int(bg_color[1:5],16)+10000, 65535))[2:6]
+c2=hex(min(int(bg_color[5:9],16)+10000, 65535))[2:6]
+c3=hex(min(int(bg_color[9:],16)+10000, 65535))[2:6]
+bg_color = "#" + c1 + c2 + c3
+
+
 head = MARKUP_TEMPLATE % (head_font.to_string(), head_color.to_string())
 normal_sub = MARKUP_TEMPLATE % (sub_font.to_string(), sub_color.to_string())
 
@@ -143,9 +156,12 @@ def unescape(text):
 
 
 class AddWidgetWizard(gtk.Dialog):
-    def __init__(self, parent, urlIn, titleIn=None, isEdit=False):
+    def __init__(self, parent, listing, urlIn, categories, titleIn=None, isEdit=False, currentCat=1):
         gtk.Dialog.__init__(self)
         self.set_transient_for(parent)
+        
+        #self.category = categories[0]
+        self.category = currentCat
 
         if isEdit:
             self.set_title('Edit RSS feed')
@@ -161,7 +177,8 @@ class AddWidgetWizard(gtk.Dialog):
 
         self.nameEntry = hildon.Entry(gtk.HILDON_SIZE_AUTO)
         self.nameEntry.set_placeholder('Feed name')
-        if not titleIn == None:
+        # If titleIn matches urlIn, there is no title.
+        if not titleIn == None and titleIn != urlIn:
             self.nameEntry.set_text(titleIn)
             self.nameEntry.select_region(-1, -1)
 
@@ -171,7 +188,7 @@ class AddWidgetWizard(gtk.Dialog):
         self.urlEntry.select_region(-1, -1)
         self.urlEntry.set_activates_default(True)
 
-        self.table = gtk.Table(2, 2, False)
+        self.table = gtk.Table(3, 2, False)
         self.table.set_col_spacings(5)
         label = gtk.Label('Name:')
         label.set_alignment(1., .5)
@@ -181,118 +198,192 @@ class AddWidgetWizard(gtk.Dialog):
         label.set_alignment(1., .5)
         self.table.attach(label, 0, 1, 1, 2, gtk.FILL)
         self.table.attach(self.urlEntry, 1, 2, 1, 2)
+        selector = self.create_selector(categories, listing)
+        picker = hildon.PickerButton(gtk.HILDON_SIZE_FINGER_HEIGHT, hildon.BUTTON_ARRANGEMENT_VERTICAL)
+        picker.set_selector(selector)
+        picker.set_title("Select category")
+        #picker.set_text(listing.getCategoryTitle(self.category), None) #, "Subtitle")
+        picker.set_name('HildonButton-finger')
+        picker.set_alignment(0,0,1,1)
+        
+        self.table.attach(picker, 0, 2, 2, 3, gtk.FILL)
+        
         self.vbox.pack_start(self.table)
 
         self.show_all()
 
     def getData(self):
-        return (self.nameEntry.get_text(), self.urlEntry.get_text())
-
-    def some_page_func(self, nb, current, userdata):
-        # Validate data for 1st page
-        if current == 0:
-            return len(self.nameEntry.get_text()) != 0
-        elif current == 1:
-            # Check the url is not null, and starts with http
-            return ( (len(self.urlEntry.get_text()) != 0) and (self.urlEntry.get_text().lower().startswith("http")) )
-        elif current != 2:
-            return False
+        return (self.nameEntry.get_text(), self.urlEntry.get_text(), self.category)
+    
+    def create_selector(self, choices, listing):
+        #self.pickerDialog = hildon.PickerDialog(self.parent)
+        selector = hildon.TouchSelector(text=True)
+        index = 0
+        self.map = {}
+        for item in choices:
+            title = listing.getCategoryTitle(item)
+            iter = selector.append_text(str(title))
+            if self.category == item: 
+                selector.set_active(0, index)
+            self.map[title] = item
+            index += 1
+        selector.connect("changed", self.selection_changed)
+        #self.pickerDialog.set_selector(selector)
+        return selector
+
+    def selection_changed(self, selector, button):
+        current_selection = selector.get_current_text()
+        if current_selection:
+            self.category = self.map[current_selection]
+
+class AddCategoryWizard(gtk.Dialog):
+    def __init__(self, parent, titleIn=None, isEdit=False):
+        gtk.Dialog.__init__(self)
+        self.set_transient_for(parent)
+
+        if isEdit:
+            self.set_title('Edit Category')
         else:
-            return True
-        
-class Download(Thread):
-    def __init__(self, listing, key, config):
-        Thread.__init__(self)
-        self.listing = listing
-        self.key = key
-        self.config = config
-        
-    def run (self):
-        (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
+            self.set_title('Add Category')
+
+        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('Category name')
+        if not titleIn == None:
+            self.nameEntry.set_text(titleIn)
+            self.nameEntry.select_region(-1, -1)
+
+        self.table = gtk.Table(1, 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()
         
 class DownloadBar(gtk.ProgressBar):
-    def __init__(self, parent, listing, listOfKeys, config, single=False):
-        
-        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 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.)
-            #print x, k, fin, fraction
-            self.set_fraction(fraction)
-
-            if len(self.listOfKeys)>0:
-                self.current = self.current+1
-                key = self.listOfKeys.pop()
-                #if self.single == True:
-                    # Check if the feed is being displayed
-                download = Download(self.listing, key, self.config)
-                download.start()
-                return True
-            elif activeCount() > 1:
-                return True
+    @classmethod
+    def class_init(cls):
+        if hasattr (cls, 'class_init_done'):
+            return
+
+        cls.downloadbars = []
+        # Total number of jobs we are monitoring.
+        cls.total = 0
+        # Number of jobs complete (of those that we are monitoring).
+        cls.done = 0
+        # Percent complete.
+        cls.progress = 0
+
+        cls.class_init_done = True
+
+        bus = dbus.SessionBus()
+        bus.add_signal_receiver(handler_function=cls.update_progress,
+                                bus_name=None,
+                                signal_name='UpdateProgress',
+                                dbus_interface='org.marcoz.feedingit',
+                                path='/org/marcoz/feedingit/update')
+
+    def __init__(self, parent):
+        self.class_init ()
+
+        gtk.ProgressBar.__init__(self)
+
+        self.downloadbars.append(weakref.ref (self))
+        self.set_fraction(0)
+        self.__class__.update_bars()
+        self.show_all()
+
+    @classmethod
+    def downloading(cls):
+        cls.class_init ()
+        return cls.done != cls.total
+
+    @classmethod
+    def update_progress(cls, percent_complete,
+                        completed, in_progress, queued,
+                        bytes_downloaded, bytes_updated, bytes_per_second,
+                        feed_updated):
+        if not cls.downloadbars:
+            return
+
+        cls.total = completed + in_progress + queued
+        cls.done = completed
+        cls.progress = percent_complete / 100.
+        if cls.progress < 0: cls.progress = 0
+        if cls.progress > 1: cls.progress = 1
+
+        if feed_updated:
+            for ref in cls.downloadbars:
+                bar = ref ()
+                if bar is None:
+                    # The download bar disappeared.
+                    cls.downloadbars.remove (ref)
+                else:
+                    bar.emit("download-done", feed_updated)
+
+        if in_progress == 0 and queued == 0:
+            for ref in cls.downloadbars:
+                bar = ref ()
+                if bar is None:
+                    # The download bar disappeared.
+                    cls.downloadbars.remove (ref)
+                else:
+                    bar.emit("download-done", None)
+            return
+
+        cls.update_bars()
+
+    @classmethod
+    def update_bars(cls):
+        # In preparation for i18n/l10n
+        def N_(a, b, n):
+            return (a if n == 1 else b)
+
+        text = (N_('Updated %d of %d feeds ', 'Updated %d of %d feeds',
+                   cls.total)
+                % (cls.done, cls.total))
+
+        for ref in cls.downloadbars:
+            bar = ref ()
+            if bar is None:
+                # The download bar disappeared.
+                cls.downloadbars.remove (ref)
             else:
-                #self.waitingWindow.destroy()
-                #self.destroy()
-                try:
-                    del self.update_lock
-                except:
-                    pass
-                self.emit("download-done", "success")
-                return False 
-        return True
-    
-    
+                bar.set_text(text)
+                bar.set_fraction(cls.progress)
+
 class SortList(hildon.StackableWindow):
-    def __init__(self, parent, listing, feedingit, after_closing):
+    def __init__(self, parent, listing, feedingit, after_closing, category=None):
         hildon.StackableWindow.__init__(self)
         self.set_transient_for(parent)
-        self.set_title('Subscriptions')
+        if category:
+            self.isEditingCategories = False
+            self.category = category
+            self.set_title(listing.getCategoryTitle(category))
+        else:
+            self.isEditingCategories = True
+            self.set_title('Categories')
         self.listing = listing
         self.feedingit = feedingit
         self.after_closing = after_closing
-        self.connect('destroy', lambda w: self.after_closing())
+        if after_closing:
+            self.connect('destroy', lambda w: self.after_closing())
         self.vbox2 = gtk.VBox(False, 2)
 
         button = hildon.GtkButton(gtk.HILDON_SIZE_FINGER_HEIGHT)
@@ -370,10 +461,16 @@ class SortList(hildon.StackableWindow):
         #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])
-            if key == selected:
-                selectedItem = item
+        if self.isEditingCategories:
+            for key in self.listing.getListOfCategories():
+                item = self.treestore.append([self.listing.getCategoryTitle(key), key])
+                if key == selected:
+                    selectedItem = item
+        else:
+            for key in self.listing.getListOfFeeds(category=self.category):
+                item = self.treestore.append([self.listing.getFeedTitle(key), key])
+                if key == selected:
+                    selectedItem = item
         self.treeview.set_model(self.treestore)
         if not selected == None:
             self.treeview.get_selection().select_iter(selectedItem)
@@ -402,13 +499,19 @@ class SortList(hildon.StackableWindow):
     def buttonUp(self, button):
         key  = self.getSelectedItem()
         if not key == None:
-            self.listing.moveUp(key)
+            if self.isEditingCategories:
+                self.listing.moveCategoryUp(key)
+            else:
+                self.listing.moveUp(key)
             self.refreshList(key, -10)
 
     def buttonDown(self, button):
         key = self.getSelectedItem()
         if not key == None:
-            self.listing.moveDown(key)
+            if self.isEditingCategories:
+                self.listing.moveCategoryDown(key)
+            else:
+                self.listing.moveDown(key)
             self.refreshList(key, 10)
 
     def buttonDelete(self, button):
@@ -419,7 +522,10 @@ class SortList(hildon.StackableWindow):
         response = dlg.run()
         dlg.destroy()
         if response == gtk.RESPONSE_OK:
-            self.listing.removeFeed(key)
+            if self.isEditingCategories:
+                self.listing.removeCategory(key)
+            else:
+                self.listing.removeFeed(key)
             self.refreshList()
 
     def buttonEdit(self, button):
@@ -429,27 +535,38 @@ class SortList(hildon.StackableWindow):
             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()
+        if self.isEditingCategories:
+            if key is not None:
+                SortList(self.parent, self.listing, self.feedingit, None, category=key)
+        else:
+            if key is not None:
+                wizard = AddWidgetWizard(self, self.listing, self.listing.getFeedUrl(key), self.listing.getListOfCategories(), self.listing.getFeedTitle(key), True, currentCat=self.category)
+                ret = wizard.run()
+                if ret == 2:
+                    (title, url, category) = wizard.getData()
+                    if url != '':
+                        self.listing.editFeed(key, title, url, category=category)
+                        self.refreshList()
+                wizard.destroy()
 
     def buttonDone(self, *args):
         self.destroy()
         
     def buttonAdd(self, button, urlIn="http://"):
-        wizard = AddWidgetWizard(self, urlIn)
-        ret = wizard.run()
-        if ret == 2:
-            (title, url) = wizard.getData()
-            if (not title == '') and (not url == ''): 
-               self.listing.addFeed(title, url)
+        if self.isEditingCategories:
+            wizard = AddCategoryWizard(self)
+            ret = wizard.run()
+            if ret == 2:
+                title = wizard.getData()
+                if (not title == ''): 
+                   self.listing.addCategory(title)
+        else:
+            wizard = AddWidgetWizard(self, self.listing, urlIn, self.listing.getListOfCategories())
+            ret = wizard.run()
+            if ret == 2:
+                (title, url, category) = wizard.getData()
+                if url:
+                   self.listing.addFeed(title, url, category=category)
         wizard.destroy()
         self.refreshList()
                
@@ -601,7 +718,7 @@ class DisplayArticle(hildon.StackableWindow):
             iface.open_new_window(link)
 
 class DisplayFeed(hildon.StackableWindow):
-    def __init__(self, listing, feed, title, key, config, updateDbusHandler):
+    def __init__(self, listing, feed, title, key, config):
         hildon.StackableWindow.__init__(self)
         self.listing = listing
         self.feed = feed
@@ -610,7 +727,6 @@ class DisplayFeed(hildon.StackableWindow):
         self.key=key
         self.current = list()
         self.config = config
-        self.updateDbusHandler = updateDbusHandler
         
         self.downloadDialog = False
         
@@ -638,7 +754,14 @@ class DisplayFeed(hildon.StackableWindow):
         self.set_app_menu(menu)
         menu.show_all()
         
+        self.main_vbox = gtk.VBox(False, 0)
+        self.add(self.main_vbox)
+
+        self.pannableFeed = None
         self.displayFeed()
+
+        if DownloadBar.downloading ():
+            self.show_download_bar ()
         
         self.connect('configure-event', self.on_configure_event)
         self.connect("destroy", self.destroyWindow)
@@ -668,6 +791,9 @@ class DisplayFeed(hildon.StackableWindow):
         return escape(unescape(title).replace("<em>","").replace("</em>","").replace("<nobr>","").replace("</nobr>","").replace("<wbr>",""))
 
     def displayFeed(self):
+        if self.pannableFeed:
+            self.pannableFeed.destroy()
+
         self.pannableFeed = hildon.PannableArea()
 
         self.pannableFeed.set_property('hscrollbar-policy', gtk.POLICY_NEVER)
@@ -675,6 +801,8 @@ class DisplayFeed(hildon.StackableWindow):
         self.feedItems = gtk.ListStore(str, str)
         #self.feedList = gtk.TreeView(self.feedItems)
         self.feedList = hildon.GtkTreeView(gtk.HILDON_UI_MODE_NORMAL)
+        self.feedList.set_rules_hint(True)
+
         selection = self.feedList.get_selection()
         selection.set_mode(gtk.SELECTION_NONE)
         #selection.connect("changed", lambda w: True)
@@ -697,10 +825,10 @@ class DisplayFeed(hildon.StackableWindow):
 
         self.markup_renderer = gtk.CellRendererText()
         self.markup_renderer.set_property('wrap-mode', pango.WRAP_WORD_CHAR)
-        self.markup_renderer.set_property('background', "#333333")
+        self.markup_renderer.set_property('background', bg_color) #"#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('ypad', 8)
         self.markup_renderer.set_property('xpad', 5)
         markup_column = gtk.TreeViewColumn('', self.markup_renderer, \
                 markup=FEED_COLUMN_MARKUP)
@@ -708,9 +836,14 @@ class DisplayFeed(hildon.StackableWindow):
 
         #self.pannableFeed.set_property("mov-mode", hildon.MOVEMENT_MODE_BOTH)
         hideReadArticles = self.config.getHideReadArticles()
+        if hideReadArticles:
+            articles = self.feed.getIds(onlyUnread=True)
+        else:
+            articles = self.feed.getIds()
+        
         hasArticle = False
         self.current = list()
-        for id in self.feed.getIds():
+        for id in articles:
             isRead = False
             try:
                 isRead = self.feed.isEntryRead(id)
@@ -732,7 +865,7 @@ class DisplayFeed(hildon.StackableWindow):
             markup = ENTRY_TEMPLATE % (self.config.getFontSize(), "No Articles To Display")
             self.feedItems.append((markup, ""))
 
-        self.add(self.pannableFeed)
+        self.main_vbox.pack_start(self.pannableFeed)
         self.show_all()
 
     def clear(self):
@@ -824,23 +957,30 @@ class DisplayFeed(hildon.StackableWindow):
         #self.feed.saveFeed(CONFIGDIR)
         self.displayFeed()
 
+
+    def do_update_feed(self):
+        self.listing.updateFeed (self.key, priority=-1)
+
     def button_update_clicked(self, button):
-        #bar = DownloadBar(self, self.listing, [self.key,], self.config ) 
+        gobject.idle_add(self.do_update_feed)
+            
+    def show_download_bar(self):
         if not type(self.downloadDialog).__name__=="DownloadBar":
-            self.pannableFeed.destroy()
-            self.vbox = gtk.VBox(False, 10)
-            self.downloadDialog = DownloadBar(self.window, self.listing, [self.key,], self.config, single=True )
-            self.downloadDialog.connect("download-done", self.onDownloadsDone)
-            self.vbox.pack_start(self.downloadDialog, expand=False, fill=False)
-            self.add(self.vbox)
+            self.downloadDialog = DownloadBar(self.window)
+            self.downloadDialog.connect("download-done", self.onDownloadDone)
+            self.main_vbox.pack_end(self.downloadDialog,
+                                    expand=False, fill=False)
             self.show_all()
-            
-    def onDownloadsDone(self, *widget):
-        self.vbox.destroy()
-        self.feed = self.listing.getFeed(self.key)
-        self.displayFeed()
-        self.updateDbusHandler.ArticleCountUpdated()
         
+    def onDownloadDone(self, widget, feed):
+        if feed == self.feed:
+            self.feed = self.listing.getFeed(self.key)
+            self.displayFeed()
+
+        if feed is None:
+            self.downloadDialog.destroy()
+            self.downloadDialog = False
+
     def buttonReadAllClicked(self, button):
         #self.clear()
         self.feed.markAllAsRead()
@@ -876,33 +1016,17 @@ class FeedingIt:
         self.window.show_all()
         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.category = 0
+        self.listing = Listing(self.config, CONFIGDIR)
+
         self.downloadDialog = False
         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"
+        except Exception, e:
+            logger.warn("Could not start rotation manager: %s" % str(e))
         
         menu = hildon.AppMenu()
         # Create a button and add it to the menu
@@ -943,9 +1067,11 @@ class FeedingIt:
         #self.articleWindow = hildon.StackableWindow()
         self.introLabel.destroy()
         self.pannableListing = hildon.PannableArea()
-        self.feedItems = gtk.ListStore(gtk.gdk.Pixbuf, str, str)
+        self.feedItems = gtk.TreeStore(gtk.gdk.Pixbuf, str, str)
         self.feedList = gtk.TreeView(self.feedItems)
         self.feedList.connect('row-activated', self.on_feedList_row_activated)
+        #self.feedList.set_enable_tree_lines(True)                                                                                           
+        #self.feedList.set_show_expanders(True)
         self.pannableListing.add(self.feedList)
 
         icon_renderer = gtk.CellRendererPixbuf()
@@ -964,22 +1090,37 @@ class FeedingIt:
         self.displayListing()
         self.autoupdate = False
         self.checkAutoUpdate()
+        
         hildon.hildon_gtk_window_set_progress_indicator(self.window, 0)
-        gobject.idle_add(self.enableDbus)
+        gobject.idle_add(self.late_init)
         
-    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):
+    def update_progress(self, percent_complete,
+                        completed, in_progress, queued,
+                        bytes_downloaded, bytes_updated, bytes_per_second,
+                        updated_feed):
+        if (in_progress or queued) and not self.downloadDialog:
+            self.downloadDialog = DownloadBar(self.window)
+            self.downloadDialog.connect("download-done", self.onDownloadDone)
+            self.mainVbox.pack_end(self.downloadDialog, expand=False, fill=False)
+            self.mainVbox.show_all()
+
+            if self.__dict__.get ('disp', None):
+                self.disp.show_download_bar ()
+
+    def onDownloadDone(self, widget, feed):
+        if feed is None:
+            self.downloadDialog.destroy()
+            self.downloadDialog = False
+            self.displayListing()
+
+    def late_init(self):
         self.dbusHandler = ServerObject(self)
-        self.updateDbusHandler = UpdateServerObject(self)
+        bus = dbus.SessionBus()
+        bus.add_signal_receiver(handler_function=self.update_progress,
+                                bus_name=None,
+                                signal_name='UpdateProgress',
+                                dbus_interface='org.marcoz.feedingit',
+                                path='/org/marcoz/feedingit/update')
 
     def button_markAll(self, button):
         for key in self.listing.getListOfFeeds():
@@ -1012,12 +1153,12 @@ class FeedingIt:
         self.displayListing()
 
     def addFeed(self, urlIn="http://"):
-        wizard = AddWidgetWizard(self.window, urlIn)
+        wizard = AddWidgetWizard(self.window, self.listing, urlIn, self.listing.getListOfCategories())
         ret = wizard.run()
         if ret == 2:
-            (title, url) = wizard.getData()
-            if (not title == '') and (not url == ''): 
-               self.listing.addFeed(title, url)
+            (title, url, category) = wizard.getData()
+            if url:
+               self.listing.addFeed(title, url, category=category)
         wizard.destroy()
         self.displayListing()
 
@@ -1026,21 +1167,17 @@ class FeedingIt:
             self.displayListing()
         SortList(self.window, self.listing, self, after_closing)
 
+    def do_update_feeds(self):
+        for k in self.listing.getListOfFeeds():
+            self.listing.updateFeed (k)
+
     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)
-            self.mainVbox.show_all()
-        #self.displayListing()
+        gobject.idle_add(self.do_update_feeds)
 
     def onDownloadsDone(self, *widget):
         self.downloadDialog.destroy()
         self.downloadDialog = False
         self.displayListing()
-        self.updateDbusHandler.UpdateFinished()
-        self.updateDbusHandler.ArticleCountUpdated()
 
     def button_preferences_clicked(self, button):
         dialog = self.config.createDialog()
@@ -1057,83 +1194,130 @@ class FeedingIt:
         else:
             return False
         
+    def saveExpandedLines(self):
+       self.expandedLines = []
+       model = self.feedList.get_model()
+       model.foreach(self.checkLine)
+
+    def checkLine(self, model, path, iter, data = None):
+       if self.feedList.row_expanded(path):
+           self.expandedLines.append(path)
+
+    def restoreExpandedLines(self):
+       model = self.feedList.get_model()
+       model.foreach(self.restoreLine)
+
+    def restoreLine(self, model, path, iter, data = None):
+       if path in self.expandedLines:
+           self.feedList.expand_row(path, False)
+        
     def displayListing(self):
         icon_theme = gtk.icon_theme_get_default()
         default_pixbuf = icon_theme.load_icon(ABOUT_ICON, LIST_ICON_SIZE, \
                 gtk.ICON_LOOKUP_USE_BUILTIN)
 
+        self.saveExpandedLines()
+
         self.feedItems.clear()
+        hideReadFeed = self.config.getHideReadFeeds()
         order = self.config.getFeedSortOrder()
-        keys = self.listing.getSortedListOfKeys(order)
-
-        for key in keys:
-            unreadItems = self.listing.getFeedNumberOfUnreadItems(key)
-            title = xml.sax.saxutils.escape(self.listing.getFeedTitle(key))
-            updateTime = self.listing.getFeedUpdateTime(key)
-            if updateTime == 0:
-                updateTime = "Never"
-            subtitle = '%s / %d unread items' % (updateTime, unreadItems)
-            if unreadItems:
-                markup = FEED_TEMPLATE_UNREAD % (title, subtitle)
-            else:
-                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
+        
+        categories = self.listing.getListOfCategories()
+        if len(categories) > 1:
+            showCategories = True
+        else:
+            showCategories = False
+        
+        for categoryId in categories:
+        
+            title = self.listing.getCategoryTitle(categoryId)
+            keys = self.listing.getSortedListOfKeys(order, onlyUnread=hideReadFeed, category=categoryId)
+            
+            if showCategories and len(keys)>0:
+                category = self.feedItems.append(None, (None, title, categoryId))
+                #print "catID" + str(categoryId) + " " + str(self.category)
+                if categoryId == self.category:
+                    #print categoryId
+                    expandedRow = category
     
-            self.feedItems.append((pixbuf, markup, key))
+            for key in keys:
+                unreadItems = self.listing.getFeedNumberOfUnreadItems(key)
+                title = xml.sax.saxutils.escape(self.listing.getFeedTitle(key))
+                updateTime = self.listing.getFeedUpdateTime(key)
+                if updateTime == 0:
+                    updateTime = "Never"
+                subtitle = '%s / %d unread items' % (updateTime, unreadItems)
+                if unreadItems:
+                    markup = FEED_TEMPLATE_UNREAD % (title, subtitle)
+                else:
+                    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
+                
+                if showCategories:
+                    self.feedItems.append(category, (pixbuf, markup, key))
+                else:
+                    self.feedItems.append(None, (pixbuf, markup, key))
+                    
+                
+        self.restoreExpandedLines()
+        #try:
+            
+        #    self.feedList.expand_row(self.feeItems.get_path(expandedRow), True)
+        #except:
+        #    pass
 
     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
+            #print "Key: " + str(key)
+            catId = int(key)
+            self.category = catId
+            if treeview.row_expanded(path):
+                treeview.collapse_row(path)
+        #else:
+        #    treeview.expand_row(path, True)
+            #treeview.collapse_all()
+            #treeview.expand_row(path, False)
+            #for i in range(len(path)):
+            #    self.feedList.expand_row(path[:i+1], False)
+            #self.show_confirmation_note(self.window, "Working")
+            #return True
         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)
+            if key:
+                self.openFeed(key)
+            
+    def openFeed(self, key):
+        if key != None:
+            self.disp = DisplayFeed(
+                self.listing, self.listing.getFeed(key),
+                self.listing.getFeedTitle(key), key,
+                self.config)
+            self.disp.connect("feed-closed", self.onFeedClosed)
                 
     def openArticle(self, key, id):
-        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.button_clicked(None, id)
-                self.disp.connect("feed-closed", self.onFeedClosed)
-        
+        if key != None:
+            self.openFeed(key)
+            self.disp.button_clicked(None, id)
 
     def onFeedClosed(self, object, key):
-        #self.listing.saveConfig()
-        #del self.feed_lock
-        gobject.idle_add(self.onFeedClosedTimeout)
         self.displayListing()
-        #self.updateDbusHandler.ArticleCountUpdated()
         
-    def onFeedClosedTimeout(self):
-        del self.feed_lock
-        self.updateDbusHandler.ArticleCountUpdated()
-     
+    def quit(self, *args):
+        self.window.hide()
+        gtk.main_quit ()
+
     def run(self):
-        self.window.connect("destroy", gtk.main_quit)
+        self.window.connect("destroy", self.quit)
         gtk.main()
-        del self.app_lock
 
     def prefsClosed(self, *widget):
         try:
@@ -1170,13 +1354,6 @@ class FeedingIt:
         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():
@@ -1187,6 +1364,9 @@ class FeedingIt:
         return status
 
 if __name__ == "__main__":
+    mainthread.init ()
+    debugging.init(dot_directory=".feedingit", program_name="feedingit")
+
     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,))
@@ -1198,7 +1378,7 @@ if __name__ == "__main__":
         try:
             mkdir(CONFIGDIR)
         except:
-            print "Error: Can't create configuration directory"
+            logger.error("Error: Can't create configuration directory")
             from sys import exit
             exit(1)
     app = FeedingIt()