URI: 
       tImprove 'send all coins' function: * do use coin chooser when sending all coins (fixes #2000) * allow "!" syntax for multiple outputs (fixes #1698) - electrum - Electrum Bitcoin wallet
  HTML git clone https://git.parazyd.org/electrum
   DIR Log
   DIR Files
   DIR Refs
   DIR Submodules
       ---
   DIR commit e123774ea8d2b9f30c958c7710cfe797bb7cd4b2
   DIR parent 662577aea6557858320504b6670cae35df48eda6
  HTML Author: ThomasV <thomasv@electrum.org>
       Date:   Sat, 31 Dec 2016 16:29:18 +0100
       
       Improve 'send all coins' function:
       * do use coin chooser when sending all coins (fixes #2000)
       * allow "!" syntax for multiple outputs (fixes #1698)
       
       Diffstat:
         M gui/kivy/main_window.py             |       4 +++-
         M gui/qt/main_window.py               |      30 ++++++++++++------------------
         M gui/qt/paytoedit.py                 |      24 +++++++++++++++++-------
         M lib/commands.py                     |       6 +-----
         M lib/wallet.py                       |      48 +++++++++++++++++--------------
       
       5 files changed, 59 insertions(+), 53 deletions(-)
       ---
   DIR diff --git a/gui/kivy/main_window.py b/gui/kivy/main_window.py
       t@@ -572,7 +572,9 @@ class ElectrumWindow(App):
            def get_max_amount(self):
                inputs = self.wallet.get_spendable_coins(None)
                addr = str(self.send_screen.screen.address) or self.wallet.dummy_address()
       -        amount, fee = self.wallet.get_max_amount(self.electrum_config, inputs, (TYPE_ADDRESS, addr), None)
       +        outputs = [(TYPE_ADDRESS, addr, '!')]
       +        tx = self.wallet.make_unsigned_transaction(inputs, outputs, self.electrum_config)
       +        amount = tx.output_value()
                return format_satoshis_plain(amount, self.decimal_point())
        
            def format_amount(self, x, is_diff=False, whitespaces=False):
   DIR diff --git a/gui/qt/main_window.py b/gui/qt/main_window.py
       t@@ -994,27 +994,14 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
                vbox.addWidget(self.invoices_label)
                vbox.addWidget(self.invoice_list)
                vbox.setStretchFactor(self.invoice_list, 1000)
       -
                # Defer this until grid is parented to avoid ugly flash during startup
                self.update_fee_edit()
       -
                run_hook('create_send_tab', grid)
                return w
        
       -
            def spend_max(self):
       -        inputs = self.get_coins()
       -        sendable = sum(map(lambda x:x['value'], inputs))
       -        fee = self.fee_e.get_amount() if self.fee_e.isModified() else None
       -        r = self.get_payto_or_dummy()
       -        amount, fee = self.wallet.get_max_amount(self.config, inputs, r, fee)
       -        if not self.fee_e.isModified():
       -            self.fee_e.setAmount(fee)
       -        self.amount_e.setAmount(amount)
       -        self.not_enough_funds = (fee + amount > sendable)
       -        # emit signal for fiat_amount update
       -        self.amount_e.textEdited.emit("")
                self.is_max = True
       +        self.do_update_fee()
        
            def reset_max(self):
                self.is_max = False
       t@@ -1034,14 +1021,14 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
                '''
                freeze_fee = (self.fee_e.isModified()
                              and (self.fee_e.text() or self.fee_e.hasFocus()))
       -        amount = self.amount_e.get_amount()
       +        amount = '!' if self.is_max else self.amount_e.get_amount()
                if amount is None:
                    if not freeze_fee:
                        self.fee_e.setAmount(None)
                    self.not_enough_funds = False
                else:
                    fee = self.fee_e.get_amount() if freeze_fee else None
       -            outputs = self.payto_e.get_outputs()
       +            outputs = self.payto_e.get_outputs(self.is_max)
                    if not outputs:
                        _type, addr = self.get_payto_or_dummy()
                        outputs = [(_type, addr, amount)]
       t@@ -1054,6 +1041,12 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
                        fee = None if self.not_enough_funds else self.wallet.get_tx_fee(tx)
                        self.fee_e.setAmount(fee)
        
       +            if self.is_max:
       +                amount = tx.output_value()
       +                self.amount_e.setAmount(amount)
       +                self.amount_e.textEdited.emit("")
       +
       +
            def update_fee_edit(self):
                b = self.config.get('dynamic_fees', True)
                self.fee_slider.setVisible(b)
       t@@ -1132,7 +1125,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
                    if errors:
                        self.show_warning(_("Invalid Lines found:") + "\n\n" + '\n'.join([ _("Line #") + str(x[0]+1) + ": " + x[1] for x in errors]))
                        return
       -            outputs = self.payto_e.get_outputs()
       +            outputs = self.payto_e.get_outputs(self.is_max)
        
                    if self.payto_e.is_alias and self.payto_e.validated is False:
                        alias = self.payto_e.toPlainText()
       t@@ -1174,7 +1167,6 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
                if not r:
                    return
                outputs, fee, tx_desc, coins = r
       -        amount = sum(map(lambda x:x[2], outputs))
                try:
                    tx = self.wallet.make_unsigned_transaction(coins, outputs, self.config, fee)
                except NotEnoughFunds:
       t@@ -1185,6 +1177,8 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
                    self.show_message(str(e))
                    return
        
       +        amount = tx.output_value() if self.is_max else sum(map(lambda x:x[2], outputs))
       +
                use_rbf = self.rbf_checkbox.isChecked()
                if use_rbf:
                    tx.set_sequence(0)
   DIR diff --git a/gui/qt/paytoedit.py b/gui/qt/paytoedit.py
       t@@ -98,6 +98,8 @@ class PayToEdit(ScanQRTextEdit):
                return script
        
            def parse_amount(self, x):
       +        if x.strip() == '!':
       +            return '!'
                p = pow(10, self.amount_edit.decimal_point())
                return int(p * Decimal(x.strip()))
        
       t@@ -138,12 +140,19 @@ class PayToEdit(ScanQRTextEdit):
                        continue
        
                    outputs.append((_type, to_address, amount))
       -            total += amount
       +            if amount == '!':
       +                self.win.is_max = True
       +            else:
       +                total += amount
        
                self.outputs = outputs
                self.payto_address = None
       -        self.amount_edit.setAmount(total if outputs else None)
       -        self.win.lock_amount(total or len(lines)>1)
       +
       +        if self.win.is_max:
       +            self.win.do_update_fee()
       +        else:
       +            self.amount_edit.setAmount(total if outputs else None)
       +            self.win.lock_amount(total or len(lines)>1)
        
            def get_errors(self):
                return self.errors
       t@@ -151,12 +160,13 @@ class PayToEdit(ScanQRTextEdit):
            def get_recipient(self):
                return self.payto_address
        
       -    def get_outputs(self):
       +    def get_outputs(self, is_max):
                if self.payto_address:
       -            try:
       +            if is_max:
       +                amount = '!'
       +            else:
                        amount = self.amount_edit.get_amount()
       -            except:
       -                amount = None
       +
                    _type, addr = self.payto_address
                    self.outputs = [(_type, addr, amount)]
        
   DIR diff --git a/lib/commands.py b/lib/commands.py
       t@@ -404,11 +404,7 @@ class Commands:
                final_outputs = []
                for address, amount in outputs:
                    address = self._resolver(address)
       -            if amount == '!':
       -                assert len(outputs) == 1
       -                inputs = self.wallet.get_spendable_coins(domain)
       -                amount, fee = self.wallet.get_max_amount(self.config, inputs, (TYPE_ADDRESS, address), fee)
       -            else:
       +            if amount != '!':
                        amount = int(COIN*Decimal(amount))
                    final_outputs.append((TYPE_ADDRESS, address, amount))
        
   DIR diff --git a/lib/wallet.py b/lib/wallet.py
       t@@ -551,18 +551,6 @@ class Abstract_Wallet(PrintError):
            def dummy_address(self):
                return self.get_receiving_addresses()[0]
        
       -    def get_max_amount(self, config, inputs, recipient, fee):
       -        sendable = sum(map(lambda x:x['value'], inputs))
       -        if fee is None:
       -            for i in inputs:
       -                self.add_input_info(i)
       -            _type, addr = recipient
       -            outputs = [(_type, addr, sendable)]
       -            dummy_tx = Transaction.from_io(inputs, outputs)
       -            fee = self.estimate_fee(config, dummy_tx.estimated_size())
       -        amount = max(0, sendable - fee)
       -        return amount, fee
       -
            def get_addresses(self):
                out = []
                out += self.get_receiving_addresses()
       t@@ -819,18 +807,24 @@ class Abstract_Wallet(PrintError):
                # this method can be overloaded
                return tx.get_fee()
        
       -    def make_unsigned_transaction(self, coins, outputs, config, fixed_fee=None, change_addr=None):
       +    def make_unsigned_transaction(self, inputs, outputs, config, fixed_fee=None, change_addr=None):
                # check outputs
       -        for type, data, value in outputs:
       -            if type == TYPE_ADDRESS:
       +        i_max = None
       +        for i, o in enumerate(outputs):
       +            _type, data, value = o
       +            if _type == TYPE_ADDRESS:
                        if not is_address(data):
                            raise BaseException("Invalid bitcoin address:" + data)
       +            if value == '!':
       +                if i_max is not None:
       +                    raise BaseException("More than one output set to spend max")
       +                i_max = i
        
                # Avoid index-out-of-range with coins[0] below
       -        if not coins:
       +        if not inputs:
                    raise NotEnoughFunds()
        
       -        for item in coins:
       +        for item in inputs:
                    self.add_input_info(item)
        
                # change address
       t@@ -855,11 +849,21 @@ class Abstract_Wallet(PrintError):
                else:
                    fee_estimator = lambda size: fixed_fee
        
       -        # Let the coin chooser select the coins to spend
       -        max_change = self.max_change_outputs if self.multiple_change else 1
       -        coin_chooser = coinchooser.get_coin_chooser(config)
       -        tx = coin_chooser.make_tx(coins, outputs, change_addrs[:max_change],
       -                                  fee_estimator, self.dust_threshold())
       +        if i_max is None:
       +            # Let the coin chooser select the coins to spend
       +            max_change = self.max_change_outputs if self.multiple_change else 1
       +            coin_chooser = coinchooser.get_coin_chooser(config)
       +            tx = coin_chooser.make_tx(inputs, outputs, change_addrs[:max_change],
       +                                      fee_estimator, self.dust_threshold())
       +        else:
       +            sendable = sum(map(lambda x:x['value'], inputs))
       +            _type, data, value = outputs[i_max]
       +            outputs[i_max] = (_type, data, 0)
       +            tx = Transaction.from_io(inputs, outputs[:])
       +            fee = fee_estimator(tx.estimated_size())
       +            amount = max(0, sendable - tx.output_value() - fee)
       +            outputs[i_max] = (_type, data, amount)
       +            tx = Transaction.from_io(inputs, outputs[:])
        
                # Sort the inputs and outputs deterministically
                tx.BIP_LI01_sort()