straw/Makefile.am: added Fetcher and ItemManager, removed URLFetch
[straw.git] / straw / ItemList.py
blob46285fbae7baf067c6e9d4e558d84abf6bf96e04
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
57 for item in items:
58 weight = (pango.WEIGHT_BOLD, pango.WEIGHT_NORMAL)[item.is_read]
59 #weight = pango.WEIGHT_NORMAL
60 #colour = ("#0000FF", "#000000")[item.is_read]
61 colour = "#000000"
62 treeiter = self.store.append()
63 self.store.set(treeiter,
64 Column.TITLE, item.title,
65 Column.PUB_DATE, item.pub_date,
66 Column.ITEM, item,
67 Column.WEIGHT, weight,
68 Column.STICKY_FLAG, False,
69 Column.FOREGROUND, colour)
71 item.connect("notify", self.item_changed_cb)
73 def item_changed_cb(self, item, property):
74 row = [row for row in self.store if row[Column.ITEM] is item]
76 if len(row) == 0:
77 return
79 row[0][Column.WEIGHT] = (pango.WEIGHT_BOLD, pango.WEIGHT_NORMAL)[item.is_read]
81 @property
82 def model(self):
83 return self.store
85 class ItemListView(MVP.WidgetView):
87 def _initialize(self):
88 self._widget.set_rules_hint(False)
89 self._widget.connect("button_press_event",self._on_button_press_event)
90 self._widget.connect("popup-menu", self._on_popup_menu)
91 self._widget.connect("row-activated", self._on_row_activated)
92 uifactory = helpers.UIFactory('ItemListActions')
93 action = uifactory.get_action('/itemlist_popup/mark_as_unread')
94 action.connect('activate', self.on_menu_mark_as_unread_activate)
95 self.popup = uifactory.get_popup('/itemlist_popup')
97 renderer = gtk.CellRendererToggle()
98 column = gtk.TreeViewColumn(_('Keep'), renderer,
99 active=Column.STICKY_FLAG)
100 column.set_resizable(True)
101 column.set_reorderable(True)
102 self._widget.append_column(column)
103 renderer.connect('toggled', self._sticky_toggled)
105 renderer = gtk.CellRendererText()
106 column = gtk.TreeViewColumn(_('_Date'), renderer,
107 text=Column.PUB_DATE,
108 weight=Column.WEIGHT)
109 column.set_resizable(True)
110 column.set_reorderable(True)
111 self._widget.append_column(column)
113 renderer = gtk.CellRendererText()
114 column = gtk.TreeViewColumn(_('_Title'), renderer,
115 text=Column.TITLE,
116 foreground=Column.FOREGROUND,
117 weight=Column.WEIGHT)
118 column.set_resizable(True)
119 column.set_reorderable(True)
120 self._widget.append_column(column)
122 selection = self._widget.get_selection()
123 selection.set_mode(gtk.SELECTION_SINGLE)
124 # selection.connect('changed', lambda x: self.item_mark_as_read(True))
126 def add_selection_changed_listener(self, listener):
127 selection = self._widget.get_selection()
128 selection.connect('changed',
129 listener.itemlist_selection_changed,
130 Column.ITEM)
132 def _model_set(self):
133 self._widget.set_model(self._model.model)
135 def _sticky_toggled(self, cell, path):
136 model = self._widget.get_model()
137 treeiter = model.get_iter((int(path),))
138 item = model.get_value(treeiter, Column.ITEM)
139 item.sticky = not item.sticky
140 model.set(treeiter, Column.STICKY_FLAG, item.sticky)
142 def _on_popup_menu(self, treeview, *args):
143 self.popup.popup(None,None,None,0,0)
145 def _on_button_press_event(self, treeview, event):
146 val = 0
147 if event.button == 3:
148 x = int(event.x)
149 y = int(event.y)
150 time = gtk.get_current_event_time()
151 path = treeview.get_path_at_pos(x, y)
152 if path is None:
153 return 1
154 path, col, cellx, celly = path
155 treeview.grab_focus()
156 treeview.set_cursor( path, col, 0)
157 self.popup.popup(None, None, None, event.button, time)
158 val = 1
159 return val
161 def on_menu_mark_as_unread_activate(self, *args):
162 self.item_mark_as_read(False)
164 def item_mark_as_read(self, isRead):
165 selection = self._widget.get_selection()
166 (model, treeiter) = selection.get_selected()
167 if not treeiter: return
168 item = model.get_value(treeiter, Column.ITEM)
170 item.props.is_read = isRead
172 if isRead:
173 weight = pango.WEIGHT_NORMAL
174 else:
175 weight = pango.WEIGHT_BOLD
176 #colour = ("#0000FF", "#000000")[item.is_read]
177 colour = "#000000"
178 model.set(treeiter, Column.FOREGROUND, colour,
179 Column.WEIGHT, weight,
180 Column.ITEM, item)
181 return
183 def _on_row_activated(self, treeview, path, column):
184 ''' double-clicking an item opens that item in the web browser '''
185 model = self._widget.get_model()
186 try:
187 treeiter = model.get_iter(path)
188 except ValueError:
189 return
190 link = model.get_value(treeiter, Column.ITEM).link
191 if link:
192 helpers.url_show(link)
193 return
195 def select_first_item(self):
196 selection = self._widget.get_selection()
197 (model, treeiter) = selection.get_selected()
198 path = model.get_path(model.get_iter_first())
199 selection.select_path(path)
200 return True
202 def select_previous_item(self):
204 Selects the item before the current selection. If there
205 is no current selection, selects the last item. If there is no
206 previous row, returns False.
208 selection = self._widget.get_selection()
209 (model, treeiter) = selection.get_selected()
210 if not treeiter: return False
211 path = model.get_path(treeiter)
212 prev = path[-1]-1
213 if prev < 0:
214 return False
215 selection.select_path(prev)
216 return True
218 def select_next_item(self):
220 Selects the item after the current selection. If there is no current
221 selection, selects the first item. If there is no next row, returns False.
223 selection = self._widget.get_selection()
224 (model, treeiter) = selection.get_selected()
225 if not treeiter:
226 treeiter = model.get_iter_first()
227 next_iter = model.iter_next(treeiter)
228 if not next_iter or not model.iter_is_valid(treeiter):
229 return False
230 next_path = model.get_path(next_iter)
231 selection.select_path(next_path)
232 return True
234 def select_last_item(self):
236 Selects the last item in this list.
238 selection = self._widget.get_selection()
239 selection.select_path(len(self.model.model) - 1)
240 return True
242 def select_next_unread_item(self):
244 Selects the first unread item after the current selection. If there is
245 no current selection, or if there are no unread items, returns False..
247 By returning False, the caller can either go to the next feed, or go
248 to the next feed with an unread item.
250 selection = self._widget.get_selection()
251 (model, treeiter) = selection.get_selected()
252 # check if we have a selection. if none,
253 # start searching from the first item
254 if not treeiter:
255 treeiter = model.get_iter_first()
256 if not treeiter: return False
257 nextiter = model.iter_next(treeiter)
258 if not nextiter: return False # no more rows to iterate
259 treerow = model[nextiter]
260 has_unread = False
261 while(treerow):
262 item = treerow[Column.ITEM]
263 if not item.is_read:
264 has_unread = True
265 selection.select_path(treerow.path)
266 break
267 treerow = treerow.next
268 return has_unread
270 class ItemListPresenter(MVP.BasicPresenter):
272 ## XXX listen for RefreshFeedDisplaySignal, FeedOrder changes,
274 def _initialize(self):
275 self.model = ItemListModel()
277 def feedlist_selection_changed(self, feedlist_selection, column):
278 (model, pathlist) = feedlist_selection.get_selected_rows()
279 iters = [model.get_iter(path) for path in pathlist]
281 if not iters:
282 return
284 nodes = [model.get_value(treeiter, column) for treeiter in iters]
285 items = []
286 node = nodes[0]
288 if node.node.type == "F":
289 items = ItemManager.get_feed_items(node.node.id)
290 elif node.node.type == "C":
291 items = ItemManager.get_category_items(node.node.id)
293 self.view._widget.set_model(None)
295 self.model.populate(items)
297 self.view._widget.set_model(self._model.model)
299 if not len(self.model.model): return
300 if not self.view.select_next_unread_item():
301 self.view.select_first_item()
303 def flatten(self, sequence):
304 ''' python cookbook recipe 4.6 '''
305 for item in sequence:
306 if isinstance(item, (list, tuple)):
307 for subitem in self.flatten(item):
308 yield subitem
309 else:
310 yield item
312 def select_previous_item(self):
313 return self.view.select_previous_item()
315 def select_next_item(self):
316 return self.view.select_next_item()
318 def select_next_unread_item(self):
319 return self.view.select_next_unread_item()
321 def select_last_item(self):
322 return self.view.select_last_item()