示例#1
0
class TermView(WebView):
    """XTerm Wrapper."""
    def __init__(self,
                 parent,
                 CONF,
                 term_url='http://127.0.0.1:8070',
                 handler=None):
        """Webview main constructor."""
        WebView.__init__(self, parent)
        self.parent = parent
        self.CONF = CONF
        self.copy_action = create_action(self,
                                         _("Copy text"),
                                         icon=ima.icon('editcopy'),
                                         triggered=self.copy,
                                         shortcut=self.CONF.get_shortcut(
                                             CONF_SECTION, 'copy'))
        self.paste_action = create_action(self,
                                          _("Paste text"),
                                          icon=ima.icon('editpaste'),
                                          triggered=self.paste,
                                          shortcut=self.CONF.get_shortcut(
                                              CONF_SECTION, 'paste'))
        self.clear_action = create_action(self,
                                          _("Clear Terminal"),
                                          triggered=self.clear,
                                          shortcut=self.CONF.get_shortcut(
                                              CONF_SECTION, 'clear'))
        if WEBENGINE:
            self.channel = QWebChannel(self.page())
            self.page().setWebChannel(self.channel)
            self.channel.registerObject('handler', handler)
        self.term_url = QUrl(term_url)
        self.load(self.term_url)

        if WEBENGINE:
            self.document = self.page()
            try:
                self.document.profile().clearHttpCache()
            except AttributeError:
                pass
        else:
            self.document = self.page().mainFrame()

        self.initial_y_pos = 0
        self.setFocusPolicy(Qt.ClickFocus)

    def copy(self):
        """Copy unicode text from terminal."""
        self.triggerPageAction(QWebEnginePage.Copy)

    def paste(self):
        """Paste unicode text into terminal."""
        self.triggerPageAction(QWebEnginePage.Paste)

    def clear(self):
        """Clear the terminal."""
        self.eval_javascript('clearTerm()')

    def contextMenuEvent(self, event):
        """Override Qt method."""
        menu = QMenu(self)
        actions = [
            self.pageAction(QWebEnginePage.SelectAll), self.copy_action,
            self.paste_action
        ]
        if DEV and not WEBENGINE:
            settings = self.page().settings()
            settings.setAttribute(QWebEngineSettings.DeveloperExtrasEnabled,
                                  True)
            actions += [None, self.pageAction(QWebEnginePage.InspectElement)]
        add_actions(menu, actions)
        menu.popup(event.globalPos())
        event.accept()

    def eval_javascript(self, script):
        """
        Evaluate Javascript instructions inside DOM with the expected prefix.
        """
        script = PREFIX + script
        if WEBENGINE:
            self.document.runJavaScript("{}".format(script),
                                        self.return_js_value)
        else:
            self.document.evaluateJavaScript("{}".format(script))

    def return_js_value(self, value):
        """Return the value of the function evaluated in Javascript."""
        return value

    def wheelEvent(self, event):
        """Catch and process wheel scrolling events via Javascript."""
        delta = event.angleDelta().y()
        self.eval_javascript('scrollTerm({0})'.format(delta))

    def event(self, event):
        """Grab all keyboard input."""
        if event.type() == QEvent.ShortcutOverride:
            key = event.key()
            modifiers = event.modifiers()

            if modifiers & Qt.ShiftModifier:
                key += Qt.SHIFT
            if modifiers & Qt.ControlModifier:
                key += Qt.CTRL
            if modifiers & Qt.AltModifier:
                key += Qt.ALT
            if modifiers & Qt.MetaModifier:
                key += Qt.META

            sequence = QKeySequence(key).toString(QKeySequence.PortableText)
            if sequence == self.CONF.get_shortcut(CONF_SECTION, 'copy'):
                self.copy()
            elif sequence == self.CONF.get_shortcut(CONF_SECTION, 'paste'):
                self.paste()
            elif sequence == self.CONF.get_shortcut(CONF_SECTION, 'clear'):
                self.clear()
            else:
                event.ignore()
                return False
            event.accept()
            return True

        return WebView.event(self, event)
