Esempio n. 1
0
    def __init__(self):
        QWidget.__init__(self, flags=Qt.Widget)

        # 레이아웃 선언 및 Form Widget에 설정
        self.layout_1 = QBoxLayout(QBoxLayout.LeftToRight, self)
        self.layout_2 = QBoxLayout(QBoxLayout.LeftToRight)
        self.layout_3 = QBoxLayout(QBoxLayout.TopToBottom)

        # 부모 레이아웃에 자식 레이아웃을 추가
        self.layout_1.addLayout(self.layout_2)
        self.layout_1.addLayout(self.layout_3)

        self.web = QWebEngineView()
        self.pb_1 = QPushButton("요청하기")
        self.pb_2 = QPushButton("밝기지도")
        self.pb_3 = QPushButton("길찾기")
        # self.te_1 = QTextEdit()

        # Web과 통신할 수 있게 채널링
        channel = QWebChannel(self.web.page())
        self.web.page().setWebChannel(channel)
        self.handler = CallHandler()  # 반드시 인스턴스 변수로 사용해야 한다.
        channel.registerObject('handler', self.handler)  # js에서 불리울 이름

        self.setLayout(self.layout_1)
        self.init_widget()
Esempio n. 2
0
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        # 设置窗口标题
        # self.setWindowTitle('My Browser')
        # 设置窗口图标
        # self.setWindowIcon(QIcon('icons/penguin.png'))
        # 设置窗口大小900*600
        # self.resize(900, 600)
        # self.show()

        # 2
        self.view = QWebEngineView()
        channel = QWebChannel()
        handler = CallHandler()
        channel.registerObject('handler', handler)
        self.view.page().setWebChannel(channel)
        self.view.loadFinished.connect(self._loadFinish)

        # 添加浏览器到窗口中
        # self.setCentralWidget(self.view)

        #
        htmlUrl = 'file:////Users/play/github/Python_Master_Courses/GUI图形界面/pyqt5/pyqt5-javascript-互相传值/js1.html'
        self.view.load(QUrl(htmlUrl))
        self.view.show()
Esempio n. 3
0
    def initUI(self):
        '''----测试数据----'''
        # --函数测试通过删除

        self.ledit_StartLat.setText(str(46.49))
        self.ledit_StartLon.setText(str(6.6))
        self.ledit_StopLat.setText(str(46.53))
        self.ledit_StopLon.setText(str(6.7))
        self.spinBox_zoom.setValue(15)

        #testWeb=myClass()
        #self.browser=QWebEngineView()
        #self.browser=QWebEngineView(self.centralwidget)
        #self.browser.setObjectName("browser_test")
        #self.gridLayout.addWidget(self.browser,0,1,2,1)
        #myChannel=QWebChannel()
        
        #self.browser.page().setWebChannel(myChannel)
        #myChannel.registerObject('testObject',testWeb)
        #self.browser.load(QUrl('E:///PySpace/tilesMap/index.html'))
        #self.browser.show()
        self.webBrowser.load(QUrl.fromLocalFile(os.path.abspath('./map/map.html')))
        self.interact_obj=TInteractObj(self)
        self.interact_obj.receive_str_from_js_callback = self.receive_data

        channel=QWebChannel(self.webBrowser.page())
        channel.registerObject("interact_obj",self.interact_obj)
        self.webBrowser.page().setWebChannel(channel)
Esempio n. 4
0
    def __init__(self, url=None):
        QWebEnginePage.__init__(self)
        # put the most recent buffer at the beginning of the BUFFERS list
        BUFFERS.insert(0, self)
        hooks.webbuffer_created(self)

        self.fullScreenRequested.connect(self._on_full_screen_requested)
        self.featurePermissionRequested.connect(self._on_feature_requested)
        self._content_handler = WebContentHandler(self)
        channel = QWebChannel(self)
        channel.registerObject("contentHandler", self._content_handler)

        self.setWebChannel(channel, QWebEngineScript.ApplicationWorld)

        self.loadFinished.connect(self.finished)
        self.authenticationRequired.connect(self.handle_authentication)
        self.linkHovered.connect(self.on_url_hovered)
        self.titleChanged.connect(self.update_title)
        self.__authentication_data = None
        self.__delay_loading_url = None
        self.__keymap_mode = Mode.KEYMAP_NORMAL
        self.__mode = get_mode("standard-mode")
        self.__text_edit_mark = False

        if url:
            if isinstance(url, DelayedLoadingUrl):
                self.__delay_loading_url = url
            else:
                self.load(url)
Esempio n. 5
0
class SearchResultsPage(BaseSearchPage):
    def __init__(self,
                 profile,
                 tmplMain,
                 ipfsConnParams,
                 webchannel=None,
                 parent=None):
        super(SearchResultsPage, self).__init__(parent, profile=profile)

        self.channel = QWebChannel(self)
        self.handler = IPFSSearchHandler(self)
        self.channel.registerObject('ipfssearch', self.handler)
        self.setWebChannel(self.channel)

        self.app = QApplication.instance()

        self.template = tmplMain
        self.ipfsConnParams = ipfsConnParams
        ensure(self.render())

    def javaScriptConsoleMessage(self, level, message, lineNumber, sourceId):
        log.debug(
            'JS: level: {0}, source: {1}, line: {2}, message: {3}'.format(
                level, sourceId, lineNumber, message))

    async def render(self):
        html = await renderTemplate(self.template,
                                    ipfsConnParams=self.ipfsConnParams)
        self.setHtml(html)
Esempio n. 6
0
class WebPlayer(QWebEngineView):
    def __init__(self, parent=None):
        super().__init__(parent)
        # 클래스 기본 설정
        path = os.getcwd() + '/support/webplayer.html'
        file = open(path, 'r', encoding="UTF8")
        self.html = file.read()
        file.close()

        NotificationCenter.subscribe(NotificationName.play, self.play)
        QWebEngineSettings.globalSettings().setAttribute(
            QWebEngineSettings.PluginsEnabled, True)
        QWebEngineSettings.globalSettings().setAttribute(
            QWebEngineSettings.JavascriptEnabled, True)

        # UI 설정
        self.setHtml(self.html)
        self.web_channel = QWebChannel(self)
        self.web_channel.registerObject('handler', self)
        self.page().setWebChannel(self.web_channel)

    # 재생
    def play(self, token):
        html = self.html.replace('{0}', token)
        self.setHtml(html)

    @pyqtSlot()
    def endVideo(self):
        NotificationCenter.notification(NotificationName.end_video)
Esempio n. 7
0
    def __init__(self, parent: QWidget = None):
        super().__init__(parent)
        self.setupUi(self)

        self.m_content = Document()
        self.m_filePath = ""

        self.editor.setFont(QFontDatabase.systemFont(QFontDatabase.FixedFont))
        self.preview.setContextMenuPolicy(Qt.NoContextMenu)

        page = PreviewPage(self)
        self.preview.setPage(page)

        self.editor.textChanged.connect(
            lambda: self.m_content.setText(self.editor.toPlainText()))

        channel = QWebChannel(self)
        channel.registerObject("content", self.m_content)
        page.setWebChannel(channel)

        self.preview.setUrl(QUrl("qrc:/index.html"))

        self.actionNew.triggered.connect(self.onFileNew)
        self.actionOpen.triggered.connect(self.onFileOpen)
        self.actionSave.triggered.connect(self.onFileSave)
        self.actionSaveAs.triggered.connect(self.onFileSaveAs)
        self.actionExit.triggered.connect(self.onExit)

        self.editor.document().modificationChanged.connect(
            self.actionSave.setEnabled)

        defaultTextFile = QFile(":/default.md")
        defaultTextFile.open(QIODevice.ReadOnly)
        self.editor.setPlainText(defaultTextFile.readAll().data().decode())
Esempio n. 8
0
class WebEngineView(QWebEngineView):
    def __init__(self, _record,*args, **kwargs):
        super(WebEngineView, self).__init__(*args, **kwargs)
        self.channel = QWebChannel(self)
        self.record=_record
        self.imgViewer = Img_Dialog()
        # 把自身对象传递进去
        self.channel.registerObject('Bridge', self)
        # 设置交互接口
        self.page().setWebChannel(self.channel)

    def view_img(self,result):
        if result:
            imgpath=result[8:]
            imgpath=imgpath.replace(imgpath.split("//")[1],"下载")
            self.imgViewer.setImg(imgpath)
            self.imgViewer.show()

    # 注意pyqtSlot用于把该函数暴露给js可以调用
    @pyqtSlot(str,str)
    def callFromJs(self,method, result):
        if method=="img":
            self.view_img(result)
        elif method=="tip":
            datas=result.split("|")
            lists=self.record.findNote(datas[2])
            response={}
            response["clientX"]=int(datas[0])
            response["clientY"] = int(datas[1])
            response["sectionId"] = datas[2]
            notes=[]
            for item in lists:
                notes.append(item["text"])
            response["notes"] = notes
            self.page().runJavaScript("\n showToolTip({});".format(response))
