Implemented "mark all as read".
[straw.git] / straw / subscribe.py
blob6ad6db0bd0ff2db32d5a7920899d5ae54e765c51
1 """ subscribe.py
3 Module for handling feed subscription process.
4 """
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 modify it under the
9 terms of the GNU General Public License as published by the Free Software
10 Foundation; either version 2 of the License, or (at your option) any later
11 version.
13 This program is distributed in the hope that it will be useful, but WITHOUT
14 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
15 FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
17 You should have received a copy of the GNU General Public License along with
18 this program; if not, write to the Free Software Foundation, Inc., 59 Temple
19 Place - Suite 330, Boston, MA 02111-1307, USA. """
21 from Constants import *
22 from gtk.glade import XML
23 import Config
24 import FeedDiscovery
25 import FeedManager
26 import MVP
27 import SummaryParser
28 import error
29 import gettext
30 import gobject
31 import gtk
32 import helpers
33 import os, os.path
34 import pygtk
35 import straw.defs
36 import time
37 import urllib
38 import urlparse
39 import xml
40 pygtk.require('2.0')
42 STATE_INTRO = 1
43 STATE_FINISH = 2
45 class SubscribeView(MVP.GladeView):
46 def _initialize(self):
47 self._window = self._widget.get_widget('subscribe_dialog')
48 self._button1 = self._widget.get_widget('button1')
49 self._button2 = self._widget.get_widget('button2')
50 self._button3 = self._widget.get_widget('button3')
51 self._progress_bar = self._widget.get_widget('progress_bar')
52 self._notebook = self._widget.get_widget('notebook')
53 self._location_entry = self._widget.get_widget('feed_location_entry')
54 self._username_entry = self._widget.get_widget('username_entry')
55 self._password_entry = self._widget.get_widget('password_entry')
56 self._category_tree = self._widget.get_widget('category_tree')
57 self._error_text = self._widget.get_widget('error_text')
58 self._error_box = self._widget.get_widget('error_box')
59 self._result_tree = self._widget.get_widget('result_tree')
61 self._result_size_label = self._widget.get_widget('result_size_label')
62 self._result_size_text = self._result_size_label.get_text()
64 self.category_pixbuf = gtk.Label().render_icon(gtk.STOCK_DIRECTORY, gtk.ICON_SIZE_MENU)
66 self.feeds = []
68 def _populate_tree(self, parent_id, parent = None):
69 if not self.nodes.has_key(parent_id):
70 return
72 for node in self.nodes[parent_id].children:
73 if node.type == "C":
74 treeiter = self.category_store.append(parent, [self.category_pixbuf, helpers.pango_escape(node.name), node])
76 if self.nodes.has_key(node.id):
77 self._populate_tree(node.id, treeiter)
79 def _on_forward(self):
80 location = self._location_entry.get_text()
81 if not location:
82 self.display_error(_("Feed Location must not be empty"))
83 return False
84 #location = "file:///home/ppawel/Desktop/test.rss"
85 username = self._username_entry.get_text()
86 username = username.strip()
87 password = self._password_entry.get_text()
88 password = password.strip()
90 if Config.get(OPTION_OFFLINE):
91 self._window.hide()
92 response = helpers.report_offline_status(self._window)
94 if response == gtk.RESPONSE_CANCEL:
95 self.show()
96 return False
98 Config.set(OPTION_OFFLINE, True)
100 credentials = None
102 if username or password:
103 credentials = username, password, ""
105 self.feeds = []
107 observers = [ { "job-done": [ self._discovery_finished ],
108 "feed-discovered": [ self._on_feed_discovered ] } ]
110 FeedDiscovery.discover(location, credentials, observers)
112 return True
114 def progress_pulse(self, obj):
115 self._progress_bar.pulse()
116 return True
118 def progress_destroy(self):
119 if self.timer != 0:
120 gobject.source_remove(self.timer)
121 self.timer = 0
123 def _setup_entries(self):
124 self._location_entry.set_text("http://")
125 self._username_entry.delete_text(0,-1)
126 self._password_entry.delete_text(0,-1)
128 self._location_entry.grab_focus()
130 def show(self):
131 self.state = STATE_INTRO
132 self.set_state(gtk.STOCK_CLOSE, gtk.STOCK_GO_FORWARD, None)
133 self.timer = 0
135 self._setup_entries()
136 self._error_box.set_property('visible',False)
137 self._notebook.set_current_page(0)
138 self._window.show()
140 self.store = gtk.ListStore(bool, str, gobject.TYPE_PYOBJECT)
142 renderer = gtk.CellRendererToggle()
143 renderer.set_property("activatable", True)
144 column = gtk.TreeViewColumn(_('Keep'), renderer, active = 0)
145 renderer.connect('toggled', self.col1_toggled_cb, self.store)
146 self._result_tree.append_column(column)
148 renderer2 = gtk.CellRendererText()
149 column = gtk.TreeViewColumn(_('_Title'), renderer2, markup = 1)
151 self._result_tree.append_column(column)
152 self._result_tree.set_model(self.store)
154 column = gtk.TreeViewColumn()
156 status_renderer = gtk.CellRendererPixbuf()
157 column.pack_start(status_renderer, False)
158 column.set_attributes(status_renderer, pixbuf = 0)
160 title_renderer = gtk.CellRendererText()
161 column.pack_start(title_renderer, False)
162 column.set_attributes(title_renderer, markup = 1)
164 self._category_tree.append_column(column)
166 def col1_toggled_cb(self, cell, path, model):
167 self.store[path][0] = not self.store[path][0]
169 # Enable "Apply" button when at least one discovered feed is selected.
171 self._button3.set_sensitive(bool(sum([feed[0] for feed in self.store])))
173 def setup_category_tree(self):
174 self.category_store = gtk.TreeStore(gtk.gdk.Pixbuf, str, gobject.TYPE_PYOBJECT)
175 self.nodes = FeedManager.get_model()
177 self._category_tree.set_model(None)
178 self._category_tree.set_model(self.category_store)
180 treeiter = self.category_store.append(None, [self.category_pixbuf, "Main category", None])
182 self._populate_tree(1, parent = treeiter)
183 self._category_tree.expand_all()
185 selection = self._category_tree.get_selection()
186 selection.select_path("0")
188 def display_error(self, text):
189 self._error_box.set_property('visible',True)
190 self._error_text.set_text(text)
192 def set_location_entry(self, location):
193 self._location_entry.set_text(location)
195 def set_parent(self, parent):
196 self._window.set_transient_for(parent)
198 def _on_button1_clicked(self, *args):
199 self.progress_destroy()
200 self._window.hide()
202 def _on_button2_clicked(self, *args):
203 if self.state == STATE_INTRO:
204 if self._on_forward():
205 self._progress_bar.props.visible = True
206 self.timer = gobject.timeout_add(100, self.progress_pulse, self)
207 self._button2.set_sensitive(False)
208 elif self.state == STATE_FINISH:
209 self.state = STATE_INTRO
210 self.set_state(gtk.STOCK_CLOSE, gtk.STOCK_GO_FORWARD, None)
211 self._notebook.set_current_page(0)
213 def _on_button3_clicked(self, *args):
214 if self.state == STATE_FINISH:
215 new_feeds = []
217 # Obtain selected category.
219 parent = None
220 iter = self._category_tree.get_selection().get_selected()[1]
222 if iter != None:
223 path = self.category_store.get_path(iter)
224 parent = self.category_store[path][2]
226 # Save selected feeds.
228 for feed in [row[2] for row in self.store if row[0]]:
229 feed.parent = parent
230 new_feeds.append(feed)
232 FeedManager.save_all(new_feeds)
234 self._window.hide()
236 def _on_feed_discovered(self, handler, feed):
237 self.feeds.append(feed)
239 def _discovery_finished(self, handler, task_result):
240 gtk.gdk.threads_enter()
241 feeds = self.feeds
243 self._result_size_label.set_text(self._result_size_text % len(feeds))
245 for feed in feeds:
246 label = helpers.pango_escape(feed.title) + '\n<span size="smaller">' + \
247 helpers.pango_escape(feed.location) + '</span>'
249 self.store.append([None, label, feed])
251 self._progress_bar.props.visible = False
252 self._button2.set_sensitive(True)
253 self._button3.set_sensitive(False)
254 self.progress_destroy()
255 self.state = STATE_FINISH
256 self.set_state(gtk.STOCK_CLOSE, gtk.STOCK_GO_BACK, gtk.STOCK_APPLY)
257 self._notebook.set_current_page(1)
259 self.setup_category_tree()
261 gtk.gdk.threads_leave()
263 def set_state(self, b1, b2, b3):
264 if b1 == None:
265 self._button1.hide()
266 else:
267 self._button1.set_label(b1)
268 self._button1.show()
270 if b2 == None:
271 self._button2.hide()
272 else:
273 self._button2.set_label(b2)
274 self._button2.show()
276 if b3 == None:
277 self._button3.hide()
278 else:
279 self._button3.set_label(b3)
280 self._button3.show()
282 def _on_feed_location_entry_key_press_event(self, widget, event):
283 if event.keyval == gtk.keysyms.Return:
284 self._presenter.subscribe(self._location_entry.get_text())
286 class SubscribePresenter(MVP.BasicPresenter):
288 def _normalize(self, url):
289 u = urlparse.urlsplit(url.strip())
290 # we check if 'scheme' is not empty here because URIs like
291 # "google.com" IS valid but, in this case, 'scheme' is empty because
292 # urlsplit() expects urls are in the format of scheme://netloc/...
293 if not u[0] or (u[0] != "http" and u[0] != "feed"):
294 return None
295 if u[0] == 'feed':
296 u = urlparse.urlsplit(u[2])
297 # .. if that happens then we munge the url by adding a // and assume
298 # that 'http' is the scheme.
299 if u[1] == '':
300 u = urlparse.urlsplit("//" + url, 'http')
301 return u
303 def subscribe(self, location, username=None, password=None):
304 self._username = username
305 self._password = password
306 self._location = location
308 import FeedManager
309 import model
311 feed = model.Feed()
312 feed.location = location
313 feed.title = location
314 FeedManager.save_feed(feed)
316 def set_parent(self, parent):
317 self.view.set_parent(parent)
319 def get_credential(self):
320 return (self._location, self._username, self._password)
322 def show(self, url=None):
323 if url:
324 self.view.set_location_entry(url)
325 self.view.show()
327 # def set_url_dnd(self, url):
328 # self._window.show()
329 # self._feed_location_presenter.view.find_feed(url)
331 def set_location_from_clipboard(self):
332 def _clipboard_cb(cboard, url, data=None):
333 if url:
334 url = self._normalize(url)
335 if url and url[0] == "http":
336 url = urlparse.urlunsplit(url)
337 self.view.set_location_entry(url)
338 clipboard = gtk.clipboard_get(selection="CLIPBOARD")
339 clipboard.request_text(_clipboard_cb, None)
341 def subscribe_show(url=None, parent=None):
342 gladefile = XML(os.path.join(straw.defs.STRAW_DATA_DIR, "subscribe.glade"))
343 _dialog = SubscribePresenter(view=SubscribeView(gladefile))
344 _dialog.set_parent(parent)
345 _dialog.set_location_from_clipboard()
346 _dialog.show()