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 ConfigOptions
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
):
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
,
65 Column
.PUB_DATE
, item
.pub_date
,
67 Column
.WEIGHT
, weight
,
68 Column
.STICKY_FLAG
, False,
69 Column
.FOREGROUND
, colour
)
71 item
.connect("notify", self
.item_changed_cb
)
73 def item_changed_cb(self
, item
, property):
74 row
= [row
for row
in self
.store
if row
[Column
.ITEM
] is item
]
79 row
[0][Column
.WEIGHT
] = (pango
.WEIGHT_BOLD
, pango
.WEIGHT_NORMAL
)[item
.is_read
]
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(_('_Date'), renderer
,
107 text
=Column
.PUB_DATE
,
108 weight
=Column
.WEIGHT
)
109 column
.set_resizable(True)
110 column
.set_reorderable(True)
111 self
._widget
.append_column(column
)
113 renderer
= gtk
.CellRendererText()
114 column
= gtk
.TreeViewColumn(_('_Title'), renderer
,
116 foreground
=Column
.FOREGROUND
,
117 weight
=Column
.WEIGHT
)
118 column
.set_resizable(True)
119 column
.set_reorderable(True)
120 self
._widget
.append_column(column
)
122 selection
= self
._widget
.get_selection()
123 selection
.set_mode(gtk
.SELECTION_SINGLE
)
124 # selection.connect('changed', lambda x: self.item_mark_as_read(True))
126 def add_selection_changed_listener(self
, listener
):
127 selection
= self
._widget
.get_selection()
128 selection
.connect('changed',
129 listener
.itemlist_selection_changed
,
132 def _model_set(self
):
133 self
._widget
.set_model(self
._model
.model
)
135 def _sticky_toggled(self
, cell
, path
):
136 model
= self
._widget
.get_model()
137 treeiter
= model
.get_iter((int(path
),))
138 item
= model
.get_value(treeiter
, Column
.ITEM
)
139 item
.sticky
= not item
.sticky
140 model
.set(treeiter
, Column
.STICKY_FLAG
, item
.sticky
)
142 def _on_popup_menu(self
, treeview
, *args
):
143 self
.popup
.popup(None,None,None,0,0)
145 def _on_button_press_event(self
, treeview
, event
):
147 if event
.button
== 3:
150 time
= gtk
.get_current_event_time()
151 path
= treeview
.get_path_at_pos(x
, y
)
154 path
, col
, cellx
, celly
= path
155 treeview
.grab_focus()
156 treeview
.set_cursor( path
, col
, 0)
157 self
.popup
.popup(None, None, None, event
.button
, time
)
161 def on_menu_mark_as_unread_activate(self
, *args
):
162 self
.item_mark_as_read(False)
164 def item_mark_as_read(self
, isRead
):
165 selection
= self
._widget
.get_selection()
166 (model
, treeiter
) = selection
.get_selected()
167 if not treeiter
: return
168 item
= model
.get_value(treeiter
, Column
.ITEM
)
170 item
.props
.is_read
= isRead
173 weight
= pango
.WEIGHT_NORMAL
175 weight
= pango
.WEIGHT_BOLD
176 #colour = ("#0000FF", "#000000")[item.is_read]
178 model
.set(treeiter
, Column
.FOREGROUND
, colour
,
179 Column
.WEIGHT
, weight
,
183 def _on_row_activated(self
, treeview
, path
, column
):
184 ''' double-clicking an item opens that item in the web browser '''
185 model
= self
._widget
.get_model()
187 treeiter
= model
.get_iter(path
)
190 link
= model
.get_value(treeiter
, Column
.ITEM
).link
192 helpers
.url_show(link
)
195 def select_first_item(self
):
196 selection
= self
._widget
.get_selection()
197 (model
, treeiter
) = selection
.get_selected()
198 path
= model
.get_path(model
.get_iter_first())
199 selection
.select_path(path
)
202 def select_previous_item(self
):
204 Selects the item before the current selection. If there
205 is no current selection, selects the last item. If there is no
206 previous row, returns False.
208 selection
= self
._widget
.get_selection()
209 (model
, treeiter
) = selection
.get_selected()
210 if not treeiter
: return False
211 path
= model
.get_path(treeiter
)
215 selection
.select_path(prev
)
218 def select_next_item(self
):
220 Selects the item after the current selection. If there is no current
221 selection, selects the first item. If there is no next row, returns False.
223 selection
= self
._widget
.get_selection()
224 (model
, treeiter
) = selection
.get_selected()
226 treeiter
= model
.get_iter_first()
227 next_iter
= model
.iter_next(treeiter
)
228 if not next_iter
or not model
.iter_is_valid(treeiter
):
230 next_path
= model
.get_path(next_iter
)
231 selection
.select_path(next_path
)
234 def select_last_item(self
):
236 Selects the last item in this list.
238 selection
= self
._widget
.get_selection()
239 selection
.select_path(len(self
.model
.model
) - 1)
242 def select_next_unread_item(self
):
244 Selects the first unread item after the current selection. If there is
245 no current selection, or if there are no unread items, returns False..
247 By returning False, the caller can either go to the next feed, or go
248 to the next feed with an unread item.
250 selection
= self
._widget
.get_selection()
251 (model
, treeiter
) = selection
.get_selected()
252 # check if we have a selection. if none,
253 # start searching from the first item
255 treeiter
= model
.get_iter_first()
256 if not treeiter
: return False
257 nextiter
= model
.iter_next(treeiter
)
258 if not nextiter
: return False # no more rows to iterate
259 treerow
= model
[nextiter
]
262 item
= treerow
[Column
.ITEM
]
265 selection
.select_path(treerow
.path
)
267 treerow
= treerow
.next
270 class ItemListPresenter(MVP
.BasicPresenter
):
272 ## XXX listen for RefreshFeedDisplaySignal, FeedOrder changes,
274 def _initialize(self
):
275 self
.model
= ItemListModel()
277 def feedlist_selection_changed(self
, feedlist_selection
, column
):
278 (model
, pathlist
) = feedlist_selection
.get_selected_rows()
279 iters
= [model
.get_iter(path
) for path
in pathlist
]
284 nodes
= [model
.get_value(treeiter
, column
) for treeiter
in iters
]
288 if node
.node
.type == "F":
289 items
= ItemManager
.get_feed_items(node
.node
.id)
290 elif node
.node
.type == "C":
291 items
= ItemManager
.get_category_items(node
.node
.id)
293 self
.view
._widget
.set_model(None)
295 self
.model
.populate(items
)
297 self
.view
._widget
.set_model(self
._model
.model
)
299 if not len(self
.model
.model
): return
300 if not self
.view
.select_next_unread_item():
301 self
.view
.select_first_item()
303 def flatten(self
, sequence
):
304 ''' python cookbook recipe 4.6 '''
305 for item
in sequence
:
306 if isinstance(item
, (list, tuple)):
307 for subitem
in self
.flatten(item
):
312 def select_previous_item(self
):
313 return self
.view
.select_previous_item()
315 def select_next_item(self
):
316 return self
.view
.select_next_item()
318 def select_next_unread_item(self
):
319 return self
.view
.select_next_unread_item()
321 def select_last_item(self
):
322 return self
.view
.select_last_item()