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 Constants
import *
25 from straw
import helpers
26 from xml
.sax
import saxutils
33 import os
.path
, operator
41 Constants for the item list treeview columns
43 TITLE
, PUB_DATE
, STICKY
, ITEM
, WEIGHT
, STICKY_FLAG
, FOREGROUND
= range(7)
46 ''' A model of summary items for tree views '''
49 self
.store
= gtk
.ListStore(str, str, gobject
.TYPE_OBJECT
, gobject
.TYPE_PYOBJECT
, int, bool, str)
51 def populate(self
, items
):
59 weight
= (pango
.WEIGHT_BOLD
, pango
.WEIGHT_NORMAL
)[item
.is_read
]
60 #weight = pango.WEIGHT_NORMAL
61 #colour = ("#0000FF", "#000000")[item.is_read]
63 treeiter
= self
.store
.append()
66 self
.store
.set(treeiter
,
67 Column
.TITLE
, item
.title
,
68 Column
.PUB_DATE
, item
.pub_date
,
70 Column
.WEIGHT
, weight
,
71 Column
.STICKY_FLAG
, False,
72 Column
.FOREGROUND
, colour
)
74 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
]
83 row
[0][Column
.WEIGHT
] = (pango
.WEIGHT_BOLD
, pango
.WEIGHT_NORMAL
)[item
.is_read
]
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
,
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
,
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
):
151 if event
.button
== 3:
154 time
= gtk
.get_current_event_time()
155 path
= treeview
.get_path_at_pos(x
, y
)
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
)
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
177 weight
= pango
.WEIGHT_NORMAL
179 weight
= pango
.WEIGHT_BOLD
180 #colour = ("#0000FF", "#000000")[item.is_read]
182 model
.set(treeiter
, Column
.FOREGROUND
, colour
,
183 Column
.WEIGHT
, weight
,
186 def mark_all_as_read(self
):
187 for row
in self
._widget
.get_model():
188 row
[Column
.WEIGHT
] = pango
.WEIGHT_NORMAL
190 def _on_row_activated(self
, treeview
, path
, column
):
191 ''' double-clicking an item opens that item in the web browser '''
192 model
= self
._widget
.get_model()
194 treeiter
= model
.get_iter(path
)
197 link
= model
.get_value(treeiter
, Column
.ITEM
).link
199 helpers
.url_show(link
)
202 def select_first_item(self
):
203 selection
= self
._widget
.get_selection()
204 (model
, treeiter
) = selection
.get_selected()
209 path
= model
.get_path(model
.get_iter_first())
210 selection
.select_path(path
)
213 def select_previous_item(self
):
215 Selects the item before the current selection. If there
216 is no current selection, selects the last item. If there is no
217 previous row, returns False.
219 selection
= self
._widget
.get_selection()
220 (model
, treeiter
) = selection
.get_selected()
221 if not treeiter
: return False
222 path
= model
.get_path(treeiter
)
226 selection
.select_path(prev
)
229 def select_next_item(self
):
231 Selects the item after the current selection. If there is no current
232 selection, selects the first item. If there is no next row, returns False.
234 selection
= self
._widget
.get_selection()
235 (model
, treeiter
) = selection
.get_selected()
237 treeiter
= model
.get_iter_first()
238 next_iter
= model
.iter_next(treeiter
)
239 if not next_iter
or not model
.iter_is_valid(treeiter
):
241 next_path
= model
.get_path(next_iter
)
242 selection
.select_path(next_path
)
245 def select_last_item(self
):
247 Selects the last item in this list.
249 selection
= self
._widget
.get_selection()
250 selection
.select_path(len(self
.model
.model
) - 1)
253 def select_next_unread_item(self
):
255 Selects the first unread item after the current selection. If there is
256 no current selection, or if there are no unread items, returns False..
258 By returning False, the caller can either go to the next feed, or go
259 to the next feed with an unread item.
261 selection
= self
._widget
.get_selection()
262 (model
, treeiter
) = selection
.get_selected()
263 # check if we have a selection. if none,
264 # start searching from the first item
266 treeiter
= model
.get_iter_first()
267 if not treeiter
: return False
268 nextiter
= model
.iter_next(treeiter
)
269 if not nextiter
: return False # no more rows to iterate
270 treerow
= model
[treeiter
]
273 item
= treerow
[Column
.ITEM
]
276 selection
.select_path(treerow
.path
)
278 treerow
= treerow
.next
281 class ItemListPresenter(MVP
.BasicPresenter
):
283 ## XXX listen for RefreshFeedDisplaySignal, FeedOrder changes,
285 def _initialize(self
):
286 self
.model
= ItemListModel()
287 self
.presented_nodes
= []
289 def populate(self
, items
):
290 self
.model
.populate(items
)
291 self
.view
.set_model(self
.model
)
293 def feedlist_selection_changed(self
, feedlist_selection
, column
):
294 (model
, pathlist
) = feedlist_selection
.get_selected_rows()
295 iters
= [model
.get_iter(path
) for path
in pathlist
]
300 nodes
= [model
.get_value(treeiter
, column
) for treeiter
in iters
]
302 if nodes
== self
.presented_nodes
:
303 # Selection hasn't actually changed so let's ignore the signal
306 self
.presented_nodes
= nodes
311 if node
.node
.type == "F":
312 items
= ItemManager
.get_feed_items(node
.node
.id)
313 self
.view
.set_model(None)
314 self
.model
.populate(items
)
315 self
.view
.set_model(self
.model
)
316 elif node
.node
.type == "C":
317 self
.view
.set_model(None)
318 items
= ItemManager
.get_category_items(node
.node
.id, self
.populate
)
320 if not self
.view
.select_next_unread_item():
321 self
.view
.select_first_item()
323 def on_mark_all_as_read(self
, action
):
324 self
.view
.mark_all_as_read()
326 def flatten(self
, sequence
):
327 ''' python cookbook recipe 4.6 '''
328 for item
in sequence
:
329 if isinstance(item
, (list, tuple)):
330 for subitem
in self
.flatten(item
):
335 def select_previous_item(self
):
336 return self
.view
.select_previous_item()
338 def select_next_item(self
):
339 return self
.view
.select_next_item()
341 def select_next_unread_item(self
):
342 return self
.view
.select_next_unread_item()
344 def select_last_item(self
):
345 return self
.view
.select_last_item()