class IndexWidget(QWidget): def __init__(self, *args, **kwargs): super(IndexWidget, self).__init__(*args, **kwargs) self.setAttribute(Qt.WA_StyledBackground, True) self.initUI() def initUI(self): self.webView = QWebEngineView(self) self.webView.setContextMenuPolicy(Qt.NoContextMenu) self.webView.load(QUrl("http://www.zhangbailong.com"))
class MainWindow(QMainWindow): def __init__(self, *args, **kwargs): super(MainWindow, self).__init__(*args, **kwargs) self.browser = QWebEngineView() self.browser.setHtml("<h1>Test</h1>") self.browser.setContextMenuPolicy(QtCore.Qt.NoContextMenu) self.setCentralWidget(self.browser) self.show() def html(self, value): self.browser.setHtml(value)
class TabWelcomeSplash(TabRootClass): def __init__(self, parent=None): super(TabWelcomeSplash, self).__init__(parent) def initUI(self): # self.setWindowOpacity(1) # self.setWindowFlags(Qt.FramelessWindowHint) # self.setAttribute(Qt.WA_TranslucentBackground) # self.showFullScreen() rect = QApplication.desktop().screenGeometry() self.resize(rect.width(), rect.height()) self.setWindowFlags(Qt.FramelessWindowHint | Qt.WindowStaysOnTopHint) self.webview = QWebEngineView() vbox = QVBoxLayout() vbox.addWidget(self.webview) main = QGridLayout() main.setSpacing(0) main.addLayout(vbox, 0, 0) self.setLayout(main) #dirname, filename = os.path.split(os.path.abspath(__file__)) # dir = os.getcwd(); # realPath = os.path.realpath(__file__) realDir = os.path.dirname(realPath); realHtml = realDir + "/splash_html_page/splash.html" realUrl = "file:///"+ realHtml; #self.webview.load(QUrl("file:////Users/jerryw/MyCode/QUANTAXIS/QUANTAXIS_Monitor_GUI/MainTabWindows/splash_html_page/splash.html")) self.webview.load(QUrl(realUrl)) self.webview.setContextMenuPolicy(Qt.NoContextMenu) self.webview.show()
class MyApp(QWidget): def __init__(self): super().__init__() QApplication.setOverrideCursor(QCursor(Qt.WaitCursor)) self.initUI() def closeEvent(self, event): msg_box = QMessageBox() msg_box.setIcon(QMessageBox.Warning) msg_box.setWindowTitle("Alert") msg_box.setText("Are you sure you want to exit the program?") msg_box.setWindowIcon(QIcon("resources/icons/trello.ico")) msg_box.setStandardButtons(QMessageBox.Yes | QMessageBox.No) retval = msg_box.exec_() if retval == QMessageBox.Yes: event.accept() else: event.ignore() def initUI(self): self.web = QWebEngineView() self.web.setWindowTitle("Trello") self.web.setWindowIcon(QIcon("resources/icons/trello.ico")) self.web.load(QUrl("https://trello.com/")) self.web.showMaximized() self.web.setContextMenuPolicy(Qt.NoContextMenu) self.web.show() self.web.loadFinished.connect(self.webpageLoaded) self.web.closeEvent = self.closeEvent def webpageLoaded(self): QApplication.restoreOverrideCursor()
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) 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, 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.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._file_name_semaphore = Semaphore(0) self._current_url_semaphore = Semaphore(0) self.load_event = Event() 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 = QWebView(self) 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.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]: 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): 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): self.view.setHtml(content, QtCore.QUrl('')) 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, 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() escaped_script = 'JSON.stringify(eval("{0}"))'.format( _escape_string(script)) try: # PyQt4 result = self.view.page().mainFrame().evaluateJavaScript( escaped_script) return_result(result) except AttributeError: # PyQt5 self.view.page().runJavaScript(escaped_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.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): self.load_event.clear() self.html_trigger.emit(content) 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() 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 # Receive func from subthread and execute it on the main thread def on_create_window(func): func()
class BrowserView(QMainWindow): instance = None load_url_trigger = pyqtSignal(str) html_trigger = pyqtSignal(str, str) dialog_trigger = pyqtSignal(int, str, bool, str) destroy_trigger = pyqtSignal() fullscreen_trigger = pyqtSignal() current_url_trigger = pyqtSignal() def __init__(self, title, url, width, height, icon, resizable, fullscreen, min_size): super(BrowserView, self).__init__() BrowserView.instance = self self.is_fullscreen = False self._file_name_semaphor = threading.Semaphore(0) self._current_url_semaphore = threading.Semaphore() self.resize(width, height) self.setWindowTitle(title) if not resizable: self.setFixedSize(width, height) self.setMinimumSize(min_size[0], min_size[1]) #self.view = QWebView(self) self.view = QWebView(self) self.view.setContextMenuPolicy( Qt.NoContextMenu) # disable right click context menu if icon is not None: self.setWindowIcon(QIcon(icon)) if url is not None: self.view.setUrl(QUrl(url)) self.setCentralWidget(self.view) self.load_url_trigger.connect(self._handle_load_url) self.html_trigger.connect(self._handle_load_html) #self.dialog_trigger.connect(self._handle_file_dialog) self.destroy_trigger.connect(self._handle_destroy_window) self.fullscreen_trigger.connect(self._handle_fullscreen) #self.current_url_trigger.connect(self._handle_current_url) if fullscreen: self.toggle_fullscreen() self.move(QApplication.desktop().availableGeometry().center() - self.rect().center()) self.activateWindow() self.raise_() #webview_ready.set() def _handle_get_current_url(self): self._current_url = self.view.url().toString() self._current_url_semaphore.release() def _handle_load_url(self, url): self.view.setUrl(QUrl(url)) def _handle_load_html(self, content, base_uri): self.view.setHtml(content, QUrl(base_uri)) def _handle_destroy_window(self): self.close() def _handle_fullscreen(self): if self.is_fullscreen: self.showNormal() else: self.showFullScreen() self.is_fullscreen = not self.is_fullscreen 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_url_trigger.emit(url) def load_html(self, content, base_uri): self.html_trigger.emit(content, base_uri) def create_file_dialog(self, dialog_type, directory, allow_multiple, save_filename): self.dialog_trigger.emit(dialog_type, directory, allow_multiple, save_filename) self._file_name_semaphor.acquire() if dialog_type == FOLDER_DIALOG or not allow_multiple: return str(self._file_name) elif allow_multiple: file_names = map(str, self._file_name) if len(file_names) == 1: return file_names[0] else: return file_names else: return None
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.view = QWebEngineView(parent=self._main_window.widget) self.page = self.view.page() self.channel = QWebChannel(self.page) self.bridge_initialized = False 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 = 'file://{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)
class Browser(Application): # pylint: disable=too-many-instance-attributes """The main browser""" url_scheme: QWebEngineUrlScheme bridge_initialized: bool dev_view: QWebEngineView dev_page: WebPage qdock: QDockWidget def __init__(self): super().__init__() self.init() # self.load() def init(self): """Initialize browser""" logger.debug("Initializing Browser Window") if web_greeter_config["config"]["greeter"]["debug_mode"]: os.environ['QTWEBENGINE_REMOTE_DEBUGGING'] = '12345' url_scheme = "web-greeter" self.url_scheme = QWebEngineUrlScheme(url_scheme.encode()) self.url_scheme.setDefaultPort(QWebEngineUrlScheme.PortUnspecified) self.url_scheme.setFlags(QWebEngineUrlScheme.SecureScheme or QWebEngineUrlScheme.LocalScheme or QWebEngineUrlScheme.LocalAccessAllowed) QWebEngineUrlScheme.registerScheme(self.url_scheme) self.profile = QWebEngineProfile.defaultProfile() self.interceptor = QtUrlRequestInterceptor(url_scheme) self.url_scheme_handler = QtUrlSchemeHandler() self.view = QWebEngineView(parent=self.window) self.page = WebPage() self.view.setPage(self.page) self.page.setObjectName("WebG Page") self.view.setObjectName("WebG View") self.channel = QWebChannel(self.page) self.bridge_initialized = False self.profile.installUrlSchemeHandler(url_scheme.encode(), self.url_scheme_handler) self._initialize_page() if web_greeter_config["config"]["greeter"]["debug_mode"]: self._initialize_devtools() else: self.view.setContextMenuPolicy(Qt.PreventContextMenu) self._init_actions() if web_greeter_config["app"]["frame"]: self._init_menu_bar() else: self.window.setWindowFlags(self.window.windowFlags() | Qt.FramelessWindowHint) if web_greeter_config["config"]["greeter"]["secure_mode"]: if hasattr(QWebEngineProfile, "setUrlRequestInterceptor"): self.profile.setUrlRequestInterceptor(self.interceptor) else: # Older Qt5 versions self.profile.setRequestInterceptor(self.interceptor) self.page.setBackgroundColor(QColor(0, 0, 0)) self.window.setStyleSheet("""QMainWindow, QWebEngineView { background: #000000; }""") self.window.setCentralWidget(self.view) logger.debug("Browser Window created") def load(self): """Load theme and initialize bridge""" self.load_theme() self.bridge_objects = (self.greeter, self.greeter_config, self.theme_utils) self.initialize_bridge_objects() self.load_script(':/_greeter/js/bundle.js', 'Web Greeter Bundle') def _initialize_devtools(self): self.dev_view = QWebEngineView(parent=self.window) self.dev_page = WebPage() self.dev_view.setPage(self.dev_page) self.page.setDevToolsPage(self.dev_page) self.dev_view.setObjectName("Devtools view") self.dev_page.setObjectName("Devtools page") self.dev_page.windowCloseRequested.connect( lambda: self.toggle_devtools_value(False)) inspect_element_action = self.page.action(self.page.InspectElement) inspect_element_action.triggered.connect( lambda: self.toggle_devtools_value(True)) self.qdock = QDockWidget() self.qdock.setWidget(self.dev_view) self.qdock.setFeatures(QDockWidget.DockWidgetMovable or QDockWidget.DockWidgetClosable) self.window.addDockWidget(Qt.RightDockWidgetArea, self.qdock) self.qdock.hide() logger.debug("DevTools initialized") def toggle_devtools(self): """Toggle devtools""" if not web_greeter_config["config"]["greeter"]["debug_mode"]: return self.toggle_devtools_value(not self.qdock.isVisible()) def toggle_devtools_value(self, value: bool): """Toggle devtools by value""" if not web_greeter_config["config"]["greeter"]["debug_mode"]: return if value: self.qdock.show() self.dev_view.setFocus() else: self.qdock.hide() self.view.setFocus() def _init_actions(self): """Init browser actions""" self.exit_action = QAction(QIcon("exit.png"), "&Quit", self.window) self.exit_action.setShortcut("Ctrl+Q") self.exit_action.setStatusTip("Exit application") self.exit_action.triggered.connect(qApp.quit) self.toggle_dev_action = QAction("Toggle Developer Tools", self.window) self.toggle_dev_action.setShortcut("Ctrl+Shift+I") self.toggle_dev_action.triggered.connect(self.toggle_devtools) self.fullscreen_action = QAction("Toggle Fullscreen", self.window) self.fullscreen_action.setShortcut("F11") self.fullscreen_action.triggered.connect( lambda: self.toggle_fullscreen(not self.window.isFullScreen())) self.inc_zoom_action = QAction("Zoom In", self.window) self.inc_zoom_action.setShortcut("Ctrl++") self.inc_zoom_action.triggered.connect(self._inc_zoom) self.dec_zoom_action = QAction("Zoom Out", self.window) self.dec_zoom_action.setShortcut("Ctrl+-") self.dec_zoom_action.triggered.connect(self._dec_zoom) self.reset_zoom_action = QAction("Actual Size", self.window) self.reset_zoom_action.setShortcut("Ctrl+0") self.reset_zoom_action.triggered.connect(self._reset_zoom) self.window.addAction(self.exit_action) self.window.addAction(self.toggle_dev_action) self.window.addAction(self.fullscreen_action) self.window.addAction(self.inc_zoom_action) self.window.addAction(self.dec_zoom_action) self.window.addAction(self.reset_zoom_action) def _inc_zoom(self): if self.view.hasFocus(): self.page.increaseZoom() else: self.dev_page.increaseZoom() def _dec_zoom(self): if self.view.hasFocus(): self.page.decreaseZoom() else: self.dev_page.decreaseZoom() def _reset_zoom(self): if self.view.hasFocus(): self.page.setZoomFactor(1) else: self.dev_page.setZoomFactor(1) def _init_menu_bar(self): minimize_action = QAction("Minimize", self.window) minimize_action.setShortcut("Ctrl+M") minimize_action.triggered.connect(self.window.showMinimized) close_action = QAction("Close", self.window) close_action.setShortcut("Ctrl+W") close_action.triggered.connect(self.window.close) self.page.action( self.page.ReloadAndBypassCache).setText("Force Reload") self.page.fullScreenRequested.connect(self.accept_fullscreen) self.menu_bar = QMenuBar() file_menu = self.menu_bar.addMenu("&File") file_menu.addAction(self.exit_action) edit_menu = self.menu_bar.addMenu("&Edit") edit_menu.addAction(self.page.action(self.page.Undo)) edit_menu.addAction(self.page.action(self.page.Redo)) edit_menu.addSeparator() edit_menu.addAction(self.page.action(self.page.Cut)) edit_menu.addAction(self.page.action(self.page.Copy)) edit_menu.addAction(self.page.action(self.page.Paste)) edit_menu.addSeparator() edit_menu.addAction(self.page.action(self.page.SelectAll)) view_menu = self.menu_bar.addMenu("&View") view_menu.addAction(self.page.action(self.page.Reload)) view_menu.addAction(self.page.action(self.page.ReloadAndBypassCache)) view_menu.addAction(self.toggle_dev_action) view_menu.addSeparator() view_menu.addAction(self.reset_zoom_action) view_menu.addAction(self.inc_zoom_action) view_menu.addAction(self.dec_zoom_action) view_menu.addSeparator() view_menu.addAction(self.fullscreen_action) window_menu = self.menu_bar.addMenu("&Window") window_menu.addAction(minimize_action) window_menu.addAction(close_action) # help_menu = menu_bar.addMenu("&Help") self.window.setMenuBar(self.menu_bar) def accept_fullscreen(self, request): """Accepts fullscreen requests""" if web_greeter_config["config"]["greeter"]["debug_mode"]: request.reject() return if request.toggleOn(): self.toggle_fullscreen(True) else: self.toggle_fullscreen(False) request.accept() def toggle_fullscreen(self, value: bool): """Toggle fullscreen""" if not web_greeter_config["config"]["greeter"]["debug_mode"]: return if value: state = self.states["FULLSCREEN"] self.window.setWindowFlags(self.window.windowFlags() or Qt.FramelessWindowHint) self.menu_bar.setParent(None) self.window.setMenuBar(None) else: state = self.states["NORMAL"] self.window.setWindowFlags(self.window.windowFlags() or not Qt.FramelessWindowHint) self.window.setMenuBar(self.menu_bar) try: self.window.windowHandle().setWindowState(state) except (AttributeError, TypeError): self.window.setWindowState(state) def _initialize_page(self): page_settings = self.page.settings().globalSettings() if not web_greeter_config["config"]["greeter"]["secure_mode"]: 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 self.page.setView(self.view) def load_theme(self): """Load theme""" theme = web_greeter_config["config"]["greeter"]["theme"] dir_t = "/usr/share/web-greeter/themes/" path_to_theme = os.path.join(dir_t, theme, "index.html") def_theme = "gruvbox" if theme.startswith("/"): path_to_theme = theme elif theme.__contains__(".") or theme.__contains__("/"): path_to_theme = os.path.join(os.getcwd(), theme) path_to_theme = os.path.realpath(path_to_theme) if not path_to_theme.endswith(".html"): path_to_theme = os.path.join(path_to_theme, "index.html") if not os.path.exists(path_to_theme): print("Path does not exists", path_to_theme) path_to_theme = os.path.join(dir_t, def_theme, "index.html") web_greeter_config["config"]["greeter"]["theme"] = path_to_theme url = QUrl(f"web-greeter://app/{path_to_theme}") self.page.load(url) logger.debug("Theme loaded") @staticmethod def _create_webengine_script(path: Url, name: str) -> QWebEngineScript: script = QWebEngineScript() script_file = QFile(path) # print(script_file, 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) # print(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.bridge_initialized = True def initialize_bridge_objects(self) -> None: """Initialize bridge objects :D""" 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: # pylint: disable=protected-access self.channel.registerObject(obj._name, obj) # print("Registered", obj._name) def load_script(self, path: Url, name: str): """Loads a script in page""" qt_api = self._get_channel_api_script() qt_api_source = qt_api.sourceCode() script = self._create_webengine_script(path, name) script.setSourceCode(qt_api_source + "\n" + script.sourceCode()) self.page.scripts().insert(script)
class Ui_MainWindow(object): def setupUi(self, MainWindow): MainWindow.setObjectName("MainWindow") MainWindow.resize(1189, 574) palette = QtGui.QPalette() brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.WindowText, brush) brush = QtGui.QBrush(QtGui.QColor(46, 52, 54)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Button, brush) brush = QtGui.QBrush(QtGui.QColor(69, 78, 81)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Light, brush) brush = QtGui.QBrush(QtGui.QColor(57, 65, 67)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Midlight, brush) brush = QtGui.QBrush(QtGui.QColor(23, 26, 27)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Dark, brush) brush = QtGui.QBrush(QtGui.QColor(30, 34, 36)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Mid, brush) brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Text, brush) brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.BrightText, brush) brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.ButtonText, brush) brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Base, brush) brush = QtGui.QBrush(QtGui.QColor(46, 52, 54)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Window, brush) brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.Shadow, brush) brush = QtGui.QBrush(QtGui.QColor(23, 26, 27)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.AlternateBase, brush) brush = QtGui.QBrush(QtGui.QColor(255, 255, 220)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.ToolTipBase, brush) brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Active, QtGui.QPalette.ToolTipText, brush) brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.WindowText, brush) brush = QtGui.QBrush(QtGui.QColor(46, 52, 54)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Button, brush) brush = QtGui.QBrush(QtGui.QColor(69, 78, 81)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Light, brush) brush = QtGui.QBrush(QtGui.QColor(57, 65, 67)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Midlight, brush) brush = QtGui.QBrush(QtGui.QColor(23, 26, 27)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Dark, brush) brush = QtGui.QBrush(QtGui.QColor(30, 34, 36)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Mid, brush) brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Text, brush) brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.BrightText, brush) brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.ButtonText, brush) brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Base, brush) brush = QtGui.QBrush(QtGui.QColor(46, 52, 54)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Window, brush) brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.Shadow, brush) brush = QtGui.QBrush(QtGui.QColor(23, 26, 27)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.AlternateBase, brush) brush = QtGui.QBrush(QtGui.QColor(255, 255, 220)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.ToolTipBase, brush) brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Inactive, QtGui.QPalette.ToolTipText, brush) brush = QtGui.QBrush(QtGui.QColor(23, 26, 27)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.WindowText, brush) brush = QtGui.QBrush(QtGui.QColor(46, 52, 54)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Button, brush) brush = QtGui.QBrush(QtGui.QColor(69, 78, 81)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Light, brush) brush = QtGui.QBrush(QtGui.QColor(57, 65, 67)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Midlight, brush) brush = QtGui.QBrush(QtGui.QColor(23, 26, 27)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Dark, brush) brush = QtGui.QBrush(QtGui.QColor(30, 34, 36)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Mid, brush) brush = QtGui.QBrush(QtGui.QColor(23, 26, 27)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Text, brush) brush = QtGui.QBrush(QtGui.QColor(255, 255, 255)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.BrightText, brush) brush = QtGui.QBrush(QtGui.QColor(23, 26, 27)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.ButtonText, brush) brush = QtGui.QBrush(QtGui.QColor(46, 52, 54)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Base, brush) brush = QtGui.QBrush(QtGui.QColor(46, 52, 54)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Window, brush) brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.Shadow, brush) brush = QtGui.QBrush(QtGui.QColor(46, 52, 54)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.AlternateBase, brush) brush = QtGui.QBrush(QtGui.QColor(255, 255, 220)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.ToolTipBase, brush) brush = QtGui.QBrush(QtGui.QColor(0, 0, 0)) brush.setStyle(QtCore.Qt.SolidPattern) palette.setBrush(QtGui.QPalette.Disabled, QtGui.QPalette.ToolTipText, brush) MainWindow.setPalette(palette) icon = QtGui.QIcon() icon.addPixmap(QtGui.QPixmap(":/resources/app.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) MainWindow.setWindowIcon(icon) MainWindow.setToolButtonStyle(QtCore.Qt.ToolButtonTextBesideIcon) self.centralwidget = QtWidgets.QWidget(MainWindow) self.centralwidget.setObjectName("centralwidget") self.gridLayout = QtWidgets.QGridLayout(self.centralwidget) self.gridLayout.setObjectName("gridLayout") self.verticalLayout = QtWidgets.QVBoxLayout() self.verticalLayout.setSizeConstraint( QtWidgets.QLayout.SetDefaultConstraint) self.verticalLayout.setObjectName("verticalLayout") self.horizontalLayout_3 = QtWidgets.QHBoxLayout() self.horizontalLayout_3.setSizeConstraint( QtWidgets.QLayout.SetDefaultConstraint) self.horizontalLayout_3.setObjectName("horizontalLayout_3") self.fileNameLabel = QtWidgets.QLabel(self.centralwidget) self.fileNameLabel.setObjectName("fileNameLabel") self.horizontalLayout_3.addWidget(self.fileNameLabel) self.fileNameTextBox = QtWidgets.QLineEdit(self.centralwidget) self.fileNameTextBox.setReadOnly(True) self.fileNameTextBox.setObjectName("fileNameTextBox") self.horizontalLayout_3.addWidget(self.fileNameTextBox) self.openFileBtn = QtWidgets.QToolButton(self.centralwidget) icon1 = QtGui.QIcon() icon1.addPixmap(QtGui.QPixmap(":/resources/open.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.openFileBtn.setIcon(icon1) self.openFileBtn.setToolButtonStyle(QtCore.Qt.ToolButtonTextBesideIcon) self.openFileBtn.setObjectName("openFileBtn") self.horizontalLayout_3.addWidget(self.openFileBtn) self.verticalLayout.addLayout(self.horizontalLayout_3) self.horizontalLayout_2 = QtWidgets.QHBoxLayout() self.horizontalLayout_2.setObjectName("horizontalLayout_2") self.previousBtn = QtWidgets.QPushButton(self.centralwidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.previousBtn.sizePolicy().hasHeightForWidth()) self.previousBtn.setSizePolicy(sizePolicy) self.previousBtn.setObjectName("previousBtn") self.horizontalLayout_2.addWidget(self.previousBtn) self.comboBox = QtWidgets.QComboBox(self.centralwidget) self.comboBox.setObjectName("comboBox") self.horizontalLayout_2.addWidget(self.comboBox) self.nextBtn = QtWidgets.QPushButton(self.centralwidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.nextBtn.sizePolicy().hasHeightForWidth()) self.nextBtn.setSizePolicy(sizePolicy) self.nextBtn.setObjectName("nextBtn") self.horizontalLayout_2.addWidget(self.nextBtn) self.testCaseBtn = QtWidgets.QPushButton(self.centralwidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.testCaseBtn.sizePolicy().hasHeightForWidth()) self.testCaseBtn.setSizePolicy(sizePolicy) icon2 = QtGui.QIcon() icon2.addPixmap(QtGui.QPixmap(":/resources/download.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.testCaseBtn.setIcon(icon2) self.testCaseBtn.setIconSize(QtCore.QSize(28, 28)) self.testCaseBtn.setFlat(True) self.testCaseBtn.setObjectName("testCaseBtn") self.horizontalLayout_2.addWidget(self.testCaseBtn) self.problemDownloadBtn = QtWidgets.QPushButton(self.centralwidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.problemDownloadBtn.sizePolicy().hasHeightForWidth()) self.problemDownloadBtn.setSizePolicy(sizePolicy) self.problemDownloadBtn.setIcon(icon2) self.problemDownloadBtn.setIconSize(QtCore.QSize(28, 28)) self.problemDownloadBtn.setFlat(True) self.problemDownloadBtn.setObjectName("problemDownloadBtn") self.horizontalLayout_2.addWidget(self.problemDownloadBtn) self.verticalLayout.addLayout(self.horizontalLayout_2) self.horizontalLayout = QtWidgets.QHBoxLayout() self.horizontalLayout.setObjectName("horizontalLayout") self.statementWebView = QWebView(self.centralwidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.statementWebView.sizePolicy().hasHeightForWidth()) self.statementWebView.setSizePolicy(sizePolicy) self.statementWebView.setContextMenuPolicy(QtCore.Qt.NoContextMenu) self.statementWebView.setUrl(QtCore.QUrl("about:blank")) self.statementWebView.setObjectName("statementWebView") self.horizontalLayout.addWidget(self.statementWebView) self.solutionWebView = QWebView(self.centralwidget) sizePolicy = QtWidgets.QSizePolicy(QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Expanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.solutionWebView.sizePolicy().hasHeightForWidth()) self.solutionWebView.setSizePolicy(sizePolicy) self.solutionWebView.setContextMenuPolicy(QtCore.Qt.NoContextMenu) self.solutionWebView.setUrl(QtCore.QUrl("about:blank")) self.solutionWebView.setObjectName("solutionWebView") self.horizontalLayout.addWidget(self.solutionWebView) self.verticalLayout.addLayout(self.horizontalLayout) self.gridLayout.addLayout(self.verticalLayout, 0, 0, 1, 1) MainWindow.setCentralWidget(self.centralwidget) self.menubar = QtWidgets.QMenuBar(MainWindow) self.menubar.setGeometry(QtCore.QRect(0, 0, 1189, 22)) self.menubar.setObjectName("menubar") self.menuOpen = QtWidgets.QMenu(self.menubar) self.menuOpen.setObjectName("menuOpen") MainWindow.setMenuBar(self.menubar) self.statusBar = QtWidgets.QStatusBar(MainWindow) self.statusBar.setObjectName("statusBar") MainWindow.setStatusBar(self.statusBar) self.actionOpen = QtWidgets.QAction(MainWindow) self.actionOpen.setIcon(icon1) self.actionOpen.setObjectName("actionOpen") self.actionReset = QtWidgets.QAction(MainWindow) self.actionReset.setObjectName("actionReset") self.actionExit = QtWidgets.QAction(MainWindow) icon3 = QtGui.QIcon() icon3.addPixmap(QtGui.QPixmap(":/resources/exit.png"), QtGui.QIcon.Normal, QtGui.QIcon.Off) self.actionExit.setIcon(icon3) self.actionExit.setObjectName("actionExit") self.menuOpen.addAction(self.actionOpen) self.menuOpen.addAction(self.actionReset) self.menuOpen.addSeparator() self.menuOpen.addAction(self.actionExit) self.menubar.addAction(self.menuOpen.menuAction()) self.retranslateUi(MainWindow) QtCore.QMetaObject.connectSlotsByName(MainWindow) def retranslateUi(self, MainWindow): _translate = QtCore.QCoreApplication.translate MainWindow.setWindowTitle( _translate("MainWindow", "Hackerrank Submission Data Reader")) self.fileNameLabel.setText(_translate("MainWindow", "File Name")) self.openFileBtn.setText(_translate("MainWindow", "Open")) self.previousBtn.setText(_translate("MainWindow", "<")) self.nextBtn.setText(_translate("MainWindow", ">")) self.testCaseBtn.setText( _translate("MainWindow", "Download Test Cases")) self.problemDownloadBtn.setText( _translate("MainWindow", "Download Problem Sattement")) self.menuOpen.setTitle(_translate("MainWindow", "File")) self.actionOpen.setText(_translate("MainWindow", "Open")) self.actionOpen.setShortcut(_translate("MainWindow", "Ctrl+O")) self.actionReset.setText(_translate("MainWindow", "Reset")) self.actionReset.setShortcut(_translate("MainWindow", "Ctrl+R")) self.actionExit.setText(_translate("MainWindow", "Exit"))
class PreviewerHTML(QWidget): """ Class implementing a previewer widget for HTML, Markdown and ReST files. """ def __init__(self, parent=None): """ Constructor @param parent reference to the parent widget (QWidget) """ super(PreviewerHTML, self).__init__(parent) self.__layout = QVBoxLayout(self) self.titleLabel = QLabel(self) self.titleLabel.setWordWrap(True) self.titleLabel.setTextInteractionFlags(Qt.NoTextInteraction) self.__layout.addWidget(self.titleLabel) try: from PyQt5.QtWebKitWidgets import QWebPage, QWebView self.previewView = QWebView(self) self.previewView.page().setLinkDelegationPolicy( QWebPage.DelegateAllLinks) self.__usesWebKit = True except ImportError: from PyQt5.QtWebEngineWidgets import QWebEngineView self.previewView = QWebEngineView(self) self.__usesWebKit = False sizePolicy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.previewView.sizePolicy().hasHeightForWidth()) self.previewView.setSizePolicy(sizePolicy) self.previewView.setContextMenuPolicy(Qt.NoContextMenu) self.previewView.setUrl(QUrl("about:blank")) self.__layout.addWidget(self.previewView) self.jsCheckBox = QCheckBox(self.tr("Enable JavaScript"), self) self.jsCheckBox.setToolTip( self.tr("Select to enable JavaScript for HTML previews")) self.__layout.addWidget(self.jsCheckBox) self.ssiCheckBox = QCheckBox(self.tr("Enable Server Side Includes"), self) self.ssiCheckBox.setToolTip( self.tr("Select to enable support for Server Side Includes")) self.__layout.addWidget(self.ssiCheckBox) self.jsCheckBox.clicked[bool].connect(self.on_jsCheckBox_clicked) self.ssiCheckBox.clicked[bool].connect(self.on_ssiCheckBox_clicked) self.previewView.titleChanged.connect(self.on_previewView_titleChanged) if self.__usesWebKit: self.previewView.linkClicked.connect( self.on_previewView_linkClicked) self.jsCheckBox.setChecked(Preferences.getUI("ShowFilePreviewJS")) self.ssiCheckBox.setChecked(Preferences.getUI("ShowFilePreviewSSI")) self.__scrollBarPositions = {} self.__vScrollBarAtEnd = {} self.__hScrollBarAtEnd = {} self.__processingThread = PreviewProcessingThread() self.__processingThread.htmlReady.connect(self.__setHtml) self.__previewedPath = None self.__previewedEditor = None def shutdown(self): """ Public method to perform shutdown actions. """ self.__processingThread.wait() @pyqtSlot(bool) def on_jsCheckBox_clicked(self, checked): """ Private slot to enable/disable JavaScript. @param checked state of the checkbox (boolean) """ Preferences.setUI("ShowFilePreviewJS", checked) self.__setJavaScriptEnabled(checked) def __setJavaScriptEnabled(self, enable): """ Private method to enable/disable JavaScript. @param enable flag indicating the enable state (boolean) """ self.jsCheckBox.setChecked(enable) settings = self.previewView.settings() settings.setAttribute(settings.JavascriptEnabled, enable) self.processEditor() @pyqtSlot(bool) def on_ssiCheckBox_clicked(self, checked): """ Private slot to enable/disable SSI. @param checked state of the checkbox (boolean) """ Preferences.setUI("ShowFilePreviewSSI", checked) self.processEditor() def processEditor(self, editor=None): """ Public slot to process an editor's text. @param editor editor to be processed (Editor) """ if editor is None: editor = self.__previewedEditor else: self.__previewedEditor = editor if editor is not None: fn = editor.getFileName() if fn: extension = os.path.normcase(os.path.splitext(fn)[1][1:]) else: extension = "" if extension in \ Preferences.getEditor("PreviewHtmlFileNameExtensions") or \ editor.getLanguage() == "HTML": language = "HTML" elif extension in \ Preferences.getEditor("PreviewMarkdownFileNameExtensions"): language = "Markdown" elif extension in \ Preferences.getEditor("PreviewRestFileNameExtensions"): language = "ReST" else: self.__setHtml( fn, self.tr( "<p>No preview available for this type of file.</p>")) return if fn: project = e5App().getObject("Project") if project.isProjectFile(fn): rootPath = project.getProjectPath() else: rootPath = os.path.dirname(os.path.abspath(fn)) else: rootPath = "" self.__processingThread.process( fn, language, editor.text(), self.ssiCheckBox.isChecked(), rootPath, Preferences.getEditor("PreviewRestUseSphinx")) def __setHtml(self, filePath, html): """ Private method to set the HTML to the view and restore the scroll bars positions. @param filePath file path of the previewed editor (string) @param html processed HTML text ready to be shown (string) """ self.__previewedPath = Utilities.normcasepath( Utilities.fromNativeSeparators(filePath)) self.__saveScrollBarPositions() if self.__usesWebKit: self.previewView.page().mainFrame().contentsSizeChanged.connect( self.__restoreScrollBarPositions) else: self.previewView.page().loadFinished.connect( self.__restoreScrollBarPositions) self.previewView.setHtml(html, baseUrl=QUrl.fromLocalFile(filePath)) if self.__previewedEditor: self.__previewedEditor.setFocus() @pyqtSlot(str) def on_previewView_titleChanged(self, title): """ Private slot to handle a change of the title. @param title new title (string) """ if title: self.titleLabel.setText(self.tr("Preview - {0}").format(title)) else: self.titleLabel.setText(self.tr("Preview")) def __saveScrollBarPositions(self): """ Private method to save scroll bar positions for a previewed editor. """ if self.__usesWebKit: frame = self.previewView.page().mainFrame() if frame.contentsSize() == QSize(0, 0): return # no valid data, nothing to save pos = frame.scrollPosition() self.__scrollBarPositions[self.__previewedPath] = pos self.__hScrollBarAtEnd[self.__previewedPath] = \ frame.scrollBarMaximum(Qt.Horizontal) == pos.x() self.__vScrollBarAtEnd[self.__previewedPath] = \ frame.scrollBarMaximum(Qt.Vertical) == pos.y() else: from PyQt5.QtCore import QPoint pos = self.__execJavaScript("(function() {" "var res = {" " x: 0," " y: 0," "};" "res.x = window.scrollX;" "res.y = window.scrollY;" "return res;" "})()") pos = QPoint(pos["x"], pos["y"]) self.__scrollBarPositions[self.__previewedPath] = pos self.__hScrollBarAtEnd[self.__previewedPath] = False self.__vScrollBarAtEnd[self.__previewedPath] = False def __restoreScrollBarPositions(self): """ Private method to restore scroll bar positions for a previewed editor. """ if self.__usesWebKit: try: self.previewView.page().mainFrame().contentsSizeChanged.\ disconnect(self.__restoreScrollBarPositions) except TypeError: # not connected, simply ignore it pass if self.__previewedPath not in self.__scrollBarPositions: return frame = self.previewView.page().mainFrame() frame.setScrollPosition( self.__scrollBarPositions[self.__previewedPath]) if self.__hScrollBarAtEnd[self.__previewedPath]: frame.setScrollBarValue(Qt.Horizontal, frame.scrollBarMaximum(Qt.Horizontal)) if self.__vScrollBarAtEnd[self.__previewedPath]: frame.setScrollBarValue(Qt.Vertical, frame.scrollBarMaximum(Qt.Vertical)) else: if self.__previewedPath not in self.__scrollBarPositions: return pos = self.__scrollBarPositions[self.__previewedPath] self.previewView.page().runJavaScript( "window.scrollTo({0}, {1});".format(pos.x(), pos.y())) @pyqtSlot(QUrl) def on_previewView_linkClicked(self, url): """ Private slot handling the clicking of a link. @param url url of the clicked link (QUrl) """ e5App().getObject("UserInterface").launchHelpViewer(url.toString()) def __execJavaScript(self, script): """ Private function to execute a JavaScript function Synchroneously. @param script JavaScript script source to be executed @type str @return result of the script @rtype depending upon script result """ from PyQt5.QtCore import QEventLoop loop = QEventLoop() resultDict = {"res": None} def resultCallback(res, resDict=resultDict): if loop and loop.isRunning(): resDict["res"] = res loop.quit() self.previewView.page().runJavaScript(script, resultCallback) loop.exec_() return resultDict["res"]
class MainWindow(QtWidgets.QMainWindow): """Main application window.""" def __init__(self, model_editor): """Initialize the class.""" super(MainWindow, self).__init__() self.setMinimumSize(960, 660) self._hsplitter = QtWidgets.QSplitter(QtCore.Qt.Horizontal, self) self._model_editor = model_editor self.setCentralWidget(self._hsplitter) # tabs self._tab = QtWidgets.QTabWidget(self._hsplitter) self._tab.setSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) self._tab.setMinimumSize(600, 200) self._tab.sizeHint = lambda: QtCore.QSize(700, 250) self.info = QWebEngineView(self._tab) self.info.setContextMenuPolicy(QtCore.Qt.NoContextMenu) self.info_page = panels.InfoPanelPage() self.info.setPage(self.info_page) """info panel""" self.err = panels.ErrorWidget(self._tab) """error message panel""" self._tab.addTab(self.info, "Structure Info") self._tab.addTab(self.err, "Messages") # debug panel if cfg.config.DEBUG_MODE: self.debug_tab = panels.DebugPanelWidget(self._tab) self._tab.addTab(self.debug_tab, "Debug") # splitters self._vsplitter = QtWidgets.QSplitter(QtCore.Qt.Vertical, self._hsplitter) self.editor = panels.YamlEditorWidget(self._vsplitter) """main editor component""" self.tree = panels.TreeWidget(self._vsplitter) """tree data display widget""" self._vsplitter.addWidget(self.editor) self._vsplitter.addWidget(self._tab) self._hsplitter.insertWidget(0, self.tree) # Menu bar self._menu = self.menuBar() self._file_menu = MainFileMenu(self, self._model_editor) self.update_recent_files(0) self._edit_menu = MainEditMenu(self, self.editor) self._settings_menu = MainSettingsMenu(self, self._model_editor) self._menu.addMenu(self._file_menu) self._menu.addMenu(self._edit_menu) self._menu.addMenu(self._settings_menu) # status bar self._column = QtWidgets.QLabel(self) self._column.setFrameStyle(QtWidgets.QFrame.StyledPanel) self._reload_icon = QtWidgets.QLabel(self) self._reload_icon.setPixmap(icon.get_pixmap("refresh", 16)) self._reload_icon.setVisible(False) self._reload_icon_timer = QtCore.QTimer(self) self._reload_icon_timer.timeout.connect(lambda: self._reload_icon.setVisible(False)) cfg.config.observers.append(self) self._status = self.statusBar() self._status.addPermanentWidget(self._reload_icon) self._status.addPermanentWidget(self._column) self.setStatusBar(self._status) self._status.showMessage("Ready", 5000) # signals self.err.itemSelected.connect(self._item_selected) self.tree.itemSelected.connect(self._item_selected) self.editor.nodeChanged.connect(self._reload_node) self.editor.cursorChanged.connect(self._cursor_changed) self.editor.structureChanged.connect(self._structure_changed) self.editor.errorMarginClicked.connect(self._error_margin_clicked) self.editor.elementChanged.connect(lambda new, old: self._update_info(new)) self.editor.nodeSelected.connect(self._on_node_selected) # initialize components self._update_info(None) self.config_changed() # set focus self.editor.setFocus() def keyPressEvent(self, event): if event.matches(QKeySequence.Copy) and self.info.selectedText() != "": QtWidgets.QApplication.clipboard().setText(self.info.selectedText()) else: super().keyReleaseEvent(event) def reload(self): """reload panels after structure changes""" self._reload_icon.setVisible(True) self._reload_icon.update() self.editor.setUpdatesEnabled(False) cfg.update() self.editor.setUpdatesEnabled(True) self.editor.reload() self.tree.reload() self.err.reload() line, index = self.editor.getCursorPosition() self._reload_node(line+1, index+1) self._reload_icon_timer.start(700) def show_status_message(self, message, duration=5000): """Show a message in status bar for the given duration (in ms).""" self._status.showMessage(message, duration) def update_recent_files(self, from_row=1): """Update recently opened files.""" self._file_menu.update_recent_files(from_row) def _item_selected(self, start_line, start_column, end_line, end_column): """Handle when an item is selected from tree or error tab. :param int start_line: line where the selection starts :param int start_column: column where the selection starts :param int end_line: line where the selection ends :param int end_column: column where the selection ends """ self.editor.setFocus() # remove empty line and whitespaces at the end of selection if end_line > start_line and end_line > 1: last_line_text = self.editor.text(end_line-1) if end_column > len(last_line_text): end_column = len(last_line_text) + 1 last_line_text_selected = last_line_text[:end_column-1] if LineAnalyzer.is_empty(last_line_text_selected): end_line -= 1 end_line_text = self.editor.text(end_line-1) end_column = len(end_line_text) # select in reversed order - move cursor to the beginning of selection self.editor.mark_selected(end_line, end_column, start_line, start_column) def _reload_node(self, line, index): """reload info after changing node selection""" node = cfg.get_data_node(Position(line, index)) self.editor.set_new_node(node) cursor_type = self.editor.cursor_type_position self._update_info(cursor_type) if cfg.config.DEBUG_MODE: self.debug_tab.show_data_node(node) def _cursor_changed(self, line, column): """Editor node change signal""" self._column.setText("Line: {:5d} Pos: {:3d}".format(line, column)) def _structure_changed(self, line, column): """Editor structure change signal""" if cfg.update_yaml_file(self.editor.text()): self.reload() else: self._reload_node(line, column) def _error_margin_clicked(self, line): """Click error icon in margin""" self._tab.setCurrentIndex(self._tab.indexOf(self.err)) self.err.select_error(line) def _update_info(self, cursor_type): """Update the info panel.""" if self.editor.pred_parent is not None: self.info_page.update_from_node(self.editor.pred_parent, CursorType.parent.value) return if self.editor.curr_node is not None: self.info_page.update_from_node(self.editor.curr_node, cursor_type) return # show root input type info by default self.info_page.update_from_data({'record_id': cfg.root_input_type['id']}, True) return def _on_node_selected(self, line, column): """Handles nodeSelected event from editor.""" node = cfg.get_data_node(Position(line, column)) self.tree.select_data_node(node) def closeEvent(self, event): """Performs actions before app is closed.""" # prompt user to save changes (if any) if not self._model_editor.save_old_file(): return event.ignore() super(MainWindow, self).closeEvent(event) def config_changed(self): """Handle changes of config.""" self.editor.set_line_endings(cfg.config.line_endings)
class Visualization(QMainWindow): themes = ['light', 'dark'] themesTip = ['切换为深色主题', '切换为浅色主题'] def __init__(self): super().__init__() self.leftTopView = None self.leftTopEcharts = False self.BottomView = None self.BottomEcharts = False self.initDataSet() self.initUi() self.loadUrl() def initUi(self): self.statusBar().showMessage('加载中...') self.setGeometry(100, 60, 600, 400) self.setWindowTitle('股票看板') self.setWindowIcon(QIcon('logo.jpg')) self.themeSetAct = QAction('更换图表主题(&T)', self) self.themeSetAct.setShortcut('Ctrl+T') # 默认浅色主题 self.themeIndex = 0 self.themeSetAct.setStatusTip(Visualization.themesTip[self.themeIndex]) self.themeSetAct.triggered.connect(self.changeTheme) menubar = self.menuBar() setMenu = menubar.addMenu('设置(&S)') setMenu.addAction(self.themeSetAct) self.widget = QWidget() self.setCentralWidget(self.widget) # 添加web view self.leftTopView = QWebEngineView() self.leftTopView.setContextMenuPolicy(Qt.NoContextMenu) self.BottomView = QWebEngineView() self.BottomView.setContextMenuPolicy(Qt.NoContextMenu) h1box = QHBoxLayout() h1box.addWidget(self.leftTopView) h1box.addWidget(RightTableView(self.data)) h1box.setStretch(0, 1) h1box.setStretch(1, 1) h2box = QHBoxLayout() # v2box = QVBoxLayout() h2box.addWidget(self.BottomView) vbox = QVBoxLayout() vbox.addLayout(h1box) vbox.addLayout(h2box) vbox.setStretch(0, 1) vbox.setStretch(1, 1) self.widget.setLayout(vbox) def resizeEvent(self, *args, **kwargs): w, h = self.width(), self.height() def changeTheme(self): self.themeIndex = (self.themeIndex + 1) % 2 self.themeSetAct.setStatusTip(Visualization.themesTip[self.themeIndex]) if not self.BottomView: return options = self.getOptions(type='K') self.BottomView.page().runJavaScript(''' myChart.dispose(); var myChart = echarts.init(document.getElementById('container'), '{theme}', {{renderer: 'canvas'}}); myChart.clear(); window.onresize = function(){{ myChart.resize(); }} var option = eval({options}); myChart.setOption(option); '''.format(theme=Visualization.themes[self.themeIndex], options=options)) if not self.leftTopView: return options = self.getOptions(type='Pie') self.leftTopView.page().runJavaScript(''' myChart.dispose(); var myChart = echarts.init(document.getElementById('container'), '{theme}', {{renderer: 'canvas'}}); myChart.clear(); window.onresize = function(){{ myChart.resize(); }} var option = eval({options}); myChart.setOption(option); '''.format(theme=Visualization.themes[self.themeIndex], options=options)) def loadUrl(self): url = QUrl("file:///template.html") self.leftTopView.load(url) self.leftTopView.loadFinished.connect(self.setOptions) self.BottomView.load(url) self.BottomView.loadFinished.connect(self.setOptions) self.statusBar().showMessage('准备就绪') def setOptions(self): if not self.BottomView: return if not self.BottomEcharts: # 初始化echarts self.BottomView.page().runJavaScript(''' var myChart = echarts.init(document.getElementById('container'), 'light', {renderer: 'canvas'}); window.onresize = function(){{ myChart.resize(); }} ''') self.BottomEcharts = True options = self.getOptions(type='K') self.BottomView.page().runJavaScript(''' var option = eval({}); myChart.setOption(option); '''.format(options)) if not self.leftTopView: return if not self.leftTopEcharts: # 初始化echarts self.leftTopView.page().runJavaScript(''' var myChart = echarts.init(document.getElementById('container'), 'light', {renderer: 'canvas'}); window.onresize = function(){{ myChart.resize(); }} ''') self.leftTopEcharts = True options = self.getOptions(type='Pie') self.leftTopView.page().runJavaScript(''' var option = eval({}); myChart.setOption(option); '''.format(options)) def getOptions(self, type): if type == None or type == 'K': return self.createKlines() elif type == 'Pie': return self.create_pie(v=self.pie_data) def createKlines(self): overlap = Overlap() for quote in self.quote_data: line = Line(quote['title']) line.add('open', quote['date'], quote['open'], is_smooth=True) line.add('close', quote['date'], quote['close'], is_smooth=True) line.add('high', quote['date'], quote['high'], is_smooth=True) line.add('low', quote['date'], quote['low'], is_smooth=True) overlap.add(line) snippet = TRANSLATOR.translate(overlap.options) options = snippet.as_snippet() return options def create_pie(self, v): pie = Pie() pie.add("昨日行情", ['涨', '平', '跌'], v, is_label_show=True) snippet = TRANSLATOR.translate(pie.options) options = snippet.as_snippet() return options def initDataSet(self): client = pymongo.MongoClient(host="localhost", port=27017) db = client['stocks'] table_basic = db['basic'] table_quotes = db['quotes'] last_weekday = get_last_WeekDay(datetime.now()) last_weekday = int(last_weekday) self.pie_data = [] self.pie_data.append( table_quotes.find({ "change": { "$gte": 0 } }).count()) self.pie_data.append(table_quotes.find({"change": 0}).count()) self.pie_data.append( table_quotes.find({ "change": { "$lte": 0 } }).count()) self.data = get_data() self.quote_data = [] stock_name = self.data[0][0][0] stock_code = self.data[0][0][1] title = f'{stock_name}_{stock_code}' date, open, close, high, low = get_selected_data(stock_code) self.quote_data.append({ 'title': title, 'date': date, 'open': open, 'close': close, 'high': high, 'low': low }) self.statusBar().showMessage('数据加载完成') print('数据加载完成')
class Form(QDialog): TITLE_TEXT = "这是大标题" TITLE_SUBTEXT = "这是副标题" ATTR = ["属性1", "属性2", "属性3", "属性4", "属性5", "属性6"] def __init__(self): super(Form, self).__init__() self.view = None self.echarts = False self.initUi() self.load_url() self.isMap = False # 初始化UI界面 def initUi(self): self.hl = QHBoxLayout(self) self.widget = QWidget() self.gl = QGridLayout(self.widget) self.isMap = False # ATTR1 #第一个输入属性值的文本框 self.QLineEdit1=QLineEdit() # 新建属性输入文本框 self.QLineEdit1.setPlaceholderText(self.ATTR[1-1]) # 默认值是“属性一” self.QLineEdit1.editingFinished.connect(self.reload_canvas) self.gl.addWidget(self.QLineEdit1,1 - 1,0,1,1)# 设置该输入框的排版位置 self.spinbox1 = QSpinBox() # 新建SpinBox self.spinbox1.setSingleStep(100)# 设置每次调节的单位 self.spinbox1.setObjectName('spinbox') self.spinbox1.valueChanged.connect(self.set_options) self.spinbox1.setMaximum(1000) self.spinbox1.setValue(randint(0, 1000)) self.gl.addWidget(self.spinbox1, 1 - 1, 1, 1, 2) # ATTR2 self.QLineEdit2=QLineEdit() self.QLineEdit2.setPlaceholderText(self.ATTR[2-1]) self.QLineEdit2.returnPressed.connect(self.reload_canvas) self.gl.addWidget(self.QLineEdit2,2 - 1,0,1,1) self.spinbox2 = QSpinBox() self.spinbox2.setSingleStep(100) self.spinbox2.setObjectName('spinbox') self.spinbox2.valueChanged.connect(self.set_options) self.spinbox2.setMaximum(1000) self.spinbox2.setValue(randint(0, 1000)) self.gl.addWidget(self.spinbox2, 2 - 1, 1, 1, 2) # ATTR3 self.QLineEdit3=QLineEdit() self.QLineEdit3.setPlaceholderText(self.ATTR[3-1]) self.QLineEdit3.returnPressed.connect(self.reload_canvas) self.gl.addWidget(self.QLineEdit3,3 - 1,0,1,1) self.spinbox3 = QSpinBox() self.spinbox3.setSingleStep(100) self.spinbox3.setObjectName('spinbox') self.spinbox3.valueChanged.connect(self.set_options) self.spinbox3.setMaximum(1000) self.spinbox3.setValue(randint(0, 1000)) self.gl.addWidget(self.spinbox3, 3 - 1, 1, 1, 2) # ATTR4 self.QLineEdit4=QLineEdit() self.QLineEdit4.setPlaceholderText(self.ATTR[4-1]) self.QLineEdit4.returnPressed.connect(self.reload_canvas) self.gl.addWidget(self.QLineEdit4,4 - 1,0,1,1) self.spinbox4 = QSpinBox() self.spinbox4.setSingleStep(100) self.spinbox4.setObjectName('spinbox') self.spinbox4.valueChanged.connect(self.set_options) self.spinbox4.setMaximum(1000) self.spinbox4.setValue(randint(0, 1000)) self.gl.addWidget(self.spinbox4, 4 - 1, 1, 1, 2) # ATTR5 self.QLineEdit5=QLineEdit() self.QLineEdit5.setPlaceholderText(self.ATTR[5-1]) self.QLineEdit5.returnPressed.connect(self.reload_canvas) self.gl.addWidget(self.QLineEdit5,5 - 1,0,1,1) self.spinbox5 = QSpinBox() self.spinbox5.setSingleStep(100) self.spinbox5.setObjectName('spinbox') self.spinbox5.valueChanged.connect(self.set_options) self.spinbox5.setMaximum(1000) self.spinbox5.setValue(randint(0, 1000)) self.gl.addWidget(self.spinbox5, 5 - 1, 1, 1, 2) # ATTR6 self.QLineEdit6=QLineEdit() self.QLineEdit6.setPlaceholderText(self.ATTR[6-1]) self.QLineEdit6.returnPressed.connect(self.reload_canvas) self.gl.addWidget(self.QLineEdit6,6 - 1,0,1,1) self.spinbox6 = QSpinBox() self.spinbox6.setSingleStep(100) self.spinbox6.setObjectName('spinbox') self.spinbox6.valueChanged.connect(self.set_options) self.spinbox6.setMaximum(1000) self.spinbox6.setValue(randint(0, 1000)) self.gl.addWidget(self.spinbox6, 6 - 1, 1, 1, 2) #属性文本框设置结束 #左侧菜单设置 self.hl.addWidget(self.widget) vs = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding) self.gl.addItem(vs, 8, 0, 1, 2) #输入大标题与副标题 #大标题 label_title = QLabel( '大标题'+ ':') self.gl.addWidget(label_title, 6, 0, 1, 2) #addWidget里的四个数字什么意思 self.QLineEdit_TITLE = QLineEdit() #查询pyqt的文本输入框是什么控件 self.QLineEdit_TITLE.setPlaceholderText(self.TITLE_TEXT) self.QLineEdit_TITLE.cursorMoveStyle='VisualMoveStyle' self.QLineEdit_TITLE.returnPressed.connect(self.reload_canvas) self.gl.addWidget(self.QLineEdit_TITLE, 6, 1, 1, 2) #小标题 label_subtitle = QLabel( '小标题'+ ':') self.gl.addWidget(label_subtitle, 7, 0, 1, 2) self.QLineEdit_SUB = QLineEdit() #查询pyqt的文本输入框是什么控件 self.QLineEdit_SUB.setPlaceholderText(self.TITLE_TEXT) self.QLineEdit_SUB.cursorMoveStyle='VisualMoveStyle' self.QLineEdit_SUB.returnPressed.connect(self.reload_canvas) self.gl.addWidget(self.QLineEdit_SUB, 7, 1, 1, 2) #输入大标题与副标题 #图例种类选择 label_kind = QLabel( '图例'+ ':') self.gl.addWidget(label_kind, 9, 0, 1, 2) self.combobox_type = QComboBox() self.combobox_type.currentIndexChanged.connect(self.reload_canvas) self.combobox_type.addItems(['饼状图', '柱状图', '折线图', '折线柱状图','桑基图','雷达图','地理热点图']) self.gl.addWidget(self.combobox_type, 9, 1, 1, 2) #主题选择按钮 label_theme = QLabel('主题' + ':') self.gl.addWidget(label_theme, 10, 0, 1, 2) self.combobox_theme = QComboBox() self.combobox_theme.addItems(['日间模式', '暗黑模式','复古香槟']) self.combobox_theme.currentTextChanged.connect(self.change_theme) self.gl.addWidget(self.combobox_theme, 10, 1, 1, 2) # 添加web view self.view = QWebEngineView() self.view.setContextMenuPolicy(Qt.NoContextMenu) self.hl.addWidget(self.view) def change_theme(self, theme): # self.isMap = False if not self.view: return options = self.get_options() if not options: return if self.combobox_theme.currentIndex()==0: theme='light' if self.combobox_theme.currentIndex()==1: theme='dark' if self.combobox_theme.currentIndex()==2: theme='vintage' self.view.page().runJavaScript( f''' myChart.dispose(); var myChart = echarts.init(document.getElementById('container'), '{theme}', {{renderer: 'canvas'}}); myChart.clear(); var option = eval({options}); myChart.setOption(option); ''' ) def load_url(self): #url = QUrl("file:///"+sys.path[0]+"/data/STEM_heatmap.html") url = QUrl("file:///"+sys.path[0]+"/template.html") # to make it adapted to any platform like MacOS,Windows,Linux #the formal version: url = QUrl("file:////Users/fangzeqiang/Desktop/PyQt_Echarts_GUI/template.html") self.view.load(url) self.view.loadFinished.connect(self.set_options) def reload_canvas(self): self.TITLE_TEXT=self.QLineEdit_TITLE.displayText() self.TITLE_SUBTEXT=self.QLineEdit_SUB.displayText() #重新渲染时,将左侧属性输入框中数据传入图表 QLineEdit_Array=[self.QLineEdit1,self.QLineEdit2,self.QLineEdit3,self.QLineEdit4,self.QLineEdit5,self.QLineEdit6] for i in range(len(QLineEdit_Array)): self.ATTR[i]= QLineEdit_Array[i].displayText() if self.ATTR[i]=='': self.ATTR[i]='属性'+str(i+1) if not self.view: return # 重载画布 options = self.get_options() if not options: return self.view.page().runJavaScript( f''' myChart.clear(); var option = eval({options}); myChart.setOption(option); ''' ) def set_options(self): if not self.view: return if not self.echarts: # 初始化echarts self.view.page().runJavaScript( ''' var myChart = echarts.init(document.getElementById('container'),'light',{renderer: 'canvas'}); ''' ) self.echarts = True options = self.get_options() if not options: return self.view.page().runJavaScript( f''' var option = eval({options}); myChart.setOption(option); ''' ) def get_options(self): v1, v2, v3, v4, v5, v6 = self.spinbox1.value(), self.spinbox2.value(), self.spinbox3.value(), self.spinbox4.value(), \ self.spinbox5.value(), self.spinbox6.value() v = [v1, v2, v3, v4, v5, v6] if self.combobox_type.currentIndex() == 0: # 饼图 options = self.create_pie(v) elif self.combobox_type.currentIndex() == 1: # 柱状图 options = self.create_bar(v) elif self.combobox_type.currentIndex() == 2: # 折线图 options = self.create_line(v) elif self.combobox_type.currentIndex() == 3: # 折线、柱状图 options = self.create_line_bar(v) elif self.combobox_type.currentIndex() == 4: # 桑基图 options=self.create_sankey(v) elif self.combobox_type.currentIndex() == 5: # 雷达图 options=self.create_radar(v) elif self.combobox_type.currentIndex() == 6: # 地理热点图 # options=self.create_map(v) return else: return return options def create_pie(self, v): pie = Pie(self.TITLE_TEXT, self.TITLE_SUBTEXT) pie.add("属性", self.ATTR, v, is_label_show=True) snippet = TRANSLATOR.translate(pie.options) options = snippet.as_snippet() return options def create_bar(self, v): bar = Bar(self.TITLE_TEXT, self.TITLE_SUBTEXT) bar.add('Science', self.ATTR, v, is_more_utils=True) bar.add('Engineering', self.ATTR, v, is_more_utils=True) bar.add('Technology', self.ATTR, v, is_more_utils=True) bar.add('Mathematics', self.ATTR, v, is_more_utils=True) snippet = TRANSLATOR.translate(bar.options) options = snippet.as_snippet() return options def create_line(self, v): line = Line(self.TITLE_TEXT, self.TITLE_SUBTEXT) line.add("属性", self.ATTR, v, is_smooth=True, mark_line=["max", "average"]) snippet = TRANSLATOR.translate(line.options) options = snippet.as_snippet() return options def create_line_bar(self, v): line = Line(self.TITLE_TEXT, self.TITLE_SUBTEXT) line.add("属性", self.ATTR, v, is_smooth=True, mark_line=["max", "average"]) bar = Bar(self.TITLE_TEXT, self.TITLE_SUBTEXT) bar.add('属性', self.ATTR, v, is_more_utils=True) overlap = Overlap() overlap.add(line) overlap.add(bar) snippet = TRANSLATOR.translate(overlap.options) options = snippet.as_snippet() return options def create_sankey(self, v): #绘制桑基图 sankey = Sankey(self.TITLE_TEXT, self.TITLE_SUBTEXT,width=1200, height=600) category1,category2,category3,category4,category5,category6=self.ATTR[0],self.ATTR[1],self.ATTR[2],self.ATTR[3],self.ATTR[4],self.ATTR[5] nodes = [ {'name': 'Java软件开发'}, {'name': '前端技术'}, {'name': '移动端技术'}, {'name': '自然语言处理'}, {'name': 'Python数据分析'}, {'name': '数据可视化'}, {'name': '量化交易'},{'name': '算法工程'},{'name': '算法优化'}, {'name': '机器学习开发'},{'name': '运维工程'},{'name': '云计算开发'}, {'name': '软件UI设计'},{'name': '计算机视觉'},{'name': '产品经理'},{'name': '深度学习'} ] links = [ {'source': 'Java软件开发', 'target': '前端技术', 'value': 5}, {'source': '前端技术', 'target': '移动端技术', 'value': 12}, {'source': 'Python数据分析', 'target': '自然语言处理', 'value': 10}, {'source': 'Python数据分析', 'target': '数据可视化', 'value': 8}, {'source': '自然语言处理', 'target': '算法工程', 'value': 15}, {'source': '算法工程', 'target': '算法优化', 'value': 15}, {'source': '自然语言处理', 'target': '算法优化', 'value': 30}, {'source': '量化交易', 'target': '算法优化', 'value': 10}, {'source': '自然语言处理', 'target': '移动端技术', 'value': 14}, {'source': '机器学习开发', 'target': '自然语言处理', 'value': 14}, {'source': '机器学习开发', 'target': '算法工程', 'value': 20}, {'source': '软件UI设计', 'target': '移动端技术', 'value': 15}, {'source': '深度学习', 'target': '计算机视觉', 'value': 12}, {'source': '计算机视觉', 'target': '算法优化', 'value': 10}, {'source': '运维工程', 'target': '云计算开发', 'value': 15}, {'source': '前端技术', 'target': '云计算开发', 'value': 20}, {'source': '云计算开发', 'target': '产品经理', 'value': 3}, {'source': '移动端技术', 'target': '产品经理', 'value': 10}, {'source': '算法优化', 'target': '产品经理', 'value': 7}, {'source': '计算机视觉', 'target': '产品经理', 'value': 6}, {'source': '数据可视化', 'target': '产品经理', 'value': 5}, ] sankey.add( "技能树", nodes, links, line_opacity=0.2, line_curve=0.5, line_color="source", is_label_show=True, label_pos="right", ) snippet = TRANSLATOR.translate(sankey.options) options = snippet.as_snippet() return options def create_radar(self, v): #绘制雷达图 radar = Radar(self.TITLE_TEXT, self.TITLE_SUBTEXT) schema = [ ("口头语言", 5), ("原创性", 5), ("问题敏感度", 5), ("逻辑思维", 5), ("数学能力", 5), ("人际沟通", 5) ] radar.config(schema) v1 = [v] v2 = [[2,3,5,4,2,1]] radar.add( "第一次推荐输入", v1, is_splitline = True, is_axisline_show = True, is_label_show=True, area_opacity = 0.2 ) radar.add( "第二次推荐输入", v2, label_color=["#4e79a7"], is_area_show= False, legend_selectedmode='single', area_opacity = 0.5 ) snippet = TRANSLATOR.translate(radar.options) options = snippet.as_snippet() return options def create_map(self, v): #绘制地理地图! self.isMap = True '''
class Visualization(QMainWindow): themes = ['light', 'dark'] themesTip = ['切换为深色主题', '切换为浅色主题'] def __init__(self): super().__init__() self.leftTopView = None self.leftTopEcharts = False self.BottomView = None self.BottomEcharts = False self.initDataSet() self.initUi() self.loadUrl() def initUi(self): self.statusBar().showMessage('加载中...') self.setGeometry(100, 60, 600, 400) self.setWindowTitle('关注微信公众号:月小水长') self.setWindowIcon(QIcon('logo.jpg')) self.themeSetAct = QAction('更换图表主题(&T)', self) self.themeSetAct.setShortcut('Ctrl+T') # 默认浅色主题 self.themeIndex = 0 self.themeSetAct.setStatusTip(Visualization.themesTip[self.themeIndex]) self.themeSetAct.triggered.connect(self.changeTheme) menubar = self.menuBar() setMenu = menubar.addMenu('设置(&S)') setMenu.addAction(self.themeSetAct) self.widget = QWidget() self.setCentralWidget(self.widget) # 添加web view self.leftTopView = QWebEngineView() self.leftTopView.setContextMenuPolicy(Qt.NoContextMenu) self.BottomView = QWebEngineView() self.BottomView.setContextMenuPolicy(Qt.NoContextMenu) h1box = QHBoxLayout() h1box.addWidget(self.leftTopView) h1box.addWidget(RightTableView()) h1box.setStretch(0, 1) h1box.setStretch(1, 1) h2box = QHBoxLayout() # v2box = QVBoxLayout() h2box.addWidget(self.BottomView) vbox = QVBoxLayout() vbox.addLayout(h1box) vbox.addLayout(h2box) vbox.setStretch(0, 1) vbox.setStretch(1, 1) self.widget.setLayout(vbox) def resizeEvent(self, *args, **kwargs): w, h = self.width(), self.height() def changeTheme(self): self.themeIndex = (self.themeIndex + 1) % 2 self.themeSetAct.setStatusTip(Visualization.themesTip[self.themeIndex]) if not self.BottomView: return options = self.getOptions(type='K') self.BottomView.page().runJavaScript(''' myChart.dispose(); var myChart = echarts.init(document.getElementById('container'), '{theme}', {{renderer: 'canvas'}}); myChart.clear(); window.onresize = function(){{ myChart.resize(); }} var option = eval({options}); myChart.setOption(option); '''.format(theme=Visualization.themes[self.themeIndex], options=options)) if not self.leftTopView: return options = self.getOptions(type='Pie') self.leftTopView.page().runJavaScript(''' myChart.dispose(); var myChart = echarts.init(document.getElementById('container'), '{theme}', {{renderer: 'canvas'}}); myChart.clear(); window.onresize = function(){{ myChart.resize(); }} var option = eval({options}); myChart.setOption(option); '''.format(theme=Visualization.themes[self.themeIndex], options=options)) def loadUrl(self): url = QUrl("file:///template.html") self.leftTopView.load(url) self.leftTopView.loadFinished.connect(self.setOptions) self.BottomView.load(url) self.BottomView.loadFinished.connect(self.setOptions) self.statusBar().showMessage('准备就绪') def setOptions(self): if not self.BottomView: return if not self.BottomEcharts: # 初始化echarts self.BottomView.page().runJavaScript(''' var myChart = echarts.init(document.getElementById('container'), 'light', {renderer: 'canvas'}); window.onresize = function(){{ myChart.resize(); }} ''') self.BottomEcharts = True options = self.getOptions(type='K') self.BottomView.page().runJavaScript(''' var option = eval({}); myChart.setOption(option); '''.format(options)) if not self.leftTopView: return if not self.leftTopEcharts: # 初始化echarts self.leftTopView.page().runJavaScript(''' var myChart = echarts.init(document.getElementById('container'), 'light', {renderer: 'canvas'}); window.onresize = function(){{ myChart.resize(); }} ''') self.leftTopEcharts = True options = self.getOptions(type='Pie') self.leftTopView.page().runJavaScript(''' var option = eval({}); myChart.setOption(option); '''.format(options)) def getOptions(self, type): if type == None or type == 'K': return self.createKlines() elif type == 'Pie': return self.create_pie(v=[3000, 600, 5000]) def createKlines(self): overlap = Overlap() for quote in self.quote_data: line = Line(quote['title']) print(quote) line.add('open', quote['date'], quote['open'], is_smooth=True) line.add('close', quote['date'], quote['close'], is_smooth=True) line.add('high', quote['date'], quote['high'], is_smooth=True) line.add('low', quote['date'], quote['low'], is_smooth=True) overlap.add(line) snippet = TRANSLATOR.translate(overlap.options) options = snippet.as_snippet() return options def create_pie(self, v): pie = Pie() pie.add("昨日行情", ['涨', '平', '跌'], v, is_label_show=True) snippet = TRANSLATOR.translate(pie.options) options = snippet.as_snippet() return options def initDataSet(self): client = pymongo.MongoClient(host="localhost", port=27017) db = client['stock'] table_basic = db['basic'] self.stock_basis = myDict.AllowKeyRepeatDict() for basic in table_basic.find(): self.stock_basis.add(key=basic['name'], value=basic['code']) self.quote_data = [] stock_name = '中信证券' stock_code = self.stock_basis.query(key=stock_name)[0] title = stock_name + '_' + stock_code table_quote = db[title] queryDateRange = [20190301, 20191130] date = [] open = [] close = [] high = [] low = [] # 为什么不能用 in 而要用 gte lte for quote in table_quote.find({ 'date': { '$gte': queryDateRange[0], '$lte': queryDateRange[1] } }).sort('date', pymongo.ASCENDING): date.append(str(quote['date'])) open.append(float(quote['open'])) close.append(float(quote['close'])) high.append(float(quote['high'])) low.append(float(quote['low'])) self.quote_data.append({ 'title': title, 'date': date, 'open': open, 'close': close, 'high': high, 'low': low }) self.statusBar().showMessage('数据加载完成') print('数据加载完成')
class PreviewerHTML(QWidget): """ Class implementing a previewer widget for HTML, Markdown and ReST files. """ def __init__(self, parent=None): """ Constructor @param parent reference to the parent widget (QWidget) """ super(PreviewerHTML, self).__init__(parent) self.__layout = QVBoxLayout(self) self.titleLabel = QLabel(self) self.titleLabel.setWordWrap(True) self.titleLabel.setTextInteractionFlags(Qt.NoTextInteraction) self.__layout.addWidget(self.titleLabel) self.__previewAvailable = True try: from PyQt5.QtWebEngineWidgets import QWebEngineView self.previewView = QWebEngineView(self) self.previewView.page().linkHovered.connect(self.__showLink) except ImportError: self.__previewAvailable = False self.titleLabel.setText(self.tr( "<b>HTML Preview is not available!<br/>" "Install QtWebEngine.</b>")) self.titleLabel.setAlignment(Qt.AlignHCenter) self.__layout.addStretch() return sizePolicy = QSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding) sizePolicy.setHorizontalStretch(0) sizePolicy.setVerticalStretch(0) sizePolicy.setHeightForWidth( self.previewView.sizePolicy().hasHeightForWidth()) self.previewView.setSizePolicy(sizePolicy) self.previewView.setContextMenuPolicy(Qt.NoContextMenu) self.previewView.setUrl(QUrl("about:blank")) self.__layout.addWidget(self.previewView) self.jsCheckBox = QCheckBox(self.tr("Enable JavaScript"), self) self.jsCheckBox.setToolTip(self.tr( "Select to enable JavaScript for HTML previews")) self.__layout.addWidget(self.jsCheckBox) self.ssiCheckBox = QCheckBox(self.tr("Enable Server Side Includes"), self) self.ssiCheckBox.setToolTip(self.tr( "Select to enable support for Server Side Includes")) self.__layout.addWidget(self.ssiCheckBox) self.jsCheckBox.clicked[bool].connect(self.on_jsCheckBox_clicked) self.ssiCheckBox.clicked[bool].connect(self.on_ssiCheckBox_clicked) self.previewView.titleChanged.connect(self.on_previewView_titleChanged) self.jsCheckBox.setChecked( Preferences.getUI("ShowFilePreviewJS")) self.ssiCheckBox.setChecked( Preferences.getUI("ShowFilePreviewSSI")) self.__scrollBarPositions = {} self.__vScrollBarAtEnd = {} self.__hScrollBarAtEnd = {} self.__processingThread = PreviewProcessingThread() self.__processingThread.htmlReady.connect(self.__setHtml) self.__previewedPath = None self.__previewedEditor = None def shutdown(self): """ Public method to perform shutdown actions. """ if self.__previewAvailable: self.__processingThread.wait() @pyqtSlot(bool) def on_jsCheckBox_clicked(self, checked): """ Private slot to enable/disable JavaScript. @param checked state of the checkbox (boolean) """ Preferences.setUI("ShowFilePreviewJS", checked) self.__setJavaScriptEnabled(checked) def __setJavaScriptEnabled(self, enable): """ Private method to enable/disable JavaScript. @param enable flag indicating the enable state (boolean) """ self.jsCheckBox.setChecked(enable) settings = self.previewView.settings() settings.setAttribute(settings.JavascriptEnabled, enable) self.processEditor() @pyqtSlot(bool) def on_ssiCheckBox_clicked(self, checked): """ Private slot to enable/disable SSI. @param checked state of the checkbox (boolean) """ Preferences.setUI("ShowFilePreviewSSI", checked) self.processEditor() @pyqtSlot(str) def __showLink(self, urlStr): """ Private slot to show the hovered link in a tooltip. @param urlStr hovered URL @type str """ QToolTip.showText(QCursor.pos(), urlStr, self.previewView) def processEditor(self, editor=None): """ Public slot to process an editor's text. @param editor editor to be processed (Editor) """ if not self.__previewAvailable: return if editor is None: editor = self.__previewedEditor else: self.__previewedEditor = editor if editor is not None: fn = editor.getFileName() if fn: extension = os.path.normcase(os.path.splitext(fn)[1][1:]) else: extension = "" if ( extension in Preferences.getEditor( "PreviewHtmlFileNameExtensions") or editor.getLanguage() == "HTML" ): language = "HTML" elif ( extension in Preferences.getEditor( "PreviewMarkdownFileNameExtensions") or editor.getLanguage().lower() == "markdown" ): language = "Markdown" elif ( extension in Preferences.getEditor( "PreviewRestFileNameExtensions") or editor.getLanguage().lower() == "restructuredtext" ): language = "ReST" else: self.__setHtml(fn, self.tr( "<p>No preview available for this type of file.</p>")) return if fn: project = e5App().getObject("Project") if project.isProjectFile(fn): rootPath = project.getProjectPath() else: rootPath = os.path.dirname(os.path.abspath(fn)) else: rootPath = "" if bool(editor.text()): self.__processingThread.process( fn, language, editor.text(), self.ssiCheckBox.isChecked(), rootPath, Preferences.getEditor("PreviewRestUseSphinx"), Preferences.getEditor("PreviewMarkdownNLtoBR"), Preferences.getEditor( "PreviewMarkdownUsePyMdownExtensions"), Preferences.getEditor("PreviewMarkdownHTMLFormat"), Preferences.getEditor("PreviewRestDocutilsHTMLFormat")) def __setHtml(self, filePath, html, rootPath): """ Private method to set the HTML to the view and restore the scroll bars positions. @param filePath file path of the previewed editor @type str @param html processed HTML text ready to be shown @type str @param rootPath path of the web site root @type str """ self.__previewedPath = Utilities.normcasepath( Utilities.fromNativeSeparators(filePath)) self.__saveScrollBarPositions() self.previewView.page().loadFinished.connect( self.__restoreScrollBarPositions) if not filePath: filePath = "/" if rootPath: baseUrl = QUrl.fromLocalFile(rootPath + "/index.html") else: baseUrl = QUrl.fromLocalFile(filePath) self.previewView.setHtml(html, baseUrl=baseUrl) if self.__previewedEditor: self.__previewedEditor.setFocus() @pyqtSlot(str) def on_previewView_titleChanged(self, title): """ Private slot to handle a change of the title. @param title new title (string) """ if title: self.titleLabel.setText(self.tr("Preview - {0}").format(title)) else: self.titleLabel.setText(self.tr("Preview")) def __saveScrollBarPositions(self): """ Private method to save scroll bar positions for a previewed editor. """ from PyQt5.QtCore import QPoint try: pos = self.previewView.scrollPosition() except AttributeError: pos = self.__execJavaScript( "(function() {" "var res = {" " x: 0," " y: 0," "};" "res.x = window.scrollX;" "res.y = window.scrollY;" "return res;" "})()" ) if pos is not None: pos = QPoint(pos["x"], pos["y"]) else: pos = QPoint(0, 0) self.__scrollBarPositions[self.__previewedPath] = pos self.__hScrollBarAtEnd[self.__previewedPath] = False self.__vScrollBarAtEnd[self.__previewedPath] = False def __restoreScrollBarPositions(self): """ Private method to restore scroll bar positions for a previewed editor. """ if self.__previewedPath not in self.__scrollBarPositions: return pos = self.__scrollBarPositions[self.__previewedPath] self.previewView.page().runJavaScript( "window.scrollTo({0}, {1});".format(pos.x(), pos.y())) def __execJavaScript(self, script): """ Private function to execute a JavaScript function Synchroneously. @param script JavaScript script source to be executed @type str @return result of the script @rtype depending upon script result """ from PyQt5.QtCore import QEventLoop loop = QEventLoop() resultDict = {"res": None} def resultCallback(res, resDict=resultDict): if loop and loop.isRunning(): resDict["res"] = res loop.quit() self.previewView.page().runJavaScript( script, resultCallback) loop.exec_() return resultDict["res"]
class Form(QWidget): def __init__(self): super(Form, self).__init__() self.view = None self.echarts = False self.initUi() self.load_url() def initUi(self): self.hl = QHBoxLayout(self) self.widget = QWidget() self.gl = QGridLayout(self.widget) # ATTR1 label1 = QLabel(ATTR[0] + ':') self.gl.addWidget(label1, 1 - 1, 0, 1, 1) self.spinbox1 = QSpinBox() self.spinbox1.setSingleStep(100) self.spinbox1.setObjectName('spinbox') self.spinbox1.valueChanged.connect(self.set_options) self.spinbox1.setMaximum(1000) self.spinbox1.setValue(randint(0, 1000)) self.gl.addWidget(self.spinbox1, 1 - 1, 1, 1, 1) # ATTR2 label2 = QLabel(ATTR[1] + ':') self.gl.addWidget(label2, 2 - 1, 0, 1, 1) self.spinbox2 = QSpinBox() self.spinbox2.setSingleStep(100) self.spinbox2.setObjectName('spinbox') self.spinbox2.valueChanged.connect(self.set_options) self.spinbox2.setMaximum(1000) self.spinbox2.setValue(randint(0, 1000)) self.gl.addWidget(self.spinbox2, 2 - 1, 1, 1, 1) # ATTR3 label3 = QLabel(ATTR[2] + ':') self.gl.addWidget(label3, 3 - 1, 0, 1, 1) self.spinbox3 = QSpinBox() self.spinbox3.setSingleStep(100) self.spinbox3.setObjectName('spinbox') self.spinbox3.valueChanged.connect(self.set_options) self.spinbox3.setMaximum(1000) self.spinbox3.setValue(randint(0, 1000)) self.gl.addWidget(self.spinbox3, 3 - 1, 1, 1, 1) # ATTR4 label4 = QLabel(ATTR[3] + ':') self.gl.addWidget(label4, 4 - 1, 0, 1, 1) self.spinbox4 = QSpinBox() self.spinbox4.setSingleStep(100) self.spinbox4.setObjectName('spinbox') self.spinbox4.valueChanged.connect(self.set_options) self.spinbox4.setMaximum(1000) self.spinbox4.setValue(randint(0, 1000)) self.gl.addWidget(self.spinbox4, 4 - 1, 1, 1, 1) # ATTR5 label5 = QLabel(ATTR[4] + ':') self.gl.addWidget(label5, 5 - 1, 0, 1, 1) self.spinbox5 = QSpinBox() self.spinbox5.setSingleStep(100) self.spinbox5.setObjectName('spinbox') self.spinbox5.valueChanged.connect(self.set_options) self.spinbox5.setMaximum(1000) self.spinbox5.setValue(randint(0, 1000)) self.gl.addWidget(self.spinbox5, 5 - 1, 1, 1, 1) # ATTR6 label6 = QLabel(ATTR[5] + ':') self.gl.addWidget(label6, 6 - 1, 0, 1, 1) self.spinbox6 = QSpinBox() self.spinbox6.setSingleStep(100) self.spinbox6.setObjectName('spinbox') self.spinbox6.valueChanged.connect(self.set_options) self.spinbox6.setMaximum(1000) self.spinbox6.setValue(randint(0, 1000)) self.gl.addWidget(self.spinbox6, 6 - 1, 1, 1, 1) self.hl.addWidget(self.widget) vs = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding) self.gl.addItem(vs, 6, 0, 1, 2) self.combobox_type = QComboBox() self.combobox_type.currentIndexChanged.connect(self.reload_canvas) self.combobox_type.addItems(['饼图', '柱状图', '折线图', '折线、柱状图']) self.gl.addWidget(self.combobox_type, 7, 0, 1, 2) self.combobox_theme = QComboBox() self.combobox_theme.currentTextChanged.connect(self.change_theme) self.combobox_theme.addItems(['light', 'dark']) self.gl.addWidget(self.combobox_theme, 8, 0, 1, 2) # 添加web view self.view = QWebEngineView() self.view.setContextMenuPolicy(Qt.NoContextMenu) self.hl.addWidget(self.view) def change_theme(self, theme): if not self.view: return options = self.get_options() if not options: return self.view.page().runJavaScript(f''' myChart.dispose(); var myChart = echarts.init(document.getElementById('container'), '{theme}', {{renderer: 'canvas'}}); myChart.clear(); var option = eval({options}); myChart.setOption(option); ''') def load_url(self): url = QUrl("file:///template.html") self.view.load(url) self.view.loadFinished.connect(self.set_options) def reload_canvas(self): if not self.view: return # 重载画布 options = self.get_options() if not options: return self.view.page().runJavaScript(f''' myChart.clear(); var option = eval({options}); myChart.setOption(option); ''') def set_options(self): if not self.view: return if not self.echarts: # 初始化echarts self.view.page().runJavaScript(''' var myChart = echarts.init(document.getElementById('container'), 'light', {renderer: 'canvas'}); ''') self.echarts = True options = self.get_options() if not options: return self.view.page().runJavaScript(f''' var option = eval({options}); myChart.setOption(option); ''') def get_options(self): v1, v2, v3, v4, v5, v6 = self.spinbox1.value(), self.spinbox2.value(), self.spinbox3.value(), self.spinbox4.value(), \ self.spinbox5.value(), self.spinbox6.value() v = [v1, v2, v3, v4, v5, v6] if self.combobox_type.currentIndex() == 0: # 饼图 options = self.create_pie(v) elif self.combobox_type.currentIndex() == 1: # 柱状图 options = self.create_bar(v) elif self.combobox_type.currentIndex() == 2: # 折线图 options = self.create_line(v) elif self.combobox_type.currentIndex() == 3: # 折线、柱状图 options = self.create_line_bar(v) else: return return options def create_pie(self, v): pie = Pie(TITLE_TEXT, TITLE_SUBTEXT) pie.add("商家", ATTR, v, is_label_show=True) snippet = TRANSLATOR.translate(pie.options) options = snippet.as_snippet() return options def create_bar(self, v): bar = Bar(TITLE_TEXT, TITLE_SUBTEXT) bar.add('商家1', ATTR, v, is_more_utils=True) bar.add('商家2', ATTR, v, is_more_utils=True) snippet = TRANSLATOR.translate(bar.options) options = snippet.as_snippet() return options def create_line(self, v): line = Line(TITLE_TEXT, TITLE_SUBTEXT) line.add("商家", ATTR, v, is_smooth=True, mark_line=["max", "average"]) snippet = TRANSLATOR.translate(line.options) options = snippet.as_snippet() return options def create_line_bar(self, v): line = Line(TITLE_TEXT, TITLE_SUBTEXT) line.add("商家", ATTR, v, is_smooth=True, mark_line=["max", "average"]) bar = Bar(TITLE_TEXT, TITLE_SUBTEXT) bar.add('商家', ATTR, v, is_more_utils=True) bar.overlap(line) snippet = TRANSLATOR.translate(bar.options) options = snippet.as_snippet() return options
class Visualization(QMainWindow): themes = ['light', 'dark'] themesTip = ['切换为深色主题', '切换为浅色主题'] def __init__(self): super().__init__() self.BottomView = None self.BottomEcharts = False self.initUi() self.js_init = False self.load_html() self.leftTopEcharts = False self.stock_datas = [] self.code = "300750" self.findData("300750") self.upKline() def initUi(self): self.statusBar().showMessage('加载中...') self.setGeometry(100, 60, 600, 400) self.setWindowTitle('王梅的股票查询分析系统') self.setWindowIcon(QIcon('logo.jpg')) self.themeSetAct = QAction('更换图表主题(&T)', self) self.themeSetAct.setShortcut('Ctrl+T') # 默认浅色主题 self.themeIndex = 0 self.themeSetAct.setStatusTip(Visualization.themesTip[self.themeIndex]) #self.themeSetAct.triggered.connect(self.changeTheme) menubar = self.menuBar() setMenu = menubar.addMenu('设置(&S)') setMenu.addAction(self.themeSetAct) self.widget = QWidget() self.setCentralWidget(self.widget) # 添加web view self.leftTopView = QWebEngineView() self.leftTopView.setContextMenuPolicy(Qt.NoContextMenu) self.BottomView = QWebEngineView() self.BottomView.setContextMenuPolicy(Qt.NoContextMenu) self.StockLineEdit = QLineEdit(" ") search_Btn = QPushButton('搜索') search_Btn.clicked.connect(self.searchStock) # 搜索布局 h0box = QHBoxLayout() h0box.addWidget(self.StockLineEdit) h0box.addWidget(search_Btn) h0box.setStretch(0, 4) h0box.setStretch(1, 1) # 左上布局 lefttopbox = QVBoxLayout() lefttopbox.addLayout(h0box) lefttopbox.addWidget(self.leftTopView) lefttopbox.setStretch(0, 1) lefttopbox.setStretch(1, 1) # 右上布局 h1box = QHBoxLayout() h1box.addLayout(lefttopbox) h1box.addWidget(RightTableView()) #h1box.addWidget(self.leftTopView) h1box.setStretch(0, 1) h1box.setStretch(1, 1) # 底部布局 h2box = QHBoxLayout() h2box.addWidget(self.BottomView) # 整个界面布局 vbox = QVBoxLayout() vbox.addLayout(h1box) vbox.addLayout(h2box) vbox.setStretch(0, 1) vbox.setStretch(1, 1) self.widget.setLayout(vbox) def load_html(self): self.BottomView.setHtml(Js.html, QtCore.QUrl("index.html")) self.BottomView.loadFinished.connect(self.set_options) def set_options(self): ''' 设置 myChart.setOption 初始化以及各项配置 ''' if not self.BottomView: # 如果浏览器对象没有创建 self.js_init = False # 将js初始化设置为假 return # 并直接返回退出 if not self.js_init: # 初始化echarts self.BottomView.page().runJavaScript( Js.Pos_Js) # 执行js页面交互,页面CSS布局和美化 self.BottomView.page().runJavaScript(Js.splitData) # 载入K线处理函数 self.BottomView.page().runJavaScript(Js.echart_init) # echart 初始化 self.BottomView.page().runJavaScript(Js.Formula_js) # 指标 self.BottomView.page().runJavaScript(f''' var KNAME, data, macd; var Zstart = 80; var Zend = 100; var MA1=0, MA2=0, MA3=0, MA4=0, MA5=0, MA6=0; var color1 = "#0CF49B"; var color2 = "#FD1050"; myChart.clear(); var option = eval({Js.Kline_js}); myChart.setOption(option); ''') self.BottomView.page().runJavaScript(Js.websize) # 页面自适应窗体 self.js_init = True def upKline(self, data=None): ''' 刷新K线 ''' if not self.BottomView: # 如果浏览器对象没有创建 return # 并直接返回退出 kdata = self.stock_datas #print(kdata) self.BottomView.page().runJavaScript(f''' KNAME= {self.code}; data = splitData({kdata}); macd = MACD(12, 26, 9, data.datas, 1); MA1 = MA(20, data.datas, 1); MA2 = MA(60, data.datas, 1); MA3 = MA(120, data.datas, 1); option = eval({Js.Kline_js}); ''') self.BottomView.page().runJavaScript(''' myChart.on('dataZoom',function(event){ if(event.batch){ Zstart=event.batch[0].start; Zend=event.batch[0].end; }else{ Zstart=event.start; Zend=event.end; }; }); myChart.setOption(option); ''') sTimer = Timer(10.0, self.upKline, (None, )).start() def searchStock(self): search_text = self.StockLineEdit.text().strip() # 获取文本框内容并去掉空格 # 药明康德 mydb = mysql.connector.connect(host="localhost", user="******", password="******", database="mydatabase", charset='utf8') mycursor = mydb.cursor() sql = "select * from stock_base \ where stock_code = '%s' or stock_name = '%s' or short_name = '%s'" % ( search_text, search_text, search_text) try: # 执行sql语句 mycursor.execute(sql) results = mycursor.fetchall() if results == None: print(results) for row in results: self.code = row[0] # 提交到数据库执行 mydb.commit() except: # Rollback in case there is any error print("find error.") mydb.rollback() self.findData(self.code) mydb.close() def addData(self, code, name): tmp_stock = "" if int(code) > 600000: tmp_stock = "sh" + code else: tmp_stock = "sz" + code stodk_data_daily = ak.stock_zh_a_daily(symbol=tmp_stock, adjust="hfq") print(stodk_data_daily) mydb = mysql.connector.connect(host="localhost", user="******", password="******", database="mydatabase", charset='utf8') mycursor = mydb.cursor() for i in range(0, len(stodk_data_daily)): # SQL 插入语句 data = [] date = [t.strftime("%Y-%m-%d") for t in stodk_data_daily.index][i] open = stodk_data_daily.iat[i, 0] high = stodk_data_daily.iat[i, 1] low = stodk_data_daily.iat[i, 2] close = stodk_data_daily.iat[i, 3] volume = stodk_data_daily.iat[i, 4] data.append(date) data.append(open) data.append(high) data.append(low) data.append(close) data.append(volume) self.stock_datas.append(data) # sql = "select * from stock_detail \ # where code = '%s' and data = '%s'" % (code,date) sql = "insert into stock_detail(code, name, date, open, high, low, close, volume) \ values('%s','%s','%s','%f','%f','%f','%f','%f')" % \ (code, name, date, open, high,low,close,volume) try: # 执行sql语句 mycursor.execute(sql) # 提交到数据库执行 mydb.commit() except: # Rollback in case there is any error mydb.rollback() mydb.close() def findData(self, code): mydb = mysql.connector.connect(host="localhost", user="******", password="******", database="mydatabase", charset='utf8') mycursor = mydb.cursor() sql = "select * from stock_detail \ where code = '%s'" % (code) try: # 执行SQL语句 mycursor.execute(sql) # 获取所有记录列表 results = mycursor.fetchall() if results == None: print(results) for row in results: data = [] #t.strftime("%Y-%m-%d") date = row[3].strftime("%Y-%m-%d") open = row[4] high = row[5] low = row[6] close = row[7] volume = row[8] data.append(date) data.append(open) data.append(high) data.append(low) data.append(close) data.append(volume) self.stock_datas.append(data) except: print("find Error: unable to fetch data") if len(self.stock_datas) == 0: self.addData(code, "") mydb.close()
class BrowserView(QMainWindow): instance = None running = False 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) def __init__(self, title, url, width, height, resizable, fullscreen, min_size, confirm_quit, background_color, debug, webview_ready): super(BrowserView, self).__init__() BrowserView.instance = self self.is_fullscreen = False self.confirm_quit = confirm_quit self._file_name_semaphor = threading.Semaphore(0) self._current_url_semaphore = threading.Semaphore() self._evaluate_js_semaphor = threading.Semaphore(0) 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) self.view.setContextMenuPolicy( QtCore.Qt.NoContextMenu) # disable right click context menu if url is not None: self.view.setUrl(QtCore.QUrl(url)) self.setCentralWidget(self.view) 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) if fullscreen: self.toggle_fullscreen() self.move(QApplication.desktop().availableGeometry().center() - self.rect().center()) self.activateWindow() self.raise_() webview_ready.set() BrowserView.running = True 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_semaphor.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() BrowserView.running = False 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_semaphor.release() try: # PyQt4 return_result(self.view.page().mainFrame().evaluateJavaScript( script).toPyObject()) except AttributeError: # PyQt5 self.view.page().runJavaScript(script, return_result) 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_url_trigger.emit(url) def load_html(self, content, base_uri): 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_semaphor.acquire() if _qt_version == 5: # 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 = (self._convert_string(self._file_name), ) elif dialog_type == SAVE_DIALOG or not allow_multiple: file_names = (self._convert_string(self._file_name[0]), ) else: file_names = tuple( [self._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.evaluate_js_trigger.emit(script) self._evaluate_js_semaphor.acquire() return self._evaluate_js_result def _convert_string(self, qstring): if sys.version < '3': return unicode(qstring) else: return str(qstring)
class FlowchartView(q.QWidget): selectRequested = qc.pyqtSignal(int) eventNameVisibilityChanged = qc.pyqtSignal(bool) eventParamVisibilityChanged = qc.pyqtSignal(bool) # View -> Core readySignal = qc.pyqtSignal() reloadedSignal = qc.pyqtSignal() eventSelected = qc.pyqtSignal(int) def __init__(self, parent, flow_data: FlowData) -> None: super().__init__(parent) self.flow_data: FlowData = flow_data self.is_current = True self.selected_event: typing.Optional[Event] = None self.showEventParams = False self.initWidgets() self.initLayout() self.connectWidgets() def initWidgets(self) -> None: self.web_object = FlowchartWebObject(self) self.flow_data.flowDataChanged.connect(self.onFlowDataChanged) self.flow_data.fileLoaded.connect(self.web_object.fileLoaded) self.selectRequested.connect(self.web_object.selectRequested) self.eventNameVisibilityChanged.connect( self.web_object.eventNameVisibilityChanged) self.eventParamVisibilityChanged.connect( self.onEventParamVisibilityChanged) self.eventParamVisibilityChanged.connect( self.web_object.eventParamVisibilityChanged) self.view = QWebEngineView() self.view.setContextMenuPolicy(qc.Qt.NoContextMenu) self.channel = QWebChannel() self.channel.registerObject('widget', self.web_object) self.view.page().setWebChannel(self.channel) self.view.page().setBackgroundColor(qg.QColor(0x38, 0x38, 0x38)) self.view.setUrl(qc.QUrl.fromLocalFile(get_path('assets/index.html'))) self.entry_point_view = q.QListView(self) self.ep_proxy_model = qc.QSortFilterProxyModel(self) self.ep_proxy_model.setSourceModel(self.flow_data.entry_point_model) self.ep_proxy_model.setFilterKeyColumn(-1) self.entry_point_view.setModel(self.ep_proxy_model) self.ep_search = SearchBar() self.ep_search.hide() self.container_model = ContainerModel(self) self.container_view = ContainerView(None, self.container_model, self.flow_data) self.container_stacked_widget = q.QStackedWidget() self.container_stacked_widget.addWidget(q.QWidget()) self.container_stacked_widget.addWidget(self.container_view) self.update_timer = qc.QTimer(self) self.update_timer.timeout.connect(self.web_object.flowDataChanged) self.update_timer.setSingleShot(True) def initLayout(self) -> None: left_pane_splitter = q.QSplitter(qc.Qt.Vertical) ep_widget = q.QWidget() ep_layout = q.QVBoxLayout(ep_widget) ep_layout.setContentsMargins(0, 0, 0, 0) ep_layout.addWidget(self.entry_point_view, stretch=1) ep_layout.addWidget(self.ep_search) left_pane_splitter.addWidget(ep_widget) left_pane_splitter.addWidget(self.container_stacked_widget) left_pane_splitter.setSizes([ int(left_pane_splitter.height() * 0.6), int(left_pane_splitter.height() * 0.4) ]) splitter = q.QSplitter() splitter.addWidget(left_pane_splitter) splitter.addWidget(self.view) splitter.setSizes( [int(splitter.width() * 0.3), int(splitter.width() * 0.7)]) layout = q.QHBoxLayout(self) layout.addWidget(splitter) layout.setContentsMargins(0, 0, 0, 0) def connectWidgets(self) -> None: self.ep_search.connectToFilterModel(self.ep_proxy_model) self.ep_search.addFindShortcut(self) self.flow_data.flowDataChanged.connect( lambda reason: self.entry_point_view.clearSelection()) self.entry_point_view.selectionModel().selectionChanged.connect( self.onEntryPointSelected) connect_model_change_signals(self.container_model, self.flow_data, FlowDataChangeReason.EventParameters) self.eventSelected.connect(self.onEventSelectedInWebView) self.flow_data.flowDataChanged.connect( lambda reason: self.refreshParamModel()) self.reloadedSignal.connect(self.onWebViewReloaded) def onEventParamVisibilityChanged(self, show: bool) -> None: self.showEventParams = show def setIsCurrentView(self, is_current: bool) -> None: self.is_current = is_current if is_current and self.update_timer.isActive(): self.update_timer.stop() self.web_object.flowDataChanged.emit() def export(self) -> None: if not self.flow_data.flow: return path = q.QFileDialog.getSaveFileName( self, 'Select a location for the graph data', self.flow_data.flow.name + '.json', 'Data (*.json)')[0] if not path: return data = self.web_object.getData() try: with open(path, 'w') as f: json.dump(data, f, default=lambda x: str(x)) except: q.QMessageBox.critical(self, 'Export graph data', 'Failed to write to ' + path) def reload(self) -> None: self.view.reload() def onWebViewReloaded(self) -> None: if not self.selected_event or not self.flow_data.flow or not self.flow_data.flow.flowchart: return try: new_idx = self.flow_data.flow.flowchart.events.index( self.selected_event) self.selectRequested.emit(new_idx) except ValueError: self.container_model.set(None) self.container_stacked_widget.setCurrentIndex(0) def refreshParamModel(self) -> bool: if self.selected_event and hasattr(self.selected_event.data, 'params'): if not self.selected_event.data.params: # type: ignore self.selected_event.data.params = Container() # type: ignore self.container_model.set( self.selected_event.data.params) # type: ignore self.container_stacked_widget.setCurrentIndex(1) return True return False def onEventSelectedInWebView(self, idx: int) -> None: if idx >= 0: event = self.flow_data.flow.flowchart.events[idx] self.selected_event = event if self.refreshParamModel(): return else: self.selected_event = None self.container_model.set(None) self.container_stacked_widget.setCurrentIndex(0) def onFlowDataChanged(self, reason: FlowDataChangeReason) -> None: should_reload = bool(reason & (FlowDataChangeReason.Reset | FlowDataChangeReason.Actors | FlowDataChangeReason.Events)) if self.showEventParams: should_reload = should_reload or bool( reason & FlowDataChangeReason.EventParameters) if not should_reload: return if self.is_current: self.web_object.flowDataChanged.emit() else: self.update_timer.start(15 * 1000) def onEntryPointSelected(self, selected, deselected) -> None: if len(selected.indexes()) != 1: return idx = selected.indexes()[0] self.selectRequested.emit(-1000 - self.ep_proxy_model.mapToSource(idx).row()) def delayedSelect(self, event: Event) -> None: try: qc.QTimer.singleShot( 1000, lambda: self.selectRequested.emit( self.flow_data.flow.flowchart.events.index(event))) except ValueError: pass def webEditEvent(self, idx: int) -> None: if idx < 0: return show_event_editor(self, self.flow_data, idx) def webAddEntryPoint(self, event_idx: int) -> None: if event_idx < 0: return ep_name, ok = q.QInputDialog.getText(self, 'Add entry point', f'Name of the new entry point:', q.QLineEdit.Normal) if not ok or not ep_name: return ep = EntryPoint(ep_name) assert self.flow_data.flow and self.flow_data.flow.flowchart ep.main_event.v = self.flow_data.flow.flowchart.events[event_idx] self.flow_data.entry_point_model.append(ep) def webRemoveEntryPoint(self, ep_idx: int) -> None: try: self.flow_data.entry_point_model.removeRow(ep_idx) except IndexError as e: q.QMessageBox.critical( self, 'Bug', f'An error has occurred: {e}\n\nPlease report this issue and mention what you were doing when this message showed up.' ) def addNewEvent(self) -> typing.Optional[Event]: return add_new_event(self, self.flow_data) def webAddEventAbove(self, parent_indices: typing.List[int], event_idx: int) -> None: if event_idx < 0: return assert self.flow_data.flow and self.flow_data.flow.flowchart event = self.flow_data.flow.flowchart.events[event_idx] parent_events = [ self.flow_data.flow.flowchart.events[i] for i in parent_indices if i >= 0 ] list_widget = CheckableEventParentListWidget(None, event, parent_events) if parent_events: dialog = q.QDialog( self, qc.Qt.WindowTitleHint | qc.Qt.WindowSystemMenuHint) dialog.setWindowTitle('Add new event above...') btn_box = q.QDialogButtonBox(q.QDialogButtonBox.Ok | q.QDialogButtonBox.Cancel) btn_box.accepted.connect(dialog.accept) btn_box.rejected.connect(dialog.reject) dialog_layout = q.QVBoxLayout(dialog) dialog_layout.addWidget( q.QLabel( 'Please select links that should be modified to point to the new event you are going to add.' )) dialog_layout.addWidget(list_widget) dialog_layout.addWidget(btn_box) ret = dialog.exec_() if not ret: return new_parent = self.addNewEvent() if not new_parent: return self._doAddEventAbove(list_widget.getSelectedEvents(), event, new_parent) self.flow_data.flowDataChanged.emit(FlowDataChangeReason.Events) self.delayedSelect(new_parent) def _doAddEventAbove(self, parents: typing.List[typing.Tuple[ Event, typing.List[typing.Any]]], event: Event, new_parent: Event) -> None: # Update the parents to point to the new parent. for parent, branches in parents: if isinstance(parent.data, ActionEvent) or isinstance( parent.data, JoinEvent) or isinstance( parent.data, SubFlowEvent): # Easy case: just set the next pointer to the new parent. parent.data.nxt.v = new_parent # For switch and fork events, update all branches that currently point to the event. elif isinstance(parent.data, SwitchEvent): for case in branches: if parent.data.cases[case].v == event: parent.data.cases[case].v = new_parent elif isinstance(parent.data, ForkEvent): for i, fork in enumerate(branches): if fork.v == event: parent.data.forks[i].v = new_parent