From bb2b1738b7fb0ec6581d2671a10bc76f5157bd67 Mon Sep 17 00:00:00 2001 From: Sander van Grieken Date: Wed, 4 May 2022 15:01:50 +0200 Subject: [PATCH] add initial address detail page --- electrum/gui/icons/pen.png | Bin 0 -> 1641 bytes .../gui/qml/components/AddressDetails.qml | 243 ++++++++++++++++++ electrum/gui/qml/components/Addresses.qml | 78 +----- .../controls/GenericShareDialog.qml | 107 ++++++++ .../components/controls/TextHighlightPane.qml | 10 + electrum/gui/qml/qeaddressdetails.py | 113 ++++++++ electrum/gui/qml/qeapp.py | 2 + 7 files changed, 483 insertions(+), 70 deletions(-) create mode 100644 electrum/gui/icons/pen.png create mode 100644 electrum/gui/qml/components/AddressDetails.qml create mode 100644 electrum/gui/qml/components/controls/GenericShareDialog.qml create mode 100644 electrum/gui/qml/components/controls/TextHighlightPane.qml create mode 100644 electrum/gui/qml/qeaddressdetails.py diff --git a/electrum/gui/icons/pen.png b/electrum/gui/icons/pen.png new file mode 100644 index 0000000000000000000000000000000000000000..74b9468e5b958186bb8dce3460848d674f3da218 GIT binary patch literal 1641 zcmV-v2A27WP)WFU8GbZ8()Nlj2>E@cM*00qrSL_t(|+TELLh*V`5 z$A8bNZON9VXem)^NZzPOA}9nip+eC$DKDv+7KN9tmJwaC$dVFM%rMi%h}Ol-B+$sP z4{Fet*eFU&40S81EpN5T=>Gd~o&%@T%+9*Z*n8&rvdlX>XU_9~|NrN9-Z4@psRrl) zoCoB9qh@xT3N=ePKLZ#G^ar{EXMkqlQ!`sv(Evz#3aA5=0w#XF#LPBV8UR)9{Inv@ zZ;YLHiBt{%?$Crh@5RnHnAuWyeh{!h(qc*7D}@0U1ASAHh@IbTX3O3A-oTSUPhcdl zUDA9>{WBc{Bn<@C=h^wyW>)Xc4*}Nrx>8`ZI#4UJ%77fumU6y6;rs~T-GsxIiWBDp zQzbP>x;l#h90giZJHOhUzY$pC@2Q8R-up@Evr2@GMSSD#=&cpsMbcxzGQe3-4Kx6kl;L}0W^FOESA%1K zv)~l)UI}ACzUpzjq=i8O;2>-Q_Lo=+X%HqznjM@35>mJb*me%?Wrdl25EKKP1;>C@ zWs*WNCd~KdD=Y>in9vAZR^rr{wl?5S&qBcgPy=l7{=Zx-FmKN%ovTrNRfmx>I{_>z zG8-sGQ0xIb7fcLDf$#;ewTu#oK^P(F?w}-)lEV^UL>c2jeBC)_))XQD7kZJ@x6DCt zKQP10+JcZkLJG%$xxkS!Ixk=V@K7)@AO*tDesis5tPcPtNg5s;01m=F;CbM5ndLcG zQdMvOI0(CdhaH4+F<~e$KA0d_ATW*sUIe<8Q4Y6yJrWKB5=_|POlU1bu@qlxesB^< zfzagH;dGf==)pdy2@?PZVGl61#B&4#egYN%y@LiIks|K}_LKkw0SAEDKsE5bq?>~V zz(L5F*({)uf1f)zTiqW3<^a8b-^@sbfOe~z&pe>M29ot(tD?w zEew_cDK>l!+~ty>2pi({W?&w0frHmGPscbh6AVZ&p&A%Nbkqh4AIjS6DYZNBHqfWQ z|Nae3H?zZ;03ZQGA7B|!%YTpE9QFVzU@g&^#rCYp%;p3wfjpA<4VVGE><-6IjrIVu zh{kTQ^C}W3ua$IPrWlZ7Lly8SFbbGXbY^Ki(67k-#gb?N>N-0!_}>9Y0%2xtiRp7k zCPoLnK4^8o>O4DqlQ{qqK-3aFhv-pc+Vlr-hnL-hfTf-(a=I7M_i?JWn)QlPZ~9|E75*|(ViAQ2mGAsWReLHN^aij%-VpuPZ8l53xs z*(@5 zmPRfykV!J2HK;nE6Cj*2vwEKf4FR4=Xg;g%Q+kFH?tokP4TW$MCTvJo7u^%O8}tB1D*0@H3Qa48f0b-z{kE;1H4cP zF#t0=uGAi99P4gY!?LZ8XWU=#ARK9H_`cYUsGP~U~ ni87r9Ix$=5@BTI?=ZF6QbPQsgx;mAH00000NkvXXu0mjfMi9Vx literal 0 HcmV?d00001 diff --git a/electrum/gui/qml/components/AddressDetails.qml b/electrum/gui/qml/components/AddressDetails.qml new file mode 100644 index 000000000..7149685eb --- /dev/null +++ b/electrum/gui/qml/components/AddressDetails.qml @@ -0,0 +1,243 @@ +import QtQuick 2.6 +import QtQuick.Layouts 1.0 +import QtQuick.Controls 2.3 +import QtQuick.Controls.Material 2.0 + +import org.electrum 1.0 + +import "controls" + +Pane { + id: root + width: parent.width + height: parent.height + + property string address + + property string title: qsTr("Address details") + + signal addressDetailsChanged + + property QtObject menu: Menu { + id: menu + MenuItem { + icon.color: 'transparent' + action: Action { + text: qsTr('Spend from') + //onTriggered: + icon.source: '../../icons/tab_send.png' + } + } + MenuItem { + icon.color: 'transparent' + action: Action { + text: qsTr('Sign/Verify') + icon.source: '../../icons/key.png' + } + } + MenuItem { + icon.color: 'transparent' + action: Action { + text: qsTr('Encrypt/Decrypt') + icon.source: '../../icons/mail_icon.png' + } + } + } + + Flickable { + anchors.fill: parent + contentHeight: rootLayout.height + clip:true + interactive: height < contentHeight + + GridLayout { + id: rootLayout + width: parent.width + columns: 2 + + Label { + text: qsTr('Address') + Layout.columnSpan: 2 + } + + TextHighlightPane { + Layout.columnSpan: 2 + Layout.fillWidth: true + padding: 0 + leftPadding: constants.paddingSmall + + RowLayout { + width: parent.width + Label { + text: root.address + font.family: FixedFont + Layout.fillWidth: true + } + ToolButton { + icon.source: '../../icons/share.png' + icon.color: 'transparent' + onClicked: { + var dialog = share.createObject(root, { 'title': qsTr('Address'), 'text': root.address }) + dialog.open() + } + } + } + } + + Label { + text: qsTr('Label') + Layout.columnSpan: 2 + } + + TextHighlightPane { + id: labelContent + + property bool editmode: false + + Layout.columnSpan: 2 + Layout.fillWidth: true + padding: 0 + leftPadding: constants.paddingSmall + + RowLayout { + width: parent.width + Label { + visible: !labelContent.editmode + text: addressdetails.label + wrapMode: Text.Wrap + Layout.fillWidth: true + } + ToolButton { + visible: !labelContent.editmode + icon.source: '../../icons/pen.png' + icon.color: 'transparent' + onClicked: { + labelEdit.text = addressdetails.label + labelContent.editmode = true + } + } + TextField { + id: labelEdit + visible: labelContent.editmode + text: addressdetails.label + Layout.fillWidth: true + } + ToolButton { + visible: labelContent.editmode + icon.source: '../../icons/confirmed.png' + icon.color: 'transparent' + onClicked: { + labelContent.editmode = false + addressdetails.set_label(labelEdit.text) + } + } + ToolButton { + visible: labelContent.editmode + icon.source: '../../icons/delete.png' + icon.color: 'transparent' + onClicked: labelContent.editmode = false + } + } + } + + Label { + text: qsTr('Public keys') + Layout.columnSpan: 2 + } + + Repeater { + model: addressdetails.pubkeys + delegate: TextHighlightPane { + Layout.columnSpan: 2 + Layout.fillWidth: true + padding: 0 + leftPadding: constants.paddingSmall + RowLayout { + width: parent.width + Label { + text: modelData + Layout.fillWidth: true + wrapMode: Text.Wrap + font.family: FixedFont + } + ToolButton { + icon.source: '../../icons/share.png' + icon.color: 'transparent' + onClicked: { + var dialog = share.createObject(root, { 'title': qsTr('Public key'), 'text': modelData }) + dialog.open() + } + } + } + } + } + + Label { + text: qsTr('Script type') + } + + Label { + text: addressdetails.scriptType + Layout.fillWidth: true + } + + Label { + text: qsTr('Balance') + } + + RowLayout { + Label { + font.family: FixedFont + text: Config.formatSats(addressdetails.balance) + } + Label { + color: Material.accentColor + text: Config.baseUnit + } + Label { + text: Daemon.fx.enabled + ? '(' + Daemon.fx.fiatValue(addressdetails.balance) + ' ' + Daemon.fx.fiatCurrency + ')' + : '' + } + } + + Label { + text: qsTr('Derivation path') + } + + Label { + text: addressdetails.derivationPath + } + + Label { + text: qsTr('Frozen') + } + + Label { + text: addressdetails.isFrozen ? qsTr('Frozen') : qsTr('Not frozen') + } + + ColumnLayout { + Layout.columnSpan: 2 + + Button { + text: addressdetails.isFrozen ? qsTr('Unfreeze') : qsTr('Freeze') + onClicked: addressdetails.freeze(!addressdetails.isFrozen) + } + } + } + } + + AddressDetails { + id: addressdetails + wallet: Daemon.currentWallet + address: root.address + onFrozenChanged: addressDetailsChanged() + onLabelChanged: addressDetailsChanged() + } + + Component { + id: share + GenericShareDialog {} + } +} diff --git a/electrum/gui/qml/components/Addresses.qml b/electrum/gui/qml/components/Addresses.qml index 51120b75c..111d47ee2 100644 --- a/electrum/gui/qml/components/Addresses.qml +++ b/electrum/gui/qml/components/Addresses.qml @@ -40,17 +40,13 @@ Pane { font.pixelSize: constants.fontSizeMedium // set default font size for child controls - onClicked: ListView.view.currentIndex == index - ? ListView.view.currentIndex = -1 - : ListView.view.currentIndex = index - - states: [ - State { - name: 'highlighted'; when: highlighted - PropertyChanges { target: drawer; visible: true } - PropertyChanges { target: labelLabel; maximumLineCount: 4 } - } - ] + onClicked: { + var page = app.stack.push(Qt.resolvedUrl('AddressDetails.qml'), {'address': model.address}) + page.addressDetailsChanged.connect(function() { + // update listmodel when details change + listview.model.update_address(model.address) + }) + } ColumnLayout { id: delegateLayout @@ -83,7 +79,7 @@ Pane { Layout.preferredWidth: constants.iconSizeMedium Layout.preferredHeight: constants.iconSizeMedium color: model.held - ? Qt.rgba(1,0.93,0,0.75) + ? Qt.rgba(1,0,0,0.75) : model.numtx > 0 ? model.balance == 0 ? Qt.rgba(0.5,0.5,0.5,1) @@ -126,64 +122,6 @@ Pane { } } - RowLayout { - id: drawer - visible: false - Layout.fillWidth: true - Layout.preferredHeight: copyButton.height - - ToolButton { - id: copyButton - icon.source: '../../icons/copy.png' - icon.color: 'transparent' - icon.width: constants.iconSizeMedium - icon.height: constants.iconSizeMedium - onClicked: console.log('TODO: copy address') - } - ToolButton { - icon.source: '../../icons/info.png' - icon.color: 'transparent' - icon.width: constants.iconSizeMedium - icon.height: constants.iconSizeMedium - onClicked: console.log('TODO: show details screen') - } - ToolButton { - icon.source: '../../icons/key.png' - icon.color: 'transparent' - icon.width: constants.iconSizeMedium - icon.height: constants.iconSizeMedium - onClicked: console.log('TODO: sign/verify dialog') - } - ToolButton { - icon.source: '../../icons/mail_icon.png' - icon.color: 'transparent' - icon.width: constants.iconSizeMedium - icon.height: constants.iconSizeMedium - onClicked: console.log('TODO: encrypt/decrypt message dialog') - } - ToolButton { - icon.source: '../../icons/globe.png' - icon.color: 'transparent' - icon.width: constants.iconSizeMedium - icon.height: constants.iconSizeMedium - onClicked: console.log('TODO: show on block explorer') - } - ToolButton { - icon.source: '../../icons/unlock.png' - icon.color: 'transparent' - icon.width: constants.iconSizeMedium - icon.height: constants.iconSizeMedium - onClicked: console.log('TODO: freeze/unfreeze') - } - ToolButton { - icon.source: '../../icons/tab_send.png' - icon.color: 'transparent' - icon.width: constants.iconSizeMedium - icon.height: constants.iconSizeMedium - onClicked: console.log('TODO: spend from address') - } - } - Item { Layout.preferredWidth: 1 Layout.preferredHeight: constants.paddingSmall diff --git a/electrum/gui/qml/components/controls/GenericShareDialog.qml b/electrum/gui/qml/components/controls/GenericShareDialog.qml new file mode 100644 index 000000000..3df017071 --- /dev/null +++ b/electrum/gui/qml/components/controls/GenericShareDialog.qml @@ -0,0 +1,107 @@ +import QtQuick 2.6 +import QtQuick.Layouts 1.0 +import QtQuick.Controls 2.14 +import QtQuick.Controls.Material 2.0 + +Dialog { + id: dialog + + property string text + + title: '' + parent: Overlay.overlay + modal: true + standardButtons: Dialog.Ok + + width: parent.width + height: parent.height + + Overlay.modal: Rectangle { + color: "#aa000000" + } + + header: RowLayout { + width: dialog.width + Label { + Layout.fillWidth: true + text: dialog.title + visible: dialog.title + elide: Label.ElideRight + padding: constants.paddingXLarge + bottomPadding: 0 + font.bold: true + font.pixelSize: constants.fontSizeMedium + } + } + + ColumnLayout { + id: rootLayout + width: parent.width + spacing: constants.paddingMedium + + Rectangle { + height: 1 + Layout.fillWidth: true + color: Material.accentColor + } + + Image { + id: qr + Layout.alignment: Qt.AlignHCenter + Layout.topMargin: constants.paddingSmall + Layout.bottomMargin: constants.paddingSmall + + Rectangle { + property int size: 57 // should be qr pixel multiple + color: 'white' + x: (parent.width - size) / 2 + y: (parent.height - size) / 2 + width: size + height: size + + Image { + source: '../../../icons/electrum.png' + x: 1 + y: 1 + width: parent.width - 2 + height: parent.height - 2 + scale: 0.9 + } + } + } + + Rectangle { + height: 1 + Layout.fillWidth: true + color: Material.accentColor + } + + TextHighlightPane { + Layout.fillWidth: true + Label { + width: parent.width + text: dialog.text + wrapMode: Text.Wrap + } + } + + RowLayout { + Layout.fillWidth: true + Layout.alignment: Qt.AlignHCenter + Button { + text: qsTr('Copy') + icon.source: '../../../icons/copy_bw.png' + onClicked: AppController.textToClipboard(dialog.text) + } + Button { + text: qsTr('Share') + icon.source: '../../../icons/share.png' + onClicked: console.log('TODO') + } + } + } + + Component.onCompleted: { + qr.source = 'image://qrgen/' + dialog.text + } +} diff --git a/electrum/gui/qml/components/controls/TextHighlightPane.qml b/electrum/gui/qml/components/controls/TextHighlightPane.qml new file mode 100644 index 000000000..9920d28c7 --- /dev/null +++ b/electrum/gui/qml/components/controls/TextHighlightPane.qml @@ -0,0 +1,10 @@ +import QtQuick 2.6 +import QtQuick.Layouts 1.0 +import QtQuick.Controls 2.0 +import QtQuick.Controls.Material 2.0 + +Pane { + background: Rectangle { + color: Qt.lighter(Material.background, 1.15) + } +} diff --git a/electrum/gui/qml/qeaddressdetails.py b/electrum/gui/qml/qeaddressdetails.py new file mode 100644 index 000000000..6c78a4ded --- /dev/null +++ b/electrum/gui/qml/qeaddressdetails.py @@ -0,0 +1,113 @@ +from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject + +from decimal import Decimal + +from electrum.logging import get_logger +from electrum.util import DECIMAL_POINT_DEFAULT + +from .qetransactionlistmodel import QEAddressTransactionListModel +from .qewallet import QEWallet +from .qetypes import QEAmount + +class QEAddressDetails(QObject): + def __init__(self, parent=None): + super().__init__(parent) + + _logger = get_logger(__name__) + + _wallet = None + _address = None + + _label = None + _frozen = False + _scriptType = None + _status = None + _balance = QEAmount() + _pubkeys = None + _privkey = None + _derivationPath = None + + _txlistmodel = None + + detailsChanged = pyqtSignal() + + walletChanged = pyqtSignal() + @pyqtProperty(QEWallet, notify=walletChanged) + def wallet(self): + return self._wallet + + @wallet.setter + def wallet(self, wallet: QEWallet): + if self._wallet != wallet: + self._wallet = wallet + self.walletChanged.emit() + + addressChanged = pyqtSignal() + @pyqtProperty(str, notify=addressChanged) + def address(self): + return self._address + + @address.setter + def address(self, address: str): + if self._address != address: + self._logger.debug('address changed') + self._address = address + self.addressChanged.emit() + self.update() + + @pyqtProperty(str, notify=detailsChanged) + def scriptType(self): + return self._scriptType + + @pyqtProperty(QEAmount, notify=detailsChanged) + def balance(self): + return self._balance + + @pyqtProperty('QStringList', notify=detailsChanged) + def pubkeys(self): + return self._pubkeys + + @pyqtProperty(str, notify=detailsChanged) + def derivationPath(self): + return self._derivationPath + + + frozenChanged = pyqtSignal() + @pyqtProperty(bool, notify=frozenChanged) + def isFrozen(self): + return self._frozen + + labelChanged = pyqtSignal() + @pyqtProperty(str, notify=labelChanged) + def label(self): + return self._label + + @pyqtSlot(bool) + def freeze(self, freeze: bool): + if freeze != self._frozen: + self._wallet.wallet.set_frozen_state_of_addresses([self._address], freeze=freeze) + self._frozen = freeze + self.frozenChanged.emit() + + @pyqtSlot(str) + def set_label(self, label: str): + if label != self._label: + self._wallet.wallet.set_label(self._address, label) + self._label = label + self.labelChanged.emit() + + def update(self): + if self._wallet is None: + self._logger.error('wallet undefined') + return + + self._frozen = self._wallet.wallet.is_frozen_address(self._address) + self.frozenChanged.emit() + + self._scriptType = self._wallet.wallet.get_txin_type(self._address) + self._label = self._wallet.wallet.get_label(self._address) + c, u, x = self._wallet.wallet.get_addr_balance(self._address) + self._balance = QEAmount(amount_sat=c + u + x) + self._pubkeys = self._wallet.wallet.get_public_keys(self._address) + self._derivationPath = self._wallet.wallet.get_address_path_str(self._address) + self.detailsChanged.emit() diff --git a/electrum/gui/qml/qeapp.py b/electrum/gui/qml/qeapp.py index aa7722e8b..146ac9307 100644 --- a/electrum/gui/qml/qeapp.py +++ b/electrum/gui/qml/qeapp.py @@ -21,6 +21,7 @@ from .qefx import QEFX from .qetxfinalizer import QETxFinalizer from .qeinvoice import QEInvoice from .qetypes import QEAmount +from .qeaddressdetails import QEAddressDetails notification = None @@ -118,6 +119,7 @@ class ElectrumQmlApplication(QGuiApplication): qmlRegisterType(QEFX, 'org.electrum', 1, 0, 'FX') qmlRegisterType(QETxFinalizer, 'org.electrum', 1, 0, 'TxFinalizer') qmlRegisterType(QEInvoice, 'org.electrum', 1, 0, 'Invoice') + qmlRegisterType(QEAddressDetails, 'org.electrum', 1, 0, 'AddressDetails') qmlRegisterUncreatableType(QEAmount, 'org.electrum', 1, 0, 'Amount', 'Amount can only be used as property')