From 93b1725820eded026cfc555efde376eb3e61f4fb Mon Sep 17 00:00:00 2001 From: jmalonzo Date: Tue, 24 Jul 2007 09:31:06 +0000 Subject: [PATCH] feed list view and feed/category event handling fixes git-svn-id: svn+ssh://svn.gnome.org/svn/straw/trunk@306 141a2093-ea25-0410-9ad2-d44d734a8f13 --- src/lib/Application.py | 20 +-- src/lib/Config.py | 20 --- src/lib/Event.py | 6 - src/lib/FeedCategoryList.py | 169 +++++++++--------- src/lib/FeedListView.py | 421 ++++++++++++++------------------------------ src/lib/ItemList.py | 26 +-- src/lib/ItemStore.py | 3 +- src/lib/OPMLImport.py | 6 +- src/lib/SummaryItem.py | 2 +- src/lib/feeds.py | 84 +++------ 10 files changed, 276 insertions(+), 481 deletions(-) diff --git a/src/lib/Application.py b/src/lib/Application.py index 43f5e44..165269e 100644 --- a/src/lib/Application.py +++ b/src/lib/Application.py @@ -199,8 +199,8 @@ class ApplicationPresenter(MVP.BasicPresenter): # self._category_selector = CategoryPresenter(view=CategoryView(widget_tree.get_widget('category_combo'))) - self._error_presenter = ErrorPresenter( - widget_tree.get_widget('statusbar_error_indicator')) + #self._error_presenter = ErrorPresenter( + # widget_tree.get_widget('statusbar_error_indicator')) self._offline_presenter = OfflineToggle( widget_tree.get_widget('offline_toggle')) @@ -252,20 +252,20 @@ class ApplicationPresenter(MVP.BasicPresenter): def _display_category_feeds(self, category, *args): self._curr_category = category - if category: +# if category: # self._feed_list_presenter.display_category_feeds(category) - self._error_presenter.display_category_error(category) - else: +# self._error_presenter.display_category_error(category) +# else: # self._feed_list_presenter.display_empty_category() - self._menufp_presenter.set_sensitive(False) + self._menufp_presenter.set_sensitive(False) #self._itemlist_presenter.display_empty_feed() - self._item_view.display_empty_feed() + self._item_view.display_empty_feed() return def _display_feed(self, feed, select_first = 1): if feed and feed.number_of_items < 1: self._item_view.display_empty_feed() - self._error_presenter.display_feed_error(feed) +# self._error_presenter.display_feed_error(feed) self._feedinfo_presenter.display(feed) #self._itemlist_presenter.display_feed_items(feed, select_first) self._menufp_presenter.set_sensitive(True) @@ -275,7 +275,7 @@ class ApplicationPresenter(MVP.BasicPresenter): #self._itemlist_presenter.display_empty_feed() self._item_view.display_empty_feed() self._feedinfo_presenter.hide() - self._error_presenter.hide() +# self._error_presenter.hide() self._menufp_presenter.set_sensitive(False) return @@ -553,7 +553,7 @@ class ApplicationView(MVP.WidgetView): uritems = urfeeds = 0 sfeeds = "feeds" listfeeds = flist.flatten_list() - for ur in [f.n_items_unread for f in listfeeds]: + for ur in [f.number_of_unread for f in listfeeds]: if ur: uritems += ur urfeeds += 1 diff --git a/src/lib/Config.py b/src/lib/Config.py index 5072505..2c23cac 100644 --- a/src/lib/Config.py +++ b/src/lib/Config.py @@ -532,26 +532,6 @@ def convert_if_necessary(config): f = open(config.straw_config_file, "r") cf = cPickle.load(f) - feeds = cf.get('feeds', None) - if feeds and feeds[0].get('_n_items_unread', None) is None: - - def _convert_feeds(feeds, parent): - import ItemStore - itemstore = ItemStore.get_instance() - for feed in feeds: - if isinstance(feed, list): - _convert_feeds(feed[1:], parent) - else: - stored = feed.get('_items_stored', -1) - if stored > -1: - cutoff = stored - else: - cutoff = config.number_of_items_stored - feed['_n_items_unread'] = itemstore.get_number_of_unread(feed['_id'], cutoff) - return feeds - - config.feeds = _convert_feeds(feeds, None) - if cf.has_key('poll_frequency'): config.poll_frequency = cf.get('poll_frequency') config.number_of_items_stored = cf.get('number_of_items_stored') diff --git a/src/lib/Event.py b/src/lib/Event.py index b62da66..5d6745b 100644 --- a/src/lib/Event.py +++ b/src/lib/Event.py @@ -46,9 +46,6 @@ class BaseSignal: self.sender = sender return -import pycallgraph -filterpy = pycallgraph.GlobbingFilter(exclude=['pycallgraph.*','*.emit_signal']) - class SignalEmitter: recorder = None @@ -92,10 +89,7 @@ class SignalEmitter: try: #if self.recorder is not None: # self.recorder.record(sc, handler) - pycallgraph.start_trace(filter=filterpy) apply(handler, args) - pycallgraph.stop_trace() - pycallgraph.make_dot_graph('event.png') except: error.log_exc("Caught an exception when trying to " "call a signal handler for " diff --git a/src/lib/FeedCategoryList.py b/src/lib/FeedCategoryList.py index 33c6418..5d74596 100644 --- a/src/lib/FeedCategoryList.py +++ b/src/lib/FeedCategoryList.py @@ -18,10 +18,10 @@ You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. """ +import gobject import feeds from StringIO import StringIO import Config -import Event import OPMLImport import error import locale @@ -31,14 +31,17 @@ import types PSEUDO_ALL_KEY = 'ALL' PSEUDO_UNCATEGORIZED_KEY = 'UNCATEGORIZED' -class FeedCategoryList(object, Event.SignalEmitter): +class FeedCategoryList(gobject.GObject): + + __gsignals__ = { + 'added' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,)), + 'deleted' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,)), + 'category-changed' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), + 'pseudo-changed' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()) + } + def __init__(self): - Event.SignalEmitter.__init__(self) - self.initialize_slots(Event.FeedCategoryChangedSignal, - Event.FeedCategorySortedSignal, - Event.FeedCategoryAddedSignal, - Event.FeedCategoryRemovedSignal, - Event.FeedCategoryListLoadedSignal) + gobject.GObject.__init__(self) # have to define this here so the titles can be translated PSEUDO_TITLES = {PSEUDO_ALL_KEY: _('All'), PSEUDO_UNCATEGORIZED_KEY: _('Uncategorized')} @@ -83,17 +86,15 @@ class FeedCategoryList(object, Event.SignalEmitter): fid = f['id'] from_sub = f['from_subscription'] feed = self._feedlist.get_feed_with_id(fid) - if feed is not None: + if feed and not pseudo_key: # we deal with pseudos later if feed in fc.feeds: error.log("%s (%d) was already in %s, skipping" % (str(feed), fid, str(fc))) continue - fc.append_feed(feed, from_sub) - categorized[feed] = True + categorized[feed] = True # User categories: connect pseudos later if not pseudo_key: - fc.signal_connect(Event.FeedCategoryChangedSignal, - self.category_changed) + fc.connect('changed', self.category_changed) self._user_categories.append(fc) # just in case we've missed any feeds, go through the list # and add to the pseudocategories. cache the feed list of all_category @@ -113,9 +114,7 @@ class FeedCategoryList(object, Event.SignalEmitter): if pseudos_changed: self.save_data() for cat in self.pseudo_categories: - cat.signal_connect( - Event.FeedCategoryChangedSignal, self.pseudo_category_changed) - self.emit_signal(Event.FeedCategoryListLoadedSignal(self)) + cat.connect('changed', self.pseudo_category_changed) def save_data(self): Config.get_instance().categories = [ @@ -123,7 +122,7 @@ class FeedCategoryList(object, Event.SignalEmitter): def pseudo_category_changed(self, signal): self.save_data() - self.emit_signal(signal) + self.emit('pseudo-changed') def category_changed(self, signal): if signal.feed is not None: @@ -140,7 +139,7 @@ class FeedCategoryList(object, Event.SignalEmitter): except ValueError: pass self.save_data() - self.emit_signal(signal) + self.emit('category-changed') def feed_deleted(self, feedlist, feed): for c in self: @@ -163,7 +162,8 @@ class FeedCategoryList(object, Event.SignalEmitter): if category and category not in self.pseudo_categories: category.extend_feed(feeds, from_sub) else: - self.un_category.extend_feed(feeds, from_sub) + print 'feed imported ', feeds + #self.un_category.extend_feed(feeds, from_sub) self.all_category.extend_feed(feeds, from_sub) return @@ -214,35 +214,35 @@ class FeedCategoryList(object, Event.SignalEmitter): return self.CategoryIterator(self) def add_category(self, category): - category.signal_connect(Event.FeedCategoryChangedSignal, - self.category_changed) + category.connect('changed', self.category_changed) self._user_categories.append(category) auxlist = [(x.title.lower(),x) for x in self._user_categories] auxlist.sort() self._user_categories = [x[1] for x in auxlist] - self.emit_signal(Event.FeedCategoryAddedSignal(self, category)) self.save_data() + self.emit('added', category) def remove_category(self, category): for feed in category.feeds: category.remove_feed(feed) - category.signal_disconnect(Event.FeedCategoryChangedSignal, - self.category_changed) self._user_categories.remove(category) - self.emit_signal(Event.FeedCategoryRemovedSignal(self, category)) self.save_data() + self.emit('deleted', category) # It might be good to have a superclass FeedCategorySubscription or something # so we could support different formats. However, I don't know of any other # relevant format used for this purpose, so that can be done later if needed. # Of course, they could also just implement the same interface. -class OPMLCategorySubscription(object, Event.SignalEmitter): +class OPMLCategorySubscription(gobject.GObject): REFRESH_DEFAULT = -1 + __gsignals__ = { + 'changed' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), + 'updated' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()) + } + def __init__(self, location=None): - Event.SignalEmitter.__init__(self) - self.initialize_slots(Event.FeedCategoryChangedSignal, - Event.SubscriptionContentsUpdatedSignal) + gobject.GObject.__init__(self) self._location = location self._username = None self._password = None @@ -258,7 +258,7 @@ class OPMLCategorySubscription(object, Event.SignalEmitter): return self._location def fset(self, location): self._location = location - self.emit_signal(Event.FeedCategoryChangedSignal(self)) + self.emit('changed') return property(**locals()) @apply @@ -268,7 +268,7 @@ class OPMLCategorySubscription(object, Event.SignalEmitter): return self._username def fset(self, username): self._username = username - self.emit_signal(Event.FeedCategoryChangedSignal(self)) + self.emit('changed') return property(**locals()) @apply @@ -278,7 +278,7 @@ class OPMLCategorySubscription(object, Event.SignalEmitter): return self._password def fset(self): self._password = password - self.emit_signal(Event.FeedCategoryChangedSignal(self)) + self.emit('changed') return property(**locals()) @apply @@ -288,7 +288,7 @@ class OPMLCategorySubscription(object, Event.SignalEmitter): return self._frequency def fset(self, freq): self._frequency = freq - self.emit_signal(Event.FeedCategoryChangedSignal(self)) + self.emit('changed') return property(**locals()) @apply @@ -298,7 +298,7 @@ class OPMLCategorySubscription(object, Event.SignalEmitter): return self._last_poll def fset(self, last_poll): self._last_poll = last_poll - self.emit_signal(Event.FeedCategoryChangedSignal(self)) + self.emit('changed') return property(**locals()) @apply @@ -308,7 +308,7 @@ class OPMLCategorySubscription(object, Event.SignalEmitter): return self._error def fset(self, error): self._error = error - self.emit_signal(Event.FeedCategoryChangedSignal(self)) + self.emit('changed') return property(**locals()) def parse(self, data): @@ -318,7 +318,7 @@ class OPMLCategorySubscription(object, Event.SignalEmitter): updated = contents == self._contents self._contents = contents if updated: - self.emit_signal(Event.SubscriptionContentsUpdatedSignal(self)) + self.emit('updated') return @property @@ -377,11 +377,16 @@ class CategoryMember(object): self._from_subscription = p return property(**locals()) -class FeedCategory(list, Event.SignalEmitter): +class FeedCategory(gobject.GObject): + + __gsignals__ = { + 'changed' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, + (gobject.TYPE_PYOBJECT,)) + } + def __init__(self, title=""): - Event.SignalEmitter.__init__(self) - self.initialize_slots(Event.FeedCategoryChangedSignal, - Event.FeedCategorySortedSignal) + gobject.GObject.__init__(self) + self.feedlist = [] self._title = title self._subscription = None @@ -392,7 +397,7 @@ class FeedCategory(list, Event.SignalEmitter): return self._title def fset(self, title): self._title = title - self.emit_signal(Event.FeedCategoryChangedSignal(self)) + self.emit('changed', None) return property(**locals()) @apply @@ -401,19 +406,10 @@ class FeedCategory(list, Event.SignalEmitter): def fget(self): return self._subscription def fset(self, sub): - if self._subscription: - self._subscription.signal_disconnect( - Event.FeedCategoryChangedSignal, self._subscription_changed) - self._subscription.signal_disconnect( - Event.SubscriptionContentsUpdatedSignal, - self._subscription_contents_updated) - if sub: - sub.signal_connect(Event.FeedCategoryChangedSignal, - self._subscription_changed) - sub.signal_connect(Event.SubscriptionContentsUpdatedSignal, - self._subscription_contents_updated) self._subscription = sub - self.emit_signal(Event.FeedCategoryChangedSignal(self)) + self._subscription.connect('changed', self._subscription_changed) + self._subscription.connect('updated', self._subscription_contents_updated) + self.emit('changed', None) return property(**locals()) def read_contents_from_subscription(self): @@ -429,20 +425,20 @@ class FeedCategory(list, Event.SignalEmitter): toadd, allfeeds.keys()) newfeeds = [feeds.Feed.create_new_feed(sfdict[f], f) for f in nonexisting] - feedlist.extend(self, newfeeds, from_sub=True) # will call extend_feed + feedlist.extend(self.feedlist, newfeeds, from_sub=True) # will call extend_feed self.extend_feed([allfeeds[f] for f in existing], True) for f in toremove: index = self.index_feed(allfeeds[f]) - member = self[index] + member = self.feedlist[index] if member.from_subscription: self.remove_feed(allfeeds[f]) return - def _subscription_changed(self, signal): - self.emit_signal(Event.FeedCategoryChangedSignal(self)) + def _subscription_changed(self, *args): + self.emit('changed', None) - def _subscription_contents_updated(self, signal): + def _subscription_contents_updated(self, *args): self.read_contents_from_subscription() def __str__(self): @@ -453,47 +449,46 @@ class FeedCategory(list, Event.SignalEmitter): def append(self, value): error.log("warning, probably should be using append_feed?") - list.append(self, value) - self.emit_signal(Event.FeedCategoryChangedSignal(self, feed=value.feed)) + self.feedlist.append(value) + self.emit('changed', value.feed) def append_feed(self, value, from_sub): - list.append(self, CategoryMember(value, from_sub)) - self.emit_signal(Event.FeedCategoryChangedSignal(self, feed=value)) + self.feedlist.append(CategoryMember(value, from_sub)) + self.emit('changed', value) def extend_feed(self, values, from_sub): - list.extend(self, [CategoryMember(v, from_sub) for v in values]) - self.emit_signal(Event.FeedCategoryChangedSignal(self)) + self.feedlist.extend([CategoryMember(v, from_sub) for v in values]) + self.emit('changed', None) def insert(self, index, value): error.log("warning, probably should be using insert_feed?") - list.insert(self, index, value) - self.emit_signal(Event.FeedCategoryChangedSignal(self, feed=value.feed)) + self.feedlist.insert(index, value) + self.emit('changed', value.feed) def insert_feed(self, index, value, from_sub): - list.insert(self, index, CategoryMember(value, from_sub)) - self.emit_signal(Event.FeedCategoryChangedSignal(self, feed=value)) + self.feedlist.insert(index, CategoryMember(value, from_sub)) + self.emit('changed', value) def remove(self, value): - list.remove(self, value) - self.emit_signal(Event.FeedCategoryChangedSignal(self, feed=value.feed)) + self.feedlist.remove(value) + self.emit('changed', value.feed) def remove_feed(self, value): - for index, member in enumerate(self): + for index, member in enumerate(self.feedlist): if member.feed is value: - del self[index] + del self.feedlist[index] break else: raise ValueError(value) - self.emit_signal(Event.FeedCategoryChangedSignal(self, feed=value)) + self.emit('changed', value) def reverse(self): - list.reverse(self) - self.emit_signal(Event.FeedCategorySortedSignal(self, - reverse=True)) + self.feedlist.reverse() + self.emit('changed') # reverse=True)) def index_feed(self, value): - for index, f in enumerate(self): - if self[index].feed is value: + for index, f in enumerate(self.feedlist): + if self.feedlist[index].feed is value: return index raise ValueError(value) @@ -504,12 +499,12 @@ class FeedCategory(list, Event.SignalEmitter): def sort(self, indices=None): if not indices or len(indices) == 1: - self[:] = self._sort_dsu(self) + self.feedlist[:] = self._sort_dsu(self.feedlist) else: items = self._sort_dsu(indices) for i,x in enumerate(items): - list.__setitem__(self, indices[i], items[i]) - self.emit_signal(Event.FeedCategorySortedSignal(self)) + list.__setitem__(self.feedlist, indices[i], items[i]) + self.emit('changed') def move_feed(self, source, target): if target > source: @@ -518,8 +513,8 @@ class FeedCategory(list, Event.SignalEmitter): return t = self[source] del self[source] - list.insert(self, target, t) - self.emit_signal(Event.FeedCategoryChangedSignal(self)) + self.feedlist.insert(target, t) + self.emit('changed') def dump(self): head = {'title': self.title} @@ -527,23 +522,23 @@ class FeedCategory(list, Event.SignalEmitter): head['subscription'] = self.subscription.dump() return [head] + [ {'id': f.feed.id, 'from_subscription': f.from_subscription} - for f in self] + for f in self.feedlist] @property def feeds(self): - return [f.feed for f in self] + return [f.feed for f in self.feedlist] def __eq__(self, ob): if isinstance(ob, types.NoneType): return 0 elif isinstance(ob, FeedCategory): - return self.title == ob.title and list.__eq__(self, ob) + return self.title == ob.title and list.__eq__(self.feedlist, ob) else: raise NotImplementedError def __contains__(self, item): error.log("warning, should probably be querying the feeds property instead?") - return list.__contains__(self, item) + return list.__contains__(self.feedlist, item) class PseudoCategory(FeedCategory): def __init__(self, title="", key=None): diff --git a/src/lib/FeedListView.py b/src/lib/FeedListView.py index ea4c6b0..3001915 100644 --- a/src/lib/FeedListView.py +++ b/src/lib/FeedListView.py @@ -41,6 +41,94 @@ class Column: pixbuf, name, foreground, object = range(4) +class TreeNodeAdapter (gobject.GObject): + ''' A Node Adapter which encapsulates either a Category or a Feed ''' + + __gsignals__ = { + 'feed-changed' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, + (gobject.TYPE_PYOBJECT,)), + 'category-changed' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, + (gobject.TYPE_PYOBJECT,)) + } + + def __init__(self, object): + gobject.GObject.__init__(self) + self.obj = object + filename = os.path.join(utils.find_image_dir(), 'feed.png') + self.default_pixbuf = gtk.gdk.pixbuf_new_from_file(filename) + if isinstance(self.obj, feeds.Feed): + self.obj.connect('changed', self.feed_changed_cb) + elif isinstance(self.obj, FeedCategoryList.FeedCategory): + self.obj.connect('changed', self.category_changed_cb) + + def feed_changed_cb(self, feed): + self.emit('feed-changed', feed) + + def category_changed_cb(self, category): + self.emit('category-changed', category) + + def has_children(self): + ''' Checks if the node has children. Essentially this means the object + is a category.''' + has_child = False + try: + has_child = self.obj.feeds and True or False + except AttributeError: + has_child = False + return has_child + + @property + def title(self): + ''' The title of the node be it a category or a feed ''' + return self.obj.title + + @property + def num_unread_items(self): + ''' The number of unread items of the feed or if it's a category, + the aggregate number of unread items of the feeds belonging to the + category.''' + unread_items = 0 + try: + unread_items = self.obj.number_of_unread + except AttributeError: + unread_items = reduce(lambda a,b: a.number_of_unread + b.number_of_unread, + self.obj.feeds) + print "number of unread of category is", unread_items + return unread_items + + @property + def pixbuf(self): + ''' gets the pixbuf to display according to the status of the feed ''' + widget = gtk.Label() + # ignore why above is a gtk.Label. We just need + # gtk.Widget.render_icon to get a pixbuf out of a stock image. + # Fyi, gtk.Widget is abstract that is why we need a subclass to do + + # this for us. + try: + if self.obj.process_status is not feeds.Feed.STATUS_IDLE: + return widget.render_icon(gtk.STOCK_EXECUTE. gtk.ICON_SIZE_MENU) + elif self.obj.error: + return widget.render_icon(gtk.STOCK_DIALOG_ERROR, gtk.ICON_SIZE_MENU) + except AttributeError: + return widget.render_icon(gtk.STOCK_DIRECTORY, gtk.ICON_SIZE_MENU) + return self.default_pixbuf + + @property + def feed(self): + ''' An alias to a Feed object ''' + if not isinstance(self.obj, feeds.Feed): + raise TypeError, _("object is not of a Feed") + return self.obj + + @property + def category(self): + ''' An alias to a Category object ''' + if not isinstance(self.obj, FeedCategoryList.FeedCategory): + raise TypeError, _("object is not a Category") + return self.obj + + class FeedListPopup: ''' Abstracts a popup widget ''' @@ -82,44 +170,56 @@ class FeedListModel: self.store = gtk.TreeStore(gtk.gdk.Pixbuf, str, str, gobject.TYPE_PYOBJECT) # unread, weight, status_flag feed object, allow_children # str, int, 'gboolean', gobject.TYPE_PYOBJECT, 'gboolean' - - self._init_model() - - def __getattribute__(self, name): - attr = None - try: - attr = getattr(self, name) - except AttributeError, ae: - attr = getattr(self.store, name) - else: - return attr - - def _init_model(self): fclist = FeedCategoryList.get_instance() - parent = None # build the user categories and its feeds for category in fclist.user_categories: + print 'adding category ', category.title, ' ', category.feeds + parent = None parent = self.store.append(parent) node_adapter = TreeNodeAdapter(category) + node_adapter.connect('category-changed', self.category_changed_cb) self.store.set(parent, Column.pixbuf, node_adapter.pixbuf, Column.name, node_adapter.title, Column.object, node_adapter) for f in category.feeds: rowiter = self.store.append(parent) node_adapter = TreeNodeAdapter(f) + node_adapter.connect('feed-changed', self.feed_changed_cb) self.store.set(rowiter, Column.pixbuf, node_adapter.pixbuf, Column.name, node_adapter.title, Column.foreground, self.get_foreground(node_adapter.num_unread_items), Column.object, node_adapter) # maybe use create_adapter(f) here? - # build the feeds that do not belong to any other category + # build the feeds that do not belong to any other user category for feed in fclist._un_category.feeds: node_adapter = TreeNodeAdapter(feed) + node_adapter.connect('feed-changed', self.feed_changed_cb) self.store.append(None, [node_adapter.pixbuf, node_adapter.title, self.get_foreground(node_adapter.num_unread_items), node_adapter]) - print "finished initializing feed list vie model" + print "finished initializing feed list via model" + + def __getattribute__(self, name): + attr = None + try: + attr = getattr(self, name) + except AttributeError, ae: + attr = getattr(self.store, name) + else: + return attr + + def category_changed_cb(self, node_adapter, category): + # XXX What changes do we need here? new feed? updated subscription? + print node_adapter + + def feed_changed_cb(self, node_adapter, feed): + match = filter(lambda row: row[Column.object] == node_adapter,self.store) + path = self.store.get_path(match.pop().iter) + self.store[path] = [node_adapter.pixbuf, node_adapter.title, + self.get_foreground(node_adapter.num_unread_items), + node_adapter] + print "node_adapter changed ....", node_adapter.number_of_unread def get_foreground(self, unread): ''' gets the foreground color according to the number of unread items''' @@ -178,17 +278,19 @@ class FeedsView(MVP.WidgetView): def _on_popup_menu(self, treeview, *args): self._popup.popup(None, None, None, 0, 0) - def foreach_selected(self, func): selection = self._widget.get_selection() (model, pathlist) = selection.get_selected_rows() iters = [model.get_iter(path) for path in pathlist] - for treeiter in iters: - object = model.get_value(treeiter, Column.object) - func(object, model, treeiter) + try: + for treeiter in iters: + object = model.get_value(treeiter, Column.object) + func(object, model, treeiter) + except TypeError, te: + ## XXX maybe object is a category + print te return - def _on_button_press_event(self, treeview, event): retval = 0 if event.button == 3: @@ -215,18 +317,17 @@ class FeedsView(MVP.WidgetView): #PollManager.get_instance().poll([self._curr_feed]) return - def on_menu_stop_poll_selected_activate(self, *args): - self.foreach_selected(lambda o,*args: o.router.stop_polling()) + self.foreach_selected(lambda o,*args: o.feed.router.stop_polling()) def on_menu_mark_all_as_read_activate(self, *args): - self.foreach_selected(lambda o,*args: o.mark_all_read()) + self.foreach_selected(lambda o,*args: o.feed.mark_all_read()) def on_remove_selected_feed(self, *args): def remove(*args): (object, model, treeiter) = args model.remove(treeiter) - #feedlist = FeedList.get_instance() + #feedlist = FeedList.get_instance() XXXX #idx = feedlist.index(object) #del feedlist[idx] self.foreach_selected(remove) @@ -242,7 +343,12 @@ class FeedsView(MVP.WidgetView): self._presenter.sort_category(reverse=True) def on_display_properties_feed(self, *args): - self._presenter.show_feed_properties() + selection = self._widget.get_selection() + (model, pathlist) = selection.get_selected_rows() + iters = [model.get_iter(path) for path in pathlist] + path = pathlist.pop() + node_adapter = self.model.model[path][Column.object] + self._presenter.show_feed_information(node_adapter.feed) return def select_first_feed(self): @@ -331,7 +437,7 @@ class FeedsView(MVP.WidgetView): treerow = self._model[model.get_path(nextiter)] while(treerow): feedrow = treerow[Column.OBJECT] - if feedrow.feed.n_items_unread: + if feedrow.feed.number_of_unread: self._view.set_cursor(treerow.iter) has_unread = True break @@ -383,7 +489,7 @@ class FeedsPresenter(MVP.BasicPresenter): def select_previous_feed(self): self.view.select_previous_feed() - # def _sort_func(self, model, a, b): + def _sort_func(self, model, a, b): """ Sorts the feeds lexically. @@ -393,7 +499,7 @@ class FeedsPresenter(MVP.BasicPresenter): the iter2 row, 0 if the rows are equal, or 1 if the iter1 row should come after the iter2 row. """ - """ retval = 0 + retval = 0 fa = model.get_value(a, Column.OBJECT) fb = model.get_value(b, Column.OBJECT) @@ -409,259 +515,6 @@ class FeedsPresenter(MVP.BasicPresenter): # self._curr_category.reverse() # return - def show_feed_properties(self): - FeedPropertiesDialog.show_feed_properties(None, self._curr_feed) - return - - - def _display_feeds(self, feeds, parent=None): - def _connect_adapter(adapter, feedindex): - adapter.signal_connect(Event.ItemsAddedSignal, - self._adapter_updated_handler, feedindex) - adapter.signal_connect(Event.FeedPolledSignal, - self._adapter_updated_handler, feedindex) - adapter.signal_connect(Event.FeedStatusChangedSignal, - self._adapter_updated_handler, feedindex) - adapter.signal_connect(Event.ItemReadSignal, - self._adapter_updated_handler, feedindex) - adapter.signal_connect(Event.AllItemsReadSignal, - self._adapter_updated_handler, feedindex) - curr_feed_iter = None - for f in feeds: - adapter = create_adapter(f) - rowiter = self._model.append(parent) - self._model.set(rowiter, - Column.NAME, adapter.title, - Column.OBJECT, adapter) - - idx = self._curr_category.index_feed(adapter.feed) - _connect_adapter(adapter, idx) # we need this to get the rest of the data - new_string, new = adapter.unread - weight = (pango.WEIGHT_NORMAL, pango.WEIGHT_BOLD)[new > 0] - status, pixbuf = adapter.status_icon - self._model.set(rowiter, - Column.UNREAD, new_string, - Column.BOLD, weight, - Column.STATUS_FLAG, status, - Column.STATUS_PIXBUF, pixbuf, - Column.ALLOW_CHILDREN, False) - if adapter.feed is self._curr_feed: - curr_feed_iter = rowiter - return curr_feed_iter - - def _disconnect(self, model, path, iter, user_data=None): - ob = model[path][Column.OBJECT] - self._disconnect_adapter(ob) - if not len(model): - return True - return False - - def _disconnect_adapter(self, adapter): - adapter.disconnect() - adapter.signal_disconnect(Event.ItemsAddedSignal, - self._adapter_updated_handler) - adapter.signal_disconnect(Event.FeedPolledSignal, - self._adapter_updated_handler) - adapter.signal_disconnect(Event.FeedStatusChangedSignal, - self._adapter_updated_handler) - del adapter - - def _adapter_updated_handler(self, signal, feed_index): - self._update_adapter_view(signal.sender, feed_index) - - def _update_adapter_view(self, adapter, feed_index): - new = adapter.unread - row = self._model[feed_index] - row[Column.NAME] = adapter.title - row[Column.UNREAD] = new[0] - row[Column.BOLD] = (pango.WEIGHT_NORMAL, pango.WEIGHT_BOLD)[new[1] > 0] - row[Column.OBJECT] = adapter - status, pixbuf = adapter.status_icon - row[Column.STATUS_FLAG] = status - if pixbuf: - row[Column.STATUS_PIXBUF] = pixbuf - self._view.queue_draw() - - def _feeds_changed(self, signal): - self._feed_view_update(signal.feed) - - def _feed_detail_changed(self, signal): - self._feed_view_update(signal.sender) - - def _feed_item_read(self, signal): - path = (0,) - selection = self._view.get_selection() - selected_row = selection.get_selected() - if selected_row: - model, treeiter = selected_row - path = model.get_path(treeiter) - treerow = self._model[path] - adapter = treerow[Column.OBJECT] - self._update_adapter_view(adapter, path) - - def _feed_all_items_read(self, signal): - pass - - def _feed_view_update(self, feed): - for index, f in enumerate(self._model): - adapter = self._model[index][Column.OBJECT] - if adapter.feed is feed: - self._update_adapter_view(adapter, index) - break - return - - def _feeds_sorted_cb(self, signal): - self.model.set_sort_func(Column.NAME, self._sort_func) - if not signal.descending: - self.model.set_sort_column_id(Column.NAME, gtk.SORT_ASCENDING) - else: - self.model.set_sort_column_id(Column.NAME, gtk.SORT_DESCENDING) - self.model.sort_column_changed() - return - - def _fcategory_changed_cb(self,signal): - if signal.sender is self._curr_category: - self._curr_category = signal.sender - self.display_category_feeds(self._curr_category) - return - - def move_feed(self, sidx, tidx): - self._curr_category.move_feed(sidx, tidx) + def show_feed_information(self, feed): + FeedPropertiesDialog.show_feed_properties(None, feed) return - -class DisplayAdapter(object, Event.SignalEmitter): - - def __init__(self, ob): - self._ob = ob - Event.SignalEmitter.__init__(self) - self.initialize_slots(Event.ItemReadSignal, - Event.ItemsAddedSignal, - Event.AllItemsReadSignal, - Event.FeedPolledSignal, - Event.FeedStatusChangedSignal) - - def equals(self, ob): - return self._ob is ob - -class FeedDisplayAdapter(DisplayAdapter): - - def __init__(self, ob): - DisplayAdapter.__init__(self, ob) - ob.signal_connect(Event.ItemReadSignal, self.resend_signal) - ob.signal_connect(Event.ItemsAddedSignal, self.resend_signal) - ob.signal_connect(Event.AllItemsReadSignal, self.resend_signal) - ob.signal_connect(Event.FeedPolledSignal, self.resend_signal) - ob.signal_connect(Event.FeedStatusChangedSignal, self.resend_signal) - - def disconnect(self): - self._ob.signal_disconnect( - Event.ItemReadSignal, self.resend_signal) - self._ob.signal_disconnect( - Event.ItemsAddedSignal, self.resend_signal) - self._ob.signal_disconnect( - Event.AllItemsReadSignal, self.resend_signal) - self._ob.signal_disconnect( - Event.FeedPolledSignal, self.resend_signal) - self._ob.signal_disconnect( - Event.FeedStatusChangedSignal, self.resend_signal) - - def resend_signal(self, signal): - new = copy.copy(signal) - new.sender = self - self.emit_signal(new) - - @property - def title(self): - return self._ob.title - - @property - def unread(self): - nu = self._ob.n_items_unread - if nu != 0: - return ("%s" % nu, nu) - else: - return ("", nu) - - @property - def status_icon(self): - if self._ob.process_status is not Feed.Feed.STATUS_IDLE: - return (1, gtk.STOCK_EXECUTE) - elif self._ob.error: - return (1, gtk.STOCK_DIALOG_ERROR) - return (0, None) - - @property - def feed(self): - return self._ob - - contents = property(lambda x: None) - open = property(lambda x: None) -""" - -class TreeNodeAdapter: - ''' A Node Adapter which encapsulates either a Category or a Feed ''' - - def __init__(self, object): - self.obj = object - filename = os.path.join(utils.find_image_dir(), 'feed.png') - self.default_pixbuf = gtk.gdk.pixbuf_new_from_file(filename) - - def has_children(self): - ''' Checks if the node has children. Essentially this means the object - is a category.''' - has_child = False - try: - has_child = self.obj.feeds and True or False - except AttributeError: - has_child = False - return has_child - - @property - def title(self): - ''' The title of the node be it a category or a feed ''' - return self.obj.title - - @property - def num_unread_items(self): - ''' The number of unread items of the feed or if it's a category, - the aggregate number of unread items of the feeds belonging to the - category.''' - unread_items = 0 - try: - unread_items = self.obj.n_items_unread - except AttributeError: - unread_items = reduce(lambda a,b: a.n_items_unread + b.n_items_unread, - self.obj.feeds) - print "number of unread of category is", unread_items - return unread_items - - @property - def pixbuf(self): - ''' gets the pixbuf to display according to the status of the feed ''' - widget = gtk.Label() - # ignore why above is a gtk.Label. We just need - # gtk.Widget.render_icon to get a pixbuf out of a stock image. - # Fyi, gtk.Widget is abstract that is why we need a subclass to do - # this for us. - try: - if self.obj.process_status is not feeds.Feed.STATUS_IDLE: - return widget.render_icon(gtk.STOCK_EXECUTE. gtk.ICON_SIZE_MENU) - elif self.obj.error: - return widget.render_icon(gtk.STOCK_DIALOG_ERROR, gtk.ICON_SIZE_MENU) - except AttributeError: - return widget.render_icon(gtk.STOCK_DIRECTORY, gtk.ICON_SIZE_MENU) - return self.default_pixbuf - - @property - def feed(self): - ''' An alias to a Feed object ''' - if not isinstance(self.obj, feeds.Feed): - raise TypeError, _("object is not of a Feed") - return self.obj - - @property - def category(self): - ''' An alias to a Category object ''' - if not isinstance(self.obj, FeedCategoryList.FeedCategory): - raise TypeError, _("object is not a Category") - return self.obj diff --git a/src/lib/ItemList.py b/src/lib/ItemList.py index 5ccb41f..ad2c34b 100644 --- a/src/lib/ItemList.py +++ b/src/lib/ItemList.py @@ -226,6 +226,7 @@ class ItemListPresenter(ItemsPresenter): if nodes: obj = nodes.pop() try: + print "displaying items of feed ", obj.feed self.display_feed_items(obj.feed) except TypeError: ## XXX display child items of Category @@ -320,21 +321,20 @@ class ItemListPresenter(ItemsPresenter): def display_feed_items(self, feed, select_first=1): redisplay = self._selected_feed is feed - if self._selected_feed and not redisplay: - self._selected_feed.signal_disconnect(Event.RefreshFeedDisplaySignal, - self._feed_order_changed) - self._selected_feed.signal_disconnect(Event.AllItemsReadSignal, - self._all_items_read) - self._selected_feed.signal_disconnect(Event.ItemReadSignal, - self._item_read) + #if self._selected_feed and not redisplay: + # self._selected_feed.signal_disconnect(Event.RefreshFeedDisplaySignal, + # self._feed_order_changed) + # self._selected_feed.signal_disconnect(Event.AllItemsReadSignal, + # self._all_items_read) + # self._selected_feed.signal_disconnect(Event.ItemReadSignal, + # self._item_read) self._selected_feed = feed if not redisplay: - self._selected_feed.signal_connect(Event.RefreshFeedDisplaySignal, - self._feed_order_changed) - self._selected_feed.signal_connect(Event.AllItemsReadSignal, - self._all_items_read) - self._selected_feed.signal_connect(Event.ItemReadSignal, - self._item_read) + #self._selected_feed.connect(Event.RefreshFeedDisplaySignal, + # self._feed_order_changed) + self._selected_feed.connect('items_read', self._all_items_read) + #self._selected_feed.signal_connect(Event.ItemReadSignal, + # self._item_read) count = self._render_feed(self._selected_feed) if not redisplay and count: if not self.select_next_unread_item(): diff --git a/src/lib/ItemStore.py b/src/lib/ItemStore.py index 1730f3e..566b5e2 100644 --- a/src/lib/ItemStore.py +++ b/src/lib/ItemStore.py @@ -224,7 +224,8 @@ class MyDB: items.append(item) except Exception, ex: txn.abort() - log(str(ex)) + raise + #XXX log(str(ex)) else: txn.commit() return items diff --git a/src/lib/OPMLImport.py b/src/lib/OPMLImport.py index 763c99e..ed38ed9 100644 --- a/src/lib/OPMLImport.py +++ b/src/lib/OPMLImport.py @@ -91,7 +91,9 @@ def import_opml(filename,category=None): opml = read(fstream) if not opml: return - feeds = [feed.access_info[0] for feed in feedlist] - newitems = [feeds.Feed.create_new_feed(b.text, b.url) for b in opml if b.url not in feeds] + listfeeds = [feed.access_info[0] for feed in feedlist] + newitems = [feeds.Feed.create_new_feed(b.text, b.url) for b in opml if b.url not in listfeeds] + + print category, newitems feedlist.extend(category, newitems) return diff --git a/src/lib/SummaryItem.py b/src/lib/SummaryItem.py index 56e05f8..0fc6706 100644 --- a/src/lib/SummaryItem.py +++ b/src/lib/SummaryItem.py @@ -110,7 +110,7 @@ class SummaryItem(gobject.GObject): def _add_image(self, image_name, restore): self.imageCache.add_refer(image_name, restore, self) - self._images[image_name] = self.imageCache.cache[image_name] + self._images[image_name] = self.imageCache[image_name] def add_image(self, image_name): self._add_image(image_name, restore = False) diff --git a/src/lib/feeds.py b/src/lib/feeds.py index a483c30..27feca6 100644 --- a/src/lib/feeds.py +++ b/src/lib/feeds.py @@ -1,6 +1,7 @@ import locale import gobject import FeedDataRouter +import ItemStore import Config from error import log @@ -19,17 +20,6 @@ class FeedList(gobject.GObject): def __init__(self, init_seq = []): gobject.GObject.__init__(self) self.feedlist = [] - #self.initialize_slots(Event.FeedsChangedSignal, - # Event.FeedDeletedSignal, - # Event.FeedCreatedSignal, - # Event.AllItemsReadSignal, - # Event.ItemReadSignal, - # Event.ItemsAddedSignal, - # Event.FeedPolledSignal, - # Event.FeedStatusChangedSignal, - # Event.FeedErrorStatusChangedSignal, - # Event.FeedsImportedSignal, - # Event.FeedDetailChangedSignal) self._loading = False def __iter__(self): @@ -39,15 +29,11 @@ class FeedList(gobject.GObject): def _load(feedlist, parent): for df in feedlist: if isinstance(df, list): - #fc = straw.FeedCategory() - #fc.undump(df[0]) - #self.append(parent, fc) _load(df[1:], parent) else: f = Feed.create_empty_feed() f.undump(df) self.append(parent, f) - self._loading = True feedlist = Config.get_instance().feeds if feedlist: @@ -55,11 +41,8 @@ class FeedList(gobject.GObject): self._loading = False self.emit('changed') - def _forward_signal(self, signal): - self.emit_signal(signal) - def connect_signals(self, ob): - ob.connect('info-updated', self.feed_detail_changed) + ob.connect('changed', self.feed_detail_changed) # these signals are forwarded so that listeners who just want to # listen for a specific event regardless of what feed it came from can @@ -151,7 +134,7 @@ class FeedList(gobject.GObject): def __hash__(self): h = 0 - for item in self: + for item in self.feedlist: h ^= hash(item) return h @@ -193,7 +176,7 @@ class Feed(gobject.GObject): '_channel_title', '_channel_link', '_channel_copyright', 'channel_lbd', 'channel_editor', 'channel_webmaster', 'channel_creator','_error', '_process_status', 'router', 'sticky', '_parent', - '_items_stored', '_poll_freq', '_last_poll', '_n_items_unread') + '_items_stored', '_poll_freq', '_last_poll','_n_items_unread') __save_fields = (('_title', ""), ('_location', ""), ('_username', ""), ('_password', ""), ('_id', ""), @@ -203,13 +186,11 @@ class Feed(gobject.GObject): ('_items_stored', DEFAULT), ('_poll_freq', DEFAULT), ('_last_poll', 0), - ('_n_items_unread', 0)) + ('_n_items_unread',0)) + __gsignals__ = { - 'info-updated' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), - 'error-occurred' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), - 'process-status-changed' : (gobject.SIGNAL_RUN_LAST, - gobject.TYPE_NONE, ()), + 'changed' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), 'poll-done' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), 'items-updated' :(gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,)), @@ -238,14 +219,14 @@ class Feed(gobject.GObject): self._password = password self._parsed = None self._error = None + self._n_items_unread = 0 self._process_status = self.STATUS_IDLE self.router = FeedDataRouter.FeedDataRouter(self) self._parent = None self._items_stored = Feed.DEFAULT self._poll_freq = Feed.DEFAULT self._last_poll = 0 - self._n_items_unread = 0 - self._items = None + self._items = set() self._items_loaded = False return @@ -276,7 +257,7 @@ class Feed(gobject.GObject): def fset(self, title): if self._title != title: self._title = title - self.emit('info-updated') + self.emit('changed') return property(**locals()) @apply @@ -288,7 +269,7 @@ class Feed(gobject.GObject): self._location = location self._username = username self._password = password - self.emit('info-updated') + self.emit('changed') return property(**locals()) @apply @@ -299,7 +280,7 @@ class Feed(gobject.GObject): def fset(self, location): if self._location != location: self._location = location - self.emit('info-updated') + self.emit('changed') return property(**locals()) @apply @@ -314,7 +295,7 @@ class Feed(gobject.GObject): changed = self._channel_title != t self._channel_title = t if changed: - self.emit('info-updated') + self.emit('changed') return property(**locals()) @apply @@ -329,7 +310,7 @@ class Feed(gobject.GObject): changed = self._channel_description != t self._channel_description = t if changed: - self.emit('info-updated') + self.emit('changed') return property(**locals()) @apply @@ -341,7 +322,7 @@ class Feed(gobject.GObject): changed = self._channel_link != t self._channel_link = t if changed: - self.emit('info-updated') + self.emit('changed') return property(**locals()) @apply @@ -353,7 +334,7 @@ class Feed(gobject.GObject): changed = self._channel_copyright != t self._channel_copyright = t if changed: - self.emit('info-updated') + self.emit('changed') return property(**locals()) @apply @@ -387,13 +368,13 @@ class Feed(gobject.GObject): return property(**locals()) @apply - def n_items_unread(): - doc = "" + def number_of_unread(): + doc = "the number of unread items for this feed" def fget(self): return self._n_items_unread def fset(self, n): - if self._n_items_unread != n: - self._n_items_unread = n + self._n_items_unread = n + self.emit('changed') return property(**locals()) @apply @@ -404,7 +385,7 @@ class Feed(gobject.GObject): def fset(self, error): if self._error != error: self._error = error - self.emit('error-occurred') + self.emit('changed') return property(**locals()) @apply @@ -415,7 +396,7 @@ class Feed(gobject.GObject): def fset(self, status): if status != self._process_status: self._process_status = status - self.emit('process_status_changed') + self.emit('changed') return property(**locals()) @apply @@ -494,7 +475,6 @@ class Feed(gobject.GObject): def restore_items(self, items): olditems = [] - num_unread = 0 config = Config.get_instance() cutpoint = self.number_of_items_stored if cutpoint == Feed.DEFAULT: @@ -503,48 +483,38 @@ class Feed(gobject.GObject): for idx, item in enumerate(items): item.feed = self if item.sticky or not item.seen or idx <= cutpoint: - item.connect('read', self.item_marked_read) # forward sticky signal? XXX self._items.add(item) - num_unread += item.seen and 1 or 0 continue else: item.clean_up() olditems.append(item) - self.n_items_unread = num_unread #self.number_of_items_stored = len(self.items) + print "\told: ", len(olditems) + print "\tnew: ", len(items) if olditems: self.emit('items-deleted', olditems) return - def item_marked_read(self, item): - self.n_items_unread += item.seen and -1 or 1 - def delete_all_items(self): self.emit('items-deleted', self._items) @property def items(self): + if not self._items_loaded: + self.load_contents() return self._items - @property - def number_of_unread(self): - return self._n_items_unread - - @property - def number_of_items(self): - return len(self._items) - def mark_all_items_as_read(self): unread = [item for item in self._items if not item.seen] self.emit('items-read', unread) - self.n_items_unread = 0 def load_contents(self): if self._items_loaded: return False itemstore = ItemStore.get_instance() items = itemstore.read_feed_items(self) + print "feed.load_contents->items: ", len(items) if items: self.restore_items(items) self._items_loaded = True -- 2.11.4.GIT