Esempio n. 9
0
class SC(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):

        self.view = QWebEngineView(self)

        self.channel = QWebChannel()
        self.handler = CallHandler()
        self.channel.registerObject('pyjs', self.handler)
        self.view.page().setWebChannel(self.channel)

        self.handler._signal1.connect(self.handler._slot1)
        #self.handler._signal2.connect(self.handler._slot2)

        b1 = QPushButton("b1", self)
        b1.clicked.connect(self.s1s)

        #b2 = QPushButton("b2", self)
        #b2.clicked.connect(self.s2s)

        url_string = "file:///test.html"
        self.view.load(QUrl(url_string))
        self.setGeometry(300, 300, 300, 220)
        self.show()

    def s1s(self):
        self.handler._signal1.emit("b1")

    def s2s(self):
        self.handler._signal2.emit("b2")
Esempio n. 10
0
class WebEngineView(QtWebEngineWidgets.QWebEngineView):
    customSignal = pyqtSignal(str)

    def __init__(self, *args, **kwargs):
        super(WebEngineView, self).__init__(*args, **kwargs)
        self.channel = QWebChannel(self)
        # 把自身对象传递进去
        self.channel.registerObject('Bridge', self)
        # 设置交互接口
        self.page().setWebChannel(self.channel)
        # self.page().loadStarted.connect(self.onLoadStart)
        # self._script = open('./qwebchannel.js', 'rb').read().decode()

    # def onLoadStart(self):
    #     print("here")
    #     self.page().runJavaScript(self._script)

    # 注意pyqtSlot用于把该函数暴露给js可以调用
    @pyqtSlot(str)
    def callFromJs(self, text):
        print(text)
        QMessageBox.information(self, "提示", "来自js调用:{}".format(text))

    def sendCustomSignal(self):
        # 发送自定义信号
        self.customSignal.emit('当前时间: ' + str(time()))

    @pyqtSlot(str)
    @pyqtSlot(QUrl)
    def load(self, url):
        '''
        eg: load("https://pyqt5.com")
        :param url: 网址
        '''
        return super(WebEngineView, self).load(QUrl(url))
Esempio n. 11
0
class Dialog(QDialog):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.ui = Ui_Dialog()
        self.ui.setupUi(self)

        self.m_view = QWebEngineView(self)
        self.channel = QWebChannel(self)
        self.b = bridge(self)

        self.channel.registerObject('b', self.b)
        self.m_view.page().setWebChannel(self.channel)
        self.m_view.page().load(
            QUrl("file:///" + QFileInfo("index.html").absoluteFilePath()))

        self.ui.viewLayout.addWidget(self.m_view)
        # js_str = '''window.bridge.showMsgBox()'''
        js_str = '''alert('from runJavaScript')'''
        self.ui.pbAlert.clicked.connect(
            lambda: self.m_view.page().runJavaScript(js_str, self.callback))
        # self.ui.pbAlert.clicked.connect(lambda: self.b.sendText.emit())

        # self.ui.pbGetWebWidth.clicked.connect(lambda: self.b.sendText.emit())

        # self.b.sendText.connect(lambda: print('b.sendText.emit():'))
        # self.b.signal_1.connect(lambda: print('b.signal_1.emit():'))

    def __del__(self):
        del self.ui

    def callback(self, v):
        print('callback:', v)
Esempio n. 12
0
    def __init__(self, *args, **kw):
        url = kw.pop('url', None)
        first = False
        if not url:
            url = get_authenticated_url()
            first = True
        self.url = url
        self.closing = False

        super(QWebView, self).__init__(*args, **kw)
        self.setWindowTitle('Bitmask')
        self.bitmask_browser = NewPageConnector(self) if first else None
        self.loadPage(self.url)

        self.bridge = AppBridge(self) if first else None

        if self.bridge is not None and HAS_WEBENGINE:
            print "[+] registering python<->js bridge"
            channel = QWebChannel(self)
            channel.registerObject("bitmaskApp", self.bridge)
            self.page().setWebChannel(channel)

        icon = QtGui.QIcon()
        icon.addPixmap(
            QtGui.QPixmap(":/mask-icon.png"),
            QtGui.QIcon.Normal, QtGui.QIcon.Off)
        self.setWindowIcon(icon)
Esempio n. 13
0
class FancyBox(QWebEngineView):
    def __init__(self, interface, title="", html=""):
        super().__init__()
        self.setWindowTitle(title)
        self.setWindowModality(Qt.ApplicationModal)
        self.resize(800, 600)

        # setup page
        self.web_page = CustomPage()
        self.web_page.setUrl(
            QUrl.fromLocalFile(html_to_file_path(html or f"<h1>{title}</h1>"))
        )
        # self.web_page.setHtml(html or f"<h1>{title}</h1>")
        self.setPage(self.web_page)

        # setup channel
        self.channel = QWebChannel()
        self.channel.registerObject("backend", interface)
        self.page().setWebChannel(self.channel)

        self.set_view(QApplication.instance())

    def set_view(self, app: QApplication):
        # Set geometry.
        screen_rect = app.desktop().screen().rect()
        # Set zoom.
        if System.is_windows():
            # view.setZoomFactor(1.8)
            screen_height = screen_rect.height()
            base_height = 1080
            if screen_height > base_height:
                scale = (screen_height / base_height) * 0.9
                print("[Qt/window] scale", scale)
                self.setZoomFactor(scale)
Esempio n. 14
0
class MotionGraphVisualizerWidget(QWebEngineView):
    def __init__(self, parent=None):
        QWebEngineView.__init__(self, parent)
        self.setAttribute(Qt.WA_TranslucentBackground)
        self.setWindowFlags(Qt.FramelessWindowHint)

        self._page = GraphVisualizePage()
        self.setPage(self._page)

        self._channel = QWebChannel(self._page)
        self._channel.registerObject('page', self._page)
        self._page.setWebChannel(self._channel)

        self.loadFinished.connect(self._on_load_finished)
        self.setUrl(QUrl('qrc:/graph_visualizer/test.html'))

    def _on_load_finished(self):
        self._page.init()

    def load_motion_graph(self, motion_graph):
        self._page.load_motion_graph(motion_graph)

    @property
    def visualizer(self):
        return self._page
Esempio n. 15
0
    class WebChannelServer:
        def __init__(self):
            self._server = None
            self._clientWrapper = None
            self._channel = None
            self._bridge = None

        def setupWebChannel(self,
                            bridge=None,
                            bridgename="bridge",
                            port=12345):
            # setup the QWebSocketServer
            self._server = QWebSocketServer("Grumble webchannel",
                                            QWebSocketServer.NonSecureMode)
            if not self._server.listen(QHostAddress.LocalHost, port):
                qFatal("Failed to open web socket server.")
                return False

            # wrap WebSocket clients in QWebChannelAbstractTransport objects
            self._clientWrapper = WebSocketClientWrapper(self._server, self)

            # setup the channel
            self._channel = QWebChannel()
            self._clientWrapper.clientConnected.connect(
                self._channel.connectTo)

            # Publish the bridge object to the QWebChannel
            self._bridge = bridge
            if bridge is not None:
                self._channel.registerObject(bridgename, bridge)
class MainWindow(QWidget):
    def __init__(self):
        super(MainWindow, self).__init__()
        self.setWindowTitle('js <-> py')
        self.resize(330, 220)
        self.layout = QVBoxLayout()

        self.btn_test = QPushButton('test')
        # self.btn_test.clicked.connect(self.test)

        # 创建一个 QWebEngineView 对象
        self.web = QWebEngineView()
        file_path = QFileInfo("./test.html").absoluteFilePath()
        self.web.load(QUrl(file_path))

        # 把QWebView和button加载到layout布局中
        self.layout.addWidget(self.web)
        self.layout.addWidget(self.btn_test)
        self.setLayout(self.layout)

        self.channel = QWebChannel()
        self.shared = Myshared()
        self.set_channel()

    def set_channel(self):
        self.channel.registerObject("conn_shared", self.shared)
        self.web.page().setWebChannel(self.channel)

    def __del__(self):
        '''
        删除相关对象
        '''
        self.web.deleteLater()
Esempio n. 17
0
 def set_action(self, action, d):
     from PyQt5.QtWebChannel import QWebChannel
     self.page().setWebChannel(None)
     channel = QWebChannel(self.page())
     channel.registerObject(action.action(), action)
     self.page().setWebChannel(channel)
     action.callback = d
class MapWindow(QDialog):
    rectSignal = pyqtSignal(str)

    def __init__(self, parent=None):
        super(MapWindow, self).__init__(parent)
        self.label = QLabel(self)
        self.setWindowTitle(APP_NAME)
        self.resize(QSize(1400, 900))
        layout = QVBoxLayout(self)
        self.webview = QWebEngineView(self)
        self.channel = QWebChannel(self)
        # 把自身对象传递进去
        self.channel.registerObject('Bridge', self)
        # 设置交互接口
        self.webview.page().setWebChannel(self.channel)
        layout.addWidget(self.webview)
        self.webview.load(
            QUrl.fromLocalFile(APP_HtmlPath + os.sep + "choose_rect.html"))
        self.show()

    @pyqtSlot(str)
    @pyqtSlot(QUrl)
    def getRect(self, rect):
        leftbottom = rect.split(' ')[0]
        righttop = rect.split(' ')[1]
        self.rectSignal.emit(rect)  # 发射信号
        self.close()
