straw/Makefile.am: added Fetcher and ItemManager, removed URLFetch
[straw.git] / straw / documentviews.py
blob250b5e1e85087934532264344d31235cd6a54f23
1 """
2 DocumentView is a generic interface to html document browsing.
3 Methods:
4 __init__(self, userdir)
5 widget(self)
6 load_url(self, url)
7 stop_load(self)
8 render_data(self, data, base_uri, mime_type)
10 Signals:
11 Required methods:
12 location_changed(location)
13 loading_started
14 loading_finished
15 status_changed(text)
16 open_uri(uri)
17 """
19 import sys
21 import gtk
22 import gobject
24 class DocumentView(gobject.GObject):
25 __gsignals__ = {
26 "location_changed" : (
27 gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, [
28 gobject.TYPE_STRING]), # The new location
29 "loading_started" : (
30 gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, []),
31 "loading_finished" : (
32 gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, []),
33 "status_changed" : (
34 gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, [
35 gobject.TYPE_STRING]), # The status
36 "open_uri": (
37 gobject.SIGNAL_RUN_LAST, gobject.TYPE_BOOLEAN, [
38 gobject.TYPE_STRING]) # URI
40 def __init__(self, emitOnIdle=False):
41 gobject.GObject.__init__(self)
42 self.emitOnIdle = emitOnIdle
44 def emit(self, *args):
45 """
46 Override the gobject signal emission so that signals
47 can be emitted from the main loop on an idle handler
48 """
49 if self.emitOnIdle == True:
50 gobject.idle_add(gobject.GObject.emit,self,*args)
51 else:
52 return gobject.GObject.emit(self,*args)
54 class WebKitDocumentView(DocumentView):
55 """wraps the PyWebKitGtk HTML view in the DocumentView interface"""
56 def __init__(self, userdir):
57 DocumentView.__init__(self)
58 global webkitgtk
59 import webkitgtk # you'll need PyWebKitGtk
61 self._view = webkitgtk.WebView()
62 self.location = ""
64 sprefix = '_signal_'
65 for method in dir(self.__class__):
66 if method.startswith(sprefix):
67 signal = method[len(sprefix):]
68 self._view.connect(signal, getattr(self, method))
70 self._scrolled_window = gtk.ScrolledWindow()
71 self._scrolled_window.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
72 self._scrolled_window.add(self._view)
74 self._scrolled_window.show_all()
76 def widget(self):
77 """returns the gtk widget of this view"""
78 return self._scrolled_window
80 def load_url(self, url):
81 self._view.open(url)
82 self.location = url
83 self.emit("location_changed",self.location)
85 def stop_load(self):
86 self._view.stop_loading()
88 def render_data(self, data, base_uri, mime_type):
89 ct = mime_type.split("; ")
90 if len(ct) > 1:
91 charset = ct[1]
92 else:
93 charset = "utf-8" # default
95 self.emit("location_changed",base_uri)
96 self._view.load_string(data, ct[0], charset, base_uri)
98 def _signal_load_started(self, object, frame):
99 self.emit("loading_started")
100 def _signal_load_progress_changed(self, object, progress):
101 pass
102 def _signal_load_finished(self, object, frame):
103 self.emit("loading_finished")
104 def _signal_title_changed(self, object, title, uri):
105 self.location = uri
106 self.emit("location_changed",self.location)
107 def _signal_status_bar_text_changed(self, object, text):
108 pass
111 class GtkHtmlDocumentView(DocumentView):
112 """wraps the GTK HTML view in the DocumentView interface"""
113 def __init__(self, userdir):
114 DocumentView.__init__(self)
115 global gtkhtml2
116 import gtkhtml2 # you'll need Debian package python-gnome2-extras
118 self._view = gtkhtml2.View()
119 self._document = gtkhtml2.Document()
120 self._widget = gtk.ScrolledWindow()
121 self._widget.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
122 self._widget.add(self._view)
123 self.location = ""
125 self._view.connect("on_url", self._signal_on_url)
126 self._document.connect("link_clicked", self._signal_link_clicked)
127 self._document.connect("request-url", self._signal_request_url)
129 self._view.set_document(self._document)
131 def widget(self):
132 """returns the gtk widget of this view"""
133 return self._widget
135 def load_url(self, url):
136 res = self._open_url(self._complete_url(url))
137 ct = res.info()['content-type']
138 self.render_data(res.read(), res.geturl(), ct)
140 def render_data(self, data, base_uri, mime_type):
141 self.location = base_uri
142 self.emit("location_changed", self.location)
144 ct = mime_type.split("; ")
145 if len(ct) > 1:
146 charset = ct[1]
147 else:
148 charset = "utf-8" # default
150 self._document.clear()
151 print "clear"
152 self._document.open_stream(ct[0])
153 print "open"
154 self._document.write_stream(data)
155 print "write"
156 self._document.close_stream()
157 print "close"
158 return
160 def _signal_on_url(self, object, url):
161 if url == None: url = ""
162 else: url = self._complete_url(url)
163 self.emit("status_changed", url)
165 def _signal_link_clicked(self, object, link):
166 self.emit("open_uri", self._complete_url(link))
168 def _signal_request_url(self, object, url, stream):
169 stream.write(self._fetch_url(self._complete_url(url)))
171 def _open_url(self, url, headers=[]):
172 import urllib2
173 opener = urllib2.build_opener()
174 opener.addheaders = [('User-agent', 'Wikitin')]+headers
175 return opener.open(url)
177 def _fetch_url(self, url, headers=[]):
178 return self._open_url(url, headers).read()
180 def _complete_url(self, url):
181 import string, urlparse, urllib
182 url = urllib.quote(url, safe=string.punctuation)
183 if urlparse.urlparse(url)[0] == '':
184 return urlparse.urljoin(self.location, url)
185 else:
186 return url
189 class MozEmbedDocumentView(DocumentView):
190 """wraps the GTK embeddable Mozilla in the DocumentView interface"""
191 def __init__(self, profiledir):
192 DocumentView.__init__(self)
193 global gtkmozembed
194 import gtkmozembed # you'll need Debian package python-gnome2-extras
196 print "Setting Mozilla profile dir to %s name %s" % (profiledir, 'default')
197 gtkmozembed.set_profile_path(profiledir, 'default')
199 self.moz = gtkmozembed.MozEmbed()
200 self.url_load_request = False # flag to break load_url recursion
201 self.location = ""
204 sprefix = '_signal_'
205 for method in dir(self.__class__):
206 if method.startswith(sprefix):
207 self.moz.connect(method[len(sprefix):], getattr(self, method))
209 def widget(self):
210 """returns the gtk widget of this view"""
211 return self.moz
213 def load_url(self, str):
214 self.url_load_request = True # don't handle open-uri signal
215 self.moz.load_url(str) # emits open-uri signal
216 self.url_load_request = False # handle open-uri again
218 def stop_load(self):
219 self.moz.stop_load()
220 def go_back(self):
221 self.url_load_request = True # don't handle open-uri signal
222 self.moz.go_back()
223 self.url_load_request = False # handle open-uri again
224 def go_forward(self):
225 self.url_load_request = True # don't handle open-uri signal
226 self.moz.go_forward()
227 self.url_load_request = False # handle open-uri again
228 def reload(self):
229 self.moz.reload(gtkmozembed.FLAG_RELOADNORMAL)
231 def render_data(self, data, base_uri, mime_type):
232 self.url_load_request = True # don't handle open-uri signal
234 ct = mime_type.split("; ")
235 if len(ct) > 1:
236 charset = ct[1]
237 else:
238 charset = "utf-8" # default
240 self.location_changed(base_uri)
242 # gtkmozembed hangs if it's fed more than 2^16 at a time
243 # XXX bytes, chars?
244 self.moz.open_stream(base_uri, ct[0])
245 while True:
246 block, data = data[:2**16], data[2**16:]
247 self.moz.append_data(block, long(len(block)))
248 if len(data) == 0: break
249 self.moz.close_stream()
251 self.url_load_request = False # handle open-uri again
253 def _signal_link_message(self, object):
254 self.emit("status_changed", self.moz.get_link_message())
256 def _signal_open_uri(self, object, uri):
257 if self.url_load_request: return False # proceed as requested
258 # otherwise, this is from the page
259 # print uri
260 import gobject
261 if uri.__class__ == gobject.GPointer:
262 print "The gpointer bug, guessing...",
263 uri = self.moz.get_link_message()
264 if uri=="": print "<empty>",
265 print uri
266 if uri=="":
267 return False # XXX can't handle, let MozEmbed do it
268 return self.emit("open_uri", uri)
269 else:
270 print "No gpointer bug here !-)"
271 return self.emit("open_uri", uri)
273 def _signal_location(self, object):
274 self.location_changed(self.moz.get_location())
276 def location_changed(self, location):
277 print "moz: location: "+location
278 self.location = location
279 self.emit("location_changed",self.location)
281 def _signal_progress(self, object, cur, maxim):
282 if maxim < 1:
283 print "Progress: %d" % cur
284 else:
285 print 'Progress: %d%%' % (cur/maxim)
286 def _signal_net_state(self, object, flags, status):
287 print 'net-state flags=%x status=%x' % (flags,status)
288 def _signal_new_window(self, object, *args):
289 print 'new-window', args
290 def _signal_net_start(self, object):
291 self.emit("loading_started")
292 def _signal_net_stop(self, object):
293 self.emit("loading_finished")
296 def get_subdir(dir, subdir=''):
297 import os
298 userdir = os.path.join(dir, subdir)
299 if not os.access(userdir, os.F_OK):
300 os.makedirs(userdir)
301 return userdir
303 documentviews = ['webkit', 'gtkmozembed', 'gtkhtml']
304 create_documentviews = {
305 'webkit': lambda userdir:WebKitDocumentView(userdir),
306 'gtkmozembed': lambda userdir:MozEmbedDocumentView(get_subdir(userdir, 'mozilla')),
307 'gtkhtml': lambda userdir:GtkHtmlDocumentView(userdir),
310 def default_documentview(userdir):
311 for documentview in documentviews:
312 try:
313 return create_documentviews[documentview](userdir)
314 except ImportError:
315 pass
316 else:
317 print "No HTML view available!"
318 sys.exit(10)