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
,
187 def _on_row_activated(self
, treeview
, path
, column
):
188 ''' double-clicking an item opens that item in the web browser '''
189 model
= self
._widget
.get_model()
191 treeiter
= model
.get_iter(path
)
194 link
= model
.get_value(treeiter
, Column
.ITEM
).link
196 helpers
.url_show(link
)
199 def select_first_item(self
):
200 selection
= self
._widget
.get_selection()
201 (model
, treeiter
) = selection
.get_selected()
202 path
= model
.get_path(model
.get_iter_first())
203 selection
.select_path(path
)
206 def select_previous_item(self
):
208 Selects the item before the current selection. If there
209 is no current selection, selects the last item. If there is no
210 previous row, returns False.
212 selection
= self
._widget
.get_selection()
213 (model
, treeiter
) = selection
.get_selected()
214 if not treeiter
: return False
215 path
= model
.get_path(treeiter
)
219 selection
.select_path(prev
)
222 def select_next_item(self
):
224 Selects the item after the current selection. If there is no current
225 selection, selects the first item. If there is no next row, returns False.
227 selection
= self
._widget
.get_selection()
228 (model
, treeiter
) = selection
.get_selected()
230 treeiter
= model
.get_iter_first()
231 next_iter
= model
.iter_next(treeiter
)
232 if not next_iter
or not model
.iter_is_valid(treeiter
):
234 next_path
= model
.get_path(next_iter
)
235 selection
.select_path(next_path
)
238 def select_last_item(self
):
240 Selects the last item in this list.
242 selection
= self
._widget
.get_selection()
243 selection
.select_path(len(self
.model
.model
) - 1)
246 def select_next_unread_item(self
):
248 Selects the first unread item after the current selection. If there is
249 no current selection, or if there are no unread items, returns False..
251 By returning False, the caller can either go to the next feed, or go
252 to the next feed with an unread item.
254 selection
= self
._widget
.get_selection()
255 (model
, treeiter
) = selection
.get_selected()
256 # check if we have a selection. if none,
257 # start searching from the first item
259 treeiter
= model
.get_iter_first()
260 if not treeiter
: return False
261 nextiter
= model
.iter_next(treeiter
)
262 if not nextiter
: return False # no more rows to iterate
263 treerow
= model
[nextiter
]
266 item
= treerow
[Column
.ITEM
]
269 selection
.select_path(treerow
.path
)
271 treerow
= treerow
.next
274 class ItemListPresenter(MVP
.BasicPresenter
):
276 ## XXX listen for RefreshFeedDisplaySignal, FeedOrder changes,
278 def _initialize(self
):
279 self
.model
= ItemListModel()
281 def feedlist_selection_changed(self
, feedlist_selection
, column
):
282 (model
, pathlist
) = feedlist_selection
.get_selected_rows()
283 iters
= [model
.get_iter(path
) for path
in pathlist
]
288 nodes
= [model
.get_value(treeiter
, column
) for treeiter
in iters
]
292 def populate_items(items
):
293 self
.view
._widget
.set_model(None)
296 self
.model
.populate(items
)
297 #print "populate took %s" % (time.time() - s)
298 #gobject.idle_add(self.model.populate, items)
299 self
.view
._widget
.set_model(self
._model
.model
)
301 if node
.node
.type == "F":
302 items
= ItemManager
.get_feed_items(node
.node
.id)
303 populate_items(items
)
304 elif node
.node
.type == "C":
305 items
= ItemManager
.get_category_items(node
.node
.id, populate_items
)
307 #self.view._widget.set_model(None)
308 #self.model.populate(items)
309 #self.view._widget.set_model(self._model.model)
311 #if not len(self.model.model): return
312 #if not self.view.select_next_unread_item():
313 # self.view.select_first_item()
315 def flatten(self
, sequence
):
316 ''' python cookbook recipe 4.6 '''
317 for item
in sequence
:
318 if isinstance(item
, (list, tuple)):
319 for subitem
in self
.flatten(item
):
324 def select_previous_item(self
):
325 return self
.view
.select_previous_item()
327 def select_next_item(self
):
328 return self
.view
.select_next_item()
330 def select_next_unread_item(self
):
331 return self
.view
.select_next_unread_item()
333 def select_last_item(self
):
334 return self
.view
.select_last_item()