示例#2
0
class TermView(QWebEngineView):
    """XTerm Wrapper."""

    def __init__(self, parent, CONF, term_url='http://127.0.0.1:8070',
                 handler=None):
        """Webview main constructor."""
        super().__init__(parent)
        web_page = QWebEnginePage(self)
        self.setPage(web_page)
        self.source_text = ''
        self.parent = parent
        self.CONF = CONF
        self.shortcuts = self.create_shortcuts()
        self.channel = QWebChannel(self.page())
        self.page().setWebChannel(self.channel)
        self.channel.registerObject('handler', handler)

        self.term_url = QUrl(term_url)
        self.load(self.term_url)

        self.document = self.page()
        try:
            self.document.profile().clearHttpCache()
        except AttributeError:
            pass

        self.initial_y_pos = 0
        self.setFocusPolicy(Qt.ClickFocus)

    def copy(self):
        """Copy unicode text from terminal."""
        self.triggerPageAction(QWebEnginePage.Copy)

    def paste(self):
        """Paste unicode text into terminal."""
        clipboard = QApplication.clipboard()
        text = str(clipboard.text())
        if len(text.splitlines()) > 1:
            eol_chars = os.linesep
            text = eol_chars.join((text + eol_chars).splitlines())
        self.eval_javascript('pasteText({})'.format(repr(text)))

    def clear(self):
        """Clear the terminal."""
        self.eval_javascript('clearTerm()')

    def increase_font(self):
        """Increase terminal font."""
        return self.eval_javascript('increaseFontSize()')

    def decrease_font(self):
        """Decrease terminal font."""
        return self.eval_javascript('decreaseFontSize()')

    def create_shortcuts(self):
        """Create the terminal shortcuts."""
        copy_shortcut = self.CONF.config_shortcut(
            lambda: self.copy(),
            context='terminal',
            name='copy',
            parent=self)
        paste_shortcut = self.CONF.config_shortcut(
            lambda: self.paste(),
            context='terminal',
            name='paste',
            parent=self)
        clear_shortcut = self.CONF.config_shortcut(
            lambda: self.clear(),
            context='terminal',
            name='clear',
            parent=self)
        zoomin_shortcut = self.CONF.config_shortcut(
            lambda: self.increase_font(),
            context='terminal',
            name='zoom in',
            parent=self)
        zoomout_shortcut = self.CONF.config_shortcut(
            lambda: self.decrease_font(),
            context='terminal',
            name='zoom out',
            parent=self)
        return [copy_shortcut, paste_shortcut, clear_shortcut, zoomin_shortcut,
                zoomout_shortcut]

    def get_shortcut_data(self):
        """
        Return shortcut data, a list of tuples (shortcut, text, default).

        shortcut (QShortcut or QAction instance)
        text (string): action/shortcut description
        default (string): default key sequence
        """
        return [sc.data for sc in self.shortcuts]

    def contextMenuEvent(self, event):
        """Override Qt method."""
        copy_action = create_action(
            self, _("Copy text"), icon=ima.icon('editcopy'),
            triggered=self.copy,
            shortcut=self.CONF.get_shortcut(CONF_SECTION, 'copy'))
        paste_action = create_action(
            self, _("Paste text"),
            icon=ima.icon('editpaste'),
            triggered=self.paste,
            shortcut=self.CONF.get_shortcut(CONF_SECTION, 'paste'))
        clear_action = create_action(
            self, _("Clear Terminal"),
            triggered=self.clear,
            shortcut=self.CONF.get_shortcut(CONF_SECTION, 'clear'))
        zoom_in = create_action(
            self, _("Zoom in"), triggered=self.increase_font,
            shortcut=self.CONF.get_shortcut(CONF_SECTION, 'zoom in'))
        zoom_out = create_action(
            self, _("Zoom out"), triggered=self.decrease_font,
            shortcut=self.CONF.get_shortcut(CONF_SECTION, 'zoom out'))
        menu = QMenu(self)
        actions = [self.pageAction(QWebEnginePage.SelectAll),
                   copy_action, paste_action, clear_action, None, zoom_in,
                   zoom_out]
        add_actions(menu, actions)
        menu.popup(event.globalPos())
        event.accept()

    def eval_javascript(self, script):
        """
        Evaluate Javascript instructions inside DOM with the expected prefix.
        """
        script = PREFIX + script
        self.document.runJavaScript("{}".format(script), self.return_js_value)

    def return_js_value(self, value):
        """Return the value of the function evaluated in Javascript."""
        return value

    def wheelEvent(self, event):
        """Catch and process wheel scrolling events via Javascript."""
        delta = event.angleDelta().y()
        self.eval_javascript('scrollTerm({0})'.format(delta))

    def event(self, event):
        """Grab all keyboard input."""
        if event.type() == QEvent.ShortcutOverride:
            self.keyPressEvent(event)
            return True
        return True

    def keyPressEvent(self, event):
        """Qt override method."""
        key = event.key()
        modifiers = event.modifiers()

        if modifiers & Qt.ShiftModifier:
            key += Qt.SHIFT
        if modifiers & Qt.ControlModifier:
            key += Qt.CTRL
        if modifiers & Qt.AltModifier:
            key += Qt.ALT
        if modifiers & Qt.MetaModifier:
            key += Qt.META

        sequence = QKeySequence(key).toString(QKeySequence.PortableText)
        if event == QKeySequence.Paste:
            self.paste()
        elif sequence == self.CONF.get_shortcut(CONF_SECTION, 'copy'):
            self.copy()
        elif sequence == self.CONF.get_shortcut(CONF_SECTION, 'paste'):
            self.paste()
        elif sequence == self.CONF.get_shortcut(CONF_SECTION, 'clear'):
            self.clear()
        elif sequence == self.CONF.get_shortcut(
                CONF_SECTION, 'zoom in'):
            self.increase_font()
        elif sequence == self.CONF.get_shortcut(
                CONF_SECTION, 'zoom out'):
            self.decrease_font()
        else:
            super().keyPressEvent(event)
