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 __init__(self, data, parent=None, title="", show_text=False): WindowModalDialog.__init__(self, parent, title) vbox = QVBoxLayout() qrw = QRCodeWidget(data) vbox.addWidget(qrw, 1) if show_text: text = QTextEdit() text.setText(data) text.setReadOnly(True) vbox.addWidget(text) hbox = QHBoxLayout() hbox.addStretch(1) # Qt & Python GC hygiene: 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. weakSelf = util.Weak.ref(self) weakQ = util.Weak.ref(qrw) b = QPushButton(_("&Copy")) hbox.addWidget(b) weakBut = util.Weak.ref(b) b.clicked.connect(lambda: copy_to_clipboard(weakQ(), weakBut())) b = QPushButton(_("&Save")) hbox.addWidget(b) b.clicked.connect(lambda: save_to_file(weakQ(), weakSelf())) b = CloseButton(self) hbox.addWidget(b) vbox.addLayout(hbox) self.setLayout(vbox)
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 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 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(SatochipSettingsDialog, self).__init__(window, title) self.setMaximumWidth(540) devmgr = plugin.device_manager() config = devmgr.config handler = keystore.handler self.thread = thread = keystore.thread def connect_and_doit(): client = devmgr.client_by_id(device_id) if not client: raise RuntimeError("Device not connected") return client body = QtWidgets.QWidget() body_layout = QtWidgets.QVBoxLayout(body) grid = QtWidgets.QGridLayout() grid.setColumnStretch(3, 1) # see <http://doc.qt.io/archives/qt-4.8/richtext-html-subset.html> title = QtWidgets.QLabel('''<center> <span style="font-size: x-large">Satochip Wallet</span> <br><a href="https://satochip.io">satochip.io</a>''') title.setTextInteractionFlags(Qt.LinksAccessibleByMouse) grid.addWidget(title, 0, 0, 1, 2, Qt.AlignHCenter) y = 3 rows = [ ('fw_version', _("Firmware Version")), ('sw_version', _("Electrum Support")), ('is_seeded', _("Wallet seeded")), ('needs_2FA', _("Requires 2FA ")), ('needs_SC', _("Secure Channel")), ('card_label', _("Card label")), ] for row_num, (member_name, label) in enumerate(rows): widget = QtWidgets.QLabel('<tt>') widget.setTextInteractionFlags(Qt.TextSelectableByMouse | Qt.TextSelectableByKeyboard) grid.addWidget(QtWidgets.QLabel(label), y, 0, 1, 1, Qt.AlignRight) grid.addWidget(widget, y, 1, 1, 1, Qt.AlignLeft) setattr(self, member_name, widget) y += 1 body_layout.addLayout(grid) pin_btn = QtWidgets.QPushButton('Change PIN') def _change_pin(): thread.add(connect_and_doit, on_success=self.change_pin) pin_btn.clicked.connect(_change_pin) seed_btn = QtWidgets.QPushButton('Reset seed') def _reset_seed(): thread.add(connect_and_doit, on_success=self.reset_seed) thread.add(connect_and_doit, on_success=self.show_values) seed_btn.clicked.connect(_reset_seed) set_2FA_btn = QtWidgets.QPushButton('Enable 2FA') def _set_2FA(): thread.add(connect_and_doit, on_success=self.set_2FA) thread.add(connect_and_doit, on_success=self.show_values) set_2FA_btn.clicked.connect(_set_2FA) reset_2FA_btn = QtWidgets.QPushButton('Disable 2FA') def _reset_2FA(): thread.add(connect_and_doit, on_success=self.reset_2FA) thread.add(connect_and_doit, on_success=self.show_values) reset_2FA_btn.clicked.connect(_reset_2FA) verify_card_btn = QtWidgets.QPushButton('Verify card') def _verify_card(): thread.add(connect_and_doit, on_success=self.verify_card) verify_card_btn.clicked.connect(_verify_card) change_card_label_btn = QtWidgets.QPushButton('Change label') def _change_card_label(): thread.add(connect_and_doit, on_success=self.change_card_label) change_card_label_btn.clicked.connect(_change_card_label) y += 3 grid.addWidget(pin_btn, y, 0, 1, 2, Qt.AlignHCenter) y += 2 grid.addWidget(seed_btn, y, 0, 1, 2, Qt.AlignHCenter) y += 2 grid.addWidget(set_2FA_btn, y, 0, 1, 2, Qt.AlignHCenter) y += 2 grid.addWidget(reset_2FA_btn, y, 0, 1, 2, Qt.AlignHCenter) y += 2 grid.addWidget(verify_card_btn, y, 0, 1, 2, Qt.AlignHCenter) y += 2 grid.addWidget(change_card_label_btn, y, 0, 1, 2, Qt.AlignHCenter) y += 2 grid.addWidget(CloseButton(self), y, 0, 1, 2, Qt.AlignHCenter) dialog_vbox = QtWidgets.QVBoxLayout(self) dialog_vbox.addWidget(body) # Fetch values and show them thread.add(connect_and_doit, on_success=self.show_values)
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, 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)