From f4f628b6a17a8d2f74998edc46571246fd812988 Mon Sep 17 00:00:00 2001 From: jmalonzo Date: Tue, 24 Jul 2007 09:31:52 +0000 Subject: [PATCH] Fixed item navigation and cleaned up feed and item changes git-svn-id: svn+ssh://svn.gnome.org/svn/straw/trunk@314 141a2093-ea25-0410-9ad2-d44d734a8f13 --- src/lib/Application.py | 18 +++---- src/lib/FeedListView.py | 124 +++++++++++++++++++++++------------------------- src/lib/ItemList.py | 56 ++++++++++++++-------- src/lib/ItemStore.py | 50 +++++-------------- src/lib/SummaryItem.py | 8 ++-- src/lib/feeds.py | 39 ++++++++------- src/lib/utils.py | 2 - 7 files changed, 140 insertions(+), 157 deletions(-) diff --git a/src/lib/Application.py b/src/lib/Application.py index bd53619..9268c7f 100644 --- a/src/lib/Application.py +++ b/src/lib/Application.py @@ -351,7 +351,6 @@ class ApplicationPresenter(MVP.BasicPresenter): """ Displays the feed after the current selected feed """ - # XXX feed_list_presenter usage self._feed_list_presenter.select_next_feed() return @@ -359,7 +358,7 @@ class ApplicationPresenter(MVP.BasicPresenter): """ Displays the next feed with an unread item """ -# self._feed_list_presenter.select_next_unread_feed() + self._feed_list_presenter.select_next_feed(with_unread=True) pass def display_previous_item(self, item=None): @@ -371,8 +370,9 @@ class ApplicationPresenter(MVP.BasicPresenter): if not is_prev: # TODO HACK - implement select_previous_feed(select_last=True) ... # ... to select previous feed's last item -# self._feed_list_presenter.select_previous_feed() + self._feed_list_presenter.select_previous_feed() self._itemlist_presenter.select_last_item() + pass return def display_next_item(self, item=None): @@ -382,18 +382,18 @@ class ApplicationPresenter(MVP.BasicPresenter): feed in the list, it goes back and selects the first feed """ is_next = self._itemlist_presenter.select_next_item() - # if not is_next: - # is_next_feed = self._feed_list_presenter.select_next_feed() - # if not is_next_feed: - # self._feed_list_presenter.select_first_feed() + if not is_next: + is_next_feed = self._feed_list_presenter.select_next_feed() + if not is_next_feed: + self._feed_list_presenter.select_firsteed_feed() return def scroll_or_display_next_unread_item(self, item=None): has_unread_item = False if not self._item_view.scroll_down(): has_unread_item = self._itemlist_presenter.select_next_unread_item() - # if not has_unread_item: - # self._feed_list_presenter.select_next_unread_feed() + if not has_unread_item: + self._feed_list_presenter.select_next_feed(with_unread=True) return def show_preferences_dialog(self, parent): diff --git a/src/lib/FeedListView.py b/src/lib/FeedListView.py index 5e2a985..b7cc764 100644 --- a/src/lib/FeedListView.py +++ b/src/lib/FeedListView.py @@ -38,7 +38,7 @@ import MVP import utils class Column: - pixbuf, name, foreground, object = range(4) + pixbuf, name, foreground, unread, object = range(5) class TreeNodeAdapter (gobject.GObject): @@ -100,14 +100,12 @@ class TreeNodeAdapter (gobject.GObject): ''' 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 + def _r(a,b):return a + b 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 + unread_items = reduce(_r, [feed.number_of_unread for feed in self.obj.feeds]) + return unread_items or '' @property def pixbuf(self): @@ -181,7 +179,7 @@ class FeedListModel: def __init__(self): # name, pixbuf, unread, foreground - self.store = gtk.TreeStore(gtk.gdk.Pixbuf, str, str, gobject.TYPE_PYOBJECT) + self.store = gtk.TreeStore(gtk.gdk.Pixbuf, str, str, str, gobject.TYPE_PYOBJECT) # unread, weight, status_flag feed object, allow_children # str, int, 'gboolean', gobject.TYPE_PYOBJECT, 'gboolean' self.fclist = FeedCategoryList.get_instance() @@ -202,6 +200,7 @@ class FeedListModel: node_adapter.connect('category-feed-removed', self.feed_removed_cb) self.store.set(parent, Column.pixbuf, node_adapter.pixbuf, Column.name, node_adapter.title, + Column.unread, node_adapter.num_unread_items, Column.object, node_adapter) for f in feeds: rowiter = self.store.append(parent) @@ -209,8 +208,9 @@ class FeedListModel: 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? + Column.foreground, node_adapter.num_unread_items and 'blue' or 'black', + Column.unread, node_adapter.num_unread_items, + Column.object, node_adapter) def __getattribute__(self, name): attr = None @@ -233,8 +233,10 @@ class FeedListModel: (Column.object, node_adapter)) feed_node_adapter = TreeNodeAdapter(feed) feed_node_adapter.connect('feed-changed', self.feed_changed_cb) - self.store.append(treemodelrow.iter, [feed_node_adapter.pixbuf, feed_node_adapter.title, - self.get_foreground(feed_node_adapter.num_unread_items), + self.store.append(treemodelrow.iter, [feed_node_adapter.pixbuf, + feed_node_adapter.title, + feed_node_adapter.num_unread_items and 'blue' or 'black', + feed_node_adapter.num_unread_items, feed_node_adapter]) def feed_removed_cb(self, node_adapter, feed): @@ -248,13 +250,10 @@ class FeedListModel: if not row: return path = self.store.get_path(row.iter) self.store[path] = [node_adapter.pixbuf, node_adapter.title, - self.get_foreground(node_adapter.num_unread_items), + node_adapter.num_unread_items and 'blue' or 'black', + node_adapter.num_unread_items, node_adapter] - def get_foreground(self, unread): - ''' gets the foreground color according to the number of unread items''' - return ('black', 'blue')[(unread > 0) and 1 or 0] - @property def model(self): return self.store @@ -279,6 +278,11 @@ class FeedsView(MVP.WidgetView): column.set_attributes(status_renderer, pixbuf=Column.pixbuf) + unread_renderer = gtk.CellRendererText() + column.pack_start(unread_renderer, False) + column.set_attributes(unread_renderer, + text=Column.unread) + # feed title renderer title_renderer = gtk.CellRendererText() column.pack_start(title_renderer, False) @@ -359,7 +363,7 @@ class FeedsView(MVP.WidgetView): 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.feed.mark_all_items_as_read()) + self.foreach_selected(lambda o,*args: o.feed.mark_items_as_read()) def on_remove_selected_feed(self, *args): def remove(*args): @@ -381,13 +385,15 @@ class FeedsView(MVP.WidgetView): return def select_first_feed(self): - treeiter = self._model.get_iter_first() - if not treeiter or not self._model.iter_is_valid(treeiter): + selection = self._widget.get_selection() + (model, pathlist) = selection.get_selected_rows() + treeiter = model.get_iter_first() + if not treeiter or not model.iter_is_valid(treeiter): return False - self._view.set_cursor(treeiter) + self.set_cursor(treeiter) return True - def select_next_feed(self, unread=False): + def select_next_feed(self, with_unread=False): ''' Scrolls to the next feed in the feed list If there is no selection, selects the first feed. If multiple feeds @@ -399,11 +405,16 @@ class FeedsView(MVP.WidgetView): child. If current selection is a child, then go to (parent + 1), provided that (parent + 1) is not a category. ''' + has_unread = False def next(model, current): treeiter = model.iter_next(current) - if not treeiter and model.iter_depth(current): - next(model, model.iter_parent(current)) + if not treeiter: return False + if model.iter_depth(current): next(model, model.iter_parent(current)) + path = model.get_path(treeiter) + if with_unread and model[path][Column.unread] < 1: + next(model, current) self.set_cursor(treeiter) + return True selection = self._widget.get_selection() (model, pathlist) = selection.get_selected_rows() iters = [model.get_iter(path) for path in pathlist] @@ -415,11 +426,18 @@ class FeedsView(MVP.WidgetView): path = model.get_path(iterchild) for i in range(len(path)): self._widget.expand_row(path[:i+1], False) - self.set_cursor(iterchild) - return - next(model,current) + # select his first born child + if with_unread and model[path][Column.unread] > 0: + self.set_cursor(iterchild) + has_unread = True + else: + has_unread = next(model, current) + has_unread = next(model,current) except IndexError: self.set_cursor(model.get_iter_first()) + has_unread = True + print "HAS UNREAD ", has_unread + return has_unread def select_previous_feed(self): ''' Scrolls to the previous feed in the feed list. @@ -434,49 +452,22 @@ class FeedsView(MVP.WidgetView): ''' def previous(model, current): path = model.get_path(current) - path_len = len(path) - #path_prev = path[:path_len - print "\tpath is -> %s , prev_path -> %s", (path, prev_path) - treeiter = model.get_iter(prev_path) - self.set_cursor(treeiter) + treerow = model[path[-1]-1] + self.set_cursor(treerow.iter) selection = self._widget.get_selection() (model, pathlist) = selection.get_selected_rows() iters = [model.get_iter(path) for path in pathlist] try: - current = iters.pop(0) - if model.iter_has_child(current): - kids = model.iter_n_children(current) - iter = model.iter_nth_child(kids - 1) - self.set_cursor(iter) + current_first = iters.pop(0) + if model.iter_has_child(current_first): + children = model.iter_n_children(current_first) + treeiter = model.iter_nth_child(children - 1) + self.set_cursor(treeiter) return - previous(model, current) + previous(model, current_first) except IndexError: self.set_cursor(model.get_iter_first()) - - def select_next_unread_feed(self): - has_unread = False - mark_treerow = 1 - treerow = self._model[0] - selection = self._view.get_selection() - srow = selection.get_selected() - if srow: - model, treeiter = srow - nextiter = model.iter_next(treeiter) - if nextiter: - treerow = self._model[model.get_path(nextiter)] - while(treerow): - feedrow = treerow[Column.OBJECT] - if feedrow.feed.number_of_unread: - self._view.set_cursor(treerow.iter) - has_unread = True - break - treerow = treerow.next - if not treerow and mark_treerow: - # should only do this once. - mark_treerow = treerow - treerow = self._model[0] - return has_unread - + return def set_cursor(self, treeiter, col_id=None, edit=False): if not treeiter: @@ -512,11 +503,14 @@ class FeedsPresenter(MVP.BasicPresenter): #fclist.signal_connect(Event.FeedCategoryChangedSignal, # self._fcategory_changed_cb) - def select_next_feed(self, unread=False): - self.view.select_next_feed(unread) + def select_first_feed(self): + return self.view.select_first_feed() + + def select_next_feed(self, with_unread=False): + return self.view.select_next_feed(with_unread) def select_previous_feed(self): - self.view.select_previous_feed() + return self.view.select_previous_feed() def _sort_func(self, model, a, b): """ diff --git a/src/lib/ItemList.py b/src/lib/ItemList.py index ae7a99b..05e1e3a 100644 --- a/src/lib/ItemList.py +++ b/src/lib/ItemList.py @@ -84,6 +84,16 @@ class ItemListModel: Column.weight, weight, Column.sticky_flag, item.sticky, Column.foreground, colour) + item.connect('changed', self.item_changed_cb) + + def item_changed_cb(self, item): + for row in self.store: + rowitem = row[Column.item] + if rowitem is item: + row[Column.weight] = item.seen and pango.WEIGHT_NORMAL or pango.WEIGHT_BOLD + row[Column.foreground] = item.seen and 'black' or 'blue' + row[Column.sticky_flag] = item.sticky + row[Column.item] = item @property def model(self): @@ -172,6 +182,7 @@ class ItemListView(MVP.WidgetView): return def _on_row_activated(self, treeview, path, column): + ''' double-clicking an item opens that item in the web browser ''' model = self._widget.get_model() try: treeiter = model.get_iter(path) @@ -185,9 +196,9 @@ class ItemListView(MVP.WidgetView): def select_first_item(self): selection = self._widget.get_selection() (model, treeiter) = selection.get_selected() - if not treeiter: return path = model.get_path(model.get_iter_first()) selection.select_path(path) + return True def select_previous_item(self): """ @@ -197,18 +208,13 @@ class ItemListView(MVP.WidgetView): """ selection = self._widget.get_selection() (model, treeiter) = selection.get_selected() - # select the first item if there is no selection - if not treeiter: - treeiter = model.get_iter_first() + if not treeiter: return False path = model.get_path(treeiter) - if not path: + prev = path[-1]-1 + print prev, path + if prev < 0: return False - prev_path = path[-1]-1 - # we don't cycle through the items so return False if there are no - # more items to go to - if prev_path < 0: - return False - selection.select_path(path[-1]-1,) + selection.select_path(prev) return True def select_next_item(self): @@ -243,21 +249,17 @@ class ItemListView(MVP.WidgetView): By returning False, the caller can either go to the next feed, or go to the next feed with an unread item. """ - has_unread = False selection = self._widget.get_selection() (model, treeiter) = selection.get_selected() # check if we have a selection. if none, # start searching from the first item if not treeiter: - treerow = model[0] - else: - model, treeiter = selected_row - if not treeiter: - return has_unread - nextiter = model.iter_next(treeiter) - if not nextiter: # no more rows to iterate - return has_unread - treerow = self._model[model.get_path(nextiter)] + treeiter = model.get_iter_first() + if not treeiter: return False + nextiter = model.iter_next(treeiter) + if not nextiter: return False # no more rows to iterate + treerow = model[nextiter] + has_unread = False while(treerow): item = treerow[Column.item] if not item.seen: @@ -302,6 +304,18 @@ class ItemListPresenter(MVP.BasicPresenter): else: yield item + def select_previous_item(self): + return self.view.select_previous_item() + + def select_next_item(self): + return self.view.select_next_item() + + def select_next_unread_item(self): + return self.view.select_next_unread_item() + + def select_last_item(self): + return self.view.select_last_item() + """ def display_feed_items(self, feed, select_first=1): redisplay = self._selected_feed is feed diff --git a/src/lib/ItemStore.py b/src/lib/ItemStore.py index 3681454..d38cb2f 100644 --- a/src/lib/ItemStore.py +++ b/src/lib/ItemStore.py @@ -457,13 +457,6 @@ class MyDB: else: txn.commit() -class ModifyItemAction: - def __init__(self, item): - self._item = item - - def doit(self, db): - db.modify_items([self._item]) - class ModifyItemsAction: def __init__(self, items): self._items = items @@ -518,7 +511,8 @@ class ItemStore: self._connect_feed_signals(feed) def _feed_deleted_cb(self, flist, feed): - self._disconnect_feed_signals(feed) + # XXX FIX THIS + pass def connect_signals(self): flist = feeds.get_instance().flatten_list() @@ -526,24 +520,18 @@ class ItemStore: self._connect_feed_signals(f) def _connect_feed_signals(self, feed): - feed.connect('items-updated', self.items_added) - feed.connect('items-read', self.all_items_read) - feed.connect('items-deleted', self.items_deleted) - # XXX - #feed.signal_connect(Event.ItemReadSignal, self.item_modified) - #feed.signal_connect(Event.ItemStickySignal, self.item_modified) - - def _disconnect_feed_signals(self, feed): - #feed.signal_disconnect(Event.NewItemsSignal, self.items_added) - #feed.signal_disconnect(Event.ItemReadSignal, self.item_modified) - #feed.signal_disconnect(Event.ItemStickySignal, self.item_modified) - #feed.signal_disconnect(Event.AllItemsReadSignal, self.all_items_read) - #feed.signal_disconnect(Event.ItemDeletedSignal, self.item_deleted) - pass + feed.connect('items-added', self.items_added_cb) + feed.connect('items-changed', self.items_changed_cb) + feed.connect('items-deleted', self.items_deleted_cb) - def modify_item(self, item): - self._action_queue.append(ModifyItemAction(item)) - return + def items_deleted_cb(self, feed, items): + self._action_queue.append(DeleteItemsAction(feed, items)) + + def items_added_cb(self, feed, items): + self._action_queue.append(ItemsAddedAction(feed, items)) + + def items_changed_cb(self, feed, items): + self._action_queue.append(ModifyItemsAction(items)) def image_updated(self, cache, url, data): self._action_queue.append( @@ -552,18 +540,6 @@ class ItemStore: def read_image(self, url): return self._db.get_image_data(url) - def items_deleted(self, feed, items): - self._action_queue.append(DeleteItemsAction(feed, items)) - - def item_modified(self, signal): - self.modify_item(signal.item) - - def all_items_read(self, feed, items): - self._action_queue.append(ModifyItemsAction(items)) - - def items_added(self, feed, items): - self._action_queue.append(ItemsAddedAction(feed, items)) - def read_feed_items(self, feed): return self._db.get_feed_items(feed) diff --git a/src/lib/SummaryItem.py b/src/lib/SummaryItem.py index d397158..20cfba9 100644 --- a/src/lib/SummaryItem.py +++ b/src/lib/SummaryItem.py @@ -33,9 +33,7 @@ class SummaryItem(gobject.GObject): 'creator', 'contributors') __gsignals__ = { - 'read' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), - #'unread' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), - 'sticky' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()) + 'changed' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, ()), } def __init__(self, imageCache=None): @@ -85,7 +83,7 @@ class SummaryItem(gobject.GObject): def fset(self, seen = True): if self._seen != seen: self._seen = seen - self.emit('read') + self.emit('changed') return property(**locals()) @apply @@ -96,7 +94,7 @@ class SummaryItem(gobject.GObject): def fset(self, sticky): if self._sticky != sticky: self._sticky = sticky - self.emit('sticky') + self.emit('changed') return property(**locals()) @apply diff --git a/src/lib/feeds.py b/src/lib/feeds.py index 9738333..0100690 100644 --- a/src/lib/feeds.py +++ b/src/lib/feeds.py @@ -191,9 +191,9 @@ class Feed(gobject.GObject): __gsignals__ = { '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, + 'items-added' :(gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,)), - 'items-read' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, + 'items-changed' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,)), 'items-deleted' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_PYOBJECT,)) @@ -224,14 +224,15 @@ class Feed(gobject.GObject): self._items_stored = Feed.DEFAULT self._poll_freq = Feed.DEFAULT self._last_poll = 0 - self._items = FifoCache(num_entries=Feed.DEFAULT) - self._items_loaded = False self.config = Config.get_instance() self.config.connect('item-order-changed', self.item_order_changed_cb) self.config.connect('item-stored-changed', self.item_stored_changed_cb) self.item_order_reverse = self.config.item_order self.item_stored_num = self.config.number_of_items_stored + self._items = FifoCache(num_entries=Feed.DEFAULT) + self._items_loaded = False + self._changes_queue = [] return def __str__(self): @@ -377,7 +378,8 @@ class Feed(gobject.GObject): def fget(self): return self._n_items_unread def fset(self, n): - self._n_items_unread = n + if self._n_items_unread: + self._n_items_unread = n self.emit('changed') return property(**locals()) @@ -451,6 +453,7 @@ class Feed(gobject.GObject): return cutpoint def add_items(self, items): + if not self._items_loaded: self.load_contents() items = sorted(items, key=operator.attrgetter('pub_date'), reverse=self.item_order_reverse) self._items.set_number_of_entries(self.get_cutpoint()) @@ -463,8 +466,10 @@ class Feed(gobject.GObject): item.id = maxid item.feed = self newitems.append(item) + item.connect('changed', self.item_changed_cb) self._items[item.id] = item - self.emit('items-updated', newitems) + self.number_of_unread = len([item for item in self._items.itervalues() if not item.seen]) + self.emit('items-added', newitems) def restore_items(self, items): items = sorted(items, key=operator.attrgetter('pub_date'), @@ -473,21 +478,26 @@ class Feed(gobject.GObject): self._items.set_number_of_entries(cutpoint) olditems = [] for idx, item in enumerate(items): - if not item.sticky and idx > cutpoint: + if not item.sticky and idx >= cutpoint: item.clean_up() olditems.append(item) continue item.feed = self + item.connect('changed', self.item_changed_cb) self._items[item.id] = item + self.number_of_unread = len([item for item in self._items.itervalues() if not item.seen]) print "\t items len: %d, olditems len: %d" % (len(items), len(olditems)) if olditems: self.emit('items-deleted', olditems) return - def item_read_cb(self, item): - print "ITEM READ! -> ", item + def item_changed_cb(self, item): + self.number_of_unread += item.seen and -1 or 1 + self._changes_queue.append(item) + self.emit('items-changed', self._changes_queue) def delete_all_items(self): + self._items.clear() self.emit('items-deleted', self._items.values()) @property @@ -496,11 +506,11 @@ class Feed(gobject.GObject): self.load_contents() return self._items.values() - def mark_all_items_as_read(self): + def mark_items_as_read(self, items=None): def mark(item): item.seen = True unread = [item for item in self._items.itervalues() if not item.seen] map(mark, unread) - self.emit('items-read', unread) + self.emit('items-changed', unread) def load_contents(self): if self._items_loaded: @@ -514,13 +524,6 @@ class Feed(gobject.GObject): return self._items_loaded def unload_contents(self): - """ - Unloads the items by disconnecting the signals and reinitialising the - instance variables - - TODO: unload seems to lose some circular references. garbage collector - will find them, though, so maybe it's not a problem. - """ if not self._items_loaded: return self._items.clear() diff --git a/src/lib/utils.py b/src/lib/utils.py index 82f78a0..111e556 100644 --- a/src/lib/utils.py +++ b/src/lib/utils.py @@ -31,8 +31,6 @@ import constants import gtk entity = re.compile(r'\&.\w*?\;') - - def convert_entities(text): def conv(ents): entities = htmlentitydefs.entitydefs -- 2.11.4.GIT