When navigating to the next article, reuse the DisplayArticle widget.
[feedingit] / src / FeedingIt.py
index 84d223a..da6c0f4 100644 (file)
@@ -596,90 +596,129 @@ class SortList(hildon.StackableWindow):
                
 
 class DisplayArticle(hildon.StackableWindow):
-    def __init__(self, feed, id, key, config, listing):
+    """
+    A Widget for displaying an article.
+    """
+    def __init__(self, article_id, feed, feed_key, articles, config, listing):
+        """
+        article_id - The identifier of the article to load.
+
+        feed - The feed object containing the article (an
+        rss_sqlite:Feed object).
+
+        feed_key - The feed's identifier.
+
+        articles - A list of articles from the feed to display.
+        Needed for selecting the next/previous article (article_next).
+
+        config - A configuration object (config:Config).
+
+        listing - The listing object (rss_sqlite:Listing) that
+        contains the feed and article.
+        """
         hildon.StackableWindow.__init__(self)
-        #self.imageDownloader = ImageDownloader()
+
+        self.article_id = None
         self.feed = feed
-        self.listing=listing
-        self.key = key
-        self.id = id
-        #self.set_title(feed.getTitle(id))
-        self.set_title(self.listing.getFeedTitle(key))
+        self.feed_key = feed_key
+        self.articles = articles
         self.config = config
-        self.set_for_removal = False
-        
+        self.listing = listing
+
+        self.set_title(self.listing.getFeedTitle(feed_key))
+
         # Init the article display
-        #if self.config.getWebkitSupport():
         self.view = WebView()
-            #self.view.set_editable(False)
-        #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():
-        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:
-            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.set_zoom_level(float(config.getArtFontSize())/10.)
         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.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.view.set_zoom_level(float(config.getArtFontSize())/10.)
-        
+        self.pannable_article = hildon.PannableArea()
+        self.pannable_article.add(self.view)
+
+        self.add(self.pannable_article)
+
+        self.pannable_article.show_all()
+
+        # Create the menu.
         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.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.open_in_browser)
-        menu.append(button)
-        
-        if key == "ArchivedArticles":
+
+        def menu_button(label, callback):
             button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
-            button.set_label("Remove from archived articles")
-            button.connect("clicked", self.remove_archive_button)
+            button.set_label(label)
+            button.connect("clicked", callback)
+            menu.append(button)
+
+        menu_button("Allow horizontal scrolling", self.horiz_scrolling_button)
+        menu_button("Open in browser", self.open_in_browser)
+        if feed_key == "ArchivedArticles":
+            menu_button(
+                "Remove from archived articles", 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)
+            menu_button("Add to archived articles", self.archive_button)
         
         self.set_app_menu(menu)
         menu.show_all()
         
-        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)
 