Esempio n. 19
0
    def __init__(self, is_static, parent):
        super().__init__(parent)

        self.language = Settings()['locale']

        self.is_static = is_static
        self.lat = None
        self.lng = None
        self.points = []
        self.activated = False
        self.initialized = False
        self.loadFinished.connect(self.onLoadFinished)

        if importedQtWebEngine:
            self.setPage(WebEnginePage(self))

            channel = QWebChannel(self.page())
            channel.registerObject("qtWidget", self)
            self.page().setWebChannel(channel)
        else:
            self.page().setLinkDelegationPolicy(QWebPage.DelegateAllLinks)
            self.page().linkClicked.connect(self.linkClicked)

            self.page().mainFrame().addToJavaScriptWindowObject(
                "qtWidget", self)

        self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
Esempio n. 20
0
class View(QWebEngineView):
    QT_SCRIPT_URL = QUrl.fromLocalFile(os.path.join(os.path.dirname(__file__), "qt.js"))

    def __init__(self, data):
        super().__init__()
        self.renderer = theria.Renderer(
            data,
            header_scripts=["qrc:///qtwebchannel/qwebchannel.js"],
            body_scripts=[self.QT_SCRIPT_URL.toString()],
        )

        # setup page
        self.web_page = CustomPage()
        self.web_page.setUrl(QUrl.fromLocalFile(html_to_file_path(self.renderer.html)))
        self.setPage(self.web_page)

        # setup channel
        self.interface = Interface(self.renderer)
        self.channel = QWebChannel()
        self.channel.registerObject("backend", self.interface)
        self.page().setWebChannel(self.channel)
        self.interface.updated.connect(self.reload_page)
        self.interface.dialog.connect(self.load_fancy_box)

        # setup fancy box
        self.fancy_box = None

    def set_view(self, app: QApplication):
        # Set geometry.
        screen_rect = app.desktop().screen().rect()
        screen_center = screen_rect.center()
        width = (7 * screen_rect.width()) // 10
        height = (2 * screen_rect.height()) // 3
        x = screen_center.x() - width // 2
        y = screen_center.y() - height // 2
        print(
            f"[Qt/window] size {width} x {height}, position ({x}; {y})", file=sys.stderr
        )
        self.setGeometry(x, y, width, height)
        # Set zoom.
        if System.is_windows():
            # view.setZoomFactor(1.8)
            screen_height = screen_rect.height()
            base_height = 1080
            if screen_height > base_height:
                scale = (screen_height / base_height) * 0.9
                print("[Qt/window] scale", scale)
                self.setZoomFactor(scale)

    def reload_page(self):
        self.web_page.setUrl(QUrl.fromLocalFile(html_to_file_path(self.renderer.html)))

    def load_fancy_box(self, html):
        print("[fancybox]")
        print(html)
        if self.fancy_box:
            del self.fancy_box
        self.fancy_box = FancyBox(self.interface, title="Form", html=html)
        self.fancy_box.show()
Esempio n. 21
0
    def clone(self):
        nc = QWebChannel(self)

        objects = self.registeredObjects()
        for objName, obj in objects:
            log.debug(f'{self}: cloning object with ID: {objName}')
            nc.registerObject(objName, obj)

        return nc
Esempio n. 22
0
 def register_delivery_channel(self):
     """主窗口与交割服务界面的js交互通道"""
     web_channel = DeliveryChannel()  # 交互信号对象
     web_channel.hasReceivedUserToken.connect(self.send_token_timer.stop)
     web_channel.moreCommunicationSig.connect(self.more_communication)  # 更多交流讨论
     web_channel.linkUsPageSig.connect(self.to_link_us_page)
     channel_qt_obj = QWebChannel(self.web_show.page())
     self.web_show.page().setWebChannel(channel_qt_obj)
     channel_qt_obj.registerObject("GUIMsgChannel", web_channel)  # 注册信号对象
Esempio n. 23
0
 def init_channel(self):
     """
     为webview绑定交互对象
     """
     self.interact_obj = TInteractObj(self)
     self.interact_obj.receive_str_from_js_callback = self.receive_webdata
     channel = QWebChannel(self.view.page())
     # interact_obj 为交互对象的名字,js中使用
     channel.registerObject("interact_obj", self.interact_obj)
     self.view.page().setWebChannel(channel)
Esempio n. 24
0
 def __init__(self, web_channel, file_url, *args, **kwargs):
     super(ChartContainWidget, self).__init__(*args, **kwargs)
     self.setAttribute(Qt.WA_DeleteOnClose, True)
     # 加载图形容器
     self.page().load(QUrl(file_url))  # 加载页面
     # 设置与页面信息交互的通道
     channel_qt_obj = QWebChannel(self.page())  # 实例化qt信道对象,必须传入页面参数
     self.contact_channel = web_channel  # 页面信息交互通道
     self.page().setWebChannel(channel_qt_obj)
     channel_qt_obj.registerObject("pageContactChannel", self.contact_channel)  # 信道对象注册信道,只能注册一个
Esempio n. 25
0
    def setup_markdown_editor(self):
        self.renderers = {}
        self.fallback_renderer = PlainRenderer()
        self.add_renderer(self.fallback_renderer)
        self.add_renderer(MarkdownRenderer())
        self.add_renderer(ReSTRenderer())

        # Set up Markdown editor
        self.setup_lexer()
        self.setup_scintilla(self.ui.markdownEditor)

        # Set up our custom page to open external links in a browser
        page = CustomWebPage(self)
        self.ui.markdownPreview.setPage(page)
        QWebEngineSettings.globalSettings().setAttribute(
            QWebEngineSettings.FocusOnNavigationEnabled, False)
        # self.ui.markdownPreview.

        # Set up the web channel to intercept clicks on links
        channel = QWebChannel(self)
        self.channel_proxy = WebChannelProxy(self)
        channel.registerObject("proxy", self.channel_proxy)
        page.setWebChannel(channel)

        self.channel_proxy.link_clicked.connect(self.link_clicked)

        # Set up HTML preview
        self.ui.htmlPreview.setLexer(QsciLexerHTML())

        # Connect signals
        self.ui.actionUndo.triggered.connect(self.undo)
        self.ui.actionRedo.triggered.connect(self.redo)

        self.ui.actionSave.triggered.connect(self.save_article)
        self.ui.actionCommit.triggered.connect(self.commit_article)
        self.ui.actionEdit.toggled.connect(self.edit_toggled)
        self.ui.actionFullscreen.triggered.connect(self.show_fullscreen_editor)
        self.ui.actionAutoLink.triggered.connect(self.auto_link_word)
        self.ui.actionAutoLinkAll.triggered.connect(self.auto_link_all)

        self.ui.markdownEditor.installEventFilter(self)

        # Load Github style
        style_file = QFile(':/styles/github.css')
        style_file.open(QIODevice.ReadOnly)
        self.style = QTextStream(style_file).readAll()
        style_file.close()

        self.current_article = None
        self.ui.markdownEditor.hide()

        self.ui.actionUndo.setEnabled(False)
        self.ui.actionRedo.setEnabled(False)

        self.update_toolbar()
Esempio n. 26
0
    def __init__(self, parent=None, bridge=None, *, debug=False, **kwargs):
        debug = debug or _ORANGE_DEBUG
        if debug:
            port = os.environ.setdefault('QTWEBENGINE_REMOTE_DEBUGGING',
                                         '12088')
            warnings.warn(
                'To debug QWebEngineView, set environment variable '
                'QTWEBENGINE_REMOTE_DEBUGGING={port} and then visit '
                'http://127.0.0.1:{port}/ in a Chromium-based browser. '
                'See https://doc.qt.io/qt-5/qtwebengine-debugging.html '
                'This has also been done for you.'.format(port=port))
        super().__init__(
            parent,
            sizeHint=QSize(500, 400),
            sizePolicy=QSizePolicy(QSizePolicy.Expanding,
                                   QSizePolicy.Expanding),
            **kwargs)
        self.bridge = bridge
        self.debug = debug
        with open(_WEBVIEW_HELPERS, encoding="utf-8") as f:
            self._onloadJS(
                f.read(),
                name='webview_helpers',
                injection_point=QWebEngineScript.DocumentCreation)

        qtwebchannel_js = QFile("://qtwebchannel/qwebchannel.js")
        if qtwebchannel_js.open(QFile.ReadOnly):
            source = bytes(qtwebchannel_js.readAll()).decode("utf-8")
            with open(_WEBENGINE_INIT_WEBCHANNEL, encoding="utf-8") as f:
                init_webchannel_src = f.read()
            self._onloadJS(
                source + init_webchannel_src % dict(
                    exposeObject_prefix=self._EXPOSED_OBJ_PREFIX),
                name='webchannel_init',
                injection_point=QWebEngineScript.DocumentCreation)
        else:
            warnings.warn("://qtwebchannel/qwebchannel.js is not readable.",
                          RuntimeWarning)

        self._onloadJS(
            ';window.__load_finished = true;',
            name='load_finished',
            injection_point=QWebEngineScript.DocumentReady)

        channel = QWebChannel(self)
        if bridge is not None:
            if isinstance(bridge, QWidget):
                print(
                    "Don't expose QWidgets in WebView. Construct minimal QObjects instead."
                )
            channel.registerObject("pybridge", bridge)

        channel.registerObject('__bridge', _QWidgetJavaScriptWrapper(self))

        self.page().setWebChannel(channel)
