[#5946] Make swipe navigation previous article iterate over _generated list_. (njsf)
[feedingit] / src / FeedingIt.py
index ca23f2a..88122f6 100644 (file)
@@ -55,6 +55,8 @@ timeout = 5
 setdefaulttimeout(timeout)
 del timeout
 
+import xml.sax
+
 LIST_ICON_SIZE = 32
 LIST_ICON_BORDER = 10
 
@@ -83,7 +85,8 @@ FEED_COLUMN_MARKUP, FEED_COLUMN_KEY = range(2)
 import style
 
 MARKUP_TEMPLATE= '<span font_desc="%s" foreground="%s">%%s</span>'
-MARKUP_TEMPLATE_ENTRY = '<span font_desc="%s %%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')
@@ -102,8 +105,8 @@ entry_normal_sub = MARKUP_TEMPLATE_ENTRY % (sub_font.get_family(), sub_color.to_
 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 % (head_font.get_family(), active_color.to_string())
-entry_active_sub = MARKUP_TEMPLATE_ENTRY % (sub_font.get_family(), 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))
@@ -139,53 +142,52 @@ def unescape(text):
     return sub("&#?\w+;", fixup, text)
 
 
-class AddWidgetWizard(hildon.WizardDialog):
-    
-    def __init__(self, parent, urlIn, titleIn=None):
-        # Create a Notebook
-        self.notebook = gtk.Notebook()
+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)
+        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)
 
-        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.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:
@@ -411,21 +413,32 @@ class SortList(hildon.StackableWindow):
 
     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()
@@ -473,26 +486,18 @@ class DisplayArticle(hildon.StackableWindow):
         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://" + contentLink)
+            self.view.open("file://%s" % contentLink)
+            self.currentUrl = self.feed.getExternalLink(self.id)
         else:
             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)
 
-        #else:
-        #self.view.load_html_string(self.text, contentLink) # "text/html", "utf-8", self.link)
         self.view.set_zoom_level(float(config.getArtFontSize())/10.)
-        #else:
-        #    if not key == "ArchivedArticles":
-                # 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()
         
         menu = hildon.AppMenu()
         # Create a button and add it to the menu
@@ -503,7 +508,7 @@ class DisplayArticle(hildon.StackableWindow):
         
         button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
         button.set_label("Open in browser")
-        button.connect("clicked", self._signal_link_clicked, self.feed.getExternalLink(self.id))
+        button.connect("clicked", self.open_in_browser)
         menu.append(button)
         
         if key == "ArchivedArticles":
@@ -519,24 +524,37 @@ class DisplayArticle(hildon.StackableWindow):
         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.gestureId = self.view.connect("button_release_event", self.button_released)
-        #self.timeout_handler_id = gobject.timeout_add(300, self.reloadArticle)
+
+    #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
@@ -551,16 +569,6 @@ class DisplayArticle(hildon.StackableWindow):
                 self.emit("article-previous", self.id)
             elif (x<-15):
                 self.emit("article-next", self.id)   
-        #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)
 
     def destroyWindow(self, *args):
         self.disconnect(self.destroyId)
@@ -581,33 +589,16 @@ class DisplayArticle(hildon.StackableWindow):
         
     def remove_archive_button(self, *widget):
         self.set_for_removal = True
-        
-    #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):
+
+    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.open_new_window(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, updateDbusHandler):
@@ -617,6 +608,7 @@ class DisplayFeed(hildon.StackableWindow):
         self.feedTitle = title
         self.set_title(title)
         self.key=key
+        self.current = list()
         self.config = config
         self.updateDbusHandler = updateDbusHandler
         
@@ -706,6 +698,7 @@ 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")
         (width, height) = self.get_size()
         self.markup_renderer.set_property('wrap-width', width-20)
         self.markup_renderer.set_property('ypad', 5)
@@ -717,6 +710,7 @@ class DisplayFeed(hildon.StackableWindow):
         #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:
@@ -724,11 +718,8 @@ class DisplayFeed(hildon.StackableWindow):
             except:
                 pass
             if not ( isRead and hideReadArticles ):
-            #if not ( self.feed.isEntryRead(id) and self.config.getHideReadArticles() ):
-                #title = self.feed.getTitle(id)
                 title = self.fix_title(self.feed.getTitle(id))
-    
-                #if self.feed.isEntryRead(id):
+                self.current.append(id)
                 if isRead:
                     markup = ENTRY_TEMPLATE % (self.config.getFontSize(), title)
                 else:
@@ -811,38 +802,16 @@ class DisplayFeed(hildon.StackableWindow):
     def nextArticle(self, object, index):
         self.mark_item_read(index)
         id = self.feed.getNextId(index)
-        if self.config.getHideReadArticles():
-            isRead = False
-            try:
-                isRead = self.feed.isEntryRead(id)
-            except:
-                pass
-            while isRead and id != index:
-                id = self.feed.getNextId(id)
-                isRead = False
-                try:
-                       isRead = self.feed.isEntryRead(id)
-                except:
-                       pass
+        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):
         self.mark_item_read(index)
         id = self.feed.getPreviousId(index)
-        if self.config.getHideReadArticles():
-            isRead = False
-            try:
-                isRead = self.feed.isEntryRead(id)
-            except:
-                pass
-            while isRead and id != index:
-                id = self.feed.getPreviousId(id)
-                isRead = False
-                try:
-                       isRead = self.feed.isEntryRead(id)
-                except:
-                       pass
+        while id not in self.current and id != index:
+            id = self.feed.getPreviousId(id)
         if id != index:
             self.button_clicked(object, id, previous=True)
 
@@ -902,9 +871,21 @@ class FeedingIt:
     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
@@ -925,7 +906,12 @@ class FeedingIt:
         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("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)
@@ -972,6 +958,16 @@ class FeedingIt:
         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)
@@ -1059,27 +1055,48 @@ class FeedingIt:
                 gtk.ICON_LOOKUP_USE_BUILTIN)
 
         self.feedItems.clear()
+        feedInfo = {}
+        count = 0
         for key in self.listing.getListOfFeeds():
             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:
+                markup = FEED_TEMPLATE % (title, subtitle)
     
-                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
+            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))
+            self.feedItems.append((pixbuf, markup, key))
 
     def on_feedList_row_activated(self, treeview, path, column):
         model = treeview.get_model()
@@ -1092,11 +1109,12 @@ class FeedingIt:
             self.feed_lock
         except:
             # If feed_lock doesn't exist, we can open the feed, else we do nothing
-            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 != 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):