diff --git a/electrum/gui/qml/components/ConfirmTxDialog.qml b/electrum/gui/qml/components/ConfirmTxDialog.qml index aa6e02406..1d69de4be 100644 --- a/electrum/gui/qml/components/ConfirmTxDialog.qml +++ b/electrum/gui/qml/components/ConfirmTxDialog.qml @@ -30,7 +30,7 @@ ElDialog { function updateAmountText() { btcValue.text = Config.formatSats(finalizer.effectiveAmount, false) fiatValue.text = Daemon.fx.enabled - ? '(' + Daemon.fx.fiatValue(finalizer.effectiveAmount, false) + ' ' + Daemon.fx.fiatCurrency + ')' + ? Daemon.fx.fiatValue(finalizer.effectiveAmount, false) : '' } @@ -57,119 +57,82 @@ ElDialog { Label { id: amountLabel - Layout.fillWidth: true - Layout.minimumWidth: implicitWidth + Layout.columnSpan: 2 text: qsTr('Amount to send') color: Material.accentColor } - RowLayout { - Layout.fillWidth: true - Label { - id: btcValue - font.bold: true - font.family: FixedFont - } - Label { - text: Config.baseUnit - color: Material.accentColor - } - - Label { - id: fiatValue - Layout.fillWidth: true - font.pixelSize: constants.fontSizeMedium - } - - Component.onCompleted: updateAmountText() - Connections { - target: finalizer - function onEffectiveAmountChanged() { - updateAmountText() + TextHighlightPane { + Layout.columnSpan: 2 + Layout.fillWidth: true + GridLayout { + columns: 2 + Label { + id: btcValue + Layout.alignment: Qt.AlignRight + font.pixelSize: constants.fontSizeXLarge + font.family: FixedFont + font.bold: true } - } - } - - Label { - text: qsTr('Mining fee') - color: Material.accentColor - } - - FormattedAmount { - amount: finalizer.fee - } - - Label { - visible: !finalizer.extraFee.isEmpty - text: qsTr('Extra fee') - color: Material.accentColor - } - - FormattedAmount { - visible: !finalizer.extraFee.isEmpty - amount: finalizer.extraFee - } - Label { - text: qsTr('Fee rate') - color: Material.accentColor - } + Label { + Layout.fillWidth: true + text: Config.baseUnit + color: Material.accentColor + font.pixelSize: constants.fontSizeXLarge + } - RowLayout { - Label { - id: feeRate - text: finalizer.feeRate - font.family: FixedFont - } + Label { + id: fiatValue + Layout.alignment: Qt.AlignRight + visible: Daemon.fx.enabled + font.pixelSize: constants.fontSizeMedium + color: constants.mutedForeground + } - Label { - text: UI_UNIT_NAME.FEERATE_SAT_PER_VB - color: Material.accentColor + Label { + Layout.fillWidth: true + visible: Daemon.fx.enabled + text: Daemon.fx.fiatCurrency + font.pixelSize: constants.fontSizeMedium + color: constants.mutedForeground + } + Component.onCompleted: updateAmountText() + Connections { + target: finalizer + function onEffectiveAmountChanged() { + updateAmountText() + } + } } } Label { - text: qsTr('Target') + Layout.columnSpan: 2 + text: qsTr('Fee') color: Material.accentColor } - Label { - id: targetdesc - text: finalizer.target - } - - RowLayout { + TextHighlightPane { Layout.columnSpan: 2 Layout.fillWidth: true + height: feepicker.height - Slider { - id: feeslider - Layout.fillWidth: true - leftPadding: constants.paddingMedium - - snapMode: Slider.SnapOnRelease - stepSize: 1 - from: 0 - to: finalizer.sliderSteps + FeePicker { + id: feepicker + width: parent.width + finalizer: dialog.finalizer - onValueChanged: { - if (activeFocus) - finalizer.sliderPos = value - } - Component.onCompleted: { - value = finalizer.sliderPos + Label { + visible: !finalizer.extraFee.isEmpty + text: qsTr('Extra fee') + color: Material.accentColor } - Connections { - target: finalizer - function onSliderPosChanged() { - feeslider.value = finalizer.sliderPos - } - } - } - FeeMethodComboBox { - id: target - feeslider: finalizer + FormattedAmount { + visible: !finalizer.extraFee.isEmpty + amount: finalizer.extraFee + } } } diff --git a/electrum/gui/qml/components/CpfpBumpFeeDialog.qml b/electrum/gui/qml/components/CpfpBumpFeeDialog.qml index 17ebd94cd..3e95fe99f 100644 --- a/electrum/gui/qml/components/CpfpBumpFeeDialog.qml +++ b/electrum/gui/qml/components/CpfpBumpFeeDialog.qml @@ -56,113 +56,101 @@ ElDialog { } Label { - Layout.preferredWidth: 1 - Layout.fillWidth: true - text: qsTr('Total size') + Layout.columnSpan: 2 + Layout.topMargin: constants.paddingSmall + text: qsTr('Child tx fee') color: Material.accentColor } - Label { - Layout.preferredWidth: 1 + TextHighlightPane { + Layout.columnSpan: 2 Layout.fillWidth: true - text: cpfpfeebumper.totalSize + ' ' + UI_UNIT_NAME.TXSIZE_VBYTES - } - - Label { - text: qsTr('Input amount') - color: Material.accentColor - } - - FormattedAmount { - amount: cpfpfeebumper.inputAmount + height: feepicker_childinfo.height + + FeePicker { + id: feepicker_childinfo + width: parent.width + finalizer: dialog.cpfpfeebumper + targetLabel: qsTr('Target total') + feeLabel: qsTr('Fee for child') + feeRateLabel: qsTr('Fee rate for child') + showPicker: false + } } Label { - text: qsTr('Output amount') + Layout.columnSpan: 2 + Layout.topMargin: constants.paddingSmall + text: qsTr('Total') color: Material.accentColor } - FormattedAmount { - amount: cpfpfeebumper.outputAmount - valid: cpfpfeebumper.valid - } - - RowLayout { + TextHighlightPane { Layout.columnSpan: 2 - Slider { - id: feeslider - leftPadding: constants.paddingMedium - snapMode: Slider.SnapOnRelease - stepSize: 1 - from: 0 - to: cpfpfeebumper.sliderSteps - onValueChanged: { - if (activeFocus) - cpfpfeebumper.sliderPos = value - } - Component.onCompleted: { - value = cpfpfeebumper.sliderPos - } - Connections { - target: cpfpfeebumper - function onSliderPosChanged() { - feeslider.value = cpfpfeebumper.sliderPos - } - } - } + Layout.fillWidth: true - FeeMethodComboBox { - id: feemethod - feeslider: cpfpfeebumper - } - } + GridLayout { + width: parent.width + columns: 2 - Label { - visible: feemethod.currentValue - text: qsTr('Target') - color: Material.accentColor - } - - Label { - visible: feemethod.currentValue - text: cpfpfeebumper.target - } + Label { + Layout.preferredWidth: 1 + Layout.fillWidth: true + text: qsTr('Total size') + color: Material.accentColor + } - Label { - text: qsTr('Fee for child') - color: Material.accentColor - } + Label { + Layout.preferredWidth: 2 + Layout.fillWidth: true + text: cpfpfeebumper.valid + ? cpfpfeebumper.totalSize + ' ' + UI_UNIT_NAME.TXSIZE_VBYTES + : '' + } - FormattedAmount { - amount: cpfpfeebumper.feeForChild - valid: cpfpfeebumper.valid - } + Label { + Layout.preferredWidth: 1 + Layout.fillWidth: true + text: qsTr('Total fee') + color: Material.accentColor + } - Label { - text: qsTr('Total fee') - color: Material.accentColor - } + FormattedAmount { + Layout.preferredWidth: 2 + Layout.fillWidth: true + amount: cpfpfeebumper.totalFee + valid: cpfpfeebumper.valid + } - FormattedAmount { - amount: cpfpfeebumper.totalFee - valid: cpfpfeebumper.valid - } + Label { + Layout.preferredWidth: 1 + Layout.fillWidth: true + text: qsTr('Total fee rate') + color: Material.accentColor + } - Label { - text: qsTr('Total fee rate') - color: Material.accentColor - } + RowLayout { + Layout.preferredWidth: 2 + Layout.fillWidth: true + Label { + text: cpfpfeebumper.valid ? cpfpfeebumper.totalFeeRate : '' + font.family: FixedFont + } - RowLayout { - Label { - text: cpfpfeebumper.valid ? cpfpfeebumper.totalFeeRate : '' - font.family: FixedFont - } + Label { + visible: cpfpfeebumper.valid + text: UI_UNIT_NAME.FEERATE_SAT_PER_VB + color: Material.accentColor + } + } - Label { - visible: cpfpfeebumper.valid - text: UI_UNIT_NAME.FEERATE_SAT_PER_VB - color: Material.accentColor + FeePicker { + id: feepicker + Layout.columnSpan: 2 + Layout.fillWidth: true + finalizer: dialog.cpfpfeebumper + showTxInfo: false + } } } diff --git a/electrum/gui/qml/components/RbfBumpFeeDialog.qml b/electrum/gui/qml/components/RbfBumpFeeDialog.qml index 8c3b815c5..22e02eb82 100644 --- a/electrum/gui/qml/components/RbfBumpFeeDialog.qml +++ b/electrum/gui/qml/components/RbfBumpFeeDialog.qml @@ -49,17 +49,12 @@ ElDialog { } Label { - Layout.preferredWidth: 1 Layout.fillWidth: true text: qsTr('Method') color: Material.accentColor } RowLayout { - Layout.preferredWidth: 1 - Layout.fillWidth: true - Layout.minimumWidth: bumpMethodComboBox.implicitWidth - ElComboBox { id: bumpMethodComboBox @@ -79,15 +74,11 @@ ElDialog { } Label { - Layout.preferredWidth: 1 - Layout.fillWidth: true text: qsTr('Old fee') color: Material.accentColor } FormattedAmount { - Layout.preferredWidth: 1 - Layout.fillWidth: true amount: rbffeebumper.oldfee } @@ -110,71 +101,22 @@ ElDialog { } Label { + Layout.columnSpan: 2 + Layout.topMargin: constants.paddingSmall text: qsTr('New fee') color: Material.accentColor } - FormattedAmount { - amount: rbffeebumper.fee - valid: rbffeebumper.valid - } - - Label { - text: qsTr('New fee rate') - color: Material.accentColor - } - - RowLayout { - Label { - id: feeRate - text: rbffeebumper.valid ? rbffeebumper.feeRate : '' - font.family: FixedFont - } - - Label { - visible: rbffeebumper.valid - text: UI_UNIT_NAME.FEERATE_SAT_PER_VB - color: Material.accentColor - } - } - - Label { - text: qsTr('Target') - color: Material.accentColor - } - - Label { - id: targetdesc - text: rbffeebumper.target - } - - RowLayout { + TextHighlightPane { Layout.columnSpan: 2 - Slider { - id: feeslider - leftPadding: constants.paddingMedium - snapMode: Slider.SnapOnRelease - stepSize: 1 - from: 0 - to: rbffeebumper.sliderSteps - onValueChanged: { - if (activeFocus) - rbffeebumper.sliderPos = value - } - Component.onCompleted: { - value = rbffeebumper.sliderPos - } - Connections { - target: rbffeebumper - function onSliderPosChanged() { - feeslider.value = rbffeebumper.sliderPos - } - } - } + Layout.fillWidth: true + height: feepicker.height + + FeePicker { + id: feepicker + width: parent.width + finalizer: dialog.rbffeebumper - FeeMethodComboBox { - id: target - feeslider: rbffeebumper } } diff --git a/electrum/gui/qml/components/RbfCancelDialog.qml b/electrum/gui/qml/components/RbfCancelDialog.qml index b8b5934d4..be270796d 100644 --- a/electrum/gui/qml/components/RbfCancelDialog.qml +++ b/electrum/gui/qml/components/RbfCancelDialog.qml @@ -73,71 +73,22 @@ ElDialog { } Label { + Layout.columnSpan: 2 + Layout.topMargin: constants.paddingSmall text: qsTr('New fee') color: Material.accentColor } - FormattedAmount { - amount: txcanceller.fee - valid: txcanceller.valid - } - - Label { - text: qsTr('New fee rate') - color: Material.accentColor - } - - RowLayout { - Label { - id: feeRate - text: txcanceller.valid ? txcanceller.feeRate : '' - font.family: FixedFont - } - - Label { - visible: txcanceller.valid - text: UI_UNIT_NAME.FEERATE_SAT_PER_VB - color: Material.accentColor - } - } - - Label { - text: qsTr('Target') - color: Material.accentColor - } - - Label { - id: targetdesc - text: txcanceller.target - } - - RowLayout { + TextHighlightPane { Layout.columnSpan: 2 - Slider { - id: feeslider - leftPadding: constants.paddingMedium - snapMode: Slider.SnapOnRelease - stepSize: 1 - from: 0 - to: txcanceller.sliderSteps - onValueChanged: { - if (activeFocus) - txcanceller.sliderPos = value - } - Component.onCompleted: { - value = txcanceller.sliderPos - } - Connections { - target: txcanceller - function onSliderPosChanged() { - feeslider.value = txcanceller.sliderPos - } - } - } + Layout.fillWidth: true + height: feepicker.height + + FeePicker { + id: feepicker + width: parent.width + finalizer: dialog.txcanceller - FeeMethodComboBox { - id: target - feeslider: txcanceller } } diff --git a/electrum/gui/qml/components/controls/FeePicker.qml b/electrum/gui/qml/components/controls/FeePicker.qml new file mode 100644 index 000000000..c8dd3b797 --- /dev/null +++ b/electrum/gui/qml/components/controls/FeePicker.qml @@ -0,0 +1,121 @@ +import QtQuick +import QtQuick.Layouts +import QtQuick.Controls +import QtQuick.Controls.Material + +import org.electrum 1.0 + +Item { + id: root + + required property QtObject finalizer + + default property alias additionalItems: rootLayout.children + + property string targetLabel: qsTr('Target') + property string feeLabel: qsTr('Mining fee') + property string feeRateLabel: qsTr('Fee rate') + + property bool showTxInfo: true + property bool showPicker: true + + implicitHeight: rootLayout.height + + GridLayout { + id: rootLayout + width: parent.width + columns: 2 + + Label { + Layout.fillWidth: true + Layout.preferredWidth: 1 + text: feeLabel + color: Material.accentColor + visible: showTxInfo + } + + FormattedAmount { + Layout.fillWidth: true + Layout.preferredWidth: 2 + amount: finalizer.fee + valid: finalizer.valid + visible: showTxInfo + } + + Label { + Layout.fillWidth: true + Layout.preferredWidth: 1 + text: feeRateLabel + color: Material.accentColor + visible: showTxInfo + } + + RowLayout { + Layout.fillWidth: true + Layout.preferredWidth: 2 + visible: showTxInfo + Label { + id: feeRate + text: finalizer.valid ? finalizer.feeRate : '' + font.family: FixedFont + } + + Label { + Layout.fillWidth: true + text: finalizer.valid ? UI_UNIT_NAME.FEERATE_SAT_PER_VBYTE : '' + color: Material.accentColor + } + } + + Label { + Layout.fillWidth: true + Layout.preferredWidth: 1 + text: targetLabel + color: Material.accentColor + visible: showPicker + } + + Label { + Layout.fillWidth: true + Layout.preferredWidth: 2 + text: finalizer.target + visible: showPicker + } + + RowLayout { + Layout.columnSpan: 2 + Layout.fillWidth: true + visible: showPicker + + Slider { + id: feeslider + Layout.fillWidth: true + leftPadding: constants.paddingMedium + + snapMode: Slider.SnapOnRelease + stepSize: 1 + from: 0 + to: finalizer.sliderSteps + + onValueChanged: { + if (activeFocus) + finalizer.sliderPos = value + } + Component.onCompleted: { + value = finalizer.sliderPos + } + Connections { + target: finalizer + function onSliderPosChanged() { + feeslider.value = finalizer.sliderPos + } + } + } + + FeeMethodComboBox { + id: target + feeslider: finalizer + } + } + } +} diff --git a/electrum/gui/qml/qetxfinalizer.py b/electrum/gui/qml/qetxfinalizer.py index 729f80005..28e84a2a2 100644 --- a/electrum/gui/qml/qetxfinalizer.py +++ b/electrum/gui/qml/qetxfinalizer.py @@ -7,7 +7,7 @@ from PyQt6.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject from electrum.logging import get_logger from electrum.i18n import _ from electrum.transaction import PartialTxOutput, PartialTransaction, Transaction, TxOutpoint -from electrum.util import NotEnoughFunds, profiler +from electrum.util import NotEnoughFunds, profiler, quantize_feerate from electrum.wallet import CannotBumpFee, CannotDoubleSpendTx, CannotCPFP, BumpFeeStrategy from electrum.plugin import run_hook @@ -719,7 +719,6 @@ class QETxCpfpFeeBumper(TxFeeSlider, TxMonMixin): self._input_amount = QEAmount() self._output_amount = QEAmount() - self._fee_for_child = QEAmount() self._total_fee = QEAmount() self._total_fee_rate = 0 self._total_size = 0 @@ -754,17 +753,6 @@ class QETxCpfpFeeBumper(TxFeeSlider, TxMonMixin): self._total_fee_rate = totalfeerate self.totalFeeRateChanged.emit() - feeForChildChanged = pyqtSignal() - @pyqtProperty(QEAmount, notify=feeForChildChanged) - def feeForChild(self): - return self._fee_for_child - - @feeForChild.setter - def feeForChild(self, feeforchild): - if self._fee_for_child != feeforchild: - self._fee_for_child.copyFrom(feeforchild) - self.feeForChildChanged.emit() - inputAmountChanged = pyqtSignal() @pyqtProperty(QEAmount, notify=inputAmountChanged) def inputAmount(self): @@ -850,12 +838,12 @@ class QETxCpfpFeeBumper(TxFeeSlider, TxMonMixin): comb_fee = fee + self._parent_fee comb_feerate = comb_fee / self._total_size - self._fee_for_child.satsInt = fee + self._fee.satsInt = fee self._output_amount.satsInt = self._max_fee - fee self.outputAmountChanged.emit() self._total_fee.satsInt = fee + self._parent_fee - self._total_fee_rate = f'{comb_feerate:.1f}' + self._total_fee_rate = str(quantize_feerate(comb_feerate)) try: self._new_tx = self._wallet.wallet.cpfp(self._parent_tx, fee) @@ -864,6 +852,9 @@ class QETxCpfpFeeBumper(TxFeeSlider, TxMonMixin): self.warning = str(e) return + child_feerate = fee / self._new_tx.estimated_size() + self.feeRate = str(quantize_feerate(child_feerate)) + self.update_inputs_from_tx(self._new_tx) self.update_outputs_from_tx(self._new_tx)