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 from straw
import helpers
25 from xml
.sax
import saxutils
31 import os
.path
, operator
38 Constants for the item list treeview columns
40 TITLE
, STICKY
, ITEM
, WEIGHT
, STICKY_FLAG
, FOREGROUND
= range(6)
43 ''' A model of summary items for tree views '''
46 self
.store
= gtk
.ListStore(str, gobject
.TYPE_OBJECT
, gobject
.TYPE_PYOBJECT
, int, bool, str)
47 config
= Config
.get_instance()
48 self
.item_order
= config
.item_order
50 def populate(self
, items
):
51 #print "POPULATE ", len(summaryItems)
52 ## XXX should we sort here or in feeds.Feed?
53 #items = sorted(summaryItems, key=operator.attrgetter('title'),
54 # reverse=self.item_order)
61 weight
= (pango
.WEIGHT_BOLD
, pango
.WEIGHT_NORMAL
)[item
.is_read
]
62 #weight = pango.WEIGHT_NORMAL
63 #colour = ("#0000FF", "#000000")[item.is_read]
65 treeiter
= self
.store
.append()
66 self
.store
.set(treeiter
,
67 Column
.TITLE
, item
.title
,
69 Column
.WEIGHT
, weight
,
70 Column
.STICKY_FLAG
, False,
71 Column
.FOREGROUND
, colour
)
73 item
.connect("notify", self
.item_changed_cb
)
75 def item_changed_cb(self
, item
, property):
76 row
= [row
for row
in self
.store
if row
[Column
.ITEM
] is item
]
81 row
[0][Column
.WEIGHT
] = (pango
.WEIGHT_BOLD
, pango
.WEIGHT_NORMAL
)[item
.is_read
]
87 class ItemListView(MVP
.WidgetView
):
89 def _initialize(self
):
90 self
._widget
.set_rules_hint(False)
91 self
._widget
.connect("button_press_event",self
._on
_button
_press
_event
)
92 self
._widget
.connect("popup-menu", self
._on
_popup
_menu
)
93 self
._widget
.connect("row-activated", self
._on
_row
_activated
)
94 uifactory
= helpers
.UIFactory('ItemListActions')
95 action
= uifactory
.get_action('/itemlist_popup/mark_as_unread')
96 action
.connect('activate', self
.on_menu_mark_as_unread_activate
)
97 self
.popup
= uifactory
.get_popup('/itemlist_popup')
99 renderer
= gtk
.CellRendererToggle()
100 column
= gtk
.TreeViewColumn(_('Keep'), renderer
,
101 active
=Column
.STICKY_FLAG
)
102 column
.set_resizable(True)
103 column
.set_reorderable(True)
104 self
._widget
.append_column(column
)
105 renderer
.connect('toggled', self
._sticky
_toggled
)
107 renderer
= gtk
.CellRendererText()
108 column
= gtk
.TreeViewColumn(_('_Title'), renderer
,
110 foreground
=Column
.FOREGROUND
,
111 weight
=Column
.WEIGHT
)
112 column
.set_resizable(True)
113 column
.set_reorderable(True)
114 self
._widget
.append_column(column
)
116 selection
= self
._widget
.get_selection()
117 selection
.set_mode(gtk
.SELECTION_SINGLE
)
118 # selection.connect('changed', lambda x: self.item_mark_as_read(True))
120 def add_selection_changed_listener(self
, listener
):
121 selection
= self
._widget
.get_selection()
122 selection
.connect('changed',
123 listener
.itemlist_selection_changed
,
126 def _model_set(self
):
127 self
._widget
.set_model(self
._model
.model
)
129 def _sticky_toggled(self
, cell
, path
):
130 model
= self
._widget
.get_model()
131 treeiter
= model
.get_iter((int(path
),))
132 item
= model
.get_value(treeiter
, Column
.ITEM
)
133 item
.sticky
= not item
.sticky
134 model
.set(treeiter
, Column
.STICKY_FLAG
, item
.sticky
)
136 def _on_popup_menu(self
, treeview
, *args
):
137 self
.popup
.popup(None,None,None,0,0)
139 def _on_button_press_event(self
, treeview
, event
):
141 if event
.button
== 3:
144 time
= gtk
.get_current_event_time()
145 path
= treeview
.get_path_at_pos(x
, y
)
148 path
, col
, cellx
, celly
= path
149 treeview
.grab_focus()
150 treeview
.set_cursor( path
, col
, 0)
151 self
.popup
.popup(None, None, None, event
.button
, time
)
155 def on_menu_mark_as_unread_activate(self
, *args
):
156 self
.item_mark_as_read(False)
158 def item_mark_as_read(self
, isRead
):
159 selection
= self
._widget
.get_selection()
160 (model
, treeiter
) = selection
.get_selected()
161 if not treeiter
: return
162 item
= model
.get_value(treeiter
, Column
.ITEM
)
164 item
.props
.is_read
= isRead
167 weight
= pango
.WEIGHT_NORMAL
169 weight
= pango
.WEIGHT_BOLD
170 #colour = ("#0000FF", "#000000")[item.is_read]
172 model
.set(treeiter
, Column
.FOREGROUND
, colour
,
173 Column
.WEIGHT
, weight
,
177 def _on_row_activated(self
, treeview
, path
, column
):
178 ''' double-clicking an item opens that item in the web browser '''
179 model
= self
._widget
.get_model()
181 treeiter
= model
.get_iter(path
)
184 link
= model
.get_value(treeiter
, Column
.ITEM
).link
186 helpers
.url_show(link
)
189 def select_first_item(self
):
190 selection
= self
._widget
.get_selection()
191 (model
, treeiter
) = selection
.get_selected()
192 path
= model
.get_path(model
.get_iter_first())
193 selection
.select_path(path
)
196 def select_previous_item(self
):
198 Selects the item before the current selection. If there
199 is no current selection, selects the last item. If there is no
200 previous row, returns False.
202 selection
= self
._widget
.get_selection()
203 (model
, treeiter
) = selection
.get_selected()
204 if not treeiter
: return False
205 path
= model
.get_path(treeiter
)
209 selection
.select_path(prev
)
212 def select_next_item(self
):
214 Selects the item after the current selection. If there is no current
215 selection, selects the first item. If there is no next row, returns False.
217 selection
= self
._widget
.get_selection()
218 (model
, treeiter
) = selection
.get_selected()
220 treeiter
= model
.get_iter_first()
221 next_iter
= model
.iter_next(treeiter
)
222 if not next_iter
or not model
.iter_is_valid(treeiter
):
224 next_path
= model
.get_path(next_iter
)
225 selection
.select_path(next_path
)
228 def select_last_item(self
):
230 Selects the last item in this list.
232 selection
= self
._widget
.get_selection()
233 selection
.select_path(len(self
.model
.model
) - 1)
236 def select_next_unread_item(self
):
238 Selects the first unread item after the current selection. If there is
239 no current selection, or if there are no unread items, returns False..
241 By returning False, the caller can either go to the next feed, or go
242 to the next feed with an unread item.
244 selection
= self
._widget
.get_selection()
245 (model
, treeiter
) = selection
.get_selected()
246 # check if we have a selection. if none,
247 # start searching from the first item
249 treeiter
= model
.get_iter_first()
250 if not treeiter
: return False
251 nextiter
= model
.iter_next(treeiter
)
252 if not nextiter
: return False # no more rows to iterate
253 treerow
= model
[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, FeedOrder changes,
268 def _initialize(self
):
269 self
.model
= ItemListModel()
271 def feedlist_selection_changed(self
, feedlist_selection
, column
):
272 (model
, pathlist
) = feedlist_selection
.get_selected_rows()
273 iters
= [model
.get_iter(path
) for path
in pathlist
]
276 nodes
= [model
.get_value(treeiter
, column
) for treeiter
in iters
]
281 if node
.node
.type == "F":
282 items
= FeedManager
.get_feed_items(node
.node
.id)
283 elif node
.node
.type == "C":
284 items
= FeedManager
.get_category_items(node
.node
.id)
286 self
.model
.populate(items
)
287 if not len(self
.model
.model
): return
288 if not self
.view
.select_next_unread_item():
289 self
.view
.select_first_item()
291 def flatten(self
, sequence
):
292 ''' python cookbook recipe 4.6 '''
293 for item
in sequence
:
294 if isinstance(item
, (list, tuple)):
295 for subitem
in self
.flatten(item
):
300 def select_previous_item(self
):
301 return self
.view
.select_previous_item()
303 def select_next_item(self
):
304 return self
.view
.select_next_item()
306 def select_next_unread_item(self
):
307 return self
.view
.select_next_unread_item()
309 def select_last_item(self
):
310 return self
.view
.select_last_item()