Updated TODO.
[straw.git] / straw / subscribe.py
blob041dc90f5cb23ce84a08d670f0165677a4acc984
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. """
22 from gtk.glade import XML
23 import Config
24 import FeedManager
25 import MVP
26 import SummaryParser
27 import error
28 import gettext
29 import gobject
30 import gtk
31 import helpers
32 import os, os.path
33 import pygtk
34 import straw
35 import time
36 import urllib
37 import urlparse
38 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.category_pixbuf = gtk.Label().render_icon(gtk.STOCK_DIRECTORY, gtk.ICON_SIZE_MENU)
63 def _populate_tree(self, parent_id, parent = None):
64 if not self.nodes.has_key(parent_id):
65 return
67 for node in self.nodes[parent_id].children:
68 if node.type == "C":
69 treeiter = self.category_store.append(parent, [self.category_pixbuf, helpers.pango_escape(node.name), node])
71 if self.nodes.has_key(node.id):
72 self._populate_tree(node.id, treeiter)
74 def _on_forward(self):
75 location = self._location_entry.get_text()
76 if not location:
77 self.display_error(_("Feed Location must not be empty"))
78 return False
79 #location = "file:///home/ppawel/Desktop/test.rss"
80 username = self._username_entry.get_text()
81 username = username.strip()
82 password = self._password_entry.get_text()
83 password = password.strip()
85 config = Config.get_instance()
86 if config.offline:
87 self._window.hide()
88 response = helpers.report_offline_status(self._window)
89 if response == gtk.RESPONSE_CANCEL:
90 self.show()
91 return False
92 config.offline = not config.offline
94 import FeedDiscovery
95 FeedDiscovery.discover(location, [ { "job-done": [ self._discovery_finished ] } ])
97 #self._presenter.subscribe(location, username, password)
98 return True
100 def progress_pulse(self, obj):
101 self._progress_bar.pulse()
102 return True
104 def progress_destroy(self):
105 if self.timer != 0:
106 gobject.source_remove(self.timer)
107 self.timer = 0
109 def _setup_entries(self):
110 self._location_entry.set_text("http://")
111 self._username_entry.delete_text(0,-1)
112 self._password_entry.delete_text(0,-1)
114 self._location_entry.grab_focus()
116 def show(self):
117 self.state = STATE_INTRO
118 self.set_state(gtk.STOCK_CLOSE, gtk.STOCK_GO_FORWARD, None)
119 self.timer = 0
121 self._setup_entries()
122 self._error_box.set_property('visible',False)
123 self._notebook.set_current_page(0)
124 self._window.show()
126 self.store = gtk.ListStore(bool, str, gobject.TYPE_PYOBJECT)
128 renderer = gtk.CellRendererToggle()
129 renderer.set_property("activatable", True)
130 column = gtk.TreeViewColumn(_('Keep'), renderer, active = 0)
131 renderer.connect('toggled', self.col1_toggled_cb, self.store)
132 self._result_tree.append_column(column)
134 renderer2 = gtk.CellRendererText()
135 column = gtk.TreeViewColumn(_('_Title'), renderer2, markup = 1)
137 self._result_tree.append_column(column)
138 self._result_tree.set_model(self.store)
140 column = gtk.TreeViewColumn()
142 status_renderer = gtk.CellRendererPixbuf()
143 column.pack_start(status_renderer, False)
144 column.set_attributes(status_renderer, pixbuf = 0)
146 title_renderer = gtk.CellRendererText()
147 column.pack_start(title_renderer, False)
148 column.set_attributes(title_renderer, markup = 1)
150 self._category_tree.append_column(column)
152 def col1_toggled_cb(self, cell, path, model):
153 self.store[path][0] = not self.store[path][0]
155 # Enable "Apply" button when at least one discovered feed is selected.
157 self._button3.set_sensitive(bool(sum([feed[0] for feed in self.store])))
159 def setup_category_tree(self):
160 self.category_store = gtk.TreeStore(gtk.gdk.Pixbuf, str, gobject.TYPE_PYOBJECT)
161 self.nodes = FeedManager.get_model()
163 self._category_tree.set_model(None)
164 self._category_tree.set_model(self.category_store)
166 treeiter = self.category_store.append(None, [self.category_pixbuf, "Main category", None])
168 self._populate_tree(1, parent = treeiter)
169 self._category_tree.expand_all()
171 selection = self._category_tree.get_selection()
172 selection.select_path("0")
174 def display_error(self, text):
175 self._error_box.set_property('visible',True)
176 self._error_text.set_text(text)
178 def set_location_entry(self, location):
179 self._location_entry.set_text(location)
181 def set_parent(self, parent):
182 self._window.set_transient_for(parent)
184 def _on_button1_clicked(self, *args):
185 self.progress_destroy()
186 self._window.hide()
188 def _on_button2_clicked(self, *args):
189 if self.state == STATE_INTRO:
190 if self._on_forward():
191 self._progress_bar.props.visible = True
192 self.timer = gobject.timeout_add(100, self.progress_pulse, self)
193 self._button2.set_sensitive(False)
194 elif self.state == STATE_FINISH:
195 self.state = STATE_INTRO
196 self.set_state(gtk.STOCK_CLOSE, gtk.STOCK_GO_FORWARD, None)
197 self._notebook.set_current_page(0)
199 def _on_button3_clicked(self, *args):
200 if self.state == STATE_FINISH:
201 new_feeds = []
203 # Obtain selected category.
205 parent = None
206 iter = self._category_tree.get_selection().get_selected()[1]
208 if iter != None:
209 path = self.category_store.get_path(iter)
210 parent = self.category_store[path][2]
212 # Save selected feeds.
214 for feed in [row[2] for row in self.store if row[0]]:
215 feed.parent = parent
216 new_feeds.append(feed)
218 FeedManager.save_all(new_feeds)
220 self._window.hide()
222 def _discovery_finished(self, handler, feeds):
223 gtk.gdk.threads_enter()
225 for feed in feeds:
226 label = helpers.pango_escape(feed.title) + '\n<span size="smaller">' + xml.sax.saxutils.escape(feed.location) + '</span>'
227 self.store.append([None, label, feed])
229 self._progress_bar.props.visible = False
230 self._button2.set_sensitive(True)
231 self._button3.set_sensitive(False)
232 self.progress_destroy()
233 self.state = STATE_FINISH
234 self.set_state(gtk.STOCK_CLOSE, gtk.STOCK_GO_BACK, gtk.STOCK_APPLY)
235 self._notebook.set_current_page(1)
237 self.setup_category_tree()
239 gtk.gdk.threads_leave()
241 def set_state(self, b1, b2, b3):
242 if b1 == None:
243 self._button1.hide()
244 else:
245 self._button1.set_label(b1)
246 self._button1.show()
248 if b2 == None:
249 self._button2.hide()
250 else:
251 self._button2.set_label(b2)
252 self._button2.show()
254 if b3 == None:
255 self._button3.hide()
256 else:
257 self._button3.set_label(b3)
258 self._button3.show()
260 def _on_feed_location_entry_key_press_event(self, widget, event):
261 if event.keyval == gtk.keysyms.Return:
262 self._presenter.subscribe(self._location_entry.get_text())
264 class SubscribePresenter(MVP.BasicPresenter):
266 def _normalize(self, url):
267 u = urlparse.urlsplit(url.strip())
268 # we check if 'scheme' is not empty here because URIs like
269 # "google.com" IS valid but, in this case, 'scheme' is empty because
270 # urlsplit() expects urls are in the format of scheme://netloc/...
271 if not u[0] or (u[0] != "http" and u[0] != "feed"):
272 return None
273 if u[0] == 'feed':
274 u = urlparse.urlsplit(u[2])
275 # .. if that happens then we munge the url by adding a // and assume
276 # that 'http' is the scheme.
277 if u[1] == '':
278 u = urlparse.urlsplit("//" + url, 'http')
279 return u
281 def subscribe(self, location, username=None, password=None):
282 self._username = username
283 self._password = password
284 self._location = location
286 import FeedManager
287 import model
289 feed = model.Feed()
290 feed.location = location
291 feed.title = location
292 FeedManager.save_feed(feed)
294 def set_parent(self, parent):
295 self.view.set_parent(parent)
297 def get_credential(self):
298 return (self._location, self._username, self._password)
300 def show(self, url=None):
301 if url:
302 self.view.set_location_entry(url)
303 self.view.show()
305 # def set_url_dnd(self, url):
306 # self._window.show()
307 # self._feed_location_presenter.view.find_feed(url)
309 def set_location_from_clipboard(self):
310 def _clipboard_cb(cboard, url, data=None):
311 if url:
312 url = self._normalize(url)
313 if url and url[0] == "http":
314 url = urlparse.urlunsplit(url)
315 self.view.set_location_entry(url)
316 clipboard = gtk.clipboard_get(selection="CLIPBOARD")
317 clipboard.request_text(_clipboard_cb, None)
319 def subscribe_show(url=None, parent=None):
320 gladefile = XML(os.path.join(straw.STRAW_DATA_DIR, "subscribe.glade"))
321 _dialog = SubscribePresenter(view=SubscribeView(gladefile))
322 _dialog.set_parent(parent)
323 _dialog.set_location_from_clipboard()
324 _dialog.show()