make sure we destroy the webview on tab/window destroy
[pywebkitgtk.git] / demos / tabbed_browser.py
blob37feb02760140ba72fe6a468e1aa3b1d09f6a41d
1 #!/usr/bin/env python
2 # Copyright (C) 2007-2008 Jan Alonzo <jmalonzo@unpluggable.com>
4 # This program is free software; you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation; either version 2 of the License, or
7 # (at your option) any later version.
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 # TODO:
20 # * fix tab relabelling
21 # * search page interface
22 # * custom button - w/o margins/padding to make tabs thin
25 from gettext import gettext as _
27 import gobject
28 import gtk
29 import pango
30 import webkit
31 from inspector import Inspector
33 ABOUT_PAGE = """
34 <html><head><title>PyWebKitGtk - About</title></head><body>
35 <h1>Welcome to <code>webbrowser.py</code></h1>
36 <p>
37 <strong>Home:</strong><a
38 href="http://code.google.com/p/pywebkitgtk/">http://code.google.com/p/pywebkitgtk/</a><br/>
39 <strong>Wiki:</strong><a
40 href="http://live.gnome.org/PyWebKitGtk">http://live.gnome.org/PyWebKitGtk</a><br/>
41 </p>
42 </body></html>
43 """
45 class BrowserPage(webkit.WebView):
47 def __init__(self):
48 webkit.WebView.__init__(self)
49 settings = self.get_settings()
50 settings.set_property("enable-developer-extras", True)
52 # scale other content besides from text as well
53 self.set_full_content_zoom(True)
55 # make sure the items will be added in the end
56 # hence the reason for the connect_after
57 self.connect_after("populate-popup", self.populate_popup)
59 def populate_popup(self, view, menu):
60 # zoom buttons
61 zoom_in = gtk.ImageMenuItem(gtk.STOCK_ZOOM_IN)
62 zoom_in.connect('activate', zoom_in_cb, view)
63 menu.append(zoom_in)
65 zoom_out = gtk.ImageMenuItem(gtk.STOCK_ZOOM_OUT)
66 zoom_out.connect('activate', zoom_out_cb, view)
67 menu.append(zoom_out)
69 zoom_hundred = gtk.ImageMenuItem(gtk.STOCK_ZOOM_100)
70 zoom_hundred.connect('activate', zoom_hundred_cb, view)
71 menu.append(zoom_hundred)
73 menu.append(gtk.SeparatorMenuItem())
75 aboutitem = gtk.ImageMenuItem(gtk.STOCK_ABOUT)
76 menu.append(aboutitem)
77 aboutitem.connect('activate', about_pywebkitgtk_cb, view)
79 menu.show_all()
80 return False
82 class TabLabel (gtk.HBox):
83 """A class for Tab labels"""
85 __gsignals__ = {
86 "close": (gobject.SIGNAL_RUN_FIRST,
87 gobject.TYPE_NONE,
88 (gobject.TYPE_OBJECT,))
91 def __init__ (self, title, child):
92 """initialize the tab label"""
93 gtk.HBox.__init__(self, False, 4)
94 self.title = title
95 self.child = child
96 self.label = gtk.Label(title)
97 self.label.props.max_width_chars = 30
98 self.label.set_ellipsize(pango.ELLIPSIZE_MIDDLE)
99 self.label.set_alignment(0.0, 0.5)
101 icon = gtk.image_new_from_stock(gtk.STOCK_ORIENTATION_PORTRAIT, gtk.ICON_SIZE_MENU)
102 close_image = gtk.image_new_from_stock(gtk.STOCK_CLOSE, gtk.ICON_SIZE_MENU)
103 close_button = gtk.Button()
104 close_button.set_relief(gtk.RELIEF_NONE)
105 close_button.connect("clicked", self._close_tab, child)
106 close_button.add(close_image)
107 self.pack_start(icon, False, False, 0)
108 self.pack_start(self.label, True, True, 0)
109 self.pack_start(close_button, False, False, 0)
111 self.set_data("label", self.label)
112 self.set_data("close-button", close_button)
113 self.connect("style-set", tab_label_style_set_cb)
115 def set_label_text (self, text):
116 """sets the text of this label"""
117 self.label.set_label(text)
119 def _close_tab (self, widget, child):
120 self.emit("close", child)
122 def tab_label_style_set_cb (tab_label, style):
123 context = tab_label.get_pango_context()
124 metrics = context.get_metrics(tab_label.style.font_desc, context.get_language())
125 char_width = metrics.get_approximate_digit_width()
126 (width, height) = gtk.icon_size_lookup_for_settings(tab_label.get_settings(), gtk.ICON_SIZE_MENU)
127 tab_label.set_size_request(20 * pango.PIXELS(char_width) + 2 * width, -1)
128 button = tab_label.get_data("close-button")
129 button.set_size_request(width + 4, height + 4)
132 class ContentPane (gtk.Notebook):
134 __gsignals__ = {
135 "focus-view-title-changed": (gobject.SIGNAL_RUN_FIRST,
136 gobject.TYPE_NONE,
137 (gobject.TYPE_OBJECT, gobject.TYPE_STRING,)),
138 "focus-view-load-committed": (gobject.SIGNAL_RUN_FIRST,
139 gobject.TYPE_NONE,
140 (gobject.TYPE_OBJECT, gobject.TYPE_OBJECT,))
143 def __init__ (self):
144 """initialize the content pane"""
145 gtk.Notebook.__init__(self)
146 self.props.scrollable = True
147 self.props.homogeneous = True
148 self.connect("switch-page", self._switch_page)
150 self.show_all()
151 self._hovered_uri = None
153 def load (self, text):
154 """load the given uri in the current web view"""
155 child = self.get_nth_page(self.get_current_page())
156 view = child.get_child()
157 view.open(text)
159 def new_tab (self, url=None):
160 """creates a new page in a new tab"""
161 # create the tab content
162 browser = BrowserPage()
163 browser.connect("hovering-over-link", self._hovering_over_link_cb)
164 browser.connect("populate-popup", self._populate_page_popup_cb)
165 browser.connect("load-committed", self._view_load_committed_cb)
166 browser.connect("load-finished", self._view_load_finished_cb)
167 inspector = Inspector(browser.get_web_inspector())
169 scrolled_window = gtk.ScrolledWindow()
170 scrolled_window.props.hscrollbar_policy = gtk.POLICY_AUTOMATIC
171 scrolled_window.props.vscrollbar_policy = gtk.POLICY_AUTOMATIC
172 scrolled_window.add(browser)
173 scrolled_window.show_all()
175 # load the content
176 self._hovered_uri = None
177 if not url:
178 browser.load_string(ABOUT_PAGE, "text/html", "iso-8859-15", "about")
179 url = "about"
180 else:
181 browser.open(url)
183 # create the tab
184 label = TabLabel(url, scrolled_window)
185 label.connect("close", self._close_tab)
186 label.show_all()
188 new_tab_number = self.append_page(scrolled_window, label)
189 self.set_tab_label_packing(scrolled_window, False, False, gtk.PACK_START)
190 self.set_tab_label(scrolled_window, label)
192 # hide the tab if there's only one
193 self.set_show_tabs(self.get_n_pages() > 1)
195 self.show_all()
196 self.set_current_page(new_tab_number)
198 def _populate_page_popup_cb(self, view, menu):
199 # misc
200 if self._hovered_uri:
201 open_in_new_tab = gtk.MenuItem(_("Open Link in New Tab"))
202 open_in_new_tab.connect("activate", self._open_in_new_tab, view)
203 menu.insert(open_in_new_tab, 0)
204 menu.show_all()
206 def _open_in_new_tab (self, menuitem, view):
207 self.new_tab(self._hovered_uri)
209 def _close_tab (self, label, child):
210 page_num = self.page_num(child)
211 if page_num != -1:
212 view = child.get_child()
213 view.destroy()
214 self.remove_page(page_num)
215 self.set_show_tabs(self.get_n_pages() > 1)
217 def _switch_page (self, notebook, page, page_num):
218 child = self.get_nth_page(page_num)
219 view = child.get_child()
220 frame = view.get_main_frame()
221 self.emit("focus-view-load-committed", view, frame)
223 def _hovering_over_link_cb (self, view, title, uri):
224 self._hovered_uri = uri
226 def _view_load_committed_cb (self, view, frame):
227 self.emit("focus-view-load-committed", view, frame)
229 def _view_load_finished_cb(self, view, frame):
230 child = self.get_nth_page(self.get_current_page())
231 label = self.get_tab_label(child)
232 title = frame.get_title()
233 if not title:
234 title = frame.get_uri()
235 label.set_label_text(title)
238 class WebToolbar(gtk.Toolbar):
240 __gsignals__ = {
241 "load-requested": (gobject.SIGNAL_RUN_FIRST,
242 gobject.TYPE_NONE,
243 (gobject.TYPE_STRING,)),
244 "new-tab-requested": (gobject.SIGNAL_RUN_FIRST,
245 gobject.TYPE_NONE, ())
248 def __init__(self):
249 gtk.Toolbar.__init__(self)
251 # location entry
252 self._entry = gtk.Entry()
253 self._entry.connect('activate', self._entry_activate_cb)
254 entry_item = gtk.ToolItem()
255 entry_item.set_expand(True)
256 entry_item.add(self._entry)
257 self._entry.show()
259 self.insert(entry_item, -1)
260 entry_item.show()
262 # add tab button
263 button = gtk.ToolButton(gtk.STOCK_ADD)
264 button.connect("clicked", self._add_tab_cb)
265 self.insert(button, -1)
266 button.show()
268 def location_set_text (self, text):
269 self._entry.set_text(text)
271 def _entry_activate_cb(self, entry):
272 self.emit("load-requested", entry.props.text)
274 def _add_tab_cb (self, button):
275 self.emit("new-tab-requested")
277 class WebBrowser(gtk.Window):
279 def __init__(self):
280 gtk.Window.__init__(self)
282 toolbar = WebToolbar()
283 content_tabs = ContentPane()
284 content_tabs.connect("focus-view-load-committed", self._load_committed_cb, toolbar)
285 toolbar.connect("load-requested", load_requested_cb, content_tabs)
286 toolbar.connect("new-tab-requested", new_tab_requested_cb, content_tabs)
288 vbox = gtk.VBox(spacing=1)
289 vbox.pack_start(toolbar, expand=False, fill=False)
290 vbox.pack_start(content_tabs)
292 self.add(vbox)
293 self.set_default_size(800, 600)
294 self.connect('destroy', destroy_cb, content_tabs)
296 content_tabs.new_tab("http://www.google.com")
298 self.show_all()
300 def _load_committed_cb (self, tabbed_pane, view, frame, toolbar):
301 title = frame.get_title()
302 if not title:
303 title = frame.get_uri()
304 self.set_title(_("PyWebKitGtk - %s") % title)
305 load_committed_cb(tabbed_pane, view, frame, toolbar)
307 # event handlers
308 def new_tab_requested_cb (toolbar, content_pane):
309 content_pane.new_tab("about:blank")
311 def load_requested_cb (widget, text, content_pane):
312 if not text:
313 return
314 content_pane.load(text)
316 def load_committed_cb (tabbed_pane, view, frame, toolbar):
317 uri = frame.get_uri()
318 if uri:
319 toolbar.location_set_text(uri)
321 def destroy_cb(window, content_pane):
322 """destroy window resources"""
323 num_pages = content_pane.get_n_pages()
324 while num_pages != -1:
325 child = content_pane.get_nth_page(num_pages)
326 if child:
327 view = child.get_child()
328 view.destroy()
329 num_pages = num_pages - 1
330 window.destroy()
331 gtk.main_quit()
333 # context menu item callbacks
334 def about_pywebkitgtk_cb(menu_item, web_view):
335 web_view.open("http://live.gnome.org/PyWebKitGtk")
337 def zoom_in_cb(menu_item, web_view):
338 """Zoom into the page"""
339 web_view.zoom_in()
341 def zoom_out_cb(menu_item, web_view):
342 """Zoom out of the page"""
343 web_view.zoom_out()
345 def zoom_hundred_cb(menu_item, web_view):
346 """Zoom 100%"""
347 if not (web_view.get_zoom_level() == 1.0):
348 web_view.set_zoom_level(1.0)
350 if __name__ == "__main__":
351 webbrowser = WebBrowser()
352 gtk.main()