Little tweaks to item fetching.
[straw.git] / straw / ItemList.py
blob04c702642d56b698ae70ae9e8aaf8adae6c4c004
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 from Constants import *
25 from straw import helpers
26 from xml.sax import saxutils
27 import Config
28 import ItemManager
29 import MVP
30 import error
31 import gobject
32 import gtk
33 import os.path, operator
34 import pango
35 import pygtk
36 import time
37 pygtk.require('2.0')
39 class Column:
40 """
41 Constants for the item list treeview columns
42 """
43 TITLE, PUB_DATE, STICKY, ITEM, WEIGHT, STICKY_FLAG, FOREGROUND = range(7)
45 class ItemListModel:
46 ''' A model of summary items for tree views '''
48 def __init__(self):
49 self.store = gtk.ListStore(str, str, gobject.TYPE_OBJECT, gobject.TYPE_PYOBJECT, int, bool, str)
51 def populate(self, items):
52 self.store.clear()
54 if not items:
55 return
56 i = 0
58 for item in items:
59 weight = (pango.WEIGHT_BOLD, pango.WEIGHT_NORMAL)[item.is_read]
60 #weight = pango.WEIGHT_NORMAL
61 #colour = ("#0000FF", "#000000")[item.is_read]
62 colour = "#000000"
63 treeiter = self.store.append()
64 #print self.store[i]
66 self.store.set(treeiter,
67 Column.TITLE, item.title,
68 Column.PUB_DATE, item.pub_date,
69 Column.ITEM, item,
70 Column.WEIGHT, weight,
71 Column.STICKY_FLAG, False,
72 Column.FOREGROUND, colour)
74 item.connect("notify", self.item_changed_cb)
75 i += 1
77 def item_changed_cb(self, item, property):
78 row = [row for row in self.store if row[Column.ITEM] is item]
80 if len(row) == 0:
81 return
83 row[0][Column.WEIGHT] = (pango.WEIGHT_BOLD, pango.WEIGHT_NORMAL)[item.is_read]
85 @property
86 def model(self):
87 return self.store
89 class ItemListView(MVP.WidgetView):
91 def _initialize(self):
92 self._widget.set_rules_hint(False)
93 self._widget.connect("button_press_event",self._on_button_press_event)
94 self._widget.connect("popup-menu", self._on_popup_menu)
95 self._widget.connect("row-activated", self._on_row_activated)
96 uifactory = helpers.UIFactory('ItemListActions')
97 action = uifactory.get_action('/itemlist_popup/mark_as_unread')
98 action.connect('activate', self.on_menu_mark_as_unread_activate)
99 self.popup = uifactory.get_popup('/itemlist_popup')
101 renderer = gtk.CellRendererToggle()
102 column = gtk.TreeViewColumn(_('Keep'), renderer,
103 active=Column.STICKY_FLAG)
104 column.set_resizable(True)
105 column.set_reorderable(True)
106 self._widget.append_column(column)
107 renderer.connect('toggled', self._sticky_toggled)
109 renderer = gtk.CellRendererText()
110 column = gtk.TreeViewColumn(_('_Date'), renderer,
111 text=Column.PUB_DATE,
112 weight=Column.WEIGHT)
113 column.set_resizable(True)
114 column.set_reorderable(True)
115 self._widget.append_column(column)
117 renderer = gtk.CellRendererText()
118 column = gtk.TreeViewColumn(_('_Title'), renderer,
119 text=Column.TITLE,
120 foreground=Column.FOREGROUND,
121 weight=Column.WEIGHT)
122 column.set_resizable(True)
123 column.set_reorderable(True)
124 self._widget.append_column(column)
126 selection = self._widget.get_selection()
127 selection.set_mode(gtk.SELECTION_SINGLE)
128 # selection.connect('changed', lambda x: self.item_mark_as_read(True))
130 def add_selection_changed_listener(self, listener):
131 selection = self._widget.get_selection()
132 selection.connect('changed',
133 listener.itemlist_selection_changed,
134 Column.ITEM)
136 def _model_set(self):
137 self._widget.set_model(self._model.model)
139 def _sticky_toggled(self, cell, path):
140 model = self._widget.get_model()
141 treeiter = model.get_iter((int(path),))
142 item = model.get_value(treeiter, Column.ITEM)
143 item.sticky = not item.sticky
144 model.set(treeiter, Column.STICKY_FLAG, item.sticky)
146 def _on_popup_menu(self, treeview, *args):
147 self.popup.popup(None,None,None,0,0)
149 def _on_button_press_event(self, treeview, event):
150 val = 0
151 if event.button == 3:
152 x = int(event.x)
153 y = int(event.y)
154 time = gtk.get_current_event_time()
155 path = treeview.get_path_at_pos(x, y)
156 if path is None:
157 return 1
158 path, col, cellx, celly = path
159 treeview.grab_focus()
160 treeview.set_cursor( path, col, 0)
161 self.popup.popup(None, None, None, event.button, time)
162 val = 1
163 return val
165 def on_menu_mark_as_unread_activate(self, *args):
166 self.item_mark_as_read(False)
168 def item_mark_as_read(self, isRead):
169 selection = self._widget.get_selection()
170 (model, treeiter) = selection.get_selected()
171 if not treeiter: return
172 item = model.get_value(treeiter, Column.ITEM)
174 item.props.is_read = isRead
176 if isRead:
177 weight = pango.WEIGHT_NORMAL
178 else:
179 weight = pango.WEIGHT_BOLD
180 #colour = ("#0000FF", "#000000")[item.is_read]
181 colour = "#000000"
182 model.set(treeiter, Column.FOREGROUND, colour,
183 Column.WEIGHT, weight,
184 Column.ITEM, item)
185 return
187 def _on_row_activated(self, treeview, path, column):
188 ''' double-clicking an item opens that item in the web browser '''
189 model = self._widget.get_model()
190 try:
191 treeiter = model.get_iter(path)
192 except ValueError:
193 return
194 link = model.get_value(treeiter, Column.ITEM).link
195 if link:
196 helpers.url_show(link)
197 return
199 def select_first_item(self):
200 selection = self._widget.get_selection()
201 (model, treeiter) = selection.get_selected()
202 path = model.get_path(model.get_iter_first())
203 selection.select_path(path)
204 return True
206 def select_previous_item(self):
208 Selects the item before the current selection. If there
209 is no current selection, selects the last item. If there is no
210 previous row, returns False.
212 selection = self._widget.get_selection()
213 (model, treeiter) = selection.get_selected()
214 if not treeiter: return False
215 path = model.get_path(treeiter)
216 prev = path[-1]-1
217 if prev < 0:
218 return False
219 selection.select_path(prev)
220 return True
222 def select_next_item(self):
224 Selects the item after the current selection. If there is no current
225 selection, selects the first item. If there is no next row, returns False.
227 selection = self._widget.get_selection()
228 (model, treeiter) = selection.get_selected()
229 if not treeiter:
230 treeiter = model.get_iter_first()
231 next_iter = model.iter_next(treeiter)
232 if not next_iter or not model.iter_is_valid(treeiter):
233 return False
234 next_path = model.get_path(next_iter)
235 selection.select_path(next_path)
236 return True
238 def select_last_item(self):
240 Selects the last item in this list.
242 selection = self._widget.get_selection()
243 selection.select_path(len(self.model.model) - 1)
244 return True
246 def select_next_unread_item(self):
248 Selects the first unread item after the current selection. If there is
249 no current selection, or if there are no unread items, returns False..
251 By returning False, the caller can either go to the next feed, or go
252 to the next feed with an unread item.
254 selection = self._widget.get_selection()
255 (model, treeiter) = selection.get_selected()
256 # check if we have a selection. if none,
257 # start searching from the first item
258 if not treeiter:
259 treeiter = model.get_iter_first()
260 if not treeiter: return False
261 nextiter = model.iter_next(treeiter)
262 if not nextiter: return False # no more rows to iterate
263 treerow = model[nextiter]
264 has_unread = False
265 while(treerow):
266 item = treerow[Column.ITEM]
267 if not item.is_read:
268 has_unread = True
269 selection.select_path(treerow.path)
270 break
271 treerow = treerow.next
272 return has_unread
274 class ItemListPresenter(MVP.BasicPresenter):
276 ## XXX listen for RefreshFeedDisplaySignal, FeedOrder changes,
278 def _initialize(self):
279 self.model = ItemListModel()
281 def feedlist_selection_changed(self, feedlist_selection, column):
282 (model, pathlist) = feedlist_selection.get_selected_rows()
283 iters = [model.get_iter(path) for path in pathlist]
285 if not iters:
286 return
288 nodes = [model.get_value(treeiter, column) for treeiter in iters]
289 items = []
290 node = nodes[0]
292 def populate_items(items):
293 self.view._widget.set_model(None)
294 #import time
295 #s = time.time()
296 self.model.populate(items)
297 #print "populate took %s" % (time.time() - s)
298 #gobject.idle_add(self.model.populate, items)
299 self.view._widget.set_model(self._model.model)
301 if node.node.type == "F":
302 items = ItemManager.get_feed_items(node.node.id)
303 populate_items(items)
304 elif node.node.type == "C":
305 items = ItemManager.get_category_items(node.node.id, populate_items)
307 #self.view._widget.set_model(None)
308 #self.model.populate(items)
309 #self.view._widget.set_model(self._model.model)
311 #if not len(self.model.model): return
312 #if not self.view.select_next_unread_item():
313 # self.view.select_first_item()
315 def flatten(self, sequence):
316 ''' python cookbook recipe 4.6 '''
317 for item in sequence:
318 if isinstance(item, (list, tuple)):
319 for subitem in self.flatten(item):
320 yield subitem
321 else:
322 yield item
324 def select_previous_item(self):
325 return self.view.select_previous_item()
327 def select_next_item(self):
328 return self.view.select_next_item()
330 def select_next_unread_item(self):
331 return self.view.select_next_unread_item()
333 def select_last_item(self):
334 return self.view.select_last_item()