# ============================================================================
import gtk
-from pango import FontDescription
import pango
import hildon
#import gtkhtml2
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
import mainthread
-from jobmanager import JobManager
from socket import setdefaulttimeout
timeout = 5
ENTRY_TEMPLATE = entry_head
ENTRY_TEMPLATE_UNREAD = entry_active_head
+notification_iface = None
+def notify(message):
+ def get_iface():
+ global notification_iface
+
+ bus = dbus.SessionBus()
+ proxy = bus.get_object('org.freedesktop.Notifications',
+ '/org/freedesktop/Notifications')
+ notification_iface \
+ = dbus.Interface(proxy, 'org.freedesktop.Notifications')
+
+ def doit():
+ notification_iface.SystemNoteInfoprint("FeedingIt: " + message)
+
+ if notification_iface is None:
+ get_iface()
+
+ try:
+ doit()
+ except dbus.DBusException:
+ # Rebind the name and try again.
+ get_iface()
+ doit()
+
+def open_in_browser(link):
+ bus = dbus.SessionBus()
+ b_proxy = bus.get_object("com.nokia.osso_browser",
+ "/com/nokia/osso_browser/request")
+ b_iface = dbus.Interface(b_proxy, 'com.nokia.osso_browser')
+
+ notify("Opening %s" % link)
+
+ # We open the link asynchronously: if the web browser is not
+ # already running, this can take a while.
+ def error_handler():
+ """
+ Something went wrong opening the URL.
+ """
+ def e(exception):
+ notify("Error opening %s: %s" % (link, str(exception)))
+ return e
+
+ b_iface.open_new_window(link,
+ reply_handler=lambda *args: None,
+ error_handler=error_handler())
+
##
# Removes HTML or XML character references and entities from a text string.
#
if hasattr (cls, 'class_init_done'):
return
- jm = JobManager ()
- jm.stats_hook_register (cls.update_progress,
- run_in_main_thread=True)
-
cls.downloadbars = []
# Total number of jobs we are monitoring.
cls.total = 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 ()
self.downloadbars.append(weakref.ref (self))
self.set_fraction(0)
self.__class__.update_bars()
- self.show_all()
@classmethod
def downloading(cls):
- return hasattr (cls, 'jobs_at_start')
+ cls.class_init ()
+ return cls.done != cls.total
@classmethod
- def update_progress(cls, jm, old_stats, new_stats, updated_feed):
- if not cls.downloading():
- cls.jobs_at_start = old_stats['jobs-completed']
-
+ def update_progress(cls, percent_complete,
+ completed, in_progress, queued,
+ bytes_downloaded, bytes_updated, bytes_per_second,
+ feed_updated):
if not cls.downloadbars:
return
- if new_stats['jobs-in-progress'] + new_stats['jobs-queued'] == 0:
- del cls.jobs_at_start
+ 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", None)
- return
+ bar.emit("download-done", feed_updated)
- # This should never be called if new_stats['jobs'] is 0, but
- # just in case...
- cls.total = max (1, new_stats['jobs'] - cls.jobs_at_start)
- cls.done = new_stats['jobs-completed'] - cls.jobs_at_start
- cls.progress = 1 - (new_stats['jobs-in-progress'] / 2.
- + new_stats['jobs-queued']) / cls.total
- cls.update_bars()
-
- if updated_feed:
+ 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", updated_feed)
+ bar.emit("download-done", None)
+ return
+
+ cls.update_bars()
@classmethod
def update_bars(cls):
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
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()
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):
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):
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):
- 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')
+ """
+ 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:
- iface.open_new_window(self.currentUrl)
- else:
- iface.open_new_window(link)
+ link = self.currentUrl
+
+ open_in_browser(link)
class DisplayFeed(hildon.StackableWindow):
- def __init__(self, listing, feed, title, key, config, updateDbusHandler):
+ def __init__(self, listing, config):
hildon.StackableWindow.__init__(self)
+ self.connect('configure-event', self.on_configure_event)
+ self.connect("delete_event", self.delete_window)
+ self.connect("destroy", self.destroyWindow)
+
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
+
+ # 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.downloadDialog = False
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)
+
+ button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
+ button.set_label("Delete read articles")
+ button.connect("clicked", self.buttonPurgeArticles)
+ menu.append(button)
+ self.archived_article_buttons = [button]
self.set_app_menu(menu)
menu.show_all()
- self.main_vbox = gtk.VBox(False, 0)
- self.add(self.main_vbox)
+ self.feedItems = gtk.ListStore(str, str)
- self.pannableFeed = None
- self.displayFeed()
+ self.feedList = hildon.GtkTreeView(gtk.HILDON_UI_MODE_NORMAL)
+ self.feedList.set_model(self.feedItems)
+ self.feedList.set_rules_hint(True)
+ self.feedList.set_grid_lines(gtk.TREE_VIEW_GRID_LINES_HORIZONTAL)
+ self.feedList.set_hover_selection(False)
+ self.feedList.connect('hildon-row-tapped',
+ self.on_feedList_row_activated)
+
+ self.markup_renderer = gtk.CellRendererText()
+ self.markup_renderer.set_property('wrap-mode', pango.WRAP_WORD_CHAR)
+ 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', 8)
+ self.markup_renderer.set_property('xpad', 5)
+ markup_column = gtk.TreeViewColumn('', self.markup_renderer, \
+ markup=FEED_COLUMN_MARKUP)
+ self.feedList.append_column(markup_column)
- if DownloadBar.downloading ():
- self.show_download_bar ()
+ vbox = gtk.VBox(False, 10)
+ vbox.pack_start(self.feedList)
- self.connect('configure-event', self.on_configure_event)
- self.connect("destroy", self.destroyWindow)
+ self.pannableFeed = hildon.PannableArea()
+ self.pannableFeed.set_property('hscrollbar-policy', gtk.POLICY_NEVER)
+ self.pannableFeed.add_with_viewport(vbox)
+
+ self.main_vbox = gtk.VBox(False, 0)
+ self.main_vbox.pack_start(self.pannableFeed)
+
+ self.add(self.main_vbox)
+
+ self.main_vbox.show_all()
def on_configure_event(self, window, event):
if getattr(self, 'markup_renderer', None) is None:
self.feedItems.set_value(it, FEED_COLUMN_MARKUP, markup)
it = self.feedItems.iter_next(it)
+ def delete_window(self, *args):
+ """
+ Prevent the window from being deleted.
+ """
+ self.hide()
+
+ try:
+ key = self.key
+ except AttributeError:
+ key = None
+
+ if key is not None:
+ self.listing.updateUnread(key)
+ self.emit("feed-closed", key)
+ self.key = None
+ self.feedItems.clear()
+
+ return True
+
def destroyWindow(self, *args):
- #self.feed.saveUnread(CONFIGDIR)
- self.listing.updateUnread(self.key)
- self.emit("feed-closed", self.key)
+ try:
+ key = self.key
+ except AttributeError:
+ key = None
+
self.destroy()
- #gobject.idle_add(self.feed.saveFeed, CONFIGDIR)
- #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):
- if self.pannableFeed:
- self.pannableFeed.destroy()
+ def displayFeed(self, key=None):
+ """
+ Select and display a feed. If feed, title and key are None,
+ reloads the current feed.
+ """
+ if key:
+ try:
+ old_key = self.key
+ except AttributeError:
+ old_key = None
- self.pannableFeed = hildon.PannableArea()
+ if old_key:
+ self.listing.updateUnread(self.key)
+ self.emit("feed-closed", self.key)
- self.pannableFeed.set_property('hscrollbar-policy', gtk.POLICY_NEVER)
+ self.feed = self.listing.getFeed(key)
+ self.feedTitle = self.listing.getFeedTitle(key)
+ self.key = key
- 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)
+ self.set_title(self.feedTitle)
- 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)
+ selection = self.feedList.get_selection()
+ if selection is not None:
+ selection.set_mode(gtk.SELECTION_NONE)
- vbox= gtk.VBox(False, 10)
- vbox.pack_start(self.feedList)
+ for b in self.archived_article_buttons:
+ if key == "ArchivedArticles":
+ b.show()
+ else:
+ b.hide()
- 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', bg_color) #"#333333")
- (width, height) = self.get_size()
- self.markup_renderer.set_property('wrap-width', width-20)
- 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)
- self.feedList.append_column(markup_column)
-
#self.pannableFeed.set_property("mov-mode", hildon.MOVEMENT_MODE_BOTH)
hideReadArticles = self.config.getHideReadArticles()
if hideReadArticles:
else:
articles = self.feed.getIds()
- hasArticle = False
- self.current = list()
+ self.articles[:] = []
+
+ self.feedItems.clear()
for id in articles:
- isRead = False
try:
isRead = self.feed.isEntryRead(id)
- except:
- pass
+ except Exception:
+ isRead = False
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:
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:
+ if not articles:
+ # No articles.
markup = ENTRY_TEMPLATE % (self.config.getFontSize(), "No Articles To Display")
self.feedItems.append((markup, ""))
- self.main_vbox.pack_start(self.pannableFeed)
- self.show_all()
+ self.show()
def clear(self):
self.pannableFeed.destroy()
#self.remove(self.pannableFeed)
def on_feedList_row_activated(self, treeview, path): #, column):
+ if not self.articles:
+ # There are not actually any articles. Ignore.
+ return False
+
selection = self.feedList.get_selection()
selection.set_mode(gtk.SELECTION_SINGLE)
self.feedList.get_selection().select_path(path)
#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()
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()
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)
#self.feed.saveFeed(CONFIGDIR)
self.displayFeed()
- def button_update_clicked(self, button):
+
+ def do_update_feed(self):
self.listing.updateFeed (self.key, priority=-1)
+
+ def button_update_clicked(self, button):
+ gobject.idle_add(self.do_update_feed)
def show_download_bar(self):
if not type(self.downloadDialog).__name__=="DownloadBar":
self.downloadDialog.connect("download-done", self.onDownloadDone)
self.main_vbox.pack_end(self.downloadDialog,
expand=False, fill=False)
- self.show_all()
-
+ self.downloadDialog.show()
+
def onDownloadDone(self, widget, feed):
- if feed == self.feed or feed is None:
- self.downloadDialog.destroy()
- self.downloadDialog = False
+ if feed is not None and hasattr(self, 'feed') and feed == self.feed:
self.feed = self.listing.getFeed(self.key)
self.displayFeed()
- self.updateDbusHandler.ArticleCountUpdated()
+
+ if feed is None:
+ self.downloadDialog.destroy()
+ self.downloadDialog = False
def buttonReadAllClicked(self, button):
#self.clear()
def __init__(self):
# Init the windows
self.window = hildon.StackableWindow()
- self.window.set_title(__appname__)
hildon.hildon_gtk_window_set_progress_indicator(self.window, 1)
- self.mainVbox = gtk.VBox(False,10)
+
+ self.config = Config(self.window, CONFIGDIR+"config.ini")
+
+ try:
+ self.orientation = FremantleRotation(__appname__, main_window=self.window, app=self)
+ self.orientation.set_mode(self.config.getOrientation())
+ except Exception, e:
+ logger.warn("Could not start rotation manager: %s" % str(e))
+ self.window.set_title(__appname__)
+ self.mainVbox = gtk.VBox(False,10)
+
if isfile(CONFIGDIR+"/feeds.db"):
self.introLabel = gtk.Label("Loading...")
else:
self.window.add(self.mainVbox)
self.window.show_all()
- self.config = Config(self.window, CONFIGDIR+"config.ini")
gobject.idle_add(self.createWindow)
- # This is set to try when the user interacts with the program.
- # If, after an update is complete, we discover that the
- # environment variable DBUS_STARTED_ADDRESS is set and
- # self.had_interaction is False, we quit.
- self.had_interaction = False
-
def createWindow(self):
self.category = 0
-
- 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(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"
+
+ self.introLabel.destroy()
+ self.pannableListing = hildon.PannableArea()
+
+ # The main area is a view consisting of an icon and two
+ # strings. The view is bound to the Listing's database via a
+ # TreeStore.
+
+ 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.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()
+ hildon.hildon_gtk_window_set_progress_indicator(self.window, 0)
+ gobject.idle_add(self.late_init)
+
+ 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.downloadDialog.show()
+
+ 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):
+ # Finish building the GUI.
menu = hildon.AppMenu()
# Create a button and add it to the menu
button = hildon.GtkButton(gtk.HILDON_SIZE_AUTO)
self.window.set_app_menu(menu)
menu.show_all()
-
- #self.feedWindow = hildon.StackableWindow()
- #self.articleWindow = hildon.StackableWindow()
- self.introLabel.destroy()
- self.pannableListing = hildon.PannableArea()
- 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()
- 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()
+ # Initialize the DBus interface.
+ self.dbusHandler = ServerObject(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')
- self.displayListing()
+ # Check whether auto-update is enabled.
self.autoupdate = False
- self.checkAutoUpdate()
-
- hildon.hildon_gtk_window_set_progress_indicator(self.window, 0)
- gobject.idle_add(self.late_init)
-
- def job_manager_update(self, jm, old_stats, new_stats, updated_feed):
- if (not self.downloadDialog
- and new_stats['jobs-in-progress'] + new_stats['jobs-queued'] > 0):
- self.updateDbusHandler.UpdateStarted()
+ #self.checkAutoUpdate()
- 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()
+ gobject.idle_add(self.build_feed_display)
+ gobject.idle_add(self.check_for_woodchuck)
- if self.__dict__.get ('disp', None):
- self.disp.show_download_bar ()
+ def build_feed_display(self):
+ if not hasattr(self, 'disp'):
+ self.disp = DisplayFeed(self.listing, self.config)
+ self.disp.connect("feed-closed", self.onFeedClosed)
- def onDownloadDone(self, widget, feed):
- if feed is None:
- self.downloadDialog.destroy()
- self.downloadDialog = False
- self.displayListing()
- self.updateDbusHandler.UpdateFinished()
- self.updateDbusHandler.ArticleCountUpdated()
-
- if not self.had_interaction and 'DBUS_STARTER_ADDRESS' in environ:
- print "Update complete. No interaction, started by dbus: quitting."
- self.quit()
- 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 increase_download_parallelism(self):
- # The system has been idle for a while. Enable parallel
- # downloads.
- JobManager().num_threads = 4
- gobject.source_remove (self.increase_download_parallelism_id)
- del self.increase_download_parallelism_id
- return False
-
- def system_inactivity_ind(self, idle):
- # The system's idle state changed.
- if (self.am_idle and idle) or (not self.am_idle and not idle):
- # No change.
+ def check_for_woodchuck(self):
+ if self.config.getAskedAboutWoodchuck():
return
- if not idle:
- if hasattr (self, 'increase_download_parallelism_id'):
- gobject.source_remove (self.increase_download_parallelism_id)
- del self.increase_download_parallelism_id
- else:
- self.increase_download_parallelism_id = \
- gobject.timeout_add_seconds(
- 60, self.increase_download_parallelism)
+ try:
+ import woodchuck
+ # It is already installed successfully.
+ self.config.setAskedAboutWoodchuck(True)
+ return
+ except ImportError:
+ pass
- if not idle:
- JobManager().num_threads = 1
+ note = hildon.hildon_note_new_confirmation(
+ self.window,
+ "\nFeedingIt can use Woodchuck, a network transfer "
+ + "daemon, to schedule transfers more intelligently.\n"
+ + "You can also use the FeedingIt widget for time-based\n"
+ + "updates.\n\n"
+ + "Install Woodchuck?\n")
+ note.set_button_texts("Install", "Cancel")
+ note.add_button("Learn More", 42)
- self.am_idle = idle
+ while True:
+ response = gtk.Dialog.run(note)
+ if response == 42:
+ open_in_browser("http://hssl.cs.jhu.edu/~neal/woodchuck")
+ continue
- def late_init(self):
- self.dbusHandler = ServerObject(self)
- self.updateDbusHandler = UpdateServerObject(self)
-
- jm = JobManager()
- jm.stats_hook_register (self.job_manager_update,
- run_in_main_thread=True)
- jm.num_threads = 1
- self.am_idle = False
- JobManager(True)
-
- import dbus
- bus = dbus.SystemBus()
- proxy = bus.get_object('com.nokia.mce',
- '/com/nokia/mce/signal')
- iface = dbus.Interface(proxy, 'com.nokia.mce.signal')
- iface.connect_to_signal('system_inactivity_ind',
- self.system_inactivity_ind)
+ break
+
+ note.destroy()
+
+ if response == gtk.RESPONSE_OK:
+ open_in_browser("http://maemo.org/downloads/product/raw/Maemo5/murmeltier?get_installfile")
+ self.config.setAskedAboutWoodchuck(True)
def button_markAll(self, button):
- self.had_interaction = True
for key in self.listing.getListOfFeeds():
feed = self.listing.getFeed(key)
feed.markAllAsRead()
self.displayListing()
def button_about_clicked(self, button):
- self.had_interaction = True
HeAboutDialog.present(self.window, \
__appname__, \
ABOUT_ICON, \
ABOUT_DONATE)
def button_export_clicked(self, button):
- self.had_interaction = True
opml = ExportOpmlData(self.window, self.listing)
def button_import_clicked(self, button):
- self.had_interaction = True
opml = GetOpmlData(self.window)
feeds = opml.getData()
for (title, url) in feeds:
self.displayListing()
def addFeed(self, urlIn="http://"):
- self.had_interaction = True
wizard = AddWidgetWizard(self.window, self.listing, urlIn, self.listing.getListOfCategories())
ret = wizard.run()
if ret == 2:
self.displayListing()
def button_organize_clicked(self, button):
- self.had_interaction = True
def after_closing():
self.displayListing()
SortList(self.window, self.listing, self, after_closing)
- def button_update_clicked(self, button, key):
- self.had_interaction = True
+ def do_update_feeds(self):
for k in self.listing.getListOfFeeds():
self.listing.updateFeed (k)
- #self.displayListing()
+
+ def button_update_clicked(self, button, key):
+ 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):
- self.had_interaction = True
dialog = self.config.createDialog()
dialog.connect("destroy", self.prefsClosed)
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)
# pass
def on_feedList_row_activated(self, treeview, path, column):
- self.had_interaction = True
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)
+ if key != None:
+ self.build_feed_display()
+ self.disp.displayFeed(key)
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()
+ gobject.idle_add(self.displayListing)
- def onFeedClosedTimeout(self):
- del self.feed_lock
- self.updateDbusHandler.ArticleCountUpdated()
-
def quit(self, *args):
self.window.hide()
-
- if hasattr (self, 'app_lock'):
- del self.app_lock
-
- # Wait until all slave threads have properly exited before
- # terminating the mainloop.
- jm = JobManager()
- jm.quit ()
- stats = jm.stats()
- if stats['jobs-in-progress'] == 0 and stats['jobs-queued'] == 0:
- gtk.main_quit ()
- else:
- gobject.timeout_add(500, self.quit)
-
- return False
+ gtk.main_quit ()
def run(self):
self.window.connect("destroy", self.quit)
self.button_update_clicked(None, None)
return True
- def stopUpdate(self):
- # Not implemented in the app (see update_feeds.py)
- try:
- JobManager().cancel ()
- except:
- pass
-
def getStatus(self):
status = ""
for key in self.listing.getListOfFeeds():
status = "No unread items"
return status
+ def grabFocus(self):
+ self.window.present()
+
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,))
- 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):
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()