class MatrixDialog(WindowModalDialog): def __init__(self, parent): super(MatrixDialog, self).__init__(parent) self.setWindowTitle(_("Trezor Matrix Recovery")) self.num = 9 self.loop = QEventLoop() vbox = QVBoxLayout(self) vbox.addWidget(WWLabel(MATRIX_RECOVERY)) grid = QGridLayout() grid.setSpacing(0) self.char_buttons = [] for y in range(3): for x in range(3): button = QPushButton('?') button.clicked.connect(partial(self.process_key, ord('1') + y * 3 + x)) grid.addWidget(button, 3 - y, x) self.char_buttons.append(button) vbox.addLayout(grid) self.backspace_button = QPushButton("<=") self.backspace_button.clicked.connect(partial(self.process_key, Qt.Key_Backspace)) self.cancel_button = QPushButton(_("Cancel")) self.cancel_button.clicked.connect(partial(self.process_key, Qt.Key_Escape)) buttons = Buttons(self.backspace_button, self.cancel_button) vbox.addSpacing(40) vbox.addLayout(buttons) self.refresh() self.show() def refresh(self): for y in range(3): self.char_buttons[3 * y + 1].setEnabled(self.num == 9) def is_valid(self, key): return key >= ord('1') and key <= ord('9') def process_key(self, key): self.data = None if key == Qt.Key_Backspace: self.data = '\010' elif key == Qt.Key_Escape: self.data = 'x' elif self.is_valid(key): self.char_buttons[key - ord('1')].setFocus() self.data = '%c' % key if self.data: self.loop.exit(0) def keyPressEvent(self, event): self.process_key(event.key()) if not self.data: QDialog.keyPressEvent(self, event) def get_matrix(self, num): self.num = num self.refresh() self.loop.exec_()
def _processPendingEvents(): """Process pending application events.""" # Create an event loop to run in. Otherwise, we need to use the # QApplication main loop, which may already be running and therefore # unusable. qe = QEventLoop() # Create a single-shot timer. Could use QTimer.singleShot(), # but can't cancel this / disconnect it. timer = QTimer() timer.setSingleShot(True) timer.timeout.connect(qe.quit) timer.start(1) # Wait for an emitted signal. qe.exec_() # Clean up: don't allow the timer to call qe.quit after this # function exits, which would produce "interesting" behavior. timer.stop() # Stopping the timer may not cancel timeout signals in the # event queue. Disconnect the signal to be sure that loop # will never receive a timeout after the function exits. timer.timeout.disconnect(qe.quit)
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()
class AppDecorator(QObject): documentWasCreatedSignal = pyqtSignal(object) # doc documentWindowWasCreatedSignal = pyqtSignal(object, object) # doc, window def __init__(self,argv): self.qApp = QApplication(argv) super(AppDecorator, self).__init__() from cadnano.gui.views.preferences import Preferences self.prefs = Preferences() #icon = QIcon(ICON_PATH) #self.qApp.setWindowIcon(icon) self.document_controllers = set() # Open documents self.active_document = None self.vh = {} # Newly created VirtualHelix register here by idnum. self.vhi = {} self.partItem = None global decode global Document global DocumentController from cadnano.gui.views.pathview import pathstyles as styles styles.setFontMetrics() # def prefsClicked(self): # self.prefs.showDialog() def exec_(self): if hasattr(self, 'qApp'): mainWindow = MainWindow() mainWindow.show() self.mainEventLoop = QEventLoop() self.mainEventLoop.exec_()
def f2(): future = ac.start(em2.g, lambda x: x, QThread.currentThread()) # The doneSignal won't be processed without an event loop. A # thread pool doesn't create one, so make our own to run ``g``. qe = QEventLoop() future._signalInvoker.doneSignal.connect(qe.exit) qe.exec_()
def send_request(self, post=None, data={}): loop = QEventLoop() self.r.setUrl(self.url) if post: encoded_data = self._urlencode_post_data(data) pprint(encoded_data) self.reply_post = self.conn.post(self.r, encoded_data) self.reply_post.downloadProgress.connect(self.prepare_responce) else: self.reply = self.conn.get(self.r) self.reply.finished.connect(self.prepare_responce) # return \ loop.exec()
def run(self, installSignalHandlers=True): if self._ownApp: self._blockApp = self.qApp else: self._blockApp = QEventLoop() self.runReturn() self._blockApp.exec_()
def __init__(self, parent): super(MatrixDialog, self).__init__(parent) self.setWindowTitle(_("Trezor Matrix Recovery")) self.num = 9 self.loop = QEventLoop() vbox = QVBoxLayout(self) vbox.addWidget(WWLabel(MATRIX_RECOVERY)) grid = QGridLayout() grid.setSpacing(0) self.char_buttons = [] for y in range(3): for x in range(3): button = QPushButton('?') button.clicked.connect(partial(self.process_key, ord('1') + y * 3 + x)) grid.addWidget(button, 3 - y, x) self.char_buttons.append(button) vbox.addLayout(grid) self.backspace_button = QPushButton("<=") self.backspace_button.clicked.connect(partial(self.process_key, Qt.Key_Backspace)) self.cancel_button = QPushButton(_("Cancel")) self.cancel_button.clicked.connect(partial(self.process_key, Qt.Key_Escape)) buttons = Buttons(self.backspace_button, self.cancel_button) vbox.addSpacing(40) vbox.addLayout(buttons) self.refresh() self.show()
def __init__(self, parent): super(CharacterDialog, self).__init__(parent) self.setWindowTitle(_("KeepKey Seed Recovery")) self.character_pos = 0 self.word_pos = 0 self.loop = QEventLoop() self.word_help = QLabel() self.char_buttons = [] vbox = QVBoxLayout(self) vbox.addWidget(WWLabel(CHARACTER_RECOVERY)) hbox = QHBoxLayout() hbox.addWidget(self.word_help) for i in range(4): char_button = CharacterButton('*') char_button.setMaximumWidth(36) self.char_buttons.append(char_button) hbox.addWidget(char_button) self.accept_button = CharacterButton(_("Accept Word")) self.accept_button.clicked.connect(partial(self.process_key, 32)) self.rejected.connect(partial(self.loop.exit, 1)) hbox.addWidget(self.accept_button) hbox.addStretch(1) vbox.addLayout(hbox) self.finished_button = QPushButton(_("Seed Entered")) self.cancel_button = QPushButton(_("Cancel")) self.finished_button.clicked.connect(partial(self.process_key, Qt.Key_Return)) self.cancel_button.clicked.connect(self.rejected) buttons = Buttons(self.finished_button, self.cancel_button) vbox.addSpacing(40) vbox.addLayout(buttons) self.refresh() self.show()
def waitForCallback(self): assert self._state == CallbackFutureState.WAITING, 'Only callbacks being waited for may be skipped.' # Run events until the callback is invoked. self._qEventLoop = QEventLoop() self._qEventLoop.exec_() self._qEventLoop = None assert self._state == CallbackFutureState.COMPLETE
def commRqData(self, requestName, trCode, inquiry, screenNo): """ 키움서버에 TR 요청을 한다. 조회요청메서드이며 빈번하게 조회요청시, 시세과부하 에러값 -200이 리턴된다. :param requestName: string - TR 요청명(사용자 정의) :param trCode: string :param inquiry: int - 조회(0: 조회, 2: 남은 데이터 이어서 요청) :param screenNo: string - 화면번호(4자리) """ if not self.getConnectState(): raise KiwoomConnectError() if not (isinstance(requestName, str) and isinstance(trCode, str) and isinstance(inquiry, int) and isinstance(screenNo, str)): raise ParameterTypeError() returnCode = self.dynamicCall("CommRqData(QString, QString, int, QString)", requestName, trCode, inquiry, screenNo) if returnCode != ReturnCode.OP_ERR_NONE: raise KiwoomProcessingError("commRqData(): " + ReturnCode.CAUSE[returnCode]) # 루프 생성: receiveTrData() 메서드에서 루프를 종료시킨다. self.requestLoop = QEventLoop() self.requestLoop.exec_()
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()
def exec_(self): if hasattr(self, 'qApp'): from initialization.ui_loader import UiLoader loader = UiLoader() loader.mainWindow.show() self.mainEventLoop = QEventLoop() self.mainEventLoop.exec_()
def comm_connect(self): """ 로그인을 시도합니다. 수동 로그인일 경우, 로그인창을 출력해서 로그인을 시도. 자동 로그인일 경우, 로그인창 출력없이 로그인 시도. """ self.dynamicCall("CommConnect()") self.login_loop = QEventLoop() self.login_loop.exec_()
class CallbackFuture: def __init__(self, # A CallbackManager instance to register with. callbackManager, # _`callbackToWrap`: The callback function to (optionally) invoke, which this future will wrap. callbackToWrap): self.callbackManager = callbackManager self._callbackToWrap = callbackToWrap self._state = CallbackFutureState.READY self._shouldInvokeCallback = True self._qEventLoop = None # Return the callback wrapper for this future and mark it as waiting. def callback(self): assert self._state == CallbackFutureState.READY, 'A callback may only be obtained once.' self._state = CallbackFutureState.WAITING self.callbackManager.add(self) return self._callbackWrapper # The callback wrapper. Update state, then invoke the wrapped callback. def _callbackWrapper(self, *args, **kwargs): assert self._state == CallbackFutureState.WAITING self._state = CallbackFutureState.COMPLETE self.callbackManager.remove(self) # If waiting for the callback, stop! if self._qEventLoop: self._qEventLoop.quit() if self._shouldInvokeCallback: self._callbackToWrap(*args, **kwargs) def skipCallback(self): assert self._state == CallbackFutureState.WAITING, 'Only callbacks being waited for may be skipped.' self._shouldInvokeCallback = False # Wait until the wrapped callback is completed (invoked or skipped). def waitForCallback(self): assert self._state == CallbackFutureState.WAITING, 'Only callbacks being waited for may be skipped.' # Run events until the callback is invoked. self._qEventLoop = QEventLoop() self._qEventLoop.exec_() self._qEventLoop = None assert self._state == CallbackFutureState.COMPLETE
def terminate(self): # Only run this once. if self._isAlive: self._isAlive = False self._terminate() # Waiting for the thread or thread pool to shut down may have # placed completed jobs in this thread's queue. Process them now to # avoid any surprises (terminated `_AsyncAbstractController`_ # instances still invoke callbacks, making it seem that the # terminate didn't fully terminate. It did, but still leaves g_ # callbacks to be run in the event queue.) el = QEventLoop(self.parent()) QTimer.singleShot(0, el.exit) el.exec_() # Delete the `QObject <http://doc.qt.io/qt-5/qobject.html>`_ # underlying this class, which disconnects all signals. sip.delete(self)
def __enter__(self): # Create an event loop to run in. Otherwise, we need to use the papp # (QApplication) main loop, which may already be running and therefore # unusable. self.qe = QEventLoop() # Connect both signals to a slot which quits the event loop. self.signal.connect(self.signalSlot) return self
def __init__(self, config, app, plugins): QDialog.__init__(self, None) BaseWizard.__init__(self, config, plugins) self.setWindowTitle('Vialectrum - ' + _('Install Wizard')) self.app = app self.config = config # Set for base base class self.language_for_seed = config.get('language') self.setMinimumSize(600, 400) self.accept_signal.connect(self.accept) self.title = QLabel() self.main_widget = QWidget() self.back_button = QPushButton(_("Back"), self) self.back_button.setText(_('Back') if self.can_go_back() else _('Cancel')) self.next_button = QPushButton(_("Next"), self) self.next_button.setDefault(True) self.logo = QLabel() self.please_wait = QLabel(_("Please wait...")) self.please_wait.setAlignment(Qt.AlignCenter) self.icon_filename = None self.loop = QEventLoop() self.rejected.connect(lambda: self.loop.exit(0)) self.back_button.clicked.connect(lambda: self.loop.exit(1)) self.next_button.clicked.connect(lambda: self.loop.exit(2)) outer_vbox = QVBoxLayout(self) inner_vbox = QVBoxLayout() inner_vbox.addWidget(self.title) inner_vbox.addWidget(self.main_widget) inner_vbox.addStretch(1) inner_vbox.addWidget(self.please_wait) inner_vbox.addStretch(1) scroll_widget = QWidget() scroll_widget.setLayout(inner_vbox) scroll = QScrollArea() scroll.setWidget(scroll_widget) scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) scroll.setWidgetResizable(True) icon_vbox = QVBoxLayout() icon_vbox.addWidget(self.logo) icon_vbox.addStretch(1) hbox = QHBoxLayout() hbox.addLayout(icon_vbox) hbox.addSpacing(5) hbox.addWidget(scroll) hbox.setStretchFactor(scroll, 1) outer_vbox.addLayout(hbox) outer_vbox.addLayout(Buttons(self.back_button, self.next_button)) self.set_icon('vialectrum.png') self.show() self.raise_() self.refresh_gui() # Need for QT on MacOSX. Lame.
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"]
def commKwRqData(self, codes, inquiry, codeCount, requestName, screenNo, typeFlag=0): """ 복수종목조회 메서드(관심종목조회 메서드라고도 함). 이 메서드는 setInputValue() 메서드를 이용하여, 사전에 필요한 값을 지정하지 않는다. 단지, 메서드의 매개변수에서 직접 종목코드를 지정하여 호출하며, 데이터 수신은 receiveTrData() 이벤트에서 아래 명시한 항목들을 1회 수신하며, 이후 receiveRealData() 이벤트를 통해 실시간 데이터를 얻을 수 있다. 복수종목조회 TR 코드는 OPTKWFID 이며, 요청 성공시 아래 항목들의 정보를 얻을 수 있다. 종목코드, 종목명, 현재가, 기준가, 전일대비, 전일대비기호, 등락율, 거래량, 거래대금, 체결량, 체결강도, 전일거래량대비, 매도호가, 매수호가, 매도1~5차호가, 매수1~5차호가, 상한가, 하한가, 시가, 고가, 저가, 종가, 체결시간, 예상체결가, 예상체결량, 자본금, 액면가, 시가총액, 주식수, 호가시간, 일자, 우선매도잔량, 우선매수잔량,우선매도건수, 우선매수건수, 총매도잔량, 총매수잔량, 총매도건수, 총매수건수, 패리티, 기어링, 손익분기, 잔본지지, ELW행사가, 전환비율, ELW만기일, 미결제약정, 미결제전일대비, 이론가, 내재변동성, 델타, 감마, 쎄타, 베가, 로 :param codes: string - 한번에 100종목까지 조회가능하며 종목코드사이에 세미콜론(;)으로 구분. :param inquiry: int - api 문서는 bool 타입이지만, int로 처리(0: 조회, 1: 남은 데이터 이어서 조회) :param codeCount: int - codes에 지정한 종목의 갯수. :param requestName: string :param screenNo: string :param typeFlag: int - 주식과 선물옵션 구분(0: 주식, 3: 선물옵션), 주의: 매개변수의 위치를 맨 뒤로 이동함. :return: list - 중첩 리스트 [[종목코드, 종목명 ... 종목 정보], [종목코드, 종목명 ... 종목 정보]] """ if not self.getConnectState(): raise KiwoomConnectError() if not (isinstance(codes, str) and isinstance(inquiry, int) and isinstance(codeCount, int) and isinstance(requestName, str) and isinstance(screenNo, str) and isinstance(typeFlag, int)): raise ParameterTypeError() returnCode = self.dynamicCall("CommKwRqData(QString, QBoolean, int, int, QString, QString)", codes, inquiry, codeCount, typeFlag, requestName, screenNo) if returnCode != ReturnCode.OP_ERR_NONE: raise KiwoomProcessingError("commKwRqData(): " + ReturnCode.CAUSE[returnCode]) # 루프 생성: receiveTrData() 메서드에서 루프를 종료시킨다. self.requestLoop = QEventLoop() self.requestLoop.exec_()
def getConditionLoad(self): """ 조건식 목록 요청 메서드 """ if not self.getConnectState(): raise KiwoomConnectError() isLoad = self.dynamicCall("GetConditionLoad()") # 요청 실패시 if not isLoad: raise KiwoomProcessingError("getConditionLoad(): 조건식 요청 실패") # receiveConditionVer() 이벤트 메서드에서 루프 종료 self.conditionLoop = QEventLoop() self.conditionLoop.exec_()
def sendOrder(self, requestName, screenNo, accountNo, orderType, code, qty, price, hogaType, originOrderNo): """ 주식 주문 메서드 sendOrder() 메소드 실행시, OnReceiveMsg, OnReceiveTrData, OnReceiveChejanData 이벤트가 발생한다. 이 중, 주문에 대한 결과 데이터를 얻기 위해서는 OnReceiveChejanData 이벤트를 통해서 처리한다. OnReceiveTrData 이벤트를 통해서는 주문번호를 얻을 수 있는데, 주문후 이 이벤트에서 주문번호가 ''공백으로 전달되면, 주문접수 실패를 의미한다. :param requestName: string - 주문 요청명(사용자 정의) :param screenNo: string - 화면번호(4자리) :param accountNo: string - 계좌번호(10자리) :param orderType: int - 주문유형(1: 신규매수, 2: 신규매도, 3: 매수취소, 4: 매도취소, 5: 매수정정, 6: 매도정정) :param code: string - 종목코드 :param qty: int - 주문수량 :param price: int - 주문단가 :param hogaType: string - 거래구분(00: 지정가, 03: 시장가, 05: 조건부지정가, 06: 최유리지정가, 그외에는 api 문서참조) :param originOrderNo: string - 원주문번호(신규주문에는 공백, 정정및 취소주문시 원주문번호르 입력합니다.) """ if not self.getConnectState(): raise KiwoomConnectError() if not (isinstance(requestName, str) and isinstance(screenNo, str) and isinstance(accountNo, str) and isinstance(orderType, int) and isinstance(code, str) and isinstance(qty, int) and isinstance(price, int) and isinstance(hogaType, str) and isinstance(originOrderNo, str)): raise ParameterTypeError() returnCode = self.dynamicCall("SendOrder(QString, QString, QString, int, QString, int, int, QString, QString)", [requestName, screenNo, accountNo, orderType, code, qty, price, hogaType, originOrderNo]) if returnCode != ReturnCode.OP_ERR_NONE: raise KiwoomProcessingError("sendOrder(): " + ReturnCode.CAUSE[returnCode]) # receiveTrData() 에서 루프종료 self.orderLoop = QEventLoop() self.orderLoop.exec_()
def __init__(self, parent): super().__init__(parent) self.set_url('http://google.ru') conn = QNetworkAccessManager() self.conn = conn self.r = QNetworkRequest() self.r.attribute(QNetworkRequest.CookieSaveControlAttribute, QVariant(True)) # self.r.setHeader(QNetworkRequest.ContentTypeHeader, "application/x-www-form-urlencoded") # self.r.setRawHeader("Referer", "http://www.facebook.com/") # self.r.setRawHeader("Host", "www.facebook.com") self.cj = QNetworkCookieJar() conn.setCookieJar(self.cj) conn.createRequest = self._create_request self.wv = WebView() self.wv.show() self.wv.page().setNetworkAccessManager(conn) # self.wv.auth() self.loop = QEventLoop() pass
def sendCondition(self, screenNo, conditionName, conditionIndex, isRealTime): """ 종목 조건검색 요청 메서드 이 메서드로 얻고자 하는 것은 해당 조건에 맞는 종목코드이다. 해당 종목에 대한 상세정보는 setRealReg() 메서드로 요청할 수 있다. 요청이 실패하는 경우는, 해당 조건식이 없거나, 조건명과 인덱스가 맞지 않거나, 조회 횟수를 초과하는 경우 발생한다. 조건검색에 대한 결과는 1회성 조회의 경우, receiveTrCondition() 이벤트로 결과값이 전달되며 실시간 조회의 경우, receiveTrCondition()과 receiveRealCondition() 이벤트로 결과값이 전달된다. :param screenNo: string :param conditionName: string - 조건식 이름 :param conditionIndex: int - 조건식 인덱스 :param isRealTime: int - 조건검색 조회구분(0: 1회성 조회, 1: 실시간 조회) """ if not self.getConnectState(): raise KiwoomConnectError() if not (isinstance(screenNo, str) and isinstance(conditionName, str) and isinstance(conditionIndex, int) and isinstance(isRealTime, int)): raise ParameterTypeError() isRequest = self.dynamicCall("SendCondition(QString, QString, int, int", screenNo, conditionName, conditionIndex, isRealTime) if not isRequest: raise KiwoomProcessingError("sendCondition(): 조건검색 요청 실패") # receiveTrCondition() 이벤트 메서드에서 루프 종료 self.conditionLoop = QEventLoop() self.conditionLoop.exec_()
class Kiwoom(QAxWidget): def __init__(self): super().__init__() self.setControl("KHOPENAPI.KHOpenAPICtrl.1") # Loop 변수 # 비동기 방식으로 동작되는 이벤트를 동기화(순서대로 동작) 시킬 때 self.login_loop = None self.request_loop = None self.order_loop = None self.condition_loop = None # 서버구분 self.server_gubun = None # 조건식 self.condition = None # 에러 self.error = None # 주문번호 self.order_no = "" # 조회 self.inquiry = 0 # 서버에서 받은 메시지 self.msg = "" # 예수금 d+2 self.data_opw00001 = 0 # 보유종목 정보 self.data_opw00018 = {'account_evaluation': [], 'stocks': []} # 주가상세정보 self.data_opt10081 = [] * 15 self.data_opt10086 = [] * 23 # signal & slot self.OnEventConnect.connect(self.event_connect) self.OnReceiveTrData.connect(self.on_receive_tr_data) self.OnReceiveChejanData.connect(self.on_receive_chejan_data) self.OnReceiveRealData.connect(self.receive_real_data) self.OnReceiveMsg.connect(self.receive_msg) self.OnReceiveConditionVer.connect(self.receive_condition_ver) self.OnReceiveTrCondition.connect(self.receive_tr_condition) self.OnReceiveRealCondition.connect(self.receive_real_condition) # 로깅용 설정파일 logging.config.fileConfig('logging.conf') self.log = logging.getLogger('Kiwoom') ############################################################### # 로깅용 메서드 정의 # ############################################################### def logger(origin): def wrapper(*args, **kwargs): args[0].log.debug('{} args - {}, kwargs - {}'.format( origin.__name__, args, kwargs)) return origin(*args, **kwargs) return wrapper ############################################################### # 이벤트 정의 # ############################################################### def event_connect(self, return_code): """ 통신 연결 상태 변경시 이벤트 return_code 0이면 로그인 성공 그 외에는 ReturnCode 클래스 참조. :param return_code: int """ try: if return_code == ReturnCode.OP_ERR_NONE: if self.get_login_info("GetServerGubun", True): self.msg += "실서버 연결 성공" + "\r\n\r\n" else: self.msg += "모의투자서버 연결 성공" + "\r\n\r\n" else: self.msg += "연결 끊김: 원인 - " + ReturnCode.CAUSE[ return_code] + "\r\n\r\n" except Exception as error: self.log.error('eventConnect {}'.format(error)) finally: # commConnect() 메서드에 의해 생성된 루프를 종료시킨다. # 로그인 후, 통신이 끊길 경우를 대비해서 예외처리함. try: self.login_loop.exit() except AttributeError: pass def receive_msg(self, screen_no, request_name, tr_code, msg): """ 수신 메시지 이벤트 서버로 어떤 요청을 했을 때(로그인, 주문, 조회 등), 그 요청에 대한 처리내용을 전달해준다. :param screen_no: string - 화면번호(4자리, 사용자 정의, 서버에 조회나 주문을 요청할 때 이 요청을 구별하기 위한 키값) :param request_name: string - TR 요청명(사용자 정의) :param tr_code: string :param msg: string - 서버로 부터의 메시지 """ if request_name == "서버구분": if msg.find('모의투자') < 0: self.server_gubun = 1 else: self.server_gubun = 0 try: self.order_loop.exit() except AttributeError: pass finally: return self.msg += request_name + ": " + msg + "\r\n\r\n" def on_receive_tr_data(self, screen_no, request_name, tr_code, record_name, inquiry, unused0, unused1, unused2, unused3): """ TR 수신 이벤트 조회요청 응답을 받거나 조회데이터를 수신했을 때 호출됩니다. request_name tr_code comm_rq_data()메소드의 매개변수와 매핑되는 값 입니다. 조회데이터는 이 이벤트 메서드 내부에서 comm_get_data() 메서드를 이용해서 얻을 수 있습니다. :param screen_no: string - 화면번호(4자리) :param request_name: string - TR 요청명(comm_rq_data() 메소드 호출시 사용된 requestName) :param tr_code: string :param record_name: string :param inquiry: string - 조회('0': 남은 데이터 없음, '2': 남은 데이터 있음) """ print( "on_receive_tr_data 실행: screen_no: %s, request_name: %s, tr_code: %s, record_name: %s, inquiry: %s" % (screen_no, request_name, tr_code, record_name, inquiry)) # 주문번호와 주문루프 self.order_no = self.comm_get_data(tr_code, "", request_name, 0, "주문번호") try: self.order_loop.exit() except AttributeError: pass self.inquiry = inquiry if request_name == "관심종목정보요청": data = self.get_comm_data_ex(tr_code, "관심종목정보") """ commGetData cnt = self.getRepeatCnt(trCode, requestName) for i in range(cnt): data = self.commGetData(trCode, "", requestName, i, "종목명") print(data) """ if request_name == "주식일봉차트조회요청": data = self.get_comm_data_ex(tr_code, "주식일봉차트조회") if data is not None: data = list( map( lambda x: list( map( lambda y: y.replace('+', '').replace( '--', '-'), x)), np.array(data)[:, 1:8].tolist())) data = list( map( lambda x: list( map(lambda y: int(y) if y != '' else 0, x)), data)) self.data_opt10081.extend(data) date = str(data[0][3]) dt = datetime.strptime(date, "%Y%m%d") if dt <= self.start_date: self.inquiry = 0 if inquiry == "0" or self.inquiry == 0: col_name = ['현재가', '거래량', '거래대금', '일자', '시가', '고가', '저가'] self.data_opt10081 = DataFrame(self.data_opt10081, columns=col_name) if request_name == "일별주가요청": data = self.get_comm_data_ex(tr_code, "일별주가요청") if data is not None: data = list( map( lambda x: list( map( lambda y: y.replace('+', '').replace( '--', '-'), x)), data)) data = list( map( lambda x: list( map(lambda y: float(y) if y != '' else 0, x)), data)) self.data_opt10086.extend(data) date = str(int(data[0][0])) dt = datetime.strptime(date, "%Y%m%d") if dt <= self.start_date: self.inquiry = 0 if inquiry == "0" or self.inquiry == 0: col_name = [ '일자', '시가', '고가', '저가', '종가', '전일비', '등락률', '거래량', '금액(백만)', '신용비', '개인', '기관', '외인수량', '외국계', '프로그램', '외인비', '체결강도', '외인보유', '외인비중', '외인순매수', '기관순매수', '개인순매수', '신용잔고율' ] self.data_opt10086 = DataFrame(self.data_opt10086, columns=col_name) if request_name == "예수금상세현황요청": estimate_day2_deposit = self.comm_get_data(tr_code, "", request_name, 0, "d+2추정예수금") estimate_day2_deposit = self.change_format(estimate_day2_deposit) self.data_opw00001 = estimate_day2_deposit if request_name == '계좌평가잔고내역요청': # 계좌 평가 정보 account_evaluation = [] key_list = ["총매입금액", "총평가금액", "총평가손익금액", "총수익률(%)", "추정예탁자산"] for key in key_list: value = self.comm_get_data(tr_code, "", request_name, 0, key) if key.startswith("총수익률"): value = self.change_format(value, 1) else: value = self.change_format(value) account_evaluation.append(value) self.data_opw00018['account_evaluation'] = account_evaluation # 보유 종목 정보 cnt = self.get_repeat_cnt(tr_code, request_name) key_list = ["종목명", "보유수량", "매입가", "현재가", "평가손익", "수익률(%)", "종목번호"] for i in range(cnt): stock = [] for key in key_list: value = self.comm_get_data(tr_code, "", request_name, i, key) if key.startswith("수익률"): value = self.change_format(value, 2) elif key != "종목명" and key != "종목번호": value = self.change_format(value) stock.append(value) self.data_opw00018['stocks'].append(stock) try: self.request_loop.exit() except AttributeError: pass def receive_real_data(self, code, real_type, real_data): """ 실시간 데이터 수신 이벤트 실시간 데이터를 수신할 때 마다 호출되며, set_real_reg() 메서드로 등록한 실시간 데이터도 이 이벤트 메서드에 전달됩니다. get_comm_real_data() 메서드를 이용해서 실시간 데이터를 얻을 수 있습니다. :param code: string - 종목코드 :param real_type: string - 실시간 타입(KOA의 실시간 목록 참조) :param real_data: string - 실시간 데이터 전문 """ try: self.log.debug("[receiveRealData]") self.log.debug("({})".format(real_type)) if real_type not in RealType.REALTYPE: return data = [] if code != "": data.append(code) codeOrNot = code else: codeOrNot = real_type for fid in sorted(RealType.REALTYPE[real_type].keys()): value = self.get_comm_real_data(codeOrNot, fid) data.append(value) # TODO: DB에 저장 self.log.debug(data) except Exception as e: self.log.error('{}'.format(e)) def on_receive_chejan_data(self, gubun, item_cnt, fid_list): print("gubun: ", gubun) print(self.GetChejanData(9203)) print(self.GetChejanData(302)) print(self.GetChejanData(900)) print(self.GetChejanData(901)) def get_codelist_by_market(self, market): func = 'GetCodeListByMarket("%s")' % market codes = self.dynamicCall(func) return codes.split(';') ############################################################### # 메서드 정의: 로그인 관련 메서드 # ############################################################### def comm_connect(self): """ 로그인을 시도합니다. 수동 로그인일 경우, 로그인창을 출력해서 로그인을 시도. 자동 로그인일 경우, 로그인창 출력없이 로그인 시도. """ self.dynamicCall("CommConnect()") self.login_loop = QEventLoop() self.login_loop.exec_() def get_connect_state(self): """ 현재 접속상태를 반환합니다. 반환되는 접속상태는 아래와 같습니다. 0: 미연결, 1: 연결 :return: int """ ret = self.dynamicCall("GetConnectState()") return ret def get_login_info(self, tag, is_connect_state=False): """ 사용자의 tag에 해당하는 정보를 반환한다. tag에 올 수 있는 값은 아래와 같다. ACCOUNT_CNT: 전체 계좌의 개수를 반환한다. ACCNO: 전체 계좌 목록을 반환한다. 계좌별 구분은 ;(세미콜론) 이다. USER_ID: 사용자 ID를 반환한다. USER_NAME: 사용자명을 반환한다. GetServerGubun: 접속서버 구분을 반환합니다.(0: 모의투자, 그외: 실서버) :param tag: string :param is_connect_state: bool - 접속상태을 확인할 필요가 없는 경우 True로 설정. :return: string """ if not is_connect_state: if not self.get_connect_state(): raise KiwoomConnectError() if not isinstance(tag, str): raise ParameterTypeError() if tag not in [ 'ACCOUNT_CNT', 'ACCNO', 'USER_ID', 'USER_NAME', 'GetServerGubun' ]: raise ParameterValueError() cmd = 'GetLoginInfo("%s")' % tag info = self.dynamicCall(cmd) if tag == 'GetServerGubun' and info == "": if self.server_gubun == None: account_list = self.get_login_info("ACCNO").split(';') self.send_order("서버구분", "0102", account_list[0], 1, "066570", 0, 0, "05", "") info = self.server_gubun return info ################################################################# # 메서드 정의: 조회 관련 메서드 # # 시세조회, 관심종목 조회, 조건검색 등 이들의 합산 조회 횟수가 1초에 5회까지 허용 # ################################################################# def set_input_value(self, id, value): self.dynamicCall("SetInputValue(QString, QString)", id, value) def comm_rq_data(self, request_name, tr_code, inquiry, screen_no): """ 키움서버에 TR 요청을 한다. 조회요청메서드이며 빈번하게 조회요청시, 시세과부하 에러값 -200이 리턴된다. :param request_name: string - TR 요청명(사용자 정의) :param tr_code: string :param inquiry: int - 조회(0: 조회, 2: 남은 데이터 이어서 요청) :param screen_no: string - 화면번호(4자리) """ if not self.get_connect_state(): raise KiwoomConnectError() if not (isinstance(request_name, str) and isinstance(tr_code, str) and isinstance(inquiry, int) and isinstance(screen_no, str)): raise ParameterTypeError() return_code = self.dynamicCall( "CommRqData(QString, QString, int, QString)", request_name, tr_code, inquiry, screen_no) if return_code != ReturnCode.OP_ERR_NONE: raise KiwoomProcessingError("comm_rq_data(): " + ReturnCode.CAUSE[return_code]) # 루프 생성: receive_tr_data() 메서드에서 루프를 종료시킨다. self.request_loop = QEventLoop() self.request_loop.exec_() def comm_get_data(self, code, real_type, field_name, index, item_name): """ 데이터 획득 메서드 receiveTrData() 이벤트 메서드가 호출될 때, 그 안에서 조회데이터를 얻어오는 메서드입니다. :param code: string :param real_type: string - TR 요청시 ""(빈문자)로 처리 :param field_name: string - TR 요청명(comm_rq_data() 메소드 호출시 사용된 field_name) :param index: int :param item_name: string - 수신 데이터에서 얻고자 하는 값의 키(출력항목이름) :return: string """ ret = self.dynamicCall( "CommGetData(QString, QString, QString, int, QString)", code, real_type, field_name, index, item_name) return ret.strip() def get_repeat_cnt(self, tr_code, request_name): """ 서버로 부터 전달받은 데이터의 갯수를 리턴합니다.(멀티데이터의 갯수) receiveTrData() 이벤트 메서드가 호출될 때, 그 안에서 사용해야 합니다. 키움 OpenApi+에서는 데이터를 싱글데이터와 멀티데이터로 구분합니다. 싱글데이터란, 서버로 부터 전달받은 데이터 내에서, 중복되는 키(항목이름)가 하나도 없을 경우. 예를들면, 데이터가 '종목코드', '종목명', '상장일', '상장주식수' 처럼 키(항목이름)가 중복되지 않는 경우를 말합니다. 반면 멀티데이터란, 서버로 부터 전달받은 데이터 내에서, 일정 간격으로 키(항목이름)가 반복될 경우를 말합니다. 예를들면, 10일간의 일봉데이터를 요청할 경우 '종목코드', '일자', '시가', '고가', '저가' 이러한 항목이 10번 반복되는 경우입니다. 이러한 멀티데이터의 경우 반복 횟수(=데이터의 갯수)만큼, 루프를 돌면서 처리하기 위해 이 메서드를 이용하여 멀티데이터의 갯수를 얻을 수 있습니다. :param tr_code: string :param request_name: string - TR 요청명(comm_rq_data() 메소드 호출시 사용된 request_name) :return: int """ ret = self.dynamicCall("GetRepeatCnt(QString, QString)", tr_code, request_name) return ret def get_comm_data_ex(self, tr_code, multi_data_name): """ 멀티데이터 획득 메서드 receiveTrData() 이벤트 메서드가 호출될 때, 그 안에서 사용해야 합니다. :param tr_code: string :param multi_data_name: string - KOA에 명시된 멀티데이터명 :return: list - 중첩리스트 """ if not (isinstance(tr_code, str) and isinstance(multi_data_name, str)): raise ParameterTypeError() data = self.dynamicCall("GetCommDataEx(QString, QString)", tr_code, multi_data_name) return data def commKwRqData(self, codes, inquiry, codeCount, requestName, screenNo, typeFlag=0): """ 복수종목조회 메서드(관심종목조회 메서드라고도 함). 이 메서드는 setInputValue() 메서드를 이용하여, 사전에 필요한 값을 지정하지 않는다. 단지, 메서드의 매개변수에서 직접 종목코드를 지정하여 호출하며, 데이터 수신은 receiveTrData() 이벤트에서 아래 명시한 항목들을 1회 수신하며, 이후 receiveRealData() 이벤트를 통해 실시간 데이터를 얻을 수 있다. 복수종목조회 TR 코드는 OPTKWFID 이며, 요청 성공시 아래 항목들의 정보를 얻을 수 있다. 종목코드, 종목명, 현재가, 기준가, 전일대비, 전일대비기호, 등락율, 거래량, 거래대금, 체결량, 체결강도, 전일거래량대비, 매도호가, 매수호가, 매도1~5차호가, 매수1~5차호가, 상한가, 하한가, 시가, 고가, 저가, 종가, 체결시간, 예상체결가, 예상체결량, 자본금, 액면가, 시가총액, 주식수, 호가시간, 일자, 우선매도잔량, 우선매수잔량,우선매도건수, 우선매수건수, 총매도잔량, 총매수잔량, 총매도건수, 총매수건수, 패리티, 기어링, 손익분기, 잔본지지, ELW행사가, 전환비율, ELW만기일, 미결제약정, 미결제전일대비, 이론가, 내재변동성, 델타, 감마, 쎄타, 베가, 로 :param codes: string - 한번에 100종목까지 조회가능하며 종목코드사이에 세미콜론(;)으로 구분. :param inquiry: int - api 문서는 bool 타입이지만, int로 처리(0: 조회, 1: 남은 데이터 이어서 조회) :param codeCount: int - codes에 지정한 종목의 갯수. :param requestName: string :param screenNo: string :param typeFlag: int - 주식과 선물옵션 구분(0: 주식, 3: 선물옵션), 주의: 매개변수의 위치를 맨 뒤로 이동함. :return: list - 중첩 리스트 [[종목코드, 종목명 ... 종목 정보], [종목코드, 종목명 ... 종목 정보]] """ if not self.getConnectState(): raise KiwoomConnectError() if not (isinstance(codes, str) and isinstance(inquiry, int) and isinstance(codeCount, int) and isinstance(requestName, str) and isinstance(screenNo, str) and isinstance(typeFlag, int)): raise ParameterTypeError() returnCode = self.dynamicCall( "CommKwRqData(QString, QBoolean, int, int, QString, QString)", codes, inquiry, codeCount, typeFlag, requestName, screenNo) if returnCode != ReturnCode.OP_ERR_NONE: raise KiwoomProcessingError("commKwRqData(): " + ReturnCode.CAUSE[returnCode]) # 루프 생성: receiveTrData() 메서드에서 루프를 종료시킨다. self.requestLoop = QEventLoop() self.requestLoop.exec_() ############################################################### # 메서드 정의: 실시간 데이터 처리 관련 메서드 # ############################################################### def disconnect_real_data(self, screen_no): """ 해당 화면번호로 설정한 모든 실시간 데이터 요청을 제거합니다. 화면을 종료할 때 반드시 이 메서드를 호출해야 합니다. :param screen_no: string """ if not self.getConnectState(): raise KiwoomConnectError() if not isinstance(screen_no, str): raise ParameterTypeError() self.dynamicCall("DisconnectRealData(QString)", screen_no) def get_comm_real_data(self, code, fid): """ 실시간 데이터 획득 메서드 이 메서드는 반드시 receiveRealData() 이벤트 메서드가 호출될 때, 그 안에서 사용해야 합니다. :param code: string - 종목코드 :param fid: - 실시간 타입에 포함된 fid :return: string - fid에 해당하는 데이터 """ if not (isinstance(code, str) and isinstance(fid, int)): raise ParameterTypeError() value = self.dynamicCall("GetCommRealData(QString, int)", code, fid) return value def set_real_reg(self, screen_no, codes, fids, real_reg_type): """ 실시간 데이터 요청 메서드 종목코드와 fid 리스트를 이용해서 실시간 데이터를 요청하는 메서드입니다. 한번에 등록 가능한 종목과 fid 갯수는 100종목, 100개의 fid 입니다. 실시간등록타입을 0으로 설정하면, 첫 실시간 데이터 요청을 의미하며 실시간등록타입을 1로 설정하면, 추가등록을 의미합니다. 실시간 데이터는 실시간 타입 단위로 receiveRealData() 이벤트로 전달되기 때문에, 이 메서드에서 지정하지 않은 fid 일지라도, 실시간 타입에 포함되어 있다면, 데이터 수신이 가능하다. :param screen_no: string :param codes: string - 종목코드 리스트(종목코드;종목코드;...) :param fids: string - fid 리스트(fid;fid;...) :param real_reg_type: string - 실시간등록타입(0: 첫 등록, 1: 추가 등록) """ if not self.getConnectState(): raise KiwoomConnectError() if not (isinstance(screen_no, str) and isinstance(codes, str) and isinstance(fids, str) and isinstance(real_reg_type, str)): raise ParameterTypeError() self.dynamicCall("SetRealReg(QString, QString, QString, QString)", screen_no, codes, fids, real_reg_type) def set_real_remove(self, screen_no, code): """ 실시간 데이터 중지 메서드 set_real_reg() 메서드로 등록한 종목만, 이 메서드를 통해 실시간 데이터 받기를 중지 시킬 수 있습니다. :param screen_no: string - 화면번호 또는 ALL 키워드 사용가능 :param code: string - 종목코드 또는 ALL 키워드 사용가능 """ if not self.getConnectState(): raise KiwoomConnectError() if not (isinstance(screen_no, str) and isinstance(code, str)): raise ParameterTypeError() self.dynamicCall("SetRealRemove(QString, QString)", screen_no, code) ############################################################### # 메서드 정의: 조건검색 관련 메서드와 이벤트 # ############################################################### def receive_condition_ver(self, receive, msg): """ getConditionLoad() 메서드의 조건식 목록 요청에 대한 응답 이벤트 :param receive: int - 응답결과(1: 성공, 나머지 실패) :param msg: string - 메세지 """ try: if not receive: return self.condition = self.get_condition_name_list() print("조건식 개수: ", len(self.condition)) for key in self.condition.keys(): print("조건식: ", key, ": ", self.condition[key]) print("key type: ", type(key)) except Exception as e: print(e) finally: self.condition_loop.exit() def receive_tr_condition(self, screen_no, codes, condition_name, condition_index, inquiry): """ (1회성, 실시간) 종목 조건검색 요청시 발생되는 이벤트 :param screen_no: string :param codes: string - 종목코드 목록(각 종목은 세미콜론으로 구분됨) :param condition_name: string - 조건식 이름 :param condition_index: int - 조건식 인덱스 :param inquiry: int - 조회구분(0: 남은데이터 없음, 2: 남은데이터 있음) """ print("[receive_tr_condition]") try: if codes == "": return code_list = codes.split(';') del code_list[-1] print(code_list) print("종목개수: ", len(code_list)) finally: self.condition_loop.exit() def receive_real_condition(self, code, event, condition_name, condition_index): """ 실시간 종목 조건검색 요청시 발생되는 이벤트 :param code: string - 종목코드 :param event: string - 이벤트종류("I": 종목편입, "D": 종목이탈) :param condition_name: string - 조건식 이름 :param condition_index: string - 조건식 인덱스(여기서만 인덱스가 string 타입으로 전달됨) """ print("[receive_real_condition]") print("종목코드: ", code) print("이벤트: ", "종목편입" if event == "I" else "종목이탈") def get_condition_load(self): """ 조건식 목록 요청 메서드 """ if not self.get_connect_state(): raise KiwoomConnectError() is_load = self.dynamicCall("GetConditionLoad()") # 요청 실패시 if not is_load: raise KiwoomProcessingError("getConditionLoad(): 조건식 요청 실패") # receiveConditionVer() 이벤트 메서드에서 루프 종료 self.condition_loop = QEventLoop() self.condition_loop.exec_() def get_condition_name_list(self): """ 조건식 획득 메서드 조건식을 딕셔너리 형태로 반환합니다. 이 메서드는 반드시 receiveConditionVer() 이벤트 메서드안에서 사용해야 합니다. :return: dict - {인덱스:조건명, 인덱스:조건명, ...} """ data = self.dynamicCall("get_condition_name_list()") if data == "": raise KiwoomProcessingError( "get_condition_name_list(): 사용자 조건식이 없습니다.") conditionList = data.split(';') del conditionList[-1] condition_dictionary = {} for condition in conditionList: key, value = condition.split('^') condition_dictionary[int(key)] = value return condition_dictionary def send_condition(self, screen_no, condition_name, condition_index, is_real_time): """ 종목 조건검색 요청 메서드 이 메서드로 얻고자 하는 것은 해당 조건에 맞는 종목코드이다. 해당 종목에 대한 상세정보는 set_real_reg() 메서드로 요청할 수 있다. 요청이 실패하는 경우는, 해당 조건식이 없거나, 조건명과 인덱스가 맞지 않거나, 조회 횟수를 초과하는 경우 발생한다. 조건검색에 대한 결과는 1회성 조회의 경우, receiveTrCondition() 이벤트로 결과값이 전달되며 실시간 조회의 경우, receiveTrCondition()과 receiveRealCondition() 이벤트로 결과값이 전달된다. :param screen_no: string :param condition_name: string - 조건식 이름 :param condition_index: int - 조건식 인덱스 :param is_real_time: int - 조건검색 조회구분(0: 1회성 조회, 1: 실시간 조회) """ if not self.get_connect_state(): raise KiwoomConnectError() if not (isinstance(screen_no, str) and isinstance(condition_name, str) and isinstance(condition_index, int) and isinstance(is_real_time, int)): raise ParameterTypeError() is_request = self.dynamicCall( "SendCondition(QString, QString, int, int", screen_no, condition_name, condition_index, is_real_time) if not is_request: raise KiwoomProcessingError("sendCondition(): 조건검색 요청 실패") # receiveTrCondition() 이벤트 메서드에서 루프 종료 self.condition_loop = QEventLoop() self.condition_loop.exec_() def send_condition_stop(self, screen_no, condition_name, condition_index): """ 종목 조건검색 중지 메서드 """ if not self.get_connect_state(): raise KiwoomConnectError() if not (isinstance(screen_no, str) and isinstance(condition_name, str) and isinstance(condition_index, int)): raise ParameterTypeError() self.dynamicCall("SendConditionStop(QString, QString, int)", screen_no, condition_name, condition_index) ############################################################### # 메서드 정의: 주문과 잔고처리 관련 메서드 # # 1초에 5회까지 주문 허용 # ############################################################### def send_order(self, request_name, screen_no, account_no, order_type, code, qty, price, hoga_type, origin_order_no): """ 주식 주문 메서드 send_order() 메소드 실행시, OnReceiveMsg, OnReceiveTrData, OnReceiveChejanData 이벤트가 발생한다. 이 중, 주문에 대한 결과 데이터를 얻기 위해서는 OnReceiveChejanData 이벤트를 통해서 처리한다. OnReceiveTrData 이벤트를 통해서는 주문번호를 얻을 수 있는데, 주문후 이 이벤트에서 주문번호가 ''공백으로 전달되면, 주문접수 실패를 의미한다. :param request_name: string - 주문 요청명(사용자 정의) :param screen_no: string - 화면번호(4자리) :param account_no: string - 계좌번호(10자리) :param order_type: int - 주문유형(1: 신규매수, 2: 신규매도, 3: 매수취소, 4: 매도취소, 5: 매수정정, 6: 매도정정) :param code: string - 종목코드 :param qty: int - 주문수량 :param price: int - 주문단가 :param hoga_type: string - 거래구분(00: 지정가, 03: 시장가, 05: 조건부지정가, 06: 최유리지정가, 그외에는 api 문서참조) :param origin_order_no: string - 원주문번호(신규주문에는 공백, 정정및 취소주문시 원주문번호르 입력합니다.) """ if not self.get_connect_state(): raise KiwoomConnectError() if not (isinstance(request_name, str) and isinstance(screen_no, str) and isinstance(account_no, str) and isinstance(order_type, int) and isinstance(code, str) and isinstance(qty, int) and isinstance(price, int) and isinstance(hoga_type, str) and isinstance(origin_order_no, str)): raise ParameterTypeError() return_code = self.dynamicCall( "SendOrder(QString, QString, QString, int, QString, int, int, QString, QString)", [ request_name, screen_no, account_no, order_type, code, qty, price, hoga_type, origin_order_no ]) if return_code != ReturnCode.OP_ERR_NONE: raise KiwoomProcessingError("send_order(): " + ReturnCode.CAUSE[return_code]) # receiveTrData() 에서 루프종료 self.order_loop = QEventLoop() self.order_loop.exec_() def GetChejanData(self, nFid): cmd = 'GetChejanData("%s")' % nFid ret = self.dynamicCall(cmd) return ret ############################################################### # 기타 메서드 정의 # ############################################################### def get_code_list(self, *market): """ 여러 시장의 종목코드를 List 형태로 반환하는 헬퍼 메서드. :param market: Tuple - 여러 개의 문자열을 매개변수로 받아 Tuple로 처리한다. :return: List """ code_list = [] for m in market: tmp_list = self.get_codelist_by_market(m) code_list += tmp_list return code_list def get_master_code_name(self, code): """ 종목코드의 한글명을 반환한다. :param code: string - 종목코드 :return: string - 종목코드의 한글명 """ if not self.get_connect_state(): raise KiwoomConnectError() if not isinstance(code, str): raise ParameterTypeError() cmd = 'GetMasterCodeName("%s")' % code name = self.dynamicCall(cmd) return name def change_format(self, data, percent=0): if percent == 0: d = int(data) format_data = '{:-,d}'.format(d) elif percent == 1: f = float(data) f -= 100 format_data = '{:-,.2f}'.format(f) elif percent == 2: f = float(data) format_data = '{:-,.2f}'.format(f) return format_data def opw_data_reset(self): """ 잔고 및 보유종목 데이터 초기화 """ self.data_opw00001 = 0 self.data_opw00018 = {'account_evaluation': [], 'stocks': []}
def render_action(self, action): from PyQt5.QtCore import QEventLoop, QPropertyAnimation, QEasingCurve, QPoint from PyQt5.QtGui import QPixmap from PyQt5.QtWidgets import QApplication, QWidget, QLabel if self._app is None: # Create the window self._app = QApplication([]) self._loop = QEventLoop(self._app) self._win = QLabel() self._win.resize(800, 600) self._win.setPixmap(QPixmap("qrcodes/terminals_bg.png")) self._win.show() # Two terminal indicators self._win_terminals = [] for i in range(2): t = QLabel(self._win) t.resize(128, 128) t.setPixmap(QPixmap("qrcodes/packet.png")) t.move(56, [132, 339][i]) t.show() self._win_terminals.append(t) # The agent self._agent = QLabel(self._win) self._agent.resize(128, 128) self._agent.move(*LOC_ROOT) self._agent.show() self._agent_on = QPixmap("qrcodes/agent_packet.png") self._agent_off = QPixmap("qrcodes/agent.png") # Reward indicator self._reward_label = QLabel(self._win) # Update the terminal indicators for i in range(2): self._win_terminals[i].setVisible(self.terminals[i] > 0) # Move the agent ann = QPropertyAnimation(self._agent, b"pos") ann.setStartValue(self._agent.pos()) ann.setEndValue(QPoint(*([LOC_ROOT, LOC_T1, LOC_T2][action]))) ann.setDuration(1000) # One second ann.setEasingCurve(QEasingCurve.InOutQuad) if action == 0 and self._last_reward == 2.0: self._agent.setPixmap( self._agent_on) # Go to the root with an object else: self._agent.setPixmap(self._agent_off) # Move and display the reward label if a reward was obtained if self._last_reward != 0.0: rann = QPropertyAnimation(self._reward_label, b"pos") self._reward_label.setText('<h1>%+i</h1>' % self._last_reward) self._reward_label.show() start = [LOC_ROOT, LOC_T1, LOC_T2][self._last_action] start = (start[0] - 20, start[1] + 50) end = (start[0] - 20, start[1] - 50) rann.setStartValue(QPoint(*start)) rann.setEndValue(QPoint(*end)) rann.setDuration(1000) rann.start() ann.finished.connect(self._loop.quit) ann.start() self._loop.exec_() self._reward_label.hide() self._app.processEvents()
def _runHtmlBuilder(self): # Build the commond line for Sphinx. if core.config()['Sphinx']['AdvancedMode']: htmlBuilderCommandLine = core.config()['Sphinx']['Cmdline'] if sys.platform.startswith('linux'): # If Linux is used, then subprocess cannot take the whole # commandline as the name of an executable file. Module shlex # has to be used to parse commandline. htmlBuilderCommandLine = shlex.split(htmlBuilderCommandLine) else: # For available builder options, refer to: http://sphinx-doc.org/builders.html htmlBuilderCommandLine = [core.config()['Sphinx']['Executable'], # Place doctrees in the ``_build`` directory; by default, Sphinx # places this in _build/html/.doctrees. '-d', os.path.join('_build', 'doctrees'), # Source directory -- the current directory, since we'll chdir to # the project directory before executing this. '.', # Build directory core.config()['Sphinx']['OutputPath']] # Invoke it. try: # Clear the log at the beginning of a Sphinx build. self.logWindowClear.emit() cwd = core.config()['Sphinx']['ProjectPath'] # If the command line is already a string (advanced mode), just print it. # Otherwise, it's a list that should be transformed to a string. if isinstance(htmlBuilderCommandLine, str): htmlBuilderCommandLineStr = htmlBuilderCommandLine else: htmlBuilderCommandLineStr = ' '.join(htmlBuilderCommandLine) self.logWindowText.emit('{} : {}\n\n'.format(cwd, htmlBuilderCommandLineStr)) # Run Sphinx, reading stdout in a separate thread. self._qe = QEventLoop() # Sphinx will output just a carriage return (0x0D) to simulate a # single line being updated by build status and the build # progresses. Without universal newline support here, we'll wait # until the build is complete (with a \n\r) to report any build # progress! So, enable universal newlines, so that each \r will be # treated as a separate line, providing immediate feedback on build # progress. popen = open_console_output(htmlBuilderCommandLine, cwd=cwd, universal_newlines=True) # Perform reads in an event loop. The loop is exit when all reads # have completed. We can't simply start the _stderr_read thread # here, because calls to self._qe_exit() will be ignored until # we're inside the event loop. QTimer.singleShot(0, lambda: self._popen_read(popen)) self._qe.exec_() except OSError as ex: return ( 'Failed to execute HTML builder:\n' '{}\n'.format(str(ex)) + 'Go to Settings -> Settings -> CodeChat to set HTML' ' builder configurations.') return self._stderr
def wait_ms(self, timeout): ''' Block loop until timeout (ms) elapses. ''' loop = QEventLoop() QTimer.singleShot(timeout, loop.exit) loop.exec_()
self.file_writer_group.root_dir_entry.setText( p['Writer']['Data dir']) self.file_writer_group.ctset_fmt_entry.setText( p['Writer']['CT scan name']) self.file_writer_group.dsetname_entry.setText( p['Writer']['Filename']) self.file_writer_group.bigtiff_checkbox.setChecked( p['Writer']['Big tiffs']) self.file_writer_group.separate_scans_checkbox.setChecked( p['Writer']['Separate scans']) except: warning_message('Cannot enter file-writer settings correctly') if __name__ == '__main__': parsed_args, unparsed_args = process_cl_args() if parsed_args.debug: log.log_to_console(level=logging.DEBUG) # QApplication expects the first argument to be the program name. qt_args = sys.argv[:1] + unparsed_args app = QApplication(qt_args) loop = QEventLoop(app) root_dir = os.path.dirname(os.path.abspath(__file__)) style_file = QFile(os.path.join(root_dir, "styles/breeze/dark.qss")) style_file.open(QFile.ReadOnly | QFile.Text) stream = QTextStream(style_file) # Set application style to dark; Comment following line to unset # app.setStyleSheet(stream.readAll()) ex = GUI() sys.exit(app.exec_())
def responseComplete(self): request = self.serverInterface().requestHandler() params = request.parameterMap() # SERVICE=RENDERGEOJSON -- we are taking over if params.get('SERVICE', '').upper() == 'RENDERGEOJSON': request.clear() try: # Parse parameters geojson = params.get('GEOJSON') if not geojson: raise ParameterError('Parameter GEOJSON must be set.') style = params.get('STYLE') if not style: raise ParameterError('Parameter STYLE must be set.') try: width = int(params.get('WIDTH')) except TypeError: raise ParameterError('Parameter WIDTH must be integer.') try: height = int(params.get('HEIGHT')) except TypeError: raise ParameterError('Parameter HEIGHT must be integer.') try: dpi = int(params.get('DPI', 96)) except TypeError: raise ParameterError('Parameter DPI must be integer.') try: minx, miny, maxx, maxy = params.get('BBOX').split(',') bbox = QgsRectangle(float(minx), float(miny), float(maxx), float(maxy)) except (ValueError, AttributeError): raise ParameterError( 'Parameter BBOX must be specified in the form `min_x,min_y,max_x,max_y`.' ) url = geojson geojson_file_name = self._resolve_url(geojson) if '$type' in style: polygon_style = self._resolve_url( style.replace('$type', 'polygons')) line_style = self._resolve_url( style.replace('$type', 'lines')) point_style = self._resolve_url( style.replace('$type', 'points')) else: polygon_style = self._resolve_url(style) line_style = polygon_style point_style = polygon_style polygon_layer = QgsVectorLayer( geojson_file_name + '|geometrytype=Polygon', 'polygons', 'ogr') self._load_style(polygon_layer, polygon_style) line_layer = QgsVectorLayer( geojson_file_name + '|geometrytype=Line', 'lines', 'ogr') self._load_style(line_layer, line_style) point_layer = QgsVectorLayer( geojson_file_name + '|geometrytype=Point', 'points', 'ogr') self._load_style(point_layer, point_style) settings = QgsMapSettings() settings.setOutputSize(QSize(width, height)) settings.setOutputDpi(dpi) settings.setExtent(bbox) settings.setLayers([polygon_layer, line_layer, point_layer]) settings.setBackgroundColor(QColor(Qt.transparent)) renderer = QgsMapRendererParallelJob(settings) event_loop = QEventLoop() renderer.finished.connect(event_loop.quit) renderer.start() event_loop.exec_() img = renderer.renderedImage() img.setDotsPerMeterX(dpi * 39.37) img.setDotsPerMeterY(dpi * 39.37) image_data = QByteArray() buf = QBuffer(image_data) buf.open(QIODevice.WriteOnly) img.save(buf, 'PNG') request.setResponseHeader('Content-type', 'image/png') request.appendBody(image_data) except ParameterError as e: QgsMessageLog.logMessage( "RenderGeojson.responseComplete :: ParameterError") request.setResponseHeader('Content-type', 'text/plain') request.appendBody(str(e).encode('utf-8')) except: QgsMessageLog.logMessage( "RenderGeojson.responseComplete :: Exception") QgsMessageLog.logMessage( "RenderGeojson.responseComplete :: {}".format( traceback.format_exc())) request.setResponseHeader('Content-type', 'text/plain') request.appendBody(b'Unhandled error') request.appendBody(traceback.format_exc().encode('utf-8'))
def q_sleep(t_s): loop = QEventLoop() QTimer.singleShot(int(t_s * 1000), loop.quit) loop.exec_()
class CadnanoQt(QObject): dontAskAndJustDiscardUnsavedChanges = False documentWasCreatedSignal = pyqtSignal(object) # doc documentWindowWasCreatedSignal = pyqtSignal(object, object) # doc, window def __init__(self, argv): """Create the application object """ self.argns, unused = util.parse_args(argv, use_gui=True) # util.init_logging(self.argns.__dict__) # logger.info("CadnanoQt initializing...") if argv is None: argv = sys.argv self.argv = argv # print("initializing new CadnanoQt", type(QCoreApplication.instance())) if QCoreApplication.instance() is None: self.qApp = QApplication(argv) assert(QCoreApplication.instance() is not None) self.qApp.setOrganizationDomain("cadnano.org") else: self.qApp = qApp super(CadnanoQt, self).__init__() # print("initialized new CadnanoQt") from cadnano.views.preferences import Preferences self.prefs = Preferences() self.icon = icon = QIcon(ICON_PATH1) icon.addFile(ICON_PATH2, QSize(256, 256)) icon.addFile(ICON_PATH3, QSize(48, 48)) self.qApp.setWindowIcon(icon) self.main_event_loop = None self.document_controllers = set() # Open documents self.active_document = None self._document = None self.documentWasCreatedSignal.connect(self.wirePrefsSlot) # end def def document(self) -> DocT: return self._document # end def def finishInit(self): global decodeFile global Document global DocumentController from cadnano.document import Document from cadnano.fileio.decode import decodeFile from cadnano.controllers.documentcontroller import DocumentController from cadnano.views.pathview import pathstyles as styles styles.setFontMetrics() doc = Document() self._document = self.createDocument(base_doc=doc) if os.environ.get('CADNANO_DISCARD_UNSAVED', False) and not self.ignoreEnv(): self.dontAskAndJustDiscardUnsavedChanges = True self.dontAskAndJustDiscardUnsavedChanges = True # end def def exec_(self): if hasattr(self, 'qApp'): self.main_event_loop = QEventLoop() self.main_event_loop.exec_() def destroyApp(self): """Destroy the QApplication. Do not set `self.qApp = None` in this method. Do it external to the CadnanoQt class """ global decodeFile global Document global DocumentController # print("documentWasCreatedSignal", self.documentWasCreatedSignal) if self.document_controllers: self.documentWasCreatedSignal.disconnect(self.wirePrefsSlot) decodeFile = None Document = None DocumentController = None self.document_controllers.clear() self.qApp.quit() # end def def ignoreEnv(self): return os.environ.get('CADNANO_IGNORE_ENV_VARS_EXCEPT_FOR_ME', False) def createDocument(self, base_doc: DocT = None): global DocumentController # print("CadnanoQt createDocument begin") default_file = self.argns.file or os.environ.get('CADNANO_DEFAULT_DOCUMENT', None) if default_file is not None and base_doc is not None: default_file = os.path.expanduser(default_file) default_file = os.path.expandvars(default_file) dc = DocumentController(base_doc) # logger.info("Loading cadnano file %s to base document %s", default_file, base_doc) decodeFile(default_file, document=base_doc) dc.setFileName(default_file) print("Loaded default document: %s" % (default_file)) else: doc_ctrlr_count = len(self.document_controllers) # logger.info("Creating new empty document...") if doc_ctrlr_count == 0: # first dc # dc adds itself to app.document_controllers dc = DocumentController(base_doc) elif doc_ctrlr_count == 1: # dc already exists dc = list(self.document_controllers)[0] dc.newDocument() # tell it to make a new doucment # print("CadnanoQt createDocument done") return dc.document() def prefsClicked(self): self.prefs.showDialog() def wirePrefsSlot(self, document: DocT): """MUST CALL THIS TO SET PREFERENCES :class:`Document` """ self.prefs.document = document
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)
import sys from PyQt5.QAxContainer import QAxWidget from PyQt5.QtCore import QEventLoop from PyQt5.QtWidgets import QApplication app = QApplication(sys.argv) control = QAxWidget("{A1574A0D-6BFA-4BD7-9020-DED88711818D}") loop = QEventLoop() def comm_connect(): err = control.dynamicCall("CommConnect()") if err < 0: raise ValueError(err) loop.exec_() def on_event_connect(errcode): if errcode < 0: raise ValueError(errcode) if errcode == 0: print("Connected!") control.OnEventConnect.disconnect(on_event_connect) loop.exit() control.OnEventConnect.connect(on_event_connect) comm_connect()
def run(): """ Creates all the top-level assets for the application, sets things up and then runs the application. Specific tasks include: - set up logging - create an application object - create an editor window and status bar - display a splash screen while starting - close the splash screen after startup timer ends """ setup_logging() logging.info("\n\n-----------------\n\nStarting Mu {}".format(__version__)) logging.info(platform.uname()) logging.info("Python path: {}".format(sys.path)) logging.info("Language code: {}".format(i18n.language_code)) # # Load settings from known locations and register them for # autosave # settings.init() # Images (such as toolbar icons) aren't scaled nicely on retina/4k displays # unless this flag is set os.environ["QT_AUTO_SCREEN_SCALE_FACTOR"] = "1" if hasattr(Qt, "AA_EnableHighDpiScaling"): QApplication.setAttribute(Qt.AA_EnableHighDpiScaling) QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps) # An issue in PyQt5 v5.13.2 to v5.15.1 makes PyQt5 application # hang on Mac OS 11 (Big Sur) # Setting this environment variable fixes the problem. # See issue #1147 for more information os.environ["QT_MAC_WANTS_LAYER"] = "1" # The app object is the application running on your computer. app = QApplication(sys.argv) # By default PyQt uses the script name (run.py) app.setApplicationName("mu") # Set hint as to the .desktop files name app.setDesktopFileName("mu.codewith.editor") app.setApplicationVersion(__version__) app.setAttribute(Qt.AA_DontShowIconsInMenus) # Display a friendly "splash" icon. splash = AnimatedSplash(load_movie("splash_screen")) splash.show() app.processEvents() # Create a blocking thread upon which to run the StartupWorker and which # will process the events for animating the splash screen. initLoop = QEventLoop() thread = QThread() worker = StartupWorker() worker.moveToThread(thread) thread.started.connect(worker.run) worker.finished.connect(thread.quit) worker.finished.connect(worker.deleteLater) # Stop the blocking event loop when the thread is finished. thread.finished.connect(initLoop.quit) thread.finished.connect(thread.deleteLater) thread.start() initLoop.exec() # start processing the pending StartupWorker. # Create the "window" we'll be looking at. editor_window = Window() @editor_window.load_theme.connect def load_theme(theme): if theme == "contrast": app.setStyleSheet(CONTRAST_STYLE) elif theme == "night": app.setStyleSheet(NIGHT_STYLE) else: app.setStyleSheet(DAY_STYLE) splash.finish(editor_window) # Make sure all windows have the Mu icon as a fallback app.setWindowIcon(load_icon(editor_window.icon)) # Create the "editor" that'll control the "window". editor = Editor(view=editor_window) editor.setup(setup_modes(editor, editor_window)) # Setup the window. editor_window.closeEvent = editor.quit editor_window.setup(editor.debug_toggle_breakpoint, editor.theme) # Connect the various UI elements in the window to the editor. editor_window.connect_tab_rename(editor.rename_tab, "Ctrl+Shift+S") editor_window.connect_find_replace(editor.find_replace, "Ctrl+F") # Connect find again both forward and backward ('Shift+F3') find_again_handlers = (editor.find_again, editor.find_again_backward) editor_window.connect_find_again(find_again_handlers, "F3") editor_window.connect_toggle_comments(editor.toggle_comments, "Ctrl+K") editor.connect_to_status_bar(editor_window.status_bar) # Restore the previous session along with files passed by the os editor.restore_session(sys.argv[1:]) # Stop the program after the application finishes executing. sys.exit(app.exec_())
class QtReactor(posixbase.PosixReactorBase): implements(IReactorFDSet) def __init__(self): self._reads = {} self._writes = {} self._notifiers = {} self._timer = QTimer() self._timer.setSingleShot(True) self._timer.timeout.connect(self.iterate) if QCoreApplication.instance() is None: # Application Object has not been started yet self.qApp = QCoreApplication([]) self._ownApp = True else: self.qApp = QCoreApplication.instance() self._ownApp = False self._blockApp = None posixbase.PosixReactorBase.__init__(self) def _add(self, xer, primary, type): """ Private method for adding a descriptor from the event loop. It takes care of adding it if new or modifying it if already added for another state (read -> read/write for example). """ if xer not in primary: primary[xer] = TwistedSocketNotifier(None, self, xer, type) def addReader(self, reader): """ Add a FileDescriptor for notification of data available to read. """ self._add(reader, self._reads, QSocketNotifier.Read) def addWriter(self, writer): """ Add a FileDescriptor for notification of data available to write. """ self._add(writer, self._writes, QSocketNotifier.Write) def _remove(self, xer, primary): """ Private method for removing a descriptor from the event loop. It does the inverse job of _add, and also add a check in case of the fd has gone away. """ if xer in primary: notifier = primary.pop(xer) notifier.shutdown() def removeReader(self, reader): """ Remove a Selectable for notification of data available to read. """ self._remove(reader, self._reads) def removeWriter(self, writer): """ Remove a Selectable for notification of data available to write. """ self._remove(writer, self._writes) def removeAll(self): """ Remove all selectables, and return a list of them. """ rv = self._removeAll(self._reads, self._writes) return rv def getReaders(self): return self._reads.keys() def getWriters(self): return self._writes.keys() def callLater(self, howlong, *args, **kargs): rval = super(QtReactor, self).callLater(howlong, *args, **kargs) self.reactorInvocation() return rval def reactorInvocation(self): self._timer.stop() self._timer.setInterval(0) self._timer.start() def _iterate(self, delay=None, fromqt=False): """ See twisted.internet.interfaces.IReactorCore.iterate. """ self.runUntilCurrent() self.doIteration(delay, fromqt) iterate = _iterate def doIteration(self, delay=None, fromqt=False): """ This method is called by a Qt timer or by network activity on a file descriptor """ if not self.running and self._blockApp: self._blockApp.quit() self._timer.stop() delay = max(delay, 1) if not fromqt: self.qApp.processEvents(QEventLoop.AllEvents, delay * 1000) if self.timeout() is None: timeout = 0.1 elif self.timeout() == 0: timeout = 0 else: timeout = self.timeout() self._timer.setInterval(timeout * 1000) self._timer.start() def runReturn(self, installSignalHandlers=True): self.startRunning(installSignalHandlers=installSignalHandlers) self.reactorInvocation() def run(self, installSignalHandlers=True): if self._ownApp: self._blockApp = self.qApp else: self._blockApp = QEventLoop() self.runReturn() self._blockApp.exec_()
class CharacterDialog(WindowModalDialog): def __init__(self, parent): super(CharacterDialog, self).__init__(parent) self.setWindowTitle(_("KeepKey Seed Recovery")) self.character_pos = 0 self.word_pos = 0 self.loop = QEventLoop() self.word_help = QLabel() self.char_buttons = [] vbox = QVBoxLayout(self) vbox.addWidget(WWLabel(CHARACTER_RECOVERY)) hbox = QHBoxLayout() hbox.addWidget(self.word_help) for i in range(4): char_button = CharacterButton('*') char_button.setMaximumWidth(36) self.char_buttons.append(char_button) hbox.addWidget(char_button) self.accept_button = CharacterButton(_("Accept Word")) self.accept_button.clicked.connect(partial(self.process_key, 32)) self.rejected.connect(partial(self.loop.exit, 1)) hbox.addWidget(self.accept_button) hbox.addStretch(1) vbox.addLayout(hbox) self.finished_button = QPushButton(_("Seed Entered")) self.cancel_button = QPushButton(_("Cancel")) self.finished_button.clicked.connect(partial(self.process_key, Qt.Key_Return)) self.cancel_button.clicked.connect(self.rejected) buttons = Buttons(self.finished_button, self.cancel_button) vbox.addSpacing(40) vbox.addLayout(buttons) self.refresh() self.show() def refresh(self): self.word_help.setText("Enter seed word %2d:" % (self.word_pos + 1)) self.accept_button.setEnabled(self.character_pos >= 3) self.finished_button.setEnabled((self.word_pos in (11, 17, 23) and self.character_pos >= 3)) for n, button in enumerate(self.char_buttons): button.setEnabled(n == self.character_pos) if n == self.character_pos: button.setFocus() def is_valid_alpha_space(self, key): # Auto-completion requires at least 3 characters if key == ord(' ') and self.character_pos >= 3: return True # Firmware aborts protocol if the 5th character is non-space if self.character_pos >= 4: return False return (key >= ord('a') and key <= ord('z') or (key >= ord('A') and key <= ord('Z'))) def process_key(self, key): self.data = None if key == Qt.Key_Return and self.finished_button.isEnabled(): self.data = {'done': True} elif key == Qt.Key_Backspace and (self.word_pos or self.character_pos): self.data = {'delete': True} elif self.is_valid_alpha_space(key): self.data = {'character': chr(key).lower()} if self.data: self.loop.exit(0) def keyPressEvent(self, event): self.process_key(event.key()) if not self.data: QDialog.keyPressEvent(self, event) def get_char(self, word_pos, character_pos): self.word_pos = word_pos self.character_pos = character_pos self.refresh() if self.loop.exec_(): self.data = None # User cancelled
class TerminalsEnv(gym.Env): """ Two terminals, A and B, contain objects that a robot has to carry to the root R. Terminals contain a finite number of objects, and the robot gets a negative reward when it goes to an empty one. This means that once a terminal is empty, the robot must remember to now go to the other one. """ metadata = {'render.modes': ['human']} def __init__(self): self.action_space = spaces.Discrete(3) # Go to root, A or B self.observation_space = spaces.Discrete( 2) # "Empty" (0) or "Full" (1), the root always returns 0 self._render_enable = False self._app = None self._last_reward = 0.0 self._last_action = 0 self._seed() def _render(self, mode, close): self._render_enable = True def _seed(self, seed=None): self.np_random, seed = seeding.np_random(seed) return [seed] def _step(self, action): assert self.action_space.contains(action) # Animate the current action if needed if self._render_enable: self.render_action(action) self._last_action = action if action == 0: # Going to the root does nothing self._last_reward = 0.0 return 0, self._last_reward, False, {} else: terminal = action - 1 if self.terminals[terminal] > 0: # There are objects left in the terminal, remove one self.terminals[terminal] -= 1 self._last_reward = 2.0 return 1, self._last_reward, False, {} else: # The terminal is empty, refill the other ones self.rounds -= 1 self._last_reward = -2.0 done = (self.rounds == 0) for i in range(TERMINALS): if i != terminal: self.terminals[i] = random.randint(TOP, TOP * 2) return 0, self._last_reward, done, {} def _reset(self): self.terminals = [random.randint(TOP, TOP * 2)] * TERMINALS self.rounds = random.randint(ROUNDS, ROUNDS + 1) self._last_reward = 0.0 if self._render_enable and self._app is not None: # Ensure that the agent starts at the root self._agent.move(*LOC_ROOT) return 0 # Implicitly start at the root with no information def render_action(self, action): from PyQt5.QtCore import QEventLoop, QPropertyAnimation, QEasingCurve, QPoint from PyQt5.QtGui import QPixmap from PyQt5.QtWidgets import QApplication, QWidget, QLabel if self._app is None: # Create the window self._app = QApplication([]) self._loop = QEventLoop(self._app) self._win = QLabel() self._win.resize(800, 600) self._win.setPixmap(QPixmap("qrcodes/terminals_bg.png")) self._win.show() # Two terminal indicators self._win_terminals = [] for i in range(2): t = QLabel(self._win) t.resize(128, 128) t.setPixmap(QPixmap("qrcodes/packet.png")) t.move(56, [132, 339][i]) t.show() self._win_terminals.append(t) # The agent self._agent = QLabel(self._win) self._agent.resize(128, 128) self._agent.move(*LOC_ROOT) self._agent.show() self._agent_on = QPixmap("qrcodes/agent_packet.png") self._agent_off = QPixmap("qrcodes/agent.png") # Reward indicator self._reward_label = QLabel(self._win) # Update the terminal indicators for i in range(2): self._win_terminals[i].setVisible(self.terminals[i] > 0) # Move the agent ann = QPropertyAnimation(self._agent, b"pos") ann.setStartValue(self._agent.pos()) ann.setEndValue(QPoint(*([LOC_ROOT, LOC_T1, LOC_T2][action]))) ann.setDuration(1000) # One second ann.setEasingCurve(QEasingCurve.InOutQuad) if action == 0 and self._last_reward == 2.0: self._agent.setPixmap( self._agent_on) # Go to the root with an object else: self._agent.setPixmap(self._agent_off) # Move and display the reward label if a reward was obtained if self._last_reward != 0.0: rann = QPropertyAnimation(self._reward_label, b"pos") self._reward_label.setText('<h1>%+i</h1>' % self._last_reward) self._reward_label.show() start = [LOC_ROOT, LOC_T1, LOC_T2][self._last_action] start = (start[0] - 20, start[1] + 50) end = (start[0] - 20, start[1] - 50) rann.setStartValue(QPoint(*start)) rann.setEndValue(QPoint(*end)) rann.setDuration(1000) rann.start() ann.finished.connect(self._loop.quit) ann.start() self._loop.exec_() self._reward_label.hide() self._app.processEvents()
class Downloader(QObject): finished = pyqtSignal(bool) def __init__(self, des_path, source_path, reqTimeout=10, readTimeout=30, try_time=3): super(Downloader, self).__init__() self.retStatus = False self.source_path = source_path self.des_path = des_path self.bak_file = des_path + ".bak" self.try_time = try_time self.readTimeout = readTimeout * 1000 self.reqTimeout = reqTimeout * 1000 self.data = None # request timer, default 5 sec self.reqTimer = QTimer() self.reqTimer.timeout.connect(self.onReqTimeOut) # read data timer, default no time unlimit self.readTimer = QTimer() self.readTimer.timeout.connect(self.onReadTimeOut) self.networkManager = QNetworkAccessManager() self.request() if os.path.exists(self.bak_file): os.remove(self.bak_file) if os.path.exists(self.des_path): os.rename(self.des_path, self.bak_file) self.fp = open(self.des_path, "wb") self.reqTimer.start(self.reqTimeout) self.eventLoop = QEventLoop() self.finished.connect(self.eventLoop.quit) self.eventLoop.exec_() self.fp.close() if self.retStatus is False: if os.path.exists(self.des_path): os.remove(self.des_path) if os.path.exists(self.bak_file): os.rename(self.bak_file, self.des_path) else: if os.path.exists(self.bak_file): os.remove(self.bak_file) def destoryRequestReply(self, reply): reply.blockSignals(True) reply.close() reply = None def requestAgain(self): self.destoryRequestReply(self.reply) self.request() def request(self): try: if self.try_time: self.reply = self.networkManager.get( QNetworkRequest(QUrl(self.source_path))) self.reply.readyRead.connect(self.startRead) self.reply.downloadProgress.connect(self.onProgress) self.try_time = self.try_time - 1 else: self.finished.emit(self.retStatus) print("no try times, emit finished") self.reqTimer.stop() except Exception as e: print(e) def onReadTimeOut(self): # stop read timer print("read time out") self.readTimer.stop() # reset file data if self.fp.seekable(): self.fp.truncate(0) self.fp.seek(0, 0) else: self.fp.close() self.fp = open(self.des_path, "wb") # request again, start request timer self.requestAgain() self.reqTimer.start(self.reqTimeout) def onReqTimeOut(self): self.requestAgain() def startRead(self): self.reqTimer.stop() self.reply.readyRead.disconnect(self.startRead) def onProgress(self, cur, total): self.readTimer.start(self.readTimeout) if self.writeFile() is False: self.endRead(False) if cur == total and total > 0: self.endRead(True) self.readTimer.stop() def endRead(self, successful): if successful: print("finish download %s" % self.des_path) self.retStatus = True self.finished.emit(self.retStatus) self.destoryRequestReply(self.reply) else: self.requestAgain() self.reqTimer.start(self.reqTimeout) def writeFile(self): try: data = self.reply.readAll() if data: readLen = len(data) if readLen <= 0: e = Exception("Invaild Data, Request again") raise e self.fp.write(data) return True except Exception as e: print("Exception happen in Function[writeFile]: " + str(e)) return False
def exec_(self): if hasattr(self, 'qApp'): self.main_event_loop = QEventLoop() self.main_event_loop.exec_()
class Kiwoom(QAxWidget): def __init__(self): super().__init__() self.setControl("KHOPENAPI.KHOpenAPICtrl.1") self.conditionbuy = True # Loop 변수 # 비동기 방식으로 동작되는 이벤트를 동기화(순서대로 동작) 시킬 때 self.loginLoop = None self.requestLoop = None self.orderLoop = None self.conditionLoop = None # 서버구분 self.server = None # 조건식 self.condition = None self.conditionCodeList = [] # 에러 self.error = None # 주문번호 self.orderNo = "" # 조회 self.inquiry = 0 # 서버에서 받은 메시지 self.msg = "" # 예수금 d+2 self.opw00001Data = 0 # 보유종목 정보 self.opw00018Data = {'accountEvaluation': [], 'stocks': []} self.opt10001Data = {'code': [], 'high': [], 'low': []} # signal & slot self.OnEventConnect.connect(self.eventConnect) self.OnReceiveTrData.connect(self.receiveTrData) self.OnReceiveChejanData.connect(self.receiveChejanData) self.OnReceiveRealData.connect(self.receiveRealData) self.OnReceiveMsg.connect(self.receiveMsg) self.OnReceiveConditionVer.connect(self.receiveConditionVer) self.OnReceiveTrCondition.connect(self.receiveTrCondition) self.OnReceiveRealCondition.connect(self.receiveRealCondition) # 로깅용 설정파일 logging.config.fileConfig('logging.conf') self.log = logging.getLogger('Kiwoom') # 내가 추가 self.accountList = None self.order_time = time.time() self.request_thread_worker = RequestThreadWorker(self) self.request_thread = QThread() self.request_thread_worker.moveToThread(self.request_thread) self.request_thread.started.connect(self.request_thread_worker.run) self.request_thread.start() ############################################################### # 로깅용 메서드 정의 # ############################################################### def logger(origin): def wrapper(*args, **kwargs): args[0].log.debug('{} args - {}, kwargs - {}'.format( origin.__name__, args, kwargs)) return origin(*args, **kwargs) return wrapper ############################################################### # 이벤트 정의 # ############################################################### @SyncRequestDecorator.kiwoom_sync_callback def eventConnect(self, returnCode): """ 통신 연결 상태 변경시 이벤트 returnCode가 0이면 로그인 성공 그 외에는 ReturnCode 클래스 참조. :param returnCode: int """ try: if returnCode == ReturnCode.OP_ERR_NONE: self.server = self.getLoginInfo("GetServerGubun", True) if len(self.server) == 0 or self.server != "1": self.msg += "실서버 연결 성공" + "\r\n\r\n" else: self.msg += "모의투자서버 연결 성공" + "\r\n\r\n" else: self.msg += "연결 끊김: 원인 - " + ReturnCode.CAUSE[ returnCode] + "\r\n\r\n" except Exception as error: self.log.error('eventConnect {}'.format(error)) finally: # commConnect() 메서드에 의해 생성된 루프를 종료시킨다. # 로그인 후, 통신이 끊길 경우를 대비해서 예외처리함. try: self.loginLoop.exit() except AttributeError: pass def receiveMsg(self, screenNo, requestName, trCode, msg): """ 수신 메시지 이벤트 서버로 어떤 요청을 했을 때(로그인, 주문, 조회 등), 그 요청에 대한 처리내용을 전달해준다. :param screenNo: string - 화면번호(4자리, 사용자 정의, 서버에 조회나 주문을 요청할 때 이 요청을 구별하기 위한 키값) :param requestName: string - TR 요청명(사용자 정의) :param trCode: string :param msg: string - 서버로 부터의 메시지 """ self.msg += requestName + ": " + msg + "\r\n\r\n" @SyncRequestDecorator.kiwoom_sync_callback def receiveTrData(self, screenNo, requestName, trCode, recordName, inquiry, deprecated1, deprecated2, deprecated3, deprecated4): """ TR 수신 이벤트 조회요청 응답을 받거나 조회데이터를 수신했을 때 호출됩니다. requestName과 trCode는 commRqData()메소드의 매개변수와 매핑되는 값 입니다. 조회데이터는 이 이벤트 메서드 내부에서 getCommData() 메서드를 이용해서 얻을 수 있습니다. :param screenNo: string - 화면번호(4자리) :param requestName: string - TR 요청명(commRqData() 메소드 호출시 사용된 requestName) :param trCode: string :param recordName: string :param inquiry: string - 조회('0': 남은 데이터 없음, '2': 남은 데이터 있음) """ print("receiveTrData 실행: ", screenNo, requestName, trCode, recordName, inquiry) # 주문번호와 주문루프 self.orderNo = self.commGetData(trCode, "", requestName, 0, "주문번호") try: self.orderLoop.exit() except AttributeError: pass self.inquiry = inquiry if requestName == "관심종목정보요청": data = self.getCommDataEx(trCode, "관심종목정보") print(type(data)) print(data) """ commGetData cnt = self.getRepeatCnt(trCode, requestName) for i in range(cnt): data = self.commGetData(trCode, "", requestName, i, "종목명") print(data) """ # elif requestName == "주식일봉차트조회요청": # data = self.getCommDataEx(trCode, "주식일봉차트조회") # colName = ['종목코드', '현재가', '거래량', '거래대금', '일자', '시가', '고가', '저가', # '수정주가구분', '수정비율', '대업종구분', '소업종구분', '종목정보', '수정주가이벤트', '전일종가'] # data = DataFrame(data, columns=colName) # print(type(data)) # print(data.head(5)) """ commGetData cnt = self.getRepeatCnt(trCode, requestName) for i in range(cnt): date = self.commGetData(trCode, "", requestName, i, "일자") open = self.commGetData(trCode, "", requestName, i, "시가") high = self.commGetData(trCode, "", requestName, i, "고가") low = self.commGetData(trCode, "", requestName, i, "저가") close = self.commGetData(trCode, "", requestName, i, "현재가") print(date, ": ", open, ' ', high, ' ', low, ' ', close) """ elif requestName == "예수금상세현황요청": deposit = self.commGetData(trCode, "", requestName, 0, "d+2추정예수금") deposit = self.changeFormat(deposit) self.opw00001Data = deposit elif requestName == "주식기본정보요청": code = self.commGetData(trCode, "", requestName, 0, "종목코드") high = self.commGetData(trCode, "", requestName, 0, "고가") low = self.commGetData(trCode, "", requestName, 0, "저가") self.opt10001Data["code"].append(code) self.opt10001Data["high"].append(high) self.opt10001Data["low"].append(low) print("{} {} {}".format(code, high, low)) #opt10081 elif requestName == "주식일봉차트조회요청": data_cnt = self.getRepeatCnt(trCode, requestName) for i in range(data_cnt): date = self.commGetData(trCode, "", requestName, i, "일자") open = self.commGetData(trCode, "", requestName, i, "시가") high = self.commGetData(trCode, "", requestName, i, "고가") low = self.commGetData(trCode, "", requestName, i, "저가") close = self.commGetData(trCode, "", requestName, i, "현재가") volume = self.commGetData(trCode, "", requestName, i, "거래량") self.ohlcv['date'].append(date) self.ohlcv['open'].append(int(open)) self.ohlcv['high'].append(int(high)) self.ohlcv['low'].append(int(low)) self.ohlcv['close'].append(int(close)) self.ohlcv['volume'].append(int(volume)) elif requestName == "계좌평가잔고내역요청": # 계좌 평가 정보 accountEvaluation = [] keyList = ["총매입금액", "총평가금액", "총평가손익금액", "총수익률(%)", "추정예탁자산"] for key in keyList: value = self.commGetData(trCode, "", requestName, 0, key) if key.startswith("총수익률"): value = self.changeFormat(value, 1) else: value = self.changeFormat(value) accountEvaluation.append(value) self.opw00018Data['accountEvaluation'] = accountEvaluation # 보유 종목 정보 cnt = self.getRepeatCnt(trCode, requestName) keyList = ["종목명", "보유수량", "매입가", "현재가", "평가손익", "수익률(%)", "종목번호"] for i in range(cnt): stock = [] for key in keyList: value = self.commGetData(trCode, "", requestName, i, key) if key.startswith("수익률"): value = self.changeFormat(value, 2) elif key == "종목번호": value = value[1:] elif key != "종목명": value = self.changeFormat(value) stock.append(value) self.opw00018Data['stocks'].append(stock) try: self.requestLoop.exit() except AttributeError: pass def receiveRealData(self, code, realType, realData): print("receiveRealData executed") """ 실시간 데이터 수신 이벤트 실시간 데이터를 수신할 때 마다 호출되며, setRealReg() 메서드로 등록한 실시간 데이터도 이 이벤트 메서드에 전달됩니다. getCommRealData() 메서드를 이용해서 실시간 데이터를 얻을 수 있습니다. :param code: string - 종목코드 :param realType: string - 실시간 타입(KOA의 실시간 목록 참조) :param realData: string - 실시간 데이터 전문 """ try: self.log.debug("[receiveRealData]") self.log.debug("({})".format(realType)) if realType not in RealType.REALTYPE: return data = [] if code != "": data.append(code) codeOrNot = code else: codeOrNot = realType for fid in sorted(RealType.REALTYPE[realType].keys()): value = self.getCommRealData(codeOrNot, fid) data.append(value) print(data) # 권민 # TODO: DB에 저장 self.log.debug(data) except Exception as e: self.log.error('{}'.format(e)) def receiveChejanData(self, gubun, itemCnt, fidList): """ 주문 접수/확인 수신시 이벤트 주문요청후 주문접수, 체결통보, 잔고통보를 수신할 때 마다 호출됩니다. :param gubun: string - 체결구분('0': 주문접수/주문체결, '1': 잔고통보, '3': 특이신호) :param itemCnt: int - fid의 갯수 :param fidList: string - fidList 구분은 ;(세미콜론) 이다. """ fids = fidList.split(';') print("[receiveChejanData]") print("gubun: ", gubun, "itemCnt: ", itemCnt, "fidList: ", fidList) print("========================================") print("[ 구분: ", self.getChejanData(913) if '913' in fids else '잔고통보', "]") for fid in fids: print( FidList.CHEJAN[int(fid)] if int(fid) in FidList.CHEJAN else fid, ": ", self.getChejanData(int(fid))) print("========================================") ############################################################### # 메서드 정의: 로그인 관련 메서드 # ############################################################### def commConnect(self): """ 로그인을 시도합니다. 수동 로그인일 경우, 로그인창을 출력해서 로그인을 시도. 자동 로그인일 경우, 로그인창 출력없이 로그인 시도. """ self.dynamicCall("CommConnect()") self.loginLoop = QEventLoop() self.loginLoop.exec_() def getConnectState(self): """ 현재 접속상태를 반환합니다. 반환되는 접속상태는 아래와 같습니다. 0: 미연결, 1: 연결 :return: int """ state = self.dynamicCall("GetConnectState()") return state def getLoginInfo(self, tag, isConnectState=False): """ 사용자의 tag에 해당하는 정보를 반환한다. tag에 올 수 있는 값은 아래와 같다. ACCOUNT_CNT: 전체 계좌의 개수를 반환한다. ACCNO: 전체 계좌 목록을 반환한다. 계좌별 구분은 ;(세미콜론) 이다. USER_ID: 사용자 ID를 반환한다. USER_NAME: 사용자명을 반환한다. GetServerGubun: 접속서버 구분을 반환합니다.("1": 모의투자, 그외(빈 문자열포함): 실서버) :param tag: string :param isConnectState: bool - 접속상태을 확인할 필요가 없는 경우 True로 설정. :return: string """ if not isConnectState: if not self.getConnectState(): raise KiwoomConnectError() if not isinstance(tag, str): raise ParameterTypeError() if tag not in [ 'ACCOUNT_CNT', 'ACCNO', 'USER_ID', 'USER_NAME', 'GetServerGubun' ]: raise ParameterValueError() if tag == "GetServerGubun": info = self.getServerGubun() else: cmd = 'GetLoginInfo("%s")' % tag info = self.dynamicCall(cmd) return info def getServerGubun(self): """ 서버구분 정보를 반환한다. 리턴값이 "1"이면 모의투자 서버이고, 그 외에는 실서버(빈 문자열포함). :return: string """ ret = self.dynamicCall("KOA_Functions(QString, QString)", "GetServerGubun", "") return ret ################################################################# # 메서드 정의: 조회 관련 메서드 # # 시세조회, 관심종목 조회, 조건검색 등 이들의 합산 조회 횟수가 1초에 5회까지 허용 # ################################################################# def setInputValue(self, key, value): """ TR 전송에 필요한 값을 설정한다. :param key: string - TR에 명시된 input 이름 :param value: string - key에 해당하는 값 """ if not (isinstance(key, str) and isinstance(value, str)): raise ParameterTypeError() self.dynamicCall("SetInputValue(QString, QString)", key, value) # @SyncRequestDecorator.kiwoom_sync_request def commRqData(self, requestName, trCode, inquiry, screenNo): """ 키움서버에 TR 요청을 한다. 조회요청메서드이며 빈번하게 조회요청시, 시세과부하 에러값 -200이 리턴된다. :param requestName: string - TR 요청명(사용자 정의) :param trCode: string :param inquiry: int - 조회(0: 조회, 2: 남은 데이터 이어서 요청) :param screenNo: string - 화면번호(4자리) """ if not self.getConnectState(): raise KiwoomConnectError() if not (isinstance(requestName, str) and isinstance(trCode, str) and isinstance(inquiry, int) and isinstance(screenNo, str)): raise ParameterTypeError() returnCode = self.dynamicCall( "CommRqData(QString, QString, int, QString)", requestName, trCode, inquiry, screenNo) if returnCode != ReturnCode.OP_ERR_NONE: raise KiwoomProcessingError("commRqData(): " + ReturnCode.CAUSE[returnCode]) # 루프 생성: receiveTrData() 메서드에서 루프를 종료시킨다. self.requestLoop = QEventLoop() self.requestLoop.exec_() # @SyncRequestDecorator.kiwoom_sync_callback def commGetData(self, trCode, realType, requestName, index, key): """ 데이터 획득 메서드 receiveTrData() 이벤트 메서드가 호출될 때, 그 안에서 조회데이터를 얻어오는 메서드입니다. getCommData() 메서드로 위임. :param trCode: string :param realType: string - TR 요청시 ""(빈문자)로 처리 :param requestName: string - TR 요청명(commRqData() 메소드 호출시 사용된 requestName) :param index: int :param key: string :return: string """ return self.getCommData(trCode, requestName, index, key) def getCommData(self, trCode, requestName, index, key): """ 데이터 획득 메서드 receiveTrData() 이벤트 메서드가 호출될 때, 그 안에서 조회데이터를 얻어오는 메서드입니다. :param trCode: string :param requestName: string - TR 요청명(commRqData() 메소드 호출시 사용된 requestName) :param index: int :param key: string - 수신 데이터에서 얻고자 하는 값의 키(출력항목이름) :return: string """ if not (isinstance(trCode, str) and isinstance(requestName, str) and isinstance(index, int) and isinstance(key, str)): raise ParameterTypeError() data = self.dynamicCall("GetCommData(QString, QString, int, QString)", trCode, requestName, index, key) return data.strip() def getRepeatCnt(self, trCode, requestName): """ 서버로 부터 전달받은 데이터의 갯수를 리턴합니다.(멀티데이터의 갯수) receiveTrData() 이벤트 메서드가 호출될 때, 그 안에서 사용해야 합니다. 키움 OpenApi+에서는 데이터를 싱글데이터와 멀티데이터로 구분합니다. 싱글데이터란, 서버로 부터 전달받은 데이터 내에서, 중복되는 키(항목이름)가 하나도 없을 경우. 예를들면, 데이터가 '종목코드', '종목명', '상장일', '상장주식수' 처럼 키(항목이름)가 중복되지 않는 경우를 말합니다. 반면 멀티데이터란, 서버로 부터 전달받은 데이터 내에서, 일정 간격으로 키(항목이름)가 반복될 경우를 말합니다. 예를들면, 10일간의 일봉데이터를 요청할 경우 '종목코드', '일자', '시가', '고가', '저가' 이러한 항목이 10번 반복되는 경우입니다. 이러한 멀티데이터의 경우 반복 횟수(=데이터의 갯수)만큼, 루프를 돌면서 처리하기 위해 이 메서드를 이용하여 멀티데이터의 갯수를 얻을 수 있습니다. :param trCode: string :param requestName: string - TR 요청명(commRqData() 메소드 호출시 사용된 requestName) :return: int """ if not (isinstance(trCode, str) and isinstance(requestName, str)): raise ParameterTypeError() count = self.dynamicCall("GetRepeatCnt(QString, QString)", trCode, requestName) return count def getCommDataEx(self, trCode, multiDataName): """ 멀티데이터 획득 메서드 receiveTrData() 이벤트 메서드가 호출될 때, 그 안에서 사용해야 합니다. :param trCode: string :param multiDataName: string - KOA에 명시된 멀티데이터명 :return: list - 중첩리스트 """ if not (isinstance(trCode, str) and isinstance(multiDataName, str)): raise ParameterTypeError() data = self.dynamicCall("GetCommDataEx(QString, QString)", trCode, multiDataName) return data def commKwRqData(self, codes, inquiry, codeCount, requestName, screenNo, typeFlag=0): """ 복수종목조회 메서드(관심종목조회 메서드라고도 함). 이 메서드는 setInputValue() 메서드를 이용하여, 사전에 필요한 값을 지정하지 않는다. 단지, 메서드의 매개변수에서 직접 종목코드를 지정하여 호출하며, 데이터 수신은 receiveTrData() 이벤트에서 아래 명시한 항목들을 1회 수신하며, 이후 receiveRealData() 이벤트를 통해 실시간 데이터를 얻을 수 있다. 복수종목조회 TR 코드는 OPTKWFID 이며, 요청 성공시 아래 항목들의 정보를 얻을 수 있다. 종목코드, 종목명, 현재가, 기준가, 전일대비, 전일대비기호, 등락율, 거래량, 거래대금, 체결량, 체결강도, 전일거래량대비, 매도호가, 매수호가, 매도1~5차호가, 매수1~5차호가, 상한가, 하한가, 시가, 고가, 저가, 종가, 체결시간, 예상체결가, 예상체결량, 자본금, 액면가, 시가총액, 주식수, 호가시간, 일자, 우선매도잔량, 우선매수잔량,우선매도건수, 우선매수건수, 총매도잔량, 총매수잔량, 총매도건수, 총매수건수, 패리티, 기어링, 손익분기, 잔본지지, ELW행사가, 전환비율, ELW만기일, 미결제약정, 미결제전일대비, 이론가, 내재변동성, 델타, 감마, 쎄타, 베가, 로 :param codes: string - 한번에 100종목까지 조회가능하며 종목코드사이에 세미콜론(;)으로 구분. :param inquiry: int - api 문서는 bool 타입이지만, int로 처리(0: 조회, 1: 남은 데이터 이어서 조회) :param codeCount: int - codes에 지정한 종목의 갯수. :param requestName: string :param screenNo: string :param typeFlag: int - 주식과 선물옵션 구분(0: 주식, 3: 선물옵션), 주의: 매개변수의 위치를 맨 뒤로 이동함. :return: list - 중첩 리스트 [[종목코드, 종목명 ... 종목 정보], [종목코드, 종목명 ... 종목 정보]] """ if not self.getConnectState(): raise KiwoomConnectError() if not (isinstance(codes, str) and isinstance(inquiry, int) and isinstance(codeCount, int) and isinstance(requestName, str) and isinstance(screenNo, str) and isinstance(typeFlag, int)): raise ParameterTypeError() returnCode = self.dynamicCall( "CommKwRqData(QString, QBoolean, int, int, QString, QString)", codes, inquiry, codeCount, typeFlag, requestName, screenNo) if returnCode != ReturnCode.OP_ERR_NONE: raise KiwoomProcessingError("commKwRqData(): " + ReturnCode.CAUSE[returnCode]) # 루프 생성: receiveTrData() 메서드에서 루프를 종료시킨다. self.requestLoop = QEventLoop() self.requestLoop.exec_() ############################################################### # 메서드 정의: 실시간 데이터 처리 관련 메서드 # ############################################################### def disconnectRealData(self, screenNo): """ 해당 화면번호로 설정한 모든 실시간 데이터 요청을 제거합니다. 화면을 종료할 때 반드시 이 메서드를 호출해야 합니다. :param screenNo: string """ if not self.getConnectState(): raise KiwoomConnectError() if not isinstance(screenNo, str): raise ParameterTypeError() self.dynamicCall("DisconnectRealData(QString)", screenNo) def getCommRealData(self, code, fid): print("getCommRealData executed") """ 실시간 데이터 획득 메서드 이 메서드는 반드시 receiveRealData() 이벤트 메서드가 호출될 때, 그 안에서 사용해야 합니다. :param code: string - 종목코드 :param fid: - 실시간 타입에 포함된 fid :return: string - fid에 해당하는 데이터 """ if not (isinstance(code, str) and isinstance(fid, int)): raise ParameterTypeError() value = self.dynamicCall("GetCommRealData(QString, int)", code, fid) return value def setRealReg(self, screenNo, codes, fids, realRegType): """ 실시간 데이터 요청 메서드 종목코드와 fid 리스트를 이용해서 실시간 데이터를 요청하는 메서드입니다. 한번에 등록 가능한 종목과 fid 갯수는 100종목, 100개의 fid 입니다. 실시간등록타입을 0으로 설정하면, 첫 실시간 데이터 요청을 의미하며 실시간등록타입을 1로 설정하면, 추가등록을 의미합니다. 실시간 데이터는 실시간 타입 단위로 receiveRealData() 이벤트로 전달되기 때문에, 이 메서드에서 지정하지 않은 fid 일지라도, 실시간 타입에 포함되어 있다면, 데이터 수신이 가능하다. :param screenNo: string :param codes: string - 종목코드 리스트(종목코드;종목코드;...) :param fids: string - fid 리스트(fid;fid;...) :param realRegType: string - 실시간등록타입(0: 첫 등록, 1: 추가 등록) """ if not self.getConnectState(): raise KiwoomConnectError() if not (isinstance(screenNo, str) and isinstance(codes, str) and isinstance(fids, str) and isinstance(realRegType, str)): raise ParameterTypeError() self.dynamicCall("SetRealReg(QString, QString, QString, QString)", screenNo, codes, fids, realRegType) def setRealRemove(self, screenNo, code): """ 실시간 데이터 중지 메서드 setRealReg() 메서드로 등록한 종목만, 이 메서드를 통해 실시간 데이터 받기를 중지 시킬 수 있습니다. :param screenNo: string - 화면번호 또는 ALL 키워드 사용가능 :param code: string - 종목코드 또는 ALL 키워드 사용가능 """ if not self.getConnectState(): raise KiwoomConnectError() if not (isinstance(screenNo, str) and isinstance(code, str)): raise ParameterTypeError() self.dynamicCall("SetRealRemove(QString, QString)", screenNo, code) ############################################################### # 메서드 정의: 조건검색 관련 메서드와 이벤트 # ############################################################### @SyncRequestDecorator.kiwoom_sync_callback def receiveConditionVer(self, receive, msg): """ getConditionLoad() 메서드의 조건식 목록 요청에 대한 응답 이벤트 :param receive: int - 응답결과(1: 성공, 나머지 실패) :param msg: string - 메세지 """ print("receiveConditionVer excecuted") try: if not receive: return self.condition = self.getConditionNameList() print("조건식 개수: ", len(self.condition)) for key in self.condition.keys(): print("조건식: ", key, ": ", self.condition[key]) print("key type: ", type(key)) except Exception as e: print(e) finally: self.conditionLoop.exit() @SyncRequestDecorator.kiwoom_sync_callback def receiveTrCondition(self, screenNo, codes, conditionName, conditionIndex, inquiry): print("receiveTrCondition executed") """ (1회성, 실시간) 종목 조건검색 요청시 발생되는 이벤트 :param screenNo: string :param codes: string - 종목코드 목록(각 종목은 세미콜론으로 구분됨) :param conditionName: string - 조건식 이름 :param conditionIndex: int - 조건식 인덱스 :param inquiry: int - 조회구분(0: 남은데이터 없음, 2: 남은데이터 있음) """ print("[receiveTrCondition]") try: if codes == "": return codeList = codes.split(';') del codeList[-1] print(codeList) print("종목개수: ", len(codeList)) self.conditionCodeList = codeList finally: self.conditionLoop.exit() def receiveRealCondition(self, code, event, conditionName, conditionIndex): print("receiveRealCondition executed") """ 실시간 종목 조건검색 요청시 발생되는 이벤트 :param code: string - 종목코드 :param event: string - 이벤트종류("I": 종목편입, "D": 종목이탈) :param conditionName: string - 조건식 이름 :param conditionIndex: string - 조건식 인덱스(여기서만 인덱스가 string 타입으로 전달됨) """ print("[receiveRealCondition]") print("종목코드: ", code) print("이벤트: ", "종목편입" if event == "I" else "종목이탈") # if code not in self.conditionCodeList: # self.conditionCodeList.append(code) # # print(self.conditionCodeList) # # if self.conditionbuy == True and event == "I" and time.time() - self.order_time > 0.3 : # self.sendOrder("자동매수주문", "0102", account_no, 1, code, 1, 0, "03", "") # self.order_time = time.time() # print("condition send buy done") def getConditionLoad(self): print("getConditionLoad executed") """ 조건식 목록 요청 메서드 """ if not self.getConnectState(): raise KiwoomConnectError() isLoad = self.dynamicCall("GetConditionLoad()") # 요청 실패시 if not isLoad: raise KiwoomProcessingError("getConditionLoad(): 조건식 요청 실패") # receiveConditionVer() 이벤트 메서드에서 루프 종료 self.conditionLoop = QEventLoop() self.conditionLoop.exec_() def getConditionNameList(self): print("getConditionNameList executed") """ 조건식 획득 메서드 조건식을 딕셔너리 형태로 반환합니다. 이 메서드는 반드시 receiveConditionVer() 이벤트 메서드안에서 사용해야 합니다. :return: dict - {인덱스:조건명, 인덱스:조건명, ...} """ data = self.dynamicCall("GetConditionNameList()") if data == "": raise KiwoomProcessingError( "getConditionNameList(): 사용자 조건식이 없습니다.") conditionList = data.split(';') del conditionList[-1] conditionDictionary = {} for condition in conditionList: key, value = condition.split('^') conditionDictionary[int(key)] = value return conditionDictionary def sendCondition(self, screenNo, conditionName, conditionIndex, isRealTime): print("sendCondition executed") """ 종목 조건검색 요청 메서드 이 메서드로 얻고자 하는 것은 해당 조건에 맞는 종목코드이다. 해당 종목에 대한 상세정보는 setRealReg() 메서드로 요청할 수 있다. 요청이 실패하는 경우는, 해당 조건식이 없거나, 조건명과 인덱스가 맞지 않거나, 조회 횟수를 초과하는 경우 발생한다. 조건검색에 대한 결과는 1회성 조회의 경우, receiveTrCondition() 이벤트로 결과값이 전달되며 실시간 조회의 경우, receiveTrCondition()과 receiveRealCondition() 이벤트로 결과값이 전달된다. :param screenNo: string :param conditionName: string - 조건식 이름 :param conditionIndex: int - 조건식 인덱스 :param isRealTime: int - 조건검색 조회구분(0: 1회성 조회, 1: 실시간 조회) """ if not self.getConnectState(): raise KiwoomConnectError() if not (isinstance(screenNo, str) and isinstance(conditionName, str) and isinstance(conditionIndex, int) and isinstance(isRealTime, int)): raise ParameterTypeError() isRequest = self.dynamicCall( "SendCondition(QString, QString, int, int", screenNo, conditionName, conditionIndex, isRealTime) # if not isRequest: # raise KiwoomProcessingError("sendCondition(): 조건검색 요청 실패") # receiveTrCondition() 이벤트 메서드에서 루프 종료 self.conditionLoop = QEventLoop() self.conditionLoop.exec_() def sendConditionStop(self, screenNo, conditionName, conditionIndex): print("sendConditionStop executed") """ 종목 조건검색 중지 메서드 """ if not self.getConnectState(): raise KiwoomConnectError() if not (isinstance(screenNo, str) and isinstance(conditionName, str) and isinstance(conditionIndex, int)): raise ParameterTypeError() self.dynamicCall("SendConditionStop(QString, QString, int)", screenNo, conditionName, conditionIndex) ############################################################### # 메서드 정의: 주문과 잔고처리 관련 메서드 # # 1초에 5회까지 주문 허용 # ############################################################### @SyncRequestDecorator.kiwoom_sync_request def sendOrder(self, requestName, screenNo, accountNo, orderType, code, qty, price, hogaType, originOrderNo): """ 주식 주문 메서드 sendOrder() 메소드 실행시, OnReceiveMsg, OnReceiveTrData, OnReceiveChejanData 이벤트가 발생한다. 이 중, 주문에 대한 결과 데이터를 얻기 위해서는 OnReceiveChejanData 이벤트를 통해서 처리한다. OnReceiveTrData 이벤트를 통해서는 주문번호를 얻을 수 있는데, 주문후 이 이벤트에서 주문번호가 ''공백으로 전달되면, 주문접수 실패를 의미한다. :param requestName: string - 주문 요청명(사용자 정의) :param screenNo: string - 화면번호(4자리) :param accountNo: string - 계좌번호(10자리) :param orderType: int - 주문유형(1: 신규매수, 2: 신규매도, 3: 매수취소, 4: 매도취소, 5: 매수정정, 6: 매도정정) :param code: string - 종목코드 :param qty: int - 주문수량 :param price: int - 주문단가 :param hogaType: string - 거래구분(00: 지정가, 03: 시장가, 05: 조건부지정가, 06: 최유리지정가, 그외에는 api 문서참조) :param originOrderNo: string - 원주문번호(신규주문에는 공백, 정정및 취소주문시 원주문번호르 입력합니다.) """ print("kiwoom/sendorder executed") if not self.getConnectState(): raise KiwoomConnectError() if not (isinstance(requestName, str) and isinstance(screenNo, str) and isinstance(accountNo, str) and isinstance(orderType, int) and isinstance(code, str) and isinstance(qty, int) and isinstance(price, int) and isinstance(hogaType, str) and isinstance(originOrderNo, str)): raise ParameterTypeError() returnCode = self.dynamicCall( "SendOrder(QString, QString, QString, int, QString, int, int, QString, QString)", [ requestName, screenNo, accountNo, orderType, code, qty, price, hogaType, originOrderNo ]) if returnCode != ReturnCode.OP_ERR_NONE: raise KiwoomProcessingError("sendOrder(): " + ReturnCode.CAUSE[returnCode]) print("kiwoom/sendorder finished") # receiveTrData() 에서 루프종료 self.orderLoop = QEventLoop() self.orderLoop.exec_() def getChejanData(self, fid): """ 주문접수, 주문체결, 잔고정보를 얻어오는 메서드 이 메서드는 receiveChejanData() 이벤트 메서드가 호출될 때 그 안에서 사용해야 합니다. :param fid: int :return: string """ if not isinstance(fid, int): raise ParameterTypeError() cmd = 'GetChejanData("%s")' % fid data = self.dynamicCall(cmd) return data ############################################################### # 기타 메서드 정의 # ############################################################### def getCodeListByMarket(self, market): """ 시장 구분에 따른 종목코드의 목록을 List로 반환한다. market에 올 수 있는 값은 아래와 같다. '0': 장내, '3': ELW, '4': 뮤추얼펀드, '5': 신주인수권, '6': 리츠, '8': ETF, '9': 하이일드펀드, '10': 코스닥, '30': 제3시장 :param market: string :return: List """ if not self.getConnectState(): raise KiwoomConnectError() if not isinstance(market, str): raise ParameterTypeError() if market not in ['0', '3', '4', '5', '6', '8', '9', '10', '30']: raise ParameterValueError() cmd = 'GetCodeListByMarket("%s")' % market codeList = self.dynamicCall(cmd) return codeList.split(';') def getCodeList(self, *market): """ 여러 시장의 종목코드를 List 형태로 반환하는 헬퍼 메서드. :param market: Tuple - 여러 개의 문자열을 매개변수로 받아 Tuple로 처리한다. :return: List """ codeList = [] for m in market: tmpList = self.getCodeListByMarket(m) codeList += tmpList return codeList def getMasterCodeName(self, code): """ 종목코드의 한글명을 반환한다. :param code: string - 종목코드 :return: string - 종목코드의 한글명 """ if not self.getConnectState(): raise KiwoomConnectError() if not isinstance(code, str): raise ParameterTypeError() cmd = 'GetMasterCodeName("%s")' % code name = self.dynamicCall(cmd) return name def changeFormat(self, data, percent=0): if percent == 0: d = int(data) formatData = '{:-,d}'.format(d) elif percent == 1: try: f = int(data) / 100 except: f = float(data) / 100 formatData = '{:-,.2f}'.format(f) elif percent == 2: f = float(data) formatData = '{:-,.2f}'.format(f) return formatData def opwDataReset(self): """ 잔고 및 보유종목 데이터 초기화 """ self.opw00001Data = 0 self.opw00018Data = {'accountEvaluation': [], 'stocks': []} self.opt10001Data = {'code': [], 'high': [], 'low': []}
class MatrixDialog(WindowModalDialog): def __init__(self, parent): super(MatrixDialog, self).__init__(parent) self.setWindowTitle(_("Trezor Matrix Recovery")) self.num = 9 self.loop = QEventLoop() vbox = QVBoxLayout(self) vbox.addWidget(WWLabel(MATRIX_RECOVERY)) grid = QGridLayout() grid.setSpacing(0) self.char_buttons = [] for y in range(3): for x in range(3): button = QPushButton('?') button.clicked.connect( partial(self.process_key, ord('1') + y * 3 + x)) grid.addWidget(button, 3 - y, x) self.char_buttons.append(button) vbox.addLayout(grid) self.backspace_button = QPushButton("<=") self.backspace_button.clicked.connect( partial(self.process_key, Qt.Key_Backspace)) self.cancel_button = QPushButton(_("Cancel")) self.cancel_button.clicked.connect( partial(self.process_key, Qt.Key_Escape)) buttons = Buttons(self.backspace_button, self.cancel_button) vbox.addSpacing(40) vbox.addLayout(buttons) self.refresh() self.show() def refresh(self): for y in range(3): self.char_buttons[3 * y + 1].setEnabled(self.num == 9) def is_valid(self, key): return key >= ord('1') and key <= ord('9') def process_key(self, key): self.data = None if key == Qt.Key_Backspace: self.data = '\010' elif key == Qt.Key_Escape: self.data = 'x' elif self.is_valid(key): self.char_buttons[key - ord('1')].setFocus() self.data = '%c' % key if self.data: self.loop.exit(0) def keyPressEvent(self, event): self.process_key(event.key()) if not self.data: QDialog.keyPressEvent(self, event) def get_matrix(self, num): self.num = num self.refresh() self.loop.exec_()
def exec_(self): if hasattr(self, 'qApp'): mainWindow = MainWindow() mainWindow.show() self.mainEventLoop = QEventLoop() self.mainEventLoop.exec_()
class QtReactor(posixbase.PosixReactorBase): # implements(IReactorFDSet) def __init__(self): self._reads = {} self._writes = {} self._notifiers = {} self._timer = QTimer() self._timer.setSingleShot(True) self._timer.timeout.connect(self.iterate_qt) if QCoreApplication.instance() is None: # Application Object has not been started yet self.qApp = QCoreApplication([]) self._ownApp = True else: self.qApp = QCoreApplication.instance() self._ownApp = False self._blockApp = None posixbase.PosixReactorBase.__init__(self) def _add(self, xer, primary, type): """ Private method for adding a descriptor from the event loop. It takes care of adding it if new or modifying it if already added for another state (read -> read/write for example). """ if xer not in primary: primary[xer] = TwistedSocketNotifier(None, self, xer, type) def addReader(self, reader): """Add a FileDescriptor for notification of data available to read.""" self._add(reader, self._reads, QSocketNotifier.Read) def addWriter(self, writer): """Add a FileDescriptor for notification of data available to write.""" self._add(writer, self._writes, QSocketNotifier.Write) def _remove(self, xer, primary): """ Private method for removing a descriptor from the event loop. It does the inverse job of _add, and also add a check in case of the fd has gone away. """ if xer in primary: notifier = primary.pop(xer) notifier.shutdown() def removeReader(self, reader): """Remove a Selectable for notification of data available to read.""" self._remove(reader, self._reads) def removeWriter(self, writer): """Remove a Selectable for notification of data available to write.""" self._remove(writer, self._writes) def removeAll(self): """Remove all selectables, and return a list of them.""" return self._removeAll(self._reads, self._writes) def getReaders(self): return self._reads.keys() def getWriters(self): return self._writes.keys() def callLater(self, howlong, *args, **kargs): rval = super(QtReactor, self).callLater(howlong, *args, **kargs) self.reactorInvocation() return rval def reactorInvocation(self): self._timer.stop() self._timer.setInterval(0) self._timer.start() def _iterate(self, delay=None, fromqt=False): """See twisted.internet.interfaces.IReactorCore.iterate.""" self.runUntilCurrent() self.doIteration(delay, fromqt=fromqt) iterate = _iterate def iterate_qt(self, delay=None): self.iterate(delay=delay, fromqt=True) def doIteration(self, delay=None, fromqt=False): """This method is called by a Qt timer or by network activity on a file descriptor""" if not self.running and self._blockApp: self._blockApp.quit() self._timer.stop() if delay is None: delay = 0 delay = max(delay, 1) if not fromqt: self.qApp.processEvents(QEventLoop.AllEvents, delay * 1000) timeout = self.timeout() if timeout is not None: self._timer.setInterval(timeout * 1000) self._timer.start() def runReturn(self, installSignalHandlers=True): self.startRunning(installSignalHandlers=installSignalHandlers) self.reactorInvocation() def run(self, installSignalHandlers=True): if self._ownApp: self._blockApp = self.qApp else: self._blockApp = QEventLoop() self.runReturn() self._blockApp.exec_() if self.running: self.stop() self.runUntilCurrent()
def write(self, text): self.textWritten.emit(str(text)) loop = QEventLoop() QTimer.singleShot(1000, loop.quit) loop.exec_()
class InstallWizard(QtWidgets.QDialog, MessageBoxMixin, BaseWizard): accept_signal = pyqtSignal() def __init__(self, config, app, plugins): BaseWizard.__init__(self, config) QtWidgets.QDialog.__init__(self, None) self.setWindowTitle(f'{PROJECT_NAME} - ' + _('Install Wizard')) self.app = app self.config = config # Set for base base class self.plugins = plugins self.setMinimumSize(600, 400) self.accept_signal.connect(self.accept) self.title = QtWidgets.QLabel() self.main_widget = QtWidgets.QWidget() self.back_button = QtWidgets.QPushButton(_("Back"), self) self.back_button.setText(_('Back') if self.can_go_back() else _('Cancel')) self.next_button = QtWidgets.QPushButton(_("Next"), self) self.next_button.setDefault(True) self.logo = QtWidgets.QLabel() self.please_wait = QtWidgets.QLabel(_("Please wait...")) self.please_wait.setAlignment(Qt.AlignCenter) self.icon_filename = None self.loop = QEventLoop() self.rejected.connect(lambda: self.loop.exit(0)) self.back_button.clicked.connect(lambda: self.loop.exit(1)) self.next_button.clicked.connect(lambda: self.loop.exit(2)) outer_vbox = QtWidgets.QVBoxLayout(self) inner_vbox = QtWidgets.QVBoxLayout() inner_vbox.addWidget(self.title) inner_vbox.addWidget(self.main_widget) inner_vbox.addStretch(1) inner_vbox.addWidget(self.please_wait) inner_vbox.addStretch(1) scroll_widget = QtWidgets.QWidget() scroll_widget.setLayout(inner_vbox) scroll = QtWidgets.QScrollArea() scroll.setWidget(scroll_widget) scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) scroll.setWidgetResizable(True) icon_vbox = QtWidgets.QVBoxLayout() icon_vbox.addWidget(self.logo) icon_vbox.addStretch(1) hbox = QtWidgets.QHBoxLayout() hbox.addLayout(icon_vbox) hbox.addSpacing(5) hbox.addWidget(scroll) hbox.setStretchFactor(scroll, 1) outer_vbox.addLayout(hbox) outer_vbox.addLayout(Buttons(self.back_button, self.next_button)) self.set_icon(':icons/electrumABC.svg') self.show() self.raise_() # Track object lifecycle finalization_print_error(self) def select_storage( self, path, get_wallet_from_daemon ) -> Tuple[str, Optional[WalletStorage]]: vbox = QtWidgets.QVBoxLayout() vbox.addWidget( QtWidgets.QLabel( _('Create a new wallet or load an existing wallet from file.')) ) vbox.addSpacing(20) hbox = QtWidgets.QHBoxLayout() hbox.addWidget(QtWidgets.QLabel(_('Wallet name') + ':')) self.name_e = QtWidgets.QLineEdit() self.name_e.setToolTip( _("Enter a wallet name (new or existing), or the path to a wallet file.") ) hbox.addWidget(self.name_e) button = QtWidgets.QPushButton(_('Load...')) button.setToolTip(_("Open a file selection dialog to load a wallet file.")) hbox.addWidget(button) vbox.addLayout(hbox) vbox.addSpacing(20) self.msg_label = QtWidgets.QLabel('') vbox.addWidget(self.msg_label) hbox2 = QtWidgets.QHBoxLayout() self.pw_e = PasswordLineEdit('', self) self.pw_e.setFixedWidth(17 * char_width_in_lineedit()) self.pw_label = QtWidgets.QLabel(_('Password') + ':') hbox2.addWidget(self.pw_label) hbox2.addWidget(self.pw_e) hbox2.addStretch() vbox.addLayout(hbox2) self.set_layout(vbox, title=_(f'{PROJECT_NAME} wallet')) self.temp_storage = WalletStorage(path, manual_upgrades=True) wallet_folder = os.path.dirname(self.temp_storage.path) def on_choose(): path, __ = QtWidgets.QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder) if path: self.name_e.setText(path) def on_filename(filename): path = os.path.join(wallet_folder, filename) wallet_from_memory = get_wallet_from_daemon(path) try: if wallet_from_memory: self.temp_storage = wallet_from_memory.storage else: self.temp_storage = WalletStorage(path, manual_upgrades=True) self.next_button.setEnabled(True) except IOError: self.temp_storage = None self.next_button.setEnabled(False) if self.temp_storage: if not self.temp_storage.file_exists(): msg =_("This file does not exist.") + '\n' \ + _("Press 'Next' to create this wallet, or choose another file.") pw = False elif not wallet_from_memory: if self.temp_storage.is_encrypted_with_user_pw(): msg = _("This file is encrypted with a password.") + '\n' \ + _('Enter your password or choose another file.') pw = True elif self.temp_storage.is_encrypted_with_hw_device(): msg = _("This file is encrypted using a hardware device.") + '\n' \ + _("Press 'Next' to choose device to decrypt.") pw = False else: msg = _("Press 'Next' to open this wallet.") pw = False else: msg = _("This file is already open in memory.") + "\n" \ + _("Press 'Next' to create/focus window.") pw = False else: msg = _('Cannot read file') pw = False self.msg_label.setText(msg) if pw: self.pw_label.show() self.pw_e.show() self.pw_e.setFocus() else: self.pw_label.hide() self.pw_e.hide() button.clicked.connect(on_choose) self.name_e.textChanged.connect(on_filename) n = os.path.basename(self.temp_storage.path) self.name_e.setText(n) while True: if self.loop.exec_() != 2: # 2 = next raise UserCancelled if self.temp_storage.file_exists() and not self.temp_storage.is_encrypted(): break if not self.temp_storage.file_exists(): break wallet_from_memory = get_wallet_from_daemon(self.temp_storage.path) if wallet_from_memory: raise WalletAlreadyOpenInMemory(wallet_from_memory) if self.temp_storage.file_exists() and self.temp_storage.is_encrypted(): if self.temp_storage.is_encrypted_with_user_pw(): password = self.pw_e.text() try: self.temp_storage.decrypt(password) break except InvalidPassword as e: QtWidgets.QMessageBox.information(None, _('Error'), str(e)) continue except BaseException as e: traceback.print_exc(file=sys.stdout) QtWidgets.QMessageBox.information(None, _('Error'), str(e)) raise UserCancelled() elif self.temp_storage.is_encrypted_with_hw_device(): try: self.run( 'choose_hw_device', HWD_SETUP_DECRYPT_WALLET, storage=self.temp_storage ) except InvalidPassword as e: QtWidgets.QMessageBox.information( None, _('Error'), _('Failed to decrypt using this hardware device.') + '\n' + _('If you use a passphrase, make sure it is correct.')) self.reset_stack() return self.select_storage(path, get_wallet_from_daemon) except BaseException as e: traceback.print_exc(file=sys.stdout) QtWidgets.QMessageBox.information(None, _('Error'), str(e)) raise UserCancelled() if self.temp_storage.is_past_initial_decryption(): break else: raise UserCancelled() else: raise Exception('Unexpected encryption version') return self.temp_storage.path, (self.temp_storage if self.temp_storage.file_exists() else None) def run_upgrades(self, storage): path = storage.path if storage.requires_split(): self.hide() msg = _("The wallet '{}' contains multiple accounts, which are no longer supported since Electrum 2.7.\n\n" "Do you want to split your wallet into multiple files?").format(path) if not self.question(msg): return file_list = '\n'.join(storage.split_accounts()) msg = _('Your accounts have been moved to') + ':\n' + file_list + '\n\n'+ _('Do you want to delete the old file') + ':\n' + path if self.question(msg): os.remove(path) self.show_warning(_('The file was removed')) # raise now, to avoid having the old storage opened raise UserCancelled() action = storage.get_action() if action and storage.requires_upgrade(): raise WalletFileException('Incomplete wallet files cannot be upgraded.') if action: self.hide() msg = _("The file '{}' contains an incompletely created wallet.\n" "Do you want to complete its creation now?").format(path) if not self.question(msg): if self.question(_("Do you want to delete '{}'?").format(path)): os.remove(path) self.show_warning(_('The file was removed')) return self.show() self.data = storage.db.data self.run(action) for k, v in self.data.items(): storage.put(k, v) storage.write() return if storage.requires_upgrade(): self.upgrade_storage(storage) def on_error(self, exc_info): if not isinstance(exc_info[1], UserCancelled): traceback.print_exception(*exc_info) self.show_error(str(exc_info[1])) def set_icon(self, filename): prior_filename, self.icon_filename = self.icon_filename, filename self.logo.setPixmap(QIcon(filename).pixmap(60)) return prior_filename def set_layout(self, layout, title=None, next_enabled=True): self.title.setText("<b>%s</b>"%title if title else "") self.title.setVisible(bool(title)) # Get rid of any prior layout by assigning it to a temporary widget prior_layout = self.main_widget.layout() if prior_layout: QtWidgets.QWidget().setLayout(prior_layout) self.main_widget.setLayout(layout) self.back_button.setEnabled(True) self.next_button.setEnabled(next_enabled) if next_enabled: self.next_button.setFocus() self.main_widget.setVisible(True) self.please_wait.setVisible(False) def exec_layout(self, layout, title=None, raise_on_cancel=True, next_enabled=True): self.set_layout(layout, title, next_enabled) result = self.loop.exec_() if not result and raise_on_cancel: raise UserCancelled if result == 1: raise GoBack from None self.title.setVisible(False) self.back_button.setEnabled(False) self.next_button.setEnabled(False) self.main_widget.setVisible(False) self.please_wait.setVisible(True) self.refresh_gui() return result def refresh_gui(self): # For some reason, to refresh the GUI this needs to be called twice self.app.processEvents() self.app.processEvents() def remove_from_recently_open(self, filename): self.config.remove_from_recently_open(filename) def text_input(self, title, message, is_valid, allow_multi=False): slayout = KeysLayout(parent=self, title=message, is_valid=is_valid, allow_multi=allow_multi) self.exec_layout(slayout, title, next_enabled=False) return slayout.get_text() def seed_input(self, title, message, is_seed, options): slayout = SeedLayout(title=message, is_seed=is_seed, options=options, parent=self, editable=True) self.exec_layout(slayout, title, next_enabled=False) return slayout.get_seed(), slayout.is_bip39, slayout.is_ext def bip38_prompt_for_pw(self, bip38_keys): ''' Reimplemented from basewizard superclass. Expected to return the pw dict or None. ''' d = Bip38Importer(bip38_keys, parent=self.top_level_window()) res = d.exec_() d.setParent(None) # python GC quicker if this happens return d.decoded_keys # dict will be empty if user cancelled @wizard_dialog def add_xpub_dialog(self, title, message, is_valid, run_next, allow_multi=False): return self.text_input(title, message, is_valid, allow_multi) @wizard_dialog def add_cosigner_dialog(self, run_next, index, is_valid): title = _("Add Cosigner") + " %d"%index message = ' '.join([ _('Please enter the master public key (xpub) of your cosigner.'), _('Enter their master private key (xprv) if you want to be able to sign for them.') ]) return self.text_input(title, message, is_valid) @wizard_dialog def restore_seed_dialog(self, run_next, test): options = [] if self.opt_ext: options.append('ext') if self.opt_bip39: options.append('bip39') title = _('Enter Seed') message = _('Please enter your seed phrase in order to restore your wallet.') return self.seed_input(title, message, test, options) @wizard_dialog def confirm_seed_dialog(self, run_next, test): self.app.clipboard().clear() title = _('Confirm Seed') message = ' '.join([ _('Your seed is important!'), _('If you lose your seed, your money will be permanently lost.'), _('To make sure that you have properly saved your seed, please retype it here.') ]) seed, is_bip39, is_ext = self.seed_input(title, message, test, None) return seed @wizard_dialog def show_seed_dialog(self, run_next, seed_text, editable=True): title = _("Your wallet generation seed is:") slayout = SeedLayout(seed=seed_text, title=title, msg=True, options=['ext'], editable=False) self.exec_layout(slayout) return slayout.is_ext def pw_layout(self, msg, kind, force_disable_encrypt_cb): playout = PasswordLayout(msg=msg, kind=kind, OK_button=self.next_button, force_disable_encrypt_cb=force_disable_encrypt_cb) playout.encrypt_cb.setChecked(True) self.exec_layout(playout.layout()) return playout.new_password(), playout.encrypt_cb.isChecked() @wizard_dialog def request_password(self, run_next, force_disable_encrypt_cb=False): """Request the user enter a new password and confirm it. Return the password or None for no password. Note that this dialog screen cannot go back, and instead the user can only cancel.""" return self.pw_layout(MSG_ENTER_PASSWORD, PW_NEW, force_disable_encrypt_cb) @wizard_dialog def request_storage_encryption(self, run_next): playout = PasswordLayoutForHW(MSG_HW_STORAGE_ENCRYPTION) playout.encrypt_cb.setChecked(True) self.exec_layout(playout.layout()) return playout.encrypt_cb.isChecked() @staticmethod def _add_extra_button_to_layout(extra_button, layout): if (not isinstance(extra_button, (list, tuple)) or not len(extra_button) == 2): return but_title, but_action = extra_button hbox = QtWidgets.QHBoxLayout() hbox.setContentsMargins(12,24,12,12) but = QtWidgets.QPushButton(but_title) hbox.addStretch(1) hbox.addWidget(but) layout.addLayout(hbox) but.clicked.connect(but_action) @wizard_dialog def confirm_dialog(self, title, message, run_next, extra_button=None): self.confirm(message, title, extra_button=extra_button) def confirm(self, message, title, extra_button=None): label = WWLabel(message) textInteractionFlags = (Qt.LinksAccessibleByMouse | Qt.TextSelectableByMouse | Qt.TextSelectableByKeyboard | Qt.LinksAccessibleByKeyboard) label.setTextInteractionFlags(textInteractionFlags) label.setOpenExternalLinks(True) vbox = QtWidgets.QVBoxLayout() vbox.addWidget(label) if extra_button: self._add_extra_button_to_layout(extra_button, vbox) self.exec_layout(vbox, title) @wizard_dialog def action_dialog(self, action, run_next): self.run(action) def terminate(self, **kwargs): self.accept_signal.emit() def run_task_without_blocking_gui(self, task, *, msg=None): assert self.gui_thread == threading.current_thread(), 'must be called from GUI thread' if msg is None: msg = _("Please wait...") exc: Optional[Exception] = None res = None def task_wrapper(): nonlocal exc nonlocal res try: task() except Exception as e: exc = e self.waiting_dialog(task_wrapper, msg=msg) if exc is None: return res else: raise exc def waiting_dialog(self, task, msg, on_finished=None): label = WWLabel(msg) vbox = QtWidgets.QVBoxLayout() vbox.addSpacing(100) label.setMinimumWidth(300) label.setAlignment(Qt.AlignCenter) vbox.addWidget(label) self.set_layout(vbox, next_enabled=False) self.back_button.setEnabled(False) t = threading.Thread(target=task) t.start() while True: t.join(1.0/60) if t.is_alive(): self.refresh_gui() else: break if on_finished: on_finished() @wizard_dialog def choice_dialog(self, title, message, choices, run_next, extra_button=None): c_values = [x[0] for x in choices] c_titles = [x[1] for x in choices] clayout = ChoicesLayout(message, c_titles) vbox = QtWidgets.QVBoxLayout() vbox.addLayout(clayout.layout()) if extra_button: self._add_extra_button_to_layout(extra_button, vbox) self.exec_layout(vbox, title) action = c_values[clayout.selected_index()] return action def query_choice(self, msg, choices): """called by hardware wallets""" clayout = ChoicesLayout(msg, choices) vbox = QtWidgets.QVBoxLayout() vbox.addLayout(clayout.layout()) self.exec_layout(vbox, '') return clayout.selected_index() @wizard_dialog def line_dialog(self, run_next, title, message, default, test, warning=''): vbox = QtWidgets.QVBoxLayout() vbox.addWidget(WWLabel(message)) line = QtWidgets.QLineEdit() line.setText(default) def f(text): self.next_button.setEnabled(test(text)) line.textEdited.connect(f) vbox.addWidget(line) vbox.addWidget(WWLabel(warning)) self.exec_layout(vbox, title, next_enabled=test(default)) return ' '.join(line.text().split()) @wizard_dialog def derivation_path_dialog(self, run_next, title, message, default, test, warning='', seed='', scannable=False): def on_derivation_scan(derivation_line, seed): derivation_scan_dialog = DerivationDialog(self, seed, DerivationPathScanner.DERIVATION_PATHS) destroyed_print_error(derivation_scan_dialog) selected_path = derivation_scan_dialog.get_selected_path() if selected_path: derivation_line.setText(selected_path) derivation_scan_dialog.deleteLater() vbox = QtWidgets.QVBoxLayout() vbox.addWidget(WWLabel(message)) line = QtWidgets.QLineEdit() line.setText(default) def f(text): self.next_button.setEnabled(test(text)) line.textEdited.connect(f) vbox.addWidget(line) vbox.addWidget(WWLabel(warning)) if scannable: hbox = QtWidgets.QHBoxLayout() hbox.setContentsMargins(12,24,12,12) but = QtWidgets.QPushButton(_("Scan Derivation Paths...")) hbox.addStretch(1) hbox.addWidget(but) vbox.addLayout(hbox) but.clicked.connect(lambda: on_derivation_scan(line, seed)) self.exec_layout(vbox, title, next_enabled=test(default)) return ' '.join(line.text().split()) @wizard_dialog def show_xpub_dialog(self, xpub, run_next): msg = ' '.join([ _("Here is your master public key."), _("Please share it with your cosigners.") ]) vbox = QtWidgets.QVBoxLayout() layout = SeedLayout(xpub, title=msg, icon=False) vbox.addLayout(layout.layout()) self.exec_layout(vbox, _('Master Public Key')) return None def init_network(self, network): message = _( f"{PROJECT_NAME} communicates with remote servers to get " "information about your transactions and addresses. The " "servers all fulfil the same purpose only differing in " f"hardware. In most cases you simply want to let {PROJECT_NAME} " "pick one at random. However if you prefer feel free to " "select a server manually.") choices = [_("Auto connect"), _("Select server manually")] title = _("How do you want to connect to a server? ") clayout = ChoicesLayout(message, choices) self.back_button.setText(_('Cancel')) self.exec_layout(clayout.layout(), title) r = clayout.selected_index() network.auto_connect = (r == 0) self.config.set_key('auto_connect', network.auto_connect, True) if r == 1: nlayout = NetworkChoiceLayout(self, network, self.config, wizard=True) if self.exec_layout(nlayout.layout()): nlayout.accept() @wizard_dialog def multisig_dialog(self, run_next): cw = CosignWidget(2, 2) m_edit = QtWidgets.QSlider(Qt.Horizontal, self) n_edit = QtWidgets.QSlider(Qt.Horizontal, self) n_edit.setMinimum(1) n_edit.setMaximum(15) m_edit.setMinimum(1) m_edit.setMaximum(2) n_edit.setValue(2) m_edit.setValue(2) n_label = QtWidgets.QLabel() m_label = QtWidgets.QLabel() grid = QtWidgets.QGridLayout() grid.addWidget(n_label, 0, 0) grid.addWidget(n_edit, 0, 1) grid.addWidget(m_label, 1, 0) grid.addWidget(m_edit, 1, 1) def on_m(m): m_label.setText(_('Require %d signatures')%m) cw.set_m(m) def on_n(n): n_label.setText(_('From %d cosigners')%n) cw.set_n(n) m_edit.setMaximum(n) n_edit.valueChanged.connect(on_n) m_edit.valueChanged.connect(on_m) on_n(2) on_m(2) vbox = QtWidgets.QVBoxLayout() vbox.addWidget(cw) vbox.addWidget(WWLabel(_("Choose the number of signatures needed to unlock funds in your wallet:"))) vbox.addLayout(grid) self.exec_layout(vbox, _("Multi-Signature Wallet")) m = int(m_edit.value()) n = int(n_edit.value()) return (m, n) linux_hw_wallet_support_dialog = None def on_hw_wallet_support(self): ''' Overrides base wizard's noop impl. ''' if sys.platform.startswith("linux"): if self.linux_hw_wallet_support_dialog: self.linux_hw_wallet_support_dialog.raise_() return # NB: this should only be imported from Linux from . import udev_installer self.linux_hw_wallet_support_dialog = udev_installer.InstallHardwareWalletSupportDialog(self.top_level_window(), self.plugins) self.linux_hw_wallet_support_dialog.exec_() self.linux_hw_wallet_support_dialog.setParent(None) self.linux_hw_wallet_support_dialog = None else: self.show_error("Linux only facility. FIXME!")
class ConverterThread(QThread): """Thread converts markdown to HTML. """ # This signal is emitted by the converter thread when a file has been # converted to HTML. htmlReady = pyqtSignal( # Path to the file which should be converted to / displayed as HTML. str, # HTML rendering of the file; empty if the HTML is provided in a file # specified by the URL below. str, # Error text resulting from the conversion process. str, # A reference to a file containing HTML rendering. Empty if the second # parameter above contains the HTML instead. QUrl) # This signal clears the context of the log window. logWindowClear = pyqtSignal() # This signal emits messages for the log window. logWindowText = pyqtSignal( # A string to append to the log window. str) _Task = collections.namedtuple("Task", ["filePath", "language", "text"]) def __init__(self): QThread.__init__(self) self._queue = queue.Queue() self.start(QThread.LowPriority) self._ac = AsyncController('QThread', self) self._ac.defaultPriority = QThread.LowPriority self._SphinxInvocationCount = 1 def process(self, filePath, language, text): """Convert data and emit result. """ self._queue.put(self._Task(filePath, language, text)) def stop_async(self): self._queue.put(None) def _getHtml(self, language, text, filePath): """Get HTML for document """ if language == 'Markdown': return self._convertMarkdown(text), None, QUrl() # For ReST, use docutils only if Sphinx isn't available. elif language == 'Restructured Text' and not sphinxEnabledForFile(filePath): htmlUnicode, errString = self._convertReST(text) return htmlUnicode, errString, QUrl() elif filePath and sphinxEnabledForFile(filePath): # Use Sphinx to generate the HTML if possible. return self._convertSphinx(filePath) elif filePath and canUseCodeChat(filePath): # Otherwise, fall back to using CodeChat+docutils. return self._convertCodeChat(text, filePath) else: return 'No preview for this type of file', None, QUrl() def _convertMarkdown(self, text): """Convert Markdown to HTML """ try: import markdown except ImportError: return 'Markdown preview requires <i>python-markdown</i> package<br/>' \ 'Install it with your package manager or see ' \ '<a href="http://packages.python.org/Markdown/install.html">installation instructions</a>' extensions = ['fenced_code', 'nl2br', 'tables', 'enki.plugins.preview.mdx_math'] # version 2.0 supports only extension names, not instances if markdown.version_info[0] > 2 or \ (markdown.version_info[0] == 2 and markdown.version_info[1] > 0): class _StrikeThroughExtension(markdown.Extension): """http://achinghead.com/python-markdown-adding-insert-delete.html Class is placed here, because depends on imported markdown, and markdown import is lazy """ DEL_RE = r'(~~)(.*?)~~' def extendMarkdown(self, md, md_globals): # Create the del pattern delTag = markdown.inlinepatterns.SimpleTagPattern(self.DEL_RE, 'del') # Insert del pattern into markdown parser md.inlinePatterns.add('del', delTag, '>not_strong') extensions.append(_StrikeThroughExtension()) return markdown.markdown(text, extensions) def _convertReST(self, text): """Convert ReST """ try: import docutils.core import docutils.writers.html4css1 except ImportError: return 'Restructured Text preview requires the <i>python-docutils</i> package.<br/>' \ 'Install it with your package manager or see ' \ '<a href="http://pypi.python.org/pypi/docutils"/>this page.</a>', None errStream = io.StringIO() settingsDict = { # Make sure to use Unicode everywhere. 'output_encoding': 'unicode', 'input_encoding' : 'unicode', # Don't stop processing, no matter what. 'halt_level' : 5, # Capture errors to a string and return it. 'warning_stream' : errStream } # Frozen-specific settings. if isFrozen: settingsDict['template'] = ( # The default docutils stylesheet and template uses a relative path, # which doesn't work when frozen ???. Under Unix when not frozen, # it produces: # ``IOError: [Errno 2] No such file or directory: # '/usr/lib/python2.7/dist-packages/docutils/writers/html4css1/template.txt'``. os.path.join(os.path.dirname(docutils.writers.html4css1.__file__), docutils.writers.html4css1.Writer.default_template) ) settingsDict['stylesheet_dirs'] = ['.', os.path.dirname(docutils.writers.html4css1.__file__)] htmlString = docutils.core.publish_string(text, writer_name='html', settings_overrides=settingsDict) errString = errStream.getvalue() errStream.close() return htmlString, errString def _convertSphinx(self, filePath): # Run the builder. errString = self._runHtmlBuilder() # Look for the HTML output. # # Get an absolute path to the output path, which could be relative. outputPath = core.config()['Sphinx']['OutputPath'] projectPath = core.config()['Sphinx']['ProjectPath'] if not os.path.isabs(outputPath): outputPath = os.path.join(projectPath, outputPath) # Create an htmlPath as OutputPath + remainder of filePath. htmlPath = os.path.join(outputPath + filePath[len(projectPath):]) html_file_suffix = '.html' try: with codecs.open(os.path.join(projectPath, 'sphinx-enki-info.txt')) as f: hfs = f.read() # If the file is empty, then html_file_suffix wasn't defined # or is None. In this case, use the default extension. # Otherwise, use the extension read from the file. if hfs: html_file_suffix = hfs except: errString = "Warning: assuming .html extension. Use " + \ "the conf.py template to set the extension.\n" + errString pass # First place to look: file.html. For example, look for foo.py # in foo.py.html. htmlFile = htmlPath + html_file_suffix # Second place to look: file without extension.html. For # example, look for foo.html for foo.rst. htmlFileAlter = os.path.splitext(htmlPath)[0] + html_file_suffix # Check that the output file produced by Sphinx is newer than # the source file it was built from. if os.path.exists(htmlFile): return _checkModificationTime(filePath, htmlFile, errString) elif os.path.exists(htmlFileAlter): return _checkModificationTime(filePath, htmlFileAlter, errString) else: return ('No preview for this type of file.<br>Expected ' + htmlFile + " or " + htmlFileAlter, errString, QUrl()) def _convertCodeChat(self, text, filePath): # Use StringIO to pass CodeChat compilation information back to # the UI. errStream = io.StringIO() try: htmlString = CodeToRest.code_to_html_string(text, errStream, filename=filePath) except KeyError: # Although the file extension may be in the list of supported # extensions, CodeChat may not support the lexer chosen by Pygments. # For example, a ``.v`` file may be Verilog (supported by CodeChat) # or Coq (not supported). In this case, provide an error messsage errStream.write('Error: this file is not supported by CodeChat.') htmlString = '' errString = errStream.getvalue() errStream.close() return htmlString, errString, QUrl() def _runHtmlBuilder(self): # Build the commond line for Sphinx. if core.config()['Sphinx']['AdvancedMode']: htmlBuilderCommandLine = core.config()['Sphinx']['Cmdline'] if sys.platform.startswith('linux'): # If Linux is used, then subprocess cannot take the whole # commandline as the name of an executable file. Module shlex # has to be used to parse commandline. htmlBuilderCommandLine = shlex.split(htmlBuilderCommandLine) else: # For available builder options, refer to: http://sphinx-doc.org/builders.html htmlBuilderCommandLine = [core.config()['Sphinx']['Executable'], # Place doctrees in the ``_build`` directory; by default, Sphinx # places this in _build/html/.doctrees. '-d', os.path.join('_build', 'doctrees'), # Source directory -- the current directory, since we'll chdir to # the project directory before executing this. '.', # Build directory core.config()['Sphinx']['OutputPath']] # Invoke it. try: # Clear the log at the beginning of a Sphinx build. self.logWindowClear.emit() cwd = core.config()['Sphinx']['ProjectPath'] # If the command line is already a string (advanced mode), just print it. # Otherwise, it's a list that should be transformed to a string. if isinstance(htmlBuilderCommandLine, str): htmlBuilderCommandLineStr = htmlBuilderCommandLine else: htmlBuilderCommandLineStr = ' '.join(htmlBuilderCommandLine) self.logWindowText.emit('{} : {}\n\n'.format(cwd, htmlBuilderCommandLineStr)) # Run Sphinx, reading stdout in a separate thread. self._qe = QEventLoop() # Sphinx will output just a carriage return (0x0D) to simulate a # single line being updated by build status and the build # progresses. Without universal newline support here, we'll wait # until the build is complete (with a \n\r) to report any build # progress! So, enable universal newlines, so that each \r will be # treated as a separate line, providing immediate feedback on build # progress. popen = open_console_output(htmlBuilderCommandLine, cwd=cwd, universal_newlines=True) # Perform reads in an event loop. The loop is exit when all reads # have completed. We can't simply start the _stderr_read thread # here, because calls to self._qe_exit() will be ignored until # we're inside the event loop. QTimer.singleShot(0, lambda: self._popen_read(popen)) self._qe.exec_() except OSError as ex: return ( 'Failed to execute HTML builder:\n' '{}\n'.format(str(ex)) + 'Go to Settings -> Settings -> CodeChat to set HTML' ' builder configurations.') return self._stderr # Read from stdout (in this thread) and stderr (in another thread), # so that the user sees output as the build progresses, rather than only # producing output after the build is complete. def _popen_read(self, popen): # Read are blocking; we can't read from both stdout and stderr in the # same thread without possible buffer overflows. So, use this thread to # read from and immediately report progress from stdout. In another # thread, read all stderr and report that after the build finishes. self._ac.start(None, self._stderr_read, popen.stderr) # Read a line of stdout then report it to the user immediately. s = popen.stdout.readline() while s: self.logWindowText.emit(s.rstrip('\n')) s = popen.stdout.readline() self._SphinxInvocationCount += 1 # I would expect the following code to do the same thing. It doesn't: # instead, it waits until Sphinx completes before returning anything. # ??? # # .. code-block: python # :linenos: # # for s in popen.stdout: # self.logWindowText.emit(s) # Runs in a separate thread to read stdout. It then exits the QEventLoop as # a way to signal that stderr reads have completed. def _stderr_read(self, stderr): self._stderr = stderr.read() self._qe.exit() def run(self): """Thread function """ while True: # exits with break # wait task task = self._queue.get() # take the last task while self._queue.qsize(): task = self._queue.get() if task is None: # None is a quit command self._ac.terminate() break # TODO: This is ugly. Should pass this exception back to the main # thread and re-raise it there, or use a QFuture like approach which # does this automaticlaly. try: html, errString, url = self._getHtml(task.language, task.text, task.filePath) except Exception: traceback.print_exc() self.htmlReady.emit(task.filePath, html, errString, url) # Free resources. self._ac.terminate()
def handleUnsupportedContent(self, reply, preset_filename=None, page_url=None): """ This is called when url content is a downloadable file. e.g- pdf,mp3,mp4 """ if reply.rawHeaderList() == []: loop = QEventLoop() reply.metaDataChanged.connect(loop.quit) QTimer.singleShot(5000, loop.quit) loop.exec_() if reply.hasRawHeader(b'Location'): URL = QUrl.fromUserInput(str_(reply.rawHeader(b'Location'))) reply.abort() reply = networkmanager.get(QNetworkRequest(URL)) self.handleUnsupportedContent(reply, preset_filename) return for (title, header) in reply.rawHeaderPairs(): print( str_(title) + "-> " + str_(header) ) # copy url to clipboard QApplication.clipboard().setText(reply.url().toString()) # Get filename and mimetype mimetype = None if reply.hasRawHeader(b'Content-Type'): mimetype = str_(reply.rawHeader(b'Content-Type')).split(';')[0] # eg - audio/mpeg; name="" content_name = str_(reply.rawHeader(b'Content-Disposition')) if preset_filename: filename = preset_filename else: filename = filenameFromHeader(content_name) if filename == '': filename = filenameFromUrl(reply.url().toString()) filename = validateFileName(filename, mimetype) # Create downld Confirmation dialog dlDialog = DownloadDialog(self) dlDialog.filenameEdit.setText(filename) # Get filesize if reply.hasRawHeader(b'Content-Length'): filesize = reply.header(1) if filesize >= 1048576 : file_size = "{} M".format(round(float(filesize)/1048576, 2)) elif 1023 < filesize < 1048576 : file_size = "{} k".format(round(float(filesize)/1024, 1)) else: file_size = "{} B".format(filesize) dlDialog.labelFileSize.setText(file_size) # Get filetype and resume support info if mimetype: dlDialog.labelFileType.setText(mimetype) if reply.hasRawHeader(b'Accept-Ranges') or reply.hasRawHeader(b'Content-Range'): dlDialog.labelResume.setText("True") # Execute dialog and show confirmation if dlDialog.exec_()== QDialog.Accepted: filepath = dlDialog.folder + dlDialog.filenameEdit.text() url = reply.url().toString() if self.useexternaldownloader: download_externally(url, self.externaldownloader) reply.abort() reply.deleteLater() return global downloads_list_file newdownload = Download(networkmanager, page_url) newdownload.startDownload(reply, filepath) newdownload.datachanged.connect(self.dwnldsmodel.datachanged) self.downloads.insert(0, newdownload) imported_downloads = importDownloads(downloads_list_file) imported_downloads.insert(0, [filepath, url, str(newdownload.totalsize), newdownload.timestamp]) exportDownloads(downloads_list_file, imported_downloads) else: reply.abort() reply.deleteLater()
class InstallWizard(QDialog, MessageBoxMixin, BaseWizard): accept_signal = pyqtSignal() def __init__(self, config, app, plugins, storage): BaseWizard.__init__(self, config, plugins, storage) QDialog.__init__(self, None) self.setWindowTitle('Electrum - ' + _('Install Wizard')) self.app = app self.config = config # Set for base base class self.language_for_seed = config.get('language') self.setMinimumSize(600, 400) self.accept_signal.connect(self.accept) self.title = QLabel() self.main_widget = QWidget() self.back_button = QPushButton(_("Back"), self) self.back_button.setText( _('Back') if self.can_go_back() else _('Cancel')) self.next_button = QPushButton(_("Next"), self) self.next_button.setDefault(True) self.logo = QLabel() self.please_wait = QLabel(_("Please wait...")) self.please_wait.setAlignment(Qt.AlignCenter) self.icon_filename = None self.loop = QEventLoop() self.rejected.connect(lambda: self.loop.exit(0)) self.back_button.clicked.connect(lambda: self.loop.exit(1)) self.next_button.clicked.connect(lambda: self.loop.exit(2)) outer_vbox = QVBoxLayout(self) inner_vbox = QVBoxLayout() inner_vbox.addWidget(self.title) inner_vbox.addWidget(self.main_widget) inner_vbox.addStretch(1) inner_vbox.addWidget(self.please_wait) inner_vbox.addStretch(1) scroll_widget = QWidget() scroll_widget.setLayout(inner_vbox) scroll = QScrollArea() scroll.setWidget(scroll_widget) scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) scroll.setWidgetResizable(True) icon_vbox = QVBoxLayout() icon_vbox.addWidget(self.logo) icon_vbox.addStretch(1) hbox = QHBoxLayout() hbox.addLayout(icon_vbox) hbox.addSpacing(5) hbox.addWidget(scroll) hbox.setStretchFactor(scroll, 1) outer_vbox.addLayout(hbox) outer_vbox.addLayout(Buttons(self.back_button, self.next_button)) self.set_icon('electrum.png') self.show() self.raise_() self.refresh_gui() # Need for QT on MacOSX. Lame. def select_storage(self, path, get_wallet_from_daemon): vbox = QVBoxLayout() hbox = QHBoxLayout() hbox.addWidget(QLabel(_('Wallet') + ':')) self.name_e = QLineEdit() hbox.addWidget(self.name_e) button = QPushButton(_('Choose...')) hbox.addWidget(button) vbox.addLayout(hbox) self.msg_label = QLabel('') vbox.addWidget(self.msg_label) hbox2 = QHBoxLayout() self.pw_e = QLineEdit('', self) self.pw_e.setFixedWidth(150) self.pw_e.setEchoMode(2) self.pw_label = QLabel(_('Password') + ':') hbox2.addWidget(self.pw_label) hbox2.addWidget(self.pw_e) hbox2.addStretch() vbox.addLayout(hbox2) self.set_layout(vbox, title=_('Electrum wallet')) self.storage = WalletStorage(path, manual_upgrades=True) wallet_folder = os.path.dirname(self.storage.path) def on_choose(): path, __ = QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder) if path: self.name_e.setText(path) def on_filename(filename): path = os.path.join(wallet_folder, filename) wallet_from_memory = get_wallet_from_daemon(path) try: if wallet_from_memory: self.storage = wallet_from_memory.storage else: self.storage = WalletStorage(path, manual_upgrades=True) self.next_button.setEnabled(True) except BaseException: traceback.print_exc(file=sys.stderr) self.storage = None self.next_button.setEnabled(False) if self.storage: if not self.storage.file_exists(): msg =_("This file does not exist.") + '\n' \ + _("Press 'Next' to create this wallet, or choose another file.") pw = False elif not wallet_from_memory: if self.storage.is_encrypted_with_user_pw(): msg = _("This file is encrypted with a password.") + '\n' \ + _('Enter your password or choose another file.') pw = True elif self.storage.is_encrypted_with_hw_device(): msg = _("This file is encrypted using a hardware device.") + '\n' \ + _("Press 'Next' to choose device to decrypt.") pw = False else: msg = _("Press 'Next' to open this wallet.") pw = False else: msg = _("This file is already open in memory.") + "\n" \ + _("Press 'Next' to create/focus window.") pw = False else: msg = _('Cannot read file') pw = False self.msg_label.setText(msg) if pw: self.pw_label.show() self.pw_e.show() self.pw_e.setFocus() else: self.pw_label.hide() self.pw_e.hide() button.clicked.connect(on_choose) self.name_e.textChanged.connect(on_filename) n = os.path.basename(self.storage.path) self.name_e.setText(n) while True: if self.loop.exec_() != 2: # 2 = next return if self.storage.file_exists() and not self.storage.is_encrypted(): break if not self.storage.file_exists(): break wallet_from_memory = get_wallet_from_daemon(self.storage.path) if wallet_from_memory: return wallet_from_memory if self.storage.file_exists() and self.storage.is_encrypted(): if self.storage.is_encrypted_with_user_pw(): password = self.pw_e.text() try: self.storage.decrypt(password) break except InvalidPassword as e: QMessageBox.information(None, _('Error'), str(e)) continue except BaseException as e: traceback.print_exc(file=sys.stdout) QMessageBox.information(None, _('Error'), str(e)) return elif self.storage.is_encrypted_with_hw_device(): try: self.run('choose_hw_device', HWD_SETUP_DECRYPT_WALLET) except InvalidPassword as e: QMessageBox.information( None, _('Error'), _('Failed to decrypt using this hardware device.') + '\n' + _('If you use a passphrase, make sure it is correct.' )) self.reset_stack() return self.run_and_get_wallet(get_wallet_from_daemon) except BaseException as e: traceback.print_exc(file=sys.stdout) QMessageBox.information(None, _('Error'), str(e)) return if self.storage.is_past_initial_decryption(): break else: return else: raise Exception('Unexpected encryption version') return True def run_and_get_wallet(self): path = self.storage.path if self.storage.requires_split(): self.hide() msg = _( "The wallet '{}' contains multiple accounts, which are no longer supported since Electrum 2.7.\n\n" "Do you want to split your wallet into multiple files?" ).format(path) if not self.question(msg): return file_list = '\n'.join(self.storage.split_accounts()) msg = _('Your accounts have been moved to' ) + ':\n' + file_list + '\n\n' + _( 'Do you want to delete the old file') + ':\n' + path if self.question(msg): os.remove(path) self.show_warning(_('The file was removed')) return action = self.storage.get_action() if action and action not in ('new', 'upgrade_storage'): self.hide() msg = _("The file '{}' contains an incompletely created wallet.\n" "Do you want to complete its creation now?").format(path) if not self.question(msg): if self.question( _("Do you want to delete '{}'?").format(path)): os.remove(path) self.show_warning(_('The file was removed')) return self.show() if action: # self.wallet is set in run self.run(action) return self.wallet self.wallet = Wallet(self.storage) return self.wallet def finished(self): """Called in hardware client wrapper, in order to close popups.""" return def on_error(self, exc_info): if not isinstance(exc_info[1], UserCancelled): traceback.print_exception(*exc_info) self.show_error(str(exc_info[1])) def set_icon(self, filename): prior_filename, self.icon_filename = self.icon_filename, filename self.logo.setPixmap( QPixmap(icon_path(filename)).scaledToWidth( 60, mode=Qt.SmoothTransformation)) return prior_filename def set_layout(self, layout, title=None, next_enabled=True): self.title.setText("<b>%s</b>" % title if title else "") self.title.setVisible(bool(title)) # Get rid of any prior layout by assigning it to a temporary widget prior_layout = self.main_widget.layout() if prior_layout: QWidget().setLayout(prior_layout) self.main_widget.setLayout(layout) self.back_button.setEnabled(True) self.next_button.setEnabled(next_enabled) if next_enabled: self.next_button.setFocus() self.main_widget.setVisible(True) self.please_wait.setVisible(False) def exec_layout(self, layout, title=None, raise_on_cancel=True, next_enabled=True): self.set_layout(layout, title, next_enabled) result = self.loop.exec_() if not result and raise_on_cancel: raise UserCancelled if result == 1: raise GoBack from None self.title.setVisible(False) self.back_button.setEnabled(False) self.next_button.setEnabled(False) self.main_widget.setVisible(False) self.please_wait.setVisible(True) self.refresh_gui() return result def refresh_gui(self): # For some reason, to refresh the GUI this needs to be called twice self.app.processEvents() self.app.processEvents() def remove_from_recently_open(self, filename): self.config.remove_from_recently_open(filename) def text_input(self, title, message, is_valid, allow_multi=False): slayout = KeysLayout(parent=self, header_layout=message, is_valid=is_valid, allow_multi=allow_multi) self.exec_layout(slayout, title, next_enabled=False) return slayout.get_text() def seed_input(self, title, message, is_seed, options): slayout = SeedLayout(title=message, is_seed=is_seed, options=options, parent=self) self.exec_layout(slayout, title, next_enabled=False) return slayout.get_seed(), slayout.is_bip39, slayout.is_ext @wizard_dialog def add_xpub_dialog(self, title, message, is_valid, run_next, allow_multi=False, show_wif_help=False): header_layout = QHBoxLayout() label = WWLabel(message) label.setMinimumWidth(400) header_layout.addWidget(label) if show_wif_help: header_layout.addWidget(InfoButton(WIF_HELP_TEXT), alignment=Qt.AlignRight) return self.text_input(title, header_layout, is_valid, allow_multi) @wizard_dialog def add_cosigner_dialog(self, run_next, index, is_valid): title = _("Add Cosigner") + " %d" % index message = ' '.join([ _('Please enter the master public key (xpub) of your cosigner.'), _('Enter their master private key (xprv) if you want to be able to sign for them.' ) ]) return self.text_input(title, message, is_valid) @wizard_dialog def restore_seed_dialog(self, run_next, test): options = [] if self.opt_ext: options.append('ext') if self.opt_bip39: options.append('bip39') title = _('Enter Seed') message = _( 'Please enter your seed phrase in order to restore your wallet.') return self.seed_input(title, message, test, options) @wizard_dialog def confirm_seed_dialog(self, run_next, test): self.app.clipboard().clear() title = _('Confirm Seed') message = ' '.join([ _('Your seed is important!'), _('If you lose your seed, your money will be permanently lost.'), _('To make sure that you have properly saved your seed, please retype it here.' ) ]) seed, is_bip39, is_ext = self.seed_input(title, message, test, None) return seed @wizard_dialog def show_seed_dialog(self, run_next, seed_text): title = _("Your wallet generation seed is:") slayout = SeedLayout(seed=seed_text, title=title, msg=True, options=['ext']) self.exec_layout(slayout) return slayout.is_ext def pw_layout(self, msg, kind, force_disable_encrypt_cb): playout = PasswordLayout( msg=msg, kind=kind, OK_button=self.next_button, force_disable_encrypt_cb=force_disable_encrypt_cb) playout.encrypt_cb.setChecked(True) self.exec_layout(playout.layout()) return playout.new_password(), playout.encrypt_cb.isChecked() @wizard_dialog def request_password(self, run_next, force_disable_encrypt_cb=False): """Request the user enter a new password and confirm it. Return the password or None for no password.""" return self.pw_layout(MSG_ENTER_PASSWORD, PW_NEW, force_disable_encrypt_cb) @wizard_dialog def request_storage_encryption(self, run_next): playout = PasswordLayoutForHW(MSG_HW_STORAGE_ENCRYPTION) playout.encrypt_cb.setChecked(True) self.exec_layout(playout.layout()) return playout.encrypt_cb.isChecked() @wizard_dialog def confirm_dialog(self, title, message, run_next): self.confirm(message, title) def confirm(self, message, title): label = WWLabel(message) vbox = QVBoxLayout() vbox.addWidget(label) self.exec_layout(vbox, title) @wizard_dialog def action_dialog(self, action, run_next): self.run(action) def terminate(self): self.accept_signal.emit() def waiting_dialog(self, task, msg, on_finished=None): label = WWLabel(msg) vbox = QVBoxLayout() vbox.addSpacing(100) label.setMinimumWidth(300) label.setAlignment(Qt.AlignCenter) vbox.addWidget(label) self.set_layout(vbox, next_enabled=False) self.back_button.setEnabled(False) t = threading.Thread(target=task) t.start() while True: t.join(1.0 / 60) if t.is_alive(): self.refresh_gui() else: break if on_finished: on_finished() @wizard_dialog def choice_dialog(self, title, message, choices, run_next): c_values = [x[0] for x in choices] c_titles = [x[1] for x in choices] clayout = ChoicesLayout(message, c_titles) vbox = QVBoxLayout() vbox.addLayout(clayout.layout()) self.exec_layout(vbox, title) action = c_values[clayout.selected_index()] return action def query_choice(self, msg, choices): """called by hardware wallets""" clayout = ChoicesLayout(msg, choices) vbox = QVBoxLayout() vbox.addLayout(clayout.layout()) self.exec_layout(vbox, '') return clayout.selected_index() @wizard_dialog def choice_and_line_dialog(self, title: str, message1: str, choices: List[Tuple[str, str, str]], message2: str, test_text: Callable[[str], int], run_next, default_choice_idx: int = 0) -> Tuple[str, str]: vbox = QVBoxLayout() c_values = [x[0] for x in choices] c_titles = [x[1] for x in choices] c_default_text = [x[2] for x in choices] def on_choice_click(clayout): idx = clayout.selected_index() line.setText(c_default_text[idx]) clayout = ChoicesLayout(message1, c_titles, on_choice_click, checked_index=default_choice_idx) vbox.addLayout(clayout.layout()) vbox.addSpacing(50) vbox.addWidget(WWLabel(message2)) line = QLineEdit() def on_text_change(text): self.next_button.setEnabled(test_text(text)) line.textEdited.connect(on_text_change) on_choice_click(clayout) # set default text for "line" vbox.addWidget(line) self.exec_layout(vbox, title) choice = c_values[clayout.selected_index()] return str(line.text()), choice @wizard_dialog def line_dialog(self, run_next, title, message, default, test, warning='', presets=(), warn_issue4566=False): vbox = QVBoxLayout() vbox.addWidget(WWLabel(message)) line = QLineEdit() line.setText(default) def f(text): self.next_button.setEnabled(test(text)) if warn_issue4566: text_whitespace_normalised = ' '.join(text.split()) warn_issue4566_label.setVisible( text != text_whitespace_normalised) line.textEdited.connect(f) vbox.addWidget(line) vbox.addWidget(WWLabel(warning)) warn_issue4566_label = WWLabel(MSG_PASSPHRASE_WARN_ISSUE4566) warn_issue4566_label.setVisible(False) vbox.addWidget(warn_issue4566_label) for preset in presets: button = QPushButton(preset[0]) button.clicked.connect( lambda __, text=preset[1]: line.setText(text)) button.setMinimumWidth(150) hbox = QHBoxLayout() hbox.addWidget(button, alignment=Qt.AlignCenter) vbox.addLayout(hbox) self.exec_layout(vbox, title, next_enabled=test(default)) return line.text() @wizard_dialog def show_xpub_dialog(self, xpub, run_next): msg = ' '.join([ _("Here is your master public key."), _("Please share it with your cosigners.") ]) vbox = QVBoxLayout() layout = SeedLayout(xpub, title=msg, icon=False, for_seed_words=False) vbox.addLayout(layout.layout()) self.exec_layout(vbox, _('Master Public Key')) return None def init_network(self, network): message = _("Electrum communicates with remote servers to get " "information about your transactions and addresses. The " "servers all fulfill the same purpose only differing in " "hardware. In most cases you simply want to let Electrum " "pick one at random. However if you prefer feel free to " "select a server manually.") choices = [_("Auto connect"), _("Select server manually")] title = _("How do you want to connect to a server? ") clayout = ChoicesLayout(message, choices) self.back_button.setText(_('Cancel')) self.exec_layout(clayout.layout(), title) r = clayout.selected_index() if r == 1: nlayout = NetworkChoiceLayout(network, self.config, wizard=True) if self.exec_layout(nlayout.layout()): nlayout.accept() else: network.auto_connect = True self.config.set_key('auto_connect', True, True) @wizard_dialog def multisig_dialog(self, run_next): cw = CosignWidget(2, 2) m_edit = QSlider(Qt.Horizontal, self) n_edit = QSlider(Qt.Horizontal, self) n_edit.setMinimum(2) n_edit.setMaximum(15) m_edit.setMinimum(1) m_edit.setMaximum(2) n_edit.setValue(2) m_edit.setValue(2) n_label = QLabel() m_label = QLabel() grid = QGridLayout() grid.addWidget(n_label, 0, 0) grid.addWidget(n_edit, 0, 1) grid.addWidget(m_label, 1, 0) grid.addWidget(m_edit, 1, 1) def on_m(m): m_label.setText(_('Require {0} signatures').format(m)) cw.set_m(m) def on_n(n): n_label.setText(_('From {0} cosigners').format(n)) cw.set_n(n) m_edit.setMaximum(n) n_edit.valueChanged.connect(on_n) m_edit.valueChanged.connect(on_m) on_n(2) on_m(2) vbox = QVBoxLayout() vbox.addWidget(cw) vbox.addWidget( WWLabel( _("Choose the number of signatures needed to unlock funds in your wallet:" ))) vbox.addLayout(grid) self.exec_layout(vbox, _("Multi-Signature Wallet")) m = int(m_edit.value()) n = int(n_edit.value()) return (m, n)
def run(self): log.info("starting beat ..") self.beat_timer.start(self.interval) loop = QEventLoop() loop.exec_()
pixmap = self.movie.currentPixmap() self.setPixmap(pixmap) self.setMask(pixmap.mask()) # Put your initialization code here def longInitialization(arg): time.sleep(arg) return 0 if __name__ == "__main__": import sys, time app = QApplication(sys.argv) # Create and display the splash screen # splash_pix = QPixmap('a.gif') splash = MySplashScreen("images/giphy2.gif", Qt.Qt.WindowStaysOnTopHint) # splash.setMask(splash_pix.mask()) #splash.raise_() splash.show() app.processEvents() # this event loop is needed for dispatching of Qt events initLoop = QEventLoop() pool = Pool(processes=1) pool.apply_async(longInitialization, [2], callback=lambda exitCode: initLoop.exit(exitCode)) initLoop.exec_() form = Form() form.show() splash.finish(form) app.exec_()
class Debugger(QObject, ComponentMixin): name = 'Debugger' preferences = Parameter.create(name='Preferences', children=[{ 'name': 'Reload CQ', 'type': 'bool', 'value': True }, { 'name': 'Add script dir to path', 'type': 'bool', 'value': True }, { 'name': 'Change working dir to script dir', 'type': 'bool', 'value': True }]) sigRendered = pyqtSignal(dict) sigLocals = pyqtSignal(dict) sigTraceback = pyqtSignal(object, str) sigFrameChanged = pyqtSignal(object) sigLineChanged = pyqtSignal(int) sigLocalsChanged = pyqtSignal(dict) sigCQChanged = pyqtSignal(dict, bool) sigDebugging = pyqtSignal(bool) def __init__(self, parent): super(Debugger, self).__init__(parent) ComponentMixin.__init__(self) self.inner_event_loop = QEventLoop(self) self._actions = \ {'Run' : [QAction(icon('run'), 'Render', self, shortcut='F5', triggered=self.render), QAction(icon('debug'), 'Debug', self, checkable=True, shortcut='ctrl+F5', triggered=self.debug), QAction(icon('arrow-step-over'), 'Step', self, shortcut='ctrl+F10', triggered=lambda: self.debug_cmd(DbgState.STEP)), QAction(icon('arrow-step-in'), 'Step in', self, shortcut='ctrl+F11', triggered=lambda: None), QAction(icon('arrow-continue'), 'Continue', self, shortcut='ctrl+F12', triggered=lambda: self.debug_cmd(DbgState.CONT)) ]} def get_current_script(self): return self.parent().components['editor'].get_text_with_eol() def get_breakpoints(self): return self.parent().components['editor'].get_breakpoints() def compile_code(self, cq_script): try: module = imp.new_module('temp') cq_code = compile(cq_script, '<string>', 'exec') return cq_code, module except Exception: self.sigTraceback.emit(sys.exc_info(), cq_script) return None, None def _exec(self, code, locals_dict, globals_dict): with ExitStack() as stack: p = Path(self.parent().components['editor'].filename).abspath( ).dirname() if self.preferences['Add script dir to path'] and p: sys.path.insert(0, p) stack.callback(sys.path.remove, p) if self.preferences['Change working dir to script dir'] and p: stack.enter_context(p) exec(code, locals_dict, globals_dict) def _inject_locals(self, module): cq_objects = {} def _show_object(obj, name=None, options={}): if name: cq_objects.update( {name: SimpleNamespace(shape=obj, options=options)}) else: cq_objects.update({ str(id(obj)): SimpleNamespace(shape=obj, options=options) }) def _debug(obj, name=None): _show_object(obj, name, options=dict(color='red', alpha=0.2)) module.__dict__['show_object'] = _show_object module.__dict__['debug'] = _debug module.__dict__['log'] = lambda x: info(str(x)) module.__dict__['cq'] = cq return cq_objects, set(module.__dict__) - {'cq'} def _cleanup_locals(self, module, injected_names): for name in injected_names: module.__dict__.pop(name) @pyqtSlot(bool) def render(self): if self.preferences['Reload CQ']: reload_cq() cq_script = self.get_current_script() cq_code, module = self.compile_code(cq_script) if cq_code is None: return cq_objects, injected_names = self._inject_locals(module) try: self._exec(cq_code, module.__dict__, module.__dict__) #remove the special methods self._cleanup_locals(module, injected_names) #collect all CQ objects if no explicit show_object was called if len(cq_objects) == 0: cq_objects = find_cq_objects(module.__dict__) self.sigRendered.emit(cq_objects) self.sigTraceback.emit(None, cq_script) self.sigLocals.emit(module.__dict__) except Exception: self.sigTraceback.emit(sys.exc_info(), cq_script) @pyqtSlot(bool) def debug(self, value): if value: self.sigDebugging.emit(True) self.state = DbgState.STEP self.script = self.get_current_script() code, module = self.compile_code(self.script) if code is None: self.sigDebugging.emit(False) self._actions['Run'][1].setChecked(False) return cq_objects, injected_names = self._inject_locals(module) self.breakpoints = [el[0] for el in self.get_breakpoints()] #clear possible traceback self.sigTraceback.emit(None, self.script) try: sys.settrace(self.trace_callback) exec(code, module.__dict__, module.__dict__) except Exception: self.sigTraceback.emit(sys.exc_info(), self.script) finally: sys.settrace(None) self.sigDebugging.emit(False) self._actions['Run'][1].setChecked(False) if len(cq_objects) == 0: cq_objects = find_cq_objects(module.__dict__) self.sigRendered.emit(cq_objects) self._cleanup_locals(module, injected_names) self.sigLocals.emit(module.__dict__) else: sys.settrace(None) self.inner_event_loop.exit(0) def debug_cmd(self, state=DbgState.STEP): self.state = state self.inner_event_loop.exit(0) def trace_callback(self, frame, event, arg): filename = frame.f_code.co_filename if filename == DUMMY_FILE: self.trace_local(frame, event, arg) return self.trace_callback else: return None def trace_local(self, frame, event, arg): lineno = frame.f_lineno line = self.script.splitlines()[lineno - 1] f_id = id(frame) if event in (DbgEevent.LINE, DbgEevent.RETURN): if (self.state in (DbgState.STEP, DbgState.STEP_IN)) \ or (lineno in self.breakpoints): self.sigLineChanged.emit(lineno) self.sigFrameChanged.emit(frame) self.sigLocalsChanged.emit(frame.f_locals) self.sigCQChanged.emit(find_cq_objects(frame.f_locals), True) self.inner_event_loop.exec_() elif event in (DbgEevent.RETURN): self.sigLocalsChanged.emit(frame.f_locals) elif event == DbgEevent.CALL: func_filename = frame.f_code.co_filename if self.state == DbgState.STEP_IN and func_filename == DUMMY_FILE: self.sigLineChanged.emit(lineno) self.sigFrameChanged.emit(frame) self.state = DbgState.STEP
def request(self, url, method="GET", body=None, headers=None, redirections=DEFAULT_MAX_REDIRECTS, connection_type=None, blocking=True): """ Make a network request by calling QgsNetworkAccessManager. redirections argument is ignored and is here only for httplib2 compatibility. """ self.http_call_result.url = url self.msg_log(u'http_call request: {0}'.format(url)) self.blocking_mode = blocking req = QNetworkRequest() # Avoid double quoting form QUrl url = urllib.parse.unquote(url) req.setUrl(QUrl(url)) if headers is not None: # This fixes a weird error with compressed content not being correctly # inflated. # If you set the header on the QNetworkRequest you are basically telling # QNetworkAccessManager "I know what I'm doing, please don't do any content # encoding processing". # See: https://bugs.webkit.org/show_bug.cgi?id=63696#c1 try: del headers['Accept-Encoding'] except KeyError: pass for k, v in list(headers.items()): self.msg_log("Setting header %s to %s" % (k, v)) req.setRawHeader(k, v) if self.authid: self.msg_log("Update request w/ authid: {0}".format(self.authid)) QgsAuthManager.instance().updateNetworkRequest(req, self.authid) if self.reply is not None and self.reply.isRunning(): self.reply.close() if method.lower() == 'delete': func = getattr(QgsNetworkAccessManager.instance(), 'deleteResource') else: func = getattr(QgsNetworkAccessManager.instance(), method.lower()) # Calling the server ... # Let's log the whole call for debugging purposes: self.msg_log("Sending %s request to %s" % (method.upper(), req.url().toString())) self.on_abort = False headers = {str(h): str(req.rawHeader(h)) for h in req.rawHeaderList()} for k, v in list(headers.items()): self.msg_log("%s: %s" % (k, v)) if method.lower() in ['post', 'put']: if isinstance(body, file): body = body.read() self.reply = func(req, body) else: self.reply = func(req) if self.authid: self.msg_log("Update reply w/ authid: {0}".format(self.authid)) QgsAuthManager.instance().updateNetworkReply( self.reply, self.authid) # necessary to trap local timeout manage by QgsNetworkAccessManager # calling QgsNetworkAccessManager::abortRequest QgsNetworkAccessManager.instance().requestTimedOut.connect( self.requestTimedOut) self.reply.sslErrors.connect(self.sslErrors) self.reply.finished.connect(self.replyFinished) self.reply.downloadProgress.connect(self.downloadProgress) # block if blocking mode otherwise return immediately # it's up to the caller to manage listeners in case of no blocking mode if not self.blocking_mode: return None, None # Call and block self.el = QEventLoop() self.reply.finished.connect(self.el.quit) # Catch all exceptions (and clean up requests) try: self.el.exec_(QEventLoop.ExcludeUserInputEvents) except Exception as e: raise e if self.reply: self.reply.finished.disconnect(self.el.quit) # emit exception in case of error if not self.http_call_result.ok: if self.http_call_result.exception and not self.exception_class: raise self.http_call_result.exception elif self.exception_class: raise self.exception_class(self.http_call_result.reason) else: raise RequestsException('Unknown reason') return self.http_call_result, self.http_call_result.content
class CharacterDialog(WindowModalDialog): def __init__(self, parent): super(CharacterDialog, self).__init__(parent) self.setWindowTitle(_("KeepKey Seed Recovery")) self.character_pos = 0 self.word_pos = 0 self.loop = QEventLoop() self.word_help = QLabel() self.char_buttons = [] vbox = QVBoxLayout(self) vbox.addWidget(WWLabel(CHARACTER_RECOVERY)) hbox = QHBoxLayout() hbox.addWidget(self.word_help) for i in range(4): char_button = CharacterButton('*') char_button.setMaximumWidth(36) self.char_buttons.append(char_button) hbox.addWidget(char_button) self.accept_button = CharacterButton(_("Accept Word")) self.accept_button.clicked.connect(partial(self.process_key, 32)) self.rejected.connect(partial(self.loop.exit, 1)) hbox.addWidget(self.accept_button) hbox.addStretch(1) vbox.addLayout(hbox) self.finished_button = QPushButton(_("Seed Entered")) self.cancel_button = QPushButton(_("Cancel")) self.finished_button.clicked.connect( partial(self.process_key, Qt.Key_Return)) self.cancel_button.clicked.connect(self.rejected) buttons = Buttons(self.finished_button, self.cancel_button) vbox.addSpacing(40) vbox.addLayout(buttons) self.refresh() self.show() def refresh(self): self.word_help.setText("Enter seed word %2d:" % (self.word_pos + 1)) self.accept_button.setEnabled(self.character_pos >= 3) self.finished_button.setEnabled((self.word_pos in (11, 17, 23) and self.character_pos >= 3)) for n, button in enumerate(self.char_buttons): button.setEnabled(n == self.character_pos) if n == self.character_pos: button.setFocus() def is_valid_alpha_space(self, key): # Auto-completion requires at least 3 characters if key == ord(' ') and self.character_pos >= 3: return True # Firmware aborts protocol if the 5th character is non-space if self.character_pos >= 4: return False return (key >= ord('a') and key <= ord('z') or (key >= ord('A') and key <= ord('Z'))) def process_key(self, key): self.data = None if key == Qt.Key_Return and self.finished_button.isEnabled(): self.data = {'done': True} elif key == Qt.Key_Backspace and (self.word_pos or self.character_pos): self.data = {'delete': True} elif self.is_valid_alpha_space(key): self.data = {'character': chr(key).lower()} if self.data: self.loop.exit(0) def keyPressEvent(self, event): self.process_key(event.key()) if not self.data: QDialog.keyPressEvent(self, event) def get_char(self, word_pos, character_pos): self.word_pos = word_pos self.character_pos = character_pos self.refresh() if self.loop.exec_(): self.data = None # User cancelled
class NetworkAccessManager(QObject): """ This class mimicks httplib2 by using QgsNetworkAccessManager for all network calls. The return value is a tuple of (response, content), the first being and instance of the Response class, the second being a string that contains the response entity body. Parameters ---------- debug : bool verbose logging if True exception_class : Exception Custom exception class Usage 1 (blocking mode) ----- :: nam = NetworkAccessManager(authcgf) try: (response, content) = nam.request('http://www.example.com') except RequestsException as e: # Handle exception pass Usage 2 (Non blocking mode) ------------------------- :: NOTE! if blocking mode returns immediatly it's up to the caller to manage listeners in case of non blocking mode nam = NetworkAccessManager(authcgf) try: nam.request('http://www.example.com', blocking=False) nam.reply.finished.connect(a_signal_listener) except RequestsException as e: # Handle exception pass Get response using method: nam.httpResult() that return a dictionary with keys: 'status' - http code result come from reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) 'status_code' - http code result come from reply.attribute(QNetworkRequest.HttpStatusCodeAttribute) 'status_message' - reply message string from reply.attribute(QNetworkRequest.HttpReasonPhraseAttribute) 'content' - bytearray returned from reply 'ok' - request success [True, False] 'headers' - Dicionary containing the reply header 'reason' - fomatted message string with reply.errorString() 'exception' - the exception returne dduring execution """ finished = pyqtSignal(Response) def __init__(self, authid=None, disable_ssl_certificate_validation=False, exception_class=None, debug=False): QObject.__init__(self) self.disable_ssl_certificate_validation = disable_ssl_certificate_validation self.authid = authid self.reply = None self.debug = debug self.exception_class = exception_class self.on_abort = False self.blocking_mode = False self.http_call_result = Response({ 'status': 0, 'status_code': 0, 'status_message': '', 'content': '', 'ok': False, 'headers': {}, 'reason': '', 'exception': None, 'url': '' }) def msg_log(self, msg): if self.debug: QgsMessageLog.logMessage(msg, "NetworkAccessManager") def httpResult(self): return self.http_call_result def request(self, url, method="GET", body=None, headers=None, redirections=DEFAULT_MAX_REDIRECTS, connection_type=None, blocking=True): """ Make a network request by calling QgsNetworkAccessManager. redirections argument is ignored and is here only for httplib2 compatibility. """ self.http_call_result.url = url self.msg_log(u'http_call request: {0}'.format(url)) self.blocking_mode = blocking req = QNetworkRequest() # Avoid double quoting form QUrl url = urllib.parse.unquote(url) req.setUrl(QUrl(url)) if headers is not None: # This fixes a weird error with compressed content not being correctly # inflated. # If you set the header on the QNetworkRequest you are basically telling # QNetworkAccessManager "I know what I'm doing, please don't do any content # encoding processing". # See: https://bugs.webkit.org/show_bug.cgi?id=63696#c1 try: del headers['Accept-Encoding'] except KeyError: pass for k, v in list(headers.items()): self.msg_log("Setting header %s to %s" % (k, v)) req.setRawHeader(k, v) if self.authid: self.msg_log("Update request w/ authid: {0}".format(self.authid)) QgsAuthManager.instance().updateNetworkRequest(req, self.authid) if self.reply is not None and self.reply.isRunning(): self.reply.close() if method.lower() == 'delete': func = getattr(QgsNetworkAccessManager.instance(), 'deleteResource') else: func = getattr(QgsNetworkAccessManager.instance(), method.lower()) # Calling the server ... # Let's log the whole call for debugging purposes: self.msg_log("Sending %s request to %s" % (method.upper(), req.url().toString())) self.on_abort = False headers = {str(h): str(req.rawHeader(h)) for h in req.rawHeaderList()} for k, v in list(headers.items()): self.msg_log("%s: %s" % (k, v)) if method.lower() in ['post', 'put']: if isinstance(body, file): body = body.read() self.reply = func(req, body) else: self.reply = func(req) if self.authid: self.msg_log("Update reply w/ authid: {0}".format(self.authid)) QgsAuthManager.instance().updateNetworkReply( self.reply, self.authid) # necessary to trap local timeout manage by QgsNetworkAccessManager # calling QgsNetworkAccessManager::abortRequest QgsNetworkAccessManager.instance().requestTimedOut.connect( self.requestTimedOut) self.reply.sslErrors.connect(self.sslErrors) self.reply.finished.connect(self.replyFinished) self.reply.downloadProgress.connect(self.downloadProgress) # block if blocking mode otherwise return immediately # it's up to the caller to manage listeners in case of no blocking mode if not self.blocking_mode: return None, None # Call and block self.el = QEventLoop() self.reply.finished.connect(self.el.quit) # Catch all exceptions (and clean up requests) try: self.el.exec_(QEventLoop.ExcludeUserInputEvents) except Exception as e: raise e if self.reply: self.reply.finished.disconnect(self.el.quit) # emit exception in case of error if not self.http_call_result.ok: if self.http_call_result.exception and not self.exception_class: raise self.http_call_result.exception elif self.exception_class: raise self.exception_class(self.http_call_result.reason) else: raise RequestsException('Unknown reason') return self.http_call_result, self.http_call_result.content # @pyqtSlot() def downloadProgress(self, bytesReceived, bytesTotal): """Keep track of the download progress""" #self.msg_log("downloadProgress %s of %s ..." % (bytesReceived, bytesTotal)) pass # @pyqtSlot(QNetworkReply) def requestTimedOut(self, QNetworkReply): """Trap the timeout. In Async mode requestTimedOut is called after replyFinished""" # adapt http_call_result basing on receiving qgs timer timout signal self.exception_class = RequestsExceptionTimeout self.http_call_result.exception = RequestsExceptionTimeout( "Timeout error") # @pyqtSlot(QObject) def replyFinished(self): err = self.reply.error() httpStatus = self.reply.attribute( QNetworkRequest.HttpStatusCodeAttribute) httpStatusMessage = self.reply.attribute( QNetworkRequest.HttpReasonPhraseAttribute) self.http_call_result.status_code = httpStatus self.http_call_result.status = httpStatus self.http_call_result.status_message = httpStatusMessage for k, v in self.reply.rawHeaderPairs(): self.http_call_result.headers[str(k)] = str(v) self.http_call_result.headers[str(k).lower()] = str(v) if err != QNetworkReply.NoError: # handle error # check if errorString is empty, if so, then set err string as # reply dump if re.match('(.)*server replied: $', self.reply.errorString()): errString = self.reply.errorString( ) + self.http_call_result.content else: errString = self.reply.errorString() # check if self.http_call_result.status_code is available (client abort # does not produce http.status_code) if self.http_call_result.status_code: msg = "Network error #{0}: {1}".format( self.http_call_result.status_code, errString) else: msg = "Network error: {0}".format(errString) self.http_call_result.reason = msg self.http_call_result.ok = False self.msg_log(msg) # set return exception if err == QNetworkReply.TimeoutError: self.http_call_result.exception = RequestsExceptionTimeout(msg) elif err == QNetworkReply.ConnectionRefusedError: self.http_call_result.exception = RequestsExceptionConnectionError( msg) elif err == QNetworkReply.OperationCanceledError: # request abort by calling NAM.abort() => cancelled by the user if self.on_abort: self.http_call_result.exception = RequestsExceptionUserAbort( msg) else: self.http_call_result.exception = RequestsException(msg) else: self.http_call_result.exception = RequestsException(msg) # overload exception to the custom exception if available if self.exception_class: self.http_call_result.exception = self.exception_class(msg) else: # Handle redirections redirection_url = self.reply.attribute( QNetworkRequest.RedirectionTargetAttribute) if redirection_url is not None and redirection_url != self.reply.url( ): if redirection_url.isRelative(): redirection_url = self.reply.url().resolved( redirection_url) msg = "Redirected from '{}' to '{}'".format( self.reply.url().toString(), redirection_url.toString()) self.msg_log(msg) self.reply.deleteLater() self.reply = None self.request(redirection_url.toString()) # really end request else: msg = "Network success #{0}".format(self.reply.error()) self.http_call_result.reason = msg self.msg_log(msg) ba = self.reply.readAll() self.http_call_result.content = bytes(ba) self.http_call_result.ok = True # Let's log the whole response for debugging purposes: self.msg_log( "Got response %s %s from %s" % (self.http_call_result.status_code, self.http_call_result.status_message, self.reply.url().toString() if self.reply else 'reply has been deleted')) for k, v in list(self.http_call_result.headers.items()): self.msg_log("%s: %s" % (k, v)) if len(self.http_call_result.content) < 1024: self.msg_log("Payload :\n%s" % self.http_call_result.content) else: self.msg_log("Payload is > 1 KB ...") # clean reply if self.reply is not None: if self.reply.isRunning(): self.reply.close() self.msg_log("Deleting reply ...") # Disconnect all slots self.reply.sslErrors.disconnect(self.sslErrors) self.reply.finished.disconnect(self.replyFinished) self.reply.downloadProgress.disconnect(self.downloadProgress) self.reply.deleteLater() self.reply = None else: self.msg_log("Reply was already deleted ...") self.finished.emit(self.http_call_result) # @pyqtSlot() def sslErrors(self, ssl_errors): """ Handle SSL errors, logging them if debug is on and ignoring them if disable_ssl_certificate_validation is set. """ if ssl_errors: for v in ssl_errors: self.msg_log("SSL Error: %s" % v.errorString()) if self.disable_ssl_certificate_validation: self.reply.ignoreSslErrors() # @pyqtSlot() def abort(self): """ Handle request to cancel HTTP call """ if self.reply and self.reply.isRunning(): self.on_abort = True self.reply.abort()
class CategoryPopupMenu(FramelessWindow): triggered = Signal(QAction) hovered = Signal(QAction) def __init__(self, parent=None, **kwargs): FramelessWindow.__init__(self, parent, **kwargs) self.setWindowFlags(self.windowFlags() | Qt.Popup) layout = QVBoxLayout() layout.setContentsMargins(6, 6, 6, 6) self.__menu = MenuPage() self.__menu.setActionRole(QtWidgetRegistry.WIDGET_ACTION_ROLE) if sys.platform == "darwin": self.__menu.view().setAttribute(Qt.WA_MacShowFocusRect, False) self.__menu.triggered.connect(self.__onTriggered) self.__menu.hovered.connect(self.hovered) self.__dragListener = ItemViewDragStartEventListener(self) self.__dragListener.dragStarted.connect(self.__onDragStarted) self.__menu.view().viewport().installEventFilter(self.__dragListener) layout.addWidget(self.__menu) self.setLayout(layout) self.__action = None self.__loop = None self.__item = None def setCategoryItem(self, item): """ Set the category root item (:class:`QStandardItem`). """ self.__item = item model = item.model() self.__menu.setModel(model) self.__menu.setRootIndex(item.index()) def popup(self, pos=None): if pos is None: pos = self.pos() geom = widget_popup_geometry(pos, self) self.setGeometry(geom) self.show() def exec_(self, pos=None): self.popup(pos) self.__loop = QEventLoop() self.__action = None self.__loop.exec_() self.__loop = None if self.__action is not None: action = self.__action else: action = None return action def hideEvent(self, event): if self.__loop is not None: self.__loop.exit(0) return FramelessWindow.hideEvent(self, event) def __onTriggered(self, action): self.__action = action self.triggered.emit(action) self.hide() if self.__loop: self.__loop.exit(0) def __onDragStarted(self, index): desc = qunwrap(index.data(QtWidgetRegistry.WIDGET_DESC_ROLE)) icon = qunwrap(index.data(Qt.DecorationRole)) drag_data = QMimeData() drag_data.setData( "application/vnv.orange-canvas.registry.qualified-name", desc.qualified_name) drag = QDrag(self) drag.setPixmap(icon.pixmap(38)) drag.setMimeData(drag_data) # TODO: Should animate (accept) hide. self.hide() # When a drag is started and the menu hidden the item's tool tip # can still show for a short time UNDER the cursor preventing a # drop. viewport = self.__menu.view().viewport() filter = ToolTipEventFilter() viewport.installEventFilter(filter) drag.exec_(Qt.CopyAction) viewport.removeEventFilter(filter)
board = chess.Board() board.reset_board() app = QApplication(sys.argv) svgWidget = QSvgWidget() svgWidget.setGeometry(400, 300, 400, 400) svgWidget.show() board_picture = chess.svg.board(board) svg_bytes = bytearray(board_picture, encoding='utf-8') svgWidget.renderer().load(svg_bytes) svgWidget.update() svgWidget.show() loop = QEventLoop() QTimer.singleShot(1000, loop.quit) loop.exec_() source_cell_index = chess.square( chess.FILE_NAMES.index('a'), chess.RANK_NAMES.index('1')) #example: A1 is cell index = 0 (out of 63) dest_cell_index = chess.square(chess.FILE_NAMES.index('a'), chess.RANK_NAMES.index('3')) print('source_cell_index:', source_cell_index) print('dest_cell_index:', dest_cell_index) board_picture = chess.svg.board(board, arrows=[ (source_cell_index, dest_cell_index)
class InstallWizard(QDialog, MessageBoxMixin, BaseWizard): accept_signal = pyqtSignal() synchronized_signal = pyqtSignal(str) def __init__(self): BaseWizard.__init__(self) QDialog.__init__(self, None) self.setWindowTitle('ElectrumSV') self.language_for_seed = app_state.config.get('language') self.setMinimumSize(600, 420) self.accept_signal.connect(self.accept) self.back_button = QPushButton(_(MSG_BUTTON_BACK), self) self.back_button.setText( _(MSG_BUTTON_BACK) if self.can_go_back() else _(MSG_BUTTON_CANCEL)) self.next_button = QPushButton(_(MSG_BUTTON_NEXT), self) self.next_button.setDefault(True) self.icon_filename = None self.loop = QEventLoop() self.rejected.connect(lambda: self.loop.exit(0)) self.back_button.clicked.connect(lambda: self.loop.exit(1)) self.next_button.clicked.connect(lambda: self.loop.exit(2)) self.scroll_widget = QWidget() self.scroll_widget.setLayout(self.create_template_layout()) scroll = QScrollArea() scroll.setWidget(self.scroll_widget) scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) scroll.setWidgetResizable(True) outer_vbox = QVBoxLayout(self) outer_vbox.addWidget(scroll) outer_vbox.addLayout(Buttons(self.back_button, self.next_button)) self.show() self.raise_() self.refresh_gui() # Need for QT on MacOSX. Lame. def create_template_layout(self): """ The standard layout divides creates a three part template. """ self.title = QLabel() self.main_widget = QWidget() self.please_wait = QLabel(_("Please wait...")) self.please_wait.setAlignment(Qt.AlignCenter) vbox = QVBoxLayout() vbox.addWidget(self.title) vbox.addWidget(self.main_widget) vbox.addStretch(1) vbox.addWidget(self.please_wait) vbox.addStretch(1) self.template_hbox = QHBoxLayout() vbox.addLayout(self.template_hbox) return vbox def select_storage(self, initial_path: str, is_startup=False): if is_startup: self._copy_electron_cash_wallets() vbox = QVBoxLayout() hbox = QHBoxLayout() hbox.addWidget(QLabel(_('Wallet') + ':')) self.name_e = QLineEdit() hbox.addWidget(self.name_e) button = QPushButton(_('Choose...')) hbox.addWidget(button) vbox.addLayout(hbox) self.msg_label = QLabel('') vbox.addWidget(self.msg_label) hbox2 = QHBoxLayout() self.pw_e = PasswordLineEdit() self.pw_e.setMinimumWidth(200) self.pw_label = QLabel(_('Password') + ':') self.pw_label.setAlignment(Qt.AlignTop) hbox2.addWidget(self.pw_label) hbox2.addWidget(self.pw_e) hbox2.addStretch() vbox.addLayout(hbox2) self._set_standard_layout(vbox, title=_('ElectrumSV wallet'), back_text=_(MSG_BUTTON_CANCEL)) esv_wallets_dir = os.path.join(app_state.config.electrum_path(), "wallets") if is_startup: def _show_copy_electron_cash_wallets_dialog(*args): nonlocal esv_wallets_dir, ec_wallets_dir d = WindowModalDialog(self, _("Copy Electron Cash Wallets")) d.setMinimumWidth(400) vbox, file_list = self._create_copy_electron_cash_wallets_layout( ec_wallets_dir) bbox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) bbox.rejected.connect(d.reject) bbox.accepted.connect(d.accept) vbox.addWidget(bbox) d.setLayout(vbox) result = d.exec() if result == QDialog.Accepted: self._do_copy_electron_cash_wallets( file_list, esv_wallets_dir, ec_wallets_dir) _update_selected_wallet() ec_import_text = ("<p>" + _( "You have previously run Electron Cash and created at " + "least one wallet with it. ElectrumSV should not be used to open these wallets " + "directly using the 'Choose' button, and you should instead use the 'Import' " + "button to help you copy them." ) + "</p>" + "<p>" + _( "There are many reasons for this, and the simplest is that if Electron Cash " + "and ElectrumSV were to operate on the same wallet at the same time, then the " + "wallet will most likely become corrupted. It's simpler in every way to just " + "copy the wallet over to ElectrumSV and use a separate wallet file for each." ) + "</p>") ec_import_icon = HelpLabel("label text", ec_import_text) ec_import_icon.setPixmap( QPixmap(icon_path("icons8-info.svg")).scaledToWidth( 16, Qt.SmoothTransformation)) ec_import_label = QLabel( _("Existing Electron Cash wallets detected")) ec_import_button = QPushButton(_("Import...")) ec_import_button.clicked.connect( _show_copy_electron_cash_wallets_dialog) self.template_hbox.addWidget(ec_import_icon) self.template_hbox.addWidget(ec_import_label) self.template_hbox.addWidget(ec_import_button) self.template_hbox.addStretch(1) ec_wallets_dir = get_electron_cash_user_dir(esv_wallets_dir) if len(self._list_user_wallets(ec_wallets_dir)) == 0: ec_import_icon.set_help_text("<p>" + _( "This feature is aimed at users who were " + "already using Electron Cash and have existing wallets associated with it. " + "None were detected on this computer, but if you do have some stored in " + "places ElectrumSV does not know about you may need to copy them " + "yourself." ) + "</p>" + "<p>" + _( "You should never open your existing Electron Cash wallets directly in " + "ElectrumSV as this can lead to them being opened in both applications at " + "the same time, and can result in corruption.") + "</p>") ec_import_button.setEnabled(False) ec_import_button.setToolTip(_("Nothing to import")) ec_import_label.setText(_("No Electron Cash wallets detected")) if WalletStorage.files_are_matched_by_path(initial_path): self._storage_existing = WalletStorage(initial_path, manual_upgrades=True) wallet_folder = os.path.dirname(initial_path) def _on_choose() -> None: path, __ = QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder) if path: self.name_e.setText(path) def _on_filename(filename: str) -> None: pw = False self._storage_existing = None # A relative path will be relative to the folder we offered in the choose dialog. # An absolute path will not get joined to the dialog folder (no-op). path = os.path.join(wallet_folder, filename) if WalletStorage.files_are_matched_by_path(path): try: self._storage_existing = WalletStorage( path, manual_upgrades=True) except IOError: self.next_button.setEnabled(False) msg = _('Cannot read file') else: self.next_button.setEnabled(True) if self._storage_existing.is_encrypted(): msg = '\n'.join([ _("This file is encrypted."), _('Enter your password or choose another file.'), ]) pw = True else: msg = _("Press 'Next' to open this wallet.") else: msg = _("This file does not exist.") if os.access(wallet_folder, os.W_OK): self.next_button.setEnabled(True) msg += "\n" + _( "Press 'Next' to create this wallet, or choose another file." ) self._path_new = path else: self.next_button.setEnabled(False) msg += "\n" + _("You do not have write access " + "to this folder to create a new wallet.") self.msg_label.setText(msg) if pw: self.pw_label.show() self.pw_e.show() self.pw_e.setFocus() else: self.pw_label.hide() self.pw_e.hide() def _update_selected_wallet( skip_pick_most_recent: bool = False) -> None: wallet_filename = None if is_startup and not skip_pick_most_recent and self._storage_existing is None: esv_wallet_names = self._list_user_wallets(esv_wallets_dir) if len(esv_wallet_names): wallet_filename = esv_wallet_names[0] if wallet_filename is None: if self._storage_existing is not None: wallet_filename = os.path.basename( self._storage_existing.get_path()) else: wallet_filename = os.path.basename(initial_path) self.name_e.setText(wallet_filename) button.clicked.connect(_on_choose) self.name_e.textChanged.connect(_on_filename) # We do not pick the most recent when first displaying the wizard because we want to # treat the preselected wallet as the user's explicit choice. So a non-existent name # should be a possible wallet creation. _update_selected_wallet(skip_pick_most_recent=True) while True: if self._storage_existing is not None and not self._storage_existing.is_encrypted( ): break if self.loop.exec_() != 2: # 2 = next return if self._storage_existing is None: break if self._storage_existing is not None and self._storage_existing.is_encrypted( ): password = self.pw_e.text() try: self._storage_existing.decrypt(password) self.pw_e.setText('') break except DecryptionError: QMessageBox.information(None, _('Error'), _("Incorrect password")) continue except Exception as e: logger.exception("decrypting storage") QMessageBox.information(None, _('Error'), str(e)) return return True def _copy_electron_cash_wallets(self): """ Work out whether we should show UI to offer to copy the user's Electron Cash wallets to their ElectrumSV wallet directory, and if so, show it and give them the chance. """ # If the user has ElectrumSV wallets already, we do not offer to copy the one's # Electron Cash has. esv_wallets_dir = os.path.join(app_state.config.electrum_path(), "wallets") if len(self._list_user_wallets(esv_wallets_dir)) > 0: return ec_wallets_dir = get_electron_cash_user_dir(esv_wallets_dir) ec_wallet_count = len(self._list_user_wallets(ec_wallets_dir)) # If the user does not have Electron Cash wallets to copy, there's no point in offering. if ec_wallet_count == 0: return vbox, file_list = self._create_copy_electron_cash_wallets_layout( ec_wallets_dir) self._set_standard_layout(vbox, title=_('Import Electron Cash wallets')) v = self.loop.exec_() # Cancel, exit application. if v == -1: raise UserCancelled() if v != 2: raise GoBack() self._do_copy_electron_cash_wallets(file_list, esv_wallets_dir, ec_wallets_dir) def _do_copy_electron_cash_wallets(self, file_list, esv_wallets_dir, ec_wallets_dir): # If the user selected any files, then we copy them before exiting to the next page. copy_count = 0 for item in file_list.selectedItems(): filename = item.text() source_path = os.path.join(ec_wallets_dir, filename) target_path = os.path.join(esv_wallets_dir, filename) # If they are copying an Electron Cash wallet over an ElectrumSV wallet, make sure # they confirm they are going to replace/overwrite it. if os.path.exists(target_path): if self.question( _("You already have a wallet named '{}' for ElectrumSV. " + "Replace/overwrite it?").format(filename), self, _("Delete Wallet?")): os.remove(target_path) else: continue try: shutil.copyfile(source_path, target_path) copy_count += 1 except shutil.Error: # For now we ignore copy errors. pass if copy_count == 1: self.show_message(_("1 wallet copied.")) elif copy_count > 1: self.show_message(_("%d wallets copied.") % copy_count) def _create_copy_electron_cash_wallets_layout(self, ec_wallets_dir): def update_summary_label(): selection_count = len(file_list.selectedItems()) if selection_count == 0: summary_label.setText( _("No wallets are selected / will be copied.")) elif selection_count == 1: summary_label.setText( _("1 wallet is selected / will be copied.")) else: summary_label.setText( _("%d wallets are selected / will be copied.") % selection_count) wallet_filenames = sorted(os.listdir(ec_wallets_dir), key=lambda s: s.lower()) file_list = QListWidget() file_list.setSelectionMode(QAbstractItemView.ExtendedSelection) for filename in wallet_filenames: if not self._ignore_wallet_file( os.path.join(ec_wallets_dir, filename)): file_list.addItem(QListWidgetItem(filename)) file_list.itemSelectionChanged.connect(update_summary_label) vbox = QVBoxLayout() introduction_label = QLabel( _("Your Electron Cash wallet directory was found. If you want ElectrumSV to import " "any of them on your behalf, select the ones you want copied from the list below " "before clicking the Next button.")) introduction_label.setWordWrap(True) vbox.setSpacing(20) vbox.addWidget(introduction_label) vbox.addWidget(file_list) summary_label = QLabel() update_summary_label() vbox.addWidget(summary_label) return vbox, file_list def _list_user_wallets(self, wallets_path): if os.path.exists(wallets_path): from stat import ST_MTIME l = [(os.stat(os.path.join(wallets_path, filename))[ST_MTIME], filename) for filename in os.listdir(wallets_path) if not self._ignore_wallet_file( os.path.join(wallets_path, filename))] l = sorted(l, reverse=True) return [entry[1] for entry in l] return [] def _ignore_wallet_file(self, wallet_path): if os.path.isdir(wallet_path): return True if wallet_path.startswith("."): return True return False def run_and_get_wallet(self) -> Optional[Abstract_Wallet]: # NOTE(rt12): This used to have unused code related to resuming incompletely created # wallets. This is worth supporting, but not at this stage where we will eventually # rewrite for a new wallet wizard and multiple accounts and legacy wallets so on. if self._storage_existing is not None: path = self._storage_existing.get_path() if self._storage_existing.requires_split(): msg = _( "The wallet '{}' contains multiple accounts, which are not supported.\n\n" "Do you want to split your wallet " "into multiple files?").format(path) if not MessageBox.question(msg): return file_list = '\n'.join(self._storage_existing.split_accounts()) msg = (_('Your accounts have been moved to') + ':\n' + file_list + '\n\n' + _('Do you want to delete the old file') + ':\n' + path) if self.question(msg): # We know that this will be the only relevant path and will not require # extensions, as it predates the use of the database. os.remove(path) self.show_warning(_('The file was removed')) return if self._storage_existing.requires_upgrade(): msg = _( "The format of your wallet '%s' must be upgraded for ElectrumSV. " "This change will not be backward compatible, " + "and your existing wallet will be backed up. Proceed?" ) % path if not MessageBox.question(msg): return self._storage_existing.upgrade() self._parent_wallet = ParentWallet(self._storage_existing) else: assert self._path_new is not None, "user should have selected storage already" wallet_filename = os.path.basename(self._path_new) # Make a temporary directory as securely as possible, where the new wallet will be # created. creation_path = tempfile.mkdtemp() try: path_new = os.path.join(creation_path, wallet_filename) self._storage_new = WalletStorage(path_new) # Ensure the path is full and includes the extension. path_new = self._storage_new.get_path() self.run("new") if self._parent_wallet is not None: self._parent_wallet.save_storage() if self._parent_wallet is not None: self._parent_wallet.move_to(self._path_new) else: # We want to make sure we are not keeping the database open, before deleting. self._storage_new.close() del self._storage_new finally: shutil.rmtree(creation_path) return self._parent_wallet def finished(self): """Called in hardware client wrapper, in order to close popups.""" return def on_error(self, exc_info): if not isinstance(exc_info[1], UserCancelled): logger.exception("") self.show_error(str(exc_info[1])) def _remove_layout_from_widget(self, widget): """ The only way to remove a layout from a first widget, is to transfer it to a second one. This needs to be done, to be able to set a new layout on the first widget. """ existing_layout = widget.layout() QWidget().setLayout(existing_layout) def _set_layout(self, layout, next_enabled=True, back_text=None): """ Set a layout that is in control of the whole display area. """ self._remove_layout_from_widget(self.scroll_widget) self.scroll_widget.setLayout(layout) self.back_button.setEnabled(True) if back_text is not None: self.back_button.setText(back_text) self.next_button.setEnabled(next_enabled) if next_enabled: self.next_button.setFocus() def _set_standard_layout(self, layout, title=None, next_enabled=True, back_text=None): """ Ensure the standard template layout is in place. And put the current stage's sub-layout in the defined place. """ self._remove_layout_from_widget(self.scroll_widget) self.scroll_widget.setLayout(self.create_template_layout()) self.title.setText("<b>%s</b>" % title if title else "") self.title.setVisible(bool(title)) self.main_widget.setLayout(layout) if back_text is None: self.back_button.setText(_(MSG_BUTTON_BACK)) else: self.back_button.setText(back_text) self.back_button.setEnabled(True) self.next_button.setText(_(MSG_BUTTON_NEXT)) self.next_button.setEnabled(next_enabled) if next_enabled: self.next_button.setFocus() self.main_widget.setVisible(True) self.please_wait.setVisible(False) def exec_layout(self, layout, title=None, raise_on_cancel=True, next_enabled=True): self._set_standard_layout(layout, title, next_enabled) result = self.loop.exec_() if not result and raise_on_cancel: raise UserCancelled if result == 1: raise GoBack self.title.setVisible(False) self.back_button.setEnabled(False) self.next_button.setEnabled(False) self.main_widget.setVisible(False) self.please_wait.setVisible(True) self.refresh_gui() return result def refresh_gui(self): # For some reason, to refresh the GUI this needs to be called twice app_state.app.processEvents() app_state.app.processEvents() def text_input(self, title, message, is_valid, allow_multi=False): slayout = KeysLayout(parent=self, title=message, is_valid=is_valid, allow_multi=allow_multi) self.exec_layout(slayout, title, next_enabled=False) return slayout.get_text() def seed_input(self, title, message, is_seed, options): slayout = SeedLayout(title=message, is_seed=is_seed, options=options, parent=self) self.exec_layout(slayout, title, next_enabled=False) return slayout.get_seed(), slayout.is_bip39, slayout.is_ext @wizard_dialog def add_xpub_dialog(self, title, message, is_valid, run_next, allow_multi=False): return self.text_input(title, message, is_valid, allow_multi) @wizard_dialog def add_cosigner_dialog(self, run_next, index, is_valid): title = _("Add Cosigner") + " %d" % index message = ' '.join([ _('Please enter the master public key (xpub) of your cosigner.'), _('Enter their master private key (xprv) if you want to be able to sign for them.' ) ]) return self.text_input(title, message, is_valid) @wizard_dialog def restore_seed_dialog(self, run_next, test): options = [] if self.opt_ext: options.append('ext') if self.opt_bip39: options.append('bip39') title = _('Enter Seed') message = _( 'Please enter your seed phrase in order to restore your wallet.') return self.seed_input(title, message, test, options) @wizard_dialog def confirm_seed_dialog(self, run_next, test): app_state.app.clipboard().clear() title = _('Confirm Seed') message = ' '.join([ _('Your seed is important!'), _('If you lose your seed, your money will be permanently lost.'), _('To make sure that you have properly saved your seed, please retype it here.' ) ]) seed, is_bip39, is_ext = self.seed_input(title, message, test, None) return seed @wizard_dialog def show_seed_dialog(self, run_next, seed_text): title = _("Your wallet generation seed is:") slayout = SeedLayout(seed=seed_text, title=title, msg=True, options=['ext']) self.exec_layout(slayout) return slayout.is_ext def pw_layout(self, msg, kind): playout = PasswordLayout(None, msg, kind, self.next_button) self.exec_layout(playout.layout()) return playout.new_password() @wizard_dialog def request_password(self, run_next): """Request the user enter a new password and confirm it. Return the password or None for no password.""" return self.pw_layout(MSG_ENTER_PASSWORD, PW_NEW) @wizard_dialog def confirm_dialog(self, title, message, run_next): self.confirm(message, title) def confirm(self, message, title): label = WWLabel(message) vbox = QVBoxLayout() vbox.addWidget(label) self.exec_layout(vbox, title) @wizard_dialog def action_dialog(self, action, run_next): self.run(action) def terminate(self): self.accept_signal.emit() @wizard_dialog def choice_dialog(self, title, message, choices, run_next): c_values = [x[0] for x in choices] c_titles = [x[1] for x in choices] clayout = ChoicesLayout(message, c_titles) vbox = QVBoxLayout() vbox.addLayout(clayout.layout()) self.exec_layout(vbox, title) action = c_values[clayout.selected_index()] return action def query_choice(self, msg, choices): """called by hardware wallets""" clayout = ChoicesLayout(msg, choices) vbox = QVBoxLayout() vbox.addLayout(clayout.layout()) self.exec_layout(vbox, '') return clayout.selected_index() @wizard_dialog def line_dialog(self, run_next, title, message, default, test, warning=''): vbox = QVBoxLayout() vbox.addWidget(WWLabel(message)) line = QLineEdit() line.setText(default) def f(text): self.next_button.setEnabled(test(text)) line.textEdited.connect(f) vbox.addWidget(line) vbox.addWidget(WWLabel(warning)) self.exec_layout(vbox, title, next_enabled=test(default)) return ' '.join(line.text().split()) @wizard_dialog def show_xpub_dialog(self, xpub, run_next): msg = ' '.join([ _("Here is your master public key."), _("Please share it with your cosigners.") ]) vbox = QVBoxLayout() layout = SeedLayout(xpub, title=msg, icon=False) vbox.addLayout(layout.layout()) self.exec_layout(vbox, _('Master Public Key')) return None def init_network(self, network): message = _( "ElectrumSV communicates with remote servers to get " "information about your transactions and addresses. The " "servers all fulfil the same purpose only differing in " "hardware. In most cases you simply want to let ElectrumSV " "pick one at random. However if you prefer feel free to " "select a server manually.") choices = [_("Auto connect"), _("Select server manually")] title = _("How do you want to connect to a server? ") clayout = ChoicesLayout(message, choices) self.back_button.setText(_(MSG_BUTTON_CANCEL)) self.exec_layout(clayout.layout(), title) r = clayout.selected_index() app_state.config.set_key('auto_connect', r == 0, True) if r == 1: nlayout = NetworkChoiceLayout(network, app_state.config, wizard=True) if self.exec_layout(nlayout.layout()): nlayout.accept() @wizard_dialog def multisig_dialog(self, run_next): cw = CosignWidget(2, 2) m_edit = QSlider(Qt.Horizontal, self) n_edit = QSlider(Qt.Horizontal, self) n_edit.setMinimum(2) n_edit.setMaximum(15) m_edit.setMinimum(1) m_edit.setMaximum(2) n_edit.setValue(2) m_edit.setValue(2) n_label = QLabel() m_label = QLabel() grid = QGridLayout() grid.addWidget(n_label, 0, 0) grid.addWidget(n_edit, 0, 1) grid.addWidget(m_label, 1, 0) grid.addWidget(m_edit, 1, 1) def on_m(m): m_label.setText(_('Require %d signatures') % m) cw.set_m(m) def on_n(n): n_label.setText(_('From %d cosigners') % n) cw.set_n(n) m_edit.setMaximum(n) n_edit.valueChanged.connect(on_n) m_edit.valueChanged.connect(on_m) on_n(2) on_m(2) vbox = QVBoxLayout() vbox.addWidget(cw) vbox.addWidget( WWLabel( _("Choose the number of signatures needed to unlock " "funds in your wallet:"))) vbox.addLayout(grid) self.exec_layout(vbox, _("Multi-Signature Wallet")) m = int(m_edit.value()) n = int(n_edit.value()) return (m, n)
class InstallWizard(QDialog, MessageBoxMixin, BaseWizard): accept_signal = pyqtSignal() def __init__(self, config: 'SimpleConfig', app: QApplication, plugins: 'Plugins', *, gui_object: 'ElectrumGui'): QDialog.__init__(self, None) BaseWizard.__init__(self, config, plugins) self.setWindowTitle('Electrum-LTC - ' + _('Install Wizard')) self.app = app self.config = config self.gui_thread = gui_object.gui_thread self.setMinimumSize(600, 400) self.accept_signal.connect(self.accept) self.title = QLabel() self.main_widget = QWidget() self.back_button = QPushButton(_("Back"), self) self.back_button.setText( _('Back') if self.can_go_back() else _('Cancel')) self.next_button = QPushButton(_("Next"), self) self.next_button.setDefault(True) self.logo = QLabel() self.please_wait = QLabel(_("Please wait...")) self.please_wait.setAlignment(Qt.AlignCenter) self.icon_filename = None self.loop = QEventLoop() self.rejected.connect(lambda: self.loop.exit(0)) self.back_button.clicked.connect(lambda: self.loop.exit(1)) self.next_button.clicked.connect(lambda: self.loop.exit(2)) outer_vbox = QVBoxLayout(self) inner_vbox = QVBoxLayout() inner_vbox.addWidget(self.title) inner_vbox.addWidget(self.main_widget) inner_vbox.addStretch(1) inner_vbox.addWidget(self.please_wait) inner_vbox.addStretch(1) scroll_widget = QWidget() scroll_widget.setLayout(inner_vbox) scroll = QScrollArea() scroll.setWidget(scroll_widget) scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) scroll.setWidgetResizable(True) icon_vbox = QVBoxLayout() icon_vbox.addWidget(self.logo) icon_vbox.addStretch(1) hbox = QHBoxLayout() hbox.addLayout(icon_vbox) hbox.addSpacing(5) hbox.addWidget(scroll) hbox.setStretchFactor(scroll, 1) outer_vbox.addLayout(hbox) outer_vbox.addLayout(Buttons(self.back_button, self.next_button)) self.set_icon('electrum-ltc.png') self.show() self.raise_() self.refresh_gui() # Need for QT on MacOSX. Lame. def select_storage( self, path, get_wallet_from_daemon) -> Tuple[str, Optional[WalletStorage]]: vbox = QVBoxLayout() hbox = QHBoxLayout() hbox.addWidget(QLabel(_('Wallet') + ':')) name_e = QLineEdit() hbox.addWidget(name_e) button = QPushButton(_('Choose...')) hbox.addWidget(button) vbox.addLayout(hbox) msg_label = WWLabel('') vbox.addWidget(msg_label) hbox2 = QHBoxLayout() pw_e = PasswordLineEdit('', self) pw_e.setFixedWidth(17 * char_width_in_lineedit()) pw_label = QLabel(_('Password') + ':') hbox2.addWidget(pw_label) hbox2.addWidget(pw_e) hbox2.addStretch() vbox.addLayout(hbox2) vbox.addSpacing(50) vbox_create_new = QVBoxLayout() vbox_create_new.addWidget(QLabel(_('Alternatively') + ':'), alignment=Qt.AlignLeft) button_create_new = QPushButton(_('Create New Wallet')) button_create_new.setMinimumWidth(120) vbox_create_new.addWidget(button_create_new, alignment=Qt.AlignLeft) widget_create_new = QWidget() widget_create_new.setLayout(vbox_create_new) vbox_create_new.setContentsMargins(0, 0, 0, 0) vbox.addWidget(widget_create_new) self.set_layout(vbox, title=_('Electrum-LTC wallet')) temp_storage = None # type: Optional[WalletStorage] wallet_folder = os.path.dirname(path) def on_choose(): path, __ = QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder) if path: name_e.setText(path) def on_filename(filename): # FIXME? "filename" might contain ".." (etc) and hence sketchy path traversals are possible nonlocal temp_storage temp_storage = None msg = None if filename: path = os.path.join(wallet_folder, filename) wallet_from_memory = get_wallet_from_daemon(path) try: if wallet_from_memory: temp_storage = wallet_from_memory.storage # type: Optional[WalletStorage] else: temp_storage = WalletStorage(path) except (StorageReadWriteError, WalletFileException) as e: msg = _('Cannot read file') + f'\n{repr(e)}' except Exception as e: self.logger.exception('') msg = _('Cannot read file') + f'\n{repr(e)}' else: msg = _('') self.next_button.setEnabled(temp_storage is not None) user_needs_to_enter_password = False if temp_storage: if not temp_storage.file_exists(): msg =_("This file does not exist.") + '\n' \ + _("Press 'Next' to create this wallet, or choose another file.") elif not wallet_from_memory: if temp_storage.is_encrypted_with_user_pw(): msg = _("This file is encrypted with a password.") + '\n' \ + _('Enter your password or choose another file.') user_needs_to_enter_password = True elif temp_storage.is_encrypted_with_hw_device(): msg = _("This file is encrypted using a hardware device.") + '\n' \ + _("Press 'Next' to choose device to decrypt.") else: msg = _("Press 'Next' to open this wallet.") else: msg = _("This file is already open in memory.") + "\n" \ + _("Press 'Next' to create/focus window.") if msg is None: msg = _('Cannot read file') msg_label.setText(msg) widget_create_new.setVisible( bool(temp_storage and temp_storage.file_exists())) if user_needs_to_enter_password: pw_label.show() pw_e.show() pw_e.setFocus() else: pw_label.hide() pw_e.hide() button.clicked.connect(on_choose) button_create_new.clicked.connect( partial(name_e.setText, get_new_wallet_name(wallet_folder))) name_e.textChanged.connect(on_filename) name_e.setText(os.path.basename(path)) def run_user_interaction_loop(): while True: if self.loop.exec_() != 2: # 2 = next raise UserCancelled() assert temp_storage if temp_storage.file_exists( ) and not temp_storage.is_encrypted(): break if not temp_storage.file_exists(): break wallet_from_memory = get_wallet_from_daemon(temp_storage.path) if wallet_from_memory: raise WalletAlreadyOpenInMemory(wallet_from_memory) if temp_storage.file_exists() and temp_storage.is_encrypted(): if temp_storage.is_encrypted_with_user_pw(): password = pw_e.text() try: temp_storage.decrypt(password) break except InvalidPassword as e: self.show_message(title=_('Error'), msg=str(e)) continue except BaseException as e: self.logger.exception('') self.show_message(title=_('Error'), msg=repr(e)) raise UserCancelled() elif temp_storage.is_encrypted_with_hw_device(): try: self.run('choose_hw_device', HWD_SETUP_DECRYPT_WALLET, storage=temp_storage) except InvalidPassword as e: self.show_message( title=_('Error'), msg= _('Failed to decrypt using this hardware device.' ) + '\n' + _('If you use a passphrase, make sure it is correct.' )) self.reset_stack() return self.select_storage(path, get_wallet_from_daemon) except (UserCancelled, GoBack): raise except BaseException as e: self.logger.exception('') self.show_message(title=_('Error'), msg=repr(e)) raise UserCancelled() if temp_storage.is_past_initial_decryption(): break else: raise UserCancelled() else: raise Exception('Unexpected encryption version') try: run_user_interaction_loop() finally: try: pw_e.clear() except RuntimeError: # wrapped C/C++ object has been deleted. pass # happens when decrypting with hw device return temp_storage.path, (temp_storage if temp_storage.file_exists() else None) def run_upgrades(self, storage: WalletStorage, db: 'WalletDB') -> None: path = storage.path if db.requires_split(): self.hide() msg = _( "The wallet '{}' contains multiple accounts, which are no longer supported since Electrum 2.7.\n\n" "Do you want to split your wallet into multiple files?" ).format(path) if not self.question(msg): return file_list = db.split_accounts(path) msg = _('Your accounts have been moved to') + ':\n' + '\n'.join( file_list) + '\n\n' + _( 'Do you want to delete the old file') + ':\n' + path if self.question(msg): os.remove(path) self.show_warning(_('The file was removed')) # raise now, to avoid having the old storage opened raise UserCancelled() action = db.get_action() if action and db.requires_upgrade(): raise WalletFileException( 'Incomplete wallet files cannot be upgraded.') if action: self.hide() msg = _("The file '{}' contains an incompletely created wallet.\n" "Do you want to complete its creation now?").format(path) if not self.question(msg): if self.question( _("Do you want to delete '{}'?").format(path)): os.remove(path) self.show_warning(_('The file was removed')) return self.show() self.data = json.loads(storage.read()) self.run(action) for k, v in self.data.items(): db.put(k, v) db.write(storage) return if db.requires_upgrade(): self.upgrade_db(storage, db) def on_error(self, exc_info): if not isinstance(exc_info[1], UserCancelled): self.logger.error("on_error", exc_info=exc_info) self.show_error(str(exc_info[1])) def set_icon(self, filename): prior_filename, self.icon_filename = self.icon_filename, filename self.logo.setPixmap( QPixmap(icon_path(filename)).scaledToWidth( 60, mode=Qt.SmoothTransformation)) return prior_filename def set_layout(self, layout, title=None, next_enabled=True): self.title.setText("<b>%s</b>" % title if title else "") self.title.setVisible(bool(title)) # Get rid of any prior layout by assigning it to a temporary widget prior_layout = self.main_widget.layout() if prior_layout: QWidget().setLayout(prior_layout) self.main_widget.setLayout(layout) self.back_button.setEnabled(True) self.next_button.setEnabled(next_enabled) if next_enabled: self.next_button.setFocus() self.main_widget.setVisible(True) self.please_wait.setVisible(False) def exec_layout(self, layout, title=None, raise_on_cancel=True, next_enabled=True, focused_widget=None): self.set_layout(layout, title, next_enabled) if focused_widget: focused_widget.setFocus() result = self.loop.exec_() if not result and raise_on_cancel: raise UserCancelled() if result == 1: raise GoBack from None self.title.setVisible(False) self.back_button.setEnabled(False) self.next_button.setEnabled(False) self.main_widget.setVisible(False) self.please_wait.setVisible(True) self.refresh_gui() return result def refresh_gui(self): # For some reason, to refresh the GUI this needs to be called twice self.app.processEvents() self.app.processEvents() def remove_from_recently_open(self, filename): self.config.remove_from_recently_open(filename) def text_input(self, title, message, is_valid, allow_multi=False): slayout = KeysLayout(parent=self, header_layout=message, is_valid=is_valid, allow_multi=allow_multi, config=self.config) self.exec_layout(slayout, title, next_enabled=False) return slayout.get_text() def seed_input(self, title, message, is_seed, options): slayout = SeedLayout( title=message, is_seed=is_seed, options=options, parent=self, config=self.config, ) self.exec_layout(slayout, title, next_enabled=False) return slayout.get_seed(), slayout.is_bip39, slayout.is_ext @wizard_dialog def add_xpub_dialog(self, title, message, is_valid, run_next, allow_multi=False, show_wif_help=False): header_layout = QHBoxLayout() label = WWLabel(message) label.setMinimumWidth(400) header_layout.addWidget(label) if show_wif_help: header_layout.addWidget(InfoButton(WIF_HELP_TEXT), alignment=Qt.AlignRight) return self.text_input(title, header_layout, is_valid, allow_multi) @wizard_dialog def add_cosigner_dialog(self, run_next, index, is_valid): title = _("Add Cosigner") + " %d" % index message = ' '.join([ _('Please enter the master public key (xpub) of your cosigner.'), _('Enter their master private key (xprv) if you want to be able to sign for them.' ) ]) return self.text_input(title, message, is_valid) @wizard_dialog def restore_seed_dialog(self, run_next, test): options = [] if self.opt_ext: options.append('ext') if self.opt_bip39: options.append('bip39') title = _('Enter Seed') message = _( 'Please enter your seed phrase in order to restore your wallet.') return self.seed_input(title, message, test, options) @wizard_dialog def confirm_seed_dialog(self, run_next, seed, test): self.app.clipboard().clear() title = _('Confirm Seed') message = ' '.join([ _('Your seed is important!'), _('If you lose your seed, your money will be permanently lost.'), _('To make sure that you have properly saved your seed, please retype it here.' ) ]) seed, is_bip39, is_ext = self.seed_input(title, message, test, None) return seed @wizard_dialog def show_seed_dialog(self, run_next, seed_text): title = _("Your wallet generation seed is:") slayout = SeedLayout( seed=seed_text, title=title, msg=True, options=['ext'], config=self.config, ) self.exec_layout(slayout) return slayout.is_ext def pw_layout(self, msg, kind, force_disable_encrypt_cb): pw_layout = PasswordLayout( msg=msg, kind=kind, OK_button=self.next_button, force_disable_encrypt_cb=force_disable_encrypt_cb) pw_layout.encrypt_cb.setChecked(True) try: self.exec_layout(pw_layout.layout(), focused_widget=pw_layout.new_pw) return pw_layout.new_password(), pw_layout.encrypt_cb.isChecked() finally: pw_layout.clear_password_fields() @wizard_dialog def request_password(self, run_next, force_disable_encrypt_cb=False): """Request the user enter a new password and confirm it. Return the password or None for no password.""" return self.pw_layout(MSG_ENTER_PASSWORD, PW_NEW, force_disable_encrypt_cb) @wizard_dialog def request_storage_encryption(self, run_next): playout = PasswordLayoutForHW(MSG_HW_STORAGE_ENCRYPTION) playout.encrypt_cb.setChecked(True) self.exec_layout(playout.layout()) return playout.encrypt_cb.isChecked() @wizard_dialog def confirm_dialog(self, title, message, run_next): self.confirm(message, title) def confirm(self, message, title): label = WWLabel(message) vbox = QVBoxLayout() vbox.addWidget(label) self.exec_layout(vbox, title) @wizard_dialog def action_dialog(self, action, run_next): self.run(action) def terminate(self, **kwargs): self.accept_signal.emit() def waiting_dialog(self, task, msg, on_finished=None): label = WWLabel(msg) vbox = QVBoxLayout() vbox.addSpacing(100) label.setMinimumWidth(300) label.setAlignment(Qt.AlignCenter) vbox.addWidget(label) self.set_layout(vbox, next_enabled=False) self.back_button.setEnabled(False) t = threading.Thread(target=task) t.start() while True: t.join(1.0 / 60) if t.is_alive(): self.refresh_gui() else: break if on_finished: on_finished() def run_task_without_blocking_gui(self, task, *, msg=None): assert self.gui_thread == threading.current_thread( ), 'must be called from GUI thread' if msg is None: msg = _("Please wait...") exc = None # type: Optional[Exception] res = None def task_wrapper(): nonlocal exc nonlocal res try: res = task() except Exception as e: exc = e self.waiting_dialog(task_wrapper, msg=msg) if exc is None: return res else: raise exc @wizard_dialog def choice_dialog(self, title, message, choices, run_next): c_values = [x[0] for x in choices] c_titles = [x[1] for x in choices] clayout = ChoicesLayout(message, c_titles) vbox = QVBoxLayout() vbox.addLayout(clayout.layout()) self.exec_layout(vbox, title) action = c_values[clayout.selected_index()] return action def query_choice(self, msg, choices): """called by hardware wallets""" clayout = ChoicesLayout(msg, choices) vbox = QVBoxLayout() vbox.addLayout(clayout.layout()) self.exec_layout(vbox, '') return clayout.selected_index() @wizard_dialog def derivation_and_script_type_gui_specific_dialog( self, *, title: str, message1: str, choices: List[Tuple[str, str, str]], hide_choices: bool = False, message2: str, test_text: Callable[[str], int], run_next, default_choice_idx: int = 0, get_account_xpub=None, ) -> Tuple[str, str]: vbox = QVBoxLayout() if get_account_xpub: button = QPushButton(_("Detect Existing Accounts")) def on_account_select(account): script_type = account["script_type"] if script_type == "p2pkh": script_type = "standard" button_index = c_values.index(script_type) button = clayout.group.buttons()[button_index] button.setChecked(True) line.setText(account["derivation_path"]) button.clicked.connect(lambda: Bip39RecoveryDialog( self, get_account_xpub, on_account_select)) vbox.addWidget(button, alignment=Qt.AlignLeft) vbox.addWidget(QLabel(_("Or"))) c_values = [x[0] for x in choices] c_titles = [x[1] for x in choices] c_default_text = [x[2] for x in choices] def on_choice_click(clayout): idx = clayout.selected_index() line.setText(c_default_text[idx]) clayout = ChoicesLayout(message1, c_titles, on_choice_click, checked_index=default_choice_idx) if not hide_choices: vbox.addLayout(clayout.layout()) vbox.addWidget(WWLabel(message2)) line = QLineEdit() def on_text_change(text): self.next_button.setEnabled(test_text(text)) line.textEdited.connect(on_text_change) on_choice_click(clayout) # set default text for "line" vbox.addWidget(line) self.exec_layout(vbox, title) choice = c_values[clayout.selected_index()] return str(line.text()), choice @wizard_dialog def line_dialog(self, run_next, title, message, default, test, warning='', presets=(), warn_issue4566=False): vbox = QVBoxLayout() vbox.addWidget(WWLabel(message)) line = QLineEdit() line.setText(default) def f(text): self.next_button.setEnabled(test(text)) if warn_issue4566: text_whitespace_normalised = ' '.join(text.split()) warn_issue4566_label.setVisible( text != text_whitespace_normalised) line.textEdited.connect(f) vbox.addWidget(line) vbox.addWidget(WWLabel(warning)) warn_issue4566_label = WWLabel(MSG_PASSPHRASE_WARN_ISSUE4566) warn_issue4566_label.setVisible(False) vbox.addWidget(warn_issue4566_label) for preset in presets: button = QPushButton(preset[0]) button.clicked.connect( lambda __, text=preset[1]: line.setText(text)) button.setMinimumWidth(150) hbox = QHBoxLayout() hbox.addWidget(button, alignment=Qt.AlignCenter) vbox.addLayout(hbox) self.exec_layout(vbox, title, next_enabled=test(default)) return line.text() @wizard_dialog def show_xpub_dialog(self, xpub, run_next): msg = ' '.join([ _("Here is your master public key."), _("Please share it with your cosigners.") ]) vbox = QVBoxLayout() layout = SeedLayout( xpub, title=msg, icon=False, for_seed_words=False, config=self.config, ) vbox.addLayout(layout.layout()) self.exec_layout(vbox, _('Master Public Key')) return None def init_network(self, network: 'Network'): message = _("Electrum communicates with remote servers to get " "information about your transactions and addresses. The " "servers all fulfill the same purpose only differing in " "hardware. In most cases you simply want to let Electrum " "pick one at random. However if you prefer feel free to " "select a server manually.") choices = [_("Auto connect"), _("Select server manually")] title = _("How do you want to connect to a server? ") clayout = ChoicesLayout(message, choices) self.back_button.setText(_('Cancel')) self.exec_layout(clayout.layout(), title) r = clayout.selected_index() if r == 1: nlayout = NetworkChoiceLayout(network, self.config, wizard=True) if self.exec_layout(nlayout.layout()): nlayout.accept() self.config.set_key('auto_connect', network.auto_connect, True) else: network.auto_connect = True self.config.set_key('auto_connect', True, True) @wizard_dialog def multisig_dialog(self, run_next): cw = CosignWidget(2, 2) m_edit = QSlider(Qt.Horizontal, self) n_edit = QSlider(Qt.Horizontal, self) n_edit.setMinimum(2) n_edit.setMaximum(15) m_edit.setMinimum(1) m_edit.setMaximum(2) n_edit.setValue(2) m_edit.setValue(2) n_label = QLabel() m_label = QLabel() grid = QGridLayout() grid.addWidget(n_label, 0, 0) grid.addWidget(n_edit, 0, 1) grid.addWidget(m_label, 1, 0) grid.addWidget(m_edit, 1, 1) def on_m(m): m_label.setText(_('Require {0} signatures').format(m)) cw.set_m(m) backup_warning_label.setVisible(cw.m != cw.n) def on_n(n): n_label.setText(_('From {0} cosigners').format(n)) cw.set_n(n) m_edit.setMaximum(n) backup_warning_label.setVisible(cw.m != cw.n) n_edit.valueChanged.connect(on_n) m_edit.valueChanged.connect(on_m) vbox = QVBoxLayout() vbox.addWidget(cw) vbox.addWidget( WWLabel( _("Choose the number of signatures needed to unlock funds in your wallet:" ))) vbox.addLayout(grid) vbox.addSpacing(2 * char_width_in_lineedit()) backup_warning_label = WWLabel( _("Warning: to be able to restore a multisig wallet, " "you should include the master public key for each cosigner " "in all of your backups.")) vbox.addWidget(backup_warning_label) on_n(2) on_m(2) self.exec_layout(vbox, _("Multi-Signature Wallet")) m = int(m_edit.value()) n = int(n_edit.value()) return (m, n)
def send(params): url = self.formatUrl(endpoint, params) request = QNetworkRequest(url) headers['User-Agent'] = 'Divi QGIS Plugin/%s' % PLUGIN_VERSION QgsMessageLog.logMessage(str(headers), 'DIVI') for key, value in headers.items(): request.setRawHeader(key.encode('utf-8'), value.encode('utf-8')) if method == 'delete': reply = manager.sendCustomRequest(request, 'DELETE'.encode('utf-8'), data) else: if not data: reply = getattr(manager, method)(request) elif isinstance(data, QHttpMultiPart) == True: reply = getattr(manager, method)(request, data) elif isinstance(data, str) == True: reply = getattr(manager, method)(request, data.encode('utf-8')) loop = QEventLoop() reply.uploadProgress.connect(self.uploadProgress) reply.downloadProgress.connect(self.downloadProgress) reply.metaDataChanged.connect(self.metaDataChanged) #reply.error.connect(self._error) reply.finished.connect(loop.exit) self.abort_sig.connect( reply.abort ) loop.exec_() self.abort_sig.disconnect( reply.abort ) return reply