示例#3
0
文件: qt.py 项目: ag-pyqt/pywebview
class BrowserView(QMainWindow):
    instances = {}
    inspector_port = None  # The localhost port at which the Remote debugger listens

    create_window_trigger = QtCore.Signal(object)
    set_title_trigger = QtCore.Signal(str)
    load_url_trigger = QtCore.Signal(str)
    html_trigger = QtCore.Signal(str, str)
    dialog_trigger = QtCore.Signal(int, str, bool, str, str)
    destroy_trigger = QtCore.Signal()
    hide_trigger = QtCore.Signal()
    show_trigger = QtCore.Signal()
    fullscreen_trigger = QtCore.Signal()
    window_size_trigger = QtCore.Signal(int, int, FixPoint)
    window_move_trigger = QtCore.Signal(int, int)
    window_minimize_trigger = QtCore.Signal()
    window_restore_trigger = QtCore.Signal()
    current_url_trigger = QtCore.Signal()
    evaluate_js_trigger = QtCore.Signal(str, str)
    on_top_trigger = QtCore.Signal(bool)

    class JSBridge(QtCore.QObject):
        qtype = QtCore.QJsonValue if is_webengine else str

        def __init__(self):
            super(BrowserView.JSBridge, self).__init__()

        @QtCore.Slot(str, qtype, str, result=str)
        def call(self, func_name, param, value_id):
            func_name = BrowserView._convert_string(func_name)
            param = BrowserView._convert_string(param)

            return js_bridge_call(self.window, func_name, json.loads(param),
                                  value_id)

    class WebView(QWebView):
        def __init__(self, parent=None):
            super(BrowserView.WebView, self).__init__(parent)

            if parent.frameless and parent.easy_drag:
                QApplication.instance().installEventFilter(self)
                self.setMouseTracking(True)

            self.transparent = parent.transparent
            if parent.transparent:
                self.setAttribute(QtCore.Qt.WA_TranslucentBackground)
                self.setAttribute(QtCore.Qt.WA_OpaquePaintEvent, False)
                self.setStyleSheet("background: transparent;")

        def contextMenuEvent(self, event):
            menu = self.page().createStandardContextMenu()

            # If 'Inspect Element' is present in the default context menu, it
            # means the inspector is already up and running.
            for i in menu.actions():
                if i.text() == 'Inspect Element':
                    break
            else:
                # Inspector is not up yet, so create a pseudo 'Inspect Element'
                # menu that will fire it up.
                inspect_element = QAction('Inspect Element', menu)
                inspect_element.triggered.connect(self.show_inspector)
                menu.addAction(inspect_element)

            menu.exec_(event.globalPos())

        # Create a new webview window pointing at the Remote debugger server
        def show_inspector(self):
            uid = self.parent().uid + '-inspector'
            try:
                # If inspector already exists, bring it to the front
                BrowserView.instances[uid].raise_()
                BrowserView.instances[uid].activateWindow()
            except KeyError:
                title = 'Web Inspector - {}'.format(self.parent().title)
                url = 'http://localhost:{}'.format(BrowserView.inspector_port)
                print(url)
                window = Window('web_inspector', title, url, '', 700, 500,
                                None, None, True, False, (300, 200), False,
                                False, False, False, False, False, '#fff',
                                None, False, False, None)
                window.localization = self.parent().localization

                inspector = BrowserView(window)
                inspector.show()

        def mousePressEvent(self, event):
            if event.button() == QtCore.Qt.LeftButton:
                self.drag_pos = event.globalPos() - self.parent(
                ).frameGeometry().topLeft()

            event.accept()

        def mouseMoveEvent(self, event):
            parent = self.parent()
            if parent.frameless and parent.easy_drag and int(
                    event.buttons()) == 1:  # left button is pressed
                parent.move(event.globalPos() - self.drag_pos)

        def eventFilter(self, object, event):
            if object.parent() == self:
                if event.type() == QtCore.QEvent.MouseMove:
                    self.mouseMoveEvent(event)
                elif event.type() == QtCore.QEvent.MouseButtonPress:
                    self.mousePressEvent(event)

            return False

    # New-window-requests handler for Qt 5.5+ only
    class NavigationHandler(QWebPage):
        def __init__(self, parent=None):
            super(BrowserView.NavigationHandler, self).__init__(parent)

        def acceptNavigationRequest(self, url, type, is_main_frame):
            webbrowser.open(url.toString(), 2, True)
            return False

    class WebPage(QWebPage):
        def __init__(self, parent=None):
            super(BrowserView.WebPage, self).__init__(parent)
            if is_webengine:
                self.featurePermissionRequested.connect(
                    self.onFeaturePermissionRequested)
                self.nav_handler = BrowserView.NavigationHandler(self)
            else:
                self.nav_handler = None

            if parent.transparent:
                self.setBackgroundColor(QtCore.Qt.transparent)

        if is_webengine:

            def onFeaturePermissionRequested(self, url, feature):
                if feature in (
                        QWebPage.MediaAudioCapture,
                        QWebPage.MediaVideoCapture,
                        QWebPage.MediaAudioVideoCapture,
                ):
                    self.setFeaturePermission(url, feature,
                                              QWebPage.PermissionGrantedByUser)
                else:
                    self.setFeaturePermission(url, feature,
                                              QWebPage.PermissionDeniedByUser)
        else:

            def acceptNavigationRequest(self, frame, request, type):
                if frame is None:
                    webbrowser.open(request.url().toString(), 2, True)
                    return False
                return True

        def userAgentForUrl(self, url):
            user_agent = settings.get('user_agent') or _user_agent
            if user_agent:
                return user_agent
            else:
                return super().userAgentForUrl(url)

        def createWindow(self, type):
            return self.nav_handler

    def __init__(self, window):
        super(BrowserView, self).__init__()
        BrowserView.instances[window.uid] = self
        self.uid = window.uid
        self.pywebview_window = window

        self.js_bridge = BrowserView.JSBridge()
        self.js_bridge.window = window

        self.is_fullscreen = False
        self.confirm_close = window.confirm_close
        self.text_select = window.text_select

        self._file_name_semaphore = Semaphore(0)
        self._current_url_semaphore = Semaphore(0)

        self.loaded = window.events.loaded
        self.shown = window.events.shown

        self.localization = window.localization

        self._js_results = {}
        self._current_url = None
        self._file_name = None

        self.resize(window.initial_width, window.initial_height)
        self.title = window.title
        self.setWindowTitle(window.title)

        # Set window background color
        self.background_color = QColor()
        self.background_color.setNamedColor(window.background_color)
        palette = self.palette()
        palette.setColor(self.backgroundRole(), self.background_color)
        self.setPalette(palette)

        if not window.resizable:
            self.setFixedSize(window.initial_width, window.initial_height)

        self.setMinimumSize(window.min_size[0], window.min_size[1])

        self.frameless = window.frameless
        self.easy_drag = window.easy_drag
        flags = self.windowFlags()
        if self.frameless:
            flags = flags | QtCore.Qt.FramelessWindowHint

        if window.on_top:
            flags = flags | QtCore.Qt.WindowStaysOnTopHint

        self.setWindowFlags(flags)

        self.transparent = window.transparent
        if self.transparent:
            # Override the background color
            self.background_color = QColor('transparent')
            palette = self.palette()
            palette.setColor(self.backgroundRole(), self.background_color)
            self.setPalette(palette)
            # Enable the transparency hint
            self.setAttribute(QtCore.Qt.WA_TranslucentBackground)

        self.view = BrowserView.WebView(self)

        if is_webengine:
            os.environ['QTWEBENGINE_CHROMIUM_FLAGS'] = (
                '--use-fake-ui-for-media-stream --enable-features=AutoplayIgnoreWebAudio'
            )

        if _debug['mode'] and is_webengine:
            # Initialise Remote debugging (need to be done only once)
            if not BrowserView.inspector_port:
                BrowserView.inspector_port = BrowserView._get_debug_port()
                os.environ[
                    'QTWEBENGINE_REMOTE_DEBUGGING'] = BrowserView.inspector_port
        else:
            self.view.setContextMenuPolicy(
                QtCore.Qt.NoContextMenu)  # disable right click context menu

        self.view.setPage(BrowserView.WebPage(self.view))
        self.view.page().loadFinished.connect(self.on_load_finished)

        self.setCentralWidget(self.view)

        self.create_window_trigger.connect(BrowserView.on_create_window)
        self.load_url_trigger.connect(self.on_load_url)
        self.html_trigger.connect(self.on_load_html)
        self.dialog_trigger.connect(self.on_file_dialog)
        self.destroy_trigger.connect(self.on_destroy_window)
        self.show_trigger.connect(self.on_show_window)
        self.hide_trigger.connect(self.on_hide_window)
        self.fullscreen_trigger.connect(self.on_fullscreen)
        self.window_size_trigger.connect(self.on_window_size)
        self.window_move_trigger.connect(self.on_window_move)
        self.window_minimize_trigger.connect(self.on_window_minimize)
        self.window_restore_trigger.connect(self.on_window_restore)
        self.current_url_trigger.connect(self.on_current_url)
        self.evaluate_js_trigger.connect(self.on_evaluate_js)
        self.set_title_trigger.connect(self.on_set_title)
        self.on_top_trigger.connect(self.on_set_on_top)

        if is_webengine and platform.system() != 'OpenBSD':
            self.channel = QWebChannel(self.view.page())
            self.view.page().setWebChannel(self.channel)

        if window.fullscreen:
            self.toggle_fullscreen()

        if window.real_url is not None:
            self.view.setUrl(QtCore.QUrl(window.real_url))
        elif window.uid == 'web_inspector':
            self.view.setUrl(QtCore.QUrl(window.original_url))
        elif window.html:
            self.view.setHtml(window.html, QtCore.QUrl(''))
        else:
            self.view.setHtml(default_html, QtCore.QUrl(''))

        if window.initial_x is not None and window.initial_y is not None:
            self.move(window.initial_x, window.initial_y)
        else:
            center = QApplication.desktop().availableGeometry().center(
            ) - self.rect().center()
            self.move(center.x(), center.y())

        if not window.minimized:
            self.activateWindow()
            self.raise_()

        self.shown.set()

    def on_set_title(self, title):
        self.setWindowTitle(title)

    def on_file_dialog(self, dialog_type, directory, allow_multiple,
                       save_filename, file_filter):
        if dialog_type == FOLDER_DIALOG:
            self._file_name = QFileDialog.getExistingDirectory(
                self,
                self.localization['linux.openFolder'],
                options=QFileDialog.ShowDirsOnly)
        elif dialog_type == OPEN_DIALOG:
            if allow_multiple:
                self._file_name = QFileDialog.getOpenFileNames(
                    self, self.localization['linux.openFiles'], directory,
                    file_filter)
            else:
                self._file_name = QFileDialog.getOpenFileName(
                    self, self.localization['linux.openFile'], directory,
                    file_filter)
        elif dialog_type == SAVE_DIALOG:
            if directory:
                save_filename = os.path.join(str(directory),
                                             str(save_filename))

            self._file_name = QFileDialog.getSaveFileName(
                self, self.localization['global.saveFile'], save_filename)

        self._file_name_semaphore.release()

    def on_current_url(self):
        url = BrowserView._convert_string(self.view.url().toString())
        self._current_url = None if url == '' or url.startswith(
            'data:text/html') else url
        self._current_url_semaphore.release()

    def on_load_url(self, url):
        self.view.setUrl(QtCore.QUrl(url))

    def on_load_html(self, content, base_uri):
        self.view.setHtml(content, QtCore.QUrl(base_uri))

    def on_set_on_top(self, top):
        flags = self.windowFlags()
        if top:
            self.setWindowFlags(flags | QtCore.Qt.WindowStaysOnTopHint)
        else:
            self.setWindowFlags(flags & ~QtCore.Qt.WindowStaysOnTopHint)

        self.show()

    def closeEvent(self, event):
        if self.confirm_close:
            reply = QMessageBox.question(
                self, self.title, self.localization['global.quitConfirmation'],
                QMessageBox.Yes, QMessageBox.No)

            if reply == QMessageBox.No:
                event.ignore()
                return

        should_cancel = self.pywebview_window.events.closing.set()

        if should_cancel:
            event.ignore()
            return

        event.accept()
        BrowserView.instances[self.uid].close()
        del BrowserView.instances[self.uid]

        if self.pywebview_window in windows:
            windows.remove(self.pywebview_window)

        self.pywebview_window.events.closed.set()

        if len(BrowserView.instances) == 0:
            self.hide()
            _app.exit()

    def changeEvent(self, e):
        if e.type() != QtCore.QEvent.WindowStateChange:
            return

        if self.windowState() == QtCore.Qt.WindowMinimized:
            self.pywebview_window.events.minimized.set()

        if self.windowState() == QtCore.Qt.WindowMaximized:
            self.pywebview_window.events.maximized.set()

        if self.windowState() == QtCore.Qt.WindowNoState and e.oldState() in (
                QtCore.Qt.WindowMinimized, QtCore.Qt.WindowMaximized):
            self.pywebview_window.events.restored.set()

    def resizeEvent(self, e):
        if self.pywebview_window.initial_width != self.width() or \
           self.pywebview_window.initial_height != self.height():
            self.pywebview_window.events.resized.set(self.width(),
                                                     self.height())

    def on_show_window(self):
        self.show()

    def on_hide_window(self):
        self.hide()

    def on_destroy_window(self):
        self.close()

    def on_fullscreen(self):
        if self.is_fullscreen:
            self.showNormal()
        else:
            self.showFullScreen()

        self.is_fullscreen = not self.is_fullscreen

    def on_window_size(self, width, height, fix_point):
        geo = self.geometry()

        if fix_point & FixPoint.EAST:
            # Keep the right of the window in the same place
            geo.setX(geo.x() + geo.width() - width)

        if fix_point & FixPoint.SOUTH:
            # Keep the top of the window in the same place
            geo.setY(geo.y() + geo.height() - height)

        self.setGeometry(geo)
        self.setFixedSize(width, height)

    def on_window_move(self, x, y):
        self.move(x, y)

    def on_window_minimize(self):
        self.setWindowState(QtCore.Qt.WindowMinimized)

    def on_window_restore(self):
        self.setWindowState(QtCore.Qt.WindowNoState)
        self.raise_()
        self.activateWindow()

    def on_evaluate_js(self, script, uuid):
        def return_result(result):
            result = BrowserView._convert_string(result)
            uuid_ = BrowserView._convert_string(uuid)

            js_result = self._js_results[uuid_]
            js_result[
                'result'] = None if result is None or result == 'null' else result if result == '' else json.loads(
                    result)
            js_result['semaphore'].release()

        try:  # < Qt5.6
            self.view.page().runJavaScript(script, return_result)
        except TypeError:
            self.view.page().runJavaScript(script)  # PySide2 & PySide6
        except AttributeError:
            result = self.view.page().mainFrame().evaluateJavaScript(script)
            return_result(result)
        except Exception as e:
            logger.exception(e)

    def on_load_finished(self):
        if self.uid == 'web_inspector':
            return

        self._set_js_api()

        if not self.text_select:
            script = disable_text_select.replace('\n', '')

            try:
                self.view.page().runJavaScript(script)
            except:  # QT < 5.6
                self.view.page().mainFrame().evaluateJavaScript(script)

    def set_title(self, title):
        self.set_title_trigger.emit(title)

    def get_current_url(self):
        self.loaded.wait()
        self.current_url_trigger.emit()
        self._current_url_semaphore.acquire()

        return self._current_url

    def load_url(self, url):
        self.loaded.clear()
        self.load_url_trigger.emit(url)

    def load_html(self, content, base_uri):
        self.loaded.clear()
        self.html_trigger.emit(content, base_uri)

    def create_file_dialog(self, dialog_type, directory, allow_multiple,
                           save_filename, file_filter):
        self.dialog_trigger.emit(dialog_type, directory, allow_multiple,
                                 save_filename, file_filter)
        self._file_name_semaphore.acquire()

        if dialog_type == FOLDER_DIALOG:
            file_names = (self._file_name, )
        elif dialog_type == SAVE_DIALOG or not allow_multiple:
            file_names = (self._file_name[0], )
        else:
            file_names = tuple(self._file_name[0])

        # Check if we got an empty tuple, or a tuple with empty string
        if len(file_names) == 0 or len(file_names[0]) == 0:
            return None
        else:
            return file_names

    def hide_(self):
        self.hide_trigger.emit()

    def show_(self):
        self.show_trigger.emit()

    def destroy_(self):
        self.destroy_trigger.emit()

    def toggle_fullscreen(self):
        self.fullscreen_trigger.emit()

    def resize_(self, width, height, fix_point):
        self.window_size_trigger.emit(width, height, fix_point)

    def move_window(self, x, y):
        self.window_move_trigger.emit(x, y)

    def minimize(self):
        self.window_minimize_trigger.emit()

    def restore(self):
        self.window_restore_trigger.emit()

    def set_on_top(self, top):
        self.on_top_trigger.emit(top)

    def evaluate_js(self, script):
        self.loaded.wait()
        result_semaphore = Semaphore(0)
        unique_id = uuid1().hex
        self._js_results[unique_id] = {
            'semaphore': result_semaphore,
            'result': ''
        }

        self.evaluate_js_trigger.emit(script, unique_id)
        result_semaphore.acquire()

        result = deepcopy(self._js_results[unique_id]['result'])
        del self._js_results[unique_id]

        return result

    def _set_js_api(self):
        def _register_window_object():
            frame.addToJavaScriptWindowObject('external', self.js_bridge)

        code = 'qtwebengine' if is_webengine else 'qtwebkit'
        script = parse_api_js(self.js_bridge.window, code)

        if is_webengine:
            qwebchannel_js = QtCore.QFile('://qtwebchannel/qwebchannel.js')
            if qwebchannel_js.open(QtCore.QFile.ReadOnly):
                source = bytes(qwebchannel_js.readAll()).decode('utf-8')
                self.view.page().runJavaScript(source)
                self.channel.registerObject('external', self.js_bridge)
                qwebchannel_js.close()
        else:
            frame = self.view.page().mainFrame()
            _register_window_object()

        try:
            self.view.page().runJavaScript(script)
        except AttributeError:  # < QT 5.6
            self.view.page().mainFrame().evaluateJavaScript(script)

        self.loaded.set()

    @staticmethod
    def _convert_string(result):
        try:
            if result is None or result.isNull():
                return None

            result = result.toString()  # QJsonValue conversion
        except AttributeError:
            pass

        return convert_string(result)

    @staticmethod
    def _get_debug_port():
        """
        Check if default debug port 8228 is available,
        increment it by 1 until a port is available.
        :return: port: str
        """
        port_available = False
        port = 8228

        while not port_available:
            try:
                sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
                sock.bind(('localhost', port))
                port_available = True
            except:
                port_available = False
                logger.warning('Port %s is in use' % port)
                port += 1
            finally:
                sock.close()

        return str(port)

    @staticmethod
    # Receive func from subthread and execute it on the main thread
    def on_create_window(func):
        func()
