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.
24 import os
.path
, operator
25 from xml
.sax
import saxutils
35 from straw
import helpers
39 Constants for the item list treeview columns
41 TITLE
, STICKY
, ITEM
, WEIGHT
, STICKY_FLAG
, FOREGROUND
= range(6)
44 ''' A model of summary items for tree views '''
47 self
.store
= gtk
.ListStore(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)
58 weight
= (pango
.WEIGHT_BOLD
, pango
.WEIGHT_NORMAL
)[item
.is_read
]
59 #weight = pango.WEIGHT_NORMAL
60 #colour = ("#0000FF", "#000000")[item.is_read]
62 treeiter
= self
.store
.append()
63 self
.store
.set(treeiter
,
64 Column
.TITLE
, item
.title
,
66 Column
.WEIGHT
, weight
,
67 Column
.STICKY_FLAG
, False,
68 Column
.FOREGROUND
, colour
)
69 #item.connect('changed', self.item_changed_cb)
71 def item_changed_cb(self
, item
):
72 for row
in self
.store
:
73 rowitem
= row
[Column
.ITEM
]
75 #row[Column.weight] = item.is_read and pango.WEIGHT_NORMAL or pango.WEIGHT_BOLD
76 #row[Column.foreground] = item.is_read and 'black' or 'blue'
77 #row[Column.sticky_flag] = item.sticky
78 row
[Column
.ITEM
] = item
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(_('_Title'), renderer
,
108 foreground
=Column
.FOREGROUND
,
109 weight
=Column
.WEIGHT
)
110 column
.set_resizable(True)
111 column
.set_reorderable(True)
112 self
._widget
.append_column(column
)
114 selection
= self
._widget
.get_selection()
115 selection
.set_mode(gtk
.SELECTION_SINGLE
)
116 selection
.connect('changed', lambda x
: self
.item_mark_as_read(True))
118 def add_selection_changed_listener(self
, listener
):
119 selection
= self
._widget
.get_selection()
120 selection
.connect('changed',
121 listener
.itemlist_selection_changed
,
124 def _model_set(self
):
125 self
._widget
.set_model(self
._model
.model
)
127 def _sticky_toggled(self
, cell
, path
):
128 model
= self
._widget
.get_model()
129 treeiter
= model
.get_iter((int(path
),))
130 item
= model
.get_value(treeiter
, Column
.ITEM
)
131 item
.sticky
= not item
.sticky
132 model
.set(treeiter
, Column
.STICKY_FLAG
, item
.sticky
)
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 self
.item_mark_as_read(False)
156 def item_mark_as_read(self
, isRead
):
157 selection
= self
._widget
.get_selection()
158 (model
, treeiter
) = selection
.get_selected()
159 if not treeiter
: return
160 item
= model
.get_value(treeiter
, Column
.ITEM
)
162 item
.props
.is_read
= isRead
165 weight
= pango
.WEIGHT_NORMAL
167 weight
= pango
.WEIGHT_BOLD
168 #colour = ("#0000FF", "#000000")[item.is_read]
170 model
.set(treeiter
, Column
.FOREGROUND
, colour
,
171 Column
.WEIGHT
, weight
,
175 def _on_row_activated(self
, treeview
, path
, column
):
176 ''' double-clicking an item opens that item in the web browser '''
177 model
= self
._widget
.get_model()
179 treeiter
= model
.get_iter(path
)
182 link
= model
.get_value(treeiter
, Column
.ITEM
).link
184 helpers
.url_show(link
)
187 def select_first_item(self
):
188 selection
= self
._widget
.get_selection()
189 (model
, treeiter
) = selection
.get_selected()
190 path
= model
.get_path(model
.get_iter_first())
191 selection
.select_path(path
)
194 def select_previous_item(self
):
196 Selects the item before the current selection. If there
197 is no current selection, selects the last item. If there is no
198 previous row, returns False.
200 selection
= self
._widget
.get_selection()
201 (model
, treeiter
) = selection
.get_selected()
202 if not treeiter
: return False
203 path
= model
.get_path(treeiter
)
207 selection
.select_path(prev
)
210 def select_next_item(self
):
212 Selects the item after the current selection. If there is no current
213 selection, selects the first item. If there is no next row, returns False.
215 selection
= self
._widget
.get_selection()
216 (model
, treeiter
) = selection
.get_selected()
218 treeiter
= model
.get_iter_first()
219 next_iter
= model
.iter_next(treeiter
)
220 if not next_iter
or not model
.iter_is_valid(treeiter
):
222 next_path
= model
.get_path(next_iter
)
223 selection
.select_path(next_path
)
226 def select_last_item(self
):
228 Selects the last item in this list.
230 selection
= self
._widget
.get_selection()
231 selection
.select_path(len(self
.model
.model
) - 1)
234 def select_next_unread_item(self
):
236 Selects the first unread item after the current selection. If there is
237 no current selection, or if there are no unread items, returns False..
239 By returning False, the caller can either go to the next feed, or go
240 to the next feed with an unread item.
242 selection
= self
._widget
.get_selection()
243 (model
, treeiter
) = selection
.get_selected()
244 # check if we have a selection. if none,
245 # start searching from the first item
247 treeiter
= model
.get_iter_first()
248 if not treeiter
: return False
249 nextiter
= model
.iter_next(treeiter
)
250 if not nextiter
: return False # no more rows to iterate
251 treerow
= model
[nextiter
]
254 item
= treerow
[Column
.ITEM
]
257 selection
.select_path(treerow
.path
)
259 treerow
= treerow
.next
262 class ItemListPresenter(MVP
.BasicPresenter
):
264 ## XXX listen for RefreshFeedDisplaySignal, FeedOrder changes,
266 def _initialize(self
):
267 self
.model
= ItemListModel()
269 def feedlist_selection_changed(self
, feedlist_selection
, column
):
270 (model
, pathlist
) = feedlist_selection
.get_selected_rows()
271 iters
= [model
.get_iter(path
) for path
in pathlist
]
274 nodes
= [model
.get_value(treeiter
, column
) for treeiter
in iters
]
280 items
= FeedManager
.get_feed_items(nodes
[0].obj
.id)
281 elif node
.type == "C":
282 items
= FeedManager
.get_category_items(nodes
[0].obj
.id)
284 # items += [node.feed.items for node in nodes]
285 #except TypeError, te:
286 # gen = (node.category.feeds for node in nodes)
287 # items += [feed.items for feed in gen.next()]
288 #items = list(self.flatten(items))
289 self
.model
.populate(items
)
290 if not len(self
.model
.model
): return
291 if not self
.view
.select_next_unread_item():
292 self
.view
.select_first_item()
294 def flatten(self
, sequence
):
295 ''' python cookbook recipe 4.6 '''
296 for item
in sequence
:
297 if isinstance(item
, (list, tuple)):
298 for subitem
in self
.flatten(item
):
303 def select_previous_item(self
):
304 return self
.view
.select_previous_item()
306 def select_next_item(self
):
307 return self
.view
.select_next_item()
309 def select_next_unread_item(self
):
310 return self
.view
.select_next_unread_item()
312 def select_last_item(self
):
313 return self
.view
.select_last_item()