Improved subscription dialog.
[straw.git] / straw / subscribe.py
blob38be1f1312747a7a3ec01058e4a5b94921248080
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 from straw import helpers
24 import Config
25 import FeedManager
26 import MVP
27 import SummaryParser
28 import error
29 import gettext
30 import gobject
31 import gtk
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, 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 # We only 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 = xml.sax.saxutils.escape(feed.title) + '\n<span size="smaller">' + xml.sax.saxutils.escape(feed.location) + '</span>'
227 self.store.append([None, label, feed])
228 self.store.append([None, "aaa", "ble"])
230 self._progress_bar.props.visible = False
231 self._button2.set_sensitive(True)
232 self._button3.set_sensitive(False)
233 self.progress_destroy()
234 self.state = STATE_FINISH
235 self.set_state(gtk.STOCK_CLOSE, gtk.STOCK_GO_BACK, gtk.STOCK_APPLY)
236 self._notebook.set_current_page(1)
238 self.setup_category_tree()
240 gtk.gdk.threads_leave()
242 def set_state(self, b1, b2, b3):
243 if b1 == None:
244 self._button1.hide()
245 else:
246 self._button1.set_label(b1)
247 self._button1.show()
249 if b2 == None:
250 self._button2.hide()
251 else:
252 self._button2.set_label(b2)
253 self._button2.show()
255 if b3 == None:
256 self._button3.hide()
257 else:
258 self._button3.set_label(b3)
259 self._button3.show()
261 def _on_feed_location_entry_key_press_event(self, widget, event):
262 if event.keyval == gtk.keysyms.Return:
263 self._presenter.subscribe(self._location_entry.get_text())
265 class SubscribePresenter(MVP.BasicPresenter):
267 def _normalize(self, url):
268 u = urlparse.urlsplit(url.strip())
269 # we check if 'scheme' is not empty here because URIs like
270 # "google.com" IS valid but, in this case, 'scheme' is empty because
271 # urlsplit() expects urls are in the format of scheme://netloc/...
272 if not u[0] or (u[0] != "http" and u[0] != "feed"):
273 return None
274 if u[0] == 'feed':
275 u = urlparse.urlsplit(u[2])
276 # .. if that happens then we munge the url by adding a // and assume
277 # that 'http' is the scheme.
278 if u[1] == '':
279 u = urlparse.urlsplit("//" + url, 'http')
280 return u
282 def subscribe(self, location, username=None, password=None):
283 self._username = username
284 self._password = password
285 self._location = location
287 import FeedManager
288 import model
290 feed = model.Feed()
291 feed.location = location
292 feed.title = location
293 FeedManager.save_feed(feed)
295 def set_parent(self, parent):
296 self.view.set_parent(parent)
298 def get_credential(self):
299 return (self._location, self._username, self._password)
301 def show(self, url=None):
302 if url:
303 self.view.set_location_entry(url)
304 self.view.show()
306 # def set_url_dnd(self, url):
307 # self._window.show()
308 # self._feed_location_presenter.view.find_feed(url)
310 def set_location_from_clipboard(self):
311 def _clipboard_cb(cboard, url, data=None):
312 if url:
313 url = self._normalize(url)
314 if url and url[0] == "http":
315 url = urlparse.urlunsplit(url)
316 self.view.set_location_entry(url)
317 clipboard = gtk.clipboard_get(selection="CLIPBOARD")
318 clipboard.request_text(_clipboard_cb, None)
320 def subscribe_show(url=None, parent=None):
321 gladefile = XML(os.path.join(straw.STRAW_DATA_DIR, "subscribe.glade"))
322 _dialog = SubscribePresenter(view=SubscribeView(gladefile))
323 _dialog.set_parent(parent)
324 _dialog.set_location_from_clipboard()
325 _dialog.show()