Esempio n. 27
0
 def register_webchannel(self, page):
     # Register one bridge object per profile so it can use the
     # seperate cookieJar and User-Agent for xhrs.
     if page.profile() not in self.channels:
         new_wc = QWebChannel()
         self.channels[page.profile()] = new_wc
         new_bridge = GreasemonkeyBridge(new_wc, page.profile())
         self.bridges[page.profile()] = new_bridge
         new_wc.registerObject('qute', new_bridge)
     wc = self.channels[page.profile()]
     page.setWebChannel(wc)  #worldId is second param
Esempio n. 28
0
 def __init__(self, parent):
     super(View, self).__init__(parent)
     self._highlightFormat = QTextCharFormat()
     self.jslink = JSLink(self)
     channel = QWebChannel(self)
     channel.registerObject("pyLinks", self.jslink)
     self.page().setWebChannel(channel)
     self.loadFinished.connect(self.svgLoaded)
     self.mainwindow().aboutToClose.connect(self.cleanupForClose)
     app.settingsChanged.connect(self.readSettings)
     self.readSettings()
     self.load(self.defaulturl)
Esempio n. 29
0
    def execute_in_view(self, view: QWebEngineView):
        view.load(Core.QUrl(self.url))
        page = view.page()

        channel = QWebChannel(view)
        page.setWebChannel(channel)
        channel.registerObject('app_receiver', self.receiver)

        script = QWebEngineScript()
        script.setSourceCode(_get_jsinject_dep_code() + self.jscode)
        script.setWorldId(QWebEngineScript.MainWorld)
        page.profile().scripts().insert(script)
Esempio n. 30
0
 def initBrowser(self):
     view = QWebEngineView()
     channel = QWebChannel()
     handler = CallHandler(self)
     channel.registerObject('pyjs', handler)
     view.page().setWebChannel(channel)
     url_string = "https://bing.com"
     view.load(QUrl(url_string))
     view.show()
     self.view = view
     self.channel = channel
     self.handler = handler
Esempio n. 31
0
    def __init__(self, qMainWindow=None, qtDesigner=None):
        if self.__class__.logger is None:
            self.__class__.logger = logging.getLogger('root')

        if self.__class__.loader is None:
            self.__class__.loader = genshi.template.TemplateLoader(
                os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), 'www')),
                auto_reload=True
            )

        if self.__class__.qMainWindow is None and qMainWindow is not None:
            self.__class__.qMainWindow = qMainWindow

        if self.__class__.qtDesigner is None and qtDesigner is not None:
            self.__class__.qtDesigner = qtDesigner

            class WebEnginePage(QWebEnginePage):
                updated = pyqtSignal(str, float, str)

                def javaScriptConsoleMessage(self, level, msg, line, source):
                    harkfm.Interface.logger.debug('%s:%s  %s', source, line, msg)

                def acceptNavigationRequest(self, url, type, is_main_frame):
                    if type == QWebEnginePage.NavigationTypeLinkClicked:
                        webbrowser.open(url.toString())
                        return False
                    return True

                # Turn a jQuery.serialize() query into a dict
                def _deserialize(self, query):
                    form = {}
                    query = query.split('&')
                    for val in query:
                        val = val.split('=')
                        form[urllib.parse.unquote(val[0])] = urllib.parse.unquote(val[1])
                    return form

                @pyqtSlot()
                def update(self):
                    if engine.current is not None:
                        self.updated.emit(engine.current.elapsed, engine.current.percent, engine.current.remaining)

                @pyqtSlot()
                def queued(self):
                    queued = 0
                    if engine.current is not None:
                        queued = engine.current.queued
                    self.updated.emit(sys._getframe().f_code.co_name, queued)

                @pyqtSlot()
                def love(self):
                    engine.current.love()
                    interface = harkfm.Interface()
                    interface.index()

                @pyqtSlot()
                def unlove(self):
                    engine.current.unlove()
                    interface = harkfm.Interface()
                    interface.index()

                @pyqtSlot(str)
                def login(self, query):
                    form = self._deserialize(query)
                    storage = harkfm.Storage()
                    api_key = harkfm.Engine.config['apis']['last.fm']['key']
                    api_secret = harkfm.Engine.config['apis']['last.fm']['secret']
                    try:
                        network = pylast.get_lastfm_network(api_key, api_secret)
                        session_key = pylast.SessionKeyGenerator(network).get_session_key(form['username'],  pylast.md5(form['password']))
                        storage.config_set('apis/last.fm/session_key', session_key)
                        interface = harkfm.Interface()
                        interface.index()
                    except Exception as e:
                        harkfm.Interface.logger.error('%s  %s', type(e), e)

                @pyqtSlot()
                def logout(self):
                    engine = harkfm.Engine()
                    engine.lfm_logout()

                @pyqtSlot(str)
                def save_settings(self, form):
                    form = json.loads(form)
                    storage = harkfm.Storage()
                    for key in form:
                        if type(form[key]) is str and form[key].isdigit():
                            form[key] = int(form[key])
                        storage.config_set('settings/' + key, form[key])
                    interface = harkfm.Interface()
                    interface.index()

            # Set custom page
            page = WebEnginePage(self.QWebEngineView)
            page.profile().setHttpCacheType(QWebEngineProfile.NoCache)
            self.QWebEngineView.setPage(page)

            # Set custom channel
            channel = QWebChannel(page)
            channel.registerObject('py', page)
            page.setWebChannel(channel)

            engine = harkfm.Engine()  # init harkfm.Engine
            lfm_network = engine.lfm_login()
            if lfm_network is None:
                self.login()
            else:
                self.index()
Esempio n. 32
0
class BrowserView(QMainWindow):
    instances = {}
    inspector_port = None  # The localhost port at which the Remote debugger listens

    create_window_trigger = QtCore.pyqtSignal(object)
    set_title_trigger = QtCore.pyqtSignal(str)
    load_url_trigger = QtCore.pyqtSignal(str)
    html_trigger = QtCore.pyqtSignal(str, str)
    dialog_trigger = QtCore.pyqtSignal(int, str, bool, str, str)
    destroy_trigger = QtCore.pyqtSignal()
    fullscreen_trigger = QtCore.pyqtSignal()
    window_size_trigger = QtCore.pyqtSignal(int, int)
    current_url_trigger = QtCore.pyqtSignal()
    evaluate_js_trigger = QtCore.pyqtSignal(str, str)

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

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

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

            return _js_bridge_call(self.parent_uid, self.api, func_name, param)

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

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

        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)
                inspector = BrowserView(uid, title, url, 700, 500, True, False, (300, 200),
                                        False, '#fff', False, None, True, self.parent().webview_ready)
                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):
            if self.parent().frameless and int(event.buttons()) == 1:  # left button is pressed
                self.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)
            self.nav_handler = BrowserView.NavigationHandler(self) if is_webengine else None

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

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

    def __init__(self, uid, title, url, width, height, resizable, fullscreen,
                 min_size, confirm_quit, background_color, debug, js_api, text_select, frameless, webview_ready):
        super(BrowserView, self).__init__()
        BrowserView.instances[uid] = self
        self.uid = uid

        self.js_bridge = BrowserView.JSBridge()
        self.js_bridge.api = js_api
        self.js_bridge.parent_uid = self.uid

        self.is_fullscreen = False
        self.confirm_quit = confirm_quit
        self.text_select = text_select

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

        self.load_event = Event()
        self.webview_ready = webview_ready

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

        self.resize(width, height)
        self.title = title
        self.setWindowTitle(title)

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

        if not resizable:
            self.setFixedSize(width, height)

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

        self.frameless = frameless
        if frameless:
            self.setWindowFlags(QtCore.Qt.Window | QtCore.Qt.FramelessWindowHint)

        self.view = BrowserView.WebView(self)

        if debug and is_webengine:
            # Initialise Remote debugging (need to be done only once)
            if not BrowserView.inspector_port:
                BrowserView.inspector_port = BrowserView._get_free_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))

        if url is not None:
            self.view.setUrl(QtCore.QUrl(url))
        else:
            self.load_event.set()

        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.fullscreen_trigger.connect(self.on_fullscreen)
        self.window_size_trigger.connect(self.on_window_size)
        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)

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

        self.view.page().loadFinished.connect(self.on_load_finished)

        if fullscreen:
            self.toggle_fullscreen()

        self.move(QApplication.desktop().availableGeometry().center() - self.rect().center())
        self.activateWindow()
        self.raise_()
        webview_ready.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, localization['linux.openFolder'], options=QFileDialog.ShowDirsOnly)
        elif dialog_type == OPEN_DIALOG:
            if allow_multiple:
                self._file_name = QFileDialog.getOpenFileNames(self, localization['linux.openFiles'], directory, file_filter)
            else:
                self._file_name = QFileDialog.getOpenFileName(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, 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 == '' 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 closeEvent(self, event):
        if self.confirm_quit:
            reply = QMessageBox.question(self, self.title, localization['global.quitConfirmation'],
                                         QMessageBox.Yes, QMessageBox.No)

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

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

        try:    # Close inspector if open
            BrowserView.instances[self.uid + '-inspector'].close()
            del BrowserView.instances[self.uid + '-inspector']
        except KeyError:
            pass

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

    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):
        self.setFixedSize(width, height)

    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
            result = self.view.page().mainFrame().evaluateJavaScript(script)
            return_result(result)
        except AttributeError:
            self.view.page().runJavaScript(script, return_result)


    def on_load_finished(self):
        if self.js_bridge.api:
            self._set_js_api()
        else:
            self.load_event.set()

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

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

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

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

        return self._current_url

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

    def load_html(self, content, base_uri):
        self.load_event.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 destroy_(self):
        self.destroy_trigger.emit()

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

    def set_window_size(self, width, height):
        self.window_size_trigger.emit(width, height)

    def evaluate_js(self, script):
        self.load_event.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)

        script = parse_api_js(self.js_bridge.api)

        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:    # < QT 5.6
            self.view.page().mainFrame().evaluateJavaScript(script)
        except AttributeError:
            self.view.page().runJavaScript(script)
        self.load_event.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
    # A simple function to obtain an unused localhost port from the os return it
    def _get_free_port():
        s = socket()
        s.bind(('localhost', 0))
        port = str(s.getsockname()[1])
        s.close()
        return port

    @staticmethod
    # Receive func from subthread and execute it on the main thread
    def on_create_window(func):
        func()
