class Render(QWebEngineView): def __init__(self, url): QWebEngineView.__init__(self) self.loop = QEventLoop( ) #Queda en un loop hasta que acaba la carga de todas las páginas self.loadFinished.connect(self._loadFinished) self.pages = [] self.page().profile().cookieStore().deleteAllCookies() self.page().profile().setPersistentCookiesPolicy( QWebEngineProfile.NoPersistentCookies) self.load(QUrl(url)) self.loop.exec() def numPages(self): return len(self.pages) def _loadFinished(self, result): """ This is an async call, you need to wait for this to be called before closing the app """ self.page().toHtml(self._callable) def _callable(self, data): """ Se llama cada vez que una página es cargada con el html en data """ time.sleep(0.25) self.pages.append(data) self.loop.quit() #CUIDADO DEBE ESTAR EN EL ULTIMO
class QSignalWait(QObject): """ Class that waits for a QTSignal and returns its value Works only with signals with no type or type int,str,bool """ def __init__(self, signal, parent=None): super(QSignalWait, self).__init__(parent) self.state = None self.signal = signal self.loop = QEventLoop() @pyqtSlot() @pyqtSlot(bool) @pyqtSlot(str) @pyqtSlot(int) def _quit(self, state=None): self.state = state self.loop.quit() def wait(self): """Waits for a signal to be emitted. """ self.signal.connect(self._quit) self.loop.exec() self.signal.disconnect(self._quit) return self.state
class WidgetPicker(QWidget): """Widget for letting user point at another widget.""" selected = Signal() def __init__(self): super(WidgetPicker, self).__init__() self.band = QRubberBand(QRubberBand.Rectangle) self.setMouseTracking(True) self.el = QEventLoop() def mousePressEvent(self, ev): self.el.quit() self.widget = QApplication.widgetAt(ev.globalPos()) self.band.hide() def mouseMoveEvent(self, ev): widget = QApplication.widgetAt(ev.globalPos()) if widget: rect = widget.frameGeometry() if widget.parent(): rect.moveTo(widget.parent().mapToGlobal(rect.topLeft())) self.band.setGeometry(rect) self.band.show() else: self.band.hide() def run(self): self.grabMouse() try: self.el.exec_() finally: self.releaseMouse() return self.widget
class Render(QWebPage): def __init__(self, url): self.url = url self.app = QEventLoop() QWebPage.__init__(self) self.urls = [] self.frame = self.mainFrame() self.viewport = self.setViewportSize(QSize(1600, 9000)) self.network = NetworkAccessManager() self.setNetworkAccessManager(self.network) self.loadFinished.connect(self._loadFinished) self.linkClicked.connect(self.linkClick) self.settings().setAttribute(QWebSettings.AutoLoadImages, False) self.settings().setAttribute(QWebSettings.JavascriptCanOpenWindows, False) self.setLinkDelegationPolicy(2) #self.action(QWebPage.OpenLinkInNewWindow).setEnabled(True) #self.settings().clearMemoryCaches() self.mainFrame().load(QUrl(self.url)) self.network.requestSignal.connect(self.networkRequest) self.app.exec_() allFinished = pyqtSignal() def _loadFinished(self, res): iList = self.frame.findAllElements('input') for i in iList: i.evaluateJavaScript('this.click()') aList = self.frame.findAllElements('a') for a in aList: a.evaluateJavaScript('this.click()') if self.frame.blockSignals(True) == True: self.isFinished = True self.allFinished.emit() self.app.quit() self.deleteLater() def javaScriptAlert(browser, frame, message): """Notifies session for alert, then pass.""" print "signal for javaScriptAlert (class) fired" def javaScriptConfirm(browser, frame, message): #"""Notifies session for alert, then pass.""" print "signal for javaScriptConfirm (class) fired" def javaScriptPrompt(browser, frame, message): #"""Notifies session for alert, then pass.""" print "signal for javaScriptPromt (class) fired" def networkRequest(self): self.urls = self.network.request_urls print self.network.request_urls print 'allfinished' def linkClick(self, url): self.urls.append(url.url())
class Render(QWebPage): def __init__(self, url): self.url = url self.app = QEventLoop() QWebPage.__init__(self) self.urls = [url] self.frame = self.mainFrame() self.viewport = self.setViewportSize(QSize(1600, 9000)) self.network = NetworkAccessManager() self.setNetworkAccessManager(self.network) self.loadFinished.connect(self._loadFinished) self.linkClicked.connect(self.change) self.settings().setAttribute(QWebSettings.AutoLoadImages, False) self.settings().setAttribute(QWebSettings.JavascriptCanOpenWindows, True) self.setLinkDelegationPolicy(1) self.action(QWebPage.OpenLinkInNewWindow).setEnabled(True) #self.settings().clearMemoryCaches() #self.mainFrame().load(QUrl(self.url)) self.network.requestSignal.connect(self.networkRequest) self.thread = QThread() self.moveToThread(self.thread) self.thread.started.connect(self.run) self.thread.run() def run(self): self.mainFrame().load(QUrl(self.urls.pop(0))) self.app.exec_() def _loadFinished(self, res): ilist = self.frame.findAllElements('input') for i in ilist: i.evaluateJavaScript('this.click()') print i.attribute("onClick") #self.setLinkDelegationPolicy(QWebPage.DelegateAllLinks) #self.setLinkDelegationPolicy(0) alist = self.frame.findAllElements('a') for a in alist: a.evaluateJavaScript("this.click()") if self.frame.blockSignals(True) == False: print 'yes' self.app.quit() def javaScriptAlert(browser, frame, message): """Notifies session for alert, then pass.""" print "signal for javaScriptAlert (class) fired" def networkRequest(self): self.urls = self.network.request_Urls print 'allfinished' def change(self, url): self.urls.append(url.url()) print url.url() #self.setLinkDelegationPolicy(0) print 'change'
def waitForSignal(signal, message="", timeout=0): """Waits (max timeout msecs if given) for a signal to be emitted. It the waiting lasts more than 2 seconds, a progress dialog is displayed with the message. Returns True if the signal was emitted. Return False if the wait timed out or the dialog was canceled by the user. """ loop = QEventLoop() dlg = QProgressDialog(minimum=0, maximum=0, labelText=message) dlg.setWindowTitle(appinfo.appname) dlg.setWindowModality(Qt.ApplicationModal) QTimer.singleShot(2000, dlg.show) dlg.canceled.connect(loop.quit) if timeout: QTimer.singleShot(timeout, dlg.cancel) stop = lambda: loop.quit() signal.connect(stop) loop.exec_() signal.disconnect(stop) dlg.hide() dlg.deleteLater() return not dlg.wasCanceled()
def waitForSignal(signal, message="", timeout=0): """Waits (max timeout msecs if given) for a signal to be emitted. It the waiting lasts more than 2 seconds, a progress dialog is displayed with the message. Returns True if the signal was emitted. Return False if the wait timed out or the dialog was canceled by the user. """ loop = QEventLoop() dlg = QProgressDialog(minimum=0, maximum=0, labelText=message) dlg.setWindowTitle(appinfo.appname) dlg.setWindowModality(Qt.ApplicationModal) QTimer.singleShot(2000, dlg.show) dlg.canceled.connect(loop.quit) if timeout: QTimer.singleShot(timeout, dlg.cancel) stop = lambda: loop.quit() signal.connect(stop) loop.exec_() signal.disconnect(stop) dlg.hide() dlg.deleteLater() return not dlg.wasCanceled()
def checkAnswer(self): if not self.List.L[self.ITER].checkTranslate(str(self.form_widjet.line.text())): self.form_widjet.result.setText(self.List.L[self.ITER].translate) loop = QEventLoop() QTimer.singleShot(1500, lambda: loop.quit()) loop.exec_() self.form_widjet.result.setText('') self.form_widjet.line.setText('')
class BrowserPage(QWebEnginePage): def __init__(self): QWebEnginePage.__init__(self) def execute_javascript(self, script_src): self.loop = QEventLoop() self.result = QVariant() QTimer.singleShot(250, self.loop.quit) self.runJavaScript(script_src, self.callback_js) self.loop.exec_() self.loop = None return self.result def callback_js(self, res): if self.loop is not None and self.loop.isRunning(): self.result = res self.loop.quit()
class BrowserPage(QWebEnginePage): def __init__(self): QWebEnginePage.__init__(self) def hitTestContent(self, pos): return WebHitTestResult(self, pos) def mapToViewport(self, pos): return QPointF(pos.x(), pos.y()) def executeJavaScript(self, scriptSrc): self.loop = QEventLoop() self.result = QVariant() QTimer.singleShot(250, self.loop.quit) self.runJavaScript(scriptSrc, self.callbackJS) self.loop.exec_() self.loop = None return self.result def callbackJS(self, res): if self.loop is not None and self.loop.isRunning(): self.result = res self.loop.quit()
class Ace(QWebView): """ Embbeded Ace javascript web editor """ isReady = pyqtSignal(name='isReady') modificationChanged = pyqtSignal(bool) cursorPositionChanged = pyqtSignal(int, int, name='cursorPositionChanged') def __init__(self, file_info, parent=None): super(Ace, self).__init__(parent) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.parent = parent self.file_info = file_info self.language = EditorHelper.lang_from_file_info(file_info) self.waitForReady = False self.loop = QEventLoop() settings = self.settings() settings.setAttribute(QWebSettings.JavascriptCanAccessClipboard, True) settings.setAttribute(QWebSettings.DeveloperExtrasEnabled, True) self.inspector = QWebInspector(self) showInspectorAction = QAction('showInspector', self) showInspectorAction.triggered.connect(self.showInspector) self.addAction(showInspectorAction) self.modificationChanged.connect(self.modification_changed) self.main_frame().javaScriptWindowObjectCleared.connect(self.__self_js) pckg, file_name = 'ace_editor', 'ace_editor.html' resource = pkg_resources.resource_string(pckg, file_name) html_template = str(resource, 'utf-8') #insert file content with open(self.file_info.absoluteFilePath(), 'r') as f: text = f.read() text = html.escape(text) html_template = html_template.replace('{{ content }}', text) base_url = QUrl.fromLocalFile(os.path.dirname(__file__)) self.setHtml(html_template, base_url) self.modified = False if not self.waitForReady: self.loop.exec() @pyqtSlot(str, name='test', result=str) @EditorHelper.json_dumps def test(self, prefix): print(prefix) return ['plop', 'cool', 42, {'plop': 23}] def modification_changed(self, b): self.modified = b def save(self, parent, action=None): Alter.invoke_all('editor_presave', self) if self.modified: with open(self.file_info.absoluteFilePath(), 'w') as f: f.write(self.get_value()) self.original_to_current_doc() self.modificationChanged.emit(False) parent.status_bar.showMessage(self.tr("Saved file.")) Alter.invoke_all('editor_save', self) else: parent.status_bar.showMessage(self.tr("Nothing to save.")) def toggle_hidden(self, parent, action=None): self.set_show_invisibles(action.isChecked()) def toggle_soft_tabs(self, parent, action=None): self.set_use_soft_tabs(action.isChecked()) def main_frame(self): """ Convinient function to get main QWebFrame """ return self.page().mainFrame() def send_js(self, script): """ Convinient function to send javascript to ace editor """ return self.main_frame().evaluateJavaScript(script) def __self_js(self): self.main_frame().addToJavaScriptWindowObject('AceEditor', self) @pyqtSlot(name='isReady') def editor_ready(self): if self.language != None: self.set_mode(self.language.lower()) self.set_focus() if self.loop.isRunning(): self.loop.quit() self.waitForReady = True def showInspector(self): self.dialogInspector = QDialog(self) self.dialogInspector.setLayout(QVBoxLayout()) self.dialogInspector.layout().addWidget(self.inspector) self.dialogInspector.setModal(False) self.dialogInspector.show() def original_to_current_doc(self): self.send_js('editor.orignalToCurrentDoc()') def get_value(self): return self.send_js('editor.getValue()') def set_value(self, content): self.send_js('editor.setValue("{0}")'.format(content)) def set_focus(self): self.send_js('editor.focus()') def insert(self, text): self.send_js('editor.insert("{0}")'.format(text)) def set_mode(self, language): cmd = 'editor.getSession().setMode("ace/mode/{0}")' self.send_js(cmd.format(language)) def set_theme(self, theme): self.send_js('editor.setTheme("ace/theme/{0}")'.format(theme)) def get_selected_text(self): cmd = 'editor.session.getTextRange(editor.getSelectionRange())' return self.send_js(cmd) def get_cursor(self): cmd = 'editor.selection.getCursor()' return self.send_js(cmd) def got_to_line(self, line): cmd = 'editor.gotoLine({0})' self.send_js(cmd.format(line)) def get_length(self): return self.send_js('editor.session.getLength()') def set_tab_size(self, tab_size): self.send_js('editor.getSession().setTabSize({0})'.format(tab_size)) def get_tab_size(self): return self.send_js('editor.getSession().getTabSize()') def set_use_soft_tabs(self, b): b = 'true' if b else 'false' self.send_js('editor.getSession().setUseSoftTabs({0})'.format(b)) def set_font_size(self, font_size): cmd = "document.getElementById('editor').style.fontSize='{0}px'" self.send_js(cmd.format(font_size)) def set_use_wrap_mode(self, b): b = 'true' if b else 'false' cmd = "editor.getSession().setUseWrapMode({0})" self.send_js(cmd.format(b)) def set_highlight_active_line(self, b): b = 'true' if b else 'false' cmd = "editor.setHighlightActiveLine({0})" self.send_js(cmd.format(b)) def set_show_print_margin(self, b): b = 'true' if b else 'false' cmd = "editor.setShowPrintMargin({0})" self.send_js(cmd.format(b)) def set_read_only(self, b): b = 'true' if b else 'false' self.send_js('editor.setReadOnly({0})'.format(b)) def set_show_invisibles(self, b): b = 'true' if b else 'false' self.send_js('editor.setShowInvisibles({0})'.format(b))
class IntegerInputDialog(QMainWindow): """ How can I show a PyQt modal dialog and get data out of its controls once its closed? https://stackoverflow.com/questions/18196799/how-can-i-show-a-pyqt-modal-dialog-and-get-data-out-of-its-controls-once-its-clo how to clear child window reference stored in parent application when child window is closed? https://stackoverflow.com/questions/27420338/how-to-clear-child-window-reference-stored-in-parent-application-when-child-wind """ def __init__(self, parent, settings, fontOptions): super().__init__(parent) self.result = 0 self.settings = settings self.setAttribute(Qt.WA_DeleteOnClose) # QWidget::setLayout: Attempting to set QLayout “” on ProgramWindow “”, which already has a layout # https://stackoverflow.com/questions/50176661/qwidgetsetlayout-attempting-to-set-qlayout-on-programwindow-which-alre self.centralwidget = QWidget() self.setCentralWidget(self.centralwidget) self.setWindowTitle("Input a Integer value") # Initial window size/pos last saved. Use default values for first time windowScreenGeometry = self.settings.value( "integerInputWindowScreenGeometry") windowScreenState = self.settings.value( "integerInputWindowScreenState") if windowScreenGeometry: self.restoreGeometry(windowScreenGeometry) else: self.resize(600, 400) # self.move( get_screen_center( self ) ) if windowScreenState: self.restoreState(windowScreenState) # nice widget for editing the date self.textEditWidget = QPlainTextEdit(self) self.textEditWidget.setLineWrapMode(QPlainTextEdit.NoWrap) # Detect Ctrl+S ion QTextedit? # https://stackoverflow.com/questions/43010630/detect-ctrls-ion-qtextedit enterShortcut = QShortcut(QKeySequence("Ctrl+Enter"), self.textEditWidget) returnShortcut = QShortcut(QKeySequence("Ctrl+Return"), self.textEditWidget) enterShortcut.activated.connect(self.accept) returnShortcut.activated.connect(self.accept) # Change font, colour of text entry box self.textEditWidget.setStyleSheet(fontOptions) self.textEditWidget.installEventFilter(self) # Set initial value of text self.textEditWidget.document().setPlainText( "# Write here bellow, an integer with the number of steps\n\n5") self.textEditWidget.selectAll() # OK and Cancel buttons self.standardButtons = QDialogButtonBox( QDialogButtonBox.Ok | QDialogButtonBox.Cancel, Qt.Horizontal, self) self.standardButtons.accepted.connect(self.accept) self.standardButtons.rejected.connect(self.reject) # Setup the main layout self.verticalLayout = QVBoxLayout(self.centralwidget) self.horizontalLayout = QHBoxLayout() self.verticalLayout.addWidget(self.textEditWidget) self.verticalLayout.addLayout(self.horizontalLayout) self.horizontalLayout.addWidget(self.standardButtons) def accept(self): # log( 1, "Print done" ) self.result = QDialog.Accepted self.close() def reject(self): # log( 1, "Print reject" ) self.result = QDialog.Rejected self.close() def exec_(self): """ Problems with connect in pyqt5 https://stackoverflow.com/questions/33236358/problems-with-connect-in-pyqt5 """ # log( 1, "Blocking main ui" ) self.event = QEventLoop() self.setWindowModality(Qt.ApplicationModal) self.show() self.event.exec() return self.result def closeEvent(self, event=None): # log( 1, "closeEvent " ) self.settings.setValue("integerInputWindowScreenGeometry", self.saveGeometry()) self.settings.setValue("integerInputWindowScreenState", self.saveState()) # super().closeEvent( event ) self.event.quit() def keyPressEvent(self, event): """ Closes the application when the escape key is pressed. """ # log( 1, "Key pressing: %s", event.key() ) if event.key() == PyQt5.QtCore.Qt.Key_Escape: self.close() def eventFilter(self, obj, event): """ eventFilter on a QWidget with PyQt4 https://stackoverflow.com/questions/28050397/eventfilter-on-a-qwidget-with-pyqt4 """ if event.type() == PyQt5.QtCore.QEvent.KeyPress: key = event.key() # log( 1, "Key pressing: %s", event.key() ) if key == Qt.Key_Return or key == Qt.Key_Enter: self.clearTextSelection() self.accept() return super().eventFilter(obj, event) def clearTextSelection(self): textCursor = self.textEditWidget.textCursor() textCursor.clearSelection() self.textEditWidget.setTextCursor(textCursor) # static method to create the dialog and return ( date, time, accepted ) @classmethod def getNewUserInput(cls, parent, settings, fontOptions): result = 1 integer = 5 while result: dialog = IntegerInputDialog(parent, settings, fontOptions) result = dialog.exec_() if result: # log( 1, "dialog.textEditWidget.toPlainText(): %s", dialog.textEditWidget.toPlainText() ) integer = cls.convertToInteger( parent, dialog.textEditWidget.toPlainText()) if integer is not None: break else: break # log( 1, "result: %s, integer: %s", result, integer ) return (integer, result == QDialog.Accepted) @staticmethod @ignore_exceptions def convertToInteger(parent, inputString): # log( 1, "inputString: %s", inputString ) inputString = "".join(getCleanSpaces(inputString)) inputInteger = int(inputString) if inputInteger < 0: raise RuntimeError("You cannot enter negative values!") return inputInteger
class CFFEXSpider(QObject): spider_finished = pyqtSignal(str, bool) def __init__(self, *args, **kwargs): super(CFFEXSpider, self).__init__(*args, **kwargs) self.date = None self.event_loop = QEventLoop(self) # 用于网络请求同步事件阻塞 def set_date(self, date): self.date = datetime.strptime(date, '%Y-%m-%d') def get_daily_source_file(self): """ 获取每日行情数据源文件保存至本地 """ if self.date is None: raise DateValueError("请先使用`set_date`设置`CZCESpider`日期.") url = "http://www.cffex.com.cn/sj/hqsj/rtj/{}/{}/{}_1.csv".format( self.date.strftime('%Y%m'), self.date.strftime('%d'), self.date.strftime('%Y%m%d')) network_manager = getattr(qApp, "_network") request = QNetworkRequest(QUrl(url)) request.setHeader(QNetworkRequest.UserAgentHeader, random.choice(USER_AGENTS)) reply = network_manager.get(request) reply.finished.connect(self.daily_source_file_reply) def daily_source_file_reply(self): """ 获取日统计数据请求返回 """ reply = self.sender() if reply.error(): reply.deleteLater() self.spider_finished.emit("失败:" + str(reply.error()), True) return save_path = os.path.join( LOCAL_SPIDER_SRC, 'cffex/daily/{}.csv'.format(self.date.strftime("%Y-%m-%d"))) file_data = reply.readAll() file_obj = QFile(save_path) is_open = file_obj.open(QFile.WriteOnly) if is_open: file_obj.write(file_data) file_obj.close() reply.deleteLater() self.spider_finished.emit( "获取中金所{}日交易数据源文件成功!".format(self.date.strftime("%Y-%m-%d")), True) def get_rank_source_file(self): """ 获取日排名数据源文件 """ base_url = "http://www.cffex.com.cn/sj/ccpm/{}/{}/{}_1.csv" network_manager = getattr(qApp, "_network") for variety in VARIETY_LIST: url = base_url.format(self.date.strftime("%Y%m"), self.date.strftime("%d"), variety) self.spider_finished.emit("准备获取{}的日排名数据文件...".format(variety), False) request = QNetworkRequest(QUrl(url)) request.setHeader(QNetworkRequest.UserAgentHeader, random.choice(USER_AGENTS)) reply = network_manager.get(request) reply.finished.connect(self.rank_source_file_reply) time.sleep(1) self.event_loop.exec_() def rank_source_file_reply(self): """ 获取日排名请求返回 """ reply = self.sender() request_url = reply.request().url().url() # 解析出请求的品种 request_filename = request_url.rsplit("/", 1)[1] request_variety = request_filename.split("_")[0] if reply.error(): reply.deleteLater() self.spider_finished.emit( "获取{}排名数据文件。\n失败:{}".format(request_variety[:2], str(reply.error())), True) logger.error("获取{}排名数据文件失败了!".format(request_url[:2])) return save_path = os.path.join( LOCAL_SPIDER_SRC, 'cffex/rank/{}_{}.csv'.format(request_variety, self.date.strftime("%Y-%m-%d"))) file_data = reply.readAll() file_obj = QFile(save_path) is_open = file_obj.open(QFile.WriteOnly) if is_open: file_obj.write(file_data) file_obj.close() reply.deleteLater() tip = "获取中金所{}_{}日持仓排名数据保存到文件成功!".format( request_variety, self.date.strftime("%Y-%m-%d")) if request_variety == "T": tip = "获取中金所{}日所有品种持仓排名数据保存到文件成功!".format( self.date.strftime("%Y-%m-%d")) self.spider_finished.emit(tip, True) self.event_loop.quit()
class Prompt(QLabel): """Blocking prompt widget asking the user a question. The prompt is initialized with a question and displays the question, its title and the valid keybindings. Calling ``run`` blocks the UI until a valid keybinding to answer/abort the question was given. Class Attributes: BINDINGS: Valid keybindings to answer/abort the question. Attributes: question: Question object defining title, question and answer. loop: Event loop used to block the UI. """ STYLESHEET = """ QLabel { font: {prompt.font}; color: {prompt.fg}; background-color: {prompt.bg}; padding: {prompt.padding}; border-top-right-radius: {prompt.border_radius}; border-top: {prompt.border} {prompt.border.color}; border-right: {prompt.border} {prompt.border.color}; } """ BINDINGS = ( ("y", "Yes"), ("n", "No"), ("<return>", "No"), ("<escape>", "Abort"), ) def __init__(self, question: api.prompt.Question, *, parent): super().__init__(parent=parent) self.question = question self.loop = QEventLoop() styles.apply(self) header = f"<h3>{question.title}</h3>{question.body}" self.setText(header + self.bindings_table()) _logger.debug("Initialized %s", self) self.setFocus() self.adjustSize() self.raise_() self.show() def __str__(self): return f"prompt for '{self.question.title}'" @classmethod def bindings_table(cls): """Return a formatted html table with the valid keybindings.""" return utils.format_html_table( (f"<b>{utils.escape_html(binding)}</b>", command) for binding, command in cls.BINDINGS ) def run(self): """Run the blocking event loop.""" _logger.debug("Running blocking %s", self) self.loop.exec_() def update_geometry(self, _width: int, bottom: int): y = bottom - self.height() self.setGeometry(0, y, self.width(), self.height()) def leave(self, *, answer=None): """Leave the prompt by answering the question and quitting the loop.""" _logger.debug("Leaving %s with '%s'", self, answer) self.question.answer = answer self.loop.quit() self.loop.deleteLater() self.deleteLater() api.modes.current().widget.setFocus() def keyPressEvent(self, event): """Leave the prompt on a valid key binding.""" if event.key() == Qt.Key_Y: self.leave(answer=True) elif event.key() in (Qt.Key_N, Qt.Key_Return): self.leave(answer=False) elif event.key() == Qt.Key_Escape: self.leave() def focusOutEvent(self, event): """Leave the prompt without answering when unfocused.""" if self.loop.isRunning(): self.leave() super().focusOutEvent(event)
class Page(QWebEnginePage): def __init__(self, view): super(Page, self).__init__() self.parent = view.parent self.view = view self.result = QVariant() self.fullView = QWebEngineView() self.exitFSAction = QAction(self.fullView) self.loop = None def javaScriptConsoleMessage(self, level, msg, line, sourceID): """Override javaScriptConsoleMessage to use debug log.""" if level == QWebEnginePage.InfoMessageLevel: print("JS - INFO - Ligne {} : {}".format(line, msg)) elif level == QWebEnginePage.WarningMessageLevel: print("JS - WARNING - Ligne {} : {}".format(line, msg)) else: print("JS - ERROR - Ligne {} : {}".format(line, msg)) def hittestcontent(self, pos): return WebHitTestResult(self, pos) def maptoviewport(self, pos): return QPointF(pos.x(), pos.y()) def executejavascript(self, scriptsrc): self.loop = QEventLoop() self.result = QVariant() QTimer.singleShot(250, self.loop.quit) self.runJavaScript(scriptsrc, self.callbackjs) self.loop.exec_() self.loop = None return self.result def callbackjs(self, res): if self.loop is not None and self.loop.isRunning(): self.result = res self.loop.quit() def vsource(self): if "view-source:http" in self.url().toString(): self.load(QUrl(self.url().toString().split("view-source:")[1])) else: self.triggerAction(self.ViewSource) def cutaction(self): self.triggerAction(self.Cut) def copyaction(self): self.triggerAction(self.Copy) def pasteaction(self): self.triggerAction(self.Paste) def exitfs(self): self.triggerAction(self.ExitFullScreen) def makefullscreen(self, request): if request.toggleOn(): self.fullView = QWebEngineView() self.exitFSAction = QAction(self.fullView) self.exitFSAction.setShortcut(Qt.Key_Escape) self.exitFSAction.triggered.connect(self.exitfs) self.fullView.addAction(self.exitFSAction) self.setView(self.fullView) self.fullView.showFullScreen() self.fullView.raise_() else: del self.fullView self.setView(self.view) request.accept()
class Homepage(HomepageUI): """ 首页业务 """ SkipPage = pyqtSignal(str, str) def __init__(self, *args, **kwargs): super(Homepage, self).__init__(*args, **kwargs) self.event_loop = QEventLoop(self) # 滚动条的滚动移动相对于父窗口的控制button位置 self.horizontalScrollBar().valueChanged.connect(self.horizontal_scroll_value_changed) self.verticalScrollBar().valueChanged.connect(self.vertical_scroll_value_changed) """ 左侧菜单及显示业务 """ self.add_left_menus() self.left_menu.currentRowChanged.connect(self.left_menu_selected) """ 右侧广告及其他相关业务 """ self.control_buttons = list() self.get_all_advertisement() # self.slide_stacked.autoStart(msec=4000) # 自动开启得放置于填充图片之后,否则闪退 self.slide_stacked.clicked_release.connect(self.image_widget_clicked) self.slide_stacked.currentChanged.connect(self.change_button_icon) # 图片变化设置icon的背景 # 获取即时资讯板块初始化信息 self.get_instant_message() # 即时通讯内容表格的点击事件 self.instant_message_widget.content_table.cellClicked.connect(self.view_detail_instant_message) # 点击更多短信通 self.instant_message_widget.more_button.clicked.connect(self.view_more_instant_message) # 获取现货报价板块初始化信息 self.get_latest_spot_price() # 点击了现货报价的更多 self.spot_price_widget.more_button.clicked.connect(self.more_sport_price_popup) # 获取每日收盘报告 self.get_latest_daily_report() # 每日报告内容表格的点击事件 self.daily_report_widget.content_table.cellClicked.connect(self.view_detail_daily_report) # 每日收盘评论点击更多 self.daily_report_widget.more_button.clicked.connect(self.view_more_daily_report) # 获取周度报告 self.get_latest_weekly_report() # 周度报告的内容表格点击事件 self.weekly_report_widget.content_table.cellClicked.connect(self.view_detail_weekly_report) # 周度报告评论点击更多 self.weekly_report_widget.more_button.clicked.connect(self.view_more_weekly_report) # 获取月季报告 self.get_latest_monthly_report() # 月季报告的内容表格点击事件 self.monthly_report_widget.content_table.cellClicked.connect(self.view_detail_monthly_report) # 周度报告评论点击更多 self.monthly_report_widget.more_button.clicked.connect(self.view_more_monthly_report) # 获取年度报告 self.get_latest_annual_report() # 月季报告的内容表格点击事件 self.annual_report_widget.content_table.cellClicked.connect(self.view_annual_monthly_report) # 周度报告评论点击更多 self.annual_report_widget.more_button.clicked.connect(self.view_more_annual_report) def add_left_menus(self): """ 添加左侧菜单列表 """ # 遍历菜单,增加QListWidgetItem和新增stackedWidget for list_menu in HOMEPAGE_MENUS: menu_item = QListWidgetItem(list_menu["name"]) self.left_menu.addItem(menu_item) left_widget = LeftChildrenMenuWidget(list_menu["children"], self) left_widget.SelectedMenu.connect(self.left_children_menu_selected) self.left_stacked.addWidget(left_widget) item = self.left_menu.item(0) item.setSelected(True) def left_menu_selected(self, row): """ 点击了左侧的listItem """ self.left_stacked.setCurrentIndex(row) def left_children_menu_selected(self, menu_id, menu_text): """ 点击左侧的子菜单 """ print(menu_id, menu_text) # 处理菜单能在本页面完成的在本页面,如不能在本页面完成的,传出信号到主窗口去跳转 self.SkipPage.emit(menu_id, menu_text) def horizontal_scroll_value_changed(self, value): """ 横向滚动条滚动事件 """ self.control_widget.move(self.CONTROL_LEFT_DISTANCE - value, 0 - self.verticalScrollBar().value()) # 移动控制控件 def vertical_scroll_value_changed(self, value): """ 纵向滚动条滚动事件 """ self.control_widget.move(self.CONTROL_LEFT_DISTANCE - self.horizontalScrollBar().value(), 0 - value) def get_all_advertisement(self): """ 获取所有的广告信息 """ url = SERVER_API + 'advertisement/' network_manager = getattr(qApp, "_network") reply = network_manager.get(QNetworkRequest(QUrl(url))) reply.finished.connect(self.get_advertisement_reply) self.event_loop.exec_() def get_advertisement_reply(self): reply = self.sender() if reply.error(): pass else: data = json.loads(reply.readAll().data().decode("utf-8")) self.show_advertisement(data["advertisements"]) self.event_loop.quit() # 没使用同步无法加载出控制的按钮 reply.deleteLater() def show_advertisement(self, advertisements): """ 显示所有的广告""" self.control_buttons.clear() for index, ad_item in enumerate(advertisements): label = PixMapLabel(ad_item, self.slide_stacked) # 内置QThread访问图片 self.slide_stacked.addWidget(label) button = ControlButton("media/icons/empty_circle.png", "media/icons/full_circle.png", self.control_widget) if index == 0: button.setIcon(QIcon(button.hover_icon_path)) setattr(button, "image_index", index) button.clicked.connect(self.skip_to_image) self.control_buttons.append(button) self.control_widget.layout().addWidget(button) if advertisements: self.slide_stacked.autoStart(msec=4000) # def add_images(self): # """ 添加轮播的图片信息(开发GUI测试代码) """ # # 获取广告信息 # self.control_buttons.clear() # for index, name in enumerate(os.listdir('Data/Images')): # label = QLabel(self.slide_stacked) # label.setScaledContents(True) # label.setPixmap(QPixmap('Data/Images/' + name)) # self.slide_stacked.addWidget(label) # button = ControlButton("media/icons/empty_circle.png", "media/icons/full_circle.png", self.control_widget) # if index == 0: # button.setIcon(QIcon(button.hover_icon_path)) # setattr(button, "image_index", index) # button.clicked.connect(self.skip_to_image) # self.control_buttons.append(button) # self.control_widget.layout().addWidget(button) def change_button_icon(self, current_index): """ 改变button的背景icon """ for button in self.control_buttons: if getattr(button, "image_index") == current_index: button.setIcon(QIcon(button.hover_icon_path)) else: button.setIcon(QIcon(button.icon_path)) def image_widget_clicked(self): """ 点击了广告 """ current_ad = self.slide_stacked.currentWidget() ad_data = current_ad.get_ad_data() # 根据ad_data显示出来广告响应 ad_type = ad_data["ad_type"] if ad_type == "file": file_url = STATIC_URL + ad_data["filepath"] p = PDFContentPopup(file=file_url, title=ad_data["title"]) p.exec_() elif ad_type == "web": webbrowser.open_new_tab(ad_data["web_url"]) elif ad_type == "content": p = TextPopup(message=ad_data["content"]) p.setWindowTitle(ad_data["title"]) p.exec_() else: pass def skip_to_image(self): """ 跳到指定广告 """ target_index = getattr(self.sender(), "image_index") if target_index is not None: self.slide_stacked.setCurrentIndex(target_index) self.change_button_icon(target_index) def get_instant_message(self): """ 获取即时资讯(短信通) """ network_manager = getattr(qApp, "_network") url = SERVER_API + "instant-message/?count=8" reply = network_manager.get(QNetworkRequest(QUrl(url))) reply.finished.connect(self.instant_message_reply) def instant_message_reply(self): """ 即时资讯返回 """ reply = self.sender() if reply.error(): pass else: data = json.loads(reply.readAll().data().decode("utf-8")) # 将数据进行展示 self.instant_message_widget.set_contents( content_values=data["short_messages"], content_keys=["content", "time_str"], data_keys=["create_time"], resize_cols=[1], column_text_color={1: QColor(100,100,100)}, zero_text_color=[], center_alignment_columns=[] ) reply.deleteLater() def view_detail_instant_message(self, row, col): """ 查看详细的即时信息 """ if col == 0: item = self.instant_message_widget.content_table.item(row, col) create_time = item.data(Qt.UserRole).get("create_time", "未知时间") create_time = create_time.replace("T", " ")[:16] text = "<div style='text-indent:30px;line-height:25px;font-size:13px;'>" \ "<span style='font-size:15px;font-weight:bold;color:rgb(233,20,20);'>{}</span>" \ "{}</div>".format(create_time, item.text()) p = TextPopup(text, self) p.setWindowTitle("即时资讯") p.resize(550, 180) p.exec_() def get_latest_spot_price(self): """ 获取最新现货报价 """ network_manager = getattr(qApp, "_network") url = SERVER_API + "latest-spotprice/?count=8" reply = network_manager.get(QNetworkRequest(QUrl(url))) reply.finished.connect(self.latest_spot_price_reply) def latest_spot_price_reply(self): """ 最新现货报价数据返回 """ reply = self.sender() if reply.error(): pass else: data = json.loads(reply.readAll().data().decode("utf-8")) # 将数据进行展示 self.spot_price_widget.set_contents( content_values=data["sport_prices"], content_keys=["variety_zh", "spot_price", "price_increase", "date"], data_keys=[], resize_cols=[3], column_text_color={3: QColor(100, 100, 100)}, zero_text_color=[2], center_alignment_columns=[1, 2] ) reply.deleteLater() def more_sport_price_popup(self): """ 显示更多现货报价的信息 """ p = SpotPricePopup(self) p.exec_() def get_latest_daily_report(self): """ 获取最新日报信息 """ network_manager = getattr(qApp, "_network") url = SERVER_API + "latest-report/?report_type=daily&count=8" reply = network_manager.get(QNetworkRequest(QUrl(url))) reply.finished.connect(self.latest_daily_report_reply) def latest_daily_report_reply(self): """ 最新日常报告数据返回 """ reply = self.sender() if reply.error(): pass else: data = json.loads(reply.readAll().data().decode("utf-8")) # 将数据进行展示 self.daily_report_widget.set_contents( content_values=data["reports"], content_keys=["title", "date"], data_keys=["filepath"], resize_cols=[1], column_text_color={1: QColor(100, 100, 100)}, zero_text_color=[], center_alignment_columns=[] ) reply.deleteLater() def view_detail_daily_report(self, row, col): """ 查看报告的详细内容 """ if col == 0: item = self.daily_report_widget.content_table.item(row, col) title = item.text() file_url = STATIC_URL + item.data(Qt.UserRole).get("filepath", 'no-found.pdf') p = PDFContentPopup(file=file_url, title=title) p.exec_() def view_more_daily_report(self): """ 查看更多的日常报告 """ self.SkipPage.emit("l_0_0", "收盘日评") def view_more_weekly_report(self): """ 查看更多的周报 """ self.SkipPage.emit("l_0_1", "周度报告") def view_more_monthly_report(self): """ 查看更多的周报 """ self.SkipPage.emit("l_0_2", "月季报告") def view_more_annual_report(self): """ 查看更多的年度报 """ self.SkipPage.emit("l_0_3", "年度报告") def view_more_instant_message(self): """ 查看更多的年度报 """ self.SkipPage.emit("l_1_0", "短信通") def get_latest_weekly_report(self): """ 获取最新周报信息 """ network_manager = getattr(qApp, "_network") url = SERVER_API + "latest-report/?report_type=weekly&count=8" reply = network_manager.get(QNetworkRequest(QUrl(url))) reply.finished.connect(self.latest_weekly_report_reply) def latest_weekly_report_reply(self): """ 最新周度报告数据返回 """ reply = self.sender() if reply.error(): pass else: data = json.loads(reply.readAll().data().decode("utf-8")) # 将数据进行展示 self.weekly_report_widget.set_contents( content_values=data["reports"], content_keys=["title", "date"], data_keys=["filepath"], resize_cols=[1], column_text_color={1: QColor(100, 100, 100)}, zero_text_color=[], center_alignment_columns=[] ) reply.deleteLater() def view_detail_weekly_report(self, row, col): """ 查看周度报告的详细内容 """ if col == 0: item = self.weekly_report_widget.content_table.item(row, col) title = item.text() file_url = STATIC_URL + item.data(Qt.UserRole).get("filepath", 'no-found.pdf') p = PDFContentPopup(file=file_url, title=title) p.exec_() def get_latest_monthly_report(self): """ 获取最新月报信息 """ network_manager = getattr(qApp, "_network") url = SERVER_API + "latest-report/?report_type=monthly&count=8" reply = network_manager.get(QNetworkRequest(QUrl(url))) reply.finished.connect(self.latest_monthly_report_reply) def latest_monthly_report_reply(self): """ 最新月季报告数据返回 """ reply = self.sender() if reply.error(): pass else: data = json.loads(reply.readAll().data().decode("utf-8")) # 将数据进行展示 self.monthly_report_widget.set_contents( content_values=data["reports"], content_keys=["title", "date"], data_keys=["filepath"], resize_cols=[1], column_text_color={1: QColor(100, 100, 100)}, zero_text_color=[], center_alignment_columns=[] ) reply.deleteLater() def view_detail_monthly_report(self, row, col): """ 查看月度报告的详细内容 """ if col == 0: item = self.monthly_report_widget.content_table.item(row, col) title = item.text() file_url = STATIC_URL + item.data(Qt.UserRole).get("filepath", 'no-found.pdf') p = PDFContentPopup(file=file_url, title=title) p.exec_() def get_latest_annual_report(self): """ 获取最新年报半年报信息 """ network_manager = getattr(qApp, "_network") url = SERVER_API + "latest-report/?report_type=annual&count=8" reply = network_manager.get(QNetworkRequest(QUrl(url))) reply.finished.connect(self.latest_annual_report_reply) def latest_annual_report_reply(self): """ 最新年报半年报数据返回 """ reply = self.sender() if reply.error(): pass else: data = json.loads(reply.readAll().data().decode("utf-8")) # 将数据进行展示 self.annual_report_widget.set_contents( content_values=data["reports"], content_keys=["title", "date"], data_keys=["filepath"], resize_cols=[1], column_text_color={1: QColor(100, 100, 100)}, zero_text_color=[], center_alignment_columns=[] ) reply.deleteLater() def view_annual_monthly_report(self, row, col): """ 查看年报半年报的详细内容 """ if col == 0: item = self.annual_report_widget.content_table.item(row, col) title = item.text() file_url = STATIC_URL + item.data(Qt.UserRole).get("filepath", 'no-found.pdf') p = PDFContentPopup(file=file_url, title=title) p.exec_()
class SwissLocatorFilter(QgsLocatorFilter): HEADERS = { b'User-Agent': b'Mozilla/5.0 QGIS Swiss Geoportal Locator Filter' } message_emitted = pyqtSignal(str, str, Qgis.MessageLevel, QWidget) def __init__(self, filter_type: FilterType, iface: QgisInterface = None, crs: str = None): """" :param filter_type: the type of filter :param locale_lang: the language of the locale. :param iface: QGIS interface, given when on the main thread (which will display/trigger results), None otherwise :param crs: if iface is not given, it shall be provided, see clone() """ super().__init__() self.type = filter_type self.rubber_band = None self.feature_rubber_band = None self.iface = iface self.map_canvas = None self.settings = Settings() self.transform_ch = None self.transform_4326 = None self.map_tip = None self.current_timer = None self.crs = None self.event_loop = None self.result_found = False self.access_managers = {} self.nam_map_tip = None self.nam_fetch_feature = None if crs: self.crs = crs self.lang = get_language() self.searchable_layers = searchable_layers(self.lang, restrict=True) if iface is not None: # happens only in main thread self.map_canvas = iface.mapCanvas() self.map_canvas.destinationCrsChanged.connect( self.create_transforms) self.rubber_band = QgsRubberBand(self.map_canvas, QgsWkbTypes.PointGeometry) self.rubber_band.setColor(QColor(255, 255, 50, 200)) self.rubber_band.setIcon(self.rubber_band.ICON_CIRCLE) self.rubber_band.setIconSize(15) self.rubber_band.setWidth(4) self.rubber_band.setBrushStyle(Qt.NoBrush) self.feature_rubber_band = QgsRubberBand( self.map_canvas, QgsWkbTypes.PolygonGeometry) self.feature_rubber_band.setColor(QColor(255, 50, 50, 200)) self.feature_rubber_band.setFillColor(QColor(255, 255, 50, 160)) self.feature_rubber_band.setBrushStyle(Qt.SolidPattern) self.feature_rubber_band.setLineStyle(Qt.SolidLine) self.feature_rubber_band.setWidth(4) self.create_transforms() def name(self): return '{}_{}'.format(self.__class__.__name__, FilterType(self.type).name) def clone(self): return SwissLocatorFilter(self.type, crs=self.crs) def priority(self): return self.settings.value( '{type}_priority'.format(type=self.type.value)) def displayName(self): if self.type is FilterType.Location: return self.tr('Swiss Geoportal locations') elif self.type is FilterType.WMS: return self.tr('Swiss Geoportal / opendata.swiss WMS layers') elif self.type is FilterType.Feature: return self.tr('Swiss Geoportal features') else: raise NameError('Filter type is not valid.') def prefix(self): if self.type is FilterType.Location: return 'chl' elif self.type is FilterType.WMS: return 'chw' elif self.type is FilterType.Feature: return 'chf' else: raise NameError('Filter type is not valid.') def clearPreviousResults(self): self.rubber_band.reset(QgsWkbTypes.PointGeometry) self.feature_rubber_band.reset(QgsWkbTypes.PolygonGeometry) if self.map_tip is not None: del self.map_tip self.map_tip = None if self.current_timer is not None: self.current_timer.stop() self.current_timer.deleteLater() self.current_timer = None def hasConfigWidget(self): return True def openConfigWidget(self, parent=None): dlg = ConfigDialog(parent) wid = dlg.findChild(QTabWidget, "tabWidget", Qt.FindDirectChildrenOnly) tab = wid.findChild(QWidget, self.type.value) wid.setCurrentWidget(tab) dlg.exec_() def create_transforms(self): # this should happen in the main thread self.crs = self.settings.value('crs') if self.crs == 'project': map_crs = self.map_canvas.mapSettings().destinationCrs() if map_crs.isValid(): self.crs = map_crs.authid().split(':')[1] if self.crs not in AVAILABLE_CRS: self.crs = '2056' assert self.crs in AVAILABLE_CRS src_crs_ch = QgsCoordinateReferenceSystem('EPSG:{}'.format(self.crs)) assert src_crs_ch.isValid() dst_crs = self.map_canvas.mapSettings().destinationCrs() self.transform_ch = QgsCoordinateTransform(src_crs_ch, dst_crs, QgsProject.instance()) src_crs_4326 = QgsCoordinateReferenceSystem('EPSG:4326') self.transform_4326 = QgsCoordinateTransform(src_crs_4326, dst_crs, QgsProject.instance()) def group_info(self, group: str) -> (str, str): groups = { 'zipcode': { 'name': self.tr('ZIP code'), 'layer': 'ch.swisstopo-vd.ortschaftenverzeichnis_plz' }, 'gg25': { 'name': self.tr('Municipal boundaries'), 'layer': 'ch.swisstopo.swissboundaries3d-gemeinde-flaeche.fill' }, 'district': { 'name': self.tr('District'), 'layer': 'ch.swisstopo.swissboundaries3d-bezirk-flaeche.fill' }, 'kantone': { 'name': self.tr('Cantons'), 'layer': 'ch.swisstopo.swissboundaries3d-kanton-flaeche.fill' }, 'gazetteer': { 'name': self.tr('Index'), 'layer': 'ch.swisstopo.swissnames3d' }, # there is also: ch.bav.haltestellen-oev ? 'address': { 'name': self.tr('Address'), 'layer': 'ch.bfs.gebaeude_wohnungs_register' }, 'parcel': { 'name': self.tr('Parcel'), 'layer': None } } if group not in groups: self.info('Could not find group {} in dictionary'.format(group)) return None, None return groups[group]['name'], groups[group]['layer'] @staticmethod def rank2priority(rank) -> float: """ Translate the rank from geoportal to the priority of the result see https://api3.geo.admin.ch/services/sdiservices.html#search :param rank: an integer from 1 to 7 :return: the priority as a float from 0 to 1, 1 being a perfect match """ return float(-rank / 7 + 1) @staticmethod def box2geometry(box: str) -> QgsRectangle: """ Creates a rectangle from a Box definition as string :param box: the box as a string :return: the rectangle """ coords = re.findall(r'\b(\d+(?:\.\d+)?)\b', box) if len(coords) != 4: raise InvalidBox('Could not parse: {}'.format(box)) return QgsRectangle(float(coords[0]), float(coords[1]), float(coords[2]), float(coords[3])) @staticmethod def url_with_param(url, params) -> str: url = QUrl(url) q = QUrlQuery(url) for key, value in params.items(): q.addQueryItem(key, value) url.setQuery(q) return url.url() def fetchResults(self, search: str, context: QgsLocatorContext, feedback: QgsFeedback): try: self.dbg_info("start Swiss locator search...") if len(search) < 2: return if len(search) < 4 and self.type is FilterType.Feature: return self.result_found = False swisstopo_base_url = 'https://api3.geo.admin.ch/rest/services/api/SearchServer' swisstopo_base_params = { 'type': self.type.value, 'searchText': str(search), 'returnGeometry': 'true', 'lang': self.lang, 'sr': self.crs, 'limit': str( self.settings.value( '{type}_limit'.format(type=self.type.value))) # bbox Must be provided if the searchText is not. # A comma separated list of 4 coordinates representing # the bounding box on which features should be filtered (SRID: 21781). } # Locations, WMS layers if self.type is not FilterType.Feature: nam = NetworkAccessManager() feedback.canceled.connect(nam.abort) search_urls = [(swisstopo_base_url, swisstopo_base_params)] if self.settings.value('layers_include_opendataswiss' ) and self.type is FilterType.WMS: search_urls.append( ('https://opendata.swiss/api/3/action/package_search?', { 'q': 'q=WMS+%C3' + str(search) })) for (swisstopo_base_url, swisstopo_base_params) in search_urls: swisstopo_base_url = self.url_with_param( swisstopo_base_url, swisstopo_base_params) self.dbg_info(swisstopo_base_url) try: (response, content) = nam.request(swisstopo_base_url, headers=self.HEADERS, blocking=True) self.handle_response(response, search, feedback) except RequestsExceptionUserAbort: pass except RequestsException as err: self.info(err) # Feature search else: # Feature search is split in several requests # otherwise URL is too long self.access_managers = {} try: layers = list(self.searchable_layers.keys()) assert len(layers) > 0 step = 30 for l in range(0, len(layers), step): last = min(l + step - 1, len(layers) - 1) swisstopo_base_params['features'] = ','.join( layers[l:last]) self.access_managers[self.url_with_param( swisstopo_base_url, swisstopo_base_params)] = None except IOError: self.info( 'Layers data file not found. Please report an issue.', Qgis.Critical) # init event loop # wait for all requests to end self.event_loop = QEventLoop() def reply_finished(response): self.handle_response(response, search, feedback) if response.url in self.access_managers: self.access_managers[response.url] = None for nam in self.access_managers.values(): if nam is not None: return self.event_loop.quit() feedback.canceled.connect(self.event_loop.quit) # init the network access managers, create the URL for swisstopo_base_url in self.access_managers: self.dbg_info(swisstopo_base_url) nam = NetworkAccessManager() self.access_managers[swisstopo_base_url] = nam nam.finished.connect(reply_finished) nam.request(swisstopo_base_url, headers=self.HEADERS, blocking=False) feedback.canceled.connect(nam.abort) # Let the requests end and catch all exceptions (and clean up requests) if len(self.access_managers) > 0: try: self.event_loop.exec_( QEventLoop.ExcludeUserInputEvents) except RequestsExceptionUserAbort: pass except RequestsException as err: self.info(str(err)) if not self.result_found: result = QgsLocatorResult() result.filter = self result.displayString = self.tr('No result found.') result.userData = NoResult().as_definition() self.resultFetched.emit(result) except Exception as e: self.info(e, Qgis.Critical) exc_type, exc_obj, exc_traceback = sys.exc_info() filename = os.path.split( exc_traceback.tb_frame.f_code.co_filename)[1] self.info( '{} {} {}'.format(exc_type, filename, exc_traceback.tb_lineno), Qgis.Critical) self.info( traceback.print_exception(exc_type, exc_obj, exc_traceback), Qgis.Critical) def handle_response(self, response, search: str, feedback: QgsFeedback): try: if response.status_code != 200: if not isinstance(response.exception, RequestsExceptionUserAbort): self.info( "Error in main response with status code: {} from {}". format(response.status_code, response.url)) return data = json.loads(response.content.decode('utf-8')) # self.dbg_info(data) if self.is_opendata_swiss_response(data): visited_capabilities = [] for loc in data['result']['results']: display_name = loc['title'].get(self.lang, "") if not display_name: # Fallback to german display_name = loc['title']['de'] for res in loc['resources']: url = res['url'] url_components = urlparse(url) wms_url = url_components.scheme + '://' + url_components.netloc + '/' + url_components.path + '?' result = QgsLocatorResult() result.filter = self result.group = 'opendata.swiss' result.icon = QgsApplication.getThemeIcon( "/mActionAddWmsLayer.svg") if 'wms' in url.lower(): if res['media_type'] == 'WMS': result.displayString = display_name result.description = url if res['title']['de'] == 'GetMap': layers = parse_qs( url_components.query)['LAYERS'] result.userData = WMSLayerResult( layer=layers[0], title=display_name, url=wms_url).as_definition() self.result_found = True self.resultFetched.emit(result) elif 'request=getcapabilities' in url.lower( ) and url_components.netloc not in visited_capabilities: visited_capabilities.append( url_components.netloc) def parse_capabilities_result(response): capabilities = ET.fromstring( response.content) # Get xml namespace match = re.match(r'\{.*\}', capabilities.tag) namespace = match.group(0) if match else '' # Search for layers containing the search term in the name or title for layer in capabilities.findall( './/{}Layer'.format(namespace)): layername = self.find_text( layer, '{}Name'.format(namespace)) layertitle = self.find_text( layer, '{}Title'.format(namespace)) if layername and ( search in layername.lower() or search in layertitle.lower()): if not layertitle: layertitle = layername result.displayString = layertitle result.description = '{}?LAYERS={}'.format( url.replace( 'GetCapabilities', 'GetMap'), layername) result.userData = WMSLayerResult( layer=layername, title=layertitle, url=wms_url).as_definition() self.result_found = True self.resultFetched.emit(result) self.event_loop.quit() # Retrieve Capabilities xml self.event_loop = QEventLoop() nam = NetworkAccessManager() nam.finished.connect(parse_capabilities_result) nam.request(url, headers=self.HEADERS, blocking=False) feedback.canceled.connect(self.event_loop.quit) try: self.event_loop.exec_( QEventLoop.ExcludeUserInputEvents) except RequestsExceptionUserAbort: pass except RequestsException as err: self.info(err) else: for loc in data['results']: self.dbg_info("keys: {}".format(loc['attrs'].keys())) result = QgsLocatorResult() result.filter = self result.group = 'Swiss Geoportal' if loc['attrs']['origin'] == 'layer': # available keys: ['origin', 'lang', 'layer', 'staging', 'title', 'topics', 'detail', 'label', 'id'] for key, val in loc['attrs'].items(): self.dbg_info('{}: {}'.format(key, val)) result.displayString = loc['attrs']['title'] result.description = loc['attrs']['layer'] result.userData = WMSLayerResult( layer=loc['attrs']['layer'], title=loc['attrs']['title'], url='http://wms.geo.admin.ch/?VERSION%3D2.0.0' ).as_definition() result.icon = QgsApplication.getThemeIcon( "/mActionAddWmsLayer.svg") self.result_found = True self.resultFetched.emit(result) elif loc['attrs']['origin'] == 'feature': for key, val in loc['attrs'].items(): self.dbg_info('{}: {}'.format(key, val)) layer = loc['attrs']['layer'] point = QgsPointXY(loc['attrs']['lon'], loc['attrs']['lat']) if layer in self.searchable_layers: layer_display = self.searchable_layers[layer] else: self.info( self. tr('Layer {} is not in the list of searchable layers.' ' Please report issue.'.format(layer)), Qgis.Warning) layer_display = layer result.group = layer_display result.displayString = loc['attrs']['detail'] result.userData = FeatureResult( point=point, layer=layer, feature_id=loc['attrs'] ['feature_id']).as_definition() result.icon = QIcon( ":/plugins/swiss_locator/icons/swiss_locator.png") self.result_found = True self.resultFetched.emit(result) else: # locations for key, val in loc['attrs'].items(): self.dbg_info('{}: {}'.format(key, val)) group_name, group_layer = self.group_info( loc['attrs']['origin']) if 'layerBodId' in loc['attrs']: self.dbg_info("layer: {}".format( loc['attrs']['layerBodId'])) if 'featureId' in loc['attrs']: self.dbg_info("feature: {}".format( loc['attrs']['featureId'])) result.displayString = strip_tags( loc['attrs']['label']) # result.description = loc['attrs']['detail'] # if 'featureId' in loc['attrs']: # result.description = loc['attrs']['featureId'] result.group = group_name result.userData = LocationResult( point=QgsPointXY(loc['attrs']['y'], loc['attrs']['x']), bbox=self.box2geometry( loc['attrs']['geom_st_box2d']), layer=group_layer, feature_id=loc['attrs']['featureId'] if 'featureId' in loc['attrs'] else None, html_label=loc['attrs']['label']).as_definition() result.icon = QIcon( ":/plugins/swiss_locator/icons/swiss_locator.png") self.result_found = True self.resultFetched.emit(result) except Exception as e: self.info(str(e), Qgis.Critical) exc_type, exc_obj, exc_traceback = sys.exc_info() filename = os.path.split( exc_traceback.tb_frame.f_code.co_filename)[1] self.info( '{} {} {}'.format(exc_type, filename, exc_traceback.tb_lineno), Qgis.Critical) self.info( traceback.print_exception(exc_type, exc_obj, exc_traceback), Qgis.Critical) def triggerResult(self, result: QgsLocatorResult): # this should be run in the main thread, i.e. mapCanvas should not be None # remove any map tip self.clearPreviousResults() user_data = NoResult try: swiss_result = result_from_data(result) except SystemError: self.message_emitted.emit( self.displayName(), self. tr('QGIS Swiss Locator encountered an error. Please <b>update to QGIS 3.16.2</b> or newer.' ), Qgis.Warning, None) if type(swiss_result) == NoResult: return # WMS if type(swiss_result) == WMSLayerResult: url_with_params = 'contextualWMSLegend=0' \ '&crs=EPSG:{crs}' \ '&dpiMode=7' \ '&featureCount=10' \ '&format=image/png' \ '&layers={layer}' \ '&styles=' \ '&url={url}' \ .format(crs=self.crs, layer=swiss_result.layer, url=swiss_result.url) wms_layer = QgsRasterLayer(url_with_params, result.displayString, 'wms') label = QLabel() label.setTextFormat(Qt.RichText) label.setTextInteractionFlags(Qt.TextBrowserInteraction) label.setOpenExternalLinks(True) if 'geo.admin.ch' in swiss_result.url.lower(): label.setText( '<a href="https://map.geo.admin.ch/' '?lang={}&bgLayer=ch.swisstopo.pixelkarte-farbe&layers={}">' 'Open layer in map.geo.admin.ch</a>'.format( self.lang, swiss_result.layer)) if not wms_layer.isValid(): msg = self.tr('Cannot load WMS layer: {} ({})'.format( swiss_result.title, swiss_result.layer)) level = Qgis.Warning self.info(msg, level) else: msg = self.tr('WMS layer added to the map: {} ({})'.format( swiss_result.title, swiss_result.layer)) level = Qgis.Info QgsProject.instance().addMapLayer(wms_layer) self.message_emitted.emit(self.displayName(), msg, level, label) # Feature elif type(swiss_result) == FeatureResult: point = QgsGeometry.fromPointXY(swiss_result.point) point.transform(self.transform_4326) self.highlight(point) if self.settings.value('show_map_tip'): self.show_map_tip(swiss_result.layer, swiss_result.feature_id, point) # Location else: point = QgsGeometry.fromPointXY(swiss_result.point) if swiss_result.bbox.isNull(): bbox = None else: bbox = QgsGeometry.fromRect(swiss_result.bbox) bbox.transform(self.transform_ch) layer = swiss_result.layer feature_id = swiss_result.feature_id if not point: return point.transform(self.transform_ch) self.highlight(point, bbox) if layer and feature_id: self.fetch_feature(layer, feature_id) if self.settings.value('show_map_tip'): self.show_map_tip(layer, feature_id, point) else: self.current_timer = QTimer() self.current_timer.timeout.connect(self.clearPreviousResults) self.current_timer.setSingleShot(True) self.current_timer.start(5000) def highlight(self, point, bbox=None): if bbox is None: bbox = point self.rubber_band.reset(QgsWkbTypes.PointGeometry) self.rubber_band.addGeometry(point, None) rect = bbox.boundingBox() rect.scale(1.1) self.map_canvas.setExtent(rect) self.map_canvas.refresh() def fetch_feature(self, layer, feature_id): # Try to get more info self.nam_fetch_feature = NetworkAccessManager() url_detail = 'https://api3.geo.admin.ch/rest/services/api/MapServer/{layer}/{feature_id}' \ .format(layer=layer, feature_id=feature_id) params = {'lang': self.lang, 'sr': self.crs} url_detail = self.url_with_param(url_detail, params) self.dbg_info(url_detail) self.nam_fetch_feature.finished.connect(self.parse_feature_response) self.nam_fetch_feature.request(url_detail, headers=self.HEADERS, blocking=False) def parse_feature_response(self, response): if response.status_code != 200: if not isinstance(response.exception, RequestsExceptionUserAbort): self.info( "Error in feature response with status code: {} from {}". format(response.status_code, response.url)) return data = json.loads(response.content.decode('utf-8')) self.dbg_info(data) if 'feature' not in data or 'geometry' not in data['feature']: return if 'rings' in data['feature']['geometry']: rings = data['feature']['geometry']['rings'] self.dbg_info(rings) for r in range(0, len(rings)): for p in range(0, len(rings[r])): rings[r][p] = QgsPointXY(rings[r][p][0], rings[r][p][1]) geometry = QgsGeometry.fromPolygonXY(rings) geometry.transform(self.transform_ch) self.feature_rubber_band.reset(QgsWkbTypes.PolygonGeometry) self.feature_rubber_band.addGeometry(geometry, None) def show_map_tip(self, layer, feature_id, point): if layer and feature_id: url_html = 'https://api3.geo.admin.ch/rest/services/api/MapServer/{layer}/{feature_id}/htmlPopup' \ .format(layer=layer, feature_id=feature_id) params = {'lang': self.lang, 'sr': self.crs} url_html = self.url_with_param(url_html, params) self.dbg_info(url_html) self.nam_map_tip = NetworkAccessManager() self.nam_map_tip.finished.connect( lambda response: self.parse_map_tip_response(response, point)) self.nam_map_tip.request(url_html, headers=self.HEADERS, blocking=False) def parse_map_tip_response(self, response, point): if response.status_code != 200: if not isinstance(response.exception, RequestsExceptionUserAbort): self.info( "Error in map tip response with status code: {} from {}". format(response.status_code, response.url)) return self.dbg_info(response.content.decode('utf-8')) self.map_tip = MapTip(self.iface, response.content.decode('utf-8'), point.asPoint()) self.map_tip.closed.connect(self.clearPreviousResults) def info(self, msg="", level=Qgis.Info): self.logMessage(str(msg), level) def dbg_info(self, msg=""): if DEBUG: self.info(msg) @staticmethod def break_camelcase(identifier): matches = re.finditer( '.+?(?:(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])|$)', identifier) return ' '.join([m.group(0) for m in matches]) def is_opendata_swiss_response(self, json): return 'opendata.swiss' in json.get("help", []) def find_text(self, xmlElement, match): node = xmlElement.find(match) return node.text if node is not None else ''
class Ace(QWebView): """ Embbeded Ace javascript web editor """ isReady = pyqtSignal(name='isReady') modificationChanged = pyqtSignal(bool) cursorPositionChanged = pyqtSignal(int, int, name='cursorPositionChanged') def __init__(self, file_info, parent=None): super(Ace, self).__init__(parent) self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) self.parent = parent self.file_info = file_info self.language = EditorHelper.lang_from_file_info(file_info) self.waitForReady = False self.loop = QEventLoop() settings = self.settings() settings.setAttribute(QWebSettings.JavascriptCanAccessClipboard, True) settings.setAttribute(QWebSettings.DeveloperExtrasEnabled, True) self.inspector = QWebInspector(self) showInspectorAction = QAction('showInspector', self) showInspectorAction.triggered.connect(self.showInspector) self.addAction(showInspectorAction) self.modificationChanged.connect(self.modification_changed) self.main_frame().javaScriptWindowObjectCleared.connect(self.__self_js) pckg, file_name = 'ace_editor', 'ace_editor.html' resource = pkg_resources.resource_string(pckg, file_name) html_template = str(resource, 'utf-8') #insert file content with open(self.file_info.absoluteFilePath(), 'r') as f: text = f.read() text = html.escape(text) html_template = html_template.replace('{{ content }}', text) base_url = QUrl.fromLocalFile(os.path.dirname(__file__)) self.setHtml(html_template, base_url) self.modified = False if not self.waitForReady: self.loop.exec() @pyqtSlot(str, name='test', result=str) @EditorHelper.json_dumps def test(self, prefix): print(prefix) return ['plop', 'cool', 42, {'plop':23}] def modification_changed(self, b): self.modified = b def save(self, parent, action=None): Alter.invoke_all('editor_presave', self) if self.modified: with open(self.file_info.absoluteFilePath(), 'w') as f: f.write(self.get_value()) self.original_to_current_doc() self.modificationChanged.emit(False) parent.status_bar.showMessage(self.tr("Saved file.")) Alter.invoke_all('editor_save', self) else : parent.status_bar.showMessage(self.tr("Nothing to save.")) def toggle_hidden(self, parent, action=None): self.set_show_invisibles(action.isChecked()) def toggle_soft_tabs(self, parent, action=None): self.set_use_soft_tabs(action.isChecked()) def main_frame(self): """ Convinient function to get main QWebFrame """ return self.page().mainFrame() def send_js(self, script): """ Convinient function to send javascript to ace editor """ return self.main_frame().evaluateJavaScript(script) def __self_js(self): self.main_frame().addToJavaScriptWindowObject('AceEditor', self) @pyqtSlot(name='isReady') def editor_ready(self): if self.language != None: self.set_mode(self.language.lower()) self.set_focus() if self.loop.isRunning(): self.loop.quit() self.waitForReady = True def showInspector(self): self.dialogInspector = QDialog(self) self.dialogInspector.setLayout(QVBoxLayout()) self.dialogInspector.layout().addWidget(self.inspector) self.dialogInspector.setModal(False) self.dialogInspector.show() def original_to_current_doc(self): self.send_js('editor.orignalToCurrentDoc()') def get_value(self): return self.send_js('editor.getValue()') def set_value(self, content): self.send_js('editor.setValue("{0}")'.format(content)) def set_focus(self): self.send_js('editor.focus()') def insert(self, text): self.send_js('editor.insert("{0}")'.format(text)) def set_mode(self, language): cmd = 'editor.getSession().setMode("ace/mode/{0}")' self.send_js(cmd.format(language)) def set_theme(self, theme): self.send_js('editor.setTheme("ace/theme/{0}")'.format(theme)) def get_selected_text(self): cmd = 'editor.session.getTextRange(editor.getSelectionRange())' return self.send_js(cmd) def get_cursor(self): cmd = 'editor.selection.getCursor()' return self.send_js(cmd) def got_to_line(self, line): cmd = 'editor.gotoLine({0})' self.send_js(cmd.format(line)) def get_length(self): return self.send_js('editor.session.getLength()'); def set_tab_size(self, tab_size): self.send_js('editor.getSession().setTabSize({0})'.format(tab_size)) def get_tab_size(self): return self.send_js('editor.getSession().getTabSize()') def set_use_soft_tabs(self, b): b = 'true' if b else 'false' self.send_js('editor.getSession().setUseSoftTabs({0})'.format(b)) def set_font_size(self, font_size): cmd = "document.getElementById('editor').style.fontSize='{0}px'" self.send_js(cmd.format(font_size)) def set_use_wrap_mode(self, b): b = 'true' if b else 'false' cmd = "editor.getSession().setUseWrapMode({0})" self.send_js(cmd.format(b)) def set_highlight_active_line(self, b): b = 'true' if b else 'false' cmd = "editor.setHighlightActiveLine({0})" self.send_js(cmd.format(b)) def set_show_print_margin(self, b): b = 'true' if b else 'false' cmd = "editor.setShowPrintMargin({0})" self.send_js(cmd.format(b)) def set_read_only(self, b): b = 'true' if b else 'false' self.send_js('editor.setReadOnly({0})'.format(b)) def set_show_invisibles(self, b): b = 'true' if b else 'false' self.send_js('editor.setShowInvisibles({0})'.format(b))
class Downloader(QObject): # error status NO_ERROR = 0 TIMEOUT_ERROR = 4 UNKNOWN_ERROR = -1 # PyQt signals replyFinished = pyqtSignal(str) allRepliesFinished = pyqtSignal() def __init__(self, parent=None, maxConnections=2, defaultCacheExpiration=24, userAgent=""): QObject.__init__(self, parent) self.maxConnections = maxConnections self.defaultCacheExpiration = defaultCacheExpiration # hours self.userAgent = userAgent # initialize variables self.clear() self.sync = False self.eventLoop = QEventLoop() self.timer = QTimer() self.timer.setSingleShot(True) self.timer.timeout.connect(self.timeOut) def clear(self): self.queue = [] self.requestingReplies = {} self.fetchedFiles = {} self._successes = 0 self._errors = 0 self._cacheHits = 0 self.errorStatus = Downloader.NO_ERROR def _replyFinished(self): reply = self.sender() url = reply.request().url().toString() if url not in self.fetchedFiles: self.fetchedFiles[url] = None if url in self.requestingReplies: del self.requestingReplies[url] httpStatusCode = reply.attribute( QNetworkRequest.HttpStatusCodeAttribute) if reply.error() == QNetworkReply.NoError: self._successes += 1 if reply.attribute(QNetworkRequest.SourceIsFromCacheAttribute): self._cacheHits += 1 elif not reply.hasRawHeader(b"Cache-Control"): cache = QgsNetworkAccessManager.instance().cache() if cache: metadata = cache.metaData(reply.request().url()) if metadata.expirationDate().isNull(): metadata.setExpirationDate( QDateTime.currentDateTime().addSecs( self.defaultCacheExpiration * 3600)) cache.updateMetaData(metadata) self.log( "Default expiration date has been set: %s (%d h)" % (url, self.defaultCacheExpiration)) if reply.isReadable(): data = reply.readAll() self.fetchedFiles[url] = data else: qDebug("http status code: " + str(httpStatusCode)) else: self._errors += 1 if self.errorStatus == self.NO_ERROR: self.errorStatus = self.UNKNOWN_ERROR self.replyFinished.emit(url) reply.deleteLater() if len(self.queue) + len(self.requestingReplies) == 0: # all replies have been received if self.sync: self.logT("eventLoop.quit()") self.eventLoop.quit() else: self.timer.stop() self.allRepliesFinished.emit() elif len(self.queue) > 0: # start fetching the next file self.fetchNext() def timeOut(self): self.log("Downloader.timeOut()") self.abort() self.errorStatus = Downloader.TIMEOUT_ERROR @pyqtSlot() def abort(self, stopTimer=True): # clear queue and abort requests self.queue = [] for reply in self.requestingReplies.values(): url = reply.url().toString() reply.abort() reply.deleteLater() self.log("request aborted: {0}".format(url)) self.errorStatus = Downloader.UNKNOWN_ERROR self.requestingReplies = {} if stopTimer: self.timer.stop() def fetchNext(self): if len(self.queue) == 0: return url = self.queue.pop(0) self.log("fetchNext: %s" % url) # create request request = QNetworkRequest(QUrl(url)) if self.userAgent: request.setRawHeader( b"User-Agent", self.userAgent.encode("ascii", "ignore") ) # will be overwritten in QgsNetworkAccessManager::createRequest() since 2.2 # send request reply = QgsNetworkAccessManager.instance().get(request) reply.finished.connect(self._replyFinished) self.requestingReplies[url] = reply return reply def fetchFiles(self, urlList, timeoutSec=0): self.log("fetchFiles()") files = self._fetch(True, urlList, timeoutSec) self.log("fetchFiles() End: %d" % self.errorStatus) return files @pyqtSlot(list, int) def fetchFilesAsync(self, urlList, timeoutSec=0): self.log("fetchFilesAsync()") self._fetch(False, urlList, timeoutSec) def _fetch(self, sync, urlList, timeoutSec): self.clear() self.sync = sync if not urlList: return {} for url in urlList: if url not in self.queue: self.queue.append(url) for i in range(self.maxConnections): self.fetchNext() if timeoutSec > 0: self.timer.setInterval(timeoutSec * 1000) self.timer.start() if sync: self.logT("eventLoop.exec_(): " + str(self.eventLoop)) self.eventLoop.exec_() if timeoutSec > 0: self.timer.stop() return self.fetchedFiles def log(self, msg): if DEBUG_MODE: qDebug(msg) def logT(self, msg): if DEBUG_MODE: qDebug("%s: %s" % (str(threading.current_thread()), msg)) def finishedCount(self): return len(self.fetchedFiles) def unfinishedCount(self): return len(self.queue) + len(self.requestingReplies) def stats(self): finished = self.finishedCount() unfinished = self.unfinishedCount() return { "total": finished + unfinished, "finished": finished, "unfinished": unfinished, "successed": self._successes, "errors": self._errors, "cacheHits": self._cacheHits, "downloaded": self._successes - self._cacheHits }