9 changed files with 365 additions and 4 deletions
@ -0,0 +1,211 @@
|
||||
import QtQuick 2.15 |
||||
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: dialog |
||||
|
||||
enum Check { |
||||
Unknown, |
||||
Valid, |
||||
Invalid |
||||
} |
||||
|
||||
property string address |
||||
|
||||
property bool _addressValid: false |
||||
property bool _addressMine: false |
||||
property int _verified: SignVerifyMessageDialog.Check.Unknown |
||||
|
||||
implicitHeight: parent.height |
||||
implicitWidth: parent.width |
||||
|
||||
title: qsTr('Sign/Verify Message') |
||||
iconSource: Qt.resolvedUrl('../../icons/pen.png') |
||||
|
||||
padding: 0 |
||||
|
||||
function validateAddress() { |
||||
// TODO: not all types of addresses are valid (e.g. p2wsh) |
||||
_addressValid = bitcoin.isAddress(addressField.text) |
||||
_addressMine = Daemon.currentWallet.isAddressMine(addressField.text) |
||||
} |
||||
|
||||
ColumnLayout { |
||||
width: parent.width |
||||
height: parent.height |
||||
spacing: constants.paddingLarge |
||||
|
||||
ColumnLayout { |
||||
Layout.fillWidth: true |
||||
Layout.fillHeight: true |
||||
Layout.leftMargin: constants.paddingLarge |
||||
Layout.rightMargin: constants.paddingLarge |
||||
|
||||
Label { |
||||
text: qsTr('Address') |
||||
color: Material.accentColor |
||||
} |
||||
|
||||
RowLayout { |
||||
Layout.fillWidth: true |
||||
TextField { |
||||
id: addressField |
||||
Layout.fillWidth: true |
||||
placeholderText: qsTr('Address') |
||||
font.family: FixedFont |
||||
onTextChanged: { |
||||
validateAddress() |
||||
_verified = SignVerifyMessageDialog.Check.Unknown |
||||
} |
||||
} |
||||
ToolButton { |
||||
icon.source: '../../icons/paste.png' |
||||
icon.color: 'transparent' |
||||
onClicked: { |
||||
addressField.text = AppController.clipboardToText() |
||||
} |
||||
} |
||||
} |
||||
|
||||
Label { |
||||
text: qsTr('Message') |
||||
color: Material.accentColor |
||||
} |
||||
|
||||
RowLayout { |
||||
Layout.fillWidth: true |
||||
Layout.fillHeight: true |
||||
ElTextArea { |
||||
id: plaintext |
||||
Layout.fillWidth: true |
||||
Layout.fillHeight: true |
||||
font.family: FixedFont |
||||
wrapMode: TextInput.Wrap |
||||
background: PaneInsetBackground { |
||||
baseColor: constants.darkerDialogBackground |
||||
} |
||||
onTextChanged: _verified = SignVerifyMessageDialog.Check.Unknown |
||||
} |
||||
ColumnLayout { |
||||
Layout.alignment: Qt.AlignTop |
||||
ToolButton { |
||||
icon.source: '../../icons/paste.png' |
||||
icon.color: 'transparent' |
||||
onClicked: { |
||||
plaintext.text = AppController.clipboardToText() |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
RowLayout { |
||||
Layout.fillWidth: true |
||||
Label { |
||||
text: qsTr('Signature') |
||||
color: Material.accentColor |
||||
} |
||||
Label { |
||||
Layout.alignment: Qt.AlignRight |
||||
visible: _verified != SignVerifyMessageDialog.Check.Unknown |
||||
text: _verified == SignVerifyMessageDialog.Check.Valid |
||||
? qsTr('Valid!') |
||||
: qsTr('Invalid!') |
||||
color: _verified == SignVerifyMessageDialog.Check.Valid |
||||
? constants.colorDone |
||||
: constants.colorError |
||||
} |
||||
} |
||||
RowLayout { |
||||
Layout.fillWidth: true |
||||
ElTextArea { |
||||
id: signature |
||||
Layout.fillWidth: true |
||||
Layout.maximumHeight: fontMetrics.lineSpacing * 4 + topPadding + bottomPadding |
||||
Layout.minimumHeight: fontMetrics.lineSpacing * 4 + topPadding + bottomPadding |
||||
font.family: FixedFont |
||||
wrapMode: TextInput.Wrap |
||||
background: PaneInsetBackground { |
||||
baseColor: _verified == SignVerifyMessageDialog.Check.Unknown |
||||
? constants.darkerDialogBackground |
||||
: _verified == SignVerifyMessageDialog.Check.Valid |
||||
? constants.colorValidBackground |
||||
: constants.colorInvalidBackground |
||||
} |
||||
onTextChanged: _verified = SignVerifyMessageDialog.Check.Unknown |
||||
} |
||||
ColumnLayout { |
||||
Layout.alignment: Qt.AlignTop |
||||
ToolButton { |
||||
icon.source: '../../icons/paste.png' |
||||
icon.color: 'transparent' |
||||
onClicked: { |
||||
signature.text = AppController.clipboardToText() |
||||
} |
||||
} |
||||
ToolButton { |
||||
icon.source: '../../icons/share.png' |
||||
icon.color: enabled ? 'transparent' : Material.iconDisabledColor |
||||
enabled: signature.text |
||||
onClicked: { |
||||
var dialog = app.genericShareDialog.createObject(app, { |
||||
title: qsTr('Message signature'), |
||||
text_qr: signature.text |
||||
}) |
||||
dialog.open() |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
ButtonContainer { |
||||
Layout.fillWidth: true |
||||
FlatButton { |
||||
Layout.fillWidth: true |
||||
Layout.preferredWidth: 1 |
||||
text: qsTr('Sign') |
||||
visible: Daemon.currentWallet.canSignMessage |
||||
enabled: _addressMine |
||||
icon.source: '../../icons/seal.png' |
||||
onClicked: { |
||||
var sig = Daemon.currentWallet.signMessage(addressField.text, plaintext.text) |
||||
signature.text = sig |
||||
} |
||||
} |
||||
FlatButton { |
||||
Layout.fillWidth: true |
||||
Layout.preferredWidth: 1 |
||||
enabled: _addressValid && signature.text |
||||
text: qsTr('Verify') |
||||
icon.source: '../../icons/confirmed.png' |
||||
onClicked: { |
||||
var result = Daemon.verifyMessage(addressField.text, plaintext.text, signature.text) |
||||
_verified = result |
||||
? SignVerifyMessageDialog.Check.Valid |
||||
: SignVerifyMessageDialog.Check.Invalid |
||||
} |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
Component.onCompleted: { |
||||
addressField.text = address |
||||
} |
||||
|
||||
Bitcoin { |
||||
id: bitcoin |
||||
} |
||||
|
||||
FontMetrics { |
||||
id: fontMetrics |
||||
font: signature.font |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,67 @@
|
||||
import QtQuick 2.15 |
||||
import QtQuick.Layouts 1.0 |
||||
import QtQuick.Controls 2.14 |
||||
import QtQuick.Controls.Material 2.0 |
||||
|
||||
import org.electrum 1.0 |
||||
|
||||
// this component adds (auto)scrolling to the bare TextArea, to make it |
||||
// workable if text overflows the available space. |
||||
// This unfortunately hides many signals and properties from the TextArea, |
||||
// so add signals propagation and property aliases when needed. |
||||
Flickable { |
||||
id: root |
||||
|
||||
property alias text: edit.text |
||||
property alias wrapMode: edit.wrapMode |
||||
property alias background: rootpane.background |
||||
property alias font: edit.font |
||||
|
||||
contentWidth: rootpane.width |
||||
contentHeight: rootpane.height |
||||
clip: true |
||||
|
||||
boundsBehavior: Flickable.StopAtBounds |
||||
flickableDirection: Flickable.VerticalFlick |
||||
|
||||
function ensureVisible(r) { |
||||
r.x = r.x + rootpane.leftPadding |
||||
r.y = r.y + rootpane.topPadding |
||||
var w = width - rootpane.leftPadding - rootpane.rightPadding |
||||
var h = height - rootpane.topPadding - rootpane.bottomPadding |
||||
if (contentX >= r.x) |
||||
contentX = r.x |
||||
else if (contentX+w <= r.x+r.width) |
||||
contentX = r.x+r.width-w |
||||
if (contentY >= r.y) |
||||
contentY = r.y |
||||
else if (contentY+h <= r.y+r.height) |
||||
contentY = r.y+r.height-h |
||||
} |
||||
|
||||
Pane { |
||||
id: rootpane |
||||
width: root.width |
||||
height: Math.max(root.height, edit.height + topPadding + bottomPadding) |
||||
padding: constants.paddingXSmall |
||||
TextArea { |
||||
id: edit |
||||
width: parent.width |
||||
focus: true |
||||
wrapMode: TextEdit.Wrap |
||||
onCursorRectangleChanged: root.ensureVisible(cursorRectangle) |
||||
onTextChanged: root.textChanged() |
||||
background: Rectangle { |
||||
color: 'transparent' |
||||
} |
||||
} |
||||
MouseArea { |
||||
// remaining area clicks focus textarea |
||||
width: parent.width |
||||
anchors.top: edit.bottom |
||||
anchors.bottom: parent.bottom |
||||
onClicked: edit.forceActiveFocus() |
||||
} |
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue