Exemplo n.º 1
0
 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()
Exemplo n.º 2
0
    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)
Exemplo n.º 4
0
    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_())
Exemplo n.º 5
0
    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()
Exemplo n.º 6
0
    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()
Exemplo n.º 7
0
 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()
Exemplo n.º 8
0
 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()
Exemplo n.º 9
0
    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)
Exemplo n.º 10
0
    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)
Exemplo n.º 11
0
 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)
Exemplo n.º 12
0
    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
Exemplo n.º 13
0
    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))
Exemplo n.º 14
0
    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)
Exemplo n.º 15
0
 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_())
Exemplo n.º 16
0
    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'))
Exemplo n.º 17
0
    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!"))
Exemplo n.º 18
0
	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))
Exemplo n.º 19
0
 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())
Exemplo n.º 20
0
    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)
Exemplo n.º 21
0
    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)
Exemplo n.º 22
0
    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()
Exemplo n.º 23
0
    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)
Exemplo n.º 24
0
    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()