def passphrase_dialog(self, msg, confirm): # If confirm is true, require the user to enter the passphrase twice parent = self.top_level_window() d = WindowModalDialog(parent, _("Enter Passphrase")) if confirm: OK_button = OkButton(d) playout = PasswordLayout(msg=msg, kind=PW_PASSPHRASE, OK_button=OK_button) vbox = QtWidgets.QVBoxLayout() vbox.addLayout(playout.layout()) vbox.addLayout(Buttons(CancelButton(d), OK_button)) d.setLayout(vbox) passphrase = playout.new_password() if d.exec_() else None else: pw = QtWidgets.QLineEdit() pw.setEchoMode(2) pw.setMinimumWidth(200) vbox = QtWidgets.QVBoxLayout() vbox.addWidget(WWLabel(msg)) vbox.addWidget(pw) vbox.addLayout(Buttons(CancelButton(d), OkButton(d))) d.setLayout(vbox) passphrase = pw.text() if d.exec_() else None self.passphrase = passphrase self.done.set()
def __init__(self, parent): super(MatrixDialog, self).__init__(parent) self.setWindowTitle(_("Trezor Matrix Recovery")) self.num = 9 self.loop = QEventLoop() vbox = QtWidgets.QVBoxLayout(self) vbox.addWidget(WWLabel(MATRIX_RECOVERY)) grid = QtWidgets.QGridLayout() grid.setSpacing(0) self.char_buttons = [] for y in range(3): for x in range(3): button = QtWidgets.QPushButton('?') button.clicked.connect(partial(self.process_key, ord('1') + y * 3 + x)) grid.addWidget(button, 3 - y, x) self.char_buttons.append(button) vbox.addLayout(grid) self.backspace_button = QtWidgets.QPushButton("<=") self.backspace_button.clicked.connect(partial(self.process_key, Qt.Key_Backspace)) self.cancel_button = QtWidgets.QPushButton(_("Cancel")) self.cancel_button.clicked.connect(partial(self.process_key, Qt.Key_Escape)) buttons = Buttons(self.backspace_button, self.cancel_button) vbox.addSpacing(40) vbox.addLayout(buttons) self.refresh() self.show()
def settings_dialog(self, window): d = WindowModalDialog(window, _("CashShuffle settings")) d.setMinimumSize(500, 200) vbox = QVBoxLayout(d) vbox.addWidget(QLabel(_('CashShuffle server'))) grid = QGridLayout() vbox.addLayout(grid) grid.addWidget(QLabel('server'), 0, 0) grid.addWidget(QLabel('use SSL'), 1, 0) server_e = QLineEdit() server_ssl_e = QCheckBox() server_e.setText(self.server) server_ssl_e.setChecked(self.config.get('coinshufflessl',False)) server_ssl_e.stateChanged.connect(lambda: self.config.set_key('coinshufflessl', server_ssl_e.isChecked())) grid.addWidget(server_e, 0, 1) grid.addWidget(server_ssl_e, 1, 1) vbox.addStretch() vbox.addLayout(Buttons(CloseButton(d), OkButton(d))) if not d.exec_(): return server = str(server_e.text()) # server_ssl = server_ssl_e.isChecked() self.config.set_key('coinshuffleserver', server)
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__(self, parent): super(CharacterDialog, self).__init__(parent) self.setWindowTitle(_("KeepKey Seed Recovery")) self.character_pos = 0 self.word_pos = 0 self.loop = QEventLoop() self.word_help = QLabel() self.char_buttons = [] vbox = QVBoxLayout(self) vbox.addWidget(WWLabel(CHARACTER_RECOVERY)) hbox = QHBoxLayout() hbox.addWidget(self.word_help) for i in range(4): char_button = CharacterButton('*') char_button.setMaximumWidth(36) self.char_buttons.append(char_button) hbox.addWidget(char_button) self.accept_button = CharacterButton(_("Accept Word")) self.accept_button.clicked.connect(partial(self.process_key, 32)) self.rejected.connect(partial(self.loop.exit, 1)) hbox.addWidget(self.accept_button) hbox.addStretch(1) vbox.addLayout(hbox) self.finished_button = QPushButton(_("Seed Entered")) self.cancel_button = QPushButton(_("Cancel")) self.finished_button.clicked.connect(partial(self.process_key, Qt.Key_Return)) self.cancel_button.clicked.connect(self.rejected) buttons = Buttons(self.finished_button, self.cancel_button) vbox.addSpacing(40) vbox.addLayout(buttons) self.refresh() self.show()
def settings_dialog(self, window): # Return a settings dialog. d = WindowModalDialog(window, _("Email settings")) vbox = QtWidgets.QVBoxLayout(d) d.setMinimumSize(500, 200) vbox.addStretch() vbox.addLayout(Buttons(CloseButton(d), OkButton(d))) d.show()
def message_dialog(self, msg, on_cancel): # Called more than once during signing, to confirm output and fee self.clear_dialog() title = _('Please check your {} device').format(self.device) self.dialog = dialog = WindowModalDialog(self.top_level_window(), title) l = QtWidgets.QLabel(msg) vbox = QtWidgets.QVBoxLayout(dialog) vbox.addWidget(l) if on_cancel: dialog.rejected.connect(on_cancel) vbox.addLayout(Buttons(CancelButton(dialog))) dialog.show()
def pin_dialog(self, msg): # Needed e.g. when resetting a device self.clear_dialog() dialog = WindowModalDialog(self.top_level_window(), _("Enter PIN")) matrix = self.pin_matrix_widget_class() vbox = QVBoxLayout() vbox.addWidget(QLabel(msg)) vbox.addWidget(matrix) vbox.addLayout(Buttons(CancelButton(dialog), OkButton(dialog))) dialog.setLayout(vbox) dialog.exec_() self.response = str(matrix.get_value()) self.done.set()
def __init__(self, parent, wallet): WindowModalDialog.__init__(self, parent) is_encrypted = wallet.has_storage_encryption() OK_button = OkButton(self) self.create_password_layout(wallet, is_encrypted, OK_button) self.setWindowTitle(self.playout.title()) vbox = QtWidgets.QVBoxLayout(self) vbox.addLayout(self.playout.layout()) vbox.addStretch(1) vbox.addLayout(Buttons(CancelButton(self), OK_button)) self.playout.encrypt_cb.setChecked(is_encrypted)
def __init__(self, window, plugin, wallet_name, action, payment_ids): # We want to be a top-level window QDialog.__init__(self, parent=None) self.main_window = window self.config = window.config self.plugin = plugin self.action = action self.wallet_name = wallet_name self.payment_ids = payment_ids self.setMinimumWidth(350) if action == ACTION_FORGET: self.setWindowTitle(_("Forget Overdue Payment Occurrences")) else: self.setWindowTitle(_("Pay Overdue Payment Occurrences")) formLayout = QFormLayout() self.setLayout(formLayout) if self.action == ACTION_FORGET: action_button_text = _("Forget") else: action_button_text = _("Pay") self.action_button = b = QPushButton(action_button_text) b.clicked.connect(self.perform_action) b.setEnabled(False) self.cancel_button = b = QPushButton(_("Cancel")) b.clicked.connect(self.close) b.setDefault(True) if sys.platform == "darwin": # macOS conventions self.buttons = [self.cancel_button, self.action_button] else: self.buttons = [self.action_button, self.cancel_button] self.summaryLabel = QLabel("Selected total: 0 BCH (0 occurrences)") payment_entries = self.plugin.get_wallet_payments(wallet_name) self.table = PaymentTable(self, plugin, wallet_name, payment_entries) formLayout.addRow(_("Wallet") + ':', QLabel(wallet_name)) formLayout.addRow(self.table) hbox = QHBoxLayout() hbox.addWidget(self.summaryLabel) hbox.addStretch(1) hbox.addLayout(Buttons(*self.buttons)) formLayout.addRow(hbox)
def __init__(self, parent=None, msg=None): msg = msg or _('Please enter your password') WindowModalDialog.__init__(self, parent, _("Enter Password")) self.pw = pw = PasswordLineEdit() vbox = QtWidgets.QVBoxLayout() vbox.addWidget(QtWidgets.QLabel(msg)) grid = QtWidgets.QGridLayout() grid.setSpacing(8) grid.addWidget(QtWidgets.QLabel(_('Password')), 1, 0) grid.addWidget(pw, 1, 1) vbox.addLayout(grid) vbox.addLayout(Buttons(CancelButton(self), OkButton(self))) self.setLayout(vbox) run_hook('password_dialog', pw, grid, 1)
def reset_seed_dialog(self, msg): print_error("In reset_seed_dialog") parent = self.top_level_window() d = WindowModalDialog(parent, _("Enter PIN")) pw = PasswordLineEdit() pw.setMinimumWidth(200) vbox = QtWidgets.QVBoxLayout() vbox.addWidget(WWLabel(msg)) vbox.addWidget(pw) vbox.addLayout(Buttons(CancelButton(d), OkButton(d))) d.setLayout(vbox) passphrase = pw.text() if d.exec_() else None return passphrase
def __init__(self, wallet, parent=None, msg=None, title=None, permit_empty=False): msg = msg or _('Please enter a passphrase') title = title or _("Enter Passphrase") super().__init__(parent, title) if parent is None: # Force app-modal if no parent window given self.setWindowModality(Qt.ApplicationModal) OK_button = OkButton(self) self.playout = PasswordLayout( msg, PW_PASSPHRASE, OK_button, wallet, permit_empty=permit_empty, ) self.setWindowTitle(title) vbox = QtWidgets.QVBoxLayout(self) vbox.addLayout(self.playout.layout()) vbox.addStretch(1) vbox.addLayout(Buttons(CancelButton(self), OK_button))
def reset_seed_dialog(self, msg): parent = self.top_level_window() d = WindowModalDialog(parent, _("Enter PIN")) pw = QLineEdit() pw.setEchoMode(2) pw.setMinimumWidth(200) cb_reset_2FA = QCheckBox(_('Also reset 2FA')) vbox = QVBoxLayout() vbox.addWidget(WWLabel(msg)) vbox.addWidget(pw) vbox.addWidget(cb_reset_2FA) vbox.addLayout(Buttons(CancelButton(d), OkButton(d))) d.setLayout(vbox) passphrase = pw.text() if d.exec_() else None reset_2FA = cb_reset_2FA.isChecked() return (passphrase, reset_2FA)
def settings_dialog(self, window): wallet = window.parent().wallet d = WindowModalDialog(window, _("Label Settings")) hbox = QHBoxLayout() hbox.addWidget(QLabel("Label sync options:")) upload = ThreadedButton("Force upload", partial(self.push_thread, wallet), partial(self.done_processing, d)) download = ThreadedButton("Force download", partial(self.pull_thread, wallet, True), partial(self.done_processing, d)) vbox = QVBoxLayout() vbox.addWidget(upload) vbox.addWidget(download) hbox.addLayout(vbox) vbox = QVBoxLayout(d) vbox.addLayout(hbox) vbox.addSpacing(20) vbox.addLayout(Buttons(OkButton(d))) return bool(d.exec_())
def settings_dialog(self, windowRef): window = windowRef() if not window: return d = WindowModalDialog(window.top_level_window(), _("Email settings")) d.setMinimumSize(500, 200) vbox = QVBoxLayout(d) vbox.addWidget(QLabel(_('Server hosting your email account'))) grid = QGridLayout() vbox.addLayout(grid) grid.addWidget(QLabel('Server (IMAP)'), 0, 0) server_e = QLineEdit() server_e.setText(self.imap_server) grid.addWidget(server_e, 0, 1) grid.addWidget(QLabel('Username'), 1, 0) username_e = QLineEdit() username_e.setText(self.username) grid.addWidget(username_e, 1, 1) grid.addWidget(QLabel('Password'), 2, 0) password_e = QLineEdit() password_e.setText(self.password) grid.addWidget(password_e, 2, 1) vbox.addStretch() vbox.addLayout(Buttons(CloseButton(d), OkButton(d))) if not d.exec_(): return server = str(server_e.text()) self.config.set_key('email_server', server) username = str(username_e.text()) self.config.set_key('email_username', username) password = str(password_e.text()) self.config.set_key('email_password', password) window.show_message( _('Please restart the plugin to activate the new settings'))
def change_card_label_dialog(self, client, msg): print_error("In change_card_label_dialog") while (True): parent = self.top_level_window() d = WindowModalDialog(parent, _("Enter Label")) pw = QtWidgets.QLineEdit() pw.setEchoMode(0) pw.setMinimumWidth(200) vbox = QtWidgets.QVBoxLayout() vbox.addWidget(WWLabel(msg)) vbox.addWidget(pw) vbox.addLayout(Buttons(CancelButton(d), OkButton(d))) d.setLayout(vbox) label = pw.text() if d.exec_() else None if label is None or len(label.encode('utf-8')) <= 64: return label else: client.handler.show_error( _("Card label should not be longer than 64 chars!"))
def export_dialog(self, tips: list): d = WindowModalDialog(self.parent, _('Export {c} Tips').format(c=len(tips))) d.setMinimumSize(400, 200) vbox = QVBoxLayout(d) defaultname = os.path.expanduser(read_config(self.wallet, 'export_history_filename', f"~/ChainTipper tips - wallet {self.wallet.basename()}.csv")) select_msg = _('Select file to export your tips to') box, filename_e, csv_button = filename_field(self.config, defaultname, select_msg) vbox.addWidget(box) vbox.addStretch(1) hbox = Buttons(CancelButton(d), OkButton(d, _('Export'))) vbox.addLayout(hbox) #run_hook('export_history_dialog', self, hbox) #self.update() res = d.exec_() d.setParent(None) # for python GC if not res: return filename = filename_e.text() write_config(self.wallet, 'export_history_filename', filename) if not filename: return success = False try: # minimum 10s time for calc. fees, etc success = self.do_export_history(filename) except Exception as reason: traceback.print_exc(file=sys.stderr) export_error_label = _("Error exporting tips") self.parent.show_critical(export_error_label + "\n" + str(reason), title=_("Unable to export tips")) else: if success: self.parent.show_message(_("{l} Tips successfully exported to {filename}").format(l=len(tips), filename=filename)) else: self.parent.show_message(_("Exporting tips to {filename} failed. More detail might be seen in terminal output.").format(filename=filename))
def __init__(self, parent, wallet): WindowModalDialog.__init__(self, parent) is_encrypted = wallet.storage.is_encrypted() if not wallet.has_password(): msg = _('Your wallet is not protected.') msg += ' ' + _('Use this dialog to add a password to your wallet.') else: if not is_encrypted: msg = _( 'Your bitcoins are password protected. However, your wallet file is not encrypted.' ) else: msg = _('Your wallet is password protected and encrypted.') msg += ' ' + _('Use this dialog to change your password.') OK_button = OkButton(self) self.playout = PasswordLayout(wallet, msg, PW_CHANGE, OK_button) self.setWindowTitle(self.playout.title()) vbox = QVBoxLayout(self) vbox.addLayout(self.playout.layout()) vbox.addStretch(1) vbox.addLayout(Buttons(CancelButton(self), OK_button)) self.playout.encrypt_cb.setChecked(is_encrypted or not wallet.has_password())
def settings_dialog(self, window): d = WindowModalDialog(window, _("Email settings")) d.setMinimumSize(500, 200) vbox = QVBoxLayout(d) vbox.addWidget(QLabel(_('Server hosting your email acount'))) grid = QGridLayout() vbox.addLayout(grid) grid.addWidget(QLabel('Server (IMAP)'), 0, 0) server_e = QLineEdit() server_e.setText(self.imap_server) grid.addWidget(server_e, 0, 1) grid.addWidget(QLabel('Username'), 1, 0) username_e = QLineEdit() username_e.setText(self.username) grid.addWidget(username_e, 1, 1) grid.addWidget(QLabel('Password'), 2, 0) password_e = QLineEdit() password_e.setText(self.password) grid.addWidget(password_e, 2, 1) vbox.addStretch() vbox.addLayout(Buttons(CloseButton(d), OkButton(d))) if not d.exec_(): return server = str(server_e.text()) self.config.set_key('email_server', server) username = str(username_e.text()) self.config.set_key('email_username', username) password = str(password_e.text()) self.config.set_key('email_password', password)
def __init__(self, window, plugin, keystore, device_id): title = _("{} Settings").format(plugin.device) super(SettingsDialog, self).__init__(window, title) self.setMaximumWidth(540) devmgr = plugin.device_manager() config = devmgr.config handler = keystore.handler thread = keystore.thread is_model_T = devmgr.client_by_id(device_id) and devmgr.client_by_id(device_id).features.model == 'T' if is_model_T: hs_cols, hs_rows, hs_mono = 144, 144, False else: hs_cols, hs_rows, hs_mono = 128, 64, True def invoke_client(method, *args, **kw_args): unpair_after = kw_args.pop('unpair_after', False) def task(): client = devmgr.client_by_id(device_id) if not client: raise RuntimeError("Device not connected") if method: getattr(client, method)(*args, **kw_args) if unpair_after: devmgr.unpair_id(device_id) return client.features thread.add(task, on_success=update) def update(features): self.features = features set_label_enabled() if features.bootloader_hash: bl_hash = bh2u(features.bootloader_hash) bl_hash = "\n".join([bl_hash[:32], bl_hash[32:]]) else: bl_hash = "N/A" noyes = [_("No"), _("Yes")] endis = [_("Enable Passphrases"), _("Disable Passphrases")] disen = [_("Disabled"), _("Enabled")] setchange = [_("Set a PIN"), _("Change PIN")] version = "%d.%d.%d" % (features.major_version, features.minor_version, features.patch_version) device_label.setText(features.label) pin_set_label.setText(noyes[features.pin_protection]) passphrases_label.setText(disen[features.passphrase_protection]) bl_hash_label.setText(bl_hash) label_edit.setText(features.label) device_id_label.setText(features.device_id) initialized_label.setText(noyes[features.initialized]) version_label.setText(version) clear_pin_button.setVisible(features.pin_protection) clear_pin_warning.setVisible(features.pin_protection) pin_button.setText(setchange[features.pin_protection]) pin_msg.setVisible(not features.pin_protection) passphrase_button.setText(endis[features.passphrase_protection]) language_label.setText(features.language) def set_label_enabled(): label_apply.setEnabled(label_edit.text() != self.features.label) def rename(): invoke_client('change_label', label_edit.text()) def toggle_passphrase(): title = _("Confirm Toggle Passphrase Protection") currently_enabled = self.features.passphrase_protection if currently_enabled: msg = _("After disabling passphrases, you can only pair this " f"{PROJECT_NAME} wallet if it had an empty passphrase. " "If its passphrase was not empty, you will need to " "create a new wallet with the install wizard. You " "can use this wallet again at any time by re-enabling " "passphrases and entering its passphrase.") else: msg = _(f"Your current {PROJECT_NAME} wallet can only be used " "with an empty passphrase. You must create a separate " "wallet with the install wizard for other passphrases " "as each one generates a new set of addresses.") msg += "\n\n" + _("Are you sure you want to proceed?") if not self.question(msg, title=title): return invoke_client('toggle_passphrase', unpair_after=currently_enabled) def change_homescreen(): le_dir = ((__class__.last_hs_dir and [__class__.last_hs_dir]) or QStandardPaths.standardLocations(QStandardPaths.DesktopLocation) or QStandardPaths.standardLocations(QStandardPaths.PicturesLocation) or QStandardPaths.standardLocations(QStandardPaths.HomeLocation) or [''])[0] filename, __ = QtWidgets.QFileDialog.getOpenFileName(self, _("Choose Homescreen"), le_dir) if not filename: return # user cancelled __class__.last_hs_dir = os.path.dirname(filename) # remember previous location if filename.lower().endswith('.toif') or filename.lower().endswith('.toig'): which = filename.lower()[-1].encode('ascii') # .toif or .toig = f or g in header if which == b'g': # For now I couldn't get Grayscale TOIG to work on any device, disabled handler.show_error(_('Grayscale TOI files are not currently supported. Try a PNG or JPG file instead.')) return if not is_model_T: handler.show_error(_('At this time, only the Trezor Model T supports the direct loading of TOIF files. Try a PNG or JPG file instead.')) return try: img = open(filename, 'rb').read() if img[:8] != b'TOI' + which + int(hs_cols).to_bytes(2, byteorder='little') + int(hs_rows).to_bytes(2, byteorder='little'): handler.show_error(_('Image must be a TOI{} file of size {}x{}').format(which.decode('ascii').upper(), hs_cols, hs_rows)) return except OSError as e: handler.show_error('Error reading {}: {}'.format(filename, e)) return else: def read_and_convert_using_qt_to_raw_mono(handler, filename, hs_cols, hs_rows, invert=True): img = QImage(filename) if img.isNull(): handler.show_error(_('Could not load the image {} -- unknown format or other error').format(os.path.basename(filename))) return if (img.width(), img.height()) != (hs_cols, hs_rows): # do we need to scale it ? img = img.scaled(hs_cols, hs_rows, Qt.IgnoreAspectRatio, Qt.SmoothTransformation) # force to our dest size. Note that IgnoreAspectRatio guarantess the right size. Ther other modes don't if img.isNull() or (img.width(), img.height()) != (hs_cols, hs_rows): handler.show_error(_("Could not scale image to {} x {} pixels").format(hs_cols, hs_rows)) return bm = QBitmap.fromImage(img, Qt.MonoOnly) # ensures 1bpp, dithers any colors if bm.isNull(): handler.show_error(_('Could not convert image to monochrome')) return target_fmt = QImage.Format_Mono img = bm.toImage().convertToFormat(target_fmt, Qt.MonoOnly|Qt.ThresholdDither|Qt.AvoidDither) # ensures MSB bytes again (above steps may have twiddled the bytes) lineSzOut = hs_cols // 8 # bits -> num bytes per line bimg = bytearray(hs_rows * lineSzOut) # 1024 bytes for a 128x64 img bpl = img.bytesPerLine() if bpl < lineSzOut: handler.show_error(_("Internal error converting image")) return # read in 1 scan line at a time since the scan lines may be > our target packed image for row in range(hs_rows): # copy image scanlines 1 line at a time to destination buffer ucharptr = img.constScanLine(row) # returned type is basically void* ucharptr.setsize(bpl) # inform python how big this C array is b = bytes(ucharptr) # aaand.. work with bytes. begin = row * lineSzOut end = begin + lineSzOut bimg[begin:end] = b[0:lineSzOut] if invert: for i in range(begin, end): bimg[i] = ~bimg[i] & 0xff # invert b/w return bytes(bimg) def read_and_convert_using_qt_to_toif(handler, filename, hs_cols, hs_rows): img = QImage(filename) if img.isNull(): handler.show_error(_('Could not load the image {} -- unknown format or other error').format(os.path.basename(filename))) return if (img.width(), img.height()) != (hs_cols, hs_rows): # do we need to scale it ? img = img.scaled(hs_cols, hs_rows, Qt.IgnoreAspectRatio, Qt.SmoothTransformation) # force to our dest size. Note that IgnoreAspectRatio guarantess the right size. Ther other modes don't if img.isNull() or (img.width(), img.height()) != (hs_cols, hs_rows): handler.show_error(_("Could not scale image to {} x {} pixels").format(hs_cols, hs_rows)) return target_fmt = QImage.Format_RGB888 img = img.convertToFormat(QImage.Format_Indexed8).convertToFormat(target_fmt) # dither it down to 256 colors to reduce image complexity then back up to 24 bit for easy reading if img.isNull(): handler.show_error(_("Could not dither or re-render image")) return def qimg_to_toif(img, handler): try: import struct, zlib except ImportError as e: handler.show_error(_("Could not convert image, a required library is missing: {}").format(e)) return data, pixeldata = bytearray(), bytearray() data += b'TOIf' for y in range(img.width()): for x in range(img.height()): rgb = img.pixel(x,y) r, g, b = qRed(rgb), qGreen(rgb), qBlue(rgb) c = ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | ((b & 0xF8) >> 3) pixeldata += struct.pack(">H", c) z = zlib.compressobj(level=9, wbits=10) zdata = z.compress(bytes(pixeldata)) + z.flush() zdata = zdata[2:-4] # strip header and checksum data += struct.pack("<HH", img.width(), img.height()) data += struct.pack("<I", len(zdata)) data += zdata return bytes(data) return qimg_to_toif(img, handler) # /read_and_convert_using_qt if hs_mono and not is_model_T: img = read_and_convert_using_qt_to_raw_mono(handler, filename, hs_cols, hs_rows) else: img = read_and_convert_using_qt_to_toif(handler, filename, hs_cols, hs_rows) if not img: return invoke_client('change_homescreen', img) def clear_homescreen(): invoke_client('change_homescreen', b'\x00') def set_pin(): invoke_client('set_pin', remove=False) def clear_pin(): invoke_client('set_pin', remove=True) def wipe_device(): wallet = window.wallet if wallet and sum(wallet.get_balance()): title = _("Confirm Device Wipe") msg = _("Are you SURE you want to wipe the device?\n" "Your wallet still has bitcoins in it!") if not self.question(msg, title=title, icon=QtWidgets.QMessageBox.Critical): return invoke_client('wipe_device', unpair_after=True) def slider_moved(): mins = timeout_slider.sliderPosition() timeout_minutes.setText(_("%2d minutes") % mins) def slider_released(): config.set_session_timeout(timeout_slider.sliderPosition() * 60) # Information tab info_tab = QtWidgets.QWidget() info_layout = QtWidgets.QVBoxLayout(info_tab) info_glayout = QtWidgets.QGridLayout() info_glayout.setColumnStretch(2, 1) device_label = QtWidgets.QLabel() pin_set_label = QtWidgets.QLabel() passphrases_label = QtWidgets.QLabel() version_label = QtWidgets.QLabel() device_id_label = QtWidgets.QLabel() bl_hash_label = QtWidgets.QLabel() bl_hash_label.setWordWrap(True) language_label = QtWidgets.QLabel() initialized_label = QtWidgets.QLabel() rows = [ (_("Device Label"), device_label), (_("PIN set"), pin_set_label), (_("Passphrases"), passphrases_label), (_("Firmware Version"), version_label), (_("Device ID"), device_id_label), (_("Bootloader Hash"), bl_hash_label), (_("Language"), language_label), (_("Initialized"), initialized_label), ] for row_num, (label, widget) in enumerate(rows): info_glayout.addWidget(QtWidgets.QLabel(label), row_num, 0) info_glayout.addWidget(widget, row_num, 1) info_layout.addLayout(info_glayout) # Settings tab settings_tab = QtWidgets.QWidget() settings_layout = QtWidgets.QVBoxLayout(settings_tab) settings_glayout = QtWidgets.QGridLayout() # Settings tab - Label label_msg = QtWidgets.QLabel(_("Name this {}. If you have multiple devices " "their labels help distinguish them.") .format(plugin.device)) label_msg.setWordWrap(True) label_label = QtWidgets.QLabel(_("Device Label")) label_edit = QtWidgets.QLineEdit() label_edit.setMinimumWidth(150) label_edit.setMaxLength(plugin.MAX_LABEL_LEN) label_apply = QtWidgets.QPushButton(_("Apply")) label_apply.clicked.connect(rename) label_edit.textChanged.connect(set_label_enabled) settings_glayout.addWidget(label_label, 0, 0) settings_glayout.addWidget(label_edit, 0, 1, 1, 2) settings_glayout.addWidget(label_apply, 0, 3) settings_glayout.addWidget(label_msg, 1, 1, 1, -1) # Settings tab - PIN pin_label = QtWidgets.QLabel(_("PIN Protection")) pin_button = QtWidgets.QPushButton() pin_button.clicked.connect(set_pin) settings_glayout.addWidget(pin_label, 2, 0) settings_glayout.addWidget(pin_button, 2, 1) pin_msg = QtWidgets.QLabel(_("PIN protection is strongly recommended. " "A PIN is your only protection against someone " "stealing your bitcoins if they obtain physical " "access to your {}.").format(plugin.device)) pin_msg.setWordWrap(True) pin_msg.setStyleSheet("color: red") settings_glayout.addWidget(pin_msg, 3, 1, 1, -1) # Settings tab - Homescreen homescreen_label = QtWidgets.QLabel(_("Homescreen")) homescreen_change_button = QtWidgets.QPushButton(_("Change...")) homescreen_clear_button = QtWidgets.QPushButton(_("Reset")) homescreen_change_button.clicked.connect(change_homescreen) homescreen_clear_button.clicked.connect(clear_homescreen) homescreen_msg = QtWidgets.QLabel(_("You can set the homescreen on your " "device to personalize it. You can choose any " "image and it will be dithered, scaled and " "converted to {} x {} {} " "for the device.").format(hs_cols, hs_rows, _("monochrome") if hs_mono else _("color"))) homescreen_msg.setWordWrap(True) settings_glayout.addWidget(homescreen_label, 4, 0) settings_glayout.addWidget(homescreen_change_button, 4, 1) settings_glayout.addWidget(homescreen_clear_button, 4, 2) settings_glayout.addWidget(homescreen_msg, 5, 1, 1, -1) # Settings tab - Session Timeout timeout_label = QtWidgets.QLabel(_("Session Timeout")) timeout_minutes = QtWidgets.QLabel() timeout_slider = QtWidgets.QSlider(Qt.Horizontal) timeout_slider.setRange(1, 60) timeout_slider.setSingleStep(1) timeout_slider.setTickInterval(5) timeout_slider.setTickPosition(QtWidgets.QSlider.TicksBelow) timeout_slider.setTracking(True) timeout_msg = QtWidgets.QLabel( _("Clear the session after the specified period " "of inactivity. Once a session has timed out, " "your PIN and passphrase (if enabled) must be " "re-entered to use the device.")) timeout_msg.setWordWrap(True) timeout_slider.setSliderPosition(config.get_session_timeout() // 60) slider_moved() timeout_slider.valueChanged.connect(slider_moved) timeout_slider.sliderReleased.connect(slider_released) settings_glayout.addWidget(timeout_label, 6, 0) settings_glayout.addWidget(timeout_slider, 6, 1, 1, 3) settings_glayout.addWidget(timeout_minutes, 6, 4) settings_glayout.addWidget(timeout_msg, 7, 1, 1, -1) settings_layout.addLayout(settings_glayout) settings_layout.addStretch(1) # Advanced tab advanced_tab = QtWidgets.QWidget() advanced_layout = QtWidgets.QVBoxLayout(advanced_tab) advanced_glayout = QtWidgets.QGridLayout() # Advanced tab - clear PIN clear_pin_button = QtWidgets.QPushButton(_("Disable PIN")) clear_pin_button.clicked.connect(clear_pin) clear_pin_warning = QtWidgets.QLabel( _("If you disable your PIN, anyone with physical access to your " "{} device can spend your bitcoins.").format(plugin.device)) clear_pin_warning.setWordWrap(True) clear_pin_warning.setStyleSheet("color: red") advanced_glayout.addWidget(clear_pin_button, 0, 2) advanced_glayout.addWidget(clear_pin_warning, 1, 0, 1, 5) # Advanced tab - toggle passphrase protection passphrase_button = QtWidgets.QPushButton() passphrase_button.clicked.connect(toggle_passphrase) passphrase_msg = WWLabel(PASSPHRASE_HELP) passphrase_warning = WWLabel(PASSPHRASE_NOT_PIN) passphrase_warning.setStyleSheet("color: red") advanced_glayout.addWidget(passphrase_button, 3, 2) advanced_glayout.addWidget(passphrase_msg, 4, 0, 1, 5) advanced_glayout.addWidget(passphrase_warning, 5, 0, 1, 5) # Advanced tab - wipe device wipe_device_button = QtWidgets.QPushButton(_("Wipe Device")) wipe_device_button.clicked.connect(wipe_device) wipe_device_msg = QtWidgets.QLabel( _("Wipe the device, removing all data from it. The firmware " "is left unchanged.")) wipe_device_msg.setWordWrap(True) wipe_device_warning = QtWidgets.QLabel( _("Only wipe a device if you have the recovery seed written down " "and the device wallet(s) are empty, otherwise the bitcoins " "will be lost forever.")) wipe_device_warning.setWordWrap(True) wipe_device_warning.setStyleSheet("color: red") advanced_glayout.addWidget(wipe_device_button, 6, 2) advanced_glayout.addWidget(wipe_device_msg, 7, 0, 1, 5) advanced_glayout.addWidget(wipe_device_warning, 8, 0, 1, 5) advanced_layout.addLayout(advanced_glayout) advanced_layout.addStretch(1) tabs = QtWidgets.QTabWidget(self) tabs.addTab(info_tab, _("Information")) tabs.addTab(settings_tab, _("Settings")) tabs.addTab(advanced_tab, _("Advanced")) dialog_vbox = QtWidgets.QVBoxLayout(self) dialog_vbox.addWidget(tabs) dialog_vbox.addLayout(Buttons(CloseButton(self))) # Update information invoke_client(None)
def __init__(self, window, plugin, payment_data): # We want to be a top-level window QDialog.__init__(self, parent=None) #print("PaymentDialog", "payment_data =", payment_data) self.payment_data = payment_data self.plugin = plugin # WARNING: Copying some attributes so PayToEdit() will work. self.main_window = window self.contacts = self.main_window.contacts self.is_max = self.main_window.is_max # Unused, as we do not use max. self.completions = self.main_window.completions self.count_labels = [ "Disabled", "Once", "Always", ] self.display_count_labels = [ "Always", ] run_always_index = self.count_labels.index("Always") # NOTE: User entered data, for verification purposes (enabling save/create), and subsequent dispatch on button press. self.value_description = "" self.value_amount = None self.value_payto_outputs = [] self.value_run_occurrences = self.count_labels.index("Always") self.set_flags(0 if self.payment_data is None else self. payment_data[PAYMENT_FLAGS]) if self.payment_data is not None: self.value_description = self.payment_data[PAYMENT_DESCRIPTION] self.value_amount = self.payment_data[PAYMENT_AMOUNT] self.value_run_occurrences = self.payment_data[PAYMENT_COUNT0] # NOTE: Set up the UI for this dialog. self.setMinimumWidth(350) if payment_data is None: self.setWindowTitle(_("Create New Scheduled Payment")) else: self.setWindowTitle(_("Edit Existing Scheduled Payment")) formLayout = QFormLayout() self.setLayout(formLayout) # Input fields. msg = _('Description of the payment (not mandatory).') + '\n\n' + _( 'The description is not sent to the recipient of the funds. It is stored in your wallet file, and displayed in the \'History\' tab.' ) self.description_label = HelpLabel(_('Description'), msg) self.description_edit = MyLineEdit() self.description_edit.setText(self.value_description) formLayout.addRow(self.description_label, self.description_edit) msg = _('How much to pay.') + '\n\n' + _('Unhelpful descriptive text') self.amount_label = HelpLabel(_('Amount'), msg) self.amount_e = BTCAmountEdit( window.get_decimal_point ) # WARNING: This has to be named this, as PayToEdit accesses it. self.amount_e.setAmount(self.value_amount) # WARNING: This needs to be present before PayToEdit is constructed (as that accesses it's attribute on this object), # but added to the layout after in order to try and reduce the "cleared amount" problem that happens when an address # is entered (perhaps on a selected completion, i.e. of a contact). # WARNING: This will clear the amount when an address is set, see PayToEdit.check_text. self.payto_edit = PayToEdit(self) msg = _('Recipient of the funds.') + '\n\n' + _( 'You may enter a Bitcoin Cash address, a label from your list of contacts (a list of completions will be proposed), or an alias (email-like address that forwards to a Bitcoin Cash address)' ) payto_label = HelpLabel(_('Pay to'), msg) formLayout.addRow(payto_label, self.payto_edit) def set_payment_address(address): self.payto_edit.payto_address = bitcoin.TYPE_ADDRESS, Address.from_string( address) self.value_payto_outputs = self.payto_edit.get_outputs(False) contact_name = None if address in window.wallet.contacts.keys(): contact_type, contact_name = window.wallet.contacts[address] if contact_name is not None: self.payto_edit.setText(contact_name + ' <' + address + '>') else: self.payto_edit.setText(address) if payment_data is not None: set_payment_address(payment_data[PAYMENT_ADDRESS]) completer = QCompleter() completer.setCaseSensitivity(False) self.payto_edit.setCompleter(completer) completer.setModel(self.completions) # WARNING: We created this before PayToEdit and add it to the layout after, due to the dependency issues with PayToEdit accessing `self.amount_e`. formLayout.addRow(self.amount_label, self.amount_e) if payment_data is not None: text = _("No payments made.") if payment_data[PAYMENT_DATELASTPAID] is not None: text = datetime.datetime.fromtimestamp( payment_data[PAYMENT_DATELASTPAID]).strftime("%c") textLabel = QLabel(text) label = HelpLabel( _('Last Paid'), _('Date last paid.') + '\n\n' + _('The date at which this scheduled payment was last meant to send a transaction to the network, which the user acted on' )) formLayout.addRow(label, textLabel) count_combo = QComboBox() count_combo.addItems(self.display_count_labels) count_combo.setCurrentIndex( self.display_count_labels.index( self.count_labels[self.value_run_occurrences])) msg = _('Repeat') + '\n\n' + _( 'The number of times the payment should be made.') label = HelpLabel(_('Repeat'), msg) formLayout.addRow(label, count_combo) # The setting will be cleared if the wallet somehow becomes unencrypted, and will only be available for unencrypted wallets. isEnabled = not self.main_window.wallet.has_password( ) and window.config.fee_per_kb() is not None self.value_autopayment = self.value_autopayment and isEnabled # Will show it for now, for encrypted wallets. Might be less confusing not to show it. self.autoPaymentCheckbox = QCheckBox( _("Make this payment automatically.")) self.autoPaymentCheckbox.setToolTip( _("Requirements") + ":\n" + _("1. The wallet must not have a password.") + "\n" + _("2. There must be a default fee/kb configured for the wallet." + "\n" + _("If this checkbox is interactive and not disabled, these requirements are met." ))) self.autoPaymentCheckbox.setChecked(self.value_autopayment) self.autoPaymentCheckbox.setEnabled(isEnabled) formLayout.addRow(_("Options"), self.autoPaymentCheckbox) import importlib from . import when_widget importlib.reload(when_widget) self.whenWidget = when_widget.WhenWidget(_("When")) self.whenWidget.setWhen( None if payment_data is None else payment_data[PAYMENT_WHEN]) formLayout.addRow(self.whenWidget) # NOTE: Hook up value events and provide handlers. def validate_input_values(): allow_commit = True allow_commit = allow_commit and len(self.value_description) > 0 allow_commit = allow_commit and self.value_amount is not None and self.value_amount > 0 allow_commit = allow_commit and len(self.value_payto_outputs) > 0 allow_commit = allow_commit and self.value_run_occurrences == run_always_index # allow_commit = allow_commit and self.value_run_occurrences > -1 and self.value_run_occurrences < len(count_labels) self.save_button.setEnabled(allow_commit) def on_run_occurrences_changed(unknown): self.value_run_occurrences = self.count_labels.index( self.display_count_labels[count_combo.currentIndex()]) validate_input_values() count_combo.currentIndexChanged.connect(on_run_occurrences_changed) def on_recipient_changed(): self.value_payto_outputs = self.payto_edit.get_outputs(False) validate_input_values() self.payto_edit.textChanged.connect(on_recipient_changed) def on_amount_changed(): self.value_amount = self.amount_e.get_amount() validate_input_values() self.amount_e.textChanged.connect(on_amount_changed) def on_description_changed(): self.value_description = self.description_edit.text().strip() validate_input_values() self.description_edit.textChanged.connect(on_description_changed) def on_autopayment_toggled(v): self.value_autopayment = v == Qt.Checked self.autoPaymentCheckbox.stateChanged.connect(on_autopayment_toggled) # Buttons at bottom right. save_button_text = _("Save") if payment_data is None: save_button_text = _("Create") self.save_button = b = QPushButton(save_button_text) b.clicked.connect(self.save) self.cancel_button = b = QPushButton(_("Cancel")) b.clicked.connect(self.close) b.setDefault(True) self.buttons = [self.save_button, self.cancel_button] hbox = QHBoxLayout() #hbox.addLayout(Buttons(*self.sharing_buttons)) hbox.addStretch(1) hbox.addLayout(Buttons(*self.buttons)) formLayout.addRow(hbox) validate_input_values() self.update()
def __init__(self, window, plugin, keystore, device_id): title = _("{} Settings").format(plugin.device) super(SettingsDialog, self).__init__(window, title) # NB: below breaks layout on some platforms. Better to let the layout # manager do its thing. #self.setMaximumWidth(540) devmgr = plugin.device_manager() config = devmgr.config handler = keystore.handler thread = keystore.thread def invoke_client(method, *args, **kw_args): unpair_after = kw_args.pop('unpair_after', False) def task(): client = devmgr.client_by_id(device_id) if not client: raise RuntimeError("Device not connected") if method: getattr(client, method)(*args, **kw_args) if unpair_after: devmgr.unpair_id(device_id) return client.features thread.add(task, on_success=update) def update(features): self.features = features set_label_enabled() bl_hash = bh2u(features.bootloader_hash) bl_hash = "\n".join([bl_hash[:32], bl_hash[32:]]) noyes = [_("No"), _("Yes")] endis = [_("Enable Passphrases"), _("Disable Passphrases")] disen = [_("Disabled"), _("Enabled")] setchange = [_("Set a PIN"), _("Change PIN")] version = "%d.%d.%d" % (features.major_version, features.minor_version, features.patch_version) coins = ", ".join(coin.coin_name for coin in features.coins) device_label.setText(features.label) pin_set_label.setText(noyes[features.pin_protection]) passphrases_label.setText(disen[features.passphrase_protection]) bl_hash_label.setText(bl_hash) label_edit.setText(features.label) device_id_label.setText(features.device_id) initialized_label.setText(noyes[features.initialized]) version_label.setText(version) coins_label.setText(coins) clear_pin_button.setVisible(features.pin_protection) clear_pin_warning.setVisible(features.pin_protection) pin_button.setText(setchange[features.pin_protection]) pin_msg.setVisible(not features.pin_protection) passphrase_button.setText(endis[features.passphrase_protection]) language_label.setText(features.language) def set_label_enabled(): label_apply.setEnabled(label_edit.text() != self.features.label) def rename(): invoke_client('change_label', label_edit.text()) def toggle_passphrase(): title = _("Confirm Toggle Passphrase Protection") currently_enabled = self.features.passphrase_protection if currently_enabled: msg = _("After disabling passphrases, you can only pair this " f"{PROJECT_NAME} wallet if it had an empty passphrase. " "If its passphrase was not empty, you will need to " "create a new wallet with the install wizard. You " "can use this wallet again at any time by re-enabling " "passphrases and entering its passphrase.") else: msg = _(f"Your current {PROJECT_NAME} wallet can only be used " "with an empty passphrase. You must create a separate " "wallet with the install wizard for other passphrases " "as each one generates a new set of addresses.") msg += "\n\n" + _("Are you sure you want to proceed?") if not self.question(msg, title=title): return invoke_client('toggle_passphrase', unpair_after=currently_enabled) def set_pin(): invoke_client('set_pin', remove=False) def clear_pin(): invoke_client('set_pin', remove=True) def wipe_device(): wallet = window.wallet if wallet and sum(wallet.get_balance()): title = _("Confirm Device Wipe") msg = _("Are you SURE you want to wipe the device?\n" "Your wallet still has bitcoins in it!") if not self.question(msg, title=title, icon=QMessageBox.Critical): return invoke_client('wipe_device', unpair_after=True) def slider_moved(): mins = timeout_slider.sliderPosition() timeout_minutes.setText(_("{:2d} minutes").format(mins)) def slider_released(): config.set_session_timeout(timeout_slider.sliderPosition() * 60) # Information tab info_tab = QWidget() info_layout = QVBoxLayout(info_tab) info_glayout = QGridLayout() info_glayout.setColumnStretch(2, 1) device_label = QLabel() pin_set_label = QLabel() passphrases_label = QLabel() version_label = QLabel() device_id_label = QLabel() bl_hash_label = QLabel() bl_hash_label.setWordWrap(True) coins_label = QLabel() coins_label.setWordWrap(True) language_label = QLabel() initialized_label = QLabel() rows = [ (_("Device Label"), device_label), (_("PIN set"), pin_set_label), (_("Passphrases"), passphrases_label), (_("Firmware Version"), version_label), (_("Device ID"), device_id_label), (_("Bootloader Hash"), bl_hash_label), (_("Supported Coins"), coins_label), (_("Language"), language_label), (_("Initialized"), initialized_label), ] for row_num, (label, widget) in enumerate(rows): info_glayout.addWidget(QLabel(label), row_num, 0) info_glayout.addWidget(widget, row_num, 1) info_layout.addLayout(info_glayout) # Settings tab settings_tab = QWidget() settings_layout = QVBoxLayout(settings_tab) settings_glayout = QGridLayout() # Settings tab - Label label_msg = QLabel(_("Name this {}. If you have multiple devices " "their labels help distinguish them.") .format(plugin.device)) label_msg.setWordWrap(True) label_label = QLabel(_("Device Label")) label_edit = QLineEdit() label_edit.setMinimumWidth(150) label_edit.setMaxLength(plugin.MAX_LABEL_LEN) label_apply = QPushButton(_("Apply")) label_apply.clicked.connect(rename) label_edit.textChanged.connect(set_label_enabled) settings_glayout.addWidget(label_label, 0, 0) settings_glayout.addWidget(label_edit, 0, 1, 1, 2) settings_glayout.addWidget(label_apply, 0, 3) settings_glayout.addWidget(label_msg, 1, 1, 1, -1) # Settings tab - PIN pin_label = QLabel(_("PIN Protection")) pin_button = QPushButton() pin_button.clicked.connect(set_pin) settings_glayout.addWidget(pin_label, 2, 0) settings_glayout.addWidget(pin_button, 2, 1) pin_msg = QLabel(_("PIN protection is strongly recommended. " "A PIN is your only protection against someone " "stealing your bitcoins if they obtain physical " "access to your {}.").format(plugin.device)) pin_msg.setWordWrap(True) pin_msg.setStyleSheet("color: red") settings_glayout.addWidget(pin_msg, 3, 1, 1, -1) # Settings tab - Session Timeout timeout_label = QLabel(_("Session Timeout")) timeout_minutes = QLabel() timeout_slider = QSlider(Qt.Horizontal) timeout_slider.setRange(1, 60) timeout_slider.setSingleStep(1) timeout_slider.setTickInterval(5) timeout_slider.setTickPosition(QSlider.TicksBelow) timeout_slider.setTracking(True) timeout_msg = QLabel( _("Clear the session after the specified period " "of inactivity. Once a session has timed out, " "your PIN and passphrase (if enabled) must be " "re-entered to use the device.")) timeout_msg.setWordWrap(True) timeout_slider.setSliderPosition(config.get_session_timeout() // 60) slider_moved() timeout_slider.valueChanged.connect(slider_moved) timeout_slider.sliderReleased.connect(slider_released) settings_glayout.addWidget(timeout_label, 6, 0) settings_glayout.addWidget(timeout_slider, 6, 1, 1, 3) settings_glayout.addWidget(timeout_minutes, 6, 4) settings_glayout.addWidget(timeout_msg, 7, 1, 1, -1) settings_layout.addLayout(settings_glayout) settings_layout.addStretch(1) # Advanced tab advanced_tab = QWidget() advanced_layout = QVBoxLayout(advanced_tab) advanced_glayout = QGridLayout() # Advanced tab - clear PIN clear_pin_button = QPushButton(_("Disable PIN")) clear_pin_button.clicked.connect(clear_pin) clear_pin_warning = QLabel( _("If you disable your PIN, anyone with physical access to your " "{} device can spend your bitcoins.").format(plugin.device)) clear_pin_warning.setWordWrap(True) clear_pin_warning.setStyleSheet("color: red") advanced_glayout.addWidget(clear_pin_button, 0, 2) advanced_glayout.addWidget(clear_pin_warning, 1, 0, 1, 5) # Advanced tab - toggle passphrase protection passphrase_button = QPushButton() passphrase_button.clicked.connect(toggle_passphrase) passphrase_msg = WWLabel(PASSPHRASE_HELP) passphrase_warning = WWLabel(PASSPHRASE_NOT_PIN) passphrase_warning.setStyleSheet("color: red") advanced_glayout.addWidget(passphrase_button, 3, 2) advanced_glayout.addWidget(passphrase_msg, 4, 0, 1, 5) advanced_glayout.addWidget(passphrase_warning, 5, 0, 1, 5) # Advanced tab - wipe device wipe_device_button = QPushButton(_("Wipe Device")) wipe_device_button.clicked.connect(wipe_device) wipe_device_msg = QLabel( _("Wipe the device, removing all data from it. The firmware " "is left unchanged.")) wipe_device_msg.setWordWrap(True) wipe_device_warning = QLabel( _("Only wipe a device if you have the recovery seed written down " "and the device wallet(s) are empty, otherwise the bitcoins " "will be lost forever.")) wipe_device_warning.setWordWrap(True) wipe_device_warning.setStyleSheet("color: red") advanced_glayout.addWidget(wipe_device_button, 6, 2) advanced_glayout.addWidget(wipe_device_msg, 7, 0, 1, 5) advanced_glayout.addWidget(wipe_device_warning, 8, 0, 1, 5) for i in range(advanced_glayout.count()): # This loop is needed to lay out the word-wrap labels properly # so that they grow down rather than across. w = advanced_glayout.itemAt(i).widget() if isinstance(w, QLabel) and w.wordWrap(): sp = w.sizePolicy() sp.setHorizontalPolicy(QSizePolicy.Ignored) sp.setVerticalPolicy(QSizePolicy.MinimumExpanding) w.setSizePolicy(sp) advanced_layout.addLayout(advanced_glayout) advanced_layout.addStretch(1) tabs = QTabWidget(self) tabs.addTab(info_tab, _("Information")) tabs.addTab(settings_tab, _("Settings")) tabs.addTab(advanced_tab, _("Advanced")) dialog_vbox = QVBoxLayout(self) dialog_vbox.addWidget(tabs) dialog_vbox.addLayout(Buttons(CloseButton(self))) # Update information invoke_client(None)
def __init__(self, wallet_ui, parent): super().__init__(parent=parent, title=_("ChainTipper - Wallet-specific Settings")) self.setWindowIcon(icon_chaintip) self.wallet_ui = wallet_ui self.wallet = self.wallet_ui.wallet # TODO: remove and refactor to increase code clarity? self.idx2confkey = dict() # int -> 'normal', 'consolidate', etc.. self.confkey2idx = dict() # str 'normal', 'consolidate', etc -> int assert not hasattr(self.wallet, '_chaintipper_settings_window') main_window = self.wallet.weak_window() assert main_window self.wallet._chaintipper_settings_window = self main_layout = QVBoxLayout(self) # header #main_layout.addWidget(QLabel(_('ChainTipper - settings for wallet "{wallet_name}"').format(wallet_name=self.wallet_ui.wallet_name)), 0, 0, Qt.AlignRight) # --- group for startup settings gbox = QGroupBox(_("Behaviour")) grid = QGridLayout(gbox) main_layout.addWidget(gbox) # active when wallet opens self.cb_activate_on_open = QCheckBox( _("Activate ChainTipper when wallet '{wallet_name}'' is opened."). format(wallet_name=self.wallet_ui.wallet_name)) self.cb_activate_on_open.setChecked( read_config(self.wallet, "activate_on_wallet_open")) def on_cb_activate_on_open(): write_config(self.wallet, "activate_on_wallet_open", self.cb_activate_on_open.isChecked()) self.cb_activate_on_open.stateChanged.connect(on_cb_activate_on_open) grid.addWidget(self.cb_activate_on_open) # mark read digested self.cb_mark_read_digested_tips = QCheckBox( _("Keep my inbox clean by marking messages/comments as read when they are digested" )) self.cb_mark_read_digested_tips.setChecked( read_config(self.wallet, "mark_read_digested_tips")) def on_cb_mark_read_digested_tips(): write_config(self.wallet, "mark_read_digested_tips", self.cb_mark_read_digested_tips.isChecked()) self.cb_mark_read_digested_tips.stateChanged.connect( on_cb_mark_read_digested_tips) grid.addWidget(self.cb_mark_read_digested_tips) # --- group Default Tip Amount ------------------------------------------------------------------------------------------ main_layout.addStretch(1) gbox = QGroupBox( _("Default Tip Amount (used when amount parsing fails)")) grid = QGridLayout(gbox) main_layout.addWidget(gbox) # amount grid.addWidget(QLabel(_('Amount')), 0, 1, Qt.AlignRight) self.default_amount = QLineEdit() self.default_amount.setText(read_config(self.wallet, "default_amount")) def on_default_amount(): try: self.default_amount.setText( str(decimal.Decimal(self.default_amount.text()))) except decimal.InvalidOperation as e: self.show_error( _("Cannot parse {string} as decimal number. Please try again." ).format(string=self.default_amount.text())) self.default_amount.setText( read_config(self.wallet, "default_amount")) write_config(self.wallet, "default_amount", self.default_amount.text()) self.default_amount.editingFinished.connect(on_default_amount) grid.addWidget(self.default_amount, 0, 2) # currency self.currencies = sorted( self.wallet_ui.window.fx.get_currencies( self.wallet_ui.window.fx.get_history_config())) grid.addWidget(QLabel(_('Currency')), 1, 1, Qt.AlignRight) self.default_amount_currency = QComboBox() self.default_amount_currency.addItems(self.currencies) self.default_amount_currency.setCurrentIndex( self.default_amount_currency.findText( read_config(self.wallet, "default_amount_currency"))) def on_default_amount_currency(): write_config( self.wallet, "default_amount_currency", self.currencies[self.default_amount_currency.currentIndex()]) self.default_amount_currency.currentIndexChanged.connect( on_default_amount_currency) grid.addWidget(self.default_amount_currency, 1, 2) # --- group Linked Default Tip Amount ---------------------------------------------------------------------------------- main_layout.addStretch(1) self.gbox_linked_amount = QGroupBox( _("Special Linked Default Tip Amount (used when amount parsing fails and recipient has linked an address)" )) self.gbox_linked_amount.setCheckable(True) self.gbox_linked_amount.setChecked( read_config(self.wallet, "use_linked_amount")) grid = QGridLayout(self.gbox_linked_amount) main_layout.addWidget(self.gbox_linked_amount) def on_gbox_linked_amount(): write_config(self.wallet, "use_linked_amount", self.gbox_linked_amount.isChecked()) self.gbox_linked_amount.toggled.connect(on_gbox_linked_amount) # amount grid.addWidget(QLabel(_('Amount')), 0, 1, Qt.AlignRight) self.default_linked_amount = QLineEdit() self.default_linked_amount.setText( read_config(self.wallet, "default_linked_amount")) def on_default_linked_amount(): try: self.default_linked_amount.setText( str(decimal.Decimal(self.default_linked_amount.text()))) except decimal.InvalidOperation as e: self.show_error( _("Cannot parse {string} as decimal number. Please try again." ).format(string=self.default_linked_amount.text())) self.default_linked_amount.setText( read_config(self.wallet, "default_linked_amount")) write_config(self.wallet, "default_linked_amount", self.default_linked_amount.text()) self.default_linked_amount.editingFinished.connect( on_default_linked_amount) grid.addWidget(self.default_linked_amount, 0, 2) # currency self.currencies = sorted( self.wallet_ui.window.fx.get_currencies( self.wallet_ui.window.fx.get_history_config())) grid.addWidget(QLabel(_('Currency')), 1, 1, Qt.AlignRight) self.default_linked_amount_currency = QComboBox() self.default_linked_amount_currency.addItems(self.currencies) self.default_linked_amount_currency.setCurrentIndex( self.default_linked_amount_currency.findText( read_config(self.wallet, "default_linked_amount_currency"))) def on_default_linked_amount_currency(): write_config( self.wallet, "default_linked_amount_currency", self.currencies[ self.default_linked_amount_currency.currentIndex()]) self.default_linked_amount_currency.currentIndexChanged.connect( on_default_linked_amount_currency) grid.addWidget(self.default_linked_amount_currency, 1, 2) # --- group autopay --------------------------------------------------------------------------------------------------- main_layout.addStretch(1) self.gbox_autopay = QGroupBox( _("AutoPay - Automatically pay unpaid tips")) self.gbox_autopay.setCheckable(True) self.gbox_autopay.setChecked(read_config(self.wallet, "autopay")) vbox = QVBoxLayout(self.gbox_autopay) main_layout.addWidget(self.gbox_autopay) def on_gbox_autopay(): write_config(self.wallet, "autopay", self.gbox_autopay.isChecked()) #on_cb_autopay_limit() self.gbox_autopay.toggled.connect(on_gbox_autopay) # disallow autopay when default amount is used self.cb_autopay_disallow_default = QCheckBox( _("Disallow AutoPay when Default Tip Amount is used")) self.cb_autopay_disallow_default.setChecked( read_config(self.wallet, "autopay_disallow_default")) def on_cb_autopay_disallow_default(): write_config(self.wallet, "autopay_disallow_default", self.cb_autopay_disallow_default.isChecked()) self.cb_autopay_disallow_default.stateChanged.connect( on_cb_autopay_disallow_default) vbox.addWidget(self.cb_autopay_disallow_default) # autopay limit checkbox self.cb_autopay_limit = QCheckBox(_("Limit AutoPay Amount")) self.cb_autopay_limit.setChecked( read_config(self.wallet, "autopay_use_limit")) def on_cb_autopay_limit(): self.autopay_limit_bch_label.setEnabled( self.gbox_autopay.isChecked() and self.cb_autopay_limit.isChecked()) self.autopay_limit_bch.setEnabled( self.gbox_autopay.isChecked() and self.cb_autopay_limit.isChecked()) write_config(self.wallet, "autopay_use_limit", self.cb_autopay_limit.isChecked()) self.cb_autopay_limit.stateChanged.connect(on_cb_autopay_limit) vbox.addWidget(self.cb_autopay_limit) # autopay limit (amount) hbox = QHBoxLayout() vbox.addLayout(hbox) self.autopay_limit_bch_label = QLabel(_('AutoPay Limit (BCH per Tip)')) hbox.addWidget(self.autopay_limit_bch_label, 10, Qt.AlignRight) self.autopay_limit_bch = QLineEdit() self.autopay_limit_bch.setText( read_config(self.wallet, "autopay_limit_bch")) def on_autopay_limit_bch(): write_config(self.wallet, "autopay_limit_bch", self.autopay_limit_bch.text()) self.autopay_limit_bch.editingFinished.connect(on_autopay_limit_bch) hbox.addWidget(self.autopay_limit_bch, 40) # ensure correct enable state #on_cb_autopay() # close button cbut = CloseButton(self) main_layout.addLayout(Buttons(cbut)) cbut.setDefault(False) cbut.setAutoDefault(False)
def __init__(self, window, plugin, payment_data): # We want to be a top-level window QDialog.__init__(self, parent=None) #print("PaymentDialog", "payment_data =", payment_data) self.payment_data = payment_data self.plugin = plugin # WARNING: Copying some attributes so PayToEdit() will work. self.main_window = window self.contacts = self.main_window.contacts self.completions = self.main_window.completions self.count_labels = [ "Disabled", "Once", "Always", ] self.display_count_labels = [ "Always", ] run_always_index = self.count_labels.index("Always") # NOTE: User entered data, for verification purposes (enabling save/create), and subsequent dispatch on button press. self.value_description = "" self.value_amount = None self.value_payto_outputs = [] self.value_run_occurrences = self.count_labels.index("Always") self.set_flags(0 if self.payment_data is None else self. payment_data[PAYMENT_FLAGS]) payment_was_fiat = False if self.payment_data is not None: self.value_description = self.payment_data[PAYMENT_DESCRIPTION] self.value_amount = abs(self.payment_data[PAYMENT_AMOUNT]) payment_was_fiat = self.payment_data[ PAYMENT_FLAGS] & PAYMENT_FLAG_AMOUNT_IS_FIAT self.value_run_occurrences = self.payment_data[PAYMENT_COUNT0] # NOTE: Set up the UI for this dialog. self.setMinimumWidth(500) if payment_data is None: self.setWindowTitle(_("Create New Scheduled Payment")) else: self.setWindowTitle(_("Edit Existing Scheduled Payment")) formLayout = QFormLayout() self.setLayout(formLayout) formLayout.setFieldGrowthPolicy(QFormLayout.ExpandingFieldsGrow) # Input fields. msg = _('Description of the payment (not mandatory).') + '\n\n' + _( 'The description is not sent to the recipient of the funds. It is stored in your wallet file, and displayed in the \'History\' tab.' ) self.description_label = HelpLabel(_('Description'), msg) self.description_edit = MyLineEdit() self.description_edit.setText(self.value_description) formLayout.addRow(self.description_label, self.description_edit) msg = _('How much to pay.') + '\n\n' + _('Unhelpful descriptive text') self.amount_label = HelpLabel(_('Amount'), msg) self.amount_e = BTCAmountEdit( window.get_decimal_point ) # WARNING: This has to be named this, as PayToEdit accesses it. if not payment_was_fiat: self.amount_e.setAmount(self.value_amount) else: self.amount_e.setHidden(True) # WARNING: This needs to be present before PayToEdit is constructed (as that accesses it's attribute on this object), # but added to the layout after in order to try and reduce the "cleared amount" problem that happens when an address # is entered (perhaps on a selected completion, i.e. of a contact). # WARNING: This will clear the amount when an address is set, see PayToEdit.check_text. self.payto_edit = PayToEdit(self) self.payto_edit.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Preferred) msg = _('Recipient of the funds.') + '\n\n' + _( 'You may enter a Bitcoin Cash address, a label from your list of contacts (a list of completions will be proposed), or an alias (email-like address that forwards to a Bitcoin Cash address)' ) payto_label = HelpLabel(_('Pay to'), msg) formLayout.addRow(payto_label, self.payto_edit) def set_payment_address(address): self.payto_edit.payto_address = bitcoin.TYPE_ADDRESS, Address.from_string( address) self.value_payto_outputs = self.payto_edit.get_outputs(False) contact_name = None contacts = window.wallet.contacts if isinstance(contacts, dict): # old contacts API if address in contacts.keys(): _ign, contact_name = contacts[address] else: # new contacts API - find the contact, extract type & name try: for c in contacts.find(address=address): contact_name = c.name except Exception as e: print_error("Could not determine contacts API, giving up:", repr(e)) if contact_name is not None: self.payto_edit.setText(contact_name + ' <' + address + '>') else: if Address.is_valid(address): address = Address.from_string(address).to_ui_string() self.payto_edit.setText(address) if payment_data is not None: set_payment_address(payment_data[PAYMENT_ADDRESS]) completer = QCompleter() completer.setCaseSensitivity(False) self.payto_edit.setCompleter(completer) completer.setModel(self.completions) amount_hbox = QHBoxLayout() self.useFiatCheckbox = QCheckBox( _("Denomiate payment in FIAT rather than BCH")) self.useFiatCheckbox.setToolTip( _("If you elect to denomiate the payment in FIAT, then the BCH transaction\nuses the FIAT price at the time of payment to determine how much\nactual BCH is transmitted to your payee." ) + "\n\n" + _("Requirements") + ":\n" + _("1. You must have a fiat currency defined and a server enabled in settings." ) + "\n" + _("2. The fiat spot price quote must but be available from the FX server." + "\n" + _("If this checkbox is interactive and not disabled, these requirements are met." ))) isFiatEnabled = self.plugin.can_do_fiat(self.main_window) self.useFiatCheckbox.setChecked(payment_was_fiat) self.useFiatCheckbox.setEnabled(isFiatEnabled) self.fiat_amount_e = AmountEdit( self.main_window.fx.get_currency if isFiatEnabled else '') self.fiat_amount_e.setHidden(not payment_was_fiat) if payment_was_fiat: self.fiat_amount_e.setText(str(self.value_amount)) amount_hbox.addWidget( self.amount_e ) # either this or fiat_amount_e are visible at any 1 time amount_hbox.addWidget( self.fiat_amount_e ) # either this or amoune_e are visible at any 1 time amount_hbox.addWidget(self.useFiatCheckbox) def useFiatToggled(b): # Note we do it this explicit way because the order matters to avoid quick visual glitches as widgets # pop into and out of existence. Hiding the visible one then revealing the invisible one is the best way # to avoid glitches. The reverse causes a slight detectable spastication of the UI. :/ -Calin if b: self.amount_e.setHidden(True) self.fiat_amount_e.setHidden(False) else: self.fiat_amount_e.setHidden(True) self.amount_e.setHidden(False) self.useFiatCheckbox.toggled.connect(useFiatToggled) # WARNING: We created this before PayToEdit and add it to the layout after, due to the dependency issues with PayToEdit accessing `self.amount_e`. formLayout.addRow(self.amount_label, amount_hbox) if payment_data is not None: text = _("No payments made.") if payment_data[PAYMENT_DATELASTPAID] is not None: text = datetime.datetime.fromtimestamp( payment_data[PAYMENT_DATELASTPAID]).strftime("%c") textLabel = QLabel(text) label = HelpLabel( _('Last Paid'), _('Date last paid.') + '\n\n' + _('The date at which this scheduled payment was last meant to send a transaction to the network, which the user acted on' )) formLayout.addRow(label, textLabel) count_combo = QComboBox() count_combo.addItems(self.display_count_labels) count_combo.setCurrentIndex( self.display_count_labels.index( self.count_labels[self.value_run_occurrences])) msg = _('Repeat') + '\n\n' + _( 'The number of times the payment should be made.') label = HelpLabel(_('Repeat'), msg) formLayout.addRow(label, count_combo) # The setting will be cleared if the wallet somehow becomes unencrypted, and will only be available for unencrypted wallets. isEnabled = not self.main_window.wallet.has_password( ) and window.config.fee_per_kb() is not None self.value_autopayment = self.value_autopayment and isEnabled # Will show it for now, for encrypted wallets. Might be less confusing not to show it. options_hbox = QHBoxLayout() self.autoPaymentCheckbox = QCheckBox( _("Make this payment automatically")) self.autoPaymentCheckbox.setToolTip( _("Requirements") + ":\n" + _("1. The wallet must not have a password.") + "\n" + _("2. There must be a default fee/kb configured for the wallet." + "\n" + _("If this checkbox is interactive and not disabled, these requirements are met." ))) self.autoPaymentCheckbox.setChecked(self.value_autopayment) self.autoPaymentCheckbox.setEnabled(isEnabled) options_hbox.addWidget(self.autoPaymentCheckbox) formLayout.addRow(_("Options"), options_hbox) import importlib from . import when_widget importlib.reload(when_widget) self.whenWidget = when_widget.WhenWidget(_("When")) self.whenWidget.setWhen( None if payment_data is None else payment_data[PAYMENT_WHEN]) formLayout.addRow(self.whenWidget) # NOTE: Hook up value events and provide handlers. def validate_input_values(): allow_commit = True allow_commit = allow_commit and len(self.value_description) > 0 allow_commit = allow_commit and self.value_amount is not None and self.value_amount > 0 allow_commit = allow_commit and len(self.value_payto_outputs) > 0 allow_commit = allow_commit and self.value_run_occurrences == run_always_index # allow_commit = allow_commit and self.value_run_occurrences > -1 and self.value_run_occurrences < len(count_labels) self.save_button.setEnabled(allow_commit) def on_run_occurrences_changed(unknown): self.value_run_occurrences = self.count_labels.index( self.display_count_labels[count_combo.currentIndex()]) validate_input_values() count_combo.currentIndexChanged.connect(on_run_occurrences_changed) def on_recipient_changed(): self.value_payto_outputs = self.payto_edit.get_outputs(False) validate_input_values() self.payto_edit.textChanged.connect(on_recipient_changed) def on_amount_changed(): self.value_amount = self.amount_e.get_amount( ) if not self.useFiatCheckbox.isChecked() else float( self.fiat_amount_e.get_amount() or 0.00) validate_input_values() self.amount_e.textChanged.connect(on_amount_changed) self.fiat_amount_e.textChanged.connect(on_amount_changed) self.useFiatCheckbox.toggled.connect(on_amount_changed) def on_description_changed(): self.value_description = self.description_edit.text().strip() validate_input_values() self.description_edit.textChanged.connect(on_description_changed) def on_autopayment_toggled(v): self.value_autopayment = v == Qt.Checked self.autoPaymentCheckbox.stateChanged.connect(on_autopayment_toggled) # Buttons at bottom right. save_button_text = _("Save") if payment_data is None: save_button_text = _("Create") self.save_button = b = QPushButton(save_button_text) b.clicked.connect(self.save) self.cancel_button = b = QPushButton(_("Cancel")) b.clicked.connect(self.close) b.setDefault(True) # pet peeve -- on macOS it's customary to have cancel on left, action-on-right in dialogs if sys.platform == 'darwin': self.buttons = [self.cancel_button, self.save_button] else: self.buttons = [self.save_button, self.cancel_button] hbox = QHBoxLayout() #hbox.addLayout(Buttons(*self.sharing_buttons)) hbox.addStretch(1) hbox.addLayout(Buttons(*self.buttons)) formLayout.addRow(hbox) validate_input_values() self.update()