Esempio n. 33
0
 def set_action(self, action, d):
     self.page().setWebChannel(None)
     channel = QWebChannel(self.page())
     channel.registerObject(action.action(), action)
     self.page().setWebChannel(channel)
     action.callback = d
Esempio n. 34
0
 def _OnUrlChanged(self, url):
     webchannel = QWebChannel(self.page())
     self.page().setWebChannel(webchannel)
     webchannel.registerObject("kiwoom", self._kiwoom)
Esempio n. 35
0
    # jsFile = constants.QTWEBCHANNELJS_FILE
    # with open(jsFile, encoding="UTF-8") as file:
    #     js = file.read()
    # script = QWebEngineScript();
    # script.setSourceCode(js)
    # script.setName('qwebchannel.js')
    # script.setInjectionPoint(QWebEngineScript.DocumentCreation)
    # script.setWorldId(QWebEngineScript.MainWorld)
    # script.setRunsOnSubFrames(False)
    #
    # mainwindow.view.page.profile().scripts().insert(script)

    channel = QWebChannel()
    myobject=myObject()
    channel.registerObject("xdpy",myobject)
    mainwindow.view.page.setWebChannel(channel)

    #
    # jsFile = constants.QTWEBCHANNELJS_FILE
    # with open(jsFile, encoding="UTF-8") as file:
    #     js = file.read()
    # mainwindow.injectJS(js, 'qwebchannel.js')


    js='\nnew QWebChannel(qt.webChannelTransport, function(channel){alert("正在调用回调函数");window.xdpy=channel.objects.xdpy;xdpy.test();});\n'
    #mainwindow.view.page.runJavaScript(js,mainwindow.js_callback)
    # mainwindow.view.page.runJavaScript('window.channel="AAAA"', mainwindow.js_callback)
    mainwindow.view.page.runJavaScript(js,mainwindow.js_callback)
    #mainwindow.view.page.runJavaScript("window.xdpy.test()",mainwindow.js_callback)
    app.exec();
