def __init__(self, plugin, window): super().__init__() # top-level QObject, no parent() self.server = TimeoutServerProxy('http://%s:%d' % (HOST, PORT), allow_none=True, timeout=2.0) self.listener = Listener(self) self.plugin_ref = Weak.ref(plugin) self.window_ref = Weak.ref(window) self.cosigner_receive_signal.connect(self.on_receive)
def __init__(self): super().__init__( ) # Top-level window. Parent needs to hold a reference to us and clean us up appropriately. self.setWindowTitle('Oregano - ' + _('Payment Request')) self.label = '' self.amount = 0 self.setFocusPolicy(Qt.NoFocus) self.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding) main_box = QHBoxLayout(self) main_box.setContentsMargins(12, 12, 12, 12) self.qrw = QRCodeWidget() self.qrw.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.MinimumExpanding) main_box.addWidget(self.qrw, 2) vbox = QVBoxLayout() vbox.setContentsMargins(12, 12, 12, 12) main_box.addLayout(vbox, 2) main_box.addStretch(1) self.address_label = WWLabel() self.address_label.setTextInteractionFlags(Qt.TextSelectableByMouse) vbox.addWidget(self.address_label) self.msg_label = WWLabel() self.msg_label.setTextInteractionFlags(Qt.TextSelectableByMouse) vbox.addWidget(self.msg_label) self.amount_label = WWLabel() self.amount_label.setTextInteractionFlags(Qt.TextSelectableByMouse) vbox.addWidget(self.amount_label) self.op_return_label = WWLabel() self.op_return_label.setTextInteractionFlags(Qt.TextSelectableByMouse) vbox.addWidget(self.op_return_label) vbox.addStretch(2) copyBut = QPushButton(_("Copy QR Image")) saveBut = QPushButton(_("Save QR Image")) vbox.addLayout(Buttons(copyBut, saveBut)) weakSelf = Weak.ref( self ) # Qt & Python GC hygeine: don't hold references to self in non-method slots as it appears Qt+Python GC don't like this too much and may leak memory in that case. weakQ = Weak.ref(self.qrw) weakBut = Weak.ref(copyBut) copyBut.clicked.connect(lambda: copy_to_clipboard(weakQ(), weakBut())) saveBut.clicked.connect(lambda: save_to_file(weakQ(), weakSelf()))
def init_qt(self, gui): if self.initted: return # already initted self.print_error("Initializing...") for window in gui.windows: self.on_new_window(window) Plugin.Instance_ref = Weak.ref(self) self.initted = True
def settings_widget(self, window): while window and window_parent(window) and not isinstance(window_parent(window), ElectrumWindow): # MacOS fixup -- find window.parent() because we can end up with window.parent() not an ElectrumWindow window = window_parent(window) windowRef = Weak.ref(window) return EnterButton(_('Settings'), partial(self.settings_dialog, windowRef))
def __init__(self, state): super().__init__() self.daemon = True self.state_ref = Weak.ref(state) self.received = set() self.keyhashes = [] self.timeoutQ = queue.Queue( ) # this queue's sole purpose is to provide an interruptible sleep
def transaction_dialog(self, d): window, state = self._find_window_and_state_for_wallet(d.wallet) if window and state: d.cosigner_send_button = b = QPushButton(_("Send to cosigner")) b.wallet_ref = Weak.ref(window.wallet) b.clicked.connect(lambda: Plugin.do_send_static(d)) d.buttons.insert(0, b) self.transaction_dialog_update(d)
def settings_dialog(self, windowRef): window = windowRef() # NB: window is the internal plugins dialog and not the wallet window if not window or not isinstance(window_parent(window), ElectrumWindow): return wallet = window_parent(window).wallet d = WindowModalDialog(window.top_level_window(), _("Label Settings")) d.ok_button = OkButton(d) dlgRef = Weak.ref(d) if wallet in self.wallets: class MySigs(QObject): ok_button_disable_sig = pyqtSignal(bool) d.sigs = MySigs(d) d.sigs.ok_button_disable_sig.connect(d.ok_button.setDisabled) # disable ok button while the TaskThread runs .. hbox = QHBoxLayout() hbox.addWidget(QLabel(_("LabelSync options:"))) upload = ThreadedButton(_("Force upload"), partial(Weak(self.do_force_upload), wallet, dlgRef), partial(Weak(self.done_processing), dlgRef), partial(Weak(self.error_processing), dlgRef)) download = ThreadedButton(_("Force download"), partial(Weak(self.do_force_download), wallet, dlgRef), partial(Weak(self.done_processing), dlgRef), partial(Weak(self.error_processing), dlgRef)) d.thread_buts = (upload, download) d.finished.connect(partial(Weak(self.on_dlg_finished), dlgRef)) vbox = QVBoxLayout() vbox.addWidget(upload) vbox.addWidget(download) hbox.addLayout(vbox) vbox = QVBoxLayout(d) vbox.addLayout(hbox) else: vbox = QVBoxLayout(d) if wallet.network: # has network, so the fact that the wallet isn't in the list means it's incompatible l = QLabel('<b>' + _("LabelSync not supported for this wallet type") + '</b>') l.setAlignment(Qt.AlignCenter) vbox.addWidget(l) l = QLabel(_("(Only deterministic wallets are supported)")) l.setAlignment(Qt.AlignCenter) vbox.addWidget(l) else: # Does not have network, so we won't speak of incompatibility, but instead remind user offline mode means OFFLINE! ;) l = QLabel(_("You are using Oregano in offline mode; restart Oregano if you want to get connected")) l.setWordWrap(True) vbox.addWidget(l) vbox.addSpacing(20) vbox.addLayout(Buttons(d.ok_button)) return bool(d.exec_())
def __init__(self, parent): super().__init__(parent, self.create_menu, [], 3, deferred_updates=True) self.refresh_headers() self.setColumnHidden(1, True) # force attributes to always be defined, even if None, at construction. self.wallet = self.parent.wallet self.cleaned_up = False self.monospaceFont = QFont(MONOSPACE_FONT) self.withdrawalBrush = QBrush(QColor("#BC1E1E")) self.invoiceIcon = QIcon(":icons/seal") self._item_cache = Weak.ValueDictionary() self.itemChanged.connect(self.item_changed) self.has_unknown_balances = False
def initiate_fetch_input_data(self, force): weakSelfRef = Weak.ref(self) def dl_prog(pct): slf = weakSelfRef() if slf: slf._dl_pct = pct slf.throttled_update_sig.emit() def dl_done(): slf = weakSelfRef() if slf: slf._dl_pct = None slf.throttled_update_sig.emit() slf.dl_done_sig.emit() dl_retries = 0 def dl_done_mainthread(): nonlocal dl_retries slf = weakSelfRef() if slf: if slf._closed: return dl_retries += 1 fee = slf.try_calculate_fee() if fee is None and dl_retries < 2: if not self.is_fetch_input_data(): slf.print_error("input fetch incomplete; network use is disabled in GUI") return # retry at most once -- in case a slow server scrwed us up slf.print_error("input fetch appears incomplete; retrying download once ...") slf.tx.fetch_input_data(self.wallet, done_callback=dl_done, prog_callback=dl_prog, force=True, use_network=self.is_fetch_input_data()) # in this case we reallly do force elif fee is not None: slf.print_error("input fetch success") else: slf.print_error("input fetch failed") try: self.dl_done_sig.disconnect() # disconnect previous except TypeError: pass self.dl_done_sig.connect(dl_done_mainthread, Qt.QueuedConnection) self.tx.fetch_input_data(self.wallet, done_callback=dl_done, prog_callback=dl_prog, force=force, use_network=self.is_fetch_input_data())
class Plugin(BasePlugin): Instance_ref = Weak.ref(_Dead( )) # Make sure Instance_ref is always defined, defaults to dead object def __init__(self, parent, config, name): BasePlugin.__init__(self, parent, config, name) self.windows = [] self.initted = False @hook def init_qt(self, gui): if self.initted: return # already initted self.print_error("Initializing...") for window in gui.windows: self.on_new_window(window) Plugin.Instance_ref = Weak.ref(self) self.initted = True @hook def on_new_window(self, window): try: wallet = window.wallet except AttributeError: # this can happen if wallet is not started up properly self.print_error( "WARNING: Window {} lacks a wallet -- startup race condition likely. FIXME!" .format(window.diagnostic_name())) return if isinstance(wallet, Multisig_Wallet): window.cosigner_pool_state = state = State(self, window) self.windows.append(window) self.update(window) # un-gray-out buttons for tx dialogs left around related to this window for b in Plugin.get_all_cosigner_buttons(): if b.wallet_ref() == wallet: b.setEnabled(True) @hook def on_close_window(self, window): if window in self.windows: state = getattr(window, 'cosigner_pool_state', None) if state: if state.listener: self.print_error("shutting down listener for", window.diagnostic_name()) state.listener.stop_join() state.deleteLater() delattr(window, 'cosigner_pool_state') self.print_error("unregistered for window", window.diagnostic_name()) self.windows.remove(window) # gray out buttons for tx dialogs left around related to this window for b in Plugin.get_all_cosigner_buttons(): if b.wallet_ref() == window.wallet: b.setEnabled(False) @staticmethod def get_all_cosigner_buttons(): ret = [] app = QApplication.instance() for w in app.topLevelWidgets(): if isinstance(w, TxDialog): but = getattr(w, 'cosigner_send_button', None) if but: ret.append(but) return ret def is_available(self): return True def on_close(self): for w in self.windows.copy(): self.on_close_window(w) self.windows = [] self.initted = False super().on_close() def update(self, window): wallet = window.wallet state = window.cosigner_pool_state if not state: self.print_error("No cosigner pool state object for window", window.diagnostic_name()) return listener = state.listener state.keys = [] state.cosigner_list = [] for key, keystore in wallet.keystores.items(): xpub = keystore.get_master_public_key() K = bitcoin.deserialize_xpub(xpub)[-1] _hash = bh2u(bitcoin.Hash(K)) if not keystore.is_watching_only(): state.keys.append((key, _hash)) else: state.cosigner_list.append((xpub, K, _hash)) listener.set_keyhashes([t[1] for t in state.keys]) if not listener.is_running(): self.print_error("Starting listener for", window.diagnostic_name()) listener.start() @hook def transaction_dialog(self, d): window, state = self._find_window_and_state_for_wallet(d.wallet) if window and state: d.cosigner_send_button = b = QPushButton(_("Send to cosigner")) b.wallet_ref = Weak.ref(window.wallet) b.clicked.connect(lambda: Plugin.do_send_static(d)) d.buttons.insert(0, b) self.transaction_dialog_update(d) @hook def transaction_dialog_update(self, d): window, state = self._find_window_and_state_for_wallet(d.wallet) but = getattr(d, 'cosigner_send_button', None) if not but or not window or not state or d.tx.is_complete( ) or d.wallet.can_sign(d.tx): but and but.hide() return for xpub, K, _hash in state.cosigner_list: if self.cosigner_can_sign(d.tx, xpub): but and but.show() break else: but and but.hide() def _find_window_and_state_for_wallet(self, wallet): for window in self.windows: if window.wallet == wallet: return window, window.cosigner_pool_state return None, None def cosigner_can_sign(self, tx, cosigner_xpub): from oregano.keystore import is_xpubkey, parse_xpubkey xpub_set = set([]) for txin in tx.inputs(): for x_pubkey in txin['x_pubkeys']: if is_xpubkey(x_pubkey): xpub, s = parse_xpubkey(x_pubkey) xpub_set.add(xpub) return cosigner_xpub in xpub_set @staticmethod def do_send_static(d): ''' Decouples button slot from running instance in case user stops/restarts the plugin while TxDialogs are up. ''' plugin = Plugin.Instance_ref() if plugin: plugin.do_send(d) else: print_error("[cosigner_pool] No plugin.") def do_send(self, d): tx = d.tx window, state = self._find_window_and_state_for_wallet(d.wallet) if not tx or not window or not state: self.print_error("Missing tx or window or state") return for xpub, K, _hash in state.cosigner_list: if not self.cosigner_can_sign(tx, xpub): continue message = bitcoin.encrypt_message(bfh(tx.raw), bh2u(K)).decode('ascii') try: state.server.put(_hash, message) except Exception as e: traceback.print_exc(file=sys.stdout) window.show_error( _("Failed to send transaction to cosigning pool.")) return d.show_message( _("Your transaction was sent to the cosigning pool.") + '\n' + _("Open your cosigner wallet to retrieve it.")) def on_receive(self, window, keyhash, message): self.print_error("signal arrived for", keyhash, "@", window.diagnostic_name()) state = getattr(window, 'cosigner_pool_state', None) if not state: self.print_error("Error: state object not found") return keys = state.keys for key, _hash in keys: if _hash == keyhash: break else: self.print_error("keyhash not found") return wallet = window.wallet if isinstance(wallet.keystore, keystore.Hardware_KeyStore): window.show_warning( _('An encrypted transaction was retrieved from cosigning pool.' ) + '\n' + _('However, hardware wallets do not support message decryption, ' 'which makes them not compatible with the current design of cosigner pool.' )) return password = None if wallet.has_password(): password = window.password_dialog( _('An encrypted transaction was retrieved from cosigning pool.' ) + '\n' + _('Please enter your password to decrypt it.')) if not password: return else: details = (_( "If you choose 'Yes', it will be decrypted and a transaction window will be shown, giving you the opportunity to sign the transaction." ) + "\n\n" + _( "If you choose 'No', you will be asked again later (the next time this wallet window is opened)." )) ret = window.msg_box( icon=QMessageBox.Question, parent=None, title=_("Cosigner Pool"), buttons=QMessageBox.Yes | QMessageBox.No, text= _("An encrypted transaction was retrieved from cosigning pool." ) + '\n' + _("Do you want to open it now?"), detail_text=details) if ret != QMessageBox.Yes: return err, badpass = "******", False try: xprv = wallet.keystore.get_master_private_key(password) except InvalidPassword as e: err, badpass = str(e), True xprv = None if not xprv: window.show_error(err) if badpass: self.on_receive(window, keyhash, message) # try again return try: k = bh2u(bitcoin.deserialize_xprv(xprv)[-1]) EC = bitcoin.EC_KEY(bfh(k)) message = bh2u(EC.decrypt_message(message)) except Exception as e: traceback.print_exc(file=sys.stdout) window.show_error(repr(e)) return state.listener.clear(keyhash) tx = transaction.Transaction(message) show_transaction(tx, window, prompt_if_unsaved=True)
def __init__(self, tx, parent, desc, prompt_if_unsaved): '''Transactions in the wallet will show their description. Pass desc to give a description for txs not yet in the wallet. ''' # We want to be a top-level window QDialog.__init__(self, parent=None) # Take a copy; it might get updated in the main window by # e.g. the FX plugin. If this happens during or after a long # sign operation the signatures are lost. self.tx = copy.deepcopy(tx) self.tx.deserialize() self.main_window = parent self.wallet = parent.wallet self.prompt_if_unsaved = prompt_if_unsaved self.saved = False self.desc = desc self.cashaddr_signal_slots = [] self._dl_pct = None self._closed = False self.tx_hash = self.tx.txid_fast() if self.tx.raw and self.tx.is_complete() else None self.tx_height = self.wallet.get_tx_height(self.tx_hash)[0] or None self.block_hash = None Weak.finalization_print_error(self) # track object lifecycle self.setMinimumWidth(750) self.setWindowTitle(_("Transaction")) vbox = QVBoxLayout() self.setLayout(vbox) self.tx_hash_e = ButtonsLineEdit() l = QLabel(_("&Transaction ID:")) l.setBuddy(self.tx_hash_e) vbox.addWidget(l) self.tx_hash_e.addCopyButton() weakSelfRef = Weak.ref(self) qr_show = lambda: weakSelfRef() and weakSelfRef().main_window.show_qrcode(str(weakSelfRef().tx_hash_e.text()), _("Transaction ID"), parent=weakSelfRef()) icon = ":icons/qrcode_white.svg" if ColorScheme.dark_scheme else ":icons/qrcode.svg" self.tx_hash_e.addButton(icon, qr_show, _("Show as QR code")) self.tx_hash_e.setReadOnly(True) vbox.addWidget(self.tx_hash_e) self.tx_desc = QLabel() vbox.addWidget(self.tx_desc) self.status_label = QLabel() vbox.addWidget(self.status_label) self.date_label = QLabel() vbox.addWidget(self.date_label) self.amount_label = QLabel() vbox.addWidget(self.amount_label) self.size_label = QLabel() vbox.addWidget(self.size_label) self.fee_label = QLabel() vbox.addWidget(self.fee_label) for l in (self.tx_desc, self.status_label, self.date_label, self.amount_label, self.size_label, self.fee_label): # make these labels selectable by mouse in case user wants to copy-paste things in tx dialog l.setTextInteractionFlags(l.textInteractionFlags() | Qt.TextSelectableByMouse) def open_be_url(link): if link: try: kind, thing = link.split(':') url = web.BE_URL(self.main_window.config, kind, thing) except: url = None if url: webopen( url ) else: self.show_error(_('Unable to open in block explorer. Please be sure your block explorer is configured correctly in preferences.')) self.status_label.linkActivated.connect(open_be_url) self.add_io(vbox) self.freeze_button = b = QPushButton(self._make_freeze_button_text()) b.setToolTip(_("Lock/unlock the coin(s) being spent in this transaction.\n\n" "Use this facility if you wish to broadcast this transaction later,\n" "in order to prevent its inputs from being accidentally spent.")) b.clicked.connect(self.do_freeze_unfreeze) self.sign_button = b = QPushButton(_("&Sign")) b.clicked.connect(self.sign) b.setToolTip(_("Sign the transaction")) self.broadcast_button = b = QPushButton(_("&Broadcast")) b.clicked.connect(self.do_broadcast) b.setToolTip(_("Submit the transaction to the blockchain")) self.last_broadcast_time = 0 self.save_button = b = QPushButton(_("S&ave")) b.setToolTip(_("Save the transaction to a file")) b.clicked.connect(self.save) self.cancel_button = b = CloseButton(self) self.qr_button = b = QPushButton() b.setToolTip(_("Show transaction QR code")) b.setIcon(QIcon(icon)) b.clicked.connect(self.show_qr) b.setShortcut(QKeySequence(Qt.ALT + Qt.Key_Q)) self.copy_button = b = CopyButton(lambda: str(weakSelfRef() and weakSelfRef().tx), callback=lambda: weakSelfRef() and weakSelfRef().show_message( _("Transaction raw hex copied to clipboard."))) b.setToolTip(_("Copy transaction raw hex to the clipboard")) # Action buttons self.buttons = [self.freeze_button, self.sign_button, self.broadcast_button, self.cancel_button] # Transaction sharing buttons self.sharing_buttons = [self.copy_button, self.qr_button, self.save_button] run_hook('transaction_dialog', self) hbox = QHBoxLayout() hbox.addLayout(Buttons(*self.sharing_buttons)) hbox.addStretch(1) hbox.addLayout(Buttons(*self.buttons)) vbox.addLayout(hbox) if self.tx_height: # this avoids downloading the block_height info if we already have it. self.tx.ephemeral['block_height'] = self.tx_height self.throttled_update_sig.connect(self.throttled_update, Qt.QueuedConnection) self.initiate_fetch_input_data(True) self.update() # connect slots so we update in realtime as blocks come in, etc parent.history_updated_signal.connect(self.update_tx_if_in_wallet) parent.labels_updated_signal.connect(self.update_tx_if_in_wallet) parent.network_signal.connect(self.got_verified_tx)
class Plugin(LabelsPlugin): def __init__(self, *args): LabelsPlugin.__init__(self, *args) self.obj = LabelsSignalObject() self.wallet_windows = {} self.initted = False def requires_settings(self): return True def settings_widget(self, window): while window and window_parent(window) and not isinstance(window_parent(window), ElectrumWindow): # MacOS fixup -- find window.parent() because we can end up with window.parent() not an ElectrumWindow window = window_parent(window) windowRef = Weak.ref(window) return EnterButton(_('Settings'), partial(self.settings_dialog, windowRef)) def settings_dialog(self, windowRef): window = windowRef() # NB: window is the internal plugins dialog and not the wallet window if not window or not isinstance(window_parent(window), ElectrumWindow): return wallet = window_parent(window).wallet d = WindowModalDialog(window.top_level_window(), _("Label Settings")) d.ok_button = OkButton(d) dlgRef = Weak.ref(d) if wallet in self.wallets: class MySigs(QObject): ok_button_disable_sig = pyqtSignal(bool) d.sigs = MySigs(d) d.sigs.ok_button_disable_sig.connect(d.ok_button.setDisabled) # disable ok button while the TaskThread runs .. hbox = QHBoxLayout() hbox.addWidget(QLabel(_("LabelSync options:"))) upload = ThreadedButton(_("Force upload"), partial(Weak(self.do_force_upload), wallet, dlgRef), partial(Weak(self.done_processing), dlgRef), partial(Weak(self.error_processing), dlgRef)) download = ThreadedButton(_("Force download"), partial(Weak(self.do_force_download), wallet, dlgRef), partial(Weak(self.done_processing), dlgRef), partial(Weak(self.error_processing), dlgRef)) d.thread_buts = (upload, download) d.finished.connect(partial(Weak(self.on_dlg_finished), dlgRef)) vbox = QVBoxLayout() vbox.addWidget(upload) vbox.addWidget(download) hbox.addLayout(vbox) vbox = QVBoxLayout(d) vbox.addLayout(hbox) else: vbox = QVBoxLayout(d) if wallet.network: # has network, so the fact that the wallet isn't in the list means it's incompatible l = QLabel('<b>' + _("LabelSync not supported for this wallet type") + '</b>') l.setAlignment(Qt.AlignCenter) vbox.addWidget(l) l = QLabel(_("(Only deterministic wallets are supported)")) l.setAlignment(Qt.AlignCenter) vbox.addWidget(l) else: # Does not have network, so we won't speak of incompatibility, but instead remind user offline mode means OFFLINE! ;) l = QLabel(_("You are using Oregano in offline mode; restart Oregano if you want to get connected")) l.setWordWrap(True) vbox.addWidget(l) vbox.addSpacing(20) vbox.addLayout(Buttons(d.ok_button)) return bool(d.exec_()) def on_dlg_finished(self, dlgRef, result_code): ''' Wait for any threaded buttons that may be still extant so we don't get a crash ''' #self.print_error("Dialog finished with code", result_code) dlg = dlgRef() if dlg: upload, download = dlg.thread_buts if upload.thread and upload.thread.isRunning(): upload.thread.stop(); upload.thread.wait() if download.thread and download.thread.isRunning(): download.thread.stop(); download.thread.wait() def do_force_upload(self, wallet, dlgRef): # this runs in a NON-GUI thread dlg = dlgRef() if dlg: dlg.sigs.ok_button_disable_sig.emit(True) # block window closing prematurely which can cause a temporary hang until thread completes self.push_thread(wallet) def do_force_download(self, wallet, dlgRef): # this runs in a NON-GUI thread dlg = dlgRef() if dlg: dlg.sigs.ok_button_disable_sig.emit(True) # block window closing prematurely which can cause a temporary hang until thread completes self.pull_thread(wallet, True) def done_processing(self, dlgRef, result): # this runs in the GUI thread dlg = dlgRef() if dlg: dlg.ok_button.setEnabled(True) self._ok_synched(dlg) def _ok_synched(self, window): if window.isVisible(): window.show_message(_("Your labels have been synchronised.")) def error_processing(self, dlgRef, exc_info): dlg = dlgRef() if dlg: dlg.ok_button.setEnabled(True) self._notok_synch(dlg, exc_info) _warn_dlg_flg = Weak.KeyDictionary() def _notok_synch(self, window, exc_info): # Runs in main thread cls = __class__ if window.isVisible() and not cls._warn_dlg_flg.get(window, False): # Guard against duplicate error dialogs (without this we may get error window spam when importing labels) cls._warn_dlg_flg[window] = True window.show_warning(_("LabelSync error:") + "\n\n" + str(exc_info[1]), rich_text=False) cls._warn_dlg_flg.pop(window, None) def on_request_exception(self, wallet, exc_info): # not main thread self.obj.request_exception_signal.emit(wallet, exc_info) def request_exception_slot(self, wallet, exc_info): # main thread window = self.wallet_windows.get(wallet, None) if window: self._notok_synch(window, exc_info) def start_wallet(self, wallet, window=None): ret = super().start_wallet(wallet) if ret and window: self.wallet_windows[wallet] = window return ret def stop_wallet(self, wallet): ret = super().stop_wallet(wallet) window = self.wallet_windows.pop(wallet, None) return ret def on_pulled(self, wallet): # not main thread super().on_pulled(wallet) # super just logs to print_error self.obj.labels_changed_signal.emit(wallet) def on_labels_changed(self, wallet): # main thread window = self.wallet_windows.get(wallet, None) if window: #self.print_error("On labels changed", wallet.basename()) window.update_labels() def on_wallet_not_synched(self, wallet): # not main thread self.obj.wallet_not_synched_signal.emit(wallet) def wallet_not_synched_slot(self, wallet): # main thread window = self.wallet_windows.get(wallet, None) if window: if window.question(_("LabelSync detected that this wallet is not synched with the label server.") + "\n\n" + _("Synchronize now?")): WaitingDialog(window, _("Synchronizing..."), partial(self.pull_thread, wallet, True), lambda *args: self._ok_synched(window), lambda exc: self._notok_synch(window, exc)) def on_close(self): if not self.initted: return try: self.obj.labels_changed_signal.disconnect(self.on_labels_changed) except TypeError: pass # not connected try: self.obj.wallet_not_synched_signal.disconnect(self.wallet_not_synched_slot) except TypeError: pass # not connected try: self.obj.request_exception_signal.disconnect(self.request_exception_slot) except TypeError: pass # not connected super().on_close() assert 0==len(self.wallet_windows), "LabelSync still had extant wallet_windows!" self.initted = False @hook def on_new_window(self, window): return self.start_wallet(window.wallet, window) @hook def on_close_window(self, window): return self.stop_wallet(window.wallet) @hook def init_qt(self, gui): if self.initted: return self.on_init() # connect signals. this needs to happen first as below on_new_window depends on these being active self.obj.labels_changed_signal.connect(self.on_labels_changed) self.obj.wallet_not_synched_signal.connect(self.wallet_not_synched_slot) self.obj.request_exception_signal.connect(self.request_exception_slot) ct, ct2 = 0, 0 for window in gui.windows: if self.on_new_window(window): ct2 += 1 ct += 1 self.initted = True self.print_error("Initialized (had {} extant windows, added {}).".format(ct,ct2))