示例#4
0
class MapsView(QWebEngineView):
    """Show Google Maps in Qt app."""
    def __init__(self, parent):
        super(MapsView, self).__init__(parent)
        self._logger = logging.getLogger(self.__class__.__name__)

        # Set browser attributes
        QNetworkProxyFactory.setUseSystemConfiguration(False)
        self.settings().setAttribute(
            QWebEngineSettings.Accelerated2dCanvasEnabled, True)
        self.settings().setAttribute(QWebEngineSettings.JavascriptEnabled,
                                     True)

        # Create maps page (it is not needed, but we can handle Javascript console logs)
        self._mapspage = MapsPage(self)
        self.setPage(self._mapspage)

        # Create connection between Javascript and Qt
        self.channel = QWebChannel(self.page())
        self.page().setWebChannel(self.channel)

        # create map events handler and register it as "jshelper" in HTML
        self.handler = CallHandler(self)
        self.handler.runJavascript.connect(self.runScript)
        self.loadFinished.connect(self.handler.on_loadFinished)
        self.loadStarted.connect(self.handler.on_loadStarted)

        self.channel.registerObject("jshelper", self.handler)

    @Slot(str, object)
    def runScript(self, script, callback):
        """Run Javascript code.

        Args:
            script (str): Script to execute.
            callback (callback, optional): Function to handle callback.
        """
        if callback is None:
            self.page().runJavaScript(script)
        else:
            self.page().runJavaScript(script, callback)

    def getHandler(self):
        """Returns map event handler."""
        return self.handler

    def enableMarkersDragging(self, value):
        """Enable or disable markers dragging feature.

        Args:
            value (bool): Enable markers dragging.
        """
        self.handler.enableMarkersDragging(value)

    def addMarker(self, marker_id, lat, lng, options=None):
        """Creates marker with marker_id id at latitude, longitude.

        Args:
            marker_id (int): Marker ID.
            lat (float): Marker latitude.
            lng (float): Marker longitude.
            options (dict): Marker options.
        """
        if options is None:
            options = {}
        self.handler.addMarker(marker_id, lat, lng, options)

    def deleteMarker(self, marker_id):
        """Delete marker with ID.

        Args:
            marker_id (int): Marker ID.
        """
        self.handler.deleteMarker(marker_id)

    def updateMarker(self, marker_id, options):
        """Delete marker with ID.

        Args:
            marker_id (int): Marker ID.
            options (dict): Marker options.
        """
        self.handler.updateMarker(marker_id, options)

    def moveMarker(self, marker_id, lat, lng):
        """Move marker to location with (lat, lng).

        Args:
            marker_id (int): Marker ID.
            lat (float): Marker latitude.
            lng (float): Marker longitude.
        """
        self.handler.moveMarker(marker_id, lat, lng)

    def deletePolyline(self, polyline_id):
        """Delete polyline with ID.

        Args:
            polyline_id (int): Polyline ID.
        """
        self.handler.deletePolyline(polyline_id)

    def addPolyline(self, polyline_id, coords: list):
        """Creates polyline using coordinates.

        Args:
            polyline_id (int): Polyline id
            coords (list): List of coordinates (dicts with "lat" and "lng" keys).
        """
        self.handler.addPolyline(polyline_id, coords)

    def addPolylineBetweenMarkers(self, polyline_id, markers: list):
        """Creates polyline using coordinates.

        Args:
            polyline_id (int): Polyline id
            markers (list): List of markers IDs.
        """
        self.handler.addPolylineBetweenMarkers(polyline_id, markers)

    def panToCenter(self):
        """Pan map to center."""
        self.handler.panToCenter()

    def disableMapDragging(self, value):
        """Enable or disable map dragging.

        Args:
            value (bool): Map dragging status.
        """
        self.handler.disableMapDragging(value)

    def showZoomControl(self, value):
        """Show or hide zoom control widget.

        Args:
            value (bool): Zoom control widget status.
        """
        self.handler.showZoomControl(value)

    def disableDoubleClickToZoom(self, value):
        """Enable or disable double click to zoom.

        Args:
            value (bool): Double click to zoom status.
        """
        self.handler.disableDoubleClickToZoom(value)

    def disableScrollWheel(self, value):
        """Enable or disable scroll to zoom.

        Args:
            value (bool): Scroll to zoom status.
        """
        self.handler.disableScrollWheel(value)
