more Event cleanups and ItemList module refactoring
[straw.git] / src / lib / ItemList.py
blob6008064e472ac33366b6ef0a4e193d05a46e37a1
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_previous_item(self):
179 Selects the item before the current selection. If there
180 is no current selection, selects the last item. If there is no
181 previous row, returns False.
183 selection = self._widget.get_selection()
184 (model, treeiter) = selection.get_selected()
185 # select the first item if there is no selection
186 if not treeiter:
187 treeiter = model.get_iter_first()
188 path = model.get_path(treeiter)
189 if not path:
190 return False
191 prev_path = path[-1]-1
192 # we don't cycle through the items so return False if there are no
193 # more items to go to
194 if prev_path < 0:
195 return False
196 selection.select_path(path[-1]-1,)
197 return True
199 def select_next_item(self):
201 Selects the item after the current selection. If there is no current
202 selection, selects the first item. If there is no next row, returns False.
204 selection = self._widget.get_selection()
205 (model, treeiter) = selection.get_selected()
206 if not treeiter:
207 treeiter = model.get_iter_first()
208 next_iter = model.iter_next(treeiter)
209 if not next_iter or not model.iter_is_valid(treeiter):
210 return False
211 next_path = model.get_path(next_iter)
212 selection.select_path(next_path)
213 return True
215 def select_last_item(self):
217 Selects the last item in this list.
219 selection = self._widget.get_selection()
220 selection.select_path(len(self._model.model) - 1)
221 return True
223 def select_next_unread_item(self):
225 Selects the first unread item after the current selection. If there is
226 no current selection, or if there are no unread items, returns False..
228 By returning False, the caller can either go to the next feed, or go
229 to the next feed with an unread item.
231 has_unread = False
232 selection = self._widget.get_widget_selection()
233 (model, treeiter) = selection.get_selected()
234 # check if we have a selection. if none,
235 # start searching from the first item
236 if not treeiter:
237 treerow = model[0]
238 else:
239 model, treeiter = selected_row
240 if not treeiter:
241 return has_unread
242 nextiter = model.iter_next(treeiter)
243 if not nextiter: # no more rows to iterate
244 return has_unread
245 treerow = self._model[model.get_path(nextiter)]
246 while(treerow):
247 item = treerow[Column.item]
248 if not item.seen:
249 has_unread = True
250 selection.select_path(treerow.path)
251 break
252 treerow = treerow.next
253 return has_unread
255 class ItemListPresenter(MVP.BasicPresenter):
257 ## XXX listen for RefreshFeedDisplaySignal, AllItemsReadSignal,
258 ## ItemReadSignal, FeedOrder changes,
260 def _initialize(self):
261 self.model = ItemListModel()
263 def feedlist_selection_changed(self, feedlist_selection, column):
264 (model, pathlist) = feedlist_selection.get_selected_rows()
265 iters = [model.get_iter(path) for path in pathlist]
266 ## XXX fix this, get and display the union of feeds
267 ## currently, it only displays the first feed at path
268 if not iters:
269 return
270 nodes = [model.get_value(treeiter, column) for treeiter in iters]
271 try:
272 feeds = [node.feed for node in nodes if node.feed]
273 map(self.model.populate, [feed.items for feed in feeds])
274 except TypeError:
275 ## XXX display child items of Category
276 pass
278 def display_feed_items(self, feed, select_first=1):
279 redisplay = self._selected_feed is feed
280 #if self._selected_feed and not redisplay:
281 # self._selected_feed.signal_disconnect(Event.RefreshFeedDisplaySignal,
282 # self._feed_order_changed)
283 # self._selected_feed.signal_disconnect(Event.AllItemsReadSignal,
284 # self._all_items_read)
285 # self._selected_feed.signal_disconnect(Event.ItemReadSignal,
286 # self._item_read)
287 self._selected_feed = feed
288 if not redisplay:
289 #self._selected_feed.connect(Event.RefreshFeedDisplaySignal,
290 # self._feed_order_changed)
291 self._selected_feed.connect('items_read', self._all_items_read)
292 #self._selected_feed.signal_connect(Event.ItemReadSignal,
293 # self._item_read)
294 count = self._render_feed(self._selected_feed)
295 if not redisplay and count:
296 if not self.select_next_unread_item():
297 selection = self._view.get_widget_selection()
298 selection.select_path((0,))
299 return
301 def _feed_order_changed(self, event):
302 if event.sender is self._selected_feed:
303 item = None
304 selection = self._view.get_widget_selection()
305 model, treeiter = selection.get_selected()
306 if treeiter is not None:
307 path = model.get_path(treeiter)
308 item = model[path[0]][Column.ITEM]
309 self._render_feed(event.sender)
310 if item:
311 path = (item.feed.get_item_index(item),)
312 else:
313 path = (0,) # first item
314 selection.select_path(path)
316 def _all_items_read(self, feed, unread_items):
317 print unread_items
318 for index, item in enumerate(unread_items):
319 self._mark_item(item, index)
321 def _item_read(self, signal):
322 self._mark_item(signal.item)
324 def _render_feed(self, feed):
325 self._model.clear()
326 curr_iter = None
327 for item in feed.items:
328 weight = (pango.WEIGHT_BOLD, pango.WEIGHT_NORMAL)[item.seen]
329 colour = ("#0000FF", "#000000")[item.seen]
330 treeiter = self._model.append()
331 self._model.set(treeiter,
332 Column.title, item.title,
333 Column.ITEM, item,
334 Column.WEIGHT, weight,
335 Column.sticky_flag, item.sticky,
336 Column.FOREGROUND, colour)
337 if item is self._selected_item:
338 curr_iter = treeiter
340 if curr_iter:
341 path = self._model.get_path(curr_iter)
342 self._view.scroll_to_cell(path)
343 return len(feed.items)
345 def _mark_item(self, item, path=None):
346 if path is None:
347 path = (item.feed.get_item_index(item),)
348 self._set_column_weight(item, path)
349 return