Fixed new feed items date handling, added date column to the item list, removed Summa...
[straw.git] / straw / ItemList.py
blob110b62796a03bf243dd378194bae3eed333dfb18
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 straw import helpers
25 from xml.sax import saxutils
26 import Config
27 import ItemManager
28 import MVP
29 import error
30 import gobject
31 import gtk
32 import os.path, operator
33 import pango
34 import pygtk
35 pygtk.require('2.0')
37 class Column:
38 """
39 Constants for the item list treeview columns
40 """
41 TITLE, PUB_DATE, STICKY, ITEM, WEIGHT, STICKY_FLAG, FOREGROUND = range(7)
43 class ItemListModel:
44 ''' A model of summary items for tree views '''
46 def __init__(self):
47 self.store = gtk.ListStore(str, str, gobject.TYPE_OBJECT, gobject.TYPE_PYOBJECT, int, bool, str)
48 config = Config.get_instance()
49 self.item_order = config.item_order
51 def populate(self, items):
52 #print "POPULATE ", len(summaryItems)
53 ## XXX should we sort here or in feeds.Feed?
54 #items = sorted(summaryItems, key=operator.attrgetter('title'),
55 # reverse=self.item_order)
56 self.store.clear()
58 if not items:
59 return
61 for item in items:
62 weight = (pango.WEIGHT_BOLD, pango.WEIGHT_NORMAL)[item.is_read]
63 #weight = pango.WEIGHT_NORMAL
64 #colour = ("#0000FF", "#000000")[item.is_read]
65 colour = "#000000"
66 treeiter = self.store.append()
67 self.store.set(treeiter,
68 Column.TITLE, item.title,
69 Column.PUB_DATE, item.pub_date,
70 Column.ITEM, item,
71 Column.WEIGHT, weight,
72 Column.STICKY_FLAG, False,
73 Column.FOREGROUND, colour)
75 item.connect("notify", self.item_changed_cb)
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 if node.node.type == "F":
293 items = ItemManager.get_feed_items(node.node.id)
294 elif node.node.type == "C":
295 items = ItemManager.get_category_items(node.node.id)
297 self.model.populate(items)
298 if not len(self.model.model): return
299 if not self.view.select_next_unread_item():
300 self.view.select_first_item()
302 def flatten(self, sequence):
303 ''' python cookbook recipe 4.6 '''
304 for item in sequence:
305 if isinstance(item, (list, tuple)):
306 for subitem in self.flatten(item):
307 yield subitem
308 else:
309 yield item
311 def select_previous_item(self):
312 return self.view.select_previous_item()
314 def select_next_item(self):
315 return self.view.select_next_item()
317 def select_next_unread_item(self):
318 return self.view.select_next_unread_item()
320 def select_last_item(self):
321 return self.view.select_last_item()