URI: 
       tconnect fee edits together - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit 3ba864bbca1ece32316cf60430ddc222b1f748ea
   DIR parent 96c86dac27beec00be9556fab0ad2c9569c8059c
  HTML Author: SomberNight <somber.night@protonmail.com>
       Date:   Mon, 18 Dec 2017 22:26:29 +0100
       
       connect fee edits together
       
       Diffstat:
         M gui/qt/amountedit.py                |      17 ++++++++++++++---
         M gui/qt/main_window.py               |     142 ++++++++++++++++++++++---------
         M lib/simple_config.py                |       6 +++++-
         M lib/wallet.py                       |      10 +++++++++-
       
       4 files changed, 130 insertions(+), 45 deletions(-)
       ---
   DIR diff --git a/gui/qt/amountedit.py b/gui/qt/amountedit.py
       t@@ -81,7 +81,6 @@ class BTCAmountEdit(AmountEdit):
        
            def _base_unit(self):
                p = self.decimal_point()
       -        assert p in [2, 5, 8]
                if p == 8:
                    return 'BTC'
                if p == 5:
       t@@ -104,6 +103,18 @@ class BTCAmountEdit(AmountEdit):
                else:
                    self.setText(format_satoshis_plain(amount, self.decimal_point()))
        
       -class BTCkBEdit(BTCAmountEdit):
       +
       +class FeerateEdit(BTCAmountEdit):
            def _base_unit(self):
       -        return BTCAmountEdit._base_unit(self) + '/kB'
       +        p = self.decimal_point()
       +        if p == 2:
       +            return 'mBTC/kB'
       +        if p == 0:
       +            return 'sat/byte'
       +        raise Exception('Unknown base unit')
       +
       +    def get_amount(self):
       +        sat_per_byte_amount = BTCAmountEdit.get_amount(self)
       +        if sat_per_byte_amount is None:
       +            return None
       +        return 1000 * sat_per_byte_amount
   DIR diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py
       t@@ -38,7 +38,7 @@ from PyQt5.QtWidgets import *
        
        from electrum.util import bh2u, bfh
        
       -from electrum import keystore
       +from electrum import keystore, simple_config
        from electrum.bitcoin import COIN, is_address, TYPE_ADDRESS, NetworkConstants
        from electrum.plugins import run_hook
        from electrum.i18n import _
       t@@ -54,7 +54,7 @@ try:
        except:
            plot_history = None
        
       -from .amountedit import AmountEdit, BTCAmountEdit, MyLineEdit
       +from .amountedit import AmountEdit, BTCAmountEdit, MyLineEdit, FeerateEdit
        from .qrcodewidget import QRCodeWidget, QRDialog
        from .qrtextedit import ShowQRTextEdit, ScanQRTextEdit
        from .transaction_dialog import show_transaction
       t@@ -1073,19 +1073,30 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
                self.fee_slider = FeeSlider(self, self.config, fee_cb)
                self.fee_slider.setFixedWidth(140)
        
       +        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 not edit_changed.get_amount():
       +                    # 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.update_fee()
       +
                self.size_e = AmountEdit(lambda: 'bytes')
       -        self.size_e.setReadOnly(True)
       -        self.feerate_e = AmountEdit(lambda: self.base_unit() + '/kB' if self.fee_unit else 'sat/bytes')
       -        self.feerate_e.textEdited.connect(self.update_fee)
       -        
       +        self.size_e.setFrozen(True)
       +
       +        self.feerate_e = FeerateEdit(lambda: 2 if self.fee_unit else 0)
       +        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)
       -        
       -        if not self.config.get('show_fee', False):
       -            self.fee_e.setVisible(False)
       -        self.fee_e.textEdited.connect(self.update_fee)
       -        # 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.
       -        self.fee_e.editingFinished.connect(self.update_fee)
       +        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))
       +
                self.connect_fields(self, self.amount_e, self.fiat_send_e, self.fee_e)
        
                #self.rbf_checkbox = QCheckBox(_('Replaceable'))
       t@@ -1095,13 +1106,28 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
                #self.rbf_checkbox.setToolTip('<p>' + ' '.join(msg) + '</p>')
                #self.rbf_checkbox.setVisible(False)
        
       -        grid.addWidget(self.fee_e_label, 5, 0)
       -        grid.addWidget(self.feerate_e, 5, 1)
       -        grid.addWidget(self.size_e, 5, 2)
       -        grid.addWidget(self.fee_e, 5, 3)
       +        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)
       +
       +        self.size_e.setStyleSheet(ColorScheme.BLUE.as_stylesheet())
        
       -        grid.addWidget(self.fee_slider, 6, 1)
       -        #grid.addWidget(self.rbf_checkbox, 5, 3)
       +        vbox_feecontrol = QVBoxLayout()
       +        vbox_feecontrol.addWidget(self.fee_adv_controls)
       +        vbox_feecontrol.addWidget(self.fee_slider)
       +
       +        grid.addLayout(vbox_feecontrol, 5, 1, 1, 3)
       +
       +        if not self.config.get('show_fee', False):
       +            self.fee_adv_controls.setVisible(False)
        
                self.preview_button = EnterButton(_("Preview"), self.do_preview)
                self.preview_button.setToolTip(_('Display the details of your transactions before signing it.'))
       t@@ -1112,7 +1138,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
                buttons.addWidget(self.clear_button)
                buttons.addWidget(self.preview_button)
                buttons.addWidget(self.send_button)
       -        grid.addLayout(buttons, 7, 1, 1, 3)
       +        grid.addLayout(buttons, 6, 1, 1, 3)
        
                self.amount_e.shortcut.connect(self.spend_max)
                self.payto_e.textChanged.connect(self.update_fee)
       t@@ -1126,26 +1152,40 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
        
                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 += ' (' + self.format_amount(c+u+x).strip() + ' ' + self.base_unit() + ' ' +_("are frozen") + ')'
        
       +            # blue color denotes auto-filled values
                    elif self.fee_e.isModified():
       -                amt_color, fee_color = ColorScheme.DEFAULT, ColorScheme.DEFAULT
       +                feerate_color = ColorScheme.BLUE
       +            elif self.feerate_e.isModified():
       +                fee_color = ColorScheme.BLUE
                    elif self.amount_e.isModified():
       -                amt_color, fee_color = ColorScheme.DEFAULT, ColorScheme.BLUE
       +                fee_color = ColorScheme.BLUE
       +                feerate_color = ColorScheme.BLUE
                    else:
       -                amt_color, fee_color = ColorScheme.BLUE, ColorScheme.BLUE
       +                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.invoices_label = QLabel(_('Invoices'))
                from .invoice_list import InvoiceList
       t@@ -1186,8 +1226,8 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
                if not self.config.get('offline') and self.config.is_dynfee() and not self.config.has_fee_estimates():
                    self.statusBar().showMessage(_('Waiting for fee estimates...'))
                    return False
       -        freeze_fee = (self.fee_e.isModified()
       -                      and (self.fee_e.text() or self.fee_e.hasFocus()))
       +        freeze_fee = self.is_send_fee_frozen()
       +        freeze_feerate = self.is_send_feerate_frozen()
                amount = '!' if self.is_max else self.amount_e.get_amount()
                if amount is None:
                    if not freeze_fee:
       t@@ -1195,7 +1235,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
                    self.not_enough_funds = False
                    self.statusBar().showMessage('')
                else:
       -            fee = self.fee_e.get_amount() if freeze_fee else None
       +            fee_estimator = self.get_send_fee_estimator()
                    outputs = self.payto_e.get_outputs(self.is_max)
                    if not outputs:
                        _type, addr = self.get_payto_or_dummy()
       t@@ -1203,7 +1243,8 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
                    try:
                        is_sweep = bool(self.tx_external_keypairs)
                        tx = self.wallet.make_unsigned_transaction(
       -                    self.get_coins(), outputs, self.config, fee, is_sweep=is_sweep)
       +                    self.get_coins(), outputs, self.config,
       +                    fixed_fee=fee_estimator, is_sweep=is_sweep)
                        self.not_enough_funds = False
                    except NotEnoughFunds:
                        self.not_enough_funds = True
       t@@ -1211,18 +1252,18 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
                            self.fee_e.setAmount(None)
                        return
                    except BaseException:
       +                traceback.print_exc(file=sys.stderr)
                        return
        
                    size = tx.estimated_size()
                    self.size_e.setAmount(size)
       -            
       +
       +            fee = tx.get_fee()
                    if not freeze_fee:
       -                fee_rate = self.config.fee_per_kb()
       -                fee = None if self.not_enough_funds else tx.get_fee()
       +                fee = None if self.not_enough_funds else fee
                        self.fee_e.setAmount(fee)
       -            elif fee:
       -                print(size, fee)
       -                fee_rate = fee // size
       +            if not freeze_feerate:
       +                fee_rate = fee // size if fee is not None else None
                        self.feerate_e.setAmount(fee_rate)
        
                    if self.is_max:
       t@@ -1307,6 +1348,26 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
                    return func(self, *args, **kwargs)
                return request_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()
       +            amount = 0 if amount is None else float(amount)
       +            fee_estimator = partial(
       +                simple_config.SimpleConfig.estimate_fee_for_feerate, amount)
       +        else:
       +            fee_estimator = None
       +        return fee_estimator
       +
            def read_send_tab(self):
                if self.payment_request and self.payment_request.has_expired():
                    self.show_error(_('Payment request has expired'))
       t@@ -1344,10 +1405,9 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
                        self.show_error(_('Invalid Amount'))
                        return
        
       -        freeze_fee = self.fee_e.isVisible() and self.fee_e.isModified() and (self.fee_e.text() or self.fee_e.hasFocus())
       -        fee = self.fee_e.get_amount() if freeze_fee else None
       +        fee_estimator = self.get_send_fee_estimator()
                coins = self.get_coins()
       -        return outputs, fee, label, coins
       +        return outputs, fee_estimator, label, coins
        
            def do_preview(self):
                self.do_send(preview = True)
       t@@ -1358,11 +1418,12 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
                r = self.read_send_tab()
                if not r:
                    return
       -        outputs, fee, tx_desc, coins = r
       +        outputs, fee_estimator, tx_desc, coins = r
                try:
                    is_sweep = bool(self.tx_external_keypairs)
                    tx = self.wallet.make_unsigned_transaction(
       -                coins, outputs, self.config, fee, is_sweep=is_sweep)
       +                coins, outputs, self.config, fixed_fee=fee_estimator,
       +                is_sweep=is_sweep)
                except NotEnoughFunds:
                    self.show_message(_("Insufficient funds"))
                    return
       t@@ -1581,7 +1642,8 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
                self.not_enough_funds = False
                self.payment_request = None
                self.payto_e.is_pr = False
       -        for e in [self.payto_e, self.message_e, self.amount_e, self.fiat_send_e, self.fee_e]:
       +        for e in [self.payto_e, self.message_e, self.amount_e, self.fiat_send_e,
       +                  self.fee_e, self.feerate_e, self.size_e]:
                    e.setText('')
                    e.setFrozen(False)
                self.set_pay_from([])
       t@@ -2522,7 +2584,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
                feebox_cb.setToolTip(_("Show fee edit box in send tab."))
                def on_feebox(x):
                    self.config.set_key('show_fee', x == Qt.Checked)
       -            self.fee_e.setVisible(bool(x))
       +            self.fee_adv_controls.setVisible(bool(x))
                feebox_cb.stateChanged.connect(on_feebox)
                fee_widgets.append((feebox_cb, None))
        
   DIR diff --git a/lib/simple_config.py b/lib/simple_config.py
       t@@ -253,7 +253,11 @@ class SimpleConfig(PrintError):
                return fee_rate
        
            def estimate_fee(self, size):
       -        return int(self.fee_per_kb() * size / 1000.)
       +        return self.estimate_fee_for_feerate(self.fee_per_kb(), size)
       +
       +    @classmethod
       +    def estimate_fee_for_feerate(cls, fee_per_kb, size):
       +        return int(fee_per_kb * size / 1000.)
        
            def update_fee_estimates(self, key, value):
                self.fee_estimates[key] = value
   DIR diff --git a/lib/wallet.py b/lib/wallet.py
       t@@ -34,8 +34,12 @@ import time
        import json
        import copy
        import errno
       +import traceback
        from functools import partial
        from collections import defaultdict
       +from numbers import Number
       +
       +import sys
        
        from .i18n import _
        from .util import NotEnoughFunds, PrintError, UserCancelled, profiler, format_satoshis
       t@@ -903,8 +907,12 @@ class Abstract_Wallet(PrintError):
                # Fee estimator
                if fixed_fee is None:
                    fee_estimator = config.estimate_fee
       -        else:
       +        elif isinstance(fixed_fee, Number):
                    fee_estimator = lambda size: fixed_fee
       +        elif callable(fixed_fee):
       +            fee_estimator = fixed_fee
       +        else:
       +            raise BaseException('Invalid argument fixed_fee: %s' % fixed_fee)
        
                if i_max is None:
                    # Let the coin chooser select the coins to spend