Esempio n. 36
0
class PreviewSync(QObject):
    """This class synchronizes the contents of the web and text views and aligns
       them vertically.
    """
    textToPreviewSynced = pyqtSignal()

    # Setup / cleanup
    # ===============
    def __init__(self,
      # The preview dock involved in synchronization.
      previewDock):

        QObject.__init__(self)
        # Only set up sync if fuzzy matching is available.
        if not findApproxTextInTarget:
            return

        # Gather into one variable all the JavaScript needed for PreviewSync.
        self._jsPreviewSync = self._jsOnClick + self._jsWebCursorCoords

        self._dock = previewDock
        self._callbackManager = CallbackManager()
        self._initPreviewToTextSync()
        self._initTextToPreviewSync()
        self._unitTest = False

    def terminate(self):
        # Uninstall the text-to-web sync only if it was installed in the first
        # place (it depends on TRE).
        if findApproxTextInTarget:
            self._cursorMovementTimer.stop()
            # Shut down the background sync. If a sync was already in progress,
            # then discard its output.
            self._runLatest.future.cancel(True)
            self._runLatest.terminate()
            # End all callbacks.
            self._callbackManager.skipAllCallbacks()
            self._callbackManager.waitForAllCallbacks()
            # Bug: DON'T de-register the QWebChannel. This casues the error message ``onmessage is not a callable property of qt.webChannelTransport. Some things might not work as expected.`` to be displayed. I see this in https://code.woboq.org/qt5/qtwebengine/src/core/renderer/web_channel_ipc_transport.cpp.html#221; I assume it's a result of attempting to send a signal to the web page, where the web page doesn't have qwebchannel.js running.
            #self.channel.deregisterObject(self)
            # Delete it. Note that the channel is `NOT owned by the page <http://doc.qt.io/qt-5/qwebenginepage.html#setWebChannel>`.
            sip.delete(self.channel)
            # Disconnect all signals.
            sip.delete(self)
    #
    # Vertical synchronization
    ##========================
    # These routines perform vertical synchronization.
    #
    # This function computes the distance, in pixels, measured from the target
    # cursor location to the source cursor location, as shown in part (a) of the
    # figure below: delta = source - target, so that source = target + delta.
    # This distance is limited by a constraint: the resulting target cursor
    # location must be kept a padding pixels amount away from the boundaries of
    # the target widget. Part (b) of the figure shows show this distance is
    # limited when the source lies above the target widget; the same constraint
    # applies when the source lies below the target widget.
    #
    # .. image:: sync_delta.png
    #
    # Ideally, this would instead operate on the baseline of the text, rather
    # than the bottom, but getting this is harder.
    def _alignScrollAmount(self,
      # The top (y) coordinate of the source widget in a global coordinate frame,
      # such as screen coordinates. In pixels.
      sourceGlobalTop,
      # The bottom coordinate of the cursor in the source widget, measured from the
      # top of the widget, NOT the top of the viewport. In pixels.
      sourceCursorBottom,

      # The top (y) coordinate of the target widget in a global coordinate frame,
      # such as screen coordinates. In pixels.
      targetGlobalTop,
      # The bottom coordinate of the cursor in the target widget, measured from the
      # top of the widget, NOT the top of the viewport. In pixels.
      targetCursorBottom,
      # The height of the target widget. In pixels.
      targetHeight,
      # The height of the cursor in the target widget. In pixels.
      targetCursorHeight,
      # The minimum allowable distance between target + delta and the top or
      # bottom of the target widget.
      padding):

        # Compute the raw delta between the source and target widgets.
        #
        # .. image:: dtop_initial_diagram.png
        delta = (
          # Global coords of the source cursor top.
          (sourceGlobalTop + sourceCursorBottom) -
          # Global coords of the target cursor top. The difference
          # gives the number of pixels separating them.
          (targetGlobalTop + targetCursorBottom) );

        # Constrain the resulting delta so that the stays padding pixels from
        # the top of the target widget.
        delta = max(-targetCursorBottom + targetCursorHeight + padding, delta)
        # Likewise, constrain the bottom.
        delta = min(targetHeight - targetCursorBottom - padding, delta)

        return delta

    # This string contains JavaScript code to determine the coordinates and height of the
    # anchor of the selection in the web view.
    _jsWebCursorCoords = (
        # This function returns the [top, left] position in pixels of ``obj``
        # relative to the screen, not to the viewport. This introduces one
        # potential problem: if obj is not visible when this is called, it
        # returns coordinates outside the screen (such that top or left is
        # negative or greater than the screen's height or width.
        #
        # It was slightly modified from http://www.quirksmode.org/js/findpos.html,
        #  which reproduces jQuery's offset method (https://api.jquery.com/offset/).
        'function findPos(obj) {'
            'var curLeft = 0;'
            'var curTop = 0;'
             # element.offsetLeft and element.offsetTop measure relative to
             # the object's parent. Walk the tree of parents, summing each
             # offset to determine the offset from the origin of the web page.
            'do {'
                'curLeft += obj.offsetLeft;'
                'curTop += obj.offsetTop;'
            '} while (obj = obj.offsetParent);'
            # See `element.getBoundingClientRect
            # <https://developer.mozilla.org/en-US/docs/Web/API/element.getBoundingClientRect>`_
            # for converting viewport coords to screen coords.
            'return [curLeft - window.scrollX, curTop - window.scrollY];'
        '}' +

        # This function returns [top, left, width], of the current
        # selection, where:
        #
        #   top, left - coordinates of the anchor of the
        #     selection relative to the screen, in pixels.
        #
        #   height - height at the beginning of the selection, in pixels.
        #
        # Adapted from http://stackoverflow.com/questions/2031518/javascript-selection-range-coordinates.
        # Changes:
        #
        # - jQuery usage eliminated for all but debug prints.
        # - The original code used ``range.endOffset`` instead of
        #   ``selection.focusOffset``. This caused occasional errors when
        #   dragging selections.
        'function selectionAnchorCoords() {'
            # Using ``window.getSelection()``
            # Make sure a `selection <https://developer.mozilla.org/en-US/docs/Web/API/Selection>`_ exists.
            'var selection = window.getSelection();'
            'if (selection.rangeCount == 0) return 0;'

            # The selection can contain not just a point (from a
            # single mouse click) but a range (from a mouse drag or
            # shift+arrow keys).
            # We're looking for the coordinates of the focus node
            # (the place where the mouse ends up after making the selection).
            # However, the range returned by ``selection.getRangeAt(0)``
            # begins earlier in the document and ends later, regardless
            # how the mouse was dragged. So, create a new range containing
            # just the point at the focus node, so we actually get
            # a range pointing to where the mouse is.
            # Ref: `focus <https://developer.mozilla.org/en-US/docs/Web/API/Selection.focusNode>`_ of the selection.
            # `Range <https://developer.mozilla.org/en-US/docs/Web/API/range>`_
            'var rangeAtFocus = document.createRange();'
            'rangeAtFocus.setStart(selection.focusNode, selection.focusOffset);'

            # Insert a measurable element (a span) at the selection's
            # focus.
            'var span = document.createElement("span");'
            'rangeAtFocus.insertNode(span);'

            # Measure coordinates at this span, then remove it.
            'var [left, top] = findPos(span);'
            'var height = span.offsetHeight;'
            'span.remove();'

            'return [left, top, height];'
        '}'

        # Clear the current selection, if it exists.
        'function clearSelection() {'
            'if (window.getSelection()) {'
                'window.getSelection().empty();'
            '}'
        '}'

        # Given text to find, place a highlight on the last line containing the
        # text.
        'function highlightFind('
          # The text to find, typically consisting of all text in the web page
          # from its beginning to the point to be found.
          'txt) {'

            # Clear the current selection, so that a find will start at the
            # beginning of the page.
            'clearSelection();'
            # Find or create a ``div`` used as a highlighter.
            'var highlighter = getHighlight();'
            'if (!highlighter) {'
                'highlighter = document.createElement("div");'
                'document.body.appendChild(highlighter);'
                'highlighter.style.zIndex = 100;'
                'highlighter.style.width = "100%";'
                'highlighter.style.position = "absolute";'
                # Pass any click on the highlight on to the webpage underneath.
                # See https://developer.mozilla.org/en-US/docs/Web/CSS/pointer-events.
                'highlighter.style.pointerEvents = "none";'
                'highlighter.style.backgroundColor = "rgba(255, 255, 0, 0.4)";'
                'highlighter.id = "highlighter";'
            '}'
            # See https://developer.mozilla.org/en-US/docs/Web/API/Window/find.
            ##                       aString, aCaseSensitive, aBackwards, aWrapAround, aWholeWord, aSearchInFrames, aShowDialog)
            'var found = window.find(txt,     true,           false,     false,        false,      true,            false);'
            # If the text was found, or the search string was empty, highlight a line.
            'if (found || txt === "") {'
                # Determine the coordiantes of the end of the selection.
                'var res = selectionAnchorCoords();'
                'if (res) {'
                    # Unpack the coordinates obtained.
                    'var [left, top, height] = res;'
                    # Position it based on the coordinates.
                    'highlighter.style.height = height + "px";'
                    'highlighter.style.top = (window.scrollY + top) + "px";'
                '}'
                'return true;'
            '}'
            # Remove the highlight if we can't find the text.
            'clearHighlight();'
            # Clear the selection, since we won't use it later.
            'clearSelection();'
            'return false;'
        '}'

        # Return the ``div`` used to produce a highlight, or None if it doesn't exist.
        'function getHighlight() {'
            'return document.getElementById("highlighter");'
        '}'

        # Delete the element used to produce a highlight.
        'function clearHighlight() {'
            'var highlighter = getHighlight();'
            'if (highlighter) {'
                'highlighter.remove();'
            '}'
        '}')

    # Scroll the web view to align its cursor with the qutepart cursor or vice
    # versa.
    def _scrollSync(self,
      # None to scroll the text view to the y coordinate of the web view's
      # cursor. True or False to do the opposite: scroll the web view so that
      # its cursor aligns vertically with the y coordinate of the text view. In
      # this case, True will use the tolerance to scroll only if the amount to
      # scroll exceeds that tolerance; False will scroll irregardless of the
      # tolerance.
      alreadyScrolling=None,
      # Ignored if ``alreadyScrolling == None``. Used as both a padding value and a
      # scroll tolerance, as described in alreadyScrolling.
      tolerance=50):

        # Per the `window geometry
        # <http://qt-project.org/doc/qt-4.8/application-windows.html#window-geometry>`_,
        # `geometry() <http://qt-project.org/doc/qt-4.8/qwidget.html#geometry-prop>`_
        # is relative to the parent frame. Then, use `mapToGlobal
        # <http://qt-project.org/doc/qt-4.8/qwidget.html#mapToGlobal>`_ to
        # put this in global coordinates. This works for `QWebEngineView
        # <http://doc.qt.io/qt-5/qwebengineview.html>`_, since it
        # inherits from QWidget.
        wv = self._dock._widget.webEngineView
        qp = core.workspace().currentDocument().qutepart
        qpGlobalTop = qp.mapToGlobal(qp.geometry().topLeft()).y()
        wvGlobalTop = wv.mapToGlobal(wv.geometry().topLeft()).y()

        # `qutepart.cursorRect()
        # <http://qt-project.org/doc/qt-4.8/qplaintextedit.html#cursorRect-2>`_
        # gives a value in viewport == widget coordinates. Use that directly.
        cr = qp.cursorRect()
        qpCursorHeight = cr.height()
        qpCursorBottom = cr.top() + qpCursorHeight

        # Widget height includes the scrollbars. Subtract that off to get a
        # viewable height for qutepart.
        qpHeight = qp.geometry().height()
        hsb = qp.horizontalScrollBar()
        # The scrollbar height is a constant, even if it's hidden. So, only
        # include it in calculations if it's visible.
        if hsb.isVisible():
            qpHeight -= qp.horizontalScrollBar().height()
        page = wv.page()
        wvHeight = wv.geometry().height()

        # JavaScript callback to determine the coordinates and height of the
        # anchor of the selection in the web view. It expects a 3-element tuple
        # of (left, top, height), or None if there was no selection, where:
        # top is the coordinate (in pixels) of the top of the selection, measured from the web page's origin;
        # left is the coordinate (in pixels) of the left of the selection, measured from the web page's origin.
        def callback(res):
            # See if a 3-element tuple is returned. Exit if the selection was empty.
            if not res:
                return

            _, wvCursorTop, wvCursorHeight = res
            wvCursorBottom = wvCursorTop + wvCursorHeight

            if alreadyScrolling is not None:
                deltaY = self._alignScrollAmount(qpGlobalTop, qpCursorBottom,
                  wvGlobalTop, wvCursorBottom, wvHeight, wvCursorHeight, tolerance)
                # Uncomment for helpful debug info.
                ##print(("qpGlobalTop = %d, qpCursorBottom = %d, qpHeight = %d, deltaY = %d, tol = %d\n" +
                ##  "  wvGlobalTop = %d, wvCursorBottom = %d, wvHeight = %d, wvCursorHeight = %d") %
                ##  (qpGlobalTop, qpCursorBottom, qpHeight, deltaY, tolerance,
                ##  wvGlobalTop, wvCursorBottom, wvHeight, wvCursorHeight))

                # Only scroll if we've outside the tolerance.
                if alreadyScrolling or (abs(deltaY) > tolerance):
                    # Note that scroll bars are backwards: to make the text go up, you must
                    # move the bars down (a positive delta) and vice versa. Hence, the
                    # subtration, rather than addition, below.
                    page.runJavaScript('window.scrollTo(0, window.scrollY - {});'.format(deltaY))
                # Clear the selection, whether we scrolled or not.
                self.clearSelection()
            else:
                deltaY = self._alignScrollAmount(wvGlobalTop, wvCursorBottom,
                  qpGlobalTop, qpCursorBottom, qpHeight, qpCursorHeight, 0)
                vsb = qp.verticalScrollBar()
                # The units for the vertical scroll bar is pixels, not lines. So, do
                # a kludgy conversion by assuming that all line heights are the
                # same.
                vsb.setValue(vsb.value() - round(deltaY/qpCursorHeight))

        self._dock._afterLoaded.afterLoaded(lambda: page.runJavaScript('selectionAnchorCoords();', QWebEngineScript.ApplicationWorld, self._callbackManager.callback(callback)))

    # Clear the current selection in the web view.
    def clearSelection(self):
        if not self._unitTest:
            self._dock._afterLoaded.afterLoaded(self._dock._widget.webEngineView.page().runJavaScript, 'clearSelection();', QWebEngineScript.ApplicationWorld)
    #
    #
    # Synchronizing between the text pane and the preview pane
    ##========================================================
    # A single click in the preview pane should move the text pane's cursor to the
    # corresponding location. Likewise, movement of the text pane's cursor should
    # select the corresponding text in the preview pane. To do so, an approximate
    # search for text surrounding the current cursor or click location perfomed on
    # text in the other pane provides the corresponding location in the other pane
    # to highlight.
    #
    # Bugs / to-do items
    ##------------------
    # #. I call ``toPlainText()`` several times. In the past, this was quite slow
    #    in a ``QTextEdit``. Check performance and possibly cache this value; it
    #    should be easy to update by adding a few lines to _setHtml().
    #
    # Preview-to-text sync
    ##--------------------
    # This functionaliy relies heavily on the Web to Qt bridge. Some helpful
    # references:
    #
    # * `The QtWebKit Bridge <http://qt-project.org/doc/qt-4.8/qtwebkit-bridge.html>`_
    #   gives a helpful overview.
    # * `QWebEngineView`_ is the top-level widget used to embed a Web page in a Qt
    #   application.
    #
    # For this sync, the first step is to find the single click's location in a
    # plain text rendering of the preview's web content. This is implemented in
    # JavaScript, which emits a Qt signal with the location on a click. A slot
    # connected to this signal then performs the approximate match and updates the
    # text pane's cursor. To do this:
    #
    # #. ``jsClick``, a PyQt signal with a single numeric argument (the index into
    #    a string containing the plain text rendering of the web page) is defined.
    #    This signal is `connected <onJavaScriptCleared.connect>`_ to the
    #    ``onWebviewClick`` slot.
    # #. The ``onJavaScriptCleared`` method inserts the JavaScript to listen for a
    #    click and then emit a signal giving the click's location.
    # #. The ``onWebviewClick`` method then performs the approximate match and
    #    updates the text pane's cursor location.
    # #. When a new web page is loaded, all JavaScript is lost and must be reinserted.
    #    The ``onJavaScriptCleared`` slot, connected to the
    #    ``javaScriptWindowObjectCleared`` signal, does this.
    #
    # The job of this JavaScript handler is to
    # translate a mouse click into an index into the text rendering of the
    # webpage. To do this, we must:
    #
    # #. Get the current selection made by the mouse click, which is typically
    #    an empty range. (I assume a click and drag will produce a non-empty
    #    range; however this code still works).
    # #. Extend a copy of this range so that it begins at the start of the
    #    webpage and, of course, ends at the character nearest the mouse
    #    click.
    # #. Get a string rendering of this range.
    # #. Emit a signal with the length of this string.
    #
    # Note: A JavaScript development environment with this code is available
    # at http://jsfiddle.net/hgDwx/110/.
    _jsOnClick = (

        # The `window.onclick
        # <https://developer.mozilla.org/en-US/docs/Web/API/Window.onclick>`_
        # event is "called when the user clicks the mouse button while the
        # cursor is in the window." Although the docs claim that "this event
        # is fired for any mouse button pressed", I found experimentally
        # that it on fires on a left-click release; middle and right clicks
        # had no effect.
        'function window_onclick() {'

            # Clear the current highlight -- it doesn't make sense to have other
            # text highlighted after a click.
            'clearHighlight();'

             # This performs step 1 above. In particular:
             #
             # - `window.getSelection <https://developer.mozilla.org/en-US/docs/Web/API/Window.getSelection>`_
             #   "returns a `Selection
             #   <https://developer.mozilla.org/en-US/docs/Web/API/Selection>`_
             #   object representing the range of text selected by the
             #   user." Since this is only called after a click, I assume
             #   the Selection object is non-null.
             # - The Selection.\ `getRangeAt <https://developer.mozilla.org/en-US/docs/Web/API/Selection.getRangeAt>`_
             #   method "returns a range object representing one of the
             #   ranges currently selected." Per the Selection `glossary
             #   <https://developer.mozilla.org/en-US/docs/Web/API/Selection#Glossary>`_,
             #   "A user will normally only select a single range at a
             #   time..." The index for retrieving a single-selection range
             #   is of course 0.
             # - "The `Range <https://developer.mozilla.org/en-US/docs/Web/API/range>`_
             #   interface represents a fragment of a document that can
             #   contain nodes and parts of text nodes in a given document."
             #   We clone it to avoid modifying the user's existing
             #   selection using `cloneRange
             #   <https://developer.mozilla.org/en-US/docs/Web/API/Range.cloneRange>`_.
            'var r = window.getSelection().getRangeAt(0).cloneRange();'

             # This performs step 2 above: the cloned range is now changed
             # to contain the web page from its beginning to the point where
             # the user clicked by calling `setStartBefore
             # <https://developer.mozilla.org/en-US/docs/Web/API/Range.setStartBefore>`_
             # on `document.body
             # <https://developer.mozilla.org/en-US/docs/Web/API/document.body>`_.
            'r.setStartBefore(document.body);'

             # Step 3:
             #
             # - `cloneContents <https://developer.mozilla.org/en-US/docs/Web/API/Range.cloneContents>`_
             #   "Returns a `DocumentFragment
             #   <https://developer.mozilla.org/en-US/docs/Web/API/DocumentFragment>`_
             #   copying the nodes of a Range."
             # - DocumentFragment's parent `Node <https://developer.mozilla.org/en-US/docs/Web/API/Node>`_
             #   provides a `textContent
             #   <https://developer.mozilla.org/en-US/docs/Web/API/Node.textContent>`_
             #   property which gives "a DOMString representing the textual
             #   content of an element and all its descendants." This therefore
             #   contains a text rendering of the webpage from the beginning of the
             #   page to the point where the user clicked.
             'var rStr = r.cloneContents().textContent.toString();'


             # Step 4: the length of the string gives the index of the click
             # into a string containing a text rendering of the webpage.
             # Call Python with the document's text and that index.
            'window.previewSync._onWebviewClick(document.body.textContent.toString(), rStr.length);'
        '}')

    _qtJsInit = (
        # _`Bug 1`: I can't seem to avoid the error message ``js: Uncaught TypeError: channel.execCallbacks[message.id] is not a function``. It seems like this occurs  when a new page is loaded, but the QWebChannel on the Python side sends a message intended for a previously-loaded page. Adding delays / waiting until all JS init finishes / wiating until the event queue is empty helps, but doesn't fix it. Even with these, enough busyness (constant CPU use), this still happends -- perhaps the Python/Qt network backend doesn't send these messages until the CPU is idle? As a workaround, don't define the channel until is needed, making it less likely this will happen.
        #
        # _`Bug 2`: Since ``qt`` may not be defined (Qt 5.7.0 doesn't provide the
        # ``qt`` object to JavaScript when loading per https://bugreports.qt.io/browse/QTBUG-53411),
        # wrap it in a try/except block.
        'function init_qwebchannel() {'
            # Switch event listeners, part 1/2 -- now that this init is done, don't call it again.
            'window.removeEventListener("click", init_qwebchannel);'
            'try {'
                'new QWebChannel(qt.webChannelTransport, function(channel) {'
                    # Save a reference to the previewSync object.
                    'window.previewSync = channel.objects.previewSync;'
                    # Switch event listeners, part 2/2 -- Invoke the usual onclick handler. This will only be run if the QWebChannel init succeeds.
                    'window.addEventListener("click", window_onclick);'
                    # Now that the QWebChannel is ready, use it to handle the click.
                    'window_onclick();'
                '});'
            '} catch (err) {'
                # Re-throw unrecognized errors. When ``qt`` isn't defined,
                # JavaScript reports ``js: Uncaught ReferenceError: qt is not
                # defined``; this works around `bug 2`_.
                'throw err;' #if (!(err instanceof ReferenceError)) throw err;'
            '}'
        '}'
        # Set up the sync system after a click. This works around `bug 1`_. See https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener.
        'window.addEventListener("click", init_qwebchannel);'
    )

    def _initPreviewToTextSync(self):
        """Initialize the system per items 1, 2, and 4 above."""
        # When a web page finishes loading, reinsert our JavaScript.
        page = self._dock._widget.webEngineView.page()

        # Insert our scripts into every loaded page.
        qwebchannel_js = QFile(':/qtwebchannel/qwebchannel.js')
        if not qwebchannel_js.open(QIODevice.ReadOnly):
            raise SystemExit(
                'Failed to load qwebchannel.js with error: %s' %
                qwebchannel_js.errorString())
        qwebchannel_js = bytes(qwebchannel_js.readAll()).decode('utf-8')

        # Set up the QWebChannel. See http://doc.qt.io/qt-5/qtwebchannel-javascript.html.
        # Run the script containing QWebChannel.js first.
        beforeScript = QWebEngineScript()
        beforeScript.setSourceCode(qwebchannel_js + self._jsPreviewSync + self._qtJsInit)
        beforeScript.setName('qwebchannel.js, previewSync')
        # Run this JavaScript separated from any JavaScript present in the loaded web page. This provides better security (rogue pages can't access the QWebChannel) and better isolation (handlers, etc. won't conflict, I hope).
        beforeScript.setWorldId(QWebEngineScript.ApplicationWorld)
        beforeScript.setInjectionPoint(QWebEngineScript.DocumentCreation)
        # Per `setWebChannel <http://doc.qt.io/qt-5/qwebenginepage.html#setWebChannel>`_, only one channel is allowed per page. So, don't run this on sub-frames, since it will attempt the creation of more channels for each subframe.
        beforeScript.setRunsOnSubFrames(False)
        page.scripts().insert(beforeScript)

        # Set up the web channel. See https://riverbankcomputing.com/pipermail/pyqt/2015-August/036346.html
        # and http://stackoverflow.com/questions/28565254/how-to-use-qt-webengine-and-qwebchannel.
        # For debug, ``set QTWEBENGINE_REMOTE_DEBUGGING=port`` then browse to
        # http://127.0.0.1:port, where port=60000 works for me. See https://riverbankcomputing.com/pipermail/pyqt/2015-August/036346.html.
        self.channel = QWebChannel(page)
        self.channel.registerObject("previewSync", self)
        # Expose the ``qt.webChannelTransport`` object in the world where these scripts live.
        page.setWebChannel(self.channel, QWebEngineScript.ApplicationWorld)

    @pyqtSlot(str, int)
    def _onWebviewClick(self, tc, webIndex):
        self._onWebviewClick_(tc, webIndex)
        # Get the qutepart text.
        qp = core.workspace().currentDocument().qutepart
        # Perform an approximate match between the clicked webpage text and the
        # qutepart text.
        textIndex = findApproxTextInTarget(tc, webIndex, qp.text)
        # Move the cursor to textIndex in qutepart, assuming corresponding text
        # was found.
        if textIndex >= 0:
            self._moveTextPaneToIndex(textIndex)

    # Used for testing -- this will be replaced by a mock. Does nothing.
    def _onWebviewClick_(self, tc, webIndex):
        pass

    def _moveTextPaneToIndex(self, textIndex, noWebSync=True):
        """Given an index into the text pane, move the cursor to that index.

        Params:

        - textIndex - The index into the text pane at which to place the cursor.
        - noWebSync - True to prevent the web-to-text sync from running as a
          result of calling this routine.
        """
        # Move the cursor to textIndex.
        qp = core.workspace().currentDocument().qutepart
        cursor = qp.textCursor()
        # Tell the text to preview sync to ignore this cursor position change.
        cursor.setPosition(textIndex, QtGui.QTextCursor.MoveAnchor)
        self._previewToTextSyncRunning = noWebSync
        qp.setTextCursor(cursor)
        self._previewToTextSyncRunning = False
        # Scroll the document to make sure the cursor is visible.
        qp.ensureCursorVisible()
        # Sync the cursors.
        self._scrollSync()
        # Focus on the editor so the cursor will be shown and ready for typing.
        core.workspace().focusCurrentDocument()

    # Text-to-preview sync
    ##--------------------
    # The opposite direction is easier, since all the work can be done in Python.
    # When the cursor moves in the text pane, find its matching location in the
    # preview pane using an approximate match. Select several characters before and
    # after the matching point to make the location more visible, since the preview
    # pane lacks a cursor. Specifically:
    #
    # #. initTextToPreviewSync sets up a timer and connects the _onCursorPositionChanged method.
    # #. _onCursorPositionChanged is called each time the cursor moves. It starts or
    #    resets a short timer. The timer's expiration calls syncTextToWeb.
    # #. syncTextToWeb performs the approximate match, then calls moveWebPaneToIndex
    #    to sync the web pane with the text pane.
    # #. moveWebToPane uses QWebFrame.find to search for the text under the anchor
    #    then select (or highlight) it.

    def _initTextToPreviewSync(self):
        """Called when constructing the PreviewDoc. It performs item 1 above."""
        # Create a timer which will sync the preview with the text cursor a
        # short time after cursor movement stops.
        self._cursorMovementTimer = QTimer()
        self._cursorMovementTimer.setInterval(300)
        self._cursorMovementTimer.timeout.connect(self.syncTextToPreview)
        # Restart this timer every time the cursor moves.
        core.workspace().cursorPositionChanged.connect(self._onCursorPositionChanged)
        # Set up a variable to tell us when the preview to text sync just fired,
        # disabling this sync. Otherwise, that sync would trigger this sync,
        # which is unnecessary.
        self._previewToTextSyncRunning = False
        # Run the approximate match in a separate thread. Cancel it if the
        # document changes.
        self._runLatest = RunLatest('QThread', self)
        self._runLatest.ac.defaultPriority = QThread.LowPriority
        core.workspace().currentDocumentChanged.connect(self._onDocumentChanged)

    def _onDocumentChanged(self, old, new):
        self._runLatest.future.cancel(True)
        self._callbackManager.skipAllCallbacks()
        self._cursorMovementTimer.stop()

    def _onCursorPositionChanged(self):
        """Called when the cursor position in the text pane changes. It (re)schedules
        a text to web sync per item 2 above. Note that the signal connected to
        this slot must be updated when the current document changes, since we only
        want cursor movement notification from the active text document. This is
        handled in _onDocumentChanged.
        """
        # Ignore this callback if a preview to text sync caused it or if the
        # preview dock is closed.
        if not self._previewToTextSyncRunning and self._dock.isVisible():
            self._cursorMovementTimer.stop()
            self._cursorMovementTimer.start()

    def syncTextToPreview(self):
        """When the timer above expires, this is called to sync text to preview
        per item 3 above. It can also be called when a sync is needed (when
        switching windows, for example).
        """
        # Only run this if we TRE is installed.
        if not findApproxTextInTarget:
            return
        # Stop the timer; the next cursor movement will restart it.
        self._cursorMovementTimer.stop()
        # Get a plain text rendering of the web view. Continue execution in a callback.
        qp = core.workspace().currentDocument().qutepart
        qp_text = qp.text
        self._dock._widget.webEngineView.page().toPlainText(
            self._callbackManager.callback(self._havePlainText))

    # Perform an approximate match in a separate thread, then update
    # the cursor based on the match results.
    def _havePlainText(self, html_text):
        # Performance notes: findApproxTextInTarget is REALLY slow. Scrolling
        # through preview.py with profiling enabled produced::
        #
        #  Output from Enki:
        #         41130 function calls in 3.642 seconds
        #
        #   Ordered by: standard name
        #
        #   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        #       13    0.000    0.000    0.000    0.000 __init__.py:406(text)
        #       13    0.000    0.000    3.398    0.261 approx_match.py:138(findApproxText)
        #       13    0.000    0.000    3.432    0.264 approx_match.py:175(findApproxTextInTarget)
        #       13    0.029    0.002    0.034    0.003 approx_match.py:252(refineSearchResult)
        #       26    0.000    0.000    0.000    0.000 core.py:177(workspace)
        #       ...snip lots more 0.000 or very small times...
        #
        # Therefore, finding ways to make this faster or run it in another
        # thread should significantly improve the GUI's responsiveness.
        qp = core.workspace().currentDocument().qutepart
        qp_text = qp.text
        qp_position = qp.textCursor().position()
        self._runLatest.start(self._movePreviewPaneToIndex,
            # Call findApproxTextInTarget, returning the index and the HTML text searched.
            lambda: (findApproxTextInTarget(qp_text, qp_position, html_text), html_text))

    def _movePreviewPaneToIndex(self, future):
        """Highlights webIndex in the preview pane, per item 4 above.

        Params:

        - webIndex - The index to move the cursor / highlight to in the preview
          pane.
        - txt - The text of the webpage, returned by mainFrame.toPlainText().
        """
        # Retrieve the return value from findApproxTextInTarget.
        webIndex, txt = future.result

        view = self._dock._widget.webEngineView
        page = view.page()
        ft = txt[:webIndex]

        def callback(found):
            if found:
                # Sync the cursors.
                self._scrollSync(False)
                self.textToPreviewSynced.emit()

        if webIndex >= 0:
            self._dock._afterLoaded.afterLoaded(lambda: page.runJavaScript('highlightFind({});'.format(repr(ft)), QWebEngineScript.ApplicationWorld, self._callbackManager.callback(callback)))
        else:
            self.clearHighlight()

    def clearHighlight(self):
        self._dock._afterLoaded.afterLoaded(self._dock._widget.webEngineView.page().runJavaScript, 'clearHighlight();', QWebEngineScript.ApplicationWorld)
Esempio n. 37
0
from PyQt5.QtWebChannel import  QWebChannel 
import sys


# 创建一个 application实例
app = QApplication(sys.argv)
win = QWidget()
win.setWindowTitle('Web页面中的JavaScript与 QWebEngineView交互例子')

# 创建一个垂直布局器
layout = QVBoxLayout()
win.setLayout(layout)

# 创建一个 QWebEngineView 对象
view =  QWebEngineView()
htmlUrl = 'http://127.0.0.1:8020/web/index.html'
view.load( QUrl( htmlUrl ))

# 创建一个 QWebChannel对象,用来传递pyqt参数到JavaScript
channel =  QWebChannel( )
myObj = MySharedObject()   
channel.registerObject( "bridge", myObj )  
view.page().setWebChannel(channel)
 
# 把QWebView和button加载到layout布局中
layout.addWidget(view)
           
# 显示窗口和运行app
win.show()
sys.exit(app.exec_())