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)
class MainWindow(QMainWindow): def __init__(self, parent=None): super(MainWindow, self).__init__(parent) self.esaskyBrowserWidget = ESASkyWrapper(self) if showCommands: self.controlWidget = ControlBox(self.esaskyBrowserWidget) _hwidget = QWidget() _hlayout = QHBoxLayout() _hwidget.setLayout(_hlayout) self.tableWidget = ResultTable() _vlayout = QVBoxLayout() _vlayout.addWidget(_hwidget) _vlayout.addWidget(self.tableWidget) _vwidget = QWidget() _vwidget.setLayout(_vlayout) if showCommands: _hlayout.addWidget(self.controlWidget, 1) _hlayout.addWidget(self.esaskyBrowserWidget, 2) else: _hlayout.addWidget(self.esaskyBrowserWidget, 1) # setup channel self.channel = QWebChannel() self.channel.registerObject('backend', self) self.esaskyBrowserWidget.page().setWebChannel(self.channel) self.setCentralWidget(_vwidget) #QObject.connect(a,SIGNAL("testInit"),self.pippo,Qt.QueuedConnection) def pippo(self): return "test" def setResult(self, result): self.controlWidget.getCurrentWidget().prepareTabularOutput(result) @pyqtSlot(str) def foo(self, arg1): import json mydict = json.loads(arg1) #print (type(self.controlWidget.getCurrentWidget())) try: astroTable = self.controlWidget.getCurrentWidget( ).prepareTabularOutput(mydict) self.tableWidget.setContent(astroTable) except AttributeError: print('MainWindow->foo->attribute not found') def setFov(self, fov): self.esaskyBrowserWidget.page().runJavaScript("setFov(" + str(fov) + ")") def initTest(self): print('sending initTest') self.esaskyBrowserWidget.page().runJavaScript("initTest()")
def addobject(self, name): if app is not None: if self.channel is None: self.channel = QWebChannel() self.channel.registerObject(name, app.frontendpy) self.setWebChannel(self.channel)
self.load(QUrl('file:///html/final.html')) self.resize(1366,768) self.show() # self.showFullScreen() t = threading.Thread(target=self.size_fix) t.setDaemon(True) t.start() def size_fix(self): while True: self.p.setZoomFactor(self.width()/1366) time.sleep(0.15) pass pass pass pass 'nya' pass if __name__=='__main__': app = QApplication([]) channel = QWebChannel() handler = CallHandler() channel.registerObject('handler', handler) view = my_view() app.exec_() data.kiri_save()
class BrowserView(QMainWindow): instances = {} 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() current_url_trigger = QtCore.pyqtSignal() evaluate_js_trigger = QtCore.pyqtSignal(str) class JSBridge(QtCore.QObject): api = None parent_uid = None try: qtype = QtCore.QJsonValue # QT5 except AttributeError: qtype = str # QT4 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) def __init__(self, uid, title, url, width, height, resizable, fullscreen, min_size, confirm_quit, background_color, debug, js_api, webview_ready): super(BrowserView, self).__init__() BrowserView.instances[uid] = self self.uid = uid self.is_fullscreen = False self.confirm_quit = confirm_quit self._file_name_semaphore = Semaphore(0) self._current_url_semaphore = Semaphore() self._evaluate_js_semaphore = Semaphore(0) self.load_event = Event() self._evaluate_js_result = None 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.view = QWebView(self) if url is not None: self.view.setUrl(QtCore.QUrl(url)) 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.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.js_bridge = BrowserView.JSBridge() self.js_bridge.api = js_api self.js_bridge.parent_uid = self.uid if _qt_version >= [5, 5]: 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.view.setContextMenuPolicy( QtCore.Qt.NoContextMenu) # disable right click context menu 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): self._current_url = self.view.url().toString() 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] 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_evaluate_js(self, script): def return_result(result): self._evaluate_js_result = result self._evaluate_js_semaphore.release() try: # PyQt4 return_result( self.view.page().mainFrame().evaluateJavaScript(script)) except AttributeError: # PyQt5 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() def set_title(self, title): self.set_title_trigger.emit(title) def get_current_url(self): 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 _qt_version >= [5, 0]: # QT5 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]) else: # QT4 if dialog_type == FOLDER_DIALOG: file_names = (BrowserView._convert_string(self._file_name), ) elif dialog_type == SAVE_DIALOG or not allow_multiple: file_names = (BrowserView._convert_string( self._file_name[0]), ) else: file_names = tuple( [BrowserView._convert_string(s) for s in self._file_name]) # 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 evaluate_js(self, script): self.load_event.wait() self.evaluate_js_trigger.emit(script) self._evaluate_js_semaphore.acquire() return self._evaluate_js_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 _qt_version >= [5, 5]: 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() elif _qt_version >= [5, 0]: frame = self.view.page().mainFrame() _register_window_object() else: frame = self.view.page().mainFrame() _register_window_object() try: # PyQt4 self.view.page().mainFrame().evaluateJavaScript(script) except AttributeError: # PyQt5 self.view.page().runJavaScript(script) self.load_event.set() @staticmethod def _convert_string(qstring): try: qstring = qstring.toString() # QJsonValue conversion except: pass if sys.version < '3': return unicode(qstring) else: return str(qstring) @staticmethod # Receive func from subthread and execute it on the main thread def on_create_window(func): func()
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.loaded self.shown = window.shown self._js_results = {} self._current_url = None self._file_name = None self.resize(window.width, window.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.width, window.height) self.setMinimumSize(window.min_size[0], window.min_size[1]) self.frameless = window.frameless if self.frameless: self.setWindowFlags(QtCore.Qt.Window | QtCore.Qt.FramelessWindowHint) self.view = BrowserView.WebView(self) if is_webengine: os.environ['QTWEBENGINE_CHROMIUM_FLAGS'] = ( '--use-fake-ui-for-media-stream --enable-features=AutoplayIgnoreWebAudio' ) 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)) 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) 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.url is not None: self.view.setUrl(QtCore.QUrl(window.url)) elif window.html: self.view.setHtml(window.html, QtCore.QUrl('')) else: self.view.setHtml(default_html, QtCore.QUrl('')) if window.x is not None and window.y is not None: self.move(window.x, window.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 connect_to_javascript_code(self): self.channel = QWebChannel() self.handler = CallHandler() self.channel.registerObject('handler', self.handler) self.page().setWebChannel(self.channel) self.loadFinished.connect(self.loadFinishedHandler)
def _OnUrlChanged(self, url): webchannel = QWebChannel(self.page()) self.page().setWebChannel(webchannel) webchannel.registerObject("kiwoom", self._kiwoom)
class QtWebContainer(WebContainer): def __init__(self, bridge_objs: BridgeObjects = None, *args, **kwargs) -> None: super().__init__(name='_web_container', bridge_objs=bridge_objs, *args, **kwargs) if self._config.debug_mode: os.environ['QTWEBENGINE_REMOTE_DEBUGGING'] = '12345' self.profile = QWebEngineProfile.defaultProfile() self.interceptor = QtUrlRequestInterceptor() self.url_scheme_handler = QtUrlSchemeHandler() self.view = QWebEngineView(parent=self._main_window.widget) self.page = self.view.page() self.channel = QWebChannel(self.page) self.bridge_initialized = False self.profile.installUrlSchemeHandler(b'web-greeter', self.url_scheme_handler) self._initialize_page(self.page) if self._config.debug_mode: self.devtools = DevTools() if not self._config.context_menu.enabled: self.view.setContextMenuPolicy(Qt.PreventContextMenu) if not self._config.allow_remote_urls: self.profile.setRequestInterceptor(self.interceptor) if self._config.entry_point.autoload: self.initialize_bridge_objects() self.load() self.view.show() self._main_window.widget.setCentralWidget(self.view) @staticmethod def _create_webengine_script(path: Url, name: str) -> QWebEngineScript: script = QWebEngineScript() script_file = QFile(path) if script_file.open(QFile.ReadOnly): script_string = str(script_file.readAll(), 'utf-8') script.setInjectionPoint(QWebEngineScript.DocumentCreation) script.setName(name) script.setWorldId(QWebEngineScript.MainWorld) script.setSourceCode(script_string) return script def _get_channel_api_script(self) -> QWebEngineScript: return self._create_webengine_script(':/qtwebchannel/qwebchannel.js', 'QWebChannel API') def _init_bridge_channel(self) -> None: self.page.setWebChannel(self.channel) self.page.scripts().insert(self._get_channel_api_script()) self.bridge_initialized = True def _initialize_page(self, page: QWebEnginePage) -> None: page_settings = self.page.settings().globalSettings() if self._config.allow_remote_urls: ENABLED_SETTINGS.append('LocalContentCanAccessRemoteUrls') else: DISABLED_SETTINGS.append('LocalContentCanAccessRemoteUrls') for setting in DISABLED_SETTINGS: try: page_settings.setAttribute( getattr(QWebEngineSettings, setting), False) except AttributeError: pass for setting in ENABLED_SETTINGS: try: page_settings.setAttribute( getattr(QWebEngineSettings, setting), True) except AttributeError: pass page.setView(self.view) def initialize_bridge_objects(self) -> None: if not self.bridge_initialized: self._init_bridge_channel() registered_objects = self.channel.registeredObjects() for obj in self.bridge_objects: if obj not in registered_objects: self.channel.registerObject(obj._name, obj) def load(self, url: str = '') -> None: url = url if url else self._config.entry_point.url if 'http' not in url and not os.path.exists(url): self.page.setHtml(DEFAULT_ENTRY_POINT) return if not url.startswith('file'): url = 'web-greeter:/{0}'.format(url) self.page.load(QUrl(url)) def load_script(self, path: Url, name: str): script = self._create_webengine_script(path, name) return self.page.scripts().insert(script)
name = self.lineEdit_username.text() pwd = self.lineEdit_pwd.text() jscode = "PyQt52WebValue('{}','{}');".format(name, pwd) self.view.page().runJavaScript(jscode) @pyqtSlot() def on_pb_reset_clicked(self): """ PyQt5的输入栏内容清空 """ self.lineEdit_username.setText("") self.lineEdit_pwd.setText("") def setLineEdit(self, list): self.lineEdit_username.setText(list[0]) self.lineEdit_pwd.setText(list[1]) def __del__(self): self.view.deleteLater() if __name__ == "__main__": app = QApplication(sys.argv) web_pyqt = Web2PyQt5() web_pyqt.show() channel = QWebChannel() shared = Myshared() channel.registerObject("connection", shared) web_pyqt.view.page().setWebChannel(channel) shared.finish[list].connect(web_pyqt.setLineEdit) sys.exit(app.exec_())
import sys import os from PyQt5.QtWidgets import * from PyQt5.QtCore import * from PyQt5.QtWebEngineWidgets import * from PyQt5.QtWebChannel import QWebChannel from factorial import Factorial channel = QWebChannel() factorial = Factorial() class PyFactorial(QWidget): def __init__(self): super(PyFactorial, self).__init__() self.setWindowTitle('Python计算阶乘') self.resize(600, 500) layout = QVBoxLayout() self.browser = QWebEngineView() url = os.getcwd() + '/templates/test3.html' self.browser.load(QUrl.fromLocalFile(url)) channel.registerObject('obj', factorial) self.browser.page().setWebChannel(channel) layout.addWidget(self.browser) self.setLayout(layout) if __name__ == '__main__':
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 try: qtype = QtCore.QJsonValue # QT5 except AttributeError: qtype = str # QT4 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) 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') 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() # 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 _qt_version >= [5, 5] else None if _qt_version < [5, 5]: 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, 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.view = BrowserView.WebView(self) if debug and _qt_version > [5, 5]: # 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 _qt_version >= [5, 5] 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: # PyQt4 result = self.view.page().mainFrame().evaluateJavaScript(script) return_result(result) except AttributeError: # PyQt5 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: # PyQt4 self.view.page().mainFrame().evaluateJavaScript(script) except AttributeError: # PyQt5 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 _qt_version >= [5, 0]: # QT5 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]) else: # QT4 if dialog_type == FOLDER_DIALOG: file_names = (BrowserView._convert_string(self._file_name), ) elif dialog_type == SAVE_DIALOG or not allow_multiple: file_names = (BrowserView._convert_string( self._file_name[0]), ) else: file_names = tuple( [BrowserView._convert_string(s) for s in self._file_name]) # 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 _qt_version >= [5, 5]: 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() elif _qt_version >= [5, 0]: frame = self.view.page().mainFrame() _register_window_object() else: frame = self.view.page().mainFrame() _register_window_object() try: # PyQt4 self.view.page().mainFrame().evaluateJavaScript(script) except AttributeError: # PyQt5 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()
def __init__(self, uid, title, url, width, height, resizable, fullscreen, min_size, confirm_quit, background_color, debug, js_api, text_select, 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.view = BrowserView.WebView(self) if debug and _qt_version > [5, 5]: # 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 _qt_version >= [5, 5] 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()
class Term(QWidget): clients = dict() def __init__(self, data, open_win=False): super(Term, self).__init__() self.resize(900, 600) self.hv = QHBoxLayout(self) # self.setWindowFlags(QtCore.Qt.Widget) self.open_win = open_win self.QWebEng = QWebEngineView(self) self.QWebEng.setObjectName('QWebEng') self.setAttribute(Qt.WA_TranslucentBackground) self.setStyleSheet('background-color:transparent') self.setContentsMargins(0, 0, 0, 0) # self.setSpacing(0) self.QWebEng.resize(400, 400) self.hv.addWidget(self.QWebEng) self.hv.setContentsMargins(10, 10, 10, 10) self.hv.setSpacing(0) url = QUrl(QFileInfo("xterm.html").absoluteFilePath()) self.QWebEng.load(url) self.QWebEng.showMaximized() # self.QWebEng.setHtml(xterm) self.data = data self.channel = QWebChannel() self.printer = Print() self.channel.registerObject('printer', self.printer) # self.QWebEng.page().setBackgroundColor(QColor(255, 0, 0, 0)) self.QWebEng.page().setWebChannel(self.channel) # self.QWebEng.page().setBackgroundColor(Qt.transparent) self.QWebEng.page().setBackgroundColor(Qt.transparent) self.setWindowOpacity(0) self.printer.sendf.connect(self.send) self.defcmd = "[%s:\~]$ " % os.getcwd().split(':')[0] self.Dos_Cmd = b'' # print(os.path()) if not self.open_win: jscode = "completeAndReturnName('%s');" % json.dumps( {'data': base64.b64encode(b'aaaaa').decode('utf-8')}) # print(jscode) # self.QWebEng.page().runJavaScript(jscode) # self.QWebEng.loadStarted() else: # self.open_client() threading.Thread(target=self.open_client).start() # Process(target=self.open_client).start() def open_client(self): if self.open_win: IOST = IOLoop.instance() IOST.start() bridge = Bridge(IOST, self) self.clients[self._id()] = bridge self.Start() def defalut_client(self, data): logger.debug('接受到默认端数据:%s' % data) if str(data) == str(b'\x7f'): print('退格键') print('>>>', data, str(data, 'gbk'), bytes(str(data, 'gbk'), encoding='utf-8')) data = b'\x07' if len(self.Dos_Cmd) != 0: self.Dos_Cmd = self.Dos_Cmd[:-1] data = b'\x08\x1b[K' elif str(data) == str(b'\r'): print('回车键') # data = b'\r\n' print(self.Dos_Cmd) if self.Dos_Cmd == b'clear': data = b'\x1b[3;J\x1b[H\x1b[2J\x1b]0;\x07' elif self.Dos_Cmd == b'ls': self.Dos_Cmd = b'dir' else: if self.Dos_Cmd != b'': try: proc = subprocess.Popen(str(self.Dos_Cmd, 'utf-8'), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, bufsize=-1) std = str(proc.stdout.read(), 'gbk') data = b'\r\n' + bytes((std if std else str( proc.stderr.read(), 'gbk')).replace('\n', '\r\n'), encoding='utf-8') except Exception as e: print(e) data = data + bytes('\r\n' + self.defcmd, encoding='utf-8') self.Dos_Cmd = b'' else: self.Dos_Cmd = self.Dos_Cmd + data jscode = "completeAndReturnName('%s');" % json.dumps( {'data': base64.b64encode(data).decode('utf-8')}) # jscode = "completeAndReturnName('%s');" % json.dumps({'data': base64.b64encode(b'\n').decode('utf-8')}) # print(jscode) self.QWebEng.page().runJavaScript(jscode) def get_client(self): return self.clients.get(self._id(), None) def _id(self): return id(self) def Start(self): self.bridge = self.get_client() logger.debug('打开ssh:%s' % self.data) # threading.Thread(target=self.bridge.open,args=).run() self.bridge.open(self.data) def send(self, data): data = json.loads(data) if self.open_win: logger.debug('发送到服务端数据:%s' % data) try: self.bridge.SHELL.resize_pty(width=data['cols'], height=data['rows']) if data['data']: self.bridge.trans_forward(data['data'].encode()) except Exception as E: logger.debug('客户端 Error:%s' % str(E)) else: logger.debug('发送到默认数据:%s' % data) # Thread(target=self.defalut_client,args=bytes(data['data'], 'utf-8')).start() self.defalut_client(bytes(data['data'], 'utf-8')) # if __name__ == "__main__": # app = QApplication(sys.argv) # data = {'host': '39.107.100.236', 'port': '1993', 'username': '******', 'ispwd': True, 'secret': '@Scjz1993'} # # data = {'host': '192.168.110.134', 'port': '22', 'username': '******', 'ispwd': True, 'secret': '123456'} # # fennbk = Terminal(data={}) # fennbk = Term(data=data,open_win=True) # fennbk.show() # sys.exit(app.exec_())
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)
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_())
def __init__(self, *args, **kwargs): super(VarietyPriceWindow, self).__init__(*args, **kwargs) """绑定公用属性""" self.products = None self.db_worker = None # 线程是否结束标志位 self.exchange_lib_thread_over = True self.selected_variety_thread_over = True """总布局""" self.vertical_layout = QVBoxLayout() # 总纵向布局 top_select = QHBoxLayout() # 顶部横向条件选择栏 """交易所选择下拉框""" exchange_label = QLabel('选择交易所:') self.exchange_lib = QComboBox() self.exchange_lib.activated[str].connect( self.exchange_lib_selected) # 选择交易所调用的方法 """品种选择下拉框""" variety_label = QLabel('选择品种:') self.variety_lib = QComboBox() self.variety_lib.setMinimumSize(80, 20) self.variety_lib.activated[str].connect( self.variety_lib_selected) # 选择品种所调用的方法 """时间选择""" begin_time_label = QLabel("起始日期:") self.begin_time = QDateEdit( QDate.currentDate().addDays(-2)) # 参数为设置的日期 self.begin_time.setDisplayFormat('yyyy-MM-dd') # 时间显示格式 self.begin_time.setCalendarPopup(True) # 使用日历选择时间 end_time_label = QLabel("终止日期:") self.end_time = QDateEdit(QDate.currentDate().addDays(-1)) # 参数为设置的日期 self.end_time.setDisplayFormat('yyyy-MM-dd') # 时间显示格式 self.end_time.setCalendarPopup(True) # 使用日历选择时间 """确认按钮""" self.confirm_button = QPushButton("确定") self.confirm_button.clicked.connect(self.confirm) """水平布局添加控件""" top_select.addWidget(exchange_label) top_select.addWidget(self.exchange_lib) top_select.addWidget(variety_label) top_select.addWidget(self.variety_lib) top_select.addWidget(begin_time_label) top_select.addWidget(self.begin_time) top_select.addWidget(end_time_label) top_select.addWidget(self.end_time) top_select.addWidget(self.confirm_button) """自定义部件""" # 工具栏 self.tools = ToolWidget() tool_btn = QPushButton("季节图表") self.tool_view_all = QPushButton("返回总览") self.tool_view_all.setEnabled(False) self.tool_view_all.clicked.connect(self.return_view_all) self.tools.addTool(tool_btn) self.tools.addTool(self.tool_view_all) self.tools.addSpacer() tool_btn.clicked.connect(self.season_table) # 画布 self.map_widget = MapWidget() # 表格 self.table_widget = TableWidget(4) # 设置格式 self.table_widget.set_style( header_labels=['日期', '价格', '成交量合计', '持仓量合计']) """创建QSplitter""" self.show_splitter = QSplitter() self.show_splitter.setOrientation(Qt.Vertical) # 垂直拉伸 # 加入自定义控件 self.show_splitter.addWidget(self.map_widget) self.show_splitter.addWidget(self.table_widget) """垂直布局添加布局和控件并设置到窗口""" self.vertical_layout.addLayout(top_select) self.vertical_layout.addWidget(self.tools) self.vertical_layout.addWidget(self.show_splitter) # 加入控件 self.web_view = QWebEngineView() self.web_view.load(QUrl("file:///static/html/variety_price.html")) self.web_view.page().profile().downloadRequested.connect( self.download_requested) # 页面下载请求(导出excel) self.show_splitter.addWidget(self.web_view) self.web_view.hide() # 隐藏,刚开始不可见 """js交互通道""" web_channel = QWebChannel(self.web_view.page()) self.web_view.page().setWebChannel(web_channel) # 网页设置信息通道 web_channel.registerObject("season_table", self.tools) # 注册信号对象 web_channel.registerObject("export_table", self) # 导出表格数据信号对象 self.season_table_file = None # 季节表的保存路径 self.setLayout(self.vertical_layout)
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 initLayout(self): # ----------------------------left layout---------------------------- # self.treeModel = QStandardItemModel() self.pageItem = QStandardItem(ROOT_ITEM_PAGE) self.pageItem.type = ITEM_TYPE_PAGE_ROOT self.buildPageItem() self.userobjectItem = QStandardItem(ROOT_ITEM_USER_OBJECT) self.userobjectItem.type = ITEM_TYPE_USER_OBJECT_ROOT self.buildUserObjectItem() self.providerRequestItem = QStandardItem(ROOT_ITEM_PROVIDER_REQUESTS) self.providerRequestItem.type = ITEM_TYPE_PROVIDER_REQUESTS_ROOT self.pluginItem = QStandardItem(ROOT_ITEM_PLUGIN) self.pluginItem.type = ITEM_TYPE_PLUGIN_ROOT self.treeModel.appendColumn([ self.pageItem, self.userobjectItem, self.providerRequestItem, self.pluginItem ]) self.treeModel.setHeaderData(0, Qt.Horizontal, 'Model') self.treeView = QTreeView() self.treeView.setModel(self.treeModel) self.treeView.setEditTriggers(QAbstractItemView.NoEditTriggers) self.treeView.setContextMenuPolicy(Qt.CustomContextMenu) self.treeView.customContextMenuRequested.connect(self.openContextMenu) self.treeView.doubleClicked.connect(self.onTreeItemDoubleClicked) self.treeView.clicked.connect(self.getDebugData) leftContainer = QWidget() layout = QVBoxLayout() layout.setContentsMargins(0, 0, 6, 0) # left, top, right, bottom layout.addWidget(self.treeView) leftContainer.setLayout(layout) # ----------------------------middle layout---------------------------- # middleContainer = QWidget() # search shall start not before the user completed typing # filter_delay = DelayedExecutionTimer(self) # new_column.search_bar.textEdited[str].connect(filter_delay.trigger) # filter_delay.triggered[str].connect(self.search) self.tabBar = QTabBar() self.tabBar.setUsesScrollButtons(False) self.tabBar.setDrawBase(False) # self.tabBar.addTab('tab1') # self.tabBar.addTab('tab2') self.pathBar = QWidget() layout = QHBoxLayout() layout.setAlignment(Qt.AlignLeft) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(1) self.pathBar.setLayout(layout) self.searchHolder = QWidget() layout = QHBoxLayout() layout.addWidget(self.tabBar) layout.addWidget(self.pathBar) layout.addSpacerItem(QSpacerItem(0, 0, QSizePolicy.Expanding)) self.searchHolder.setLayout(layout) self.searchHolder.layout().setContentsMargins(6, 6, 6, 0) self.tabContentWidget = QWidget() self.browser = QWebEngineView() self.browser.setZoomFactor(1.3) self.channel = QWebChannel() self.webObject = WebShareObject() self.channel.registerObject('bridge', self.webObject) self.browser.page().setWebChannel(self.channel) self.webObject.jsCallback.connect(lambda value: self.addUpdate(value)) 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') script = QWebEngineScript() script.setSourceCode(qwebchannel_js) script.setInjectionPoint(QWebEngineScript.DocumentCreation) script.setName('qtwebchannel.js') script.setWorldId(QWebEngineScript.MainWorld) script.setRunsOnSubFrames(True) self.browser.page().scripts().insert(script) Utils.scriptCreator(os.path.join('..', 'resources', 'js', 'event.js'), 'event.js', self.browser.page()) self.browser.page().setWebChannel(self.channel) layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(self.initQCheckBoxUI()) layout.addWidget(self.initSearchView()) layout.addWidget(self.browser) self.tabContentWidget.setLayout(layout) self.searchWidget.hide() middleContainer.stackedWidget = QStackedWidget() self.url = XulDebugServerHelper.HOST + 'list-pages' self.showXulDebugData(self.url) middleContainer.stackedWidget.addWidget(self.tabContentWidget) middleContainer.stackedWidget.addWidget(QLabel('tab2 content')) self.tabBar.currentChanged.connect( lambda: middleContainer.stackedWidget.setCurrentIndex( self.tabBar.currentIndex())) layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(self.searchHolder) layout.addWidget(middleContainer.stackedWidget) middleContainer.setLayout(layout) # ----------------------------right layout---------------------------- # self.rightSiderClickInfo = 'Property' self.rightSiderTabWidget = QTabWidget() self.rightSiderTabBar = QTabBar() self.rightSiderTabWidget.setTabBar(self.rightSiderTabBar) self.rightSiderTabWidget.setTabPosition(QTabWidget.East) self.favoriteTreeView = FavoriteTreeView(self) # self.propertyEditor = PropertyEditor(['Key', 'Value']) self.inputWidget = UpdateProperty() self.rightSiderTabWidget.addTab(self.inputWidget, IconTool.buildQIcon('property.png'), 'Property') self.rightSiderTabWidget.setStyleSheet( ('QTab::tab{height:60px;width:32px;color:black;padding:0px}' 'QTabBar::tab:selected{background:lightgray}')) # self.rightSiderTabWidget.addTab(self.propertyEditor,IconTool.buildQIcon('property.png'),'property') self.rightSiderTabWidget.addTab(self.favoriteTreeView, IconTool.buildQIcon('favorites.png'), 'Favorites') self.rightSiderTabBar.tabBarClicked.connect(self.rightSiderClick) # ----------------------------entire layout---------------------------- # self.contentSplitter = QSplitter(Qt.Horizontal) self.contentSplitter.setHandleWidth(0) # thing to grab the splitter self.contentSplitter.addWidget(leftContainer) self.contentSplitter.addWidget(middleContainer) self.contentSplitter.addWidget(self.rightSiderTabWidget) self.contentSplitter.setStretchFactor(0, 0) self.contentSplitter.setStretchFactor(1, 6) self.contentSplitter.setStretchFactor(2, 6) self.mainSplitter = QSplitter(Qt.Vertical) self.mainSplitter.setHandleWidth(0) self.mainSplitter.addWidget(self.contentSplitter) self.mainSplitter.addWidget(self.consoleWindow) self.mainSplitter.setStretchFactor(1, 0) self.mainSplitter.setStretchFactor(2, 1) self.setCentralWidget(self.mainSplitter) # 默认隐藏掉复选框 self.groupBox.setHidden(True)
class MainWindow(QMainWindow): WIDTH = 950 HEIGHT = 600 expWidget = None expName = '' hlpName = '' hwin = None uncheckHelpBox = pyqtSignal() setEditorText = pyqtSignal(str) setConfigText = pyqtSignal(str) def closeEvent(self, e): if self.hwin != None: self.hwin.close() def __init__(self, lang, app, tr_eyes, tr_qt): """ The constructor. :param lang: the autodetected language, which comes from shell variables :type lang: str :param app: pointer to the Application :type app: QApplication :param tr_eyes: translator to localize eyes :type tr_eyes: QTranslator :param tr_qt: translator to localize Qt5 :type tr_qt: QTranslator """ QMainWindow.__init__(self) self.lang=None # this will be set later, after self.translate() tries self.app=app self.tr_eyes=tr_eyes self.tr_qt=tr_qt self.conf = configparser.ConfigParser() self.conf.read(cnf) try: self.translate(self.conf['ScreenTheme']['language']) self.lang = self.conf['ScreenTheme']['language'] except: self.translate(lang) self.lang=lang self.init_UI() self.uncheckHelpBox.connect(self.uncheckTheHelpBox) #self.setEditorText.connect(self.updateEditor) #self.setConfigText.connect(self.updateConfig) def uncheckTheHelpBox(self): """ unchecks the help checkbox """ self.helpCB.setChecked(False) return def init_UI(self): self.makeMenu() self.setMinimumSize(self.WIDTH-100, self.HEIGHT-50) self.resize(self.WIDTH,self.HEIGHT) self._x = 100 self._y = 10 palette = QPalette() # background color palette.setColor(QPalette.Background, QColor(81,188,185)) #("#99ccff")) "#88bbcc" self.setPalette(palette) self.helpCB = QCheckBox(self.tr('Enable PopUp Help Window')) self.helpCB.stateChanged.connect(self.showHelp) #self.helpCB.setStyleSheet('background-color: white') self.statusBar = QStatusBar() self.setStatusBar(self.statusBar) self.statusBar.addWidget(self.helpCB) self.callExpt(electronicsExptsScope[0]) # Start the scope by default self.screen = QDesktopWidget().screenGeometry() self.show() self.move(20, 20) def showHelp(self): if self.helpCB.isChecked() == True: if self.hwin == None: self.hwin = helpWin(self, (self.title,self.hlpName), self.lang) if(self.hlpName[1] in ['editor','advanced_logger']): try: from PyQt5.QtWebChannel import QWebChannel self.channel = QWebChannel() self.handler = self.editorHandler(self.setEditorText,self.setConfigText) self.channel.registerObject('handler', self.handler) self.hwin.page().setWebChannel(self.channel) except Exception as e: print(e) self.hwin.show() else: if self.hwin != None: self.hwin.hide() def scope_help(self,e): self.hlpName = e[1] if self.expName != 'scope': explib = importlib.import_module('scope') try: if self.expWidget != None: self.expWidget.timer.stop() # Stop the timer loop of current widget self.hwin = None self.expWidget= None # Let python delete it w = explib.Expt(p) self.setCentralWidget(w) self.expWidget = w self.expName = 'scope' except: self.expName = '' self.setWindowTitle(self.tr('Failed to load scope')) self.setWindowTitle(self.tr(e[0])) self.hwin = None self.title = e[0] self.showHelp() def callExpt(self, e): """ :parm: e lst with a title and a HTML file designation; when e[1] is not a string, then it is an iterable with possible HTML file names, and the last file name may also be a module name. """ module_name = e[1] if type(e[1]) is str else e[1][-1] explib = importlib.import_module(module_name) try: if self.expWidget != None: self.expWidget.timer.stop() # Stop the timer loop of current widget self.hwin = None self.expWidget= None # Let python delete it w = explib.Expt(p) self.setWindowTitle(self.tr(e[0])) self.setCentralWidget(w) self.expWidget = w self.expName = e[1] self.hlpName = e[1] self.title = e[0] self.showHelp() except Exception as err: print("Exception:", err) self.expName = '' self.setWindowTitle(unicode(self.tr('Failed to load %s')) %e[0]) return def runCode(self, e): if self.expName != 'editor': #Moved here from some other non coding expt self.hlpName = e self.callExpt( ['Python Coding', ('9.0','editor')]) self.expWidget.mycode = e[1] self.expWidget.update() def setConfig(self,section, key, value): """ Sets some part of eyes's configuration @param section a section of the configuration file cnf, for example: 'ScreenTheme' @param key for example: 'Background' @param value the text to assign to the key, for example: 'dark' """ self.conf = configparser.ConfigParser() self.conf.read(cnf) self.conf[section][key] = value with open(cnf,"w") as out: self.conf.write(out) return def setWBG(self): """ sets a light background for the scope's screen """ self.setConfig('ScreenTheme', 'Background', 'light') QMessageBox.warning( self, self.tr('No immediate application'), self.tr("Please restart the application to lighten the screen's background") ) return def setBBG(self): """ sets a dark background for the scope's screen """ self.setConfig('ScreenTheme', 'Background', 'dark') QMessageBox.warning( self, self.tr('No immediate application'), self.tr("Please restart the application to darken the screen's background.") ) return def makeMenu(self): imagePath = os.path.join(os.path.dirname( os.path.abspath(__file__)),'images') bar = self.menuBar() bar.clear() # reset all menu actions mb = bar.addMenu(self.tr("Device")) mb.addAction(self.tr('Reconnect'), self.reconnect) mb.addAction(self.tr('LightBackGround next time'), self.setWBG) mb.addAction(self.tr('DarkBackGround next time'), self.setBBG) sm = mb.addMenu(self.tr("Choose Language")) sm.setIcon(QIcon(os.path.join(imagePath, "UN_emblem_blue.svg"))) for e in languages: action = sm.addAction(str(e), lambda item=str(e): self.setLanguage(item)) flag=e.flag(imagePath) if flag: action.setIcon(QIcon(flag)) action.setIconVisibleInMenu(True) em = bar.addMenu(self.tr("School Expts")) for e in schoolExpts: em.addAction(self.tr(e[0]), lambda item=e: self.scope_help(item)) em = bar.addMenu(self.tr("Electronics")) for e in electronicsExptsScope: em.addAction(self.tr(e[0]), lambda item=e: self.scope_help(item)) for e in electronicsExpts: em.addAction(self.tr(e[0]), lambda item=e: self.callExpt(item)) em = bar.addMenu(self.tr("Electrical")) for e in electricalExpts: em.addAction(self.tr(e[0]), lambda item=e: self.callExpt(item)) em = bar.addMenu(self.tr("Sound")) for e in soundExpts: em.addAction(self.tr(e[0]), lambda item=e: self.callExpt(item)) em = bar.addMenu(self.tr("Mechanics")) for e in mechanicsExpts: em.addAction(self.tr(e[0]), lambda item=e: self.callExpt(item)) em = bar.addMenu(self.tr("Other Expts")) for e in otherExpts: em.addAction(self.tr(e[0]), lambda item=e: self.callExpt(item)) def setLanguage(self,l): self.setConfig('ScreenTheme', 'language', l) self.translators=self.translate(l) self.lang=l self.init_UI() return def reconnect(self): global p,eyes try: p.H.disconnect() except: pass p=eyes.open() if self.expWidget is None: explib = importlib.import_module('scope') self.expWidget = explib.Expt(p) self.setCentralWidget(self.expWidget) self.setWindowTitle(self.tr('Oscilloscope')) self.expName = 'scope' self.expWidget.p = p self.expWidget.msg('') if p != None: print('recovering...',self.expName) if self.expName == 'scope': self.expWidget.recover() # translation stuff def translate(self, lang = None): try: self.app.removeTranslator(self.tr_eyes) self.app.removeTranslator(self.tr_qt) except: pass if lang is None: lang=QLocale.system().name() self.tr_eyes=QTranslator() self.tr_eyes.load("lang/"+lang, os.path.dirname(__file__)) self.app.installTranslator(self.tr_eyes) self.tr_qt=QTranslator() self.tr_qt.load("qt_"+lang, QLibraryInfo.location(QLibraryInfo.TranslationsPath)) self.app.installTranslator(self.tr_qt) self.uncheckHelpBox.emit()
class MainWindow(BaseWindow): def __init__(self): super().__init__() self.qObject = QObject() self.initConsole() self.initUI() self.show() def initConsole(self): self.consoleWindow = ButtomWindow() def initUI(self): self.resize(int(Utils.getWindowWidth() * 0.8), int(Utils.getWindowHeight() * 0.8)) self.initMenuBar() self.initLayout() super().initWindow() def initMenuBar(self): menuBar = self.menuBar() fileMenu = menuBar.addMenu('File') disConnectAction = QAction(IconTool.buildQIcon('disconnect.png'), '&Disconnect', self) disConnectAction.setShortcut('Ctrl+D') disConnectAction.setShortcutContext(Qt.ApplicationShortcut) disConnectAction.triggered.connect(self.restartProgram) settingAction = QAction(IconTool.buildQIcon('setting.png'), '&Setting...', self) settingAction.setShortcut('Ctrl+Shift+S') settingAction.setShortcutContext(Qt.ApplicationShortcut) settingAction.triggered.connect(lambda: STCLogger().d('setting')) clearCacheAction = QAction(IconTool.buildQIcon('clearCache.png'), '&ClearCache', self) clearCacheAction.setShortcut('Ctrl+Alt+C') clearCacheAction.setShortcutContext(Qt.ApplicationShortcut) clearCacheAction.triggered.connect(self.clearCache) clearSearchHistoryAction = QAction( IconTool.buildQIcon('clearCache.png'), '&ClearSearchHistory', self) clearSearchHistoryAction.triggered.connect(self.clearSearchHistory) settingLogPathAction = QAction(IconTool.buildQIcon('path.png'), '&LogPath', self) settingLogPathAction.triggered.connect(self.setLogPath) fileMenu.addAction(disConnectAction) fileMenu.addAction(clearCacheAction) fileMenu.addAction(settingLogPathAction) fileMenu.addAction(clearSearchHistoryAction) # fileMenu.addAction(settingAction) # fileMenu.addAction(showLogAction) # settingAction.triggered.connect(self.openSettingWindow) editMenu = menuBar.addMenu('Edit') findAction = QAction(IconTool.buildQIcon('find.png'), '&Find', self) findAction.setShortcut('Ctrl+F') findAction.triggered.connect(self.findActionClick) editMenu.addAction(findAction) focusItemAction = QAction(IconTool.buildQIcon('focus.png'), '&Focus Item', self) focusItemAction.triggered.connect(self.focusChooseItem) editMenu.addAction(focusItemAction) self.chooseItemType = '' self.chooseItemId = '' settingMenu = menuBar.addMenu('Setting') autoLoginAction = QAction(IconTool.buildQIcon('setting.png'), '&Auto Login', self) autoLoginAction.setShortcutContext(Qt.ApplicationShortcut) autoLoginAction.triggered.connect(self.setAutoState) settingMenu.addAction(autoLoginAction) helpMenu = menuBar.addMenu('Help') aboutAction = QAction(IconTool.buildQIcon('about.png'), '&About', self) aboutAction.triggered.connect(self.openAboutWindow) helpMenu.addAction(aboutAction) def closeEvent(self, e): self.settings.setValue('searchHistory', self.searchHistory) e.accept() def openAboutWindow(self): print('open about') self.aboutWindow = AboutWindow() self.aboutWindow.show() def setAutoState(self): reply = QMessageBox.question(self, "提示", "是否取消自动登录功能", QMessageBox.Yes | QMessageBox.No, QMessageBox.No) if reply == QMessageBox.Yes: Utils.setAutoLoginState(False) def restartProgram(self): from XulDebugTool.ui.ConnectWindow import ConnectWindow # 不应该在这里导入,但是放在前面会有问题 try: hasAbout = object.__getattribute__(self, "aboutWindow") except: hasAbout = None if hasAbout is not None: self.aboutWindow.close() print("新建连接页面") self.con = ConnectWindow() self.close() # def openSettingWindow(self): # self.tableInfoModel = SettingWindow() # self.tableInfoModel.show() def initLayout(self): # ----------------------------left layout---------------------------- # self.treeModel = QStandardItemModel() self.pageItem = QStandardItem(ROOT_ITEM_PAGE) self.pageItem.type = ITEM_TYPE_PAGE_ROOT self.buildPageItem() self.userobjectItem = QStandardItem(ROOT_ITEM_USER_OBJECT) self.userobjectItem.type = ITEM_TYPE_USER_OBJECT_ROOT self.buildUserObjectItem() self.providerRequestItem = QStandardItem(ROOT_ITEM_PROVIDER_REQUESTS) self.providerRequestItem.type = ITEM_TYPE_PROVIDER_REQUESTS_ROOT self.pluginItem = QStandardItem(ROOT_ITEM_PLUGIN) self.pluginItem.type = ITEM_TYPE_PLUGIN_ROOT self.treeModel.appendColumn([ self.pageItem, self.userobjectItem, self.providerRequestItem, self.pluginItem ]) self.treeModel.setHeaderData(0, Qt.Horizontal, 'Model') self.treeView = QTreeView() self.treeView.setModel(self.treeModel) self.treeView.setEditTriggers(QAbstractItemView.NoEditTriggers) self.treeView.setContextMenuPolicy(Qt.CustomContextMenu) self.treeView.customContextMenuRequested.connect(self.openContextMenu) self.treeView.doubleClicked.connect(self.onTreeItemDoubleClicked) self.treeView.clicked.connect(self.getDebugData) leftContainer = QWidget() layout = QVBoxLayout() layout.setContentsMargins(0, 0, 6, 0) # left, top, right, bottom layout.addWidget(self.treeView) leftContainer.setLayout(layout) # ----------------------------middle layout---------------------------- # middleContainer = QWidget() # search shall start not before the user completed typing # filter_delay = DelayedExecutionTimer(self) # new_column.search_bar.textEdited[str].connect(filter_delay.trigger) # filter_delay.triggered[str].connect(self.search) self.tabBar = QTabBar() self.tabBar.setUsesScrollButtons(False) self.tabBar.setDrawBase(False) # self.tabBar.addTab('tab1') # self.tabBar.addTab('tab2') self.pathBar = QWidget() layout = QHBoxLayout() layout.setAlignment(Qt.AlignLeft) layout.setContentsMargins(0, 0, 0, 0) layout.setSpacing(1) self.pathBar.setLayout(layout) self.searchHolder = QWidget() layout = QHBoxLayout() layout.addWidget(self.tabBar) layout.addWidget(self.pathBar) layout.addSpacerItem(QSpacerItem(0, 0, QSizePolicy.Expanding)) self.searchHolder.setLayout(layout) self.searchHolder.layout().setContentsMargins(6, 6, 6, 0) self.tabContentWidget = QWidget() self.browser = QWebEngineView() self.browser.setZoomFactor(1.3) self.channel = QWebChannel() self.webObject = WebShareObject() self.channel.registerObject('bridge', self.webObject) self.browser.page().setWebChannel(self.channel) self.webObject.jsCallback.connect(lambda value: self.addUpdate(value)) 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') script = QWebEngineScript() script.setSourceCode(qwebchannel_js) script.setInjectionPoint(QWebEngineScript.DocumentCreation) script.setName('qtwebchannel.js') script.setWorldId(QWebEngineScript.MainWorld) script.setRunsOnSubFrames(True) self.browser.page().scripts().insert(script) Utils.scriptCreator(os.path.join('..', 'resources', 'js', 'event.js'), 'event.js', self.browser.page()) self.browser.page().setWebChannel(self.channel) layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(self.initQCheckBoxUI()) layout.addWidget(self.initSearchView()) layout.addWidget(self.browser) self.tabContentWidget.setLayout(layout) self.searchWidget.hide() middleContainer.stackedWidget = QStackedWidget() self.url = XulDebugServerHelper.HOST + 'list-pages' self.showXulDebugData(self.url) middleContainer.stackedWidget.addWidget(self.tabContentWidget) middleContainer.stackedWidget.addWidget(QLabel('tab2 content')) self.tabBar.currentChanged.connect( lambda: middleContainer.stackedWidget.setCurrentIndex( self.tabBar.currentIndex())) layout = QVBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(self.searchHolder) layout.addWidget(middleContainer.stackedWidget) middleContainer.setLayout(layout) # ----------------------------right layout---------------------------- # self.rightSiderClickInfo = 'Property' self.rightSiderTabWidget = QTabWidget() self.rightSiderTabBar = QTabBar() self.rightSiderTabWidget.setTabBar(self.rightSiderTabBar) self.rightSiderTabWidget.setTabPosition(QTabWidget.East) self.favoriteTreeView = FavoriteTreeView(self) # self.propertyEditor = PropertyEditor(['Key', 'Value']) self.inputWidget = UpdateProperty() self.rightSiderTabWidget.addTab(self.inputWidget, IconTool.buildQIcon('property.png'), 'Property') self.rightSiderTabWidget.setStyleSheet( ('QTab::tab{height:60px;width:32px;color:black;padding:0px}' 'QTabBar::tab:selected{background:lightgray}')) # self.rightSiderTabWidget.addTab(self.propertyEditor,IconTool.buildQIcon('property.png'),'property') self.rightSiderTabWidget.addTab(self.favoriteTreeView, IconTool.buildQIcon('favorites.png'), 'Favorites') self.rightSiderTabBar.tabBarClicked.connect(self.rightSiderClick) # ----------------------------entire layout---------------------------- # self.contentSplitter = QSplitter(Qt.Horizontal) self.contentSplitter.setHandleWidth(0) # thing to grab the splitter self.contentSplitter.addWidget(leftContainer) self.contentSplitter.addWidget(middleContainer) self.contentSplitter.addWidget(self.rightSiderTabWidget) self.contentSplitter.setStretchFactor(0, 0) self.contentSplitter.setStretchFactor(1, 6) self.contentSplitter.setStretchFactor(2, 6) self.mainSplitter = QSplitter(Qt.Vertical) self.mainSplitter.setHandleWidth(0) self.mainSplitter.addWidget(self.contentSplitter) self.mainSplitter.addWidget(self.consoleWindow) self.mainSplitter.setStretchFactor(1, 0) self.mainSplitter.setStretchFactor(2, 1) self.setCentralWidget(self.mainSplitter) # 默认隐藏掉复选框 self.groupBox.setHidden(True) def addUpdate(self, value=None): self.inputWidget.initData(self.pageId, value) self.inputWidget.updateItemUI() dict = json.loads(value) if dict['action'] == "click": self.chooseItemId = dict['Id'] self.chooseItemType = Utils.findNodeById(dict['Id'], dict['xml']).tag elif dict['action'] == "load": self.browser.load(QUrl(dict['url'])) else: pass def focusChooseItem(self): if self.chooseItemType in ('area', 'item'): XulDebugServerHelper.focusChooseItemUrl(self.chooseItemId) def initQCheckBoxUI(self): self.groupBox = QGroupBox() self.skipPropCheckBox = QCheckBox(SKIP_PROP, self) self.skipPropCheckBox.setChecked(False) self.skipPropCheckBox.stateChanged.connect( lambda: self.clickCheckBox(self.skipPropCheckBox, SKIP_PROP)) self.withChildrenCheckBox = QCheckBox(WITH_CHILDREN, self) self.withChildrenCheckBox.setChecked(False) self.withChildrenCheckBox.stateChanged.connect( lambda: self.clickCheckBox(self.withChildrenCheckBox, WITH_CHILDREN )) self.withBindingDataCheckBox = QCheckBox(WITH_BINDING_DATA, self) self.withBindingDataCheckBox.setChecked(False) self.withBindingDataCheckBox.stateChanged.connect( lambda: self.clickCheckBox(self.withBindingDataCheckBox, WITH_BINDING_DATA)) self.withPositionCheckBox = QCheckBox(WITH_POSITION, self) self.withPositionCheckBox.setChecked(False) self.withPositionCheckBox.stateChanged.connect( lambda: self.clickCheckBox(self.withPositionCheckBox, WITH_POSITION )) self.withSelectorCheckBox = QCheckBox(WITH_SELECTOR, self) self.withSelectorCheckBox.setChecked(False) self.withSelectorCheckBox.stateChanged.connect( lambda: self.clickCheckBox(self.withSelectorCheckBox, WITH_SELECTOR )) checkGrouplayout = QHBoxLayout() checkGrouplayout.addWidget(self.skipPropCheckBox) checkGrouplayout.addWidget(self.withChildrenCheckBox) checkGrouplayout.addWidget(self.withBindingDataCheckBox) checkGrouplayout.addWidget(self.withPositionCheckBox) checkGrouplayout.addWidget(self.withSelectorCheckBox) self.groupBox.setLayout(checkGrouplayout) return self.groupBox def initSearchView(self): self.settings = QSettings('XulDebugTool') self.searchHistory = self.settings.value('searchHistory', []) self.searchWidget = QWidget() self.searchWidget.setStyleSheet( ".QWidget{border:1px solid rgb(220, 220, 220)}") searchPageLayout = QHBoxLayout() self.searchLineEdit = QLineEdit() self.searchIcon = QAction(self) self.searchIcon.setIcon(IconTool.buildQIcon('find_h.png')) self.searchMenu = QMenu(self) for text in self.searchHistory: self.action = QAction( text, self, triggered=lambda: self.searchLineEdit.setText(text)) self.searchMenu.addAction(self.action) self.searchIcon.setMenu(self.searchMenu) self.searchDelIcon = QAction(self) self.searchDelIcon.setIcon(IconTool.buildQIcon('del.png')) self.searchLineEdit.addAction(self.searchIcon, QLineEdit.LeadingPosition) self.searchLineEdit.addAction(self.searchDelIcon, QLineEdit.TrailingPosition) self.searchDelIcon.setVisible(False) self.searchLineEdit.setStyleSheet( "border:2px groove gray;border-radius:10px;padding:2px 4px") searchPageLayout.addWidget(self.searchLineEdit) self.searchLineEdit.textChanged.connect(self.searchPage) self.searchLineEdit.editingFinished.connect(self.saveSearchHistory) self.searchDelIcon.triggered.connect(self.searchDelClick) self.previousBtn = QPushButton() self.previousBtn.setStyleSheet("background:transparent;") self.previousBtn.setIcon(IconTool.buildQIcon('up.png')) self.previousBtn.setFixedSize(15, 20) searchPageLayout.addWidget(self.previousBtn) self.previousBtn.clicked.connect( lambda: self.previousBtnClick(self.searchLineEdit.text())) self.nextBtn = QPushButton() self.nextBtn.setIcon(IconTool.buildQIcon('down.png')) self.nextBtn.setStyleSheet("background:transparent;") self.nextBtn.setFixedSize(15, 20) self.nextBtn.clicked.connect( lambda: self.nextBtnClick(self.searchLineEdit.text())) searchPageLayout.addWidget(self.nextBtn) self.matchCase = QCheckBox("Match Case") self.matchCase.setChecked(False) self.matchCase.stateChanged.connect(self.matchCaseChange) searchPageLayout.addWidget(self.matchCase) self.matchTips = QLabel() self.matchTips.setFixedWidth(100) self.searchClose = QPushButton("×") self.searchClose.setFixedWidth(10) self.searchClose.setStyleSheet("background:transparent;") self.searchClose.clicked.connect(self.searchCloseClick) searchPageLayout.addWidget(self.matchTips) searchPageLayout.addWidget(self.searchClose) self.searchWidget.setLayout(searchPageLayout) return self.searchWidget def clickCheckBox(self, checkBox, name): if checkBox.isChecked(): STCLogger().i('select ' + name) self.selectCheckBoxInfo(name) else: STCLogger().i('cancel ' + name) self.cancelCheckBoxInfo(name) def selectCheckBoxInfo(self, str): if None != self.url: checkedStr = str + '=' + 'true' unCheckedStr = str + '=' + 'false' if self.url.find('?') == -1: self.url += '?' self.url += checkedStr else: if self.url.find(str) == -1: self.url += '&' self.url += checkedStr elif self.url.find(unCheckedStr) != -1: self.url = self.url.replace(unCheckedStr, checkedStr) self.showXulDebugData(self.url) def cancelCheckBoxInfo(self, str): if None != self.url: checkedStr = str + '=' + 'true' if self.url.find(checkedStr) >= -1: split = self.url.split(checkedStr) self.url = ''.join(split) self.url = self.url.replace('&&', '&') self.url = self.url.replace('?&', '?') if self.url.endswith('?'): self.url = self.url[:-1] if self.url.endswith('&'): self.url = self.url[:-1] self.showXulDebugData(self.url) def rightSiderClick(self, index): # 两次单击同一个tabBar时显示隐藏内容区域 if self.rightSiderTabBar.tabText(index) == self.rightSiderClickInfo: if self.rightSiderTabWidget.width() == Utils.getItemHeight(): self.rightSiderTabWidget.setMaximumWidth( Utils.getWindowWidth()) self.rightSiderTabWidget.setMinimumWidth(Utils.getItemHeight()) else: self.rightSiderTabWidget.setFixedWidth(Utils.getItemHeight()) else: if self.rightSiderTabWidget.width() == Utils.getItemHeight(): self.rightSiderTabWidget.setMaximumWidth( Utils.getWindowWidth()) self.rightSiderTabWidget.setMinimumWidth(Utils.getItemHeight()) self.rightSiderClickInfo = self.rightSiderTabBar.tabText(index) def clearCache(self): r = XulDebugServerHelper.clearAllCaches() if r.status == 200: self.statusBar().showMessage('cache cleanup success') else: self.statusBar().showMessage('cache cleanup failed') def setLogPath(self): file_path = QFileDialog.getSaveFileName(self, 'save file', ConfigHelper.LOGCATPATH, "Txt files(*.txt)") if len(file_path[0]) > 0: ConfigurationDB.saveConfiguration(ConfigHelper.KEY_LOGCATPATH, file_path[0]) @pyqtSlot(QPoint) def openContextMenu(self, point): index = self.treeView.indexAt(point) if not index.isValid(): return item = self.treeModel.itemFromIndex(index) menu = QMenu() if item.type == ITEM_TYPE_PROVIDER: queryAction = QAction( IconTool.buildQIcon('data.png'), '&Query Data...', self, triggered=lambda: self.showQueryDialog(item.data)) queryAction.setShortcut('Alt+Q') menu.addAction(queryAction) copyAction = QAction( IconTool.buildQIcon('copy.png'), '&Copy', self, triggered=lambda: pyperclip.copy('%s' % index.data())) copyAction.setShortcut(QKeySequence.Copy) menu.addAction(copyAction) menu.exec_(self.treeView.viewport().mapToGlobal(point)) @pyqtSlot(QModelIndex) def getDebugData(self, index): item = self.treeModel.itemFromIndex(index) if item.type == ITEM_TYPE_PAGE_ROOT: # 树第一层,page节点 self.buildPageItem() self.url = XulDebugServerHelper.HOST + 'list-pages' self.showXulDebugData(self.url) elif item.type == ITEM_TYPE_USER_OBJECT_ROOT: # 树第一层,userObject节点 self.buildUserObjectItem() self.url = XulDebugServerHelper.HOST + 'list-user-objects' self.showXulDebugData(self.url) elif item.type == ITEM_TYPE_PROVIDER_REQUESTS_ROOT: # 树第一层,Provider Request节点 self.url = XulDebugServerHelper.HOST + 'list-provider-requests' self.showXulDebugData(self.url) elif item.type == ITEM_TYPE_PLUGIN_ROOT: # 树第一层,plugin节点 pass elif item.type == ITEM_TYPE_PAGE: # 树第二层,page下的子节点 pageId = item.id self.url = XulDebugServerHelper.HOST + 'get-layout/' + pageId self.showXulDebugData(self.url) elif item.type == ITEM_TYPE_USER_OBJECT: # 树第二层,userObject下的子节点 objectId = item.id self.url = XulDebugServerHelper.HOST + 'get-user-object/' + objectId self.showXulDebugData(self.url) elif item.type == ITEM_TYPE_PROVIDER: # 树第三层,userObject下的DataService下的子节点 pass self.groupBox.setHidden(item.type != ITEM_TYPE_PAGE) # self.fillPropertyEditor(item.data) @pyqtSlot(QModelIndex) def onTreeItemDoubleClicked(self, index): item = self.treeModel.itemFromIndex(index) if item.type == ITEM_TYPE_PROVIDER: self.showQueryDialog(item.data) def buildPageItem(self): self.pageItem.removeRows(0, self.pageItem.rowCount()) r = XulDebugServerHelper.listPages() if r: pagesNodes = Utils.xml2json(r.data, 'pages') if pagesNodes == '': return # 如果只有一个page,转化出来的json不是数据.分开处理 if isinstance(pagesNodes['page'], list): for i, page in enumerate(pagesNodes['page']): # 把page解析了以后放page节点下 row = QStandardItem(page['@pageId']) row.id = self.pageId = page['@id'] row.data = page row.type = ITEM_TYPE_PAGE self.pageItem.appendRow(row) else: page = pagesNodes['page'] row = QStandardItem(page['@pageId']) row.id = self.pageId = page['@id'] row.data = page row.type = ITEM_TYPE_PAGE self.pageItem.appendRow(row) if self.pageItem.rowCount() > 0: self.pageItem.setText( '%s(%s)' % (ROOT_ITEM_PAGE, self.pageItem.rowCount())) def buildUserObjectItem(self): self.userobjectItem.removeRows(0, self.userobjectItem.rowCount()) r = XulDebugServerHelper.listUserObject() if r: userObjectNodes = Utils.xml2json(r.data, 'objects') # 如果只有一个userObject,转化出来的json不是数据.分开处理 if userObjectNodes and isinstance(userObjectNodes['object'], list): for i, o in enumerate(userObjectNodes['object']): # 把userObject加到User-Object节点下 row = QStandardItem(o['@name']) row.id = o['@id'] row.data = o row.type = ITEM_TYPE_USER_OBJECT self.userobjectItem.appendRow(row) # 如果是DataServcie, 填充所有的Provider到该节点下 if o['@name'] == CHILD_ITEM_DATA_SERVICE: r = XulDebugServerHelper.getUserObject(o['@id']) if r: dataServiceNodes = Utils.xml2json(r.data, 'object') if isinstance( dataServiceNodes['object']['provider'], list): for j, provider in enumerate( dataServiceNodes['object'] ['provider']): dsRow = QStandardItem( provider['ds']['@providerClass']) dsRow.id = provider['@name'] dsRow.data = provider dsRow.type = ITEM_TYPE_PROVIDER row.appendRow(dsRow) else: provider = dataServiceNodes['object'][ 'provider'] dsRow = QStandardItem( provider['ds']['@providerClass']) dsRow.id = provider['@name'] dsRow.data = provider dsRow.type = ITEM_TYPE_PROVIDER row.appendRow(dsRow) # 对Provider按升序排序 row.sortChildren(0) if row.rowCount() > 0: row.setText('%s(%s)' % (row.text(), row.rowCount())) else: # 没有只有一个userObject的情况, 暂不处理 pass if self.userobjectItem.rowCount() > 0: self.userobjectItem.setText( '%s(%s)' % (ROOT_ITEM_USER_OBJECT, self.userobjectItem.rowCount())) def showXulDebugData(self, url): STCLogger().i('request url:' + url) self.browser.load(QUrl(url)) self.statusBar().showMessage(url) def convertProperty(self, k, v): """递归的将多层属性字典转成单层的.""" if isinstance(v, dict): for subk, subv in v.items(): self.convertProperty(subk, subv) else: setattr(self.qObject, k, v) def showQueryDialog(self, data): STCLogger().i('show query dialog: ', data) self.dialog = DataQueryDialog(data) self.dialog.finishSignal.connect(self.onGetQueryUrl) self.dialog.show() def onGetQueryUrl(self, url): STCLogger().i('request url:' + url) self.favoriteTreeView.updateTree() self.browser.load(QUrl(url)) self.statusBar().showMessage(url) def findActionClick(self): self.searchWidget.show() self.searchLineEdit.setFocus() self.searchLineEdit.setText(self.browser.selectedText()) def searchPage(self, text): if not text.strip(): self.searchDelIcon.setVisible(False) else: self.searchDelIcon.setVisible(True) check = self.matchCase.isChecked() if check: self.browser.findText(text, QWebEnginePage.FindFlags(2), lambda result: self.changeMatchTip(result)) else: self.browser.findText(text, QWebEnginePage.FindFlags(0), lambda result: self.changeMatchTip(result)) def saveSearchHistory(self): text = self.searchLineEdit.text() if text != '' and text not in self.searchHistory: self.action = QAction( text, self, triggered=lambda: self.searchLineEdit.setText(text)) self.searchMenu.addAction(self.action) self.searchHistory.append(text) def clearSearchHistory(self): self.searchHistory = [] def previousBtnClick(self, text): check = self.matchCase.isChecked() if check: self.browser.findText( text, QWebEnginePage.FindFlags(1) | QWebEnginePage.FindFlags(2), lambda result: self.changeMatchTip(result)) else: self.browser.findText(text, QWebEnginePage.FindFlags(1), lambda result: self.changeMatchTip(result)) def nextBtnClick(self, text): check = self.matchCase.isChecked() if check: self.browser.findText(text, QWebEnginePage.FindFlags(2), lambda result: self.changeMatchTip(result)) else: self.browser.findText(text, QWebEnginePage.FindFlags(0), lambda result: self.changeMatchTip(result)) def matchCaseChange(self): self.browser.findText("") self.searchPage(self.searchLineEdit.text()) def changeMatchTip(self, result): if result: self.matchTips.setText("Find matches") else: self.matchTips.setText("No matches") def searchDelClick(self): self.searchLineEdit.setText("") self.browser.findText("") self.matchTips.setText("") def searchCloseClick(self): self.searchLineEdit.setText("") self.browser.findText("") self.matchTips.setText("") self.searchWidget.hide()
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() hide_trigger = QtCore.pyqtSignal() show_trigger = QtCore.pyqtSignal() fullscreen_trigger = QtCore.pyqtSignal() window_size_trigger = QtCore.pyqtSignal(int, int) window_move_trigger = QtCore.pyqtSignal(int, int) window_minimize_trigger = QtCore.pyqtSignal() window_restore_trigger = QtCore.pyqtSignal() current_url_trigger = QtCore.pyqtSignal() evaluate_js_trigger = QtCore.pyqtSignal(str, str) class JSBridge(QtCore.QObject): qtype = QtCore.QJsonValue if is_webengine else str def __init__(self): super(BrowserView.JSBridge, self).__init__() @QtCore.pyqtSlot(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, param, value_id) 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) window = Window('web_inspector', title, url, '', 700, 500, None, None, True, False, (300, 200), False, False, False, False, '#fff', None, False) 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): 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) if is_webengine: self.featurePermissionRequested.connect( self.onFeaturePermissionRequested) self.nav_handler = BrowserView.NavigationHandler(self) else: self.nav_handler = None 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 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.loaded self.shown = window.shown self._js_results = {} self._current_url = None self._file_name = None self.resize(window.width, window.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.width, window.height) self.setMinimumSize(window.min_size[0], window.min_size[1]) self.frameless = window.frameless if self.frameless: self.setWindowFlags(QtCore.Qt.Window | QtCore.Qt.FramelessWindowHint) self.view = BrowserView.WebView(self) if is_webengine: os.environ['QTWEBENGINE_CHROMIUM_FLAGS'] = ( '--use-fake-ui-for-media-stream --enable-features=AutoplayIgnoreWebAudio' ) 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)) 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) 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.url is not None: self.view.setUrl(QtCore.QUrl(window.url)) elif window.html: self.view.setHtml(window.html, QtCore.QUrl('')) else: self.view.setHtml(default_html, QtCore.QUrl('')) if window.x is not None and window.y is not None: self.move(window.x, window.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, 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 == '' 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 closeEvent(self, event): self.pywebview_window.closing.set() if self.confirm_close: 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] if self.pywebview_window in windows: windows.remove(self.pywebview_window) try: # Close inspector if open BrowserView.instances[self.uid + '-inspector'].close() del BrowserView.instances[self.uid + '-inspector'] except KeyError: pass self.pywebview_window.closed.set() if len(BrowserView.instances) == 0: self.hide() _app.exit() 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): 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 result = self.view.page().mainFrame().evaluateJavaScript(script) return_result(result) except AttributeError: self.view.page().runJavaScript(script, return_result) except Exception as e: logger.exception(e) def on_load_finished(self): self._set_js_api() 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.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): self.window_size_trigger.emit(width, height) 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 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.js_api, 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: # < QT 5.6 self.view.page().mainFrame().evaluateJavaScript(script) except AttributeError: self.view.page().runJavaScript(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 # 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()
#Este se usa para abrir localmente config.mainWindow.abrirLocal.clicked.connect(window.callLocal) config.mainWindow.abrirLocal_2.clicked.connect(window.callLocal) config.mainWindow.btnSyncDestinos.clicked.connect(destinamelos) config.mainWindow.btnFiltroFechas.clicked.connect( interfacePochteca.manejador) config.mainWindow.btnLogin_3.clicked.connect(validateLogin) config.mainWindow.progress_fn(0, "Sesión no iniciada") window.dashboardMotor = QWebEngineView(window.mapFrame) p = WebPage() window.dashboardMotor.setPage(p) window.dashboardMotor.setGeometry(0, 0, window.mapFrame.frameGeometry().width(), window.mapFrame.frameGeometry().height()) p.profile().scripts().insert(client_script()) c = QWebChannel(p) p.setWebChannel(c) c.registerObject('bridge', p) window.dashboardMotor.setHtml( '<!DOCTYPE html> <html> <head> <meta name="viewport" content="initial-scale=1.0, user-scalable=no" /> <style type="text/css"> html { height: 100% } body { height: 100%; margin: 0; padding: 0 } #map-canvas { height: 100% } </style> <script type="text/javascript" src="https://maps.googleapis.com/maps/api/js?v=3.exp&sensor=false"> </script> <script type="text/javascript"> function palPy(arg){new QWebChannel(qt.webChannelTransport, function(channel) { channel.objects.bridge.print(arg); });} function initialize() { var myLatlng = new google.maps.LatLng(19.358322,-99.2775786); var mapOptions = { center: myLatlng, zoom: 16 }; var map = new google.maps.Map(document.getElementById("map-canvas"), mapOptions); var marker = new google.maps.Marker({ position: myLatlng, map: map, title: "Hello World!" }); google.maps.event.addListener(map, "center_changed", function() { window.setTimeout(function() { var center = map.getCenter(); marker.setPosition(center); console.log(center.toString()); palPy(center.toString()); window.status = center.lat(); }, 100); }); } google.maps.event.addDomListener(window, "load", initialize); </script> </head> <body><div id="map-canvas"/></body></html>' ) window.show() window.dashboardMotor.setGeometry(0, 0, 1272, 300) sys.exit(app.exec_())
def __init__(self, uid, title, url, width, height, resizable, fullscreen, min_size, confirm_quit, background_color, debug, js_api, webview_ready): super(BrowserView, self).__init__() BrowserView.instances[uid] = self self.uid = uid self.is_fullscreen = False self.confirm_quit = confirm_quit self._file_name_semaphore = Semaphore(0) self._current_url_semaphore = Semaphore() self._evaluate_js_semaphore = Semaphore(0) self.load_event = Event() self._evaluate_js_result = None 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.view = QWebView(self) if url is not None: self.view.setUrl(QtCore.QUrl(url)) 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.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.js_bridge = BrowserView.JSBridge() self.js_bridge.api = js_api self.js_bridge.parent_uid = self.uid if _qt_version >= [5, 5]: 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.view.setContextMenuPolicy( QtCore.Qt.NoContextMenu) # disable right click context menu self.move(QApplication.desktop().availableGeometry().center() - self.rect().center()) self.activateWindow() self.raise_() webview_ready.set()
class CustomWebPage(QWebEnginePage): def __init__(self, parent): self.myprofile = self.getProfile() super().__init__(self.myprofile, parent) self.profile = self.myprofile self.jsReturned = False self.channel = None self._overrideFile = None #self.applyCustomStyleSheet() self.loadStarted.connect(self.slotFrameLoadStarted) self.urlChanged.connect(self.slotUrlChanged, Qt.QueuedConnection) self.loadFinished.connect(self.injectXwareDesktop) app.sigMainWinLoaded.connect(self.connectUI) self.javaScriptConsoleMessage(QWebEnginePage.InfoMessageLevel, 'Info', 0, 'Error') sigFrameLoadStarted = pyqtSignal() @pyqtSlot() def connectUI(self): app.mainWin.action_refreshPage.triggered.connect(self.slotRefreshPage) # Local Torrent File Chooser Support def chooseFile(self, parentFrame, suggestFile): if self._overrideFile: return self.overrideFile else: return super().chooseFile(parentFrame, suggestFile) @property def overrideFile(self): result = self._overrideFile self._overrideFile = None return result @overrideFile.setter def overrideFile(self, url): self._overrideFile = url def urlMatchIn(self, *againsts): return parse.urldefrag(self.url().toString())[0] in againsts # Local Torrent File Chooser Support Ends Here def applyCustomStyleSheet(self): styleSheet = QUrl.fromLocalFile(constants.XWARESTYLE_FILE) self.setHtml("U", styleSheet) # mainFrame functions @pyqtSlot() def slotFrameLoadStarted(self): self.overrideFile = None self.sigFrameLoadStarted.emit() @pyqtSlot() def slotUrlChanged(self): if self.urlMatchIn(constants.V2_PAGE): logging.info("webView: redirect to V3.") self.triggerAction(QWebEnginePage.Stop) self.load(QUrl(constants.V3_PAGE)) elif self.urlMatchIn(constants.V3_PAGE, constants.LOGIN_PAGE): pass else: logging.error("Unable to handle {}".format(self.url().toString())) def addobject(self): if app is not None: if self.channel is None: self.channel = QWebChannel() self.channel.registerObject('xdpy1', app.frontendpy) self.setWebChannel(self.channel) def addobject(self, name): if app is not None: if self.channel is None: self.channel = QWebChannel() self.channel.registerObject(name, app.frontendpy) self.setWebChannel(self.channel) @pyqtSlot() def blockUpdates(self): print('blockUpdates') def getProfile(self): path = constants.QWEBENGINECACHE_PATH tryMkdir(path) profile = QWebEngineProfile() profile.setCachePath(path) profile.clearHttpCache() 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) profile.scripts().insert(script) return profile @pyqtSlot("QList<QVariant>") def js_callback(self, result): if result is not None: callBackResult = result print("js_callback:{}".format(result)) self.jsReturned = True def injectJs(self, source, name): js = QWebEngineScript() js.setName(name) js.setSourceCode(source) js.setInjectionPoint(QWebEngineScript.DocumentCreation) js.setWorldId(QWebEngineScript.MainWorld) js.setRunsOnSubFrames(True) self.scripts().insert(js) def getUrl(self): if self.urlMatchIn(constants.LOGIN_PAGE): jsCode = 'document.getElementsByTagName("iFrame").item(0).src' self.runJavaScript(jsCode, self.js_callback) elif self.urlMatchIn(constants.V3_PAGE): print('False!') @pyqtSlot(bool) def injectXwareDesktop(self, ok): if not ok: # terminated prematurely return js_prifx = '\nnew QWebChannel(qt.webChannelTransport, function(channel){window.xdpy=channel.objects.xdpy;\n' js_suffix = '\n})\n' if self.urlMatchIn(constants.LOGIN_PAGE): jsFile = constants.XWAREJS_LOGIN_FILE else: jsFile = constants.XWAREJS_FILE self.addobject('xdpy') with open(jsFile, encoding="UTF-8") as file: js = file.read() js_main = js_prifx + js + js_suffix self.runJavaScript(js_main, self.js_callback) @pyqtSlot() def slotRefreshPage(self): self.load(QUrl(constants.V3_PAGE))
def register_channel_objects(): wc = QWebChannel(page) wc.registerObjects(wcobjects) page.setWebChannel(wc)
self.setGeometry(300, 300, 300, 220) self.show() def s1s(self): self.handler._signal1.emit("b1") def s2s(self): self.handler._signal2.emit("b2") if __name__ == '__main__': app = QApplication(sys.argv) s = QWidget() view = QWebEngineView(s) channel = QWebChannel(view) handler = CallHandler() channel.registerObject('pyjs', handler) handler._signal1.connect(handler._slot1) view.page().setWebChannel(channel) b1 = QPushButton("b1", s) #b1.clicked.connect() url_string = "file:///test.html" view.load(QUrl(url_string)) s.setGeometry(300, 300, 300, 220) s.show() sys.exit(app.exec_())
self.site.setStyleSheet("height:30px") self.toolbar.addWidget(self.site) self.refresh = QAction(QIcon("../images/refresh.png"), "刷新", self) self.toolbar.addAction(self.refresh) self.toolbar.actionTriggered[QAction].connect( lambda: MainWebView(self).refresh(self.site.text())) self.toolbar.addSeparator() def resizeEvent(self, event): if self.isResize: height = self.size().height() width = self.size().width() self.webview.setMinimumSize(width, height) self.webview.setMaximumSize(width, height) else: self.isResize = True if __name__ == '__main__': app = QApplication(sys.argv) main = Ui_Main_WebView() main.show() main.webview.load( QUrl("http://www.bidcenter.com.cn/newssearchyz-19852799.html")) channel = QWebChannel() myObj = PageToQt() channel.registerObject('bridge', myObj) main.webview.page().setWebChannel(channel) sys.exit(app.exec_())
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()
class WebView(object): def __init__(self, parent, object_name, dev_mode: bool): self._event_listeners = {} self._web_engine_view = None self._dev_mode = dev_mode self._object_name = object_name self._setup_web_engine_view(parent) self.move_window_func = None def _setup_web_engine_view(self, parent) -> NoReturn: self._web_engine_view = self._create_web_engine_view( parent, self._object_name) self._web_engine_view.urlChanged.connect(self._on_url_changed) self._web_engine_view.page().loadFinished.connect( self._on_page_load_finished) # cookies self._cookiesJar = CookiesJar() self._web_engine_view.page().profile().cookieStore( ).cookieAdded.connect(self._on_cookie_added) self._web_engine_view.page().profile().cookieStore( ).cookieRemoved.connect(self._on_cookie_removed) # web channel self._web_channel = QWebChannel(self._web_engine_view.page()) self._web_engine_view.page().setWebChannel(self._web_channel) self._web_bridge = WebViewBridge(self) self._web_channel.registerObject("proxy", self._web_bridge) # inject scripts self.inject_javascript_file( os.path.join(os.path.dirname(os.path.abspath(__file__)), "res", "js", "qwebchannel.js")) self.inject_javascript_file( os.path.join(os.path.dirname(os.path.abspath(__file__)), "res", "js", "mmgui.js")) def register_web_channel_object(self, namespace: str, object: QObject) -> NoReturn: self._web_channel.registerObject(namespace, object) def get_web_engine_view(self): return self._web_engine_view def get_web_engine_view_page(self): return self._web_engine_view.page() def get_widget_view(self): return self._web_engine_view def register_event_listener( self, event_type: str, listener: Callable[[WebViewEvent], None]) -> NoReturn: if event_type not in self._event_listeners: self._event_listeners[event_type] = [] self._event_listeners[event_type].append(listener) def unregister_event_listener( self, event_type: str, listener: Callable[[WebViewEvent], None]) -> NoReturn: if event_type in self._event_listeners and self._event_listeners[ event_type]: for item in self._event_listeners[event_type]: if item == listener: self._event_listeners[event_type].remove(item) break def _notify_event_listeners(self, event: WebViewEvent): event_type = event.type if event_type in self._event_listeners and self._event_listeners[ event_type]: for listener in self._event_listeners[event_type]: listener(event) def _on_url_changed(self, qurl): logger.info("_on_url_changed %s", qurl.toString()) self._notify_event_listeners( WebViewEvent("on_url_changed", qurl.toString())) def _on_page_load_finished(self, ok): logger.info("_on_page_load_finished %s", ok) self._notify_event_listeners(WebViewEvent("on_page_load_finished", ok)) def _on_cookie_added(self, cookie): logger.info("_on_cookie_added %s", cookie) self._cookiesJar.add_cookie(cookie) def _on_cookie_removed(self, cookie): logger.info("_on_cookie_removed %s", cookie) self._cookiesJar.remove_cookie(cookie) def delete_all_cookies(self): self._web_engine_view.page().profile().cookieStore().deleteAllCookies() def get_cookie(self, domain, name): return self._cookiesJar.get_cookie(domain, name) def _create_web_engine_view(self, parent, object_name: str) -> QWebEngineView: web_engine_view = MyQWebEngineView(parent, lambda e: self._on_drop(e), self._dev_mode) web_engine_view.setAutoFillBackground(False) web_engine_view.setStyleSheet("") web_engine_view.setUrl(QtCore.QUrl("about:blank")) web_engine_view.setObjectName(object_name) web_engine_view.setContextMenuPolicy( QtCore.Qt.CustomContextMenu) # 禁用右键菜单 return web_engine_view def _on_drop(self, event: QDropEvent) -> NoReturn: pass # TODO def bind_function(self, js_function_name: str, py_function: Callable) -> NoReturn: self._web_bridge.bind_function(js_function_name, py_function) def send_message_to_js(self, msg: Any) -> NoReturn: self._web_bridge.send_message(msg) def run_javascript_code(self, javascript_code: str, callback: Callable[[Any], None]) -> NoReturn: self._web_engine_view.page().runJavaScript(javascript_code, callback) def inject_javascript_file(self, javascript_file: str) -> NoReturn: if not os.path.exists(javascript_file): raise Exception("javascript file is not exists, filename=%s" % javascript_file) script = QWebEngineScript() with codecs.open(javascript_file, "r", "utf-8") as f: script.setSourceCode(f.read()) script.setName(os.path.basename(javascript_file)) script.setWorldId(QWebEngineScript.MainWorld) script.setInjectionPoint(QWebEngineScript.DocumentCreation) script.setRunsOnSubFrames(False) profile = self._web_engine_view.page().profile( ) # type: QWebEngineProfile scripts = profile.scripts() # type: QWebEngineScriptCollection scripts.insert(script) logger.info("inject javascript file %s" % javascript_file) def load_file(self, filename: str) -> NoReturn: self._web_engine_view.load(QUrl.fromLocalFile(filename)) def load_url(self, url: str) -> NoReturn: self._web_engine_view.load(QUrl(url)) def set_web_dev_tools_page(self, page) -> NoReturn: self._web_engine_view.page().setDevToolsPage(page) def reload(self): self._web_engine_view.reload() def destroy(self): self._web_engine_view.urlChanged.disconnect() self._web_engine_view.deleteLater() self._web_engine_view.close()
# project: PyQt5 # File : WebViewJSTest02.py # @software: PyCharm import sys from PyQt5.QtWebEngineWidgets import QWebEngineView from PyQt5.QtWidgets import * from PyQt5.QtCore import * from PyQt5.QtGui import * from 高级界面控件.MyShareObject import MyShareObject from PyQt5.QtWebChannel import QWebChannel if __name__ == "__main__": app = QApplication(sys.argv) win = QWidget() win.setWindowTitle('Web 页面中 JavaScript 与 QWebEngineView 交互示例') win.setWindowIcon(QIcon("./images/Python2.ico")) layout = QVBoxLayout() win.setLayout(layout) view = QWebEngineView() htmlUrl = 'http://192.168.3.105:8020/web/index.html' view.load(QUrl(htmlUrl)) channel = QWebChannel() myObj = MyShareObject() channel.registerObject("bridge", myObj) view.page().setWebChannel(channel) layout.addWidget(view) win.show() sys.exit(app.exec_())
class scMap(QWidget): # map class used def __init__(self, app, parent=None): super().__init__(parent=parent) self.app = app self.browser = QWebEngineView(self) self.channel = QWebChannel() self.bridge = Bridge() self.channel.registerObject('bridge', self.bridge) self.browser.page().setWebChannel(self.channel) self.initUI() self.bool_zoom_change = True self.bool_reset_map = True self.zoom_got = None self.bounds_got = None self.bool_contains = None self.bool_contains_1 = None self.bool_contains_2 = None def initUI(self): self.browser.load(QUrl("file:///display/map.html")) self.browser.setFixedSize(783, 616) self.show() def runJavaScript(self, js_str): self.browser.page().runJavaScript(js_str) def runJavaScript_with_callback(self, js_str, callback): self.browser.page().runJavaScript(js_str, callback) def createMarker(self, name, lat, lng, icon): js_str = """%s = new google.maps.Marker({ position: {lat: %f, lng: %f}, map: map, icon: "data/%s" });""" % (name, lat, lng, icon) self.runJavaScript(js_str) def moveMarker(self, name, lat, lng, link): # 判断是否越界,越界则重置地图中心与缩放 if self.cross_the_border(lat, lng, link): # print('cross_the_border') self.reset_map(link) js_str = """%s.setPosition({lat: %f, lng: %f})""" % (name, lat, lng) self.runJavaScript(js_str) def deleteMarker(self, name): pass def setCenter(self, lat, lng): js_str = """map.setCenter({lat: %f, lng: %f})""" % (lat, lng) self.runJavaScript(js_str) def panTo(self, lat, lng): js_str = """map.panTo({lat: %f, lng: %f})""" % (lat, lng) self.runJavaScript(js_str) def set_home(self, lat, lng): js_str = """home = new google.maps.Marker({ position: {lat: %f, lng: %f}, map: map, icon: "data/home.png" });""" % (lat, lng) self.runJavaScript(js_str) def cross_the_border(self, lat, lng, link): # None for no result js_str = "map.getBounds().contains({lat: %f, lng: %f})" % (lat, lng) self.runJavaScript_with_callback(js_str, link.js_callback) bool_ctb = link.js_result link.js_result = None if bool_ctb is None: return bool_ctb else: return not bool_ctb def cross_the_border_1(self, lat, lng): # None for no result js_str = "map.getBounds().contains({lat: %f, lng: %f})" % (lat, lng) self.runJavaScript_with_callback(js_str, self.ctb_callback_1) bool_ctb = self.bool_contains_1 self.bool_contains_1 = None if bool_ctb is None: return bool_ctb else: return not bool_ctb def cross_the_border_2(self, lat, lng): # None for no result js_str = "map.getBounds().contains({lat: %f, lng: %f})" % (lat, lng) self.runJavaScript_with_callback(js_str, self.ctb_callback_2) bool_ctb = self.bool_contains_2 self.bool_contains_2 = None if bool_ctb is None: return bool_ctb else: return not bool_ctb def ctb_callback(self, result): self.bool_contains = result def ctb_callback_1(self, result): self.bool_contains_1 = result def ctb_callback_2(self, result): self.bool_contains_2 = result def callback_print(self, v): print(type(v), v) def reset_map(self, link): if self.bool_reset_map: self.bool_reset_map = False lat1 = None lat2 = None lng1 = None lng2 = None for linkInt in self.app.toolbox.getLinkManager().links: if lat1 is None or linkInt.uav_lat < lat1: lat1 = linkInt.uav_lat if lat2 is None or linkInt.uav_lat > lat2: lat2 = linkInt.uav_lat if lng1 is None or linkInt.uav_lng < lng1: lng1 = linkInt.uav_lng if lng2 is None or linkInt.uav_lng > lng2: lng2 = linkInt.uav_lng self.panTo((lat1 + lat2) / 2, (lng1 + lng2) / 2) # 预留范围 p = 1 / 8 lat1_goal = lat1 - (lat2 - lat1) * p lat2_goal = lat2 + (lat2 - lat1) * p lng1_goal = lng1 - (lng2 - lng1) * p lng2_goal = lng2 + (lng2 - lng1) * p # get map bounds (lat1_got, lat2_got, lng1_got, lng2_got) bounds = None while bounds is None: bounds = self.getBounds() lat1_got, lat2_got, lng1_got, lng2_got = bounds # get zoom(n_got) n_got = None while n_got is None: n_got = self.getZoom() # compare (lat1_goal, lat2_goal, lng1_goal, lng2_goal) and (lat1_got, lat2_got, lng1_got, lng2_got) ratio = max(([(lat2_goal - lat1_goal) / (lat2_got - lat1_got), (lng2_goal - lng1_goal) / (lng2_got - lng1_got)])) # confirm delta delta = 0 if ratio > 1: # while ratio > 1: # delta -= 1 # ratio /= 2 delta = -1 elif 1 / 2 >= ratio > 0: # while ratio <= 1 / 2: # delta += 1 # ratio *= 2 delta = 1 elif ratio == 0 or 1 >= ratio > 1 / 2: pass else: input('reset map::ratio error') n_goal = n_got + delta if n_goal > 20: n_goal = 20 elif n_goal < 3: n_goal = 3 #if delta != 0: # print(threading.current_thread().getName(), 'delta:', delta, '|', 'n_goal:', n_goal) # print((lat1_goal, lat2_goal, lng1_goal, lng2_goal), (lat1_got, lat2_got, lng1_got, lng2_got)) # self.zoom_change(zoom=n_goal) self.fitBounds(lat1_goal, lat2_goal, lng1_goal, lng2_goal) self.bool_reset_map = True def fitBounds(self, lat1_goal, lat2_goal, lng1_goal, lng2_goal): js_str = '''fitBounds(%f, %f, %f, %f)''' % (lat1_goal, lat2_goal, lng1_goal, lng2_goal) self.runJavaScript(js_str) def zoom_change(self, delta=-1, zoom=None): if self.bool_zoom_change: self.bool_zoom_change = False if zoom is None: n_got = None while n_got is None: n_got = self.getZoom() n_goal = n_got + delta if n_goal > 20: n_goal = 20 elif n_goal < 3: n_goal = 3 if delta != 0: print('n_goal:', n_goal, threading.current_thread().getName()) while n_got != n_goal: # setZoom js_str = '''map.setZoom(%d)''' % n_goal self.runJavaScript(js_str) # getZoom n_temp = None while n_temp is None: n_temp = self.getZoom() n_got = n_temp else: n_got = None while n_got is None: n_got = self.getZoom() n_goal = zoom while n_got != n_goal: # setZoom js_str = '''map.setZoom(%d)''' % n_goal self.runJavaScript(js_str) # getZoom n_temp = None while n_temp is None: n_temp = self.getZoom() n_got = n_temp self.bool_zoom_change = True def getZoom(self): js_str = '''map.getZoom()''' self.runJavaScript_with_callback(js_str, self.callback_getZoom) zoom = self.zoom_got self.zoom_got = None return zoom def callback_getZoom(self, result): self.zoom_got = result def getBounds(self): js_str = '''map.getBounds()''' self.runJavaScript_with_callback(js_str, self.callback_getBounds) bounds = self.bounds_got self.bounds_got = None if bounds is None: return bounds else: # return (lat1_got, lat2_got, lng1_got, lng2_got) return (bounds['f']['b'], bounds['f']['f'], bounds['b']['b'], bounds['b']['f']) def callback_getBounds(self, result): self.bounds_got = result
url = QUrl('file:///home/yyk/Desktop/index.html') mainwindow.view.page.load(url) # 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)
def __init__(self, root): super(VdomrWebView, self).__init__() self._root = root self._channel = QWebChannel() self._pyqt5_api = PyQt5Api() self._channel.registerObject('pyqt5_api', self._pyqt5_api) self.page().setWebChannel(self._channel) html = """ <html> <head> <style> .overlay { background-color: rgba(1, 1, 1, 0.8); color:white; font-size:24px; bottom: 0; left: 0; position: fixed; right: 0; top: 0; text-align: center; padding: 40px; } </style> <script src="qrc:///qtwebchannel/qwebchannel.js"></script> <script> // we do the following so we can get all console.log messages on the python console console.log=console.error; {script} </script> </head> <body> <div id=overlay class=overlay style="visibility:hidden">Please wait...</div> {content} <script language="JavaScript"> new QWebChannel(qt.webChannelTransport, function (channel) { window.pyqt5_api = channel.objects.pyqt5_api; /* // instead of doing the following, we map console.log to console.error above console.log=function(a,b,c) { let txt; if (c) txt=a+' '+b+' '+c; else if (b) txt=a+' '+b; else txt=a+''; pyqt5_api.console_log(txt); } */ }); </script> </body> """ html = html.replace('{content}', root._repr_html_()) script = """ window.show_overlay=function() { document.getElementById('overlay').style.visibility='visible' } window.hide_overlay=function() { document.getElementById('overlay').style.visibility='hidden' } window.vdomr_invokeFunction=function(callback_id,args,kwargs) { window.show_overlay(); setTimeout(function() { pyqt5_api.invokeFunction({callback_id:callback_id,args:args,kwargs:kwargs}); window.hide_overlay(); },100); // the timeout might be important to prevent crashes, not sure } """ while True: js = _take_javascript_to_execute() if js is None: break script = script+'\n'+js html = html.replace('{script}', script) self.page().setHtml(html)
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
class VQMemoryCanvas(e_memcanvas.MemoryCanvas, QWebEngineView): #syncSignal = QtCore.pyqtSignal() def __init__(self, mem, syms=None, parent=None, **kwargs): e_memcanvas.MemoryCanvas.__init__(self, mem=mem, syms=syms) QWebEngineView.__init__(self, parent=parent, **kwargs) self._canv_cache = None self._canv_curva = None self._canv_rendtagid = '#memcanvas' self._canv_rend_middle = False self.fname = None # DEV: DO NOT DELETE THIS REFERENCE TO THESE # Otherwise they'll get garbage collected and things like double click # to navigate and logging won't work # (but pyqt5 won't throw an exception, because ugh). self._log_page = LoggerPage() self.setPage(self._log_page) self.channel = QWebChannel() self.page().setWebChannel(self.channel) self.channel.registerObject('vnav', self) htmlpage = e_q_html.template.replace('{{hotexamples_com}}', e_q_jquery.jquery_2_1_0) page = self.page() page.setHtml(htmlpage) loop = QtCore.QEventLoop() page.loadFinished.connect(loop.quit) loop.exec() QtCore.QCoreApplication.processEvents( QtCore.QEventLoop.ExcludeUserInputEvents | QtCore.QEventLoop.ExcludeSocketNotifiers) page.runJavaScript(e_q_jquery.jquery_2_1_0) self.forceSync() page.contentsSizeChanged.connect(self._frameContentsSizeChanged) # Allow our parent to handle these... self.setAcceptDrops(False) def forceSync(self): cthr = QtCore.QThread.currentThread() loop = QtCore.QThread.eventDispatcher(cthr) loop.processEvents(QtCore.QEventLoop.ExcludeUserInputEvents | QtCore.QEventLoop.ExcludeSocketNotifiers | QtCore.QEventLoop.WaitForMoreEvents) def renderMemory(self, va, size, rend=None): if self._canv_rend_middle: vmap = self.mem.getMemoryMap(va) if vmap is None: raise e_exc.InvalidAddress(va) origva = va va, szdiff = self._loc_helper(max(va - size, vmap[0])) size += size + szdiff def callScroll(data): self._scrollToVa(origva) ret = e_memcanvas.MemoryCanvas.renderMemory(self, va, size, rend=rend, cb=callScroll) else: ret = e_memcanvas.MemoryCanvas.renderMemory(self, va, size, rend=rend) return ret def _frameContentsSizeChanged(self, size): if self._canv_scrolled: page = self.page() page.runJavaScript('window.scrollTo(1, 0x0fffffff);') @idlethread def _scrollToVa(self, va, cb=None): vq_main.eatevents() # Let all render events go first page = self.page() selector = 'viv:0x%.8x' % va js = f''' var nodes = document.getElementsByName("{selector}"); if (nodes != null && nodes.length > 0) {{ nodes[0].scrollIntoView() }} ''' if cb: page.runJavaScript(js, cb) else: page.runJavaScript(js) @idlethread def _selectVa(self, va, cb=None): page = self.page() js = ''' selectva("0x%.8x"); scrolltoid("a_%.8x"); ''' % (va, va) page.runJavaSript(js, cb) def _beginRenderMemory(self, va, size, rend): self._canv_cache = '' def _endRenderMemory(self, va, size, rend, cb=None): self._appendInside(self._canv_cache, cb) self._canv_cache = None def _beginRenderVa(self, va, cb=None): self._add_raw('<a name="viv:0x%.8x" id="a_%.8x">' % (va, va), cb) def _endRenderVa(self, va, cb=None): self._add_raw('</a>', cb) def _beginUpdateVas(self, valist, cb=None): self._canv_cache = '' page = self.page() selector = 'a#a_%.8x' % valist[0][0] js = f''' node = document.querySelector("{selector}"); node.outerHTML = '<update id="updatetmp"></update>' + node.outerHTML; ''' for va, size in valist: selector = 'a#a_%.8x' % va js += f''' document.querySelector("{selector}").remove(); ''' if cb: page.runJavaScript(js, cb) else: page.runJavaScript(js) def _endUpdateVas(self, cb=None): js = f''' node = document.querySelector('update#updatetmp'); node.outerHTML = `{self._canv_cache}` + node.outerHTML; ''' if cb: self.page().runJavaScript(js, cb) else: self.page().runJavaScript(js) self._canv_cache = None def _beginRenderPrepend(self): self._canv_cache = '' self._canv_ppjump = self._canv_rendvas[0][0] def _endRenderPrepend(self, cb=None): selector = 'viv:0x%.8x' % self._canv_ppjump js = f''' var node = document.querySelector("{self._canv_rendtagid}"); node.innerHTML = `{self._canv_cache}` + node.innerHTML var snode = document.getElementsByName("{selector}"); if (snode != null && snode.length > 0) {{ snode[0].scrollIntoView() }} ''' self._canv_cache = None if cb: self.page().runJavaScript(js, cb) else: self.page().runJavaScript(js) def _beginRenderAppend(self): self._canv_cache = '' def _endRenderAppend(self, cb=None): page = self.page() js = f'document.querySelector("{self._canv_rendtagid}").innerHTML += `{self._canv_cache}`;' self._canv_cache = None if cb: page.runJavaScript(js, cb) else: page.runJavaScript(js) def getNameTag(self, name, typename='name'): ''' Return a "tag" for this memory canvas. In the case of the qt tags, they are a tuple of html text (<opentag>, <closetag>) ''' clsname = 'envi-%s' % typename namehex = binascii.hexlify(name.lower()).decode('utf-8') subclsname = 'envi-%s-%s' % (typename, namehex) return ( '<span class="%s %s" envitag="%s" envival="%s" onclick="nameclick(this)">' % (clsname, subclsname, typename, namehex), '</span>') def getVaTag(self, va): # The "class" will be the same that we get back from goto event return ( '<span class="envi-va envi-va-0x%.8x" va="0x%.8x" ondblclick="vagoto(this)" oncontextmenu="vaclick(this)" onclick="vaclick(this)">' % (va, va), '</span>') @QtCore.pyqtSlot(str) def _jsGotoExpr(self, expr): # The routine used by the javascript code to trigger nav events if self._canv_navcallback: self._canv_navcallback(expr) @QtCore.pyqtSlot(str) def _jsSetCurVa(self, vastr): self._canv_curva = int(str(vastr), 0) # NOTE: doing append / scroll seperately allows render to catch up @idlethread def _appendInside(self, text, cb=None): page = self.page() js = f'document.querySelector("{self._canv_rendtagid}").innerHTML += `{text}`;' if cb: page.runJavaScript(js, cb) else: page.runJavaScript(js) def _add_raw(self, text, cb=None): # If we are in a call to renderMemory, cache til the end. if self._canv_cache is not None: self._canv_cache += text return self._appendInside(text, cb) def addText(self, text, tag=None, cb=None): text = html.escape(text) #text = text.replace('\n', '<br>') if tag is not None: otag, ctag = tag text = otag + text + ctag self._add_raw(text, cb) @idlethreadsync def clearCanvas(self): page = self.page() page.runJavaScript(f''' var node = document.querySelector("{self._canv_rendtagid}"); if (node != null) {{ node.innerHTML = ""; }} ''') def contextMenuEvent(self, event): va = self._canv_curva menu = QMenu() if self._canv_curva is not None: self.initMemWindowMenu(va, menu) viewmenu = menu.addMenu('view ') viewmenu.addAction("Save frame to HTML", ACT(self._menuSaveToHtml)) menu.exec_(event.globalPos()) def initMemWindowMenu(self, va, menu): initMemSendtoMenu('0x%.8x' % va, menu) def dumpHtml(self, data): if self.fname: with open(self.fname, 'w') as f: f.write(data) self.fname = None def _menuSaveToHtml(self): fname = getSaveFileName(self, 'Save As HTML...') if fname is not None: fname = str(fname) if len(fname): self.fname = fname self.page().toHtml(self.dumpHtml)
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()
class BrowserWidget(QWebEngineView, VIGITIABaseApplication): """ BrowserWidget Example application demonstrating how to display Webpages and local HTML Files on the table """ # Flag to toggle visibility is_hidden = False def __init__(self, rendering_manager): super().__init__() self.set_name(self.__class__.__name__) self.set_rendering_manager(rendering_manager) self.x = 2500 self.y = 1000 self.width = 1920 self.height = 1080 self.rotation = 0 self.z_index = 100 # Prevent ESC from beeing blocked by BrowserWidget # https://stackoverflow.com/questions/56890831/qwidget-cannot-catch-escape-backspace-or-c-x-key-press-events QShortcut(QKeySequence("Escape"), self, activated=self.on_Escape) self.initUI() # Uncomment this line to establish connection to js code # self.connect_to_javascript_code() # Define url or local index.html path here url = QUrl('https://maps.google.com') # Explanation on how to load a local HTML page # url = QUrl.fromLocalFile(os.path.abspath(os.path.join(os.path.dirname(__file__), "index.html"))) self.load(url) def connect_to_javascript_code(self): self.channel = QWebChannel() self.handler = CallHandler() self.channel.registerObject('handler', self.handler) self.page().setWebChannel(self.channel) self.loadFinished.connect(self.loadFinishedHandler) def initUI(self): pass #self.setGeometry(0, 0, self.width, self.height) @pyqtSlot() def loadFinishedHandler(self): # Example on how to call a function in the javascript code of a website js_code = 'pythonToJS("I am a message from python");' self.page().runJavaScript(js_code, self.js_callback) def js_callback(self, result): print('Python called back:', result) def on_new_token_messages(self, data): # print('Tokens:', data) for token in data: if token['component_id'] == 4: angle = token['angle'] self.set_rotation(angle) #self.set_position(token['x_pos'], token['y_pos']) #self.set_x(token['x_pos']) #self.set_y(token['y_pos']) def on_key_pressed(self, event): if event.key() == Qt.Key_F1: #self.set_rotation(self.rotation + 10) self.set_rotation(random.randint(0, 359)) if event.key() == Qt.Key_F2: #self.set_position(self.get_x() - 10, self.get_y()) self.set_position(1845, 750) # Use Touch events in the application: Convert the pointer messages to mouse click events def on_new_pointer_messages(self, messages): for message in messages: global_pos = QPoint(message['x_pos'], message['y_pos']) local_pos = self.mapFromGlobal(global_pos) #target = self.focusProxy() target = self self.emulate_mouse_event(QEvent.MouseMove, local_pos, global_pos, target) self.emulate_mouse_event(QEvent.MouseButtonPress, local_pos, global_pos, target) def on_new_control_messages(self, data): pass # Show/hide video # if data[2] == 3 and data[3] == 1: # self.is_hidden = not self.is_hidden # # if self.is_hidden: # self.hide() # else: # self.show() # def emulate_mouse_event(self, event_type, local_pos, global_pos, target): mouse_event = QMouseEvent(event_type, local_pos, global_pos, Qt.LeftButton, Qt.LeftButton, Qt.NoModifier) try: QCoreApplication.sendEvent(target, mouse_event) except: print( '[BrowserWidget]: Error on injecting Touch Event in BrowserWidget' ) @pyqtSlot() def on_Escape(self): self.rendering_manager.close_window()