def __init__(self, rate, obj, func): # note: obj here is really the __class__ of the obj because we prepended the class in our custom invoke() above. super().__init__(rate, obj, func) self.func_target = func self.func = self._call_func_for_all self.saved_args = Weak.KeyDictionary( ) # we don't use a simple arg tuple, but instead an instance -> args,kwargs dictionary to store collated calls, per instance collated
def __init__(self, config, daemon, plugins): super(__class__, self).__init__() # QObject init assert __class__.instance is None, "ElectrumGui is a singleton, yet an instance appears to already exist! FIXME!" __class__.instance = self set_language(config.get('language')) # Uncomment this call to verify objects are being properly # GC-ed when windows are closed #if daemon.network: # from electroncash.util import DebugMem # from electroncash.wallet import Abstract_Wallet # from electroncash.verifier import SPV # from electroncash.synchronizer import Synchronizer # daemon.network.add_jobs([DebugMem([Abstract_Wallet, SPV, Synchronizer, # ElectrumWindow], interval=5)]) QCoreApplication.setAttribute(Qt.AA_X11InitThreads) if hasattr(Qt, "AA_ShareOpenGLContexts"): QCoreApplication.setAttribute(Qt.AA_ShareOpenGLContexts) if hasattr(QGuiApplication, 'setDesktopFileName'): QGuiApplication.setDesktopFileName('electron-cash.desktop') self.config = config self.daemon = daemon self.plugins = plugins self.windows = [] self.app = QApplication(sys.argv) self._set_icon() self.app.installEventFilter(self) self.timer = QTimer(self); self.timer.setSingleShot(False); self.timer.setInterval(500) #msec self.gc_timer = QTimer(self); self.gc_timer.setSingleShot(True); self.gc_timer.timeout.connect(ElectrumGui.gc); self.gc_timer.setInterval(500) #msec self.nd = None # Dark Theme -- ideally set this before any widgets are created. self.set_dark_theme_if_needed() # / # Wallet Password Cache # wallet -> (password, QTimer) map for some plugins (like CashShuffle) # that need wallet passwords to operate, and we don't want to prompt # for pw twice right after the InstallWizard runs (see #106). # Entries in this map are deleted after 10 seconds by the QTimer (which # also deletes itself) self._wallet_password_cache = Weak.KeyDictionary() # / self.update_checker = UpdateChecker() self.update_checker_timer = QTimer(self); self.update_checker_timer.timeout.connect(self.on_auto_update_timeout); self.update_checker_timer.setSingleShot(False) self.update_checker.got_new_version.connect(lambda x: self.show_update_checker(parent=None, skip_check=True)) # init tray self.dark_icon = self.config.get("dark_icon", False) self.tray = QSystemTrayIcon(self.tray_icon(), self) self.tray.setToolTip('Electron Cash') self.tray.activated.connect(self.tray_activated) self.build_tray_menu() self.tray.show() self.new_window_signal.connect(self.start_new_window) if self.has_auto_update_check(): self._start_auto_update_timer(first_run = True) run_hook('init_qt', self) # We did this once already in the set_dark_theme call, but we do this # again here just in case some plugin modified the color scheme. ColorScheme.update_from_widget(QWidget())
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 = QtWidgets.QHBoxLayout() hbox.addWidget(QtWidgets.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 = QtWidgets.QVBoxLayout() vbox.addWidget(upload) vbox.addWidget(download) hbox.addLayout(vbox) vbox = QtWidgets.QVBoxLayout(d) vbox.addLayout(hbox) else: vbox = QtWidgets.QVBoxLayout(d) if wallet.network: # has network, so the fact that the wallet isn't in the list means it's incompatible l = QtWidgets.QLabel('<b>' + _("LabelSync not supported for this wallet type") + '</b>') l.setAlignment(Qt.AlignCenter) vbox.addWidget(l) l = QtWidgets.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 = QtWidgets.QLabel(_(f"You are using {PROJECT_NAME} in offline mode;" f" restart Electron Cash if you want to get " f"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))
def __init__(self, config, daemon, plugins): super(__class__, self).__init__() # QtCore.QObject init assert __class__.instance is None, "ElectrumGui is a singleton, yet an instance appears to already exist! FIXME!" __class__.instance = self self.config = config self.daemon = daemon self.plugins = plugins self.windows = [] self._setup_do_in_main_thread_handler() # Uncomment this call to verify objects are being properly # GC-ed when windows are closed #if daemon.network: # from electroncash.util import DebugMem # from electroncash.wallet import Abstract_Wallet # from electroncash.verifier import SPV # from electroncash.synchronizer import Synchronizer # daemon.network.add_jobs([DebugMem([Abstract_Wallet, SPV, Synchronizer, # ElectrumWindow], interval=5)]) self.app = QtWidgets.QApplication.instance() self._load_fonts( ) # this needs to be done very early, before the font engine loads fonts.. out of paranoia self._exit_if_required_pyqt_is_missing( ) # This may immediately exit the app if missing required PyQt5 modules, so it should also be done early. self.new_version_available = None self._set_icon() self.app.installEventFilter(self) self.timer = QtCore.QTimer(self) self.timer.setSingleShot(False) self.timer.setInterval(500) #msec self.gc_timer = QtCore.QTimer(self) self.gc_timer.setSingleShot(True) self.gc_timer.timeout.connect(ElectrumGui.gc) self.gc_timer.setInterval(500) #msec self.nd = None self._last_active_window = None # we remember the last activated ElectrumWindow as a Weak.ref Address.set_address_format(self.get_config_addr_format()) # Dark Theme -- ideally set this before any widgets are created. self.set_dark_theme_if_needed() # / # Wallet Password Cache # wallet -> (password, QTimer) map for some plugins (like CashShuffle) # that need wallet passwords to operate, and we don't want to prompt # for pw twice right after the InstallWizard runs (see #106). # Entries in this map are deleted after 10 seconds by the QTimer (which # also deletes itself) self._wallet_password_cache = Weak.KeyDictionary() # / self.update_checker = UpdateChecker() self.update_checker_timer = QtCore.QTimer(self) self.update_checker_timer.timeout.connect(self.on_auto_update_timeout) self.update_checker_timer.setSingleShot(False) self.update_checker.got_new_version.connect(self.on_new_version) # init tray self.dark_icon = self.config.get("dark_icon", False) self.tray = QtWidgets.QSystemTrayIcon(self.tray_icon(), self) self.tray.setToolTip(f'{PROJECT_NAME}') self.tray.activated.connect(self.tray_activated) self.build_tray_menu() self.tray.show() self.new_window_signal.connect(self.start_new_window) if self.has_auto_update_check(): self._start_auto_update_timer(first_run=True) self.app.focusChanged.connect( self.on_focus_change) # track last window the user interacted with self.shutdown_signal.connect(self.close, QtCore.Qt.QueuedConnection) run_hook('init_qt', self) # We did this once already in the set_dark_theme call, but we do this # again here just in case some plugin modified the color scheme. ColorScheme.update_from_widget(QtWidgets.QWidget()) self._check_and_warn_qt_version()
def _pop_args(self): weak_dict = self.saved_args self.saved_args = Weak.KeyDictionary() return (weak_dict, ), dict()
def __init__(self, config, daemon, plugins): super(__class__, self).__init__() # QObject init assert __class__.instance is None, "ElectrumGui is a singleton, yet an instance appears to already exist! FIXME!" __class__.instance = self set_language(config.get('language')) self.config = config self.daemon = daemon self.plugins = plugins self.windows = [] if self.windows_qt_use_freetype: # Use FreeType for font rendering on Windows. This fixes rendering # of the Schnorr sigil and allows us to load the Noto Color Emoji # font if needed. os.environ['QT_QPA_PLATFORM'] = 'windows:fontengine=freetype' # Uncomment this call to verify objects are being properly # GC-ed when windows are closed #if daemon.network: # from electroncash.util import DebugMem # from electroncash.wallet import Abstract_Wallet # from electroncash.verifier import SPV # from electroncash.synchronizer import Synchronizer # daemon.network.add_jobs([DebugMem([Abstract_Wallet, SPV, Synchronizer, # ElectrumWindow], interval=5)]) QCoreApplication.setAttribute(Qt.AA_X11InitThreads) if hasattr(Qt, "AA_ShareOpenGLContexts"): QCoreApplication.setAttribute(Qt.AA_ShareOpenGLContexts) if sys.platform not in ('darwin', ) and hasattr( Qt, "AA_EnableHighDpiScaling"): # The below only applies to non-macOS. On macOS this setting is # never used (because it is implicitly auto-negotiated by the OS # in a differernt way). # # qt_disable_highdpi will be set to None by default, or True if # specified on command-line. The command-line override is intended # to supporess high-dpi mode just for this run for testing. # # The more permanent setting is qt_enable_highdpi which is the GUI # preferences option, so we don't enable highdpi if it's explicitly # set to False in the GUI. # # The default on Linux, Windows, etc is to enable high dpi disable_scaling = config.get('qt_disable_highdpi', False) enable_scaling = config.get('qt_enable_highdpi', True) if not disable_scaling and enable_scaling: QCoreApplication.setAttribute(Qt.AA_EnableHighDpiScaling) if hasattr(Qt, "AA_UseHighDpiPixmaps"): QCoreApplication.setAttribute(Qt.AA_UseHighDpiPixmaps) if hasattr(QGuiApplication, 'setDesktopFileName'): QGuiApplication.setDesktopFileName('electron-cash.desktop') self.app = QApplication(sys.argv) self._load_fonts( ) # this needs to be done very early, before the font engine loads fonts.. out of paranoia self._exit_if_required_pyqt_is_missing( ) # This may immediately exit the app if missing required PyQt5 modules, so it should also be done early. self.new_version_available = None self._set_icon() self.app.installEventFilter(self) self.timer = QTimer(self) self.timer.setSingleShot(False) self.timer.setInterval(500) #msec self.gc_timer = QTimer(self) self.gc_timer.setSingleShot(True) self.gc_timer.timeout.connect(ElectrumGui.gc) self.gc_timer.setInterval(500) #msec self.nd = None self._last_active_window = None # we remember the last activated ElectrumWindow as a Weak.ref Address.show_cashaddr(self.is_cashaddr()) # Dark Theme -- ideally set this before any widgets are created. self.set_dark_theme_if_needed() # / # Wallet Password Cache # wallet -> (password, QTimer) map for some plugins (like CashShuffle) # that need wallet passwords to operate, and we don't want to prompt # for pw twice right after the InstallWizard runs (see #106). # Entries in this map are deleted after 10 seconds by the QTimer (which # also deletes itself) self._wallet_password_cache = Weak.KeyDictionary() # / self.update_checker = UpdateChecker() self.update_checker_timer = QTimer(self) self.update_checker_timer.timeout.connect(self.on_auto_update_timeout) self.update_checker_timer.setSingleShot(False) self.update_checker.got_new_version.connect(self.on_new_version) # init tray self.dark_icon = self.config.get("dark_icon", False) self.tray = QSystemTrayIcon(self.tray_icon(), self) self.tray.setToolTip('Electron Cash') self.tray.activated.connect(self.tray_activated) self.build_tray_menu() self.tray.show() self.new_window_signal.connect(self.start_new_window) if self.has_auto_update_check(): self._start_auto_update_timer(first_run=True) self.app.focusChanged.connect( self.on_focus_change) # track last window the user interacted with run_hook('init_qt', self) # We did this once already in the set_dark_theme call, but we do this # again here just in case some plugin modified the color scheme. ColorScheme.update_from_widget(QWidget()) self._check_and_warn_qt_version()