diff --git a/electrum/gui/icons/pen.png b/electrum/gui/icons/pen.png new file mode 100644 index 000000000..74b9468e5 Binary files /dev/null and b/electrum/gui/icons/pen.png differ 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')