cleanup ErrorPresenter, item selection, removed feedlistview.get_selected_nodes,...
[straw.git] / src / lib / ItemList.py
blobc9696a97ac7e51d11093d4d0f6081e5bb02c2b91
1 """ ItemList.py
3 Handles listing of feed items in a view (i.e. GTK+ TreeView)
4 """
5 __copyright__ = "Copyright (c) 2002-2005 Free Software Foundation, Inc."
6 __license__ = """GNU General Public License
8 This program is free software; you can redistribute it and/or
9 modify it under the terms of the GNU General Public License as
10 published by the Free Software Foundation; either version 2 of the
11 License, or (at your option) any later version.
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 General Public License for more details.
18 You should have received a copy of the GNU General Public
19 License along with this program; if not, write to the
20 Free Software Foundation, Inc., 59 Temple Place - Suite 330,
21 Boston, MA 02111-1307, USA.
22 """
24 import os.path
25 from xml.sax import saxutils
26 import pygtk
27 pygtk.require('2.0')
28 import gobject
29 import gtk
30 import pango
31 import MVP
32 import error
33 import utils
35 class Column:
36 """
37 Constants for the item list treeview columns
38 """
39 title, sticky, item, weight, sticky_flag, foreground = range(6)
41 # XXX FIX THIS
42 class Popup:
43 ''' A popup '''
45 def __init__(self, listener):
46 self.manager = gtk.UIManager()
47 actions = [
48 ("mark_as_unread", gtk.STOCK_CONVERT, "Mark as _Unread", None,
49 "Mark this item as unread", listener.on_menu_mark_as_unread_activate)
51 actiongroup = gtk.ActionGroup("ItemListActions")
52 actiongroup.add_actions(actions)
53 self.manager.insert_action_group(actiongroup, 1)
54 #popupui = os.path.join(utils.find_image_dir(), 'ui.xml')
55 #self.manager.add_ui_from_file(popupui)
57 @property
58 def popup(self):
59 return self.manager.get_widget('/itemlist_popup')
62 class ItemListModel:
63 ''' A model of summary items for tree views '''
65 def __init__(self):
66 self.store = gtk.ListStore(str, gobject.TYPE_OBJECT, gobject.TYPE_PYOBJECT, int, bool, str)
68 def populate(self, summaryItems):
69 self.store.clear()
70 for item in summaryItems:
71 weight = (pango.WEIGHT_BOLD, pango.WEIGHT_NORMAL)[item.seen]
72 colour = ("#0000FF", "#000000")[item.seen]
73 treeiter = self.store.append()
74 self.store.set(treeiter,
75 Column.title, item.title,
76 Column.item, item,
77 Column.weight, weight,
78 Column.sticky_flag, item.sticky,
79 Column.foreground, colour)
81 @property
82 def model(self):
83 return self.store
86 class ItemListView(MVP.WidgetView):
88 def _initialize(self):
89 self._widget.set_rules_hint(False)
90 self._widget.connect("button_press_event",self._on_button_press_event)
91 self._widget.connect("popup-menu", self._on_popup_menu)
92 self._widget.connect("row-activated", self._on_row_activated)
93 #self._popup = Popup(self)
95 renderer = gtk.CellRendererToggle()
96 column = gtk.TreeViewColumn(_('Keep'), renderer,
97 active=Column.sticky_flag)
98 column.set_resizable(True)
99 column.set_reorderable(True)
100 self._widget.append_column(column)
101 renderer.connect('toggled', self._sticky_toggled)
103 renderer = gtk.CellRendererText()
104 column = gtk.TreeViewColumn(_('_Title'), renderer,
105 text=Column.title,
106 foreground=Column.foreground,
107 weight=Column.weight)
108 column.set_resizable(True)
109 column.set_reorderable(True)
110 self._widget.append_column(column)
112 selection = self._widget.get_selection()
113 selection.set_mode(gtk.SELECTION_SINGLE)
115 def add_selection_changed_listener(self, listener):
116 selection = self._widget.get_selection()
117 selection.connect('changed',
118 listener.itemlist_selection_changed,
119 Column.item)
121 def _model_set(self):
122 self._widget.set_model(self._model.model)
124 def _sticky_toggled(self, cell, path):
125 print "STICKY TOGGLED ", cell, path
126 #treeiter = self._model.get_iter((int(path),))
127 #item = self._model.get_value(treeiter, item_column)
128 #item.sticky = not item.sticky
129 #self._model.set(treeiter, stickyflag_column, item.sticky)
130 #self._presenter.sticky_toggled(cell, path,
131 # Column.item, Column.sticky_flag)
132 return
134 def _on_popup_menu(self, treeview, *args):
135 self._popup.popup(None,None,None,0,0)
137 def _on_button_press_event(self, treeview, event):
138 val = 0
139 if event.button == 3:
140 x = int(event.x)
141 y = int(event.y)
142 time = gtk.get_current_event_time()
143 path = treeview.get_path_at_pos(x, y)
144 if path is None:
145 return 1
146 path, col, cellx, celly = path
147 treeview.grab_focus()
148 treeview.set_cursor( path, col, 0)
149 self._popup.popup(None, None, None, event.button, time)
150 val = 1
151 return val
153 def on_menu_mark_as_unread_activate(self, *args):
154 selection = self._widget.get_selection()
155 (model, treeiter) = selection.get_selected()
156 if not treeiter: return
157 item = model.get_value(treeiter, Column.item)
158 item.seen = False
159 weight = (pango.WEIGHT_BOLD, pango.WEIGHT_NORMAL)[item.seen]
160 colour = ("#0000FF", "#000000")[item.seen]
161 model.set(treeiter, Column.foreground, colour,
162 Column.weight, weight,
163 Column.item, item)
164 return
166 def _on_row_activated(self, treeview, path, column):
167 model = self._widget.get_model()
168 try:
169 treeiter = model.get_iter(path)
170 except ValueError:
171 return
172 link = model.get_value(treeiter, Column.ITEM).link
173 if link:
174 utils.url_show(link)
175 return
177 def select_first_item(self):
178 selection = self._widget.get_selection()
179 (model, treeiter) = selection.get_selected()
180 if not treeiter: return
181 path = model.get_path(model.get_iter_first())
182 self._widget.set_cursor(path)
183 self._widget.scroll_to_cell(path, column)
184 self._widget.grab_focus()
186 def select_previous_item(self):
188 Selects the item before the current selection. If there
189 is no current selection, selects the last item. If there is no
190 previous row, returns False.
192 selection = self._widget.get_selection()
193 (model, treeiter) = selection.get_selected()
194 # select the first item if there is no selection
195 if not treeiter:
196 treeiter = model.get_iter_first()
197 path = model.get_path(treeiter)
198 if not path:
199 return False
200 prev_path = path[-1]-1
201 # we don't cycle through the items so return False if there are no
202 # more items to go to
203 if prev_path < 0:
204 return False
205 selection.select_path(path[-1]-1,)
206 return True
208 def select_next_item(self):
210 Selects the item after the current selection. If there is no current
211 selection, selects the first item. If there is no next row, returns False.
213 selection = self._widget.get_selection()
214 (model, treeiter) = selection.get_selected()
215 if not treeiter:
216 treeiter = model.get_iter_first()
217 next_iter = model.iter_next(treeiter)
218 if not next_iter or not model.iter_is_valid(treeiter):
219 return False
220 next_path = model.get_path(next_iter)
221 selection.select_path(next_path)
222 return True
224 def select_last_item(self):
226 Selects the last item in this list.
228 selection = self._widget.get_selection()
229 selection.select_path(len(self._model.model) - 1)
230 return True
232 def select_next_unread_item(self):
234 Selects the first unread item after the current selection. If there is
235 no current selection, or if there are no unread items, returns False..
237 By returning False, the caller can either go to the next feed, or go
238 to the next feed with an unread item.
240 has_unread = False
241 selection = self._widget.get_selection()
242 (model, treeiter) = selection.get_selected()
243 # check if we have a selection. if none,
244 # start searching from the first item
245 if not treeiter:
246 treerow = model[0]
247 else:
248 model, treeiter = selected_row
249 if not treeiter:
250 return has_unread
251 nextiter = model.iter_next(treeiter)
252 if not nextiter: # no more rows to iterate
253 return has_unread
254 treerow = self._model[model.get_path(nextiter)]
255 while(treerow):
256 item = treerow[Column.item]
257 if not item.seen:
258 has_unread = True
259 selection.select_path(treerow.path)
260 break
261 treerow = treerow.next
262 return has_unread
264 class ItemListPresenter(MVP.BasicPresenter):
266 ## XXX listen for RefreshFeedDisplaySignal, AllItemsReadSignal,
267 ## ItemReadSignal, FeedOrder changes,
269 def _initialize(self):
270 self.model = ItemListModel()
272 def feedlist_selection_changed(self, feedlist_selection, column):
273 (model, pathlist) = feedlist_selection.get_selected_rows()
274 iters = [model.get_iter(path) for path in pathlist]
275 ## XXX fix this, get and display the union of feeds
276 ## currently, it only displays the first feed at path
277 if not iters:
278 return
279 nodes = [model.get_value(treeiter, column) for treeiter in iters]
280 try:
281 feeds = [node.feed for node in nodes if node.feed]
282 map(self.model.populate, [feed.items for feed in feeds])
283 if not len(self.model.model): return
284 if not self.view.select_next_unread_item():
285 self.view.select_first_item()
286 except TypeError:
287 ## XXX display child items of Category
288 pass
290 def display_feed_items(self, feed, select_first=1):
291 redisplay = self._selected_feed is feed
292 #if self._selected_feed and not redisplay:
293 # self._selected_feed.signal_disconnect(Event.RefreshFeedDisplaySignal,
294 # self._feed_order_changed)
295 # self._selected_feed.signal_disconnect(Event.AllItemsReadSignal,
296 # self._all_items_read)
297 # self._selected_feed.signal_disconnect(Event.ItemReadSignal,
298 # self._item_read)
299 self._selected_feed = feed
300 if not redisplay:
301 #self._selected_feed.connect(Event.RefreshFeedDisplaySignal,
302 # self._feed_order_changed)
303 self._selected_feed.connect('items_read', self._all_items_read)
304 #self._selected_feed.signal_connect(Event.ItemReadSignal,
305 # self._item_read)
306 count = self._render_feed(self._selected_feed)
307 if not redisplay and count:
308 if not self.select_next_unread_item():
309 selection = self._view.get_widget_selection()
310 selection.select_path((0,))
311 return
313 def _feed_order_changed(self, event):
314 if event.sender is self._selected_feed:
315 item = None
316 selection = self._view.get_widget_selection()
317 model, treeiter = selection.get_selected()
318 if treeiter is not None:
319 path = model.get_path(treeiter)
320 item = model[path[0]][Column.ITEM]
321 self._render_feed(event.sender)
322 if item:
323 path = (item.feed.get_item_index(item),)
324 else:
325 path = (0,) # first item
326 selection.select_path(path)
328 def _all_items_read(self, feed, unread_items):
329 print unread_items
330 for index, item in enumerate(unread_items):
331 self._mark_item(item, index)
333 def _item_read(self, signal):
334 self._mark_item(signal.item)
336 def _render_feed(self, feed):
337 self._model.clear()
338 curr_iter = None
339 for item in feed.items:
340 weight = (pango.WEIGHT_BOLD, pango.WEIGHT_NORMAL)[item.seen]
341 colour = ("#0000FF", "#000000")[item.seen]
342 treeiter = self._model.append()
343 self._model.set(treeiter,
344 Column.title, item.title,
345 Column.ITEM, item,
346 Column.WEIGHT, weight,
347 Column.sticky_flag, item.sticky,
348 Column.FOREGROUND, colour)
349 if item is self._selected_item:
350 curr_iter = treeiter
352 if curr_iter:
353 path = self._model.get_path(curr_iter)
354 self._view.scroll_to_cell(path)
355 return len(feed.items)
357 def _mark_item(self, item, path=None):
358 if path is None:
359 path = (item.feed.get_item_index(item),)
360 self._set_column_weight(item, path)
361 return