diff --git a/MiddlePanel.qml b/MiddlePanel.qml index 845092a6..9c1096f6 100644 --- a/MiddlePanel.qml +++ b/MiddlePanel.qml @@ -112,7 +112,7 @@ Rectangle { }, State { name: "Receive" PropertyChanges { target: root; currentView: receiveView } - PropertyChanges { target: mainFlickable; contentHeight: minHeight } + PropertyChanges { target: mainFlickable; contentHeight: 1000 * scaleRatio } }, State { name: "TxKey" PropertyChanges { target: root; currentView: txkeyView } diff --git a/components/HistoryTable.qml b/components/HistoryTable.qml index ba3971b5..633998ee 100644 --- a/components/HistoryTable.qml +++ b/components/HistoryTable.qml @@ -266,6 +266,45 @@ ListView { } } + Item { //separator + width: 100 + height: 14 + } + // -- "Received by" title + Text { + anchors.bottom: parent.bottom + font.family: "Arial" + font.pixelSize: 12 + color: "#535353" + text: (isOut ? qsTr("Spent from:") : qsTr("Received by:")) + translationManager.emptyString + } + Item { //separator + width: 5 + height: 14 + } + // -- "Index" value + Text { + anchors.bottom: parent.bottom + font.family: "Arial" + font.pixelSize: 13 + font.bold: true + color: "#545454" + text: "#" + subaddrIndex + } + Item { //separator + width: 5 + height: 14 + } + // -- "Label" value + Text { + anchors.bottom: parent.bottom + font.family: "Arial" + font.pixelSize: 13 + color: "#545454" + text: label + elide: Text.ElideRight + width: detailsButton.x - x - 30 + } } // -- "Date", "Balance" and "Amound" section diff --git a/components/IconButton.qml b/components/IconButton.qml index 042439f1..d927e296 100644 --- a/components/IconButton.qml +++ b/components/IconButton.qml @@ -30,6 +30,7 @@ import QtQuick 2.0 Item { + property alias image : buttonImage property alias imageSource : buttonImage.source signal clicked(var mouse) @@ -53,7 +54,8 @@ Item { MouseArea { id: buttonArea anchors.fill: parent - + hoverEnabled: true + cursorShape: Qt.PointingHandCursor onPressed: { buttonImage.x = buttonImage.x + 2 diff --git a/components/InputDialog.qml b/components/InputDialog.qml new file mode 100644 index 00000000..8ab5b5d8 --- /dev/null +++ b/components/InputDialog.qml @@ -0,0 +1,181 @@ +// Copyright (c) 2014-2018, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import QtQuick 2.0 +import QtQuick.Controls 1.4 +import QtQuick.Dialogs 1.2 +import QtQuick.Layouts 1.1 +import QtQuick.Controls.Styles 1.4 +import QtQuick.Window 2.0 + +import "../components" as MoneroComponents + +Item { + id: root + visible: false + Rectangle { + id: bg + z: parent.z + 1 + anchors.fill: parent + color: "white" + opacity: 0.9 + } + + property alias labelText: label.text + property alias inputText: input.text + + // same signals as Dialog has + signal accepted() + signal rejected() + + function open() { + leftPanel.enabled = false + middlePanel.enabled = false + titleBar.enabled = false + show() + root.visible = true; + input.focus = true; + input.text = ""; + } + + function close() { + leftPanel.enabled = true + middlePanel.enabled = true + titleBar.enabled = true + root.visible = false; + } + + ColumnLayout { + z: bg.z + 1 + id: mainLayout + spacing: 10 + anchors { fill: parent; margins: 35 } + + ColumnLayout { + id: column + //anchors {fill: parent; margins: 16 } + Layout.alignment: Qt.AlignHCenter + + Label { + id: label + Layout.alignment: Qt.AlignHCenter + // Layout.columnSpan: 2 + Layout.fillWidth: true + horizontalAlignment: Text.AlignHCenter + font.pixelSize: 18 * scaleRatio + font.family: "Arial" + color: "#555555" + } + + TextField { + id : input + focus: true + Layout.fillWidth: true + Layout.alignment: Qt.AlignHCenter + horizontalAlignment: TextInput.AlignHCenter + verticalAlignment: TextInput.AlignVCenter + font.family: "Arial" + font.pixelSize: 32 * scaleRatio + // echoMode: TextInput.Password + KeyNavigation.tab: okButton + + style: TextFieldStyle { + renderType: Text.NativeRendering + textColor: "#35B05A" + // passwordCharacter: "•" + // no background + background: Rectangle { + radius: 0 + border.width: 0 + } + } + Keys.onReturnPressed: { + root.close() + root.accepted() + } + Keys.onEscapePressed: { + root.close() + root.rejected() + } + } + // underline + Rectangle { + height: 1 + color: "#DBDBDB" + Layout.fillWidth: true + Layout.alignment: Qt.AlignHCenter + anchors.bottomMargin: 3 + } + // padding + Rectangle { + Layout.fillWidth: true + Layout.alignment: Qt.AlignHCenter + height: 10 + opacity: 0 + color: "black" + } + } + // Ok/Cancel buttons + RowLayout { + id: buttons + spacing: 60 + Layout.alignment: Qt.AlignHCenter + + MoneroComponents.StandardButton { + id: cancelButton + width: 120 + fontSize: 14 + shadowReleasedColor: "#FF4304" + shadowPressedColor: "#B32D00" + releasedColor: "#FF6C3C" + pressedColor: "#FF4304" + text: qsTr("Cancel") + translationManager.emptyString + KeyNavigation.tab: input + onClicked: { + root.close() + root.rejected() + } + } + MoneroComponents.StandardButton { + id: okButton + width: 120 + fontSize: 14 + shadowReleasedColor: "#FF4304" + shadowPressedColor: "#B32D00" + releasedColor: "#FF6C3C" + pressedColor: "#FF4304" + text: qsTr("Ok") + KeyNavigation.tab: cancelButton + onClicked: { + root.close() + root.accepted() + } + } + } + } +} diff --git a/components/LineEdit.qml b/components/LineEdit.qml index f7cb0612..4e34f241 100644 --- a/components/LineEdit.qml +++ b/components/LineEdit.qml @@ -37,6 +37,7 @@ Item { property alias cursorPosition: input.cursorPosition property alias echoMode: input.echoMode property int fontSize: 18 * scaleRatio + property bool showBorder: true property bool error: false signal editingFinished() signal accepted(); @@ -52,6 +53,7 @@ Item { } Rectangle { + visible: showBorder anchors.fill: parent anchors.bottomMargin: 1 * scaleRatio color: "#DBDBDB" diff --git a/components/SubaddressTable.qml b/components/SubaddressTable.qml new file mode 100644 index 00000000..8d6b8d1d --- /dev/null +++ b/components/SubaddressTable.qml @@ -0,0 +1,109 @@ +// Copyright (c) 2014-2018, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import QtQuick 2.0 +import moneroComponents.Clipboard 1.0 + +ListView { + id: listView + clip: true + boundsBehavior: ListView.StopAtBounds + highlightMoveDuration: 0 + + delegate: Rectangle { + id: delegate + height: 64 + width: listView.width + + LineEdit { + id: addressLine + fontSize: 12 + readOnly: true + width: parent.width + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.margins: 5 + onTextChanged: cursorPosition = 0 + text: address + showBorder: false + + IconButton { + id: clipboardButton + imageSource: "../images/copyToClipboard.png" + onClicked: { + console.log(addressLine.text + " copied to clipboard"); + clipboard.setText(addressLine.text); + appWindow.showStatusMessage(qsTr("Address copied to clipboard"),3); + } + } + } + + Text { + id: indexText + anchors.top: addressLine.bottom + anchors.left: parent.left + anchors.leftMargin: 20 + font.family: "Arial" + font.bold: true + font.pixelSize: 12 + color: "#444444" + text: "#" + index + } + + Text { + id: labelText + anchors.top: addressLine.bottom + anchors.left: indexText.right + anchors.right: parent.right + anchors.leftMargin: 10 + font.family: "Arial" + font.bold: true + font.pixelSize: 12 + color: "#444444" + text: label + } + + MouseArea { + z: 5 + anchors.top: parent.top + anchors.left: parent.left + anchors.bottom: parent.bottom + anchors.right: parent.right + anchors.rightMargin: clipboardButton.width + onClicked: listView.currentIndex = index + } + } + + highlight: Rectangle { + height: 64 + color: '#FF4304' + opacity: 0.2 + z: 2 + } +} diff --git a/main.cpp b/main.cpp index 3e15d373..43608815 100644 --- a/main.cpp +++ b/main.cpp @@ -50,6 +50,8 @@ #include "model/TransactionHistorySortFilterModel.h" #include "AddressBook.h" #include "model/AddressBookModel.h" +#include "Subaddress.h" +#include "model/SubaddressModel.h" #include "wallet/api/wallet2_api.h" #include "MainApp.h" @@ -133,6 +135,12 @@ int main(int argc, char *argv[]) qmlRegisterUncreatableType("moneroComponents.AddressBook", 1, 0, "AddressBook", "AddressBook can't be instantiated directly"); + qmlRegisterUncreatableType("moneroComponents.SubaddressModel", 1, 0, "SubaddressModel", + "SubaddressModel can't be instantiated directly"); + + qmlRegisterUncreatableType("moneroComponents.Subaddress", 1, 0, "Subaddress", + "Subaddress can't be instantiated directly"); + qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); diff --git a/main.qml b/main.qml index 387d1b5d..01197161 100644 --- a/main.qml +++ b/main.qml @@ -310,8 +310,10 @@ ApplicationWindow { } function updateBalance() { - middlePanel.unlockedBalanceText = leftPanel.unlockedBalanceText = middlePanel.state === "Receive" ? qsTr("HIDDEN") : walletManager.displayAmount(currentWallet.unlockedBalance); - middlePanel.balanceText = leftPanel.balanceText = middlePanel.state === "Receive" ? qsTr("HIDDEN") : walletManager.displayAmount(currentWallet.balance); + if (!currentWallet) + return; + middlePanel.unlockedBalanceText = leftPanel.unlockedBalanceText = middlePanel.state === "Receive" ? qsTr("HIDDEN") : walletManager.displayAmount(currentWallet.unlockedBalance(currentWallet.currentSubaddressAccount)); + middlePanel.balanceText = leftPanel.balanceText = middlePanel.state === "Receive" ? qsTr("HIDDEN") : walletManager.displayAmount(currentWallet.balance(currentWallet.currentSubaddressAccount)); } function onWalletConnectionStatusChanged(status){ @@ -329,7 +331,7 @@ ApplicationWindow { } // initialize transaction history once wallet is initialized first time; if (!walletInitialized) { - currentWallet.history.refresh() + currentWallet.history.refresh(currentWallet.currentSubaddressAccount) walletInitialized = true } } @@ -377,7 +379,7 @@ ApplicationWindow { if(foundNewBlock) { foundNewBlock = false; console.log("New block found - updating history") - currentWallet.history.refresh() + currentWallet.history.refresh(currentWallet.currentSubaddressAccount) timeToUnlock = currentWallet.history.minutesToUnlock leftPanel.minutesToUnlockTxt = (timeToUnlock > 0)? (timeToUnlock == 20)? qsTr("Unlocked balance (waiting for block)") : qsTr("Unlocked balance (~%1 min)").arg(timeToUnlock) : qsTr("Unlocked balance"); } @@ -453,6 +455,9 @@ ApplicationWindow { console.log("Saving wallet after first refresh"); currentWallet.store() isNewWallet = false + + // Update History + currentWallet.history.refresh(currentWallet.currentSubaddressAccount); } // recovering from seed is finished after first refresh @@ -463,7 +468,7 @@ ApplicationWindow { // Update history on every refresh if it's empty if(currentWallet.history.count == 0) - currentWallet.history.refresh() + currentWallet.history.refresh(currentWallet.currentSubaddressAccount) onWalletUpdate(); } @@ -526,19 +531,21 @@ ApplicationWindow { currentWallet.refresh() console.log("Confirmed money found") // history refresh is handled by walletUpdated + currentWallet.history.refresh(currentWallet.currentSubaddressAccount) // this will refresh model + currentWallet.subaddress.refresh(currentWallet.currentSubaddressAccount) } function onWalletUnconfirmedMoneyReceived(txId, amount) { // refresh history console.log("unconfirmed money found") - currentWallet.history.refresh() + currentWallet.history.refresh(currentWallet.currentSubaddressAccount) } function onWalletMoneySent(txId, amount) { // refresh transaction history here console.log("money sent found") currentWallet.refresh() - currentWallet.history.refresh() // this will refresh model + currentWallet.history.refresh(currentWallet.currentSubaddressAccount) // this will refresh model } function walletsFound() { @@ -584,8 +591,11 @@ ApplicationWindow { // here we show confirmation popup; transactionConfirmationPopup.title = qsTr("Confirmation") + translationManager.emptyString - transactionConfirmationPopup.text = qsTr("Please confirm transaction:\n") - + (address === "" ? "" : (qsTr("\nAddress: ") + address)) + transactionConfirmationPopup.text = qsTr("Please confirm transaction:\n"); + for (var i = 0; i < transaction.subaddrIndices.length; ++i) + transactionConfirmationPopup.text += qsTr("\nSpending address index: ") + transaction.subaddrIndices[i] + transactionConfirmationPopup.text += + (address === "" ? "" : (qsTr("\n\nAddress: ") + address)) + (paymentId === "" ? "" : (qsTr("\nPayment ID: ") + paymentId)) + qsTr("\n\nAmount: ") + walletManager.displayAmount(transaction.amount) + qsTr("\nFee: ") + walletManager.displayAmount(transaction.fee) @@ -1185,6 +1195,23 @@ ApplicationWindow { } } + InputDialog { + id: inputDialog + visible: false + z: parent.z + 1 + anchors.fill: parent + property var onAcceptedCallback + property var onRejectedCallback + onAccepted: { + if (onAcceptedCallback) + onAcceptedCallback() + } + onRejected: { + if (onRejectedCallback) + onRejectedCallback() + } + } + DaemonManagerDialog { id: daemonManagerDialog onRejected: { diff --git a/monero-wallet-gui.pro b/monero-wallet-gui.pro index 221d4017..578ad0e7 100644 --- a/monero-wallet-gui.pro +++ b/monero-wallet-gui.pro @@ -35,6 +35,8 @@ HEADERS += \ src/QR-Code-generator/QrSegment.hpp \ src/model/AddressBookModel.h \ src/libwalletqt/AddressBook.h \ + src/model/SubaddressModel.h \ + src/libwalletqt/Subaddress.h \ src/zxcvbn-c/zxcvbn.h \ src/libwalletqt/UnsignedTransaction.h \ MainApp.h @@ -58,6 +60,8 @@ SOURCES += main.cpp \ src/QR-Code-generator/QrSegment.cpp \ src/model/AddressBookModel.cpp \ src/libwalletqt/AddressBook.cpp \ + src/model/SubaddressModel.cpp \ + src/libwalletqt/Subaddress.cpp \ src/zxcvbn-c/zxcvbn.c \ src/libwalletqt/UnsignedTransaction.cpp \ MainApp.cpp diff --git a/pages/History.qml b/pages/History.qml index 9b184741..97162751 100644 --- a/pages/History.qml +++ b/pages/History.qml @@ -546,7 +546,7 @@ Rectangle { function onPageCompleted() { if(currentWallet != null && typeof currentWallet.history !== "undefined" ) { - currentWallet.history.refresh() + currentWallet.history.refresh(currentWallet.currentSubaddressAccount) table.addressBookModel = currentWallet ? currentWallet.addressBookModel : null transactionTypeDropdown.update() } diff --git a/pages/Mining.qml b/pages/Mining.qml index 0d87310f..a6bcb941 100644 --- a/pages/Mining.qml +++ b/pages/Mining.qml @@ -151,7 +151,7 @@ Rectangle { releasedColor: "#FF6C3C" pressedColor: "#FF4304" onClicked: { - var success = walletManager.startMining(appWindow.currentWallet.address, soloMinerThreadsLine.text, persistentSettings.allow_background_mining, persistentSettings.miningIgnoreBattery) + var success = walletManager.startMining(appWindow.currentWallet.address(0, 0), soloMinerThreadsLine.text, persistentSettings.allow_background_mining, persistentSettings.miningIgnoreBattery) if (success) { update() } else { diff --git a/pages/Receive.qml b/pages/Receive.qml index d31100c2..881ff138 100644 --- a/pages/Receive.qml +++ b/pages/Receive.qml @@ -38,14 +38,17 @@ import moneroComponents.Wallet 1.0 import moneroComponents.WalletManager 1.0 import moneroComponents.TransactionHistory 1.0 import moneroComponents.TransactionHistoryModel 1.0 +import moneroComponents.Subaddress 1.0 +import moneroComponents.SubaddressModel 1.0 Rectangle { - + id: pageReceive color: "#F0EEEE" - property alias addressText : addressLine.text + property var model + property var current_address + property alias addressText : pageReceive.current_address property alias paymentIdText : paymentIdLine.text property alias integratedAddressText : integratedAddressLine.text - property var model property string trackingLineText: "" function updatePaymentId(payment_id) { @@ -80,7 +83,7 @@ Rectangle { function makeQRCodeString() { var s = "monero:" var nfields = 0 - s += addressLine.text + s += current_address; var amount = amountLine.text.trim() if (amount !== "") { s += (nfields++ ? "&" : "?") @@ -192,27 +195,66 @@ Rectangle { id: addressRow Label { id: addressLabel - text: qsTr("Address") + translationManager.emptyString + text: qsTr("Addresses") + translationManager.emptyString width: mainLayout.labelWidth } - LineEdit { - id: addressLine - fontSize: mainLayout.lineEditFontSize - placeholderText: qsTr("ReadOnly wallet address displayed here") + translationManager.emptyString; - readOnly: true - width: mainLayout.editWidth + Rectangle { + id: tableRect Layout.fillWidth: true - onTextChanged: cursorPosition = 0 + Layout.preferredHeight: 200 + color: "#FFFFFF" + Scroll { + id: flickableScroll + anchors.right: table.right + anchors.top: table.top + anchors.bottom: table.bottom + flickable: table + } + SubaddressTable { + id: table + anchors.fill: parent + onContentYChanged: flickableScroll.flickableContentYChanged() + onCurrentItemChanged: { + current_address = appWindow.currentWallet.address(appWindow.currentWallet.currentSubaddressAccount, table.currentIndex); + } + } + } - IconButton { - imageSource: "../images/copyToClipboard.png" + RowLayout { + spacing: 20 + StandardButton { + shadowReleasedColor: "#FF4304" + shadowPressedColor: "#B32D00" + releasedColor: "#FF6C3C" + pressedColor: "#FF4304" + text: qsTr("Create new address") + translationManager.emptyString; onClicked: { - if (addressLine.text.length > 0) { - console.log(addressLine.text + " copied to clipboard") - clipboard.setText(addressLine.text) - appWindow.showStatusMessage(qsTr("Address copied to clipboard"),3) + inputDialog.labelText = qsTr("Set the label of the new address:") + translationManager.emptyString + inputDialog.inputText = qsTr("(Untitled)") + inputDialog.onAcceptedCallback = function() { + appWindow.currentWallet.subaddress.addRow(appWindow.currentWallet.currentSubaddressAccount, inputDialog.inputText) + table.currentIndex = appWindow.currentWallet.numSubaddresses() - 1 } + inputDialog.onRejectedCallback = null; + inputDialog.open() + } + } + StandardButton { + shadowReleasedColor: "#FF4304" + shadowPressedColor: "#B32D00" + releasedColor: "#FF6C3C" + pressedColor: "#FF4304" + enabled: table.currentIndex > 0 + text: qsTr("Rename") + translationManager.emptyString; + onClicked: { + inputDialog.labelText = qsTr("Set the label of the selected address:") + translationManager.emptyString + inputDialog.inputText = appWindow.currentWallet.getSubaddressLabel(appWindow.currentWallet.currentSubaddressAccount, table.currentIndex) + inputDialog.onAcceptedCallback = function() { + appWindow.currentWallet.subaddress.setLabel(appWindow.currentWallet.currentSubaddressAccount, table.currentIndex, inputDialog.inputText) + } + inputDialog.onRejectedCallback = null; + inputDialog.open() } } } @@ -436,11 +478,12 @@ Rectangle { function onPageCompleted() { console.log("Receive page loaded"); + table.model = currentWallet.subaddressModel; if (appWindow.currentWallet) { - if (addressLine.text.length === 0 || addressLine.text !== appWindow.currentWallet.address) { - addressLine.text = appWindow.currentWallet.address - } + current_address = appWindow.currentWallet.address(appWindow.currentWallet.currentSubaddressAccount, 0) + appWindow.currentWallet.subaddress.refresh(appWindow.currentWallet.currentSubaddressAccount) + table.currentIndex = 0 } update() diff --git a/qml.qrc b/qml.qrc index 7ea33ba2..295b363b 100644 --- a/qml.qrc +++ b/qml.qrc @@ -51,6 +51,7 @@ tabs/TweetsModel.qml components/Scroll.qml components/AddressBookTable.qml + components/SubaddressTable.qml images/deleteIcon.png images/moneroIcon.png components/StandardDropdown.qml @@ -136,6 +137,7 @@ components/IconButton.qml components/PasswordDialog.qml components/NewPasswordDialog.qml + components/InputDialog.qml components/ProcessingSplash.qml components/ProgressBar.qml components/StandardDialog.qml diff --git a/src/libwalletqt/PendingTransaction.cpp b/src/libwalletqt/PendingTransaction.cpp index bd621d6c..c6f1d069 100644 --- a/src/libwalletqt/PendingTransaction.cpp +++ b/src/libwalletqt/PendingTransaction.cpp @@ -50,6 +50,16 @@ quint64 PendingTransaction::txCount() const return m_pimpl->txCount(); } +QList PendingTransaction::subaddrIndices() const +{ + std::vector> subaddrIndices = m_pimpl->subaddrIndices(); + QList result; + for (const auto& x : subaddrIndices) + for (uint32_t i : x) + result.push_back(i); + return result; +} + void PendingTransaction::setFilename(const QString &fileName) { m_fileName = fileName; diff --git a/src/libwalletqt/PendingTransaction.h b/src/libwalletqt/PendingTransaction.h index 5aa94e0b..a73aab2e 100644 --- a/src/libwalletqt/PendingTransaction.h +++ b/src/libwalletqt/PendingTransaction.h @@ -2,6 +2,8 @@ #define PENDINGTRANSACTION_H #include +#include +#include #include @@ -19,6 +21,7 @@ class PendingTransaction : public QObject Q_PROPERTY(quint64 fee READ fee) Q_PROPERTY(QStringList txid READ txid) Q_PROPERTY(quint64 txCount READ txCount) + Q_PROPERTY(QList subaddrIndices READ subaddrIndices) public: enum Status { @@ -44,6 +47,7 @@ public: quint64 fee() const; QStringList txid() const; quint64 txCount() const; + QList subaddrIndices() const; Q_INVOKABLE void setFilename(const QString &fileName); private: diff --git a/src/libwalletqt/Subaddress.cpp b/src/libwalletqt/Subaddress.cpp new file mode 100644 index 00000000..f9a798ce --- /dev/null +++ b/src/libwalletqt/Subaddress.cpp @@ -0,0 +1,84 @@ +// Copyright (c) 2018, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "Subaddress.h" +#include + +Subaddress::Subaddress(Monero::Subaddress *subaddressImpl, QObject *parent) + : QObject(parent), m_subaddressImpl(subaddressImpl) +{ + qDebug(__FUNCTION__); + getAll(); +} + +QList Subaddress::getAll(bool update) const +{ + qDebug(__FUNCTION__); + + emit refreshStarted(); + + if(update) + m_rows.clear(); + + if (m_rows.empty()){ + for (auto &row: m_subaddressImpl->getAll()) { + m_rows.append(row); + } + } + + emit refreshFinished(); + return m_rows; +} + +Monero::SubaddressRow * Subaddress::getRow(int index) const +{ + return m_rows.at(index); +} + +void Subaddress::addRow(quint32 accountIndex, const QString &label) const +{ + m_subaddressImpl->addRow(accountIndex, label.toStdString()); + getAll(true); +} + +void Subaddress::setLabel(quint32 accountIndex, quint32 addressIndex, const QString &label) const +{ + m_subaddressImpl->setLabel(accountIndex, addressIndex, label.toStdString()); + getAll(true); +} + +void Subaddress::refresh(quint32 accountIndex) const +{ + m_subaddressImpl->refresh(accountIndex); + getAll(true); +} + +quint64 Subaddress::count() const +{ + return m_rows.size(); +} diff --git a/src/libwalletqt/Subaddress.h b/src/libwalletqt/Subaddress.h new file mode 100644 index 00000000..c43c11fd --- /dev/null +++ b/src/libwalletqt/Subaddress.h @@ -0,0 +1,61 @@ +// Copyright (c) 2018, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef SUBADDRESS_H +#define SUBADDRESS_H + +#include +#include +#include +#include + +class Subaddress : public QObject +{ + Q_OBJECT +public: + Q_INVOKABLE QList getAll(bool update = false) const; + Q_INVOKABLE Monero::SubaddressRow * getRow(int index) const; + Q_INVOKABLE void addRow(quint32 accountIndex, const QString &label) const; + Q_INVOKABLE void setLabel(quint32 accountIndex, quint32 addressIndex, const QString &label) const; + Q_INVOKABLE void refresh(quint32 accountIndex) const; + quint64 count() const; + +signals: + void refreshStarted() const; + void refreshFinished() const; + +public slots: + +private: + explicit Subaddress(Monero::Subaddress * subaddressImpl, QObject *parent); + friend class Wallet; + Monero::Subaddress * m_subaddressImpl; + mutable QList m_rows; +}; + +#endif // SUBADDRESS_H diff --git a/src/libwalletqt/TransactionHistory.cpp b/src/libwalletqt/TransactionHistory.cpp index 40b3d3c4..243db642 100644 --- a/src/libwalletqt/TransactionHistory.cpp +++ b/src/libwalletqt/TransactionHistory.cpp @@ -22,7 +22,7 @@ TransactionInfo *TransactionHistory::transaction(int index) // return nullptr; //} -QList TransactionHistory::getAll() const +QList TransactionHistory::getAll(quint32 accountIndex) const { // XXX this invalidates previously saved history that might be used by model emit refreshStarted(); @@ -37,6 +37,10 @@ QList TransactionHistory::getAll() const TransactionHistory * parent = const_cast(this); for (const auto i : m_pimpl->getAll()) { TransactionInfo * ti = new TransactionInfo(i, parent); + if (ti->subaddrAccount() != accountIndex) { + delete ti; + continue; + } m_tinfo.append(ti); // looking for transactions timestamp scope if (ti->timestamp() >= lastDateTime) { @@ -69,12 +73,12 @@ QList TransactionHistory::getAll() const return m_tinfo; } -void TransactionHistory::refresh() +void TransactionHistory::refresh(quint32 accountIndex) { // rebuilding transaction list in wallet_api; m_pimpl->refresh(); // copying list here and keep track on every item to avoid memleaks - getAll(); + getAll(accountIndex); } quint64 TransactionHistory::count() const diff --git a/src/libwalletqt/TransactionHistory.h b/src/libwalletqt/TransactionHistory.h index 0c8bfc9d..ac0c4400 100644 --- a/src/libwalletqt/TransactionHistory.h +++ b/src/libwalletqt/TransactionHistory.h @@ -23,8 +23,8 @@ class TransactionHistory : public QObject public: Q_INVOKABLE TransactionInfo *transaction(int index); // Q_INVOKABLE TransactionInfo * transaction(const QString &id); - Q_INVOKABLE QList getAll() const; - Q_INVOKABLE void refresh(); + Q_INVOKABLE QList getAll(quint32 accountIndex) const; + Q_INVOKABLE void refresh(quint32 accountIndex); quint64 count() const; QDateTime firstDateTime() const; QDateTime lastDateTime() const; diff --git a/src/libwalletqt/TransactionInfo.cpp b/src/libwalletqt/TransactionInfo.cpp index a787b151..72b56b6a 100644 --- a/src/libwalletqt/TransactionInfo.cpp +++ b/src/libwalletqt/TransactionInfo.cpp @@ -48,6 +48,24 @@ quint64 TransactionInfo::blockHeight() const return m_pimpl->blockHeight(); } +QSet TransactionInfo::subaddrIndex() const +{ + QSet result; + for (uint32_t i : m_pimpl->subaddrIndex()) + result.insert(i); + return result; +} + +quint32 TransactionInfo::subaddrAccount() const +{ + return m_pimpl->subaddrAccount(); +} + +QString TransactionInfo::label() const +{ + return QString::fromStdString(m_pimpl->label()); +} + quint64 TransactionInfo::confirmations() const { return m_pimpl->confirmations(); diff --git a/src/libwalletqt/TransactionInfo.h b/src/libwalletqt/TransactionInfo.h index d957c455..1bb6cc56 100644 --- a/src/libwalletqt/TransactionInfo.h +++ b/src/libwalletqt/TransactionInfo.h @@ -4,6 +4,7 @@ #include #include #include +#include class Transfer; @@ -18,6 +19,9 @@ class TransactionInfo : public QObject Q_PROPERTY(QString displayAmount READ displayAmount) Q_PROPERTY(QString fee READ fee) Q_PROPERTY(quint64 blockHeight READ blockHeight) + Q_PROPERTY(QSet subaddrIndex READ subaddrIndex) + Q_PROPERTY(quint32 subaddrAccount READ subaddrAccount) + Q_PROPERTY(QString label READ label) Q_PROPERTY(quint64 confirmations READ confirmations) Q_PROPERTY(quint64 unlockTime READ unlockTime) Q_PROPERTY(QString hash READ hash) @@ -44,6 +48,9 @@ public: QString displayAmount() const; QString fee() const; quint64 blockHeight() const; + QSet subaddrIndex() const; + quint32 subaddrAccount() const; + QString label() const; quint64 confirmations() const; quint64 unlockTime() const; //! transaction_id diff --git a/src/libwalletqt/Wallet.cpp b/src/libwalletqt/Wallet.cpp index b694a05f..4b18ccc8 100644 --- a/src/libwalletqt/Wallet.cpp +++ b/src/libwalletqt/Wallet.cpp @@ -3,9 +3,11 @@ #include "UnsignedTransaction.h" #include "TransactionHistory.h" #include "AddressBook.h" +#include "Subaddress.h" #include "model/TransactionHistoryModel.h" #include "model/TransactionHistorySortFilterModel.h" #include "model/AddressBookModel.h" +#include "model/SubaddressModel.h" #include "wallet/api/wallet2_api.h" #include @@ -155,9 +157,9 @@ bool Wallet::setPassword(const QString &password) return m_walletImpl->setPassword(password.toStdString()); } -QString Wallet::address() const +QString Wallet::address(quint32 accountIndex, quint32 addressIndex) const { - return QString::fromStdString(m_walletImpl->address()); + return QString::fromStdString(m_walletImpl->address(accountIndex, addressIndex)); } QString Wallet::path() const @@ -241,14 +243,63 @@ bool Wallet::viewOnly() const return m_walletImpl->watchOnly(); } -quint64 Wallet::balance() const +quint64 Wallet::balance(quint32 accountIndex) const { - return m_walletImpl->balance(); + return m_walletImpl->balance(accountIndex); } -quint64 Wallet::unlockedBalance() const +quint64 Wallet::balanceAll() const { - return m_walletImpl->unlockedBalance(); + return m_walletImpl->balanceAll(); +} + +quint64 Wallet::unlockedBalance(quint32 accountIndex) const +{ + return m_walletImpl->unlockedBalance(accountIndex); +} + +quint64 Wallet::unlockedBalanceAll() const +{ + return m_walletImpl->unlockedBalanceAll(); +} + +quint32 Wallet::currentSubaddressAccount() const +{ + return m_currentSubaddressAccount; +} +void Wallet::switchSubaddressAccount(quint32 accountIndex) +{ + if (accountIndex < numSubaddressAccounts()) + { + m_currentSubaddressAccount = accountIndex; + m_subaddress->refresh(m_currentSubaddressAccount); + m_history->refresh(m_currentSubaddressAccount); + } +} +void Wallet::addSubaddressAccount(const QString& label) +{ + m_walletImpl->addSubaddressAccount(label.toStdString()); + switchSubaddressAccount(numSubaddressAccounts() - 1); +} +quint32 Wallet::numSubaddressAccounts() const +{ + return m_walletImpl->numSubaddressAccounts(); +} +quint32 Wallet::numSubaddresses(quint32 accountIndex) const +{ + return m_walletImpl->numSubaddresses(accountIndex); +} +void Wallet::addSubaddress(const QString& label) +{ + m_walletImpl->addSubaddress(currentSubaddressAccount(), label.toStdString()); +} +QString Wallet::getSubaddressLabel(quint32 accountIndex, quint32 addressIndex) const +{ + return QString::fromStdString(m_walletImpl->getSubaddressLabel(accountIndex, addressIndex)); +} +void Wallet::setSubaddressLabel(quint32 accountIndex, quint32 addressIndex, const QString &label) +{ + m_walletImpl->setSubaddressLabel(accountIndex, addressIndex, label.toStdString()); } quint64 Wallet::blockChainHeight() const @@ -288,7 +339,8 @@ quint64 Wallet::daemonBlockChainTargetHeight() const bool Wallet::refresh() { bool result = m_walletImpl->refresh(); - m_history->refresh(); + m_history->refresh(currentSubaddressAccount()); + m_subaddress->refresh(currentSubaddressAccount()); if (result) emit updated(); return result; @@ -324,9 +376,10 @@ PendingTransaction *Wallet::createTransaction(const QString &dst_addr, const QSt quint64 amount, quint32 mixin_count, PendingTransaction::Priority priority) { + std::set subaddr_indices; Monero::PendingTransaction * ptImpl = m_walletImpl->createTransaction( dst_addr.toStdString(), payment_id.toStdString(), amount, mixin_count, - static_cast(priority)); + static_cast(priority), currentSubaddressAccount(), subaddr_indices); PendingTransaction * result = new PendingTransaction(ptImpl,0); return result; } @@ -351,9 +404,10 @@ void Wallet::createTransactionAsync(const QString &dst_addr, const QString &paym PendingTransaction *Wallet::createTransactionAll(const QString &dst_addr, const QString &payment_id, quint32 mixin_count, PendingTransaction::Priority priority) { + std::set subaddr_indices; Monero::PendingTransaction * ptImpl = m_walletImpl->createTransaction( dst_addr.toStdString(), payment_id.toStdString(), Monero::optional(), mixin_count, - static_cast(priority)); + static_cast(priority), currentSubaddressAccount(), subaddr_indices); PendingTransaction * result = new PendingTransaction(ptImpl, this); return result; } @@ -458,6 +512,18 @@ AddressBookModel *Wallet::addressBookModel() const return m_addressBookModel; } +Subaddress *Wallet::subaddress() +{ + return m_subaddress; +} + +SubaddressModel *Wallet::subaddressModel() +{ + if (!m_subaddressModel) { + m_subaddressModel = new SubaddressModel(this, m_subaddress); + } + return m_subaddressModel; +} QString Wallet::generatePaymentId() const { @@ -668,14 +734,18 @@ Wallet::Wallet(Monero::Wallet *w, QObject *parent) , m_historyModel(nullptr) , m_addressBook(nullptr) , m_addressBookModel(nullptr) + , m_subaddress(nullptr) + , m_subaddressModel(nullptr) , m_daemonBlockChainHeight(0) , m_daemonBlockChainHeightTtl(DAEMON_BLOCKCHAIN_HEIGHT_CACHE_TTL_SECONDS) , m_daemonBlockChainTargetHeight(0) , m_daemonBlockChainTargetHeightTtl(DAEMON_BLOCKCHAIN_TARGET_HEIGHT_CACHE_TTL_SECONDS) , m_connectionStatusTtl(WALLET_CONNECTION_STATUS_CACHE_TTL_SECONDS) + , m_currentSubaddressAccount(0) { m_history = new TransactionHistory(m_walletImpl->history(), this); m_addressBook = new AddressBook(m_walletImpl->addressBook(), this); + m_subaddress = new Subaddress(m_walletImpl->subaddress(), this); m_walletImpl->setListener(new WalletListenerImpl(this)); m_connectionStatus = Wallet::ConnectionStatus_Disconnected; // start cache timers @@ -696,6 +766,10 @@ Wallet::~Wallet() delete m_history; m_history = NULL; + delete m_addressBook; + m_addressBook = NULL; + delete m_subaddress; + m_subaddress = NULL; //Monero::WalletManagerFactory::getWalletManager()->closeWallet(m_walletImpl); if(status() == Status_Critical) qDebug("Not storing wallet cache"); diff --git a/src/libwalletqt/Wallet.h b/src/libwalletqt/Wallet.h index 8c833f4e..c8e78339 100644 --- a/src/libwalletqt/Wallet.h +++ b/src/libwalletqt/Wallet.h @@ -20,6 +20,8 @@ class TransactionHistoryModel; class TransactionHistorySortFilterModel; class AddressBook; class AddressBookModel; +class Subaddress; +class SubaddressModel; class Wallet : public QObject { @@ -29,17 +31,17 @@ class Wallet : public QObject Q_PROPERTY(Status status READ status) Q_PROPERTY(bool testnet READ testnet) // Q_PROPERTY(ConnectionStatus connected READ connected) + Q_PROPERTY(quint32 currentSubaddressAccount READ currentSubaddressAccount) Q_PROPERTY(bool synchronized READ synchronized) Q_PROPERTY(QString errorString READ errorString) - Q_PROPERTY(QString address READ address) - Q_PROPERTY(quint64 balance READ balance) - Q_PROPERTY(quint64 unlockedBalance READ unlockedBalance) Q_PROPERTY(TransactionHistory * history READ history) Q_PROPERTY(QString paymentId READ paymentId WRITE setPaymentId) Q_PROPERTY(TransactionHistorySortFilterModel * historyModel READ historyModel NOTIFY historyModelChanged) Q_PROPERTY(QString path READ path) Q_PROPERTY(AddressBookModel * addressBookModel READ addressBookModel) Q_PROPERTY(AddressBook * addressBook READ addressBook) + Q_PROPERTY(SubaddressModel * subaddressModel READ subaddressModel) + Q_PROPERTY(Subaddress * subaddress READ subaddress) Q_PROPERTY(bool viewOnly READ viewOnly) Q_PROPERTY(QString secretViewKey READ getSecretViewKey) Q_PROPERTY(QString publicViewKey READ getPublicViewKey) @@ -98,7 +100,7 @@ public: Q_INVOKABLE bool setPassword(const QString &password); //! returns wallet's public address - QString address() const; + Q_INVOKABLE QString address(quint32 accountIndex, quint32 addressIndex) const; //! returns wallet file's path QString path() const; @@ -126,10 +128,22 @@ public: Q_INVOKABLE void setTrustedDaemon(bool arg); //! returns balance - Q_INVOKABLE quint64 balance() const; + Q_INVOKABLE quint64 balance(quint32 accountIndex) const; + Q_INVOKABLE quint64 balanceAll() const; //! returns unlocked balance - Q_INVOKABLE quint64 unlockedBalance() const; + Q_INVOKABLE quint64 unlockedBalance(quint32 accountIndex) const; + Q_INVOKABLE quint64 unlockedBalanceAll() const; + + //! account/address management + quint32 currentSubaddressAccount() const; + Q_INVOKABLE void switchSubaddressAccount(quint32 accountIndex); + Q_INVOKABLE void addSubaddressAccount(const QString& label); + Q_INVOKABLE quint32 numSubaddressAccounts() const; + Q_INVOKABLE quint32 numSubaddresses(quint32 accountIndex) const; + Q_INVOKABLE void addSubaddress(const QString& label); + Q_INVOKABLE QString getSubaddressLabel(quint32 accountIndex, quint32 addressIndex) const; + Q_INVOKABLE void setSubaddressLabel(quint32 accountIndex, quint32 addressIndex, const QString &label); //! returns if view only wallet Q_INVOKABLE bool viewOnly() const; @@ -209,6 +223,12 @@ public: //! returns adress book model AddressBookModel *addressBookModel() const; + //! returns subaddress + Subaddress *subaddress(); + + //! returns subadress model + SubaddressModel *subaddressModel(); + //! generate payment id Q_INVOKABLE QString generatePaymentId() const; @@ -302,8 +322,11 @@ private: int m_connectionStatusTtl; mutable QTime m_connectionStatusTime; mutable bool m_initialized; + uint32_t m_currentSubaddressAccount; AddressBook * m_addressBook; mutable AddressBookModel * m_addressBookModel; + Subaddress * m_subaddress; + mutable SubaddressModel * m_subaddressModel; QMutex m_connectionStatusMutex; bool m_connectionStatusRunning; QString m_daemonUsername; diff --git a/src/libwalletqt/WalletManager.cpp b/src/libwalletqt/WalletManager.cpp index 3b33b8b7..73b0a006 100644 --- a/src/libwalletqt/WalletManager.cpp +++ b/src/libwalletqt/WalletManager.cpp @@ -49,7 +49,7 @@ Wallet *WalletManager::openWallet(const QString &path, const QString &password, __PRETTY_FUNCTION__, qPrintable(path), testnet); Monero::Wallet * w = m_pimpl->openWallet(path.toStdString(), password.toStdString(), testnet); - qDebug("%s: opened wallet: %s, status: %d", __PRETTY_FUNCTION__, w->address().c_str(), w->status()); + qDebug("%s: opened wallet: %s, status: %d", __PRETTY_FUNCTION__, w->address(0, 0).c_str(), w->status()); m_currentWallet = new Wallet(w); // move wallet to the GUI thread. Otherwise it wont be emitting signals @@ -110,7 +110,7 @@ QString WalletManager::closeWallet() QMutexLocker locker(&m_mutex); QString result; if (m_currentWallet) { - result = m_currentWallet->address(); + result = m_currentWallet->address(0, 0); delete m_currentWallet; } else { qCritical() << "Trying to close non existing wallet " << m_currentWallet; diff --git a/src/model/SubaddressModel.cpp b/src/model/SubaddressModel.cpp new file mode 100644 index 00000000..aa2b67db --- /dev/null +++ b/src/model/SubaddressModel.cpp @@ -0,0 +1,89 @@ +// Copyright (c) 2018, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include "SubaddressModel.h" +#include "Subaddress.h" +#include +#include +#include + +SubaddressModel::SubaddressModel(QObject *parent, Subaddress *subaddress) + : QAbstractListModel(parent), m_subaddress(subaddress) +{ + qDebug(__FUNCTION__); + connect(m_subaddress,SIGNAL(refreshStarted()),this,SLOT(startReset())); + connect(m_subaddress,SIGNAL(refreshFinished()),this,SLOT(endReset())); + +} + +void SubaddressModel::startReset(){ + qDebug(__FUNCTION__); + beginResetModel(); +} +void SubaddressModel::endReset(){ + qDebug(__FUNCTION__); + endResetModel(); +} + +int SubaddressModel::rowCount(const QModelIndex &parent) const +{ + return m_subaddress->count(); +} + +QVariant SubaddressModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid() || index.row() < 0 || (unsigned)index.row() >= m_subaddress->count()) + return {}; + + Monero::SubaddressRow * sr = m_subaddress->getRow(index.row()); + if (!sr) + return {}; + + QVariant result = ""; + switch (role) { + case SubaddressAddressRole: + result = QString::fromStdString(sr->getAddress()); + break; + case SubaddressLabelRole: + result = index.row() == 0 ? tr("Primary address") : QString::fromStdString(sr->getLabel()); + break; + } + + return result; +} + +QHash SubaddressModel::roleNames() const +{ + static QHash roleNames; + if (roleNames.empty()) + { + roleNames.insert(SubaddressAddressRole, "address"); + roleNames.insert(SubaddressLabelRole, "label"); + } + return roleNames; +} diff --git a/src/model/SubaddressModel.h b/src/model/SubaddressModel.h new file mode 100644 index 00000000..b494e1f5 --- /dev/null +++ b/src/model/SubaddressModel.h @@ -0,0 +1,62 @@ +// Copyright (c) 2018, The Monero Project +// +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, are +// permitted provided that the following conditions are met: +// +// 1. Redistributions of source code must retain the above copyright notice, this list of +// conditions and the following disclaimer. +// +// 2. Redistributions in binary form must reproduce the above copyright notice, this list +// of conditions and the following disclaimer in the documentation and/or other +// materials provided with the distribution. +// +// 3. Neither the name of the copyright holder nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY +// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL +// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, +// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF +// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifndef SUBADDRESSMODEL_H +#define SUBADDRESSMODEL_H + +#include + +class Subaddress; + +class SubaddressModel : public QAbstractListModel +{ + Q_OBJECT + +public: + enum SubaddressRowRole { + SubaddressRole = Qt::UserRole + 1, // for the SubaddressRow object; + SubaddressAddressRole, + SubaddressLabelRole, + }; + Q_ENUM(SubaddressRowRole) + + SubaddressModel(QObject *parent, Subaddress *subaddress); + + int rowCount(const QModelIndex &parent = QModelIndex()) const override; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + QHash roleNames() const override; + +public slots: + void startReset(); + void endReset(); + +private: + Subaddress *m_subaddress; +}; + +#endif // SUBADDRESSMODEL_H diff --git a/src/model/TransactionHistoryModel.cpp b/src/model/TransactionHistoryModel.cpp index 907c21e8..42dfcd24 100644 --- a/src/model/TransactionHistoryModel.cpp +++ b/src/model/TransactionHistoryModel.cpp @@ -83,6 +83,25 @@ QVariant TransactionHistoryModel::data(const QModelIndex &index, int role) const } break; + case TransactionSubaddrIndexRole: + { + QString str = QString{""}; + bool first = true; + for (quint32 i : tInfo->subaddrIndex()) { + if (!first) + str += QString{","}; + first = false; + str += QString::number(i); + } + result = str; + } + break; + case TransactionSubaddrAccountRole: + result = tInfo->subaddrAccount(); + break; + case TransactionLabelRole: + result = tInfo->subaddrIndex().size() == 1 && *tInfo->subaddrIndex().begin() == 0 ? tr("Primary address") : tInfo->label(); + break; case TransactionConfirmationsRole: result = tInfo->confirmations(); break; @@ -133,6 +152,9 @@ QHash TransactionHistoryModel::roleNames() const roleNames.insert(TransactionAtomicAmountRole, "atomicAmount"); roleNames.insert(TransactionFeeRole, "fee"); roleNames.insert(TransactionBlockHeightRole, "blockHeight"); + roleNames.insert(TransactionSubaddrIndexRole, "subaddrIndex"); + roleNames.insert(TransactionSubaddrAccountRole, "subaddrAccount"); + roleNames.insert(TransactionLabelRole, "label"); roleNames.insert(TransactionConfirmationsRole, "confirmations"); roleNames.insert(TransactionConfirmationsRequiredRole, "confirmationsRequired"); roleNames.insert(TransactionHashRole, "hash"); diff --git a/src/model/TransactionHistoryModel.h b/src/model/TransactionHistoryModel.h index a2dbd967..cbbec8ad 100644 --- a/src/model/TransactionHistoryModel.h +++ b/src/model/TransactionHistoryModel.h @@ -25,6 +25,9 @@ public: TransactionDisplayAmountRole, TransactionFeeRole, TransactionBlockHeightRole, + TransactionSubaddrIndexRole, + TransactionSubaddrAccountRole, + TransactionLabelRole, TransactionConfirmationsRole, TransactionConfirmationsRequiredRole, TransactionHashRole,