-    #def navigation_policy_decision(self, wv, fr, req, action, decision):
+        self.article_open(article_id)
+
+    def article_open(self, article_id):
+        """
+        Load the article with the specified id.
+        """
+        # If an article was open, close it.
+        if self.article_id is not None:
+            self.article_closed()
+
+        self.article_id = article_id
+        self.set_for_removal = False
+        self.loadedArticle = False
+        self.initial_article_load = True
+
+        contentLink = self.feed.getContentLink(self.article_id)
+        if contentLink.startswith("/home/user/"):
+            self.view.open("file://%s" % contentLink)
+            self.currentUrl = self.feed.getExternalLink(self.article_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 = str(contentLink)
+
+        self.feed.setEntryRead(self.article_id)
+
+    def article_closed(self):
+        """
+        The user has navigated away from the article.  Execute any
+        pending actions.
+        """
+        if self.set_for_removal:
+            self.emit("article-deleted", self.article_id)
+        else:
+            self.emit("article-closed", self.article_id)
+
+
     def navigation_requested(self, wv, fr, req):
+        """
+        http://webkitgtk.org/reference/webkitgtk-webkitwebview.html#WebKitWebView-navigation-requested
+
+        wv - a WebKitWebView
+        fr - a WebKitWebFrame
+        req - WebKitNetworkRequest
+        """
+        if self.initial_article_load:
+            # Always initially load an article in the internal
+            # browser.
+            self.initial_article_load = False
+            return False
+
+        # When following a link, only use the internal browser if so
+        # configured.  Otherwise, launch an external browser.
         if self.config.getOpenInExternalBrowser():
             self.open_in_browser(None, req.get_uri())
             return True
@@ -688,7 +727,7 @@ class DisplayArticle(hildon.StackableWindow):
 
     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()
@@ -698,7 +737,14 @@ class DisplayArticle(hildon.StackableWindow):
             self.loadedArticle = True
 
     def button_pressed(self, window, event):
-        #print event.x, event.y
+        """
+        The user pressed a "mouse button" (in our case, this means the
+        user likely started to drag with the finger).
+
+        We are only interested in whether the user performs a drag.
+        We record the starting position and when the user "releases
+        the button," we see how far the mouse moved.
+        """
         self.coords = (event.x, event.y)
         
     def button_released(self, window, event):
@@ -707,17 +753,38 @@ class DisplayArticle(hildon.StackableWindow):
         
         if (2*abs(y) < abs(x)):
             if (x > 15):
-                self.emit("article-previous", self.id)
+                self.article_next(forward=False)
             elif (x<-15):
-                self.emit("article-next", self.id)   
+                self.article_next(forward=True)
+
+            # We handled the event.  Don't propagate it further.
+            return True
+
+    def article_next(self, forward=True):
+        """
+        Advance to the next (or, if forward is false, the previous)
+        article.
+        """
+        first_id = None
+        id = self.article_id
+        i = 0
+        while True:
+            i += 1
+            id = self.feed.getNextId(id, forward)
+            if id == first_id:
+                # We looped.
+                break
+
+            if first_id is None:
+                first_id = id
+
+            if id in self.articles:
+                self.article_open(id)
+                break
 
     def destroyWindow(self, *args):
+        self.article_closed()
         self.disconnect(self.destroyId)
-        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):
@@ -726,12 +793,17 @@ class DisplayArticle(hildon.StackableWindow):
         
     def archive_button(self, *widget):
         # Call the listing.addArchivedArticle
-        self.listing.addArchivedArticle(self.key, self.id)
+        self.listing.addArchivedArticle(self.feed_key, self.article_id)
         
     def remove_archive_button(self, *widget):
         self.set_for_removal = True
 
     def open_in_browser(self, object, link=None):
+        """
+        Open the specified link using the system's browser.  If not
+        link is specified, reopen the current page using the system's
+        browser.
+        """
         if link == None:
             link = self.currentUrl
 
@@ -764,7 +836,14 @@ class DisplayFeed(hildon.StackableWindow):
         self.feedTitle = title
         self.set_title(title)
         self.key=key
-        self.current = list()
+        # Articles to show.
+        #
+        # If hide read articles is set, this is set to the set of
+        # unread articles at the time that feed is loaded.  The last
+        # bit is important: when the user selects the next article,
+        # but then decides to move back, previous should select the
+        # just read article.
+        self.articles = list()
         self.config = config
         
         self.downloadDialog = False
@@ -881,7 +960,7 @@ class DisplayFeed(hildon.StackableWindow):
             articles = self.feed.getIds()
         
         hasArticle = False
-        self.current = list()
+        self.articles[:] = []
         for id in articles:
             isRead = False
             try:
@@ -890,7 +969,7 @@ class DisplayFeed(hildon.StackableWindow):
                 pass
             if not ( isRead and hideReadArticles ):
                 title = self.fix_title(self.feed.getTitle(id))
-                self.current.append(id)
+                self.articles.append(id)
                 if isRead:
                     markup = ENTRY_TEMPLATE % (self.config.getFontSize(), title)
                 else:
@@ -923,8 +1002,7 @@ class DisplayFeed(hildon.StackableWindow):
         #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.feed, index, self.key, self.config, self.listing)
+        newDisp = DisplayArticle(index, self.feed, self.key, self.articles, self.config, self.listing)
         stack = hildon.WindowStack.get_default()
         if previous:
             tmp = stack.peek()
@@ -946,8 +1024,6 @@ class DisplayFeed(hildon.StackableWindow):
         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()
@@ -969,22 +1045,6 @@ class DisplayFeed(hildon.StackableWindow):
                 break
             it = self.feedItems.iter_next(it)
 
-    def nextArticle(self, object, index):
-        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):
-        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):
         selection = self.feedList.get_selection()
         selection.set_mode(gtk.SELECTION_NONE)
@@ -1409,8 +1469,6 @@ 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,))
     gobject.threads_init()
     if not isdir(CONFIGDIR):