Example #1
0
 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
Example #2
0
 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())
Example #3
0
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))
Example #4
0
    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()
Example #5
0
 def _pop_args(self):
     weak_dict = self.saved_args
     self.saved_args = Weak.KeyDictionary()
     return (weak_dict, ), dict()
Example #6
0
    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()