3 Handles listing of feed items in a view (i.e. GTK+ TreeView)
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.
25 from xml
.sax
import saxutils
37 Constants for the item list treeview columns
39 title
, sticky
, item
, weight
, sticky_flag
, foreground
= range(6)
45 def __init__(self
, listener
):
46 self
.manager
= gtk
.UIManager()
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)
59 return self
.manager
.get_widget('/itemlist_popup')
63 ''' A model of summary items for tree views '''
66 self
.store
= gtk
.ListStore(str, gobject
.TYPE_OBJECT
, gobject
.TYPE_PYOBJECT
, int, bool, str)
68 def populate(self
, summaryItems
):
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
,
77 Column
.weight
, weight
,
78 Column
.sticky_flag
, item
.sticky
,
79 Column
.foreground
, colour
)
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
,
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
,
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)
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
):
139 if event
.button
== 3:
142 time
= gtk
.get_current_event_time()
143 path
= treeview
.get_path_at_pos(x
, y
)
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
)
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
)
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
,
166 def _on_row_activated(self
, treeview
, path
, column
):
167 model
= self
._widget
.get_model()
169 treeiter
= model
.get_iter(path
)
172 link
= model
.get_value(treeiter
, Column
.ITEM
).link
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
196 treeiter
= model
.get_iter_first()
197 path
= model
.get_path(treeiter
)
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
205 selection
.select_path(path
[-1]-1,)
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()
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
):
220 next_path
= model
.get_path(next_iter
)
221 selection
.select_path(next_path
)
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)
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.
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
248 model
, treeiter
= selected_row
251 nextiter
= model
.iter_next(treeiter
)
252 if not nextiter
: # no more rows to iterate
254 treerow
= self
._model
[model
.get_path(nextiter
)]
256 item
= treerow
[Column
.item
]
259 selection
.select_path(treerow
.path
)
261 treerow
= treerow
.next
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
279 nodes
= [model
.get_value(treeiter
, column
) for treeiter
in iters
]
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()
287 ## XXX display child items of Category
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,
299 self._selected_feed = feed
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,
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,))
313 def _feed_order_changed(self, event):
314 if event.sender is self._selected_feed:
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)
323 path = (item.feed.get_item_index(item),)
325 path = (0,) # first item
326 selection.select_path(path)
328 def _all_items_read(self, feed, 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):
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,
346 Column.WEIGHT, weight,
347 Column.sticky_flag, item.sticky,
348 Column.FOREGROUND, colour)
349 if item is self._selected_item:
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):
359 path = (item.feed.get_item_index(item),)
360 self._set_column_weight(item, path)