示例#5
0
class TermView(QWebEngineView, SpyderWidgetMixin):
    """XTerm Wrapper."""
    sig_focus_in_event = Signal()
    """
    This signal is emitted when the widget receives focus.
    """

    sig_focus_out_event = Signal()
    """
    This signal is emitted when the widget loses focus.
    """

    def __init__(self, parent, term_url='http://127.0.0.1:8070', handler=None):
        """Webview main constructor."""
        super().__init__(parent, class_parent=parent)
        web_page = QWebEnginePage(self)
        self.setPage(web_page)
        self.source_text = ''
        self.parent = parent
        self.channel = QWebChannel(self.page())
        self.page().setWebChannel(self.channel)
        self.channel.registerObject('handler', handler)

        self.term_url = QUrl(term_url)
        self.load(self.term_url)
        self.focusProxy().installEventFilter(self)

        self.document = self.page()
        try:
            self.document.profile().clearHttpCache()
        except AttributeError:
            pass

        self.initial_y_pos = 0
        self.setFocusPolicy(Qt.ClickFocus)
        self.setup()

    def setup(self):
        """Create the terminal context menu."""
        # Create context menu
        self.context_menu = self.get_menu(TermViewMenus.Context)
        for item in [self.get_action(TerminalMainWidgetActions.Copy),
                     self.get_action(TerminalMainWidgetActions.Paste),
                     self.get_action(TerminalMainWidgetActions.Clear)]:
            self.add_item_to_menu(
                item,
                menu=self.context_menu,
                section=TermViewSections.CommonActions,
            )

        for item in [self.get_action(TerminalMainWidgetActions.ZoomIn),
                     self.get_action(TerminalMainWidgetActions.ZoomOut)]:
            self.add_item_to_menu(
                item,
                menu=self.context_menu,
                section=TermViewSections.ZoomActions,
            )

    def copy(self):
        """Copy unicode text from terminal."""
        self.triggerPageAction(QWebEnginePage.Copy)

    def paste(self):
        """Paste unicode text into terminal."""
        clipboard = QApplication.clipboard()
        text = str(clipboard.text())
        if len(text.splitlines()) > 1:
            eol_chars = os.linesep
            text = eol_chars.join((text + eol_chars).splitlines())
        self.eval_javascript('pasteText({})'.format(repr(text)))

    def clear(self):
        """Clear the terminal."""
        self.eval_javascript('clearTerm()')

    def increase_font(self):
        """Increase terminal font."""
        zoom = self.get_conf('zoom')
        self.set_conf('zoom', zoom + 1)
        return self.eval_javascript('increaseFontSize()')

    def decrease_font(self):
        """Decrease terminal font."""
        zoom = self.get_conf('zoom')
        self.set_conf('zoom', zoom - 1)
        return self.eval_javascript('decreaseFontSize()')

    def contextMenuEvent(self, event):
        """Override Qt method."""
        self.context_menu.popup(event.globalPos())
        event.accept()

    def eval_javascript(self, script):
        """
        Evaluate Javascript instructions inside DOM with the expected prefix.
        """
        script = PREFIX + script
        self.document.runJavaScript("{}".format(script), self.return_js_value)

    def return_js_value(self, value):
        """Return the value of the function evaluated in Javascript."""
        return value

    def wheelEvent(self, event):
        """Catch and process wheel scrolling events via Javascript."""
        delta = event.angleDelta().y()
        self.eval_javascript('scrollTerm({0})'.format(delta))

    def event(self, event):
        """Grab all keyboard input."""
        if event.type() == QEvent.ShortcutOverride:
            self.keyPressEvent(event)
            return True
        return True

    def keyPressEvent(self, event):
        """Qt override method."""
        key = event.key()
        modifiers = event.modifiers()

        if modifiers & Qt.ShiftModifier:
            key += Qt.SHIFT
        if modifiers & Qt.ControlModifier:
            key += Qt.CTRL
        if modifiers & Qt.AltModifier:
            key += Qt.ALT
        if modifiers & Qt.MetaModifier:
            key += Qt.META

        sequence = QKeySequence(key).toString(QKeySequence.PortableText)
        if event == QKeySequence.Paste:
            self.paste()
        elif sequence == self.get_shortcut('copy'):
            self.copy()
        elif sequence == self.get_shortcut('paste'):
            self.paste()
        elif sequence == self.get_shortcut('clear'):
            self.clear()
        elif sequence == self.get_shortcut('zoom_in'):
            self.increase_font()
        elif sequence == self.get_shortcut('zoom_out'):
            self.decrease_font()
        else:
            super().keyPressEvent(event)

    def eventFilter(self, widget, event):
        """
        Handle events that affect the view.
        All events (e.g. focus in/out) reach the focus proxy, not this
        widget itself. That's why this event filter is necessary.
        """
        if self.focusProxy() is widget:
            if event.type() == QEvent.FocusIn:
                self.sig_focus_in_event.emit()
            elif event.type() == QEvent.FocusOut:
                self.sig_focus_out_event.emit()
        return super().eventFilter(widget, event)
