def __init__( self, parent=None, python_namespace=None, callbacks=[], debug=True, root_paths={} ): super(ProxyQWebView, self).__init__(parent) self.setPage(ProxyQWebPage()) # Connect JS with python. self.expose_python_namespace(python_namespace, callbacks) # Install custom access manager to handle '/jigna' requests. access_manager = ProxyAccessManager(root_paths=root_paths) self.page().setNetworkAccessManager(access_manager) # Disable some actions for action in self.DISABLED_ACTIONS: self.pageAction(action).setVisible(False) # Setup debug flag self.page().settings().setAttribute( QtWebKit.QWebSettings.DeveloperExtrasEnabled, debug ) # Set sizing policy self.setSizePolicy( QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding )
def _create_control(self, parent): """ Create and return the toolkit-specific control for the widget. """ # Create control. _WebView = WebView if sys.platform == 'darwin' else QtWebKit.QWebView control = _WebView(parent) control.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) page = _WebPage(control) page.js_console_msg.connect(self._on_js_console_msg) control.setPage(page) frame = page.mainFrame() # Connect signals. frame.javaScriptWindowObjectCleared.connect(self._js_cleared_signal) frame.titleChanged.connect(self._title_signal) frame.urlChanged.connect(self._url_signal) page.navigation_request.connect(self._navigation_request_signal, QtCore.Qt.DirectConnection) page.loadFinished.connect(self._load_finished_signal) for action in self._disabled_actions: control.pageAction(action).setVisible(False) # Install the access manager. self._network_access = ProxyAccessManager(root_paths=self.root_paths, hosts=self.hosts) self._network_access.inject(control) if hasattr(self, '_zoom'): # _zoom attribute is set by _set_zoom() if zoom property is set # before the control is created self.zoom = self._zoom del self._zoom return control
class HTMLWidget(Widget): """ A widget for displaying web content. See ``IHTMLWidget`` for detailed documentation. """ implements(IHTMLWidget) #### 'IHTMLWidget' interface ############################################## # The URL for the current page. Read only. url = Str # Whether the page is currently loading. loading = Bool(False) # Fired when the page has completely loaded. loaded = Event # The title of the current web page. title = Unicode # Should links be opened in an external browser? Note that any custom URL # handling takes precedence over this option. open_externally = Bool(False) # The zoom level of the page zoom = Property(Float) # Additional JavaScript to be executed after the page load finishes. post_load_js = List(Str) # Whether debugging tools are enabled in the web view. debug = Bool(False) nav_bar = Bool(True) #### Python-JavaScript interoperation ##################################### # The object to expose to JavaScript using the information below. js_object = Instance(HasTraits) # A list of callables to expose to Javascript. callbacks = List(Either(Str, Tuple(Str, Callable))) # A list of traits to expose to Javascript. properties = List(Either(Str, Tuple(Str, Str))) # The name of the Javascript object that will contain the registered # callbacks and properties. python_namespace = Str('python') # A list of schemes to intercept clicks on and functions to handle them. click_schemes = Dict(Str, Callable) # A list of hosts and wsgi apps to handle them. hosts = Dict(Str, Callable) # A list of url root paths and wsgi apps to handle them. root_paths = Dict(Str, Callable) #### Private interface #################################################### _network_access = Any # The exposed `PythonContainer` qobjects exposed to javascript in the # main frame. This list is maintained to delete the object when # it is no longer referenced. _exposed_containers = List # The disabled actions on the page _disabled_actions = List ########################################################################### # 'IWidget' interface. ########################################################################### def _create_control(self, parent): """ Create and return the toolkit-specific control for the widget. """ # Create control. _WebView = WebView if sys.platform == 'darwin' else QtWebKit.QWebView control = _WebView(parent) control.setSizePolicy(QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Expanding) page = _WebPage(control) page.js_console_msg.connect(self._on_js_console_msg) control.setPage(page) frame = page.mainFrame() # Connect signals. frame.javaScriptWindowObjectCleared.connect(self._js_cleared_signal) frame.titleChanged.connect(self._title_signal) frame.urlChanged.connect(self._url_signal) page.navigation_request.connect(self._navigation_request_signal, QtCore.Qt.DirectConnection) page.loadFinished.connect(self._load_finished_signal) for action in self._disabled_actions: control.pageAction(action).setVisible(False) # Install the access manager. self._network_access = ProxyAccessManager(root_paths=self.root_paths, hosts=self.hosts) self._network_access.inject(control) if hasattr(self, '_zoom'): # _zoom attribute is set by _set_zoom() if zoom property is set # before the control is created self.zoom = self._zoom del self._zoom return control def destroy(self): """ Destroy the control, if it exists. """ # Stop loading and call the page's deleteLater(). Setting the # page to None cause crazy crashes and perhaps memory corruption. self.control.stop() self.control.close() self._network_access.deleteLater() self.control.page().deleteLater() super(HTMLWidget, self).destroy() ########################################################################### # 'IHTMLWidget' interface. ########################################################################### def create(self, parent=None): """ Create the HTML widget's underlying control. """ self.parent = parent self.control = self._create_control(parent) def execute_js(self, js): """ Execute JavaScript synchronously. """ frame = self.control.page().mainFrame() return frame.evaluateJavaScript(js) def load_html(self, html, base_url=None): """ Loads raw HTML into the widget. """ if base_url: url = base_url if not url.endswith('/'): url += '/' self.control.setHtml(html, QtCore.QUrl.fromLocalFile(url)) else: self.control.setHtml(html) self.url = '' def load_url(self, url): """ Loads the given URL. """ self.loading = True self.control.load(QtCore.QUrl(url)) self.url = url #### Navigation ########################################################### def back(self): """ Navigate backward in history. """ self.control.back() def forward(self): """ Navigate forward in history. """ self.control.forward() def reload(self): """ Reload the current web page. """ self.control.reload() def stop(self): """ Stop loading the curent web page. """ self.control.stop() #### Generic GUI methods ################################################## def undo(self): """ Performs an undo action in the underlying widget. """ self.control.page().triggerAction(QWebPage.Undo) def redo(self): """ Performs a redo action in the underlying widget. """ self.control.page().triggerAction(QWebPage.Redo) def cut(self): """ Performs a cut action in the underlying widget. """ self.control.page().triggerAction(QWebPage.Cut) def copy(self): """ Performs a copy action in the underlying widget. """ self.control.page().triggerAction(QWebPage.Copy) def paste(self): """ Performs a paste action in the underlying widget. """ self.control.page().triggerAction(QWebPage.Paste) def select_all(self): """ Performs a select all action in the underlying widget. """ self.control.page().triggerAction(QWebPage.SelectAll) ########################################################################### # Private interface. ########################################################################### #### Trait change handlers ################################################ @on_trait_change('control, debug') def _update_debug(self): if self.control: page = self.control.page() page.settings().setAttribute( QtWebKit.QWebSettings.DeveloperExtrasEnabled, self.debug) @on_trait_change('hosts') def _update_network_access(self): if self._network_access: self._network_access.hosts = self.hosts #### Trait property getters/setters ####################################### def _get_zoom(self): if self.control is not None: return self.control.zoomFactor() else: return 1.0 def _set_zoom(self, zoom): if self.control is not None: self.control.setZoomFactor(zoom) else: self._zoom = zoom #### Trait initializers ################################################### def __disabled_actions_default(self): return [QWebPage.OpenLinkInNewWindow, QWebPage.DownloadLinkToDisk, QWebPage.OpenImageInNewWindow, QWebPage.OpenFrameInNewWindow, QWebPage.DownloadImageToDisk] #### Signal handlers ###################################################### def _js_cleared_signal(self): if self.control is not None: frame = self.control.page().mainFrame() # Since the js `window` object is cleared by the frame which still # exists, we need to explicitly delete the exposed objects. for exposed_obj in self._exposed_containers: exposed_obj.deleteLater() self._exposed_containers = exposed_containers = [] if self.callbacks or self.properties: exposed_containers.append(create_js_object_wrapper( self.js_object, self.callbacks, self.properties, parent=frame, )) frame.addToJavaScriptWindowObject( self.python_namespace, exposed_containers[-1], ) def _navigation_request_signal(self, nav_req): if self.url == '': # If no url has been loaded yet, let the url load. return qurl = nav_req.request.url() str_url = qurl.toString() if nav_req.frame is None: # request to open in new window. # TODO: implement createWindow() to open in a tab. # open externally until TODO is completed. webbrowser.open_new(str_url) nav_req.reject() # Now fall back to other handling methods. scheme = str(qurl.scheme()) if scheme in self.click_schemes: self.click_schemes[scheme](str_url) nav_req.reject() elif qurl.host() in self.hosts: # Load hosts pages locally even if open_externally is specified. return elif self.open_externally: webbrowser.open_new(str_url) nav_req.reject() def _load_finished_signal(self, ok): # Make sure that the widget has not been destroyed during loading. if self.control is not None: if ok: # Evaluate post-load JS. for script in self.post_load_js: self.execute_js(script) self.loading = False self.loaded = ok def _title_signal(self, title): self.title = title def _url_signal(self, url): self.url = url.toString() def _on_js_console_msg(self, msg, lineno, sourceid): """ Log the javascript console messages. """ logger.debug('JS: <%s>:%s(%s) %s', self.url, lineno, sourceid, msg) def default_context_menu(self): """ Return the default context menu (pyface). """ if self.control is None: return None page = self.control.page() qmenu = page.createStandardContextMenu() return Menu_from_QMenu(qmenu)