예제 #1
0
    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
        )
예제 #2
0
파일: html_widget.py 프로젝트: r0k3/jigna
    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
예제 #3
0
파일: html_widget.py 프로젝트: r0k3/jigna
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)