示例#6
0
class WebEngineView(QWebEngineView):
    customSignal = Signal(str, str)
    saveSignal = Signal()
    signal_open_file = Signal(str, str)
    signal_request_text = Signal()
    signal_text_got = Signal(str)
    signal_save_as = Signal(str)
    signal_set_autocomplete_apis = Signal(str)

    def __init__(self, *args, **kwargs):
        super(WebEngineView, self).__init__(*args, **kwargs)
        self._untitled_id = 0
        self.initSettings()
        self.channel = QWebChannel(self)
        # 把自身对象传递进去
        self.channel.registerObject('Bridge', self)
        # 设置交互接口
        self.page().setWebChannel(self.channel)

        # self.signal_set_autocomplete_apis.emit({"keywords": ["aaaaa", "bbbbbb"]})

    @Slot(str, str)
    def on_text_received(self, path, text):
        print(text)
        self.signal_text_got.emit(text)

    # 注意pyqtSlot用于把该函数暴露给js可以调用
    @Slot(str)
    def print_from_js(self, text):
        print('print from js', text)

    @Slot(str, str)
    def callFromJs(self, file, text):
        print('call from js!')
        try:
            with open(file, mode='w', encoding='utf-8') as f:
                f.write(text.replace('\r', ''))
                f.close()
        except Exception as e:
            print(e)

    @Slot(str, str)
    def on_save(self, file_name: str, text: str):
        if os.path.isabs(file_name):
            pass
        else:
            file_name, ext = QFileDialog.getSaveFileName(self, "aaa", "/home/hzy/Desktop", "All Files(*)")
        print(file_name, text)
        with open(file_name, 'w') as f:
            f.write(text)
        self.signal_save_as.emit(file_name)

    def open_file(self, file: str):
        """
        打开文件
        Args:
            file:

        Returns:

        """
        with open(file, 'r', encoding='utf-8') as f:
            text = f.read()
        self.signal_open_file.emit(file, text)

    def new_file(self):
        self._untitled_id += 1
        self.signal_open_file.emit("Untitled-%d.py" % self._untitled_id, "")

    def sendCustomSignal(self, file):
        # 发送自定义信号
        with open(file, 'r', encoding='utf-8') as f:
            text = f.read()
        self.customSignal.emit(file, text)

    def sendSaveSignal(self):
        self.saveSignal.emit()

    # @Slot(str)
    # @Slot(QUrl)
    def load(self, url):
        '''
        eg: load("https://qtpy.com")
        :param url: 网址
        '''
        return super(WebEngineView, self).load(QUrl(url))

    def initSettings(self):
        '''
        eg: 初始化设置
        '''
        # 获取浏览器默认设置
        settings = QWebEngineSettings.globalSettings()
        # 设置默认编码utf8
        settings.setDefaultTextEncoding("utf-8")
        # 自动加载图片,默认开启
        # settings.setAttribute(QWebEngineSettings.AutoLoadImages,True)
        # 自动加载图标,默认开启
        # settings.setAttribute(QWebEngineSettings.AutoLoadIconsForPage,True)
        # 开启js,默认开启
        # settings.setAttribute(QWebEngineSettings.JavascriptEnabled,True)
        # js可以访问剪贴板
        settings.setAttribute(
            QWebEngineSettings.JavascriptCanAccessClipboard, True)
        # js可以打开窗口,默认开启
        # settings.setAttribute(QWebEngineSettings.JavascriptCanOpenWindows,True)
        # 链接获取焦点时的状态,默认开启
        # settings.setAttribute(QWebEngineSettings.LinksIncludedInFocusChain,True)
        # 本地储存,默认开启
        # settings.setAttribute(QWebEngineSettings.LocalStorageEnabled,True)
        # 本地访问远程
        settings.setAttribute(
            QWebEngineSettings.LocalContentCanAccessRemoteUrls, True)
        # 本地加载,默认开启
        # settings.setAttribute(QWebEngineSettings.LocalContentCanAccessFileUrls,True)
        # 监控负载要求跨站点脚本,默认关闭
        # settings.setAttribute(QWebEngineSettings.XSSAuditingEnabled,False)
        # 空间导航特性,默认关闭
        # settings.setAttribute(QWebEngineSettings.SpatialNavigationEnabled,False)
        # 支持平超链接属性,默认关闭
        # settings.setAttribute(QWebEngineSettings.HyperlinkAuditingEnabled,False)
        # 使用滚动动画,默认关闭
        settings.setAttribute(QWebEngineSettings.ScrollAnimatorEnabled, True)
        # 支持错误页面,默认启用
        # settings.setAttribute(QWebEngineSettings.ErrorPageEnabled, True)
        # 支持插件,默认关闭
        settings.setAttribute(QWebEngineSettings.PluginsEnabled, True)
        # 支持全屏应用程序,默认关闭
        settings.setAttribute(
            QWebEngineSettings.FullScreenSupportEnabled, True)
        # 支持屏幕截屏,默认关闭
        settings.setAttribute(QWebEngineSettings.ScreenCaptureEnabled, True)
        # 支持html5 WebGl,默认开启
        settings.setAttribute(QWebEngineSettings.WebGLEnabled, True)
        # 支持2d绘制,默认开启
        settings.setAttribute(
            QWebEngineSettings.Accelerated2dCanvasEnabled, True)
        # 支持图标触摸,默认关闭
        settings.setAttribute(QWebEngineSettings.TouchIconsEnabled, True)