URI: 
       tGUI: Separate output selection and transaction finalization. - Output selection belongs in the Send tab. - Tx finalization is performed in a confirmation dialog (ConfirmTxDialog or PreviewTxDialog) - the fee slider is shown in the confirmation dialog - coin control works by selecting items in the coins tab - user can save invoices and pay them later - ConfirmTxDialog is used when opening channels and sweeping keys - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit dd6cb2caf7e1eff6a8af07a2e0363da371aa1b13
   DIR parent f8c84fbb1e5d316a734e386ccc55190957a607ee
  HTML Author: ThomasV <thomasv@electrum.org>
       Date:   Mon,  4 Nov 2019 08:40:06 +0100
       
       GUI: Separate output selection and transaction finalization.
        - Output selection belongs in the Send tab.
        - Tx finalization is performed in a confirmation dialog
          (ConfirmTxDialog or PreviewTxDialog)
        - the fee slider is shown in the confirmation dialog
        - coin control works by selecting items in the coins tab
        - user can save invoices and pay them later
        - ConfirmTxDialog is used when opening channels and sweeping keys
       
       Diffstat:
         M RELEASE-NOTES                       |       1 +
         A electrum/gui/qt/confirm_tx_dialog.… |     261 +++++++++++++++++++++++++++++++
         M electrum/gui/qt/invoice_list.py     |       6 ++++++
         M electrum/gui/qt/main_window.py      |     530 +++++--------------------------
         M electrum/gui/qt/settings_dialog.py  |      17 ++++++++---------
         M electrum/gui/qt/transaction_dialog… |     307 +++++++++++++++++++++++++++----
         M electrum/gui/qt/utxo_list.py        |      35 ++++++++++++++++++++++++++-----
       
       7 files changed, 655 insertions(+), 502 deletions(-)
       ---
   DIR diff --git a/RELEASE-NOTES b/RELEASE-NOTES
       t@@ -1,6 +1,7 @@
        # Release 4.0 - (Not released yet; release notes are incomplete)
        
         * Lightning Network
       + * Qt GUI: Separation between output selection and transaction finalization.
         * Http PayServer can be configured from GUI
        
        # Release 3.3.8 - (July 11, 2019)
   DIR diff --git a/electrum/gui/qt/confirm_tx_dialog.py b/electrum/gui/qt/confirm_tx_dialog.py
       t@@ -0,0 +1,261 @@
       +#!/usr/bin/env python
       +#
       +# Electrum - lightweight Bitcoin client
       +# Copyright (2019) The Electrum Developers
       +#
       +# Permission is hereby granted, free of charge, to any person
       +# obtaining a copy of this software and associated documentation files
       +# (the "Software"), to deal in the Software without restriction,
       +# including without limitation the rights to use, copy, modify, merge,
       +# publish, distribute, sublicense, and/or sell copies of the Software,
       +# and to permit persons to whom the Software is furnished to do so,
       +# subject to the following conditions:
       +#
       +# The above copyright notice and this permission notice shall be
       +# included in all copies or substantial portions of the Software.
       +#
       +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
       +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
       +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
       +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
       +# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
       +# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
       +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
       +# SOFTWARE.
       +
       +from typing import TYPE_CHECKING
       +import copy
       +
       +from PyQt5.QtCore import Qt, QSize
       +from PyQt5.QtGui import QTextCharFormat, QBrush, QFont
       +from PyQt5.QtWidgets import QHBoxLayout, QVBoxLayout, QLabel, QGridLayout, QPushButton, QWidget, QTextEdit, QLineEdit, QCheckBox
       +
       +from electrum.i18n import _
       +from electrum.util import quantize_feerate, NotEnoughFunds, NoDynamicFeeEstimates
       +from electrum.plugin import run_hook
       +from electrum.transaction import TxOutput
       +from electrum.simple_config import SimpleConfig, FEERATE_WARNING_HIGH_FEE
       +from electrum.wallet import InternalAddressCorruption
       +
       +from .util import WindowModalDialog, ButtonsLineEdit, ColorScheme, Buttons, CloseButton, FromList, HelpLabel, read_QIcon, char_width_in_lineedit, Buttons, CancelButton, OkButton
       +from .util import MONOSPACE_FONT
       +
       +from .fee_slider import FeeSlider
       +from .history_list import HistoryList, HistoryModel
       +from .qrtextedit import ShowQRTextEdit
       +
       +if TYPE_CHECKING:
       +    from .main_window import ElectrumWindow
       +
       +
       +
       +class TxEditor:
       +
       +    def __init__(self, window, inputs, outputs, external_keypairs):
       +        self.main_window = window
       +        self.outputs = outputs
       +        self.get_coins = inputs
       +        self.tx = None
       +        self.config = window.config
       +        self.wallet = window.wallet
       +        self.external_keypairs = external_keypairs
       +        self.not_enough_funds = False
       +        self.no_dynfee_estimates = False
       +        self.needs_update = False
       +        self.password_required = self.wallet.has_keystore_encryption() and not external_keypairs
       +        self.main_window.gui_object.timer.timeout.connect(self.timer_actions)
       +
       +    def timer_actions(self):
       +        if self.needs_update:
       +            self.update_tx()
       +            self.update()
       +            self.needs_update = False
       +
       +    def fee_slider_callback(self, dyn, pos, fee_rate):
       +        if dyn:
       +            if self.config.use_mempool_fees():
       +                self.config.set_key('depth_level', pos, False)
       +            else:
       +                self.config.set_key('fee_level', pos, False)
       +        else:
       +            self.config.set_key('fee_per_kb', fee_rate, False)
       +        self.needs_update = True
       +
       +    def get_fee_estimator(self):
       +        return None
       +
       +    def update_tx(self):
       +        fee_estimator = self.get_fee_estimator()
       +        is_sweep = bool(self.external_keypairs)
       +        coins = self.get_coins()
       +        # deepcopy outputs because '!' is converted to number
       +        outputs = copy.deepcopy(self.outputs)
       +        make_tx = lambda fee_est: self.wallet.make_unsigned_transaction(
       +            coins=coins,
       +            outputs=outputs,
       +            fee=fee_est,
       +            is_sweep=is_sweep)
       +        try:
       +            self.tx = make_tx(fee_estimator)
       +            self.not_enough_funds = False
       +            self.no_dynfee_estimates = False
       +        except NotEnoughFunds:
       +            self.not_enough_funds = True
       +            self.tx = None
       +            return
       +        except NoDynamicFeeEstimates:
       +            self.no_dynfee_estimates = True
       +            self.tx = None
       +            try:
       +                self.tx = make_tx(0)
       +            except BaseException:
       +                return
       +        except InternalAddressCorruption as e:
       +            self.tx = None
       +            self.main_window.show_error(str(e))
       +            raise
       +        except BaseException as e:
       +            self.tx = None
       +            self.main_window.logger.exception('')
       +            self.show_message(str(e))
       +            return
       +        use_rbf = bool(self.config.get('use_rbf', True))
       +        if use_rbf:
       +            self.tx.set_rbf(True)
       +
       +
       +
       +
       +
       +
       +class ConfirmTxDialog(TxEditor, WindowModalDialog):
       +    # set fee and return password (after pw check)
       +
       +    def __init__(self, window: 'ElectrumWindow', inputs, outputs, external_keypairs):
       +
       +        TxEditor.__init__(self, window, inputs, outputs, external_keypairs)
       +        WindowModalDialog.__init__(self, window, _("Confirm Transaction"))
       +        vbox = QVBoxLayout()
       +        self.setLayout(vbox)
       +        grid = QGridLayout()
       +        vbox.addLayout(grid)
       +        self.amount_label = QLabel('')
       +        grid.addWidget(QLabel(_("Amount to be sent") + ": "), 0, 0)
       +        grid.addWidget(self.amount_label, 0, 1)
       +
       +        msg = _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
       +              + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
       +              + _('A suggested fee is automatically added to this field. You may override it. The suggested fee increases with the size of the transaction.')
       +        self.fee_label = QLabel('')
       +        grid.addWidget(HelpLabel(_("Mining fee") + ": ", msg), 1, 0)
       +        grid.addWidget(self.fee_label, 1, 1)
       +
       +        self.extra_fee_label = QLabel(_("Additional fees") + ": ")
       +        self.extra_fee_label.setVisible(False)
       +        self.extra_fee_value = QLabel('')
       +        self.extra_fee_value.setVisible(False)
       +        grid.addWidget(self.extra_fee_label, 2, 0)
       +        grid.addWidget(self.extra_fee_value, 2, 1)
       +
       +        self.fee_slider = FeeSlider(self, self.config, self.fee_slider_callback)
       +        grid.addWidget(self.fee_slider, 5, 1)
       +
       +        self.message_label = QLabel(self.default_message())
       +        grid.addWidget(self.message_label, 6, 0, 1, -1)
       +        self.pw_label = QLabel(_('Password'))
       +        self.pw_label.setVisible(self.password_required)
       +        self.pw = QLineEdit()
       +        self.pw.setEchoMode(2)
       +        self.pw.setVisible(self.password_required)
       +        grid.addWidget(self.pw_label, 8, 0)
       +        grid.addWidget(self.pw, 8, 1, 1, -1)
       +        vbox.addLayout(grid)
       +        self.preview_button = QPushButton(_('Advanced'))
       +        self.preview_button.clicked.connect(self.on_preview)
       +        grid.addWidget(self.preview_button, 0, 2)
       +        self.send_button = QPushButton(_('Send'))
       +        self.send_button.clicked.connect(self.on_send)
       +        self.send_button.setDefault(True)
       +        vbox.addLayout(Buttons(CancelButton(self), self.send_button))
       +        self.update_tx()
       +        self.update()
       +        self.is_send = False
       +
       +    def default_message(self):
       +        return _('Enter your password to proceed') if self.password_required else _('Click Send to proceed')
       +
       +    def on_preview(self):
       +        self.accept()
       +
       +    def run(self):
       +        cancelled = not self.exec_()
       +        password = self.pw.text() or None
       +        return cancelled, self.is_send, password, self.tx
       +
       +    def on_send(self):
       +        password = self.pw.text() or None
       +        if self.password_required:
       +            if password is None:
       +                return
       +            try:
       +                self.wallet.check_password(password)
       +            except Exception as e:
       +                self.main_window.show_error(str(e), parent=self)
       +                return
       +        self.is_send = True
       +        self.accept()
       +
       +    def disable(self, reason):
       +        self.message_label.setStyleSheet(ColorScheme.RED.as_stylesheet())
       +        self.message_label.setText(reason)
       +        self.pw.setEnabled(False)
       +        self.send_button.setEnabled(False)
       +
       +    def enable(self):
       +        self.message_label.setStyleSheet(None)
       +        self.message_label.setText(self.default_message())
       +        self.pw.setEnabled(True)
       +        self.send_button.setEnabled(True)
       +
       +    def update(self):
       +        tx = self.tx
       +        output_values = [x.value for x in self.outputs]
       +        is_max = '!' in output_values
       +        amount = tx.output_value() if is_max else sum(output_values)
       +        self.amount_label.setText(self.main_window.format_amount_and_units(amount))
       +
       +        if self.not_enough_funds:
       +            text = _("Not enough funds")
       +            c, u, x = self.wallet.get_frozen_balance()
       +            if c+u+x:
       +                text += " ({} {} {})".format(
       +                    self.main_window.format_amount(c + u + x).strip(), self.main_window.base_unit(), _("are frozen")
       +                )
       +            self.disable(text)
       +            return
       +
       +        if not tx:
       +            return
       +
       +        fee = tx.get_fee()
       +        self.fee_label.setText(self.main_window.format_amount_and_units(fee))
       +        x_fee = run_hook('get_tx_extra_fee', self.wallet, tx)
       +        if x_fee:
       +            x_fee_address, x_fee_amount = x_fee
       +            self.extra_fee_label.setVisible(True)
       +            self.extra_fee_value.setVisible(True)
       +            self.extra_fee_value.setText(self.main_window.format_amount_and_units(x_fee_amount))
       +
       +        feerate_warning = FEERATE_WARNING_HIGH_FEE
       +        low_fee = fee < self.wallet.relayfee() * tx.estimated_size() / 1000
       +        high_fee = fee > feerate_warning * tx.estimated_size() / 1000
       +        if low_fee:
       +            msg = '\n'.join([
       +                _("This transaction requires a higher fee, or it will not be propagated by your current server"),
       +                _("Try to raise your transaction fee, or use a server with a lower relay fee.")
       +            ])
       +            self.disable(msg)
       +        elif high_fee:
       +            self.disable(_('Warning') + ': ' + _("The fee for this transaction seems unusually high."))
       +        else:
       +            self.enable()
   DIR diff --git a/electrum/gui/qt/invoice_list.py b/electrum/gui/qt/invoice_list.py
       t@@ -27,6 +27,7 @@ from enum import IntEnum
        
        from PyQt5.QtCore import Qt, QItemSelectionModel
        from PyQt5.QtGui import QStandardItemModel, QStandardItem, QFont
       +from PyQt5.QtWidgets import QAbstractItemView
        from PyQt5.QtWidgets import QHeaderView, QMenu, QVBoxLayout, QGridLayout, QLabel, QTreeWidget, QTreeWidgetItem
        
        from electrum.i18n import _
       t@@ -70,6 +71,7 @@ class InvoiceList(MyTreeView):
                                 editable_columns=[])
                self.setSortingEnabled(True)
                self.setModel(QStandardItemModel(self))
       +        self.setSelectionMode(QAbstractItemView.ExtendedSelection)
                self.update()
        
            def update_item(self, key, status):
       t@@ -143,6 +145,10 @@ class InvoiceList(MyTreeView):
                export_meta_gui(self.parent, _('invoices'), self.parent.invoices.export_file)
        
            def create_menu(self, position):
       +        items = self.selected_in_column(0)
       +        if len(items) > 1:
       +            print(items)
       +            return
                idx = self.indexAt(position)
                item = self.model().itemFromIndex(idx)
                item_col0 = self.model().itemFromIndex(idx.sibling(idx.row(), self.Columns.DATE))
   DIR diff --git a/electrum/gui/qt/main_window.py b/electrum/gui/qt/main_window.py
       t@@ -95,6 +95,8 @@ from .installwizard import WIF_HELP_TEXT
        from .history_list import HistoryList, HistoryModel
        from .update_checker import UpdateCheck, UpdateCheckThread
        from .channels_list import ChannelsList
       +from .confirm_tx_dialog import ConfirmTxDialog
       +from .transaction_dialog import PreviewTxDialog
        
        if TYPE_CHECKING:
            from . import ElectrumGui
       t@@ -153,11 +155,9 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
                self.payto_URI = None
                self.checking_accounts = False
                self.qr_window = None
       -        self.not_enough_funds = False
                self.pluginsdialog = None
                self.require_fee_update = False
                self.tl_windows = []
       -        self.tx_external_keypairs = {}
                Logger.__init__(self)
        
                self.tx_notification_queue = queue.Queue()
       t@@ -174,8 +174,6 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
        
                self.completions = QStringListModel()
        
       -        self.send_tab_is_onchain = False
       -
                self.tabs = tabs = QTabWidget(self)
                self.send_tab = self.create_send_tab()
                self.receive_tab = self.create_receive_tab()
       t@@ -244,7 +242,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
                    self.console.showMessage(self.network.banner)
        
                # update fee slider in case we missed the callback
       -        self.fee_slider.update()
       +        #self.fee_slider.update()
                self.load_wallet(wallet)
                gui_object.timer.timeout.connect(self.timer_actions)
                self.fetch_alias()
       t@@ -397,11 +395,11 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
                        self.history_model.update_tx_mined_status(tx_hash, tx_mined_status)
                elif event == 'fee':
                    if self.config.is_dynfee():
       -                self.fee_slider.update()
       +                #self.fee_slider.update()
                        self.require_fee_update = True
                elif event == 'fee_histogram':
                    if self.config.is_dynfee():
       -                self.fee_slider.update()
       +                #self.fee_slider.update()
                        self.require_fee_update = True
                    self.history_model.on_fee_histogram()
                else:
       t@@ -769,7 +767,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
                self.payto_e.resolve()
                # update fee
                if self.require_fee_update:
       -            self.do_update_fee()
       +            #self.do_update_fee()
                    self.require_fee_update = False
                self.notify_transactions()
        
       t@@ -946,7 +944,9 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
                if not self.fx or not self.fx.is_enabled():
                    self.fiat_receive_e.setVisible(False)
                grid.addWidget(self.fiat_receive_e, 1, 2, Qt.AlignLeft)
       +
                self.connect_fields(self, self.receive_amount_e, self.fiat_receive_e, None)
       +        self.connect_fields(self, self.amount_e, self.fiat_send_e, None)
        
                self.expires_combo = QComboBox()
                evl = sorted(pr_expiration_values.items())
       t@@ -1179,10 +1179,6 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
                    self.receive_address_e.setStyleSheet("")
                    self.receive_address_e.setToolTip("")
        
       -    def set_feerounding_text(self, num_satoshis_added):
       -        self.feerounding_text = (_('Additional {} satoshis are going to be added.')
       -                                 .format(num_satoshis_added))
       -
            def create_send_tab(self):
                # A 4-column grid layout.  All the stretch is in the last column.
                # The exchange rate plugin adds a fiat widget in column 2
       t@@ -1232,131 +1228,18 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
                self.max_button.setCheckable(True)
                grid.addWidget(self.max_button, 3, 3)
        
       -        self.from_label = QLabel(_('From'))
       -        grid.addWidget(self.from_label, 4, 0)
       -        self.from_list = FromList(self, self.from_list_menu)
       -        grid.addWidget(self.from_list, 4, 1, 1, -1)
       -        self.set_pay_from([])
       -
       -        msg = _('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
       -              + _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
       -              + _('A suggested fee is automatically added to this field. You may override it. The suggested fee increases with the size of the transaction.')
       -        self.fee_e_label = HelpLabel(_('Fee'), msg)
       -
       -        def fee_cb(dyn, pos, fee_rate):
       -            if dyn:
       -                if self.config.use_mempool_fees():
       -                    self.config.set_key('depth_level', pos, False)
       -                else:
       -                    self.config.set_key('fee_level', pos, False)
       -            else:
       -                self.config.set_key('fee_per_kb', fee_rate, False)
       -
       -            if fee_rate:
       -                fee_rate = Decimal(fee_rate)
       -                self.feerate_e.setAmount(quantize_feerate(fee_rate / 1000))
       -            else:
       -                self.feerate_e.setAmount(None)
       -            self.fee_e.setModified(False)
       -
       -            self.fee_slider.activate()
       -            self.spend_max() if self.max_button.isChecked() else self.update_fee()
       -
       -        self.fee_slider = FeeSlider(self, self.config, fee_cb)
       -        self.fee_slider.setFixedWidth(self.amount_e.width())
       -
       -        def on_fee_or_feerate(edit_changed, editing_finished):
       -            edit_other = self.feerate_e if edit_changed == self.fee_e else self.fee_e
       -            if editing_finished:
       -                if edit_changed.get_amount() is None:
       -                    # This is so that when the user blanks the fee and moves on,
       -                    # we go back to auto-calculate mode and put a fee back.
       -                    edit_changed.setModified(False)
       -            else:
       -                # edit_changed was edited just now, so make sure we will
       -                # freeze the correct fee setting (this)
       -                edit_other.setModified(False)
       -            self.fee_slider.deactivate()
       -            self.update_fee()
       -
       -        class TxSizeLabel(QLabel):
       -            def setAmount(self, byte_size):
       -                self.setText(('x   %s bytes   =' % byte_size) if byte_size else '')
       -
       -        self.size_e = TxSizeLabel()
       -        self.size_e.setAlignment(Qt.AlignCenter)
       -        self.size_e.setAmount(0)
       -        self.size_e.setFixedWidth(self.amount_e.width())
       -        self.size_e.setStyleSheet(ColorScheme.DEFAULT.as_stylesheet())
       -
       -        self.feerate_e = FeerateEdit(lambda: 0)
       -        self.feerate_e.setAmount(self.config.fee_per_byte())
       -        self.feerate_e.textEdited.connect(partial(on_fee_or_feerate, self.feerate_e, False))
       -        self.feerate_e.editingFinished.connect(partial(on_fee_or_feerate, self.feerate_e, True))
       -
       -        self.fee_e = BTCAmountEdit(self.get_decimal_point)
       -        self.fee_e.textEdited.connect(partial(on_fee_or_feerate, self.fee_e, False))
       -        self.fee_e.editingFinished.connect(partial(on_fee_or_feerate, self.fee_e, True))
       -
       -        def feerounding_onclick():
       -            text = (self.feerounding_text + '\n\n' +
       -                    _('To somewhat protect your privacy, Electrum tries to create change with similar precision to other outputs.') + ' ' +
       -                    _('At most 100 satoshis might be lost due to this rounding.') + ' ' +
       -                    _("You can disable this setting in '{}'.").format(_('Preferences')) + '\n' +
       -                    _('Also, dust is not kept as change, but added to the fee.')  + '\n' +
       -                    _('Also, when batching RBF transactions, BIP 125 imposes a lower bound on the fee.'))
       -            self.show_message(title=_('Fee rounding'), msg=text)
       -
       -        self.feerounding_icon = QPushButton(read_QIcon('info.png'), '')
       -        self.feerounding_icon.setFixedWidth(round(2.2 * char_width_in_lineedit()))
       -        self.feerounding_icon.setFlat(True)
       -        self.feerounding_icon.clicked.connect(feerounding_onclick)
       -        self.feerounding_icon.setVisible(False)
       -
       -        self.connect_fields(self, self.amount_e, self.fiat_send_e, self.fee_e)
       -
       -        vbox_feelabel = QVBoxLayout()
       -        vbox_feelabel.addWidget(self.fee_e_label)
       -        vbox_feelabel.addStretch(1)
       -        grid.addLayout(vbox_feelabel, 5, 0)
       -
       -        self.fee_adv_controls = QWidget()
       -        hbox = QHBoxLayout(self.fee_adv_controls)
       -        hbox.setContentsMargins(0, 0, 0, 0)
       -        hbox.addWidget(self.feerate_e)
       -        hbox.addWidget(self.size_e)
       -        hbox.addWidget(self.fee_e)
       -        hbox.addWidget(self.feerounding_icon, Qt.AlignLeft)
       -        hbox.addStretch(1)
       -
       -        self.feecontrol_fields = QWidget()
       -        vbox_feecontrol = QVBoxLayout(self.feecontrol_fields)
       -        vbox_feecontrol.setContentsMargins(0, 0, 0, 0)
       -        vbox_feecontrol.addWidget(self.fee_adv_controls)
       -        vbox_feecontrol.addWidget(self.fee_slider)
       -
       -        grid.addWidget(self.feecontrol_fields, 5, 1, 1, -1)
       -
       -        if not self.config.get('show_fee', False):
       -            self.fee_adv_controls.setVisible(False)
       -
                self.save_button = EnterButton(_("Save"), self.do_save_invoice)
       -        self.preview_button = EnterButton(_("Preview"), self.do_preview)
       -        self.preview_button.setToolTip(_('Display the details of your transaction before signing it.'))
       -        self.send_button = EnterButton(_("Send"), self.do_pay)
       +        self.send_button = EnterButton(_("Pay"), self.do_pay)
                self.clear_button = EnterButton(_("Clear"), self.do_clear)
        
                buttons = QHBoxLayout()
                buttons.addStretch(1)
                buttons.addWidget(self.clear_button)
                buttons.addWidget(self.save_button)
       -        buttons.addWidget(self.preview_button)
                buttons.addWidget(self.send_button)
                grid.addLayout(buttons, 6, 1, 1, 4)
        
                self.amount_e.shortcut.connect(self.spend_max)
       -        self.payto_e.textChanged.connect(self.update_fee)
       -        self.amount_e.textEdited.connect(self.update_fee)
        
                def reset_max(text):
                    self.max_button.setChecked(False)
       t@@ -1365,45 +1248,6 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
                self.amount_e.textEdited.connect(reset_max)
                self.fiat_send_e.textEdited.connect(reset_max)
        
       -        def entry_changed():
       -            text = ""
       -
       -            amt_color = ColorScheme.DEFAULT
       -            fee_color = ColorScheme.DEFAULT
       -            feerate_color = ColorScheme.DEFAULT
       -
       -            if self.not_enough_funds:
       -                amt_color, fee_color = ColorScheme.RED, ColorScheme.RED
       -                feerate_color = ColorScheme.RED
       -                text = _("Not enough funds")
       -                c, u, x = self.wallet.get_frozen_balance()
       -                if c+u+x:
       -                    text += " ({} {} {})".format(
       -                        self.format_amount(c + u + x).strip(), self.base_unit(), _("are frozen")
       -                    )
       -
       -            # blue color denotes auto-filled values
       -            elif self.fee_e.isModified():
       -                feerate_color = ColorScheme.BLUE
       -            elif self.feerate_e.isModified():
       -                fee_color = ColorScheme.BLUE
       -            elif self.amount_e.isModified():
       -                fee_color = ColorScheme.BLUE
       -                feerate_color = ColorScheme.BLUE
       -            else:
       -                amt_color = ColorScheme.BLUE
       -                fee_color = ColorScheme.BLUE
       -                feerate_color = ColorScheme.BLUE
       -
       -            self.statusBar().showMessage(text)
       -            self.amount_e.setStyleSheet(amt_color.as_stylesheet())
       -            self.fee_e.setStyleSheet(fee_color.as_stylesheet())
       -            self.feerate_e.setStyleSheet(feerate_color.as_stylesheet())
       -
       -        self.amount_e.textChanged.connect(entry_changed)
       -        self.fee_e.textChanged.connect(entry_changed)
       -        self.feerate_e.textChanged.connect(entry_changed)
       -
                self.set_onchain(False)
        
                self.invoices_label = QLabel(_('Outgoing payments'))
       t@@ -1430,144 +1274,12 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
                if run_hook('abort_send', self):
                    return
                self.max_button.setChecked(True)
       -        self.do_update_fee()
       -
       -    def update_fee(self):
       -        self.require_fee_update = True
       -
       -    def get_payto_or_dummy(self) -> bytes:
       -        r = self.payto_e.get_destination_scriptpubkey()
       -        if r:
       -            return r
       -        return bfh(bitcoin.address_to_script(self.wallet.dummy_address()))
       -
       -    def do_update_fee(self):
       -        '''Recalculate the fee.  If the fee was manually input, retain it, but
       -        still build the TX to see if there are enough funds.
       -        '''
       -        if not self.is_onchain:
       -            return
       -        freeze_fee = self.is_send_fee_frozen()
       -        freeze_feerate = self.is_send_feerate_frozen()
       -        amount = '!' if self.max_button.isChecked() else self.amount_e.get_amount()
       -        if amount is None:
       -            if not freeze_fee:
       -                self.fee_e.setAmount(None)
       -            self.not_enough_funds = False
       -            self.statusBar().showMessage('')
       -            return
       -
       -        outputs = self.read_outputs()
       -        fee_estimator = self.get_send_fee_estimator()
       -        coins = self.get_coins()
       -
       -        if not outputs:
       -            scriptpubkey = self.get_payto_or_dummy()
       -            outputs = [PartialTxOutput(scriptpubkey=scriptpubkey, value=amount)]
       -        is_sweep = bool(self.tx_external_keypairs)
       -        make_tx = lambda fee_est: \
       -            self.wallet.make_unsigned_transaction(
       -                coins=coins,
       -                outputs=outputs,
       -                fee=fee_est,
       -                is_sweep=is_sweep)
       -        try:
       -            tx = make_tx(fee_estimator)
       -            self.not_enough_funds = False
       -        except (NotEnoughFunds, NoDynamicFeeEstimates) as e:
       -            if not freeze_fee:
       -                self.fee_e.setAmount(None)
       -            if not freeze_feerate:
       -                self.feerate_e.setAmount(None)
       -            self.feerounding_icon.setVisible(False)
       -
       -            if isinstance(e, NotEnoughFunds):
       -                self.not_enough_funds = True
       -            elif isinstance(e, NoDynamicFeeEstimates):
       -                try:
       -                    tx = make_tx(0)
       -                    size = tx.estimated_size()
       -                    self.size_e.setAmount(size)
       -                except BaseException:
       -                    pass
       -            return
       -        except BaseException:
       -            self.logger.exception('')
       -            return
       -
       -        size = tx.estimated_size()
       -        self.size_e.setAmount(size)
       -
       -        fee = tx.get_fee()
       -        fee = None if self.not_enough_funds else fee
       -
       -        # Displayed fee/fee_rate values are set according to user input.
       -        # Due to rounding or dropping dust in CoinChooser,
       -        # actual fees often differ somewhat.
       -        if freeze_feerate or self.fee_slider.is_active():
       -            displayed_feerate = self.feerate_e.get_amount()
       -            if displayed_feerate is not None:
       -                displayed_feerate = quantize_feerate(displayed_feerate)
       -            else:
       -                # fallback to actual fee
       -                displayed_feerate = quantize_feerate(fee / size) if fee is not None else None
       -                self.feerate_e.setAmount(displayed_feerate)
       -            displayed_fee = round(displayed_feerate * size) if displayed_feerate is not None else None
       -            self.fee_e.setAmount(displayed_fee)
       -        else:
       -            if freeze_fee:
       -                displayed_fee = self.fee_e.get_amount()
       -            else:
       -                # fallback to actual fee if nothing is frozen
       -                displayed_fee = fee
       -                self.fee_e.setAmount(displayed_fee)
       -            displayed_fee = displayed_fee if displayed_fee else 0
       -            displayed_feerate = quantize_feerate(displayed_fee / size) if displayed_fee is not None else None
       -            self.feerate_e.setAmount(displayed_feerate)
       -
       -        # show/hide fee rounding icon
       -        feerounding = (fee - displayed_fee) if fee else 0
       -        self.set_feerounding_text(int(feerounding))
       -        self.feerounding_icon.setToolTip(self.feerounding_text)
       -        self.feerounding_icon.setVisible(abs(feerounding) >= 1)
       -
       -        if self.max_button.isChecked():
       -            amount = tx.output_value()
       -            __, x_fee_amount = run_hook('get_tx_extra_fee', self.wallet, tx) or (None, 0)
       -            amount_after_all_fees = amount - x_fee_amount
       -            self.amount_e.setAmount(amount_after_all_fees)
       -
       -    def from_list_delete(self, item):
       -        i = self.from_list.indexOfTopLevelItem(item)
       -        self.pay_from.pop(i)
       -        self.redraw_from_list()
       -        self.update_fee()
       -
       -    def from_list_menu(self, position):
       -        item = self.from_list.itemAt(position)
       -        menu = QMenu()
       -        menu.addAction(_("Remove"), lambda: self.from_list_delete(item))
       -        menu.exec_(self.from_list.viewport().mapToGlobal(position))
       -
       -    def set_pay_from(self, coins: Sequence[PartialTxInput]):
       -        self.pay_from = list(coins)
       -        self.redraw_from_list()
       -
       -    def redraw_from_list(self):
       -        self.from_list.clear()
       -        self.from_label.setHidden(len(self.pay_from) == 0)
       -        self.from_list.setHidden(len(self.pay_from) == 0)
       -
       -        def format(txin: PartialTxInput):
       -            h = txin.prevout.txid.hex()
       -            out_idx = txin.prevout.out_idx
       -            addr = txin.address
       -            return h[0:10] + '...' + h[-10:] + ":%d"%out_idx + '\t' + addr + '\t'
       -
       -        for coin in self.pay_from:
       -            item = QTreeWidgetItem([format(coin), self.format_amount(coin.value_sats())])
       -            item.setFont(0, QFont(MONOSPACE_FONT))
       -            self.from_list.addTopLevelItem(item)
       +        amount = sum(x.value_sats() for x in self.get_coins())
       +        self.amount_e.setAmount(amount)
       +        ## substract extra fee
       +        #__, x_fee_amount = run_hook('get_tx_extra_fee', self.wallet, tx) or (None, 0)
       +        #amount_after_all_fees = amount - x_fee_amount
       +        #self.amount_e.setAmount(amount_after_all_fees)
        
            def get_contact_payto(self, key):
                _type, label = self.contacts.get(key)
       t@@ -1605,26 +1317,6 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
            def protect(self, func, args, password):
                return func(*args, password)
        
       -    def is_send_fee_frozen(self):
       -        return self.fee_e.isVisible() and self.fee_e.isModified() \
       -               and (self.fee_e.text() or self.fee_e.hasFocus())
       -
       -    def is_send_feerate_frozen(self):
       -        return self.feerate_e.isVisible() and self.feerate_e.isModified() \
       -               and (self.feerate_e.text() or self.feerate_e.hasFocus())
       -
       -    def get_send_fee_estimator(self):
       -        if self.is_send_fee_frozen():
       -            fee_estimator = self.fee_e.get_amount()
       -        elif self.is_send_feerate_frozen():
       -            amount = self.feerate_e.get_amount()  # sat/byte feerate
       -            amount = 0 if amount is None else amount * 1000  # sat/kilobyte feerate
       -            fee_estimator = partial(
       -                simple_config.SimpleConfig.estimate_fee_for_feerate, amount)
       -        else:
       -            fee_estimator = None
       -        return fee_estimator
       -
            def read_outputs(self) -> List[PartialTxOutput]:
                if self.payment_request:
                    outputs = self.payment_request.get_outputs()
       t@@ -1734,115 +1426,69 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
                self.do_clear()
                self.invoice_list.update()
        
       -    def do_preview(self):
       -        self.do_pay(preview=True)
       -
       -    def do_pay(self, preview=False):
       +    def do_pay(self):
                invoice = self.read_invoice()
                if not invoice:
                    return
                self.wallet.save_invoice(invoice)
                self.invoice_list.update()
       -        self.do_pay_invoice(invoice, preview)
       +        self.do_clear()
       +        self.do_pay_invoice(invoice)
        
       -    def do_pay_invoice(self, invoice, preview=False):
       +    def do_pay_invoice(self, invoice):
                if invoice['type'] == PR_TYPE_LN:
                    self.pay_lightning_invoice(invoice['invoice'])
       -            return
                elif invoice['type'] == PR_TYPE_ONCHAIN:
       -            message = invoice['message']
       -            outputs = invoice['outputs']  # type: List[PartialTxOutput]
       +            outputs = invoice['outputs']
       +            self.pay_onchain_dialog(self.get_coins, outputs, invoice=invoice)
                else:
                    raise Exception('unknown invoice type')
        
       +    def get_coins(self):
       +        coins = self.utxo_list.get_spend_list()
       +        return coins or self.wallet.get_spendable_coins(None)
       +
       +    def pay_onchain_dialog(self, inputs, outputs, invoice=None, external_keypairs=None):
       +        # trustedcoin requires this
                if run_hook('abort_send', self):
                    return
       -
       -        for txout in outputs:
       -            assert isinstance(txout, PartialTxOutput)
       -        fee_estimator = self.get_send_fee_estimator()
       -        coins = self.get_coins()
       -        try:
       -            is_sweep = bool(self.tx_external_keypairs)
       -            tx = self.wallet.make_unsigned_transaction(
       -                coins=coins,
       -                outputs=outputs,
       -                fee=fee_estimator,
       -                is_sweep=is_sweep)
       -        except (NotEnoughFunds, NoDynamicFeeEstimates) as e:
       -            self.show_message(str(e))
       +        if self.config.get('advanced_preview'):
       +            self.preview_tx_dialog(inputs, outputs, invoice=invoice)
                    return
       -        except InternalAddressCorruption as e:
       -            self.show_error(str(e))
       -            raise
       -        except BaseException as e:
       -            self.logger.exception('')
       -            self.show_message(str(e))
       +        d = ConfirmTxDialog(self, inputs, outputs, external_keypairs)
       +        d.update_tx()
       +        if d.not_enough_funds:
       +            self.show_message(_('Not Enough Funds'))
                    return
       -
       -        amount = tx.output_value() if self.max_button.isChecked() else sum(map(lambda x: x.value, outputs))
       -        fee = tx.get_fee()
       -
       -        use_rbf = bool(self.config.get('use_rbf', True))
       -        if use_rbf:
       -            tx.set_rbf(True)
       -
       -        if fee < self.wallet.relayfee() * tx.estimated_size() / 1000:
       -            self.show_error('\n'.join([
       -                _("This transaction requires a higher fee, or it will not be propagated by your current server"),
       -                _("Try to raise your transaction fee, or use a server with a lower relay fee.")
       -            ]))
       +        cancelled, is_send, password, tx = d.run()
       +        if cancelled:
                    return
       +        if is_send:
       +            def sign_done(success):
       +                if success:
       +                    self.broadcast_or_show(tx, invoice=invoice)
       +            self.sign_tx_with_password(tx, sign_done, password, external_keypairs)
       +        else:
       +            self.preview_tx_dialog(inputs, outputs, external_keypairs=external_keypairs, invoice=invoice)
        
       -        if preview:
       -            self.show_transaction(tx, invoice=invoice)
       -            return
       +    def preview_tx_dialog(self, inputs, outputs, external_keypairs=None, invoice=None):
       +        d = PreviewTxDialog(inputs, outputs, external_keypairs, window=self, invoice=invoice)
       +        d.show()
        
       +    def broadcast_or_show(self, tx, invoice=None):
                if not self.network:
                    self.show_error(_("You can't broadcast a transaction without a live network connection."))
       -            return
       -
       -        # confirmation dialog
       -        msg = [
       -            _("Amount to be sent") + ": " + self.format_amount_and_units(amount),
       -            _("Mining fee") + ": " + self.format_amount_and_units(fee),
       -        ]
       -
       -        x_fee = run_hook('get_tx_extra_fee', self.wallet, tx)
       -        if x_fee:
       -            x_fee_address, x_fee_amount = x_fee
       -            msg.append( _("Additional fees") + ": " + self.format_amount_and_units(x_fee_amount) )
       -
       -        feerate_warning = simple_config.FEERATE_WARNING_HIGH_FEE
       -        if fee > feerate_warning * tx.estimated_size() / 1000:
       -            msg.append(_('Warning') + ': ' + _("The fee for this transaction seems unusually high."))
       -
       -        if self.wallet.has_keystore_encryption():
       -            msg.append("")
       -            msg.append(_("Enter your password to proceed"))
       -            password = self.password_dialog('\n'.join(msg))
       -            if not password:
       -                return
       +            self.show_transaction(tx, invoice=invoice)
       +        elif not tx.is_complete():
       +            self.show_transaction(tx, invoice=invoice)
                else:
       -            msg.append(_('Proceed?'))
       -            password = None
       -            if not self.question('\n'.join(msg)):
       -                return
       -
       -        def sign_done(success):
       -            if success:
       -                self.do_clear()
       -                if not tx.is_complete():
       -                    self.show_transaction(tx, invoice=invoice)
       -                else:
       -                    self.broadcast_transaction(tx, invoice=invoice)
       -        self.sign_tx_with_password(tx, sign_done, password)
       +            self.broadcast_transaction(tx, invoice=invoice)
        
            @protected
       -    def sign_tx(self, tx, callback, password):
       -        self.sign_tx_with_password(tx, callback, password)
       +    def sign_tx(self, tx, callback, external_keypairs, password):
       +        self.sign_tx_with_password(tx, callback, password, external_keypairs=external_keypairs)
        
       -    def sign_tx_with_password(self, tx: PartialTransaction, callback, password):
       +    def sign_tx_with_password(self, tx: PartialTransaction, callback, password, external_keypairs=None):
                '''Sign the transaction in a separate thread.  When done, calls
                the callback with a success code of True or False.
                '''
       t@@ -1852,9 +1498,9 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
                    self.on_error(exc_info)
                    callback(False)
                on_success = run_hook('tc_sign_wrapper', self.wallet, tx, on_success, on_failure) or on_success
       -        if self.tx_external_keypairs:
       +        if external_keypairs:
                    # can sign directly
       -            task = partial(tx.sign, self.tx_external_keypairs)
       +            task = partial(tx.sign, external_keypairs)
                else:
                    task = partial(self.wallet.sign_transaction, tx, password)
                msg = _('Signing transaction...')
       t@@ -1908,10 +1554,21 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
                WaitingDialog(self, _('Broadcasting transaction...'),
                              broadcast_thread, broadcast_done, self.on_error)
        
       -    @protected
       -    def open_channel(self, *args, **kwargs):
       +    def open_channel(self, connect_str, local_amt, push_amt):
       +        # use ConfirmTxDialog
       +        # we need to know the fee before we broadcast, because the txid is required
       +        # however, the user must be allowed to broadcast early
       +        funding_sat = local_amt + push_amt
       +        inputs = self.get_coins
       +        outputs = [PartialTxOutput.from_address_and_value(self.wallet.dummy_address(), funding_sat)]
       +        d = ConfirmTxDialog(self, inputs, outputs, None)
       +        cancelled, is_send, password, tx = d.run()
       +        if not is_send:
       +            return
       +        if cancelled:
       +            return
                def task():
       -            return self.wallet.lnworker.open_channel(*args, **kwargs)
       +            return self.wallet.lnworker.open_channel(connect_str, local_amt, push_amt, password)
                def on_success(chan):
                    n = chan.constraints.funding_txn_minimum_depth
                    message = '\n'.join([
       t@@ -2014,13 +1671,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
        
            def set_onchain(self, b):
                self.is_onchain = b
       -        self.preview_button.setEnabled(b)
                self.max_button.setEnabled(b)
       -        self.show_send_tab_onchain_fees(b)
       -
       -    def show_send_tab_onchain_fees(self, b: bool):
       -        self.feecontrol_fields.setEnabled(b)
       -        #self.fee_e_label.setVisible(b)
        
            def pay_to_URI(self, URI):
                if not URI:
       t@@ -2056,36 +1707,25 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
        
            def do_clear(self):
                self.max_button.setChecked(False)
       -        self.not_enough_funds = False
                self.payment_request = None
                self.payto_URI = None
                self.payto_e.is_pr = False
                self.is_onchain = False
                self.set_onchain(False)
       -        for e in [self.payto_e, self.message_e, self.amount_e, self.fiat_send_e,
       -                  self.fee_e, self.feerate_e]:
       +        for e in [self.payto_e, self.message_e, self.amount_e]:
                    e.setText('')
                    e.setFrozen(False)
       -        self.fee_slider.activate()
       -        self.feerate_e.setAmount(self.config.fee_per_byte())
       -        self.size_e.setAmount(0)
       -        self.feerounding_icon.setVisible(False)
       -        self.set_pay_from([])
       -        self.tx_external_keypairs = {}
                self.update_status()
                run_hook('do_clear', self)
        
       -
            def set_frozen_state_of_addresses(self, addrs, freeze: bool):
                self.wallet.set_frozen_state_of_addresses(addrs, freeze)
                self.address_list.update()
                self.utxo_list.update()
       -        self.update_fee()
        
            def set_frozen_state_of_coins(self, utxos: Sequence[PartialTxInput], freeze: bool):
                self.wallet.set_frozen_state_of_coins(utxos, freeze)
                self.utxo_list.update()
       -        self.update_fee()
        
            def create_list_tab(self, l, toolbar=None):
                w = QWidget()
       t@@ -2109,8 +1749,9 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
        
            def create_utxo_tab(self):
                from .utxo_list import UTXOList
       -        self.utxo_list = l = UTXOList(self)
       -        return self.create_list_tab(l)
       +        self.utxo_list = UTXOList(self)
       +        t = self.utxo_list.get_toolbar()
       +        return self.create_list_tab(self.utxo_list, t)
        
            def create_contacts_tab(self):
                from .contact_list import ContactList
       t@@ -2123,18 +1764,6 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
                    self.need_update.set()  # history, addresses, coins
                    self.clear_receive_tab()
        
       -    def get_coins(self):
       -        if self.pay_from:
       -            return self.pay_from
       -        else:
       -            return self.wallet.get_spendable_coins(None)
       -
       -    def spend_coins(self, coins: Sequence[PartialTxInput]):
       -        self.set_pay_from(coins)
       -        self.set_onchain(len(coins) > 0)
       -        self.show_send_tab()
       -        self.update_fee()
       -
            def paytomany(self):
                self.show_send_tab()
                self.payto_e.paytomany()
       t@@ -2915,14 +2544,11 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
            def sweep_key_dialog(self):
                d = WindowModalDialog(self, title=_('Sweep private keys'))
                d.setMinimumSize(600, 300)
       -
                vbox = QVBoxLayout(d)
       -
                hbox_top = QHBoxLayout()
                hbox_top.addWidget(QLabel(_("Enter private keys:")))
                hbox_top.addWidget(InfoButton(WIF_HELP_TEXT), alignment=Qt.AlignRight)
                vbox.addLayout(hbox_top)
       -
                keys_e = ScanQRTextEdit(allow_multi=True)
                keys_e.setTabChangesFocus(True)
                vbox.addWidget(keys_e)
       t@@ -2978,14 +2604,10 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
                except Exception as e:  # FIXME too broad...
                    self.show_message(repr(e))
                    return
       -        self.do_clear()
       -        self.tx_external_keypairs = keypairs
       -        self.spend_coins(coins)
       -        self.payto_e.setText(addr)
       -        self.spend_max()
       -        self.payto_e.setFrozen(True)
       -        self.amount_e.setFrozen(True)
       +        scriptpubkey = bfh(bitcoin.address_to_script(addr))
       +        outputs = [PartialTxOutput(scriptpubkey=scriptpubkey, value='!')]
                self.warn_if_watching_only()
       +        self.pay_onchain_dialog(lambda: coins, outputs, invoice=None, external_keypairs=keypairs)
        
            def _do_import(self, title, header_layout, func):
                text = text_dialog(self, title, header_layout, _('Import'), allow_multi=True)
   DIR diff --git a/electrum/gui/qt/settings_dialog.py b/electrum/gui/qt/settings_dialog.py
       t@@ -120,15 +120,6 @@ class SettingsDialog(WindowModalDialog):
                fee_type_combo.currentIndexChanged.connect(on_fee_type)
                fee_widgets.append((fee_type_label, fee_type_combo))
        
       -        feebox_cb = QCheckBox(_('Edit fees manually'))
       -        feebox_cb.setChecked(bool(self.config.get('show_fee', False)))
       -        feebox_cb.setToolTip(_("Show fee edit box in send tab."))
       -        def on_feebox(x):
       -            self.config.set_key('show_fee', x == Qt.Checked)
       -            self.window.fee_adv_controls.setVisible(bool(x))
       -        feebox_cb.stateChanged.connect(on_feebox)
       -        fee_widgets.append((feebox_cb, None))
       -
                use_rbf = bool(self.config.get('use_rbf', True))
                use_rbf_cb = QCheckBox(_('Use Replace-By-Fee'))
                use_rbf_cb.setChecked(use_rbf)
       t@@ -321,6 +312,14 @@ that is always connected to the internet. Configure a port if you want it to be 
                filelogging_cb.setToolTip(_('Debug logs can be persisted to disk. These are useful for troubleshooting.'))
                gui_widgets.append((filelogging_cb, None))
        
       +        preview_cb = QCheckBox(_('Advanced preview'))
       +        preview_cb.setChecked(bool(self.config.get('advanced_preview', False)))
       +        preview_cb.setToolTip(_("Open advanced transaction preview dialog when 'Pay' is clicked."))
       +        def on_preview(x):
       +            self.config.set_key('advanced_preview', x == Qt.Checked)
       +        preview_cb.stateChanged.connect(on_preview)
       +        tx_widgets.append((preview_cb, None))
       +
                usechange_cb = QCheckBox(_('Use change addresses'))
                usechange_cb.setChecked(self.window.wallet.use_change)
                if not self.config.is_modifiable('use_change'): usechange_cb.setEnabled(False)
   DIR diff --git a/electrum/gui/qt/transaction_dialog.py b/electrum/gui/qt/transaction_dialog.py
       t@@ -29,14 +29,18 @@ import datetime
        import traceback
        import time
        from typing import TYPE_CHECKING, Callable
       +from functools import partial
       +from decimal import Decimal
        
        from PyQt5.QtCore import QSize, Qt
        from PyQt5.QtGui import QTextCharFormat, QBrush, QFont, QPixmap
       -from PyQt5.QtWidgets import (QDialog, QLabel, QPushButton, QHBoxLayout, QVBoxLayout,
       -                             QTextEdit, QFrame, QAction, QToolButton, QMenu)
       +from PyQt5.QtWidgets import (QDialog, QLabel, QPushButton, QHBoxLayout, QVBoxLayout, QWidget,
       +                             QTextEdit, QFrame, QAction, QToolButton, QMenu, QCheckBox)
        import qrcode
        from qrcode import exceptions
        
       +from electrum.simple_config import SimpleConfig
       +from electrum.util import quantize_feerate
        from electrum.bitcoin import base_encode
        from electrum.i18n import _
        from electrum.plugin import run_hook
       t@@ -49,9 +53,21 @@ from .util import (MessageBoxMixin, read_QIcon, Buttons, CopyButton, icon_path,
                           MONOSPACE_FONT, ColorScheme, ButtonsLineEdit, text_dialog,
                           char_width_in_lineedit, TRANSACTION_FILE_EXTENSION_FILTER)
        
       +from .fee_slider import FeeSlider
       +from .confirm_tx_dialog import TxEditor
       +from .amountedit import FeerateEdit, BTCAmountEdit
       +
        if TYPE_CHECKING:
            from .main_window import ElectrumWindow
        
       +class TxSizeLabel(QLabel):
       +    def setAmount(self, byte_size):
       +        self.setText(('x   %s bytes   =' % byte_size) if byte_size else '')
       +
       +class QTextEditWithDefaultSize(QTextEdit):
       +    def sizeHint(self):
       +        return QSize(0, 100)
       +
        
        SAVE_BUTTON_ENABLED_TOOLTIP = _("Save transaction offline")
        SAVE_BUTTON_DISABLED_TOOLTIP = _("Please sign this transaction in order to save it")
       t@@ -72,36 +88,25 @@ def show_transaction(tx: Transaction, *, parent: 'ElectrumWindow', invoice=None,
                d.show()
        
        
       -class TxDialog(QDialog, MessageBoxMixin):
        
       -    def __init__(self, tx: Transaction, *, parent: 'ElectrumWindow', invoice, desc, prompt_if_unsaved):
       +class BaseTxDialog(QDialog, MessageBoxMixin):
       +
       +    def __init__(self, *, parent: 'ElectrumWindow', invoice, desc, prompt_if_unsaved, finalized):
                '''Transactions in the wallet will show their description.
                Pass desc to give a description for txs not yet in the wallet.
                '''
                # We want to be a top-level window
                QDialog.__init__(self, parent=None)
       -        # Take a copy; it might get updated in the main window by
       -        # e.g. the FX plugin.  If this happens during or after a long
       -        # sign operation the signatures are lost.
       -        self.tx = tx = copy.deepcopy(tx)
       -        try:
       -            self.tx.deserialize()
       -        except BaseException as e:
       -            raise SerializationError(e)
       +        self.finalized = finalized
                self.main_window = parent
       +        self.config = parent.config
                self.wallet = parent.wallet
                self.prompt_if_unsaved = prompt_if_unsaved
                self.saved = False
                self.desc = desc
                self.invoice = invoice
       -
       -        # if the wallet can populate the inputs with more info, do it now.
       -        # as a result, e.g. we might learn an imported address tx is segwit,
       -        # or that a beyond-gap-limit address is is_mine
       -        tx.add_info_from_wallet(self.wallet)
       -
                self.setMinimumWidth(950)
       -        self.setWindowTitle(_("Transaction"))
       +        self.set_title()
        
                vbox = QVBoxLayout()
                self.setLayout(vbox)
       t@@ -115,6 +120,7 @@ class TxDialog(QDialog, MessageBoxMixin):
                vbox.addWidget(self.tx_hash_e)
        
                self.add_tx_stats(vbox)
       +
                vbox.addSpacing(10)
        
                self.inputs_header = QLabel()
       t@@ -125,7 +131,6 @@ class TxDialog(QDialog, MessageBoxMixin):
                vbox.addWidget(self.outputs_header)
                self.outputs_textedit = QTextEditWithDefaultSize()
                vbox.addWidget(self.outputs_textedit)
       -
                self.sign_button = b = QPushButton(_("Sign"))
                b.clicked.connect(self.sign)
        
       t@@ -133,7 +138,7 @@ class TxDialog(QDialog, MessageBoxMixin):
                b.clicked.connect(self.do_broadcast)
        
                self.save_button = b = QPushButton(_("Save"))
       -        save_button_disabled = not tx.is_complete()
       +        save_button_disabled = False #not tx.is_complete()
                b.setDisabled(save_button_disabled)
                if save_button_disabled:
                    b.setToolTip(SAVE_BUTTON_DISABLED_TOOLTIP)
       t@@ -148,15 +153,18 @@ class TxDialog(QDialog, MessageBoxMixin):
                self.export_actions_menu = export_actions_menu = QMenu()
                self.add_export_actions_to_menu(export_actions_menu)
                export_actions_menu.addSeparator()
       -        if isinstance(tx, PartialTransaction):
       -            export_for_coinjoin_submenu = export_actions_menu.addMenu(_("For CoinJoin; strip privates"))
       -            self.add_export_actions_to_menu(export_for_coinjoin_submenu, gettx=self._gettx_for_coinjoin)
       +        #if isinstance(tx, PartialTransaction):
       +        export_for_coinjoin_submenu = export_actions_menu.addMenu(_("For CoinJoin; strip privates"))
       +        self.add_export_actions_to_menu(export_for_coinjoin_submenu, gettx=self._gettx_for_coinjoin)
        
                self.export_actions_button = QToolButton()
                self.export_actions_button.setText(_("Export"))
                self.export_actions_button.setMenu(export_actions_menu)
                self.export_actions_button.setPopupMode(QToolButton.InstantPopup)
        
       +        self.finalize_button = QPushButton(_('Finalize'))
       +        self.finalize_button.clicked.connect(self.on_finalize)
       +
                partial_tx_actions_menu = QMenu()
                ptx_merge_sigs_action = QAction(_("Merge signatures from"), self)
                ptx_merge_sigs_action.triggered.connect(self.merge_sigs)
       t@@ -171,20 +179,41 @@ class TxDialog(QDialog, MessageBoxMixin):
        
                # Action buttons
                self.buttons = []
       -        if isinstance(tx, PartialTransaction):
       -            self.buttons.append(self.partial_tx_actions_button)
       +        #if isinstance(tx, PartialTransaction):
       +        self.buttons.append(self.partial_tx_actions_button)
                self.buttons += [self.sign_button, self.broadcast_button, self.cancel_button]
                # Transaction sharing buttons
       -        self.sharing_buttons = [self.export_actions_button, self.save_button]
       -
       +        self.sharing_buttons = [self.finalize_button, self.export_actions_button, self.save_button]
                run_hook('transaction_dialog', self)
       -
       -        hbox = QHBoxLayout()
       +        if not self.finalized:
       +            self.create_fee_controls()
       +            vbox.addWidget(self.feecontrol_fields)
       +        self.hbox = hbox = QHBoxLayout()
                hbox.addLayout(Buttons(*self.sharing_buttons))
                hbox.addStretch(1)
                hbox.addLayout(Buttons(*self.buttons))
                vbox.addLayout(hbox)
       -        self.update()
       +        self.set_buttons_visibility()
       +
       +    def set_buttons_visibility(self):
       +        for b in [self.export_actions_button, self.save_button, self.sign_button, self.broadcast_button, self.partial_tx_actions_button]:
       +            b.setVisible(self.finalized)
       +        for b in [self.finalize_button]:
       +            b.setVisible(not self.finalized)
       +
       +    def set_tx(self, tx):
       +        # Take a copy; it might get updated in the main window by
       +        # e.g. the FX plugin.  If this happens during or after a long
       +        # sign operation the signatures are lost.
       +        self.tx = tx = copy.deepcopy(tx)
       +        try:
       +            self.tx.deserialize()
       +        except BaseException as e:
       +            raise SerializationError(e)
       +        # if the wallet can populate the inputs with more info, do it now.
       +        # as a result, e.g. we might learn an imported address tx is segwit,
       +        # or that a beyond-gap-limit address is is_mine
       +        tx.add_info_from_wallet(self.wallet)
        
            def do_broadcast(self):
                self.main_window.push_top_level_window(self)
       t@@ -269,7 +298,7 @@ class TxDialog(QDialog, MessageBoxMixin):
        
                self.sign_button.setDisabled(True)
                self.main_window.push_top_level_window(self)
       -        self.main_window.sign_tx(self.tx, sign_done)
       +        self.main_window.sign_tx(self.tx, sign_done, self.external_keypairs)
        
            def save(self):
                self.main_window.push_top_level_window(self)
       t@@ -341,6 +370,10 @@ class TxDialog(QDialog, MessageBoxMixin):
                self.update()
        
            def update(self):
       +        if not self.finalized:
       +            self.update_fee_fields()
       +        if self.tx is None:
       +            return
                self.update_io()
                desc = self.desc
                base_unit = self.main_window.base_unit()
       t@@ -373,7 +406,8 @@ class TxDialog(QDialog, MessageBoxMixin):
                else:
                    self.date_label.hide()
                self.locktime_label.setText(f"LockTime: {self.tx.locktime}")
       -        self.rbf_label.setText(f"RBF: {not self.tx.is_final()}")
       +        self.rbf_label.setText(f"Replace by Fee: {not self.tx.is_final()}")
       +
                if tx_mined_status.header_hash:
                    self.block_hash_label.setText(_("Included in block: {}")
                                                  .format(tx_mined_status.header_hash))
       t@@ -443,7 +477,7 @@ class TxDialog(QDialog, MessageBoxMixin):
                        addr = self.wallet.get_txin_address(txin)
                        if addr is None:
                            addr = ''
       -                cursor.insertText(addr, text_format(addr))
       +                #cursor.insertText(addr, text_format(addr))
                        if isinstance(txin, PartialTxInput) and txin.value_sats() is not None:
                            cursor.insertText(format_amount(txin.value_sats()), ext)
                    cursor.insertBlock()
       t@@ -509,6 +543,11 @@ class TxDialog(QDialog, MessageBoxMixin):
                vbox_right.addWidget(self.size_label)
                self.rbf_label = TxDetailLabel()
                vbox_right.addWidget(self.rbf_label)
       +        self.rbf_cb = QCheckBox(_('Replace by fee'))
       +        vbox_right.addWidget(self.rbf_cb)
       +        self.rbf_label.setVisible(self.finalized)
       +        self.rbf_cb.setVisible(not self.finalized)
       +
                self.locktime_label = TxDetailLabel()
                vbox_right.addWidget(self.locktime_label)
                self.block_hash_label = TxDetailLabel(word_wrap=True)
       t@@ -520,6 +559,19 @@ class TxDialog(QDialog, MessageBoxMixin):
        
                vbox.addLayout(hbox_stats)
        
       +    def set_title(self):
       +        self.setWindowTitle(_("Create transaction") if not self.finalized else _("Transaction"))
       +
       +    def on_finalize(self):
       +        self.finalized = True
       +        for widget in [self.fee_slider, self.feecontrol_fields, self.rbf_cb]:
       +            widget.setEnabled(False)
       +            widget.setVisible(False)
       +        for widget in [self.rbf_label]:
       +            widget.setVisible(True)
       +        self.set_title()
       +        self.set_buttons_visibility()
       +
        
        class QTextEditWithDefaultSize(QTextEdit):
            def sizeHint(self):
       t@@ -532,3 +584,190 @@ class TxDetailLabel(QLabel):
                self.setTextInteractionFlags(Qt.TextSelectableByMouse)
                if word_wrap is not None:
                    self.setWordWrap(word_wrap)
       +
       +
       +class TxDialog(BaseTxDialog):
       +    def __init__(self, tx: Transaction, *, parent: 'ElectrumWindow', invoice, desc, prompt_if_unsaved):
       +        BaseTxDialog.__init__(self, parent=parent, invoice=invoice, desc=desc, prompt_if_unsaved=prompt_if_unsaved, finalized=True)
       +        self.set_tx(tx)
       +        self.update()
       +
       +
       +
       +class PreviewTxDialog(BaseTxDialog, TxEditor):
       +
       +    def __init__(self, inputs, outputs, external_keypairs, *, window: 'ElectrumWindow', invoice):
       +        TxEditor.__init__(self, window, inputs, outputs, external_keypairs)
       +        BaseTxDialog.__init__(self, parent=window, invoice=invoice, desc='', prompt_if_unsaved=False, finalized=False)
       +        self.update_tx()
       +        self.update()
       +
       +    def create_fee_controls(self):
       +
       +        self.size_e = TxSizeLabel()
       +        self.size_e.setAlignment(Qt.AlignCenter)
       +        self.size_e.setAmount(0)
       +        self.size_e.setStyleSheet(ColorScheme.DEFAULT.as_stylesheet())
       +
       +        self.feerate_e = FeerateEdit(lambda: 0)
       +        self.feerate_e.setAmount(self.config.fee_per_byte())
       +        self.feerate_e.textEdited.connect(partial(self.on_fee_or_feerate, self.feerate_e, False))
       +        self.feerate_e.editingFinished.connect(partial(self.on_fee_or_feerate, self.feerate_e, True))
       +
       +        self.fee_e = BTCAmountEdit(self.main_window.get_decimal_point)
       +        self.fee_e.textEdited.connect(partial(self.on_fee_or_feerate, self.fee_e, False))
       +        self.fee_e.editingFinished.connect(partial(self.on_fee_or_feerate, self.fee_e, True))
       +
       +        self.fee_e.textChanged.connect(self.entry_changed)
       +        self.feerate_e.textChanged.connect(self.entry_changed)
       +
       +        self.fee_slider = FeeSlider(self, self.config, self.fee_slider_callback)
       +        self.fee_slider.setFixedWidth(self.fee_e.width())
       +
       +        def feerounding_onclick():
       +            text = (self.feerounding_text + '\n\n' +
       +                    _('To somewhat protect your privacy, Electrum tries to create change with similar precision to other outputs.') + ' ' +
       +                    _('At most 100 satoshis might be lost due to this rounding.') + ' ' +
       +                    _("You can disable this setting in '{}'.").format(_('Preferences')) + '\n' +
       +                    _('Also, dust is not kept as change, but added to the fee.')  + '\n' +
       +                    _('Also, when batching RBF transactions, BIP 125 imposes a lower bound on the fee.'))
       +            self.show_message(title=_('Fee rounding'), msg=text)
       +
       +        self.feerounding_icon = QPushButton(read_QIcon('info.png'), '')
       +        self.feerounding_icon.setFixedWidth(round(2.2 * char_width_in_lineedit()))
       +        self.feerounding_icon.setFlat(True)
       +        self.feerounding_icon.clicked.connect(feerounding_onclick)
       +        self.feerounding_icon.setVisible(False)
       +
       +        self.fee_adv_controls = QWidget()
       +        hbox = QHBoxLayout(self.fee_adv_controls)
       +        hbox.setContentsMargins(0, 0, 0, 0)
       +        hbox.addWidget(self.feerate_e)
       +        hbox.addWidget(self.size_e)
       +        hbox.addWidget(self.fee_e)
       +        hbox.addWidget(self.feerounding_icon, Qt.AlignLeft)
       +        hbox.addStretch(1)
       +
       +        self.feecontrol_fields = QWidget()
       +        vbox_feecontrol = QVBoxLayout(self.feecontrol_fields)
       +        vbox_feecontrol.setContentsMargins(0, 0, 0, 0)
       +        vbox_feecontrol.addWidget(self.fee_adv_controls)
       +        vbox_feecontrol.addWidget(self.fee_slider)
       +
       +    def fee_slider_callback(self, dyn, pos, fee_rate):
       +        super().fee_slider_callback(dyn, pos, fee_rate)
       +        self.fee_slider.activate()
       +        if fee_rate:
       +            fee_rate = Decimal(fee_rate)
       +            self.feerate_e.setAmount(quantize_feerate(fee_rate / 1000))
       +        else:
       +            self.feerate_e.setAmount(None)
       +        self.fee_e.setModified(False)
       +
       +    def on_fee_or_feerate(self, edit_changed, editing_finished):
       +        edit_other = self.feerate_e if edit_changed == self.fee_e else self.fee_e
       +        if editing_finished:
       +            if edit_changed.get_amount() is None:
       +                # This is so that when the user blanks the fee and moves on,
       +                # we go back to auto-calculate mode and put a fee back.
       +                edit_changed.setModified(False)
       +        else:
       +            # edit_changed was edited just now, so make sure we will
       +            # freeze the correct fee setting (this)
       +            edit_other.setModified(False)
       +        self.fee_slider.deactivate()
       +        self.update()
       +
       +    def is_send_fee_frozen(self):
       +        return self.fee_e.isVisible() and self.fee_e.isModified() \
       +               and (self.fee_e.text() or self.fee_e.hasFocus())
       +
       +    def is_send_feerate_frozen(self):
       +        return self.feerate_e.isVisible() and self.feerate_e.isModified() \
       +               and (self.feerate_e.text() or self.feerate_e.hasFocus())
       +
       +    def set_feerounding_text(self, num_satoshis_added):
       +        self.feerounding_text = (_('Additional {} satoshis are going to be added.')
       +                                 .format(num_satoshis_added))
       +
       +    def get_fee_estimator(self):
       +        if self.is_send_fee_frozen():
       +            fee_estimator = self.fee_e.get_amount()
       +        elif self.is_send_feerate_frozen():
       +            amount = self.feerate_e.get_amount()  # sat/byte feerate
       +            amount = 0 if amount is None else amount * 1000  # sat/kilobyte feerate
       +            fee_estimator = partial(
       +                SimpleConfig.estimate_fee_for_feerate, amount)
       +        else:
       +            fee_estimator = None
       +        return fee_estimator
       +
       +    def entry_changed(self):
       +        # blue color denotes auto-filled values
       +        text = ""
       +        fee_color = ColorScheme.DEFAULT
       +        feerate_color = ColorScheme.DEFAULT
       +        if self.not_enough_funds:
       +            fee_color = ColorScheme.RED
       +            feerate_color = ColorScheme.RED
       +        elif self.fee_e.isModified():
       +            feerate_color = ColorScheme.BLUE
       +        elif self.feerate_e.isModified():
       +            fee_color = ColorScheme.BLUE
       +        else:
       +            fee_color = ColorScheme.BLUE
       +            feerate_color = ColorScheme.BLUE
       +        self.fee_e.setStyleSheet(fee_color.as_stylesheet())
       +        self.feerate_e.setStyleSheet(feerate_color.as_stylesheet())
       +        #
       +        self.needs_update = True
       +
       +    def update_fee_fields(self):
       +        freeze_fee = self.is_send_fee_frozen()
       +        freeze_feerate = self.is_send_feerate_frozen()
       +        if self.no_dynfee_estimates:
       +            size = self.tx.estimated_size()
       +            self.size_e.setAmount(size)
       +        if self.not_enough_funds or self.no_dynfee_estimates:
       +            if not freeze_fee:
       +                self.fee_e.setAmount(None)
       +            if not freeze_feerate:
       +                self.feerate_e.setAmount(None)
       +            self.feerounding_icon.setVisible(False)
       +            return
       +
       +        tx = self.tx
       +        size = tx.estimated_size()
       +        fee = tx.get_fee()
       +
       +        self.size_e.setAmount(size)
       +
       +        # Displayed fee/fee_rate values are set according to user input.
       +        # Due to rounding or dropping dust in CoinChooser,
       +        # actual fees often differ somewhat.
       +        if freeze_feerate or self.fee_slider.is_active():
       +            displayed_feerate = self.feerate_e.get_amount()
       +            if displayed_feerate is not None:
       +                displayed_feerate = quantize_feerate(displayed_feerate)
       +            else:
       +                # fallback to actual fee
       +                displayed_feerate = quantize_feerate(fee / size) if fee is not None else None
       +                self.feerate_e.setAmount(displayed_feerate)
       +            displayed_fee = round(displayed_feerate * size) if displayed_feerate is not None else None
       +            self.fee_e.setAmount(displayed_fee)
       +        else:
       +            if freeze_fee:
       +                displayed_fee = self.fee_e.get_amount()
       +            else:
       +                # fallback to actual fee if nothing is frozen
       +                displayed_fee = fee
       +                self.fee_e.setAmount(displayed_fee)
       +            displayed_fee = displayed_fee if displayed_fee else 0
       +            displayed_feerate = quantize_feerate(displayed_fee / size) if displayed_fee is not None else None
       +            self.feerate_e.setAmount(displayed_feerate)
       +
       +        # show/hide fee rounding icon
       +        feerounding = (fee - displayed_fee) if fee else 0
       +        self.set_feerounding_text(int(feerounding))
       +        self.feerounding_icon.setToolTip(self.feerounding_text)
       +        self.feerounding_icon.setVisible(abs(feerounding) >= 1)
   DIR diff --git a/electrum/gui/qt/utxo_list.py b/electrum/gui/qt/utxo_list.py
       t@@ -28,12 +28,12 @@ from enum import IntEnum
        
        from PyQt5.QtCore import Qt
        from PyQt5.QtGui import QStandardItemModel, QStandardItem, QFont
       -from PyQt5.QtWidgets import QAbstractItemView, QMenu
       +from PyQt5.QtWidgets import QAbstractItemView, QMenu, QLabel, QHBoxLayout
        
        from electrum.i18n import _
        from electrum.transaction import PartialTxInput
        
       -from .util import MyTreeView, ColorScheme, MONOSPACE_FONT
       +from .util import MyTreeView, ColorScheme, MONOSPACE_FONT, EnterButton
        
        
        class UTXOList(MyTreeView):
       t@@ -58,6 +58,9 @@ class UTXOList(MyTreeView):
                super().__init__(parent, self.create_menu,
                                 stretch_column=self.Columns.LABEL,
                                 editable_columns=[])
       +        self.cc_label = QLabel('')
       +        self.clear_cc_button = EnterButton(_('Reset'), lambda: self.set_spend_list([]))
       +        self.spend_list = []
                self.setModel(QStandardItemModel(self))
                self.setSelectionMode(QAbstractItemView.ExtendedSelection)
                self.setSortingEnabled(True)
       t@@ -72,6 +75,11 @@ class UTXOList(MyTreeView):
                for idx, utxo in enumerate(utxos):
                    self.insert_utxo(idx, utxo)
                self.filter()
       +        self.clear_cc_button.setEnabled(bool(self.spend_list))
       +        coins = [self.utxo_dict[x] for x in self.spend_list] or utxos
       +        amount = sum(x.value_sats() for x in coins)
       +        amount_str = self.parent.format_amount_and_units(amount)
       +        self.cc_label.setText('%d outputs, %s'%(len(coins), amount_str))
        
            def insert_utxo(self, idx, utxo: PartialTxInput):
                address = utxo.address
       t@@ -88,10 +96,13 @@ class UTXOList(MyTreeView):
                utxo_item[self.Columns.AMOUNT].setFont(QFont(MONOSPACE_FONT))
                utxo_item[self.Columns.OUTPOINT].setFont(QFont(MONOSPACE_FONT))
                utxo_item[self.Columns.ADDRESS].setData(name, Qt.UserRole)
       -        if self.wallet.is_frozen_address(address):
       +        if name in self.spend_list:
       +            for i in range(5):
       +                utxo_item[i].setBackground(ColorScheme.GREEN.as_color(True))
       +        elif self.wallet.is_frozen_address(address):
                    utxo_item[self.Columns.ADDRESS].setBackground(ColorScheme.BLUE.as_color(True))
                    utxo_item[self.Columns.ADDRESS].setToolTip(_('Address is frozen'))
       -        if self.wallet.is_frozen_coin(utxo):
       +        elif self.wallet.is_frozen_coin(utxo):
                    utxo_item[self.Columns.OUTPOINT].setBackground(ColorScheme.BLUE.as_color(True))
                    utxo_item[self.Columns.OUTPOINT].setToolTip(f"{name}\n{_('Coin is frozen')}")
                else:
       t@@ -106,6 +117,20 @@ class UTXOList(MyTreeView):
                    return None
                return [x.data(Qt.UserRole) for x in items]
        
       +    def set_spend_list(self, coins):
       +        self.spend_list = [utxo.prevout.to_str() for utxo in coins]
       +        self.update()
       +
       +    def get_spend_list(self):
       +        return [self.utxo_dict[x] for x in self.spend_list]
       +
       +    def get_toolbar(self):
       +        h = QHBoxLayout()
       +        h.addWidget(self.cc_label)
       +        h.addStretch()
       +        h.addWidget(self.clear_cc_button)
       +        return h
       +
            def create_menu(self, position):
                selected = self.get_selected_outpoints()
                if not selected:
       t@@ -113,7 +138,7 @@ class UTXOList(MyTreeView):
                menu = QMenu()
                menu.setSeparatorsCollapsible(True)  # consecutive separators are merged together
                coins = [self.utxo_dict[name] for name in selected]
       -        menu.addAction(_("Spend"), lambda: self.parent.spend_coins(coins))
       +        menu.addAction(_("Spend"), lambda: self.set_spend_list(coins))
                assert len(coins) >= 1, len(coins)
                if len(coins) == 1:
                    utxo = coins[0]