def on_view_blacklist(self, ignored): ''' The 'view ban list...' link leads to a modal dialog box where the user has the option to clear the entire blacklist. Build that dialog here. ''' bl = sorted(self.network.blacklisted_servers) parent = self.parent() if not bl: parent.show_error(_("Server ban list is empty!")) return d = WindowModalDialog(parent.top_level_window(), _("Banned Servers")) vbox = QVBoxLayout(d) vbox.addWidget(QLabel(_("Banned Servers") + " ({})".format(len(bl)))) tree = QTreeWidget() tree.setHeaderLabels([_('Host'), _('Port')]) for s in bl: host, port, protocol = deserialize_server(s) item = QTreeWidgetItem([host, str(port)]) item.setFlags(Qt.ItemIsEnabled) tree.addTopLevelItem(item) tree.setIndentation(3) h = tree.header() h.setStretchLastSection(False) h.setSectionResizeMode(0, QHeaderView.Stretch) h.setSectionResizeMode(1, QHeaderView.ResizeToContents) vbox.addWidget(tree) clear_but = QPushButton(_("Clear ban list")) weakSelf = Weak.ref(self) weakD = Weak.ref(d) clear_but.clicked.connect(lambda: weakSelf() and weakSelf(). on_clear_blacklist() and weakD().reject()) vbox.addLayout(Buttons(clear_but, CloseButton(d))) d.exec_()
def __init__(self, parent, on_error=None, *, name=None): QThread.__init__(self, parent) if name is not None: self.setObjectName(name) self.on_error = on_error self.tasks = queue.Queue() self.doneSig.connect(self.on_done) Weak.finalization_print_error(self) # track task thread lifecycle in debug log self.start()
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 _cache_password(self, wallet, password): self._expire_cached_password(wallet) if password is None: return timer = QTimer() # NB a top-level parentless QObject will get delete by Python when its Python refct goes to 0, which is what we want here. Future programmers: Do not give this timer a parent! self._wallet_password_cache[wallet] = (password, timer) weakWallet = Weak.ref(wallet) weakSelf = Weak.ref(self) def timeout(): slf = weakSelf() slf and slf._expire_cached_password(weakWallet) timer.setSingleShot(True); timer.timeout.connect(timeout); timer.start(10000) # 10 sec
def __init__(self): super().__init__( ) # Top-level window. Parent needs to hold a reference to us and clean us up appropriately. self.setWindowTitle('Electron Cash - ' + _('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 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 Electron Cash in offline mode; restart Electron Cash 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_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 __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, rate, obj, func): self.n = func.__name__ self.qn = func.__qualname__ self.rate = rate self.obj = Weak.ref( obj) # keep a weak reference to the object to prevent cycles self.func = func
def update(self): # Defer updates if editing if self.editor: self.pending_update = True else: # Deferred update mode won't actually update the GUI if it's # not on-screen, and will instead update it the next time it is # shown. This has been found to radically speed up large wallets # on initial synch or when new TX's arrive. if self.should_defer_update_incr(): return self.setUpdatesEnabled(False) scroll_pos_val = self.verticalScrollBar().value() # save previous scroll bar position self.on_update() self.deferred_update_ct = 0 weakSelf = Weak.ref(self) def restoreScrollBar(): slf = weakSelf() if slf: slf.updateGeometry() slf.verticalScrollBar().setValue(scroll_pos_val) # restore scroll bar to previous slf.setUpdatesEnabled(True) QTimer.singleShot(0, restoreScrollBar) # need to do this from a timer some time later due to Qt quirks if self.current_filter: self.filter(self.current_filter)
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, parent, tab): MyTreeWidget.__init__(self, parent, self.create_menu, [ _('Address'), _('Amount'), ], stretch_column=0, deferred_updates=True) self.tab = Weak.ref(tab) self.main_window = parent self.setSelectionMode(QAbstractItemView.ExtendedSelection) self.setSortingEnabled(False) self.sending = None self.update_sig.connect(self.update) self.monospace_font = QFont(MONOSPACE_FONT) self.italic_font = QFont() self.italic_font.setItalic(True) self.loaded_icon = self._get_loaded_icon() self.collected_icon = self._get_collected_icon() self.wallet = tab.wallet self.balances = dict() self.balances_batch = dict() self.synch_event = threading.Event() lock = threading.Lock() self.synch = threading.Thread(target=self.synchronize, daemon=True, args=( self.synch_event, lock, )) self.synch.start()
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 __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 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())
def transfer(self): self.show_message( _("You should not use either wallet during the transfer. Leave Electron Cash active. " "The plugin ceases operation and will have to be re-activated if Electron Cash " "is stopped during the operation.")) self.storage = WalletStorage(self.file) self.storage.set_password(self.tmp_pass, encrypt=True) self.storage.put('keystore', self.keystore.dump()) self.recipient_wallet = Standard_Wallet(self.storage) self.recipient_wallet.start_threads(self.network) # comment the below out if you want to disable auto-clean of temp file # otherwise the temp file will be auto-cleaned on app exit or # on the recepient_wallet object's destruction (when refct drops to 0) Weak.finalize(self.recipient_wallet, self.delete_temp_wallet_file, self.file) self.plugin.switch_to(Transfer, self.wallet_name, self.recipient_wallet, float(self.time_e.text()), self.password)
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())
def on_focus_change(self, ignored, new_focus_widget): ''' Remember the last wallet window that was activated because start_new_window uses this information. We store the ElectrumWindow in a weak reference so that we don't interfere with its gc when it is closed.''' if not new_focus_widget: return if isinstance(new_focus_widget, QWidget): window = QWidget.window(new_focus_widget) # call base class because some widgets may actually override 'window' with Python attributes. if isinstance(window, ElectrumWindow): self._last_active_window = Weak.ref(window)
def __init__(self, config): super().__init__(None) # Top-level Object if Exception_Hook._instance: return # This is ok, we will be GC'd later. if not _is_enabled(config): print_error("[{}] Not installed due to user config.".format(__class__.__qualname__)) return # self will get auto-gc'd Exception_Hook._instance = self # strong reference to self should keep us alive until uninstall() is called self.config = config sys.excepthook = self.handler # yet another strong reference. We really won't die unless uninstall() is called self._report_exception.connect(_show_window) print_error("[{}] Installed.".format(__class__.__qualname__)) Exception_Hook._weak_instances.append(Weak.ref(self, Exception_Hook.finalized))
def create_window_for_wallet(self, wallet): w = ElectrumWindow(self, wallet) self.windows.append(w) dname = w.diagnostic_name() def onFinalized(wr,dname=dname): print_error("[{}] finalized".format(dname)) self.weak_windows.remove(wr) self.weak_windows.append(Weak.ref(w,onFinalized)) self.build_tray_menu() # FIXME: Remove in favour of the load_wallet hook run_hook('on_new_window', w) return w
def __init__(self, parent=None): super().__init__(parent, self.create_menu, [], 3, deferred_updates=True) self.refresh_headers() self.setColumnHidden(1, True) self.setSortingEnabled(True) self.sortByColumn(0, Qt.AscendingOrder) # force attributes to always be defined, even if None, at construction. self.wallet = self.parent.wallet self.monospaceFont = QFont(MONOSPACE_FONT) self.withdrawalBrush = QBrush(QColor("#BC1E1E")) self.invoiceIcon = QIcon(":icons/seal") self._item_cache = Weak.ValueDictionary()
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 _setup_do_in_main_thread_handler(self): ''' Sets up "do_in_main_thread" handler mechanism for Qt GUI. ''' self.do_in_main_thread_signal.connect(self._do_in_main_thread_handler_slot) orig_handler = Handlers.do_in_main_thread weakSelf = Weak.ref(self) def my_do_in_main_thread_handler(func, *args, **kwargs): strongSelf = weakSelf() if strongSelf: # We are still alive, emit the signal which will be handled # in the main thread. strongSelf.do_in_main_thread_signal.emit(func, args, kwargs) else: # We died. Uninstall this handler, invoke original handler. Handlers.do_in_main_thread = orig_handler orig_handler(func, *args, **kwargs) Handlers.do_in_main_thread = my_do_in_main_thread_handler
def _setup_save_sort_mechanism(self): if (self._save_sort_settings and isinstance(getattr(self.parent, 'wallet', None), Abstract_Wallet)): storage = self.parent.wallet.storage key = f'mytreewidget_default_sort_{type(self).__name__}' default = (storage and storage.get(key, None)) or self.default_sort if default and isinstance(default, (tuple, list)) and len(default) >= 2 and all(isinstance(i, int) for i in default): self.setSortingEnabled(True) self.sortByColumn(default[0], default[1]) if storage: # Paranoia; hold a weak reference just in case subclass code # does unusual things. weakStorage = Weak.ref(storage) def save_sort(column, qt_sort_order): storage = weakStorage() if storage: storage.put(key, [column, qt_sort_order]) self.header().sortIndicatorChanged.connect(save_sort) elif self.default_sort: self.setSortingEnabled(True) self.sortByColumn(self.default_sort[0], self.default_sort[1])
def __init__(self, parent, tab): MyTreeWidget.__init__(self, parent, self.create_menu, [ _('Address'), _('Amount'), _('Time'), _('When'), _('Status'), ], stretch_column=3, deferred_updates=True) self.tab = Weak.ref(tab) self.t0 = time.time() self.t0_last = None self._recalc_times(tab.times) self.print_error("transferring utxo") self.utxos = list(tab.utxos) self.main_window = parent self.setSelectionMode(QAbstractItemView.NoSelection) self.setSortingEnabled(False) self.sent_utxos = dict() self.failed_utxos = dict() self.sending = None self.check_icon = self._get_check_icon() self.fail_icon = self._get_fail_icon() self.update_sig.connect(self.update) self.monospace_font = QFont(MONOSPACE_FONT) self.italic_font = QFont() self.italic_font.setItalic(True) self.timer = QTimer(self) self.timer.setSingleShot(False) self.timer.timeout.connect(self.update_sig) self.timer.start( 2000 ) # update every 2 seconds since the granularity of our "When" column is ~5 seconds self.wallet = tab.recipient_wallet
def __init__(self, parent, plugin, wallet_name, password=None): QDialog.__init__(self, parent) self.main_window = parent self.password = password self.wallet = parent.wallet self.plugin = plugin self.network = parent.network self.wallet_name = wallet_name self.batch_label = "BitcoinBiletoj1" self.template_file = '' self.working_directory = self.wallet.storage.get("bileto_path") if self.working_directory: if not os.path.exists(self.working_directory): self.working_directory = None self.number = 0 self.times = 1 self.public_key = '' for x in range(10): name = 'tmp_wo_wallet' + ''.join( random.choices(string.ascii_letters + string.digits, k=10)) self.file = os.path.join(tempfile.gettempdir(), name) if not os.path.exists(self.file): break else: raise RuntimeError( 'Could not find a unique temp file in tmp directory', tempfile.gettempdir()) self.tmp_pass = ''.join( random.choices(string.ascii_uppercase + string.digits, k=10)) from electroncash import mnemonic seed = mnemonic.Mnemonic('en').make_seed('standard') self.keystore = keystore.from_seed(seed, self.tmp_pass, False) self.storage = WalletStorage(self.file) self.storage.set_password(self.tmp_pass, encrypt=True) self.storage.put('keystore', self.keystore.dump()) self.recipient_wallet = Standard_Wallet(self.storage) self.recipient_wallet.start_threads(self.network) Weak.finalize(self.recipient_wallet, self.delete_temp_wallet_file, self.file) vbox = QVBoxLayout() self.setLayout(vbox) hbox = QHBoxLayout() vbox.addLayout(hbox) l = QLabel("<b>%s</b>" % (_("Bitcoin Bileto"))) hbox.addStretch(1) hbox.addWidget(l) hbox.addStretch(1) vbox.addWidget(QLabel("Working directory:")) hbox = QHBoxLayout() vbox.addLayout(hbox) self.wd_label = QLabel(self.working_directory) hbox.addWidget(self.wd_label) b = QPushButton("Set") b.clicked.connect(lambda: self.plugin.settings_dialog( self, self.settings_updated_signal)) self.settings_updated_signal.connect(self.on_settings_updated) hbox.addWidget(b) data = "prywatny klucz do portfela" self.qrw_priv = QRCodeWidget(data) self.qrw_add = QRCodeWidget(data) self.batch_label_wid = QLineEdit() self.batch_label_wid.setPlaceholderText( _("Bitcoin biletoj batch label")) self.batch_label_wid.textEdited.connect(self.batch_info_changed) vbox.addWidget(self.batch_label_wid) grid = QGridLayout() vbox.addLayout(grid) self.number_wid = QLineEdit() self.number_wid.setPlaceholderText(_("Number of biletoj")) self.number_wid.textEdited.connect(self.batch_info_changed) self.times_wid = QLineEdit() self.times_wid.textEdited.connect(self.batch_info_changed) self.times_wid.setText("1") hbox = QHBoxLayout() vbox.addLayout(hbox) hbox.addWidget(self.number_wid) #hbox.addWidget(QLabel("x")) #hbox.addWidget(self.times_wid) hbox.addStretch(1) self.times_wid.setMaximumWidth(120) self.number_wid.setMaximumWidth(140) self.only_qrcodes_checkbox = QCheckBox("Only make QR codes.") self.only_qrcodes_checkbox.stateChanged.connect( self.batch_info_changed) self.encrypt_checkbox = QCheckBox("Encrypt Batch.") vbox.addWidget(self.encrypt_checkbox) vbox.addWidget(self.only_qrcodes_checkbox) #b = QPushButton(_("Load .tex template")) #b.clicked.connect(self.load_template) #b.setMaximumWidth(130) #grid.addWidget(b, 0, 0) #self.template_path_label_wid = QLabel('set path') #grid.addWidget(self.template_path_label_wid, 0, 1) self.public_key_wid = QLineEdit() self.public_key_wid.setPlaceholderText( _("Public Key") + _(" for encryption")) self.public_key_wid.textEdited.connect(self.batch_info_changed) #vbox.addWidget(self.public_key_wid) self.b = QPushButton(_("Generate biletoj")) self.b.clicked.connect(self.generate_biletoj) self.prog_bar = QProgressBar() self.prog_bar.setVisible(False) vbox.addWidget(self.b) vbox.addWidget(self.prog_bar) self.b.setDisabled(True) vbox.addStretch(1)
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, 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_height = None self.setMinimumWidth(750) self.setWindowTitle(_("Transaction")) vbox = QVBoxLayout() self.setLayout(vbox) vbox.addWidget(QLabel(_("Transaction ID:"))) self.tx_hash_e = ButtonsLineEdit() 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.sign_button = b = QPushButton(_("Sign")) b.clicked.connect(self.sign) self.broadcast_button = b = QPushButton(_("Broadcast")) b.clicked.connect(self.do_broadcast) self.save_button = b = QPushButton(_("Save")) b.clicked.connect(self.save) self.cancel_button = b = QPushButton(_("Close")) b.clicked.connect(self.close) b.setDefault(True) self.qr_button = b = QPushButton() b.setIcon(QIcon(icon)) b.clicked.connect(self.show_qr) self.copy_button = CopyButton( lambda: str(weakSelfRef() and weakSelfRef().tx), callback=lambda: weakSelfRef() and weakSelfRef().show_message( _("Transaction raw hex copied to clipboard."))) # Action buttons self.buttons = [ 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) 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)
def __init__(self, parent, network, config, wizard=False): super().__init__(parent) self.network = network self.config = config self.protocol = None self.tor_proxy = None # tor detector self.td = TorDetector(self) self.td.found_proxy.connect(self.suggest_proxy) self.tabs = tabs = QTabWidget() server_tab = QWidget() weakTd = Weak.ref(self.td) class ProxyTab(QWidget): def showEvent(slf, e): super().showEvent(e) td = weakTd() if e.isAccepted() and td: td.start( ) # starts the tor detector when proxy_tab appears def hideEvent(slf, e): super().hideEvent(e) td = weakTd() if e.isAccepted() and td: td.stop( ) # stops the tor detector when proxy_tab disappears proxy_tab = ProxyTab() blockchain_tab = QWidget() slp_tab = QWidget() tabs.addTab(blockchain_tab, _('Overview')) tabs.addTab(server_tab, _('Server')) tabs.addTab(proxy_tab, _('Proxy')) tabs.addTab(slp_tab, _('Tokens')) if wizard: tabs.setCurrentIndex(1) # server tab grid = QGridLayout(server_tab) grid.setSpacing(8) self.server_host = QLineEdit() self.server_host.setFixedWidth(200) self.server_port = QLineEdit() self.server_port.setFixedWidth(60) self.ssl_cb = QCheckBox(_('Use SSL')) self.autoconnect_cb = QCheckBox(_('Select server automatically')) self.autoconnect_cb.setEnabled( self.config.is_modifiable('auto_connect')) weakSelf = Weak.ref( self ) # Qt/Python GC hygeine: avoid strong references to self in lambda slots. self.server_host.editingFinished.connect( lambda: weakSelf() and weakSelf().set_server(onion_hack=True)) self.server_port.editingFinished.connect( lambda: weakSelf() and weakSelf().set_server(onion_hack=True)) self.ssl_cb.clicked.connect(self.change_protocol) self.autoconnect_cb.clicked.connect(self.set_server) self.autoconnect_cb.clicked.connect(self.update) msg = ' '.join([ _("If auto-connect is enabled, Electron Cash will always use a server that is on the longest blockchain." ), _("If it is disabled, you have to choose a server you want to use. Electron Cash will warn you if your server is lagging." ) ]) grid.addWidget(self.autoconnect_cb, 0, 0, 1, 3) grid.addWidget(HelpButton(msg), 0, 4) self.preferred_only_cb = QCheckBox( _("Connect only to preferred servers")) self.preferred_only_cb.setEnabled( self.config.is_modifiable('whitelist_servers_only')) self.preferred_only_cb.setToolTip( _("If enabled, restricts Electron Cash to connecting to servers only marked as 'preferred'." )) self.preferred_only_cb.clicked.connect( self.set_whitelisted_only ) # re-set the config key and notify network.py msg = '\n\n'.join([ _("If 'Connect only to preferred servers' is enabled, Electron Cash will only connect to servers marked as 'preferred' servers ({})." ).format(ServerFlag.Symbol[ServerFlag.Preferred]), _("This feature was added in response to the potential for a malicious actor to deny service via launching many servers (aka a sybil attack)." ), _("If unsure, most of the time it's safe to leave this option disabled. However leaving it enabled is safer (if a little bit discouraging to new server operators wanting to populate their servers)." ) ]) grid.addWidget(self.preferred_only_cb, 1, 0, 1, 3) grid.addWidget(HelpButton(msg), 1, 4) grid.addWidget(self.ssl_cb, 2, 0, 1, 3) self.ssl_help = HelpButton( _('SSL is used to authenticate and encrypt your connections with the blockchain servers.' ) + "\n\n" + _('Due to potential security risks, you may only disable SSL when using a Tor Proxy.' )) grid.addWidget(self.ssl_help, 2, 4) grid.addWidget(QLabel(_('Server') + ':'), 3, 0) grid.addWidget(self.server_host, 3, 1, 1, 2) grid.addWidget(self.server_port, 3, 3) self.server_list_label = label = QLabel( '') # will get set by self.update() grid.addWidget(label, 4, 0, 1, 5) self.servers_list = ServerListWidget(self) grid.addWidget(self.servers_list, 5, 0, 1, 5) self.legend_label = label = WWLabel( '') # will get populated with the legend by self.update() label.setTextInteractionFlags(label.textInteractionFlags() & ( ~Qt.TextSelectableByMouse)) # disable text selection by mouse here self.legend_label.linkActivated.connect(self.on_view_blacklist) grid.addWidget(label, 6, 0, 1, 4) msg = ' '.join([ _("Preferred servers ({}) are servers you have designated as reliable and/or trustworthy." ).format(ServerFlag.Symbol[ServerFlag.Preferred]), _("Initially, the preferred list is the hard-coded list of known-good servers vetted by the Electron Cash developers." ), _("You can add or remove any server from this list and optionally elect to only connect to preferred servers." ), "\n\n" + _("Banned servers ({}) are servers deemed unreliable and/or untrustworthy, and so they will never be connected-to by Electron Cash." ).format(ServerFlag.Symbol[ServerFlag.Banned]) ]) grid.addWidget(HelpButton(msg), 6, 4) # Proxy tab grid = QGridLayout(proxy_tab) grid.setSpacing(8) # proxy setting self.proxy_cb = QCheckBox(_('Use proxy')) self.proxy_cb.clicked.connect(self.check_disable_proxy) self.proxy_cb.clicked.connect(self.set_proxy) self.proxy_mode = QComboBox() self.proxy_mode.addItems(['SOCKS4', 'SOCKS5', 'HTTP']) self.proxy_host = QLineEdit() self.proxy_host.setFixedWidth(200) self.proxy_port = QLineEdit() self.proxy_port.setFixedWidth(60) self.proxy_user = QLineEdit() self.proxy_user.setPlaceholderText(_("Proxy user")) self.proxy_password = QLineEdit() self.proxy_password.setPlaceholderText(_("Password")) self.proxy_password.setEchoMode(QLineEdit.Password) self.proxy_password.setFixedWidth(60) self.proxy_mode.currentIndexChanged.connect(self.set_proxy) self.proxy_host.editingFinished.connect(self.set_proxy) self.proxy_port.editingFinished.connect(self.set_proxy) self.proxy_user.editingFinished.connect(self.set_proxy) self.proxy_password.editingFinished.connect(self.set_proxy) self.proxy_mode.currentIndexChanged.connect( self.proxy_settings_changed) self.proxy_host.textEdited.connect(self.proxy_settings_changed) self.proxy_port.textEdited.connect(self.proxy_settings_changed) self.proxy_user.textEdited.connect(self.proxy_settings_changed) self.proxy_password.textEdited.connect(self.proxy_settings_changed) self.tor_cb = QCheckBox(_("Use Tor Proxy")) self.tor_cb.setIcon(QIcon(":icons/tor_logo.svg")) self.tor_cb.setEnabled(False) self.tor_cb.clicked.connect(self.use_tor_proxy) grid.addWidget(self.tor_cb, 1, 0, 1, 3) grid.addWidget(self.proxy_cb, 2, 0, 1, 3) grid.addWidget( HelpButton( _('Proxy settings apply to all connections: with Electron Cash servers, but also with third-party services.' )), 2, 4) grid.addWidget(self.proxy_mode, 4, 1) grid.addWidget(self.proxy_host, 4, 2) grid.addWidget(self.proxy_port, 4, 3) grid.addWidget(self.proxy_user, 5, 2) grid.addWidget(self.proxy_password, 5, 3) grid.setRowStretch(7, 1) # SLP Validation Tab grid = QGridLayout(slp_tab) self.slpdb_cb = QCheckBox( _('Use SLPDB Graph Search to speed up validation')) self.slpdb_cb.clicked.connect(self.use_slpdb) self.slpdb_cb.setChecked( config.get('slp_validator_graphsearch_enabled', False)) grid.addWidget(self.slpdb_cb, 0, 0, 1, 3) hbox = QHBoxLayout() hbox.addWidget(QLabel(_('Server') + ':')) self.slp_server_host = QLineEdit() self.slp_server_host.setFixedWidth(250) self.slp_server_host.editingFinished.connect( lambda: weakSelf() and weakSelf().set_slp_server()) hbox.addWidget(self.slp_server_host) hbox.addStretch(1) grid.addLayout(hbox, 1, 0) self.slpdb_list_widget = SlpServeListWidget(self) grid.addWidget(self.slpdb_list_widget, 2, 0, 1, 5) grid.addWidget(QLabel(_("Current Graph Search Jobs:")), 3, 0) self.slp_search_job_list_widget = SlpSearchJobListWidget(self) grid.addWidget(self.slp_search_job_list_widget, 4, 0, 1, 5) # Blockchain Tab grid = QGridLayout(blockchain_tab) msg = ' '.join([ _("Electron Cash connects to several nodes in order to download block headers and find out the longest blockchain." ), _("This blockchain is used to verify the transactions sent by your transaction server." ) ]) self.status_label = QLabel('') self.status_label.setTextInteractionFlags( self.status_label.textInteractionFlags() | Qt.TextSelectableByMouse) grid.addWidget(QLabel(_('Status') + ':'), 0, 0) grid.addWidget(self.status_label, 0, 1, 1, 3) grid.addWidget(HelpButton(msg), 0, 4) self.server_label = QLabel('') self.server_label.setTextInteractionFlags( self.server_label.textInteractionFlags() | Qt.TextSelectableByMouse) msg = _( "Electron Cash sends your wallet addresses to a single server, in order to receive your transaction history." ) grid.addWidget(QLabel(_('Server') + ':'), 1, 0) grid.addWidget(self.server_label, 1, 1, 1, 3) grid.addWidget(HelpButton(msg), 1, 4) self.height_label = QLabel('') self.height_label.setTextInteractionFlags( self.height_label.textInteractionFlags() | Qt.TextSelectableByMouse) msg = _('This is the height of your local copy of the blockchain.') grid.addWidget(QLabel(_('Blockchain') + ':'), 2, 0) grid.addWidget(self.height_label, 2, 1) grid.addWidget(HelpButton(msg), 2, 4) self.split_label = QLabel('') self.split_label.setTextInteractionFlags( self.split_label.textInteractionFlags() | Qt.TextSelectableByMouse) grid.addWidget(self.split_label, 3, 0, 1, 3) self.nodes_list_widget = NodesListWidget(self) grid.addWidget(self.nodes_list_widget, 5, 0, 1, 5) vbox = QVBoxLayout() vbox.addWidget(tabs) self.layout_ = vbox self.fill_in_proxy_settings() self.update()
def __init__(self, parent, network, config, wizard=False): super().__init__(parent) self.network = network self.config = config self.protocol = None self.tor_proxy = None # tor detector self.td = TorDetector(self) self.td.found_proxy.connect(self.suggest_proxy) self.tabs = tabs = QTabWidget() server_tab = QWidget() weakTd = Weak.ref(self.td) class ProxyTab(QWidget): def showEvent(slf, e): super().showEvent(e) td = weakTd() if e.isAccepted() and td: td.start( ) # starts the tor detector when proxy_tab appears def hideEvent(slf, e): super().hideEvent(e) td = weakTd() if e.isAccepted() and td: td.stop( ) # stops the tor detector when proxy_tab disappears proxy_tab = ProxyTab() blockchain_tab = QWidget() tabs.addTab(blockchain_tab, _('Overview')) tabs.addTab(server_tab, _('Server')) tabs.addTab(proxy_tab, _('Proxy')) if wizard: tabs.setCurrentIndex(1) # server tab grid = QGridLayout(server_tab) grid.setSpacing(8) self.server_host = QLineEdit() self.server_host.setFixedWidth(200) self.server_port = QLineEdit() self.server_port.setFixedWidth(60) self.autoconnect_cb = QCheckBox(_('Select server automatically')) self.autoconnect_cb.setEnabled( self.config.is_modifiable('auto_connect')) self.server_host.editingFinished.connect(self.set_server) self.server_port.editingFinished.connect(self.set_server) self.autoconnect_cb.clicked.connect(self.set_server) self.autoconnect_cb.clicked.connect(self.update) msg = ' '.join([ _("If auto-connect is enabled, Electron Cash will always use a server that is on the longest blockchain." ), _("If it is disabled, you have to choose a server you want to use. Electron Cash will warn you if your server is lagging." ) ]) grid.addWidget(self.autoconnect_cb, 0, 0, 1, 3) grid.addWidget(HelpButton(msg), 0, 4) grid.addWidget(QLabel(_('Server') + ':'), 1, 0) grid.addWidget(self.server_host, 1, 1, 1, 2) grid.addWidget(self.server_port, 1, 3) label = _('Server peers') if network.is_connected() else _( 'Default Servers') grid.addWidget(QLabel(label), 2, 0, 1, 5) self.servers_list = ServerListWidget(self) grid.addWidget(self.servers_list, 3, 0, 1, 5) # Proxy tab grid = QGridLayout(proxy_tab) grid.setSpacing(8) # proxy setting self.proxy_cb = QCheckBox(_('Use proxy')) self.proxy_cb.clicked.connect(self.check_disable_proxy) self.proxy_cb.clicked.connect(self.set_proxy) self.proxy_mode = QComboBox() self.proxy_mode.addItems(['SOCKS4', 'SOCKS5', 'HTTP']) self.proxy_host = QLineEdit() self.proxy_host.setFixedWidth(200) self.proxy_port = QLineEdit() self.proxy_port.setFixedWidth(60) self.proxy_user = QLineEdit() self.proxy_user.setPlaceholderText(_("Proxy user")) self.proxy_password = QLineEdit() self.proxy_password.setPlaceholderText(_("Password")) self.proxy_password.setEchoMode(QLineEdit.Password) self.proxy_password.setFixedWidth(60) self.proxy_mode.currentIndexChanged.connect(self.set_proxy) self.proxy_host.editingFinished.connect(self.set_proxy) self.proxy_port.editingFinished.connect(self.set_proxy) self.proxy_user.editingFinished.connect(self.set_proxy) self.proxy_password.editingFinished.connect(self.set_proxy) self.proxy_mode.currentIndexChanged.connect( self.proxy_settings_changed) self.proxy_host.textEdited.connect(self.proxy_settings_changed) self.proxy_port.textEdited.connect(self.proxy_settings_changed) self.proxy_user.textEdited.connect(self.proxy_settings_changed) self.proxy_password.textEdited.connect(self.proxy_settings_changed) self.tor_cb = QCheckBox(_("Use Tor Proxy")) self.tor_cb.setIcon(QIcon(":icons/tor_logo.png")) self.tor_cb.hide() self.tor_cb.clicked.connect(self.use_tor_proxy) grid.addWidget(self.tor_cb, 1, 0, 1, 3) grid.addWidget(self.proxy_cb, 2, 0, 1, 3) grid.addWidget( HelpButton( _('Proxy settings apply to all connections: with Electron Cash servers, but also with third-party services.' )), 2, 4) grid.addWidget(self.proxy_mode, 4, 1) grid.addWidget(self.proxy_host, 4, 2) grid.addWidget(self.proxy_port, 4, 3) grid.addWidget(self.proxy_user, 5, 2) grid.addWidget(self.proxy_password, 5, 3) grid.setRowStretch(7, 1) # Blockchain Tab grid = QGridLayout(blockchain_tab) msg = ' '.join([ _("Electron Cash connects to several nodes in order to download block headers and find out the longest blockchain." ), _("This blockchain is used to verify the transactions sent by your transaction server." ) ]) self.status_label = QLabel('') grid.addWidget(QLabel(_('Status') + ':'), 0, 0) grid.addWidget(self.status_label, 0, 1, 1, 3) grid.addWidget(HelpButton(msg), 0, 4) self.server_label = QLabel('') msg = _( "Electron Cash sends your wallet addresses to a single server, in order to receive your transaction history." ) grid.addWidget(QLabel(_('Server') + ':'), 1, 0) grid.addWidget(self.server_label, 1, 1, 1, 3) grid.addWidget(HelpButton(msg), 1, 4) self.height_label = QLabel('') msg = _('This is the height of your local copy of the blockchain.') grid.addWidget(QLabel(_('Blockchain') + ':'), 2, 0) grid.addWidget(self.height_label, 2, 1) grid.addWidget(HelpButton(msg), 2, 4) self.split_label = QLabel('') grid.addWidget(self.split_label, 3, 0, 1, 3) self.nodes_list_widget = NodesListWidget(self) grid.addWidget(self.nodes_list_widget, 5, 0, 1, 5) vbox = QVBoxLayout() vbox.addWidget(tabs) self.layout_ = vbox self.fill_in_proxy_settings() self.update()