diff --git a/electrum/gui/qml/components/Channels.qml b/electrum/gui/qml/components/Channels.qml index 7bcd16609..4e0bb056b 100644 --- a/electrum/gui/qml/components/Channels.qml +++ b/electrum/gui/qml/components/Channels.qml @@ -140,7 +140,10 @@ Pane { FlatButton { Layout.fillWidth: true text: qsTr('Open Channel') - onClicked: app.stack.push(Qt.resolvedUrl('OpenChannel.qml')) + onClicked: { + var dialog = openChannelDialog.createObject(root) + dialog.open() + } icon.source: '../../icons/lightning.png' } @@ -166,7 +169,16 @@ Pane { Component { id: swapDialog - SwapDialog {} + SwapDialog { + onClosed: destroy() + } + } + + Component { + id: openChannelDialog + OpenChannelDialog { + onClosed: destroy() + } } Component { diff --git a/electrum/gui/qml/components/ImportChannelBackupDialog.qml b/electrum/gui/qml/components/ImportChannelBackupDialog.qml index effd04371..497d3615a 100644 --- a/electrum/gui/qml/components/ImportChannelBackupDialog.qml +++ b/electrum/gui/qml/components/ImportChannelBackupDialog.qml @@ -11,7 +11,7 @@ ElDialog { property bool valid: false - standardButtons: Dialog.Close + standardButtons: Dialog.Cancel modal: true parent: Overlay.overlay Overlay.modal: Rectangle { @@ -23,6 +23,7 @@ ElDialog { padding: 0 title: qsTr('Import channel backup') + iconSource: Qt.resolvedUrl('../../icons/file.png') function verifyChannelBackup(text) { return valid = Daemon.currentWallet.isValidChannelBackup(text) diff --git a/electrum/gui/qml/components/OpenChannel.qml b/electrum/gui/qml/components/OpenChannel.qml deleted file mode 100644 index 84f6d1a3f..000000000 --- a/electrum/gui/qml/components/OpenChannel.qml +++ /dev/null @@ -1,220 +0,0 @@ -import QtQuick 2.6 -import QtQuick.Layouts 1.0 -import QtQuick.Controls 2.0 -import QtQuick.Controls.Material 2.0 - -import org.electrum 1.0 - -import "controls" - -Pane { - id: root - - property string title: qsTr("Open Lightning Channel") - - function close() { - app.stack.pop() - } - - GridLayout { - id: form - width: parent.width - rowSpacing: constants.paddingSmall - columnSpacing: constants.paddingSmall - columns: 4 - - Label { - text: qsTr('Node') - } - - // gossip - TextArea { - id: node - visible: Config.useGossip - Layout.columnSpan: 2 - Layout.fillWidth: true - font.family: FixedFont - wrapMode: Text.Wrap - placeholderText: qsTr('Paste or scan node uri/pubkey') - onActiveFocusChanged: { - if (!activeFocus) - channelopener.nodeid = text - } - } - - RowLayout { - visible: Config.useGossip - spacing: 0 - ToolButton { - icon.source: '../../icons/paste.png' - icon.height: constants.iconSizeMedium - icon.width: constants.iconSizeMedium - onClicked: { - if (channelopener.validate_nodeid(AppController.clipboardToText())) { - channelopener.nodeid = AppController.clipboardToText() - node.text = channelopener.nodeid - } - } - } - ToolButton { - icon.source: '../../icons/qrcode.png' - icon.height: constants.iconSizeMedium - icon.width: constants.iconSizeMedium - scale: 1.2 - onClicked: { - var page = app.stack.push(Qt.resolvedUrl('Scan.qml')) - page.onFound.connect(function() { - if (channelopener.validate_nodeid(page.scanData)) { - channelopener.nodeid = page.scanData - node.text = channelopener.nodeid - } - app.stack.pop() - }) - } - } - } - - // trampoline - ComboBox { - visible: !Config.useGossip - Layout.columnSpan: 3 - Layout.fillWidth: true - model: channelopener.trampolineNodeNames - onCurrentValueChanged: { - if (activeFocus) - channelopener.nodeid = currentValue - } - // preselect a random node - Component.onCompleted: { - if (!Config.useGossip) { - currentIndex = Math.floor(Math.random() * channelopener.trampolineNodeNames.length) - channelopener.nodeid = currentValue - } - } - } - - Label { - text: qsTr('Amount') - } - - BtcField { - id: amount - fiatfield: amountFiat - Layout.preferredWidth: parent.width /3 - onTextChanged: channelopener.amount = Config.unitsToSats(amount.text) - enabled: !is_max.checked - } - - RowLayout { - Layout.columnSpan: 2 - Layout.fillWidth: true - Label { - text: Config.baseUnit - color: Material.accentColor - } - Switch { - id: is_max - text: qsTr('Max') - onCheckedChanged: { - channelopener.amount = checked ? MAX : Config.unitsToSats(amount.text) - } - } - } - - Item { width: 1; height: 1; visible: Daemon.fx.enabled } - - FiatField { - id: amountFiat - btcfield: amount - visible: Daemon.fx.enabled - Layout.preferredWidth: parent.width /3 - enabled: !is_max.checked - } - - Label { - visible: Daemon.fx.enabled - text: Daemon.fx.fiatCurrency - color: Material.accentColor - Layout.fillWidth: true - } - - Item { visible: Daemon.fx.enabled ; height: 1; width: 1 } - - RowLayout { - Layout.columnSpan: 4 - Layout.alignment: Qt.AlignHCenter - - Button { - text: qsTr('Open Channel') - enabled: channelopener.valid - onClicked: channelopener.open_channel() - } - } - } - - Component { - id: confirmOpenChannelDialog - ConfirmTxDialog { - title: qsTr('Confirm Open Channel') - amountLabelText: qsTr('Channel capacity') - sendButtonText: qsTr('Open Channel') - finalizer: channelopener.finalizer - } - } - - ChannelOpener { - id: channelopener - wallet: Daemon.currentWallet - onAuthRequired: { - app.handleAuthRequired(channelopener, method) - } - onValidationError: { - if (code == 'invalid_nodeid') { - var dialog = app.messageDialog.createObject(root, { 'text': message }) - dialog.open() - } - } - onConflictingBackup: { - var dialog = app.messageDialog.createObject(root, { 'text': message, 'yesno': true }) - dialog.open() - dialog.yesClicked.connect(function() { - channelopener.open_channel(true) - }) - } - onFinalizerChanged: { - var dialog = confirmOpenChannelDialog.createObject(root, { - 'satoshis': channelopener.amount - }) - dialog.txaccepted.connect(function() { - dialog.finalizer.signAndSend() - }) - dialog.open() - } - onChannelOpening: { - console.log('Channel is opening') - app.channelOpenProgressDialog.reset() - app.channelOpenProgressDialog.peer = peer - app.channelOpenProgressDialog.open() - } - onChannelOpenError: { - app.channelOpenProgressDialog.state = 'failed' - app.channelOpenProgressDialog.error = message - } - onChannelOpenSuccess: { - var message = qsTr('Channel established.') + ' ' - + qsTr('This channel will be usable after %1 confirmations').arg(min_depth) - if (!tx_complete) { - message = message + '\n\n' + qsTr('Please sign and broadcast the funding transaction.') - channelopener.wallet.historyModel.init_model() // local tx doesn't trigger model update - } - app.channelOpenProgressDialog.state = 'success' - app.channelOpenProgressDialog.info = message - if (!has_onchain_backup) { - app.channelOpenProgressDialog.channelBackup = channelopener.channelBackup(cid) - } - // TODO: handle incomplete TX - channelopener.wallet.channelModel.new_channel(cid) - root.close() - } - } -} diff --git a/electrum/gui/qml/components/OpenChannelDialog.qml b/electrum/gui/qml/components/OpenChannelDialog.qml new file mode 100644 index 000000000..b5160cf8b --- /dev/null +++ b/electrum/gui/qml/components/OpenChannelDialog.qml @@ -0,0 +1,235 @@ +import QtQuick 2.6 +import QtQuick.Layouts 1.0 +import QtQuick.Controls 2.14 +import QtQuick.Controls.Material 2.0 + +import org.electrum 1.0 + +import "controls" + +ElDialog { + id: root + + title: qsTr("Open Lightning Channel") + iconSource: Qt.resolvedUrl('../../icons/lightning.png') + + parent: Overlay.overlay + modal: true + standardButtons: Dialog.Cancel + padding: 0 + + width: parent.width + height: parent.height + + Overlay.modal: Rectangle { + color: "#aa000000" + } + + ColumnLayout { + anchors.fill: parent + spacing: 0 + + GridLayout { + id: form + Layout.fillWidth: true + Layout.leftMargin: constants.paddingLarge + Layout.rightMargin: constants.paddingLarge + + columns: 4 + + Label { + text: qsTr('Node') + color: Material.accentColor + } + + // gossip + TextArea { + id: node + visible: Config.useGossip + Layout.columnSpan: 2 + Layout.fillWidth: true + font.family: FixedFont + wrapMode: Text.Wrap + placeholderText: qsTr('Paste or scan node uri/pubkey') + onActiveFocusChanged: { + if (!activeFocus) + channelopener.nodeid = text + } + } + + RowLayout { + visible: Config.useGossip + spacing: 0 + ToolButton { + icon.source: '../../icons/paste.png' + icon.height: constants.iconSizeMedium + icon.width: constants.iconSizeMedium + onClicked: { + if (channelopener.validate_nodeid(AppController.clipboardToText())) { + channelopener.nodeid = AppController.clipboardToText() + node.text = channelopener.nodeid + } + } + } + ToolButton { + icon.source: '../../icons/qrcode.png' + icon.height: constants.iconSizeMedium + icon.width: constants.iconSizeMedium + scale: 1.2 + onClicked: { + var page = app.stack.push(Qt.resolvedUrl('Scan.qml')) + page.onFound.connect(function() { + if (channelopener.validate_nodeid(page.scanData)) { + channelopener.nodeid = page.scanData + node.text = channelopener.nodeid + } + app.stack.pop() + }) + } + } + } + + // trampoline + ComboBox { + visible: !Config.useGossip + Layout.columnSpan: 3 + Layout.fillWidth: true + model: channelopener.trampolineNodeNames + onCurrentValueChanged: { + if (activeFocus) + channelopener.nodeid = currentValue + } + // preselect a random node + Component.onCompleted: { + if (!Config.useGossip) { + currentIndex = Math.floor(Math.random() * channelopener.trampolineNodeNames.length) + channelopener.nodeid = currentValue + } + } + } + + Label { + text: qsTr('Amount') + color: Material.accentColor + } + + BtcField { + id: amount + fiatfield: amountFiat + Layout.preferredWidth: parent.width /3 + onTextChanged: channelopener.amount = Config.unitsToSats(amount.text) + enabled: !is_max.checked + } + + RowLayout { + Layout.columnSpan: 2 + Layout.fillWidth: true + Label { + text: Config.baseUnit + color: Material.accentColor + } + Switch { + id: is_max + text: qsTr('Max') + onCheckedChanged: { + channelopener.amount = checked ? MAX : Config.unitsToSats(amount.text) + } + } + } + + Item { width: 1; height: 1; visible: Daemon.fx.enabled } + + FiatField { + id: amountFiat + btcfield: amount + visible: Daemon.fx.enabled + Layout.preferredWidth: parent.width /3 + enabled: !is_max.checked + } + + Label { + visible: Daemon.fx.enabled + text: Daemon.fx.fiatCurrency + color: Material.accentColor + Layout.fillWidth: true + } + + Item { visible: Daemon.fx.enabled ; height: 1; width: 1 } + } + + Item { Layout.fillHeight: true; Layout.preferredWidth: 1 } + + FlatButton { + Layout.fillWidth: true + text: qsTr('Open Channel') + enabled: channelopener.valid + onClicked: channelopener.open_channel() + } + } + + Component { + id: confirmOpenChannelDialog + ConfirmTxDialog { + title: qsTr('Confirm Open Channel') + amountLabelText: qsTr('Channel capacity') + sendButtonText: qsTr('Open Channel') + finalizer: channelopener.finalizer + } + } + + ChannelOpener { + id: channelopener + wallet: Daemon.currentWallet + onAuthRequired: { + app.handleAuthRequired(channelopener, method) + } + onValidationError: { + if (code == 'invalid_nodeid') { + var dialog = app.messageDialog.createObject(app, { 'text': message }) + dialog.open() + } + } + onConflictingBackup: { + var dialog = app.messageDialog.createObject(app, { 'text': message, 'yesno': true }) + dialog.open() + dialog.yesClicked.connect(function() { + channelopener.open_channel(true) + }) + } + onFinalizerChanged: { + var dialog = confirmOpenChannelDialog.createObject(app, { + 'satoshis': channelopener.amount + }) + dialog.txaccepted.connect(function() { + dialog.finalizer.signAndSend() + }) + dialog.open() + } + onChannelOpening: { + console.log('Channel is opening') + app.channelOpenProgressDialog.reset() + app.channelOpenProgressDialog.peer = peer + app.channelOpenProgressDialog.open() + } + onChannelOpenError: { + app.channelOpenProgressDialog.state = 'failed' + app.channelOpenProgressDialog.error = message + } + onChannelOpenSuccess: { + var message = qsTr('Channel established.') + ' ' + + qsTr('This channel will be usable after %1 confirmations').arg(min_depth) + if (!tx_complete) { + message = message + '\n\n' + qsTr('Please sign and broadcast the funding transaction.') + channelopener.wallet.historyModel.init_model() // local tx doesn't trigger model update + } + app.channelOpenProgressDialog.state = 'success' + app.channelOpenProgressDialog.info = message + if (!has_onchain_backup) { + app.channelOpenProgressDialog.channelBackup = channelopener.channelBackup(cid) + } + // TODO: handle incomplete TX + channelopener.wallet.channelModel.new_channel(cid) + root.close() + } + } +} diff --git a/electrum/gui/qml/components/SwapDialog.qml b/electrum/gui/qml/components/SwapDialog.qml index 14eb5fd64..43ddb283a 100644 --- a/electrum/gui/qml/components/SwapDialog.qml +++ b/electrum/gui/qml/components/SwapDialog.qml @@ -14,6 +14,7 @@ ElDialog { height: parent.height title: qsTr('Lightning Swap') + iconSource: Qt.resolvedUrl('../../icons/update.png') standardButtons: Dialog.Cancel modal: true diff --git a/electrum/gui/qml/qechannelopener.py b/electrum/gui/qml/qechannelopener.py index fb4229df2..864b5f656 100644 --- a/electrum/gui/qml/qechannelopener.py +++ b/electrum/gui/qml/qechannelopener.py @@ -71,7 +71,7 @@ class QEChannelOpener(QObject, AuthMixin): @amount.setter def amount(self, amount: QEAmount): if self._amount != amount: - self._amount = amount + self._amount.copyFrom(amount) self.amountChanged.emit() self.validate()