aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRiccardo Spagni <ric@spagni.net>2017-01-15 14:58:47 -0500
committerRiccardo Spagni <ric@spagni.net>2017-01-15 14:58:47 -0500
commit98ca4f1a70051447c39d5f9161db2e1741fdceb9 (patch)
tree620c481c47e094d34990fe1c210f1fd0b2fa6f90
parent7cbd61dae21fdf8a27081d28ebb130195d77ed69 (diff)
parentae5c21ef17b5585e1c90423a9f45b62b3b678f46 (diff)
downloadmonzero-gui-98ca4f1a70051447c39d5f9161db2e1741fdceb9.tar.gz
monzero-gui-98ca4f1a70051447c39d5f9161db2e1741fdceb9.tar.xz
monzero-gui-98ca4f1a70051447c39d5f9161db2e1741fdceb9.zip
Merge pull request #398
ae5c21e export/import key images when cold signing (Jaquee) 24ccd27 Add rescan spent button (Jaquee) 15c79df GUI cold signing (Jaquee) fd98395 view only wallets (Jaquee) 8f56e98 save wallet name in appwindow (Jaquee) 842e4df adjust button size dynamically (Jaquee)
-rw-r--r--components/PasswordDialog.qml9
-rw-r--r--components/StandardButton.qml7
-rw-r--r--main.cpp4
-rw-r--r--main.qml77
-rw-r--r--monero-wallet-gui.pro11
-rw-r--r--pages/Settings.qml334
-rw-r--r--pages/Transfer.qml246
-rw-r--r--qml.qrc2
-rw-r--r--src/libwalletqt/PendingTransaction.cpp10
-rw-r--r--src/libwalletqt/PendingTransaction.h2
-rw-r--r--src/libwalletqt/UnsignedTransaction.cpp92
-rw-r--r--src/libwalletqt/UnsignedTransaction.h59
-rw-r--r--src/libwalletqt/Wallet.cpp43
-rw-r--r--src/libwalletqt/Wallet.h22
-rw-r--r--wizard/WizardCreateViewOnlyWallet.qml94
-rw-r--r--wizard/WizardMain.qml81
-rw-r--r--wizard/WizardManageWalletUI.qml6
-rw-r--r--wizard/WizardPassword.qml75
-rw-r--r--wizard/WizardPasswordUI.qml96
-rw-r--r--wizard/utils.js7
20 files changed, 1005 insertions, 272 deletions
diff --git a/components/PasswordDialog.qml b/components/PasswordDialog.qml
index 19414987..1b83bcfa 100644
--- a/components/PasswordDialog.qml
+++ b/components/PasswordDialog.qml
@@ -52,13 +52,6 @@ Window {
show()
}
- function usefulName(path) {
- // arbitrary "short enough" limit
- if (path.length < 32)
- return path
- return path.replace(/.*[\/\\]/, '').replace(/\.keys$/, '')
- }
-
// TODO: implement without hardcoding sizes
width: 480
height: walletName ? 240 : 200
@@ -74,7 +67,7 @@ Window {
Layout.alignment: Qt.AlignHCenter
Label {
- text: root.walletName.length > 0 ? qsTr("Please enter wallet password for:<br>") + usefulName(root.walletName) : qsTr("Please enter wallet password")
+ text: root.walletName.length > 0 ? qsTr("Please enter wallet password for:<br>") + root.walletName : qsTr("Please enter wallet password")
Layout.alignment: Qt.AlignHCenter
Layout.columnSpan: 2
Layout.fillWidth: true
diff --git a/components/StandardButton.qml b/components/StandardButton.qml
index 17f56c3d..1d3f8192 100644
--- a/components/StandardButton.qml
+++ b/components/StandardButton.qml
@@ -27,6 +27,7 @@
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import QtQuick 2.0
+import QtQuick.Layouts 1.1
Item {
id: button
@@ -41,6 +42,10 @@ Item {
property alias text: label.text
signal clicked()
+ // Dynamic label width
+ width: label.contentWidth + 20
+ Layout.minimumWidth: 100
+
Rectangle {
anchors.left: parent.left
@@ -78,13 +83,13 @@ Item {
anchors.left: parent.left
anchors.right: parent.right
horizontalAlignment: Text.AlignHCenter
- elide: Text.ElideRight
font.family: "Arial"
font.bold: true
font.letterSpacing: -1
font.pixelSize: button.fontSize
color: parent.textColor
visible: parent.icon === ""
+ font.capitalization : Font.AllUppercase
}
Image {
diff --git a/main.cpp b/main.cpp
index 9f87124f..232f4e15 100644
--- a/main.cpp
+++ b/main.cpp
@@ -40,6 +40,7 @@
#include "Wallet.h"
#include "QRCodeImageProvider.h"
#include "PendingTransaction.h"
+#include "UnsignedTransaction.h"
#include "TranslationManager.h"
#include "TransactionInfo.h"
#include "TransactionHistory.h"
@@ -72,6 +73,9 @@ int main(int argc, char *argv[])
qmlRegisterUncreatableType<PendingTransaction>("moneroComponents.PendingTransaction", 1, 0, "PendingTransaction",
"PendingTransaction can't be instantiated directly");
+ qmlRegisterUncreatableType<UnsignedTransaction>("moneroComponents.UnsignedTransaction", 1, 0, "UnsignedTransaction",
+ "UnsignedTransaction can't be instantiated directly");
+
qmlRegisterUncreatableType<WalletManager>("moneroComponents.WalletManager", 1, 0, "WalletManager",
"WalletManager can't be instantiated directly");
diff --git a/main.qml b/main.qml
index 663f9005..a3ca149d 100644
--- a/main.qml
+++ b/main.qml
@@ -228,6 +228,8 @@ ApplicationWindow {
currentWallet = wallet
updateSyncing(false)
+ viewOnly = currentWallet.viewOnly;
+
// connect handlers
currentWallet.refreshed.connect(onWalletRefresh)
currentWallet.updated.connect(onWalletUpdate)
@@ -252,12 +254,20 @@ ApplicationWindow {
return wallet_path;
}
+ function usefulName(path) {
+ // arbitrary "short enough" limit
+ if (path.length < 32)
+ return path
+ return path.replace(/.*[\/\\]/, '').replace(/\.keys$/, '')
+ }
+
function onWalletConnectionStatusChanged(){
console.log("Wallet connection status changed")
middlePanel.updateStatus();
}
function onWalletOpened(wallet) {
+ walletName = usefulName(wallet.path)
console.log(">>> wallet opened: " + wallet)
if (wallet.status !== Wallet.Status_Ok) {
if (appWindow.password === '') {
@@ -265,7 +275,7 @@ ApplicationWindow {
console.log("closing wallet async : " + wallet.address)
closeWallet();
// try to open wallet with password;
- passwordDialog.open(wallet.path);
+ passwordDialog.open(walletName);
} else {
// opening with password but password doesn't match
console.error("Error opening wallet with password: ", wallet.errorString);
@@ -277,7 +287,7 @@ ApplicationWindow {
closeWallet();
informationPopup.open()
informationPopup.onCloseCallback = function() {
- passwordDialog.open(wallet.path)
+ passwordDialog.open(walletName)
}
}
return;
@@ -285,7 +295,6 @@ ApplicationWindow {
// wallet opened successfully, subscribing for wallet updates
connectWallet(wallet)
-
}
@@ -466,7 +475,7 @@ ApplicationWindow {
// called on "transfer"
- function handlePayment(address, paymentId, amount, mixinCount, priority, description) {
+ function handlePayment(address, paymentId, amount, mixinCount, priority, description, createFile) {
console.log("Creating transaction: ")
console.log("\taddress: ", address,
", payment_id: ", paymentId,
@@ -514,6 +523,24 @@ ApplicationWindow {
currentWallet.createTransactionAsync(address, paymentId, amountxmr, mixinCount, priority);
}
+ //Choose where to save transaction
+ FileDialog {
+ id: saveTxDialog
+ title: "Please choose a location"
+ folder: "file://" +moneroAccountsDir
+ selectExisting: false;
+
+ onAccepted: {
+ handleTransactionConfirmed()
+ }
+ onRejected: {
+ // do nothing
+
+ }
+
+ }
+
+
function handleSweepUnmixable() {
console.log("Creating transaction: ")
@@ -554,7 +581,7 @@ ApplicationWindow {
}
// called after user confirms transaction
- function handleTransactionConfirmed() {
+ function handleTransactionConfirmed(fileName) {
// grab transaction.txid before commit, since it clears it.
// we actually need to copy it, because QML will incredibly
// call the function multiple times when the variable is used
@@ -565,6 +592,20 @@ ApplicationWindow {
for (var i = 0; i < txid_org.length; ++i)
txid[i] = txid_org[i]
+ // View only wallet - we save the tx
+ if(viewOnly && saveTxDialog.fileUrl){
+ // No file specified - abort
+ if(!saveTxDialog.fileUrl) {
+ currentWallet.disposeTransaction(transaction)
+ return;
+ }
+
+ var path = walletManager.urlToLocalPath(saveTxDialog.fileUrl)
+
+ // Store to file
+ transaction.setFilename(path);
+ }
+
if (!transaction.commit()) {
console.log("Error committing transaction: " + transaction.errorString);
informationPopup.title = qsTr("Error") + translationManager.emptyString
@@ -577,7 +618,7 @@ ApplicationWindow {
txid_text += ", "
txid_text += txid[i]
}
- informationPopup.text = qsTr("Money sent successfully: %1 transaction(s) ").arg(txid.length) + txid_text + translationManager.emptyString
+ informationPopup.text = (viewOnly)? qsTr("Transaction saved to file: %1").arg(path) : qsTr("Money sent successfully: %1 transaction(s) ").arg(txid.length) + txid_text + translationManager.emptyString
informationPopup.icon = StandardIcon.Information
if (transactionDescription.length > 0) {
for (var i = 0; i < txid.length; ++i)
@@ -674,7 +715,6 @@ ApplicationWindow {
rootItem.state = "wizard"
}
-
objectName: "appWindow"
visible: true
width: rightPanelExpanded ? 1269 : 1269 - 300
@@ -764,10 +804,31 @@ ApplicationWindow {
id: transactionConfirmationPopup
onAccepted: {
close();
- handleTransactionConfirmed()
+
+ // Save transaction to file if view only wallet
+ if(viewOnly) {
+ saveTxDialog.open();
+ return;
+ } else
+ handleTransactionConfirmed()
+ }
+ }
+
+ StandardDialog {
+ id: confirmationDialog
+ property var onAcceptedCallback
+ property var onRejectedCallback
+ onAccepted: {
+ if (onAcceptedCallback)
+ onAcceptedCallback()
+ }
+ onRejected: {
+ if (onRejectedCallback)
+ onRejectedCallback();
}
}
+
//Open Wallet from file
FileDialog {
id: fileDialog
diff --git a/monero-wallet-gui.pro b/monero-wallet-gui.pro
index 2f0275b1..4f687802 100644
--- a/monero-wallet-gui.pro
+++ b/monero-wallet-gui.pro
@@ -9,7 +9,7 @@ CONFIG += c++11
# cleaning "auto-generated" bitmonero directory on "make distclean"
QMAKE_DISTCLEAN += -r $$WALLET_ROOT
-INCLUDEPATH += $$WALLET_ROOT/include \
+INCLUDEPATH += $$WALLET_ROOT/include \
$$PWD/src/libwalletqt \
$$PWD/src/QR-Code-generator \
$$PWD/src \
@@ -36,7 +36,8 @@ HEADERS += \
src/daemon/DaemonManager.h \
src/model/AddressBookModel.h \
src/libwalletqt/AddressBook.h \
- src/zxcvbn-c/zxcvbn.h
+ src/zxcvbn-c/zxcvbn.h \
+ src/libwalletqt/UnsignedTransaction.h
SOURCES += main.cpp \
@@ -59,7 +60,8 @@ SOURCES += main.cpp \
src/daemon/DaemonManager.cpp \
src/model/AddressBookModel.cpp \
src/libwalletqt/AddressBook.cpp \
- src/zxcvbn-c/zxcvbn.c
+ src/zxcvbn-c/zxcvbn.c \
+ src/libwalletqt/UnsignedTransaction.cpp
lupdate_only {
SOURCES = *.qml \
@@ -289,7 +291,8 @@ OTHER_FILES += \
$$TRANSLATIONS
DISTFILES += \
- notes.txt
+ notes.txt \
+ monero/src/wallet/CMakeLists.txt
# windows application icon
diff --git a/pages/Settings.qml b/pages/Settings.qml
index f735badf..4d8a4a49 100644
--- a/pages/Settings.qml
+++ b/pages/Settings.qml
@@ -39,51 +39,26 @@ import moneroComponents.Clipboard 1.0
Rectangle {
property var daemonAddress
+ property bool viewOnly: false
color: "#F0EEEE"
Clipboard { id: clipboard }
function initSettings() {
+ //runs on every page load
-
- // Mnemonic seed settings
- memoTextInput.text = qsTr("Click button to show seed") + translationManager.emptyString
- showSeedButton.visible = true
+ // Mnemonic seed setting
+ memoTextInput.text = (viewOnly)? qsTr("View only wallets doesn't have a mnemonic seed") : qsTr("Click button to show seed") + translationManager.emptyString
+ showSeedButton.enabled = !viewOnly
// Daemon settings
-
daemonAddress = persistentSettings.daemon_address.split(":");
console.log("address: " + persistentSettings.daemon_address)
// try connecting to daemon
}
- PasswordDialog {
- id: settingsPasswordDialog
-
- onAccepted: {
- if(appWindow.password === settingsPasswordDialog.password){
- memoTextInput.text = currentWallet.seed
- showSeedButton.visible = false
- } else {
- informationPopup.title = qsTr("Error") + translationManager.emptyString;
- informationPopup.text = qsTr("Wrong password");
- informationPopup.open()
- informationPopup.onCloseCallback = function() {
- settingsPasswordDialog.open()
- }
- }
-
- settingsPasswordDialog.password = ""
- }
- onRejected: {
-
- }
-
- }
-
-
ColumnLayout {
id: mainLayout
anchors.margins: 40
@@ -92,17 +67,59 @@ Rectangle {
anchors.right: parent.right
spacing: 10
+ //! Manage wallet
+ RowLayout {
+ Label {
+ id: manageWalletLabel
+ Layout.fillWidth: true
+ color: "#4A4949"
+ text: qsTr("Manage wallet") + translationManager.emptyString
+ fontSize: 16
+ Layout.topMargin: 10
+ }
+ }
- Label {
- id: seedLabel
- color: "#4A4949"
- fontSize: 16
- text: qsTr("Mnemonic seed: ") + translationManager.emptyString
- Layout.preferredWidth: 100
- Layout.alignment: Qt.AlignLeft
+ Rectangle {
+ Layout.fillWidth: true
+ height: 1
+ color: "#DEDEDE"
}
+ RowLayout {
+ StandardButton {
+ id: closeWalletButton
+ width: 100
+ text: qsTr("Close wallet") + translationManager.emptyString
+ shadowReleasedColor: "#FF4304"
+ shadowPressedColor: "#B32D00"
+ releasedColor: "#FF6C3C"
+ pressedColor: "#FF4304"
+ visible: true
+ onClicked: {
+ console.log("closing wallet button clicked")
+ appWindow.showWizard();
+ }
+ }
+
+ StandardButton {
+ enabled: !viewOnly
+ id: createViewOnlyWalletButton
+ text: qsTr("Create view only wallet") + translationManager.emptyString
+ shadowReleasedColor: "#FF4304"
+ shadowPressedColor: "#B32D00"
+ releasedColor: "#FF6C3C"
+ pressedColor: "#FF4304"
+ visible: true
+ onClicked: {
+ wizard.openCreateViewOnlyWalletPage();
+ }
+ }
+
+ }
+
+ //! show seed
TextArea {
+ enabled: !viewOnly
id: memoTextInput
textMargin: 6
wrapMode: TextEdit.WordWrap
@@ -113,7 +130,7 @@ Rectangle {
Layout.preferredHeight: 100
Layout.alignment: Qt.AlignHCenter
- text: qsTr("Click button to show seed") + translationManager.emptyString
+ text: (viewOnly)? qsTr("View only wallets doesn't have a mnemonic seed") : qsTr("Click button to show seed") + translationManager.emptyString
style: TextAreaStyle {
backgroundColor: "#FFFFFF"
@@ -137,7 +154,9 @@ Rectangle {
}
}
+
RowLayout {
+ enabled: !viewOnly
Layout.fillWidth: true
Text {
id: wordsTipText
@@ -151,26 +170,30 @@ Rectangle {
}
StandardButton {
-
id: showSeedButton
-
- fontSize: 14
shadowReleasedColor: "#FF4304"
shadowPressedColor: "#B32D00"
releasedColor: "#FF6C3C"
pressedColor: "#FF4304"
text: qsTr("Show seed")
Layout.alignment: Qt.AlignRight
- Layout.preferredWidth: 100
onClicked: {
settingsPasswordDialog.open();
}
}
}
-
-
-
+ //! Manage daemon
+ RowLayout {
+ Label {
+ id: manageDaemonLabel
+ color: "#4A4949"
+ text: qsTr("Manage daemon") + translationManager.emptyString
+ fontSize: 16
+ anchors.topMargin: 30
+ Layout.topMargin: 30
+ }
+ }
Rectangle {
Layout.fillWidth: true
height: 1
@@ -178,10 +201,68 @@ Rectangle {
}
RowLayout {
+ StandardButton {
+ visible: true
+ enabled: !appWindow.daemonRunning
+ id: startDaemonButton
+ text: qsTr("Start daemon") + translationManager.emptyString
+ shadowReleasedColor: "#FF4304"
+ shadowPressedColor: "#B32D00"
+ releasedColor: "#FF6C3C"
+ pressedColor: "#FF4304"
+ onClicked: {
+ appWindow.startDaemon(daemonFlags.text)
+ }
+ }
+
+ StandardButton {
+ visible: true
+ enabled: appWindow.daemonRunning
+ id: stopDaemonButton
+ text: qsTr("Stop daemon") + translationManager.emptyString
+ shadowReleasedColor: "#FF4304"
+ shadowPressedColor: "#B32D00"
+ releasedColor: "#FF6C3C"
+ pressedColor: "#FF4304"
+ onClicked: {
+ appWindow.stopDaemon()
+ }
+ }
+
+ StandardButton {
+ visible: true
+ id: daemonConsolePopupButton
+ text: qsTr("Show log") + translationManager.emptyString
+ shadowReleasedColor: "#FF4304"
+ shadowPressedColor: "#B32D00"
+ releasedColor: "#FF6C3C"
+ pressedColor: "#FF4304"
+ onClicked: {
+ daemonConsolePopup.open();
+ }
+ }
+ }
+
+ RowLayout {
+ id: daemonFlagsRow
+ Label {
+ id: daemonFlagsLabel
+ color: "#4A4949"
+ text: qsTr("Daemon startup flags") + translationManager.emptyString
+ fontSize: 16
+ }
+ LineEdit {
+ id: daemonFlags
+ Layout.preferredWidth: 200
+ Layout.fillWidth: true
+ text: appWindow.persistentSettings.daemonFlags;
+ placeholderText: qsTr("(optional)") + translationManager.emptyString
+ }
+ }
+
+ RowLayout {
id: daemonAddrRow
Layout.fillWidth: true
- Layout.preferredHeight: 40
- Layout.topMargin: 40
spacing: 10
Label {
@@ -213,12 +294,8 @@ Rectangle {
StandardButton {
id: daemonAddrSave
-
Layout.fillWidth: false
-
Layout.leftMargin: 30
- Layout.minimumWidth: 100
- width: 60
text: qsTr("Save") + translationManager.emptyString
shadowReleasedColor: "#FF4304"
shadowPressedColor: "#B32D00"
@@ -238,120 +315,19 @@ Rectangle {
}
-
RowLayout {
Label {
- id: closeWalletLabel
-
- Layout.fillWidth: true
color: "#4A4949"
- text: qsTr("Manage wallet") + translationManager.emptyString
+ text: qsTr("Layout settings") + translationManager.emptyString
fontSize: 16
+ anchors.topMargin: 30
+ Layout.topMargin: 30
}
}
- RowLayout {
-
- Text {
- id: closeWalletTip
- font.family: "Arial"
- font.pointSize: 12
- color: "#4A4646"
- Layout.fillWidth: true
- wrapMode: Text.WordWrap
- text: qsTr("Close current wallet and open wizard")
- + translationManager.emptyString
- }
-
-
- StandardButton {
- id: closeWalletButton
-
-// Layout.leftMargin: 30
-// Layout.minimumWidth: 100
- width: 100
- text: qsTr("Close wallet") + translationManager.emptyString
- shadowReleasedColor: "#FF4304"
- shadowPressedColor: "#B32D00"
- releasedColor: "#FF6C3C"
- pressedColor: "#FF4304"
- visible: true
- onClicked: {
- console.log("closing wallet button clicked")
- appWindow.showWizard();
- }
- }
- }
-
- RowLayout {
- Label {
- id: manageDaemonLabel
- color: "#4A4949"
- text: qsTr("Manage daemon") + translationManager.emptyString
- fontSize: 16
- }
-
- StandardButton {
- visible: true
- enabled: !appWindow.daemonRunning
- id: startDaemonButton
- width: 110
- text: qsTr("Start daemon") + translationManager.emptyString
- shadowReleasedColor: "#FF4304"
- shadowPressedColor: "#B32D00"
- releasedColor: "#FF6C3C"
- pressedColor: "#FF4304"
- onClicked: {
- appWindow.startDaemon(daemonFlags.text)
- }
- }
-
- StandardButton {
- visible: true
- enabled: appWindow.daemonRunning
- id: stopDaemonButton
- width: 110
- text: qsTr("Stop daemon") + translationManager.emptyString
- shadowReleasedColor: "#FF4304"
- shadowPressedColor: "#B32D00"
- releasedColor: "#FF6C3C"
- pressedColor: "#FF4304"
- onClicked: {
- appWindow.stopDaemon()
- }
- }
-
- StandardButton {
- visible: true
- // enabled: appWindow.daemonRunning
- id: daemonConsolePopupButton
- width: 110
- text: qsTr("Show log") + translationManager.emptyString
- shadowReleasedColor: "#FF4304"
- shadowPressedColor: "#B32D00"
- releasedColor: "#FF6C3C"
- pressedColor: "#FF4304"
- onClicked: {
- daemonConsolePopup.open();
- }
- }
-
- }
-
- RowLayout {
- id: daemonFlagsRow
- Label {
- id: daemonFlagsLabel
- color: "#4A4949"
- text: qsTr("Daemon startup flags") + translationManager.emptyString
- fontSize: 16
- }
- LineEdit {
- id: daemonFlags
- Layout.preferredWidth: 200
- Layout.fillWidth: true
- text: appWindow.persistentSettings.daemonFlags;
- placeholderText: qsTr("(optional)") + translationManager.emptyString
- }
+ Rectangle {
+ Layout.fillWidth: true
+ height: 1
+ color: "#DEDEDE"
}
RowLayout {
@@ -386,6 +362,22 @@ Rectangle {
}
}
+ // Version
+ RowLayout {
+ Label {
+ color: "#4A4949"
+ text: qsTr("Version") + translationManager.emptyString
+ fontSize: 16
+ anchors.topMargin: 30
+ Layout.topMargin: 30
+ }
+ }
+ Rectangle {
+ Layout.fillWidth: true
+ height: 1
+ color: "#DEDEDE"
+ }
+
Label {
id: guiVersion
Layout.topMargin: 8
@@ -414,11 +406,35 @@ Rectangle {
}
}
+ PasswordDialog {
+ id: settingsPasswordDialog
+
+ onAccepted: {
+ if(appWindow.password === settingsPasswordDialog.password){
+ memoTextInput.text = currentWallet.seed
+ showSeedButton.enabled = false
+ } else {
+ informationPopup.title = qsTr("Error") + translationManager.emptyString;
+ informationPopup.text = qsTr("Wrong password");
+ informationPopup.open()
+ informationPopup.onCloseCallback = function() {
+ settingsPasswordDialog.open()
+ }
+ }
+
+ settingsPasswordDialog.password = ""
+ }
+ onRejected: {
+
+ }
+
+ }
// fires on every page load
function onPageCompleted() {
console.log("Settings page loaded");
initSettings();
+ viewOnly = currentWallet.viewOnly;
}
// fires only once
diff --git a/pages/Transfer.qml b/pages/Transfer.qml
index 5d04f457..78dfde5e 100644
--- a/pages/Transfer.qml
+++ b/pages/Transfer.qml
@@ -92,7 +92,10 @@ Rectangle {
Item {
id: pageRoot
- anchors.fill: parent
+ anchors.top: parent.top
+ anchors.left: parent.left
+ anchors.right: parent.right
+ height:550
Label {
id: amountLabel
anchors.left: parent.left
@@ -381,7 +384,7 @@ Rectangle {
shadowPressedColor: "#B32D00"
releasedColor: "#FF6C3C"
pressedColor: "#FF4304"
- enabled : pageRoot.checkInformation(amountLine.text, addressLine.text, paymentIdLine.text, appWindow.persistentSettings.testnet)
+ enabled : !appWindow.viewOnly && pageRoot.checkInformation(amountLine.text, addressLine.text, paymentIdLine.text, appWindow.persistentSettings.testnet)
onClicked: {
console.log("Transfer: paymentClicked")
var priority = priorityModel.get(priorityDropdown.currentIndex).priority
@@ -395,25 +398,7 @@ Rectangle {
}
}
- StandardButton {
- id: sweepUnmixableButton
- anchors.right: parent.right
- anchors.top: descriptionLine.bottom
- anchors.rightMargin: 17
- anchors.topMargin: 17
- width: 60*2
- text: qsTr("SWEEP UNMIXABLE") + translationManager.emptyString
- shadowReleasedColor: "#FF4304"
- shadowPressedColor: "#B32D00"
- releasedColor: "#FF6C3C"
- pressedColor: "#FF4304"
- enabled : true
- onClicked: {
- console.log("Transfer: sweepUnmixableClicked")
- root.sweepUnmixableClicked()
-
- }
- }
+ } // pageRoot
Rectangle {
id:desaturate
@@ -422,7 +407,218 @@ Rectangle {
opacity: 0.1
visible: (pageRoot.enabled)? 0 : 1;
}
- } // Rectangle
+
+ ColumnLayout {
+ anchors.top: pageRoot.bottom
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.margins: 17
+ spacing:10
+ enabled: !viewOnly || pageRoot.enabled
+
+ RowLayout {
+ Label {
+ id: manageWalletLabel
+ Layout.fillWidth: true
+ color: "#4A4949"
+ text: qsTr("Advanced") + translationManager.emptyString
+ fontSize: 16
+ Layout.topMargin: 20
+ }
+ }
+
+ Rectangle {
+ Layout.fillWidth: true
+ height: 1
+ color: "#DEDEDE"
+ }
+
+ RowLayout {
+ StandardButton {
+ id: sweepUnmixableButton
+ text: qsTr("SWEEP UNMIXABLE") + translationManager.emptyString
+ shadowReleasedColor: "#FF4304"
+ shadowPressedColor: "#B32D00"
+ releasedColor: "#FF6C3C"
+ pressedColor: "#FF4304"
+ enabled : pageRoot.enabled
+ onClicked: {
+ console.log("Transfer: sweepUnmixableClicked")
+ root.sweepUnmixableClicked()
+ }
+ }
+
+ StandardButton {
+ id: saveTxButton
+ text: qsTr("create tx file") + translationManager.emptyString
+ shadowReleasedColor: "#FF4304"
+ shadowPressedColor: "#B32D00"
+ releasedColor: "#FF6C3C"
+ pressedColor: "#FF4304"
+ visible: appWindow.viewOnly
+ enabled: pageRoot.checkInformation(amountLine.text, addressLine.text, paymentIdLine.text, appWindow.persistentSettings.testnet)
+ onClicked: {
+ console.log("Transfer: saveTx Clicked")
+ var priority = priorityModel.get(priorityDropdown.currentIndex).priority
+ console.log("priority: " + priority)
+ console.log("amount: " + amountLine.text)
+ addressLine.text = addressLine.text.trim()
+ paymentIdLine.text = paymentIdLine.text.trim()
+ root.paymentClicked(addressLine.text, paymentIdLine.text, amountLine.text, scaleValueToMixinCount(privacyLevelItem.fillLevel),
+ priority, descriptionLine.text)
+
+ }
+ }
+
+ StandardButton {
+ id: signTxButton
+ text: qsTr("sign tx file") + translationManager.emptyString
+ shadowReleasedColor: "#FF4304"
+ shadowPressedColor: "#B32D00"
+ releasedColor: "#FF6C3C"
+ pressedColor: "#FF4304"
+ visible: !appWindow.viewOnly
+ onClicked: {
+ console.log("Transfer: sign tx clicked")
+ signTxDialog.open();
+ }
+ }
+
+ StandardButton {
+ id: submitTxButton
+ text: qsTr("submit tx file") + translationManager.emptyString
+ shadowReleasedColor: "#FF4304"
+ shadowPressedColor: "#B32D00"
+ releasedColor: "#FF6C3C"
+ pressedColor: "#FF4304"
+ visible: appWindow.viewOnly
+ enabled: pageRoot.enabled
+ onClicked: {
+ console.log("Transfer: submit tx clicked")
+ submitTxDialog.open();
+ }
+ }
+
+ StandardButton {
+ id: rescanSpentButton
+ text: qsTr("Rescan spent") + translationManager.emptyString
+ shadowReleasedColor: "#FF4304"
+ shadowPressedColor: "#B32D00"
+ releasedColor: "#FF6C3C"
+ pressedColor: "#FF4304"
+ enabled: pageRoot.enabled
+ onClicked: {
+ if (!currentWallet.rescanSpent()) {
+ console.error("Error: ", currentWallet.errorString);
+ informationPopup.title = qsTr("Error") + translationManager.emptyString;
+ informationPopup.text = qsTr("Error: ") + currentWallet.errorString
+ informationPopup.icon = StandardIcon.Critical
+ informationPopup.onCloseCallback = null
+ informationPopup.open();
+ } else {
+ informationPopup.title = qsTr("Information") + translationManager.emptyString
+ informationPopup.text = qsTr("Sucessfully rescanned spent outputs") + translationManager.emptyString
+ informationPopup.icon = StandardIcon.Information
+ informationPopup.onCloseCallback = null
+ informationPopup.open();
+ }
+ }
+ }
+ }
+
+
+ }
+
+
+
+ //SignTxDialog
+ FileDialog {
+ id: signTxDialog
+ title: "Please choose a file"
+ folder: "file://" +moneroAccountsDir
+ nameFilters: [ "Unsigned transfers (*)"]
+
+ onAccepted: {
+ var path = walletManager.urlToLocalPath(fileUrl);
+ // Load the unsigned tx from file
+ var transaction = currentWallet.loadTxFile(path);
+
+ if (transaction.status !== PendingTransaction.Status_Ok) {
+ console.error("Can't load unsigned transaction: ", transaction.errorString);
+ informationPopup.title = qsTr("Error") + translationManager.emptyString;
+ informationPopup.text = qsTr("Can't load unsigned transaction: ") + transaction.errorString
+ informationPopup.icon = StandardIcon.Critical
+ informationPopup.onCloseCallback = null
+ informationPopup.open();
+ // deleting transaction object, we don't want memleaks
+ transaction.destroy();
+ } else {
+ confirmationDialog.text = qsTr("\nNumber of transactions: ") + transaction.txCount
+ for (var i = 0; i < transaction.txCount; ++i) {
+ confirmationDialog.text += qsTr("\nTransaction #%1").arg(i+1)
+ +qsTr("\nRecipient: ") + transaction.recipientAddress[i]
+ + (transaction.paymentId[i] == "" ? "" : qsTr("\n\payment ID: ") + transaction.paymentId[i])
+ + qsTr("\nAmount: ") + walletManager.displayAmount(transaction.amount(i))
+ + qsTr("\nFee: ") + walletManager.displayAmount(transaction.fee(i))
+ + qsTr("\nMixin: ") + transaction.mixin(i)
+
+ // TODO: add descriptions to unsigned_tx_set?
+ // + (transactionDescription === "" ? "" : (qsTr("\n\nDescription: ") + transactionDescription))
+ + translationManager.emptyString
+ if (i > 0) {
+ confirmationDialog.text += "\n\n"
+ }
+
+ }
+
+ console.log(transaction.confirmationMessage);
+
+ // Show confirmation dialog
+ confirmationDialog.title = qsTr("Confirmation") + translationManager.emptyString
+ confirmationDialog.icon = StandardIcon.Question
+ confirmationDialog.onAcceptedCallback = function() {
+ transaction.sign(path+"_signed");
+ transaction.destroy();
+ };
+ confirmationDialog.onRejectedCallback = transaction.destroy;
+
+ confirmationDialog.open()
+ }
+
+ }
+ onRejected: {
+ // File dialog closed
+ console.log("Canceled")
+ }
+ }
+
+ //SignTxDialog
+ FileDialog {
+ id: submitTxDialog
+ title: "Please choose a file"
+ folder: "file://" +moneroAccountsDir
+ nameFilters: [ "signed transfers (*)"]
+
+ onAccepted: {
+ if(!currentWallet.submitTxFile(walletManager.urlToLocalPath(fileUrl))){
+ informationPopup.title = qsTr("Error") + translationManager.emptyString;
+ informationPopup.text = qsTr("Can't submit transaction: ") + currentWallet.errorString
+ informationPopup.icon = StandardIcon.Critical
+ informationPopup.onCloseCallback = null
+ informationPopup.open();
+ } else {
+ informationPopup.title = qsTr("Information") + translationManager.emptyString
+ informationPopup.text = qsTr("Money sent successfully") + translationManager.emptyString
+ informationPopup.icon = StandardIcon.Information
+ informationPopup.onCloseCallback = null
+ informationPopup.open();
+ }
+ }
+ onRejected: {
+ console.log("Canceled")
+ }
+
+ }
Rectangle {
x: root.width/2 - width/2
@@ -464,6 +660,12 @@ Rectangle {
return;
}
+ if (currentWallet.viewOnly) {
+ // statusText.text = qsTr("Wallet is view only.")
+ //return;
+ }
+ pageRoot.enabled = false;
+
switch (currentWallet.connected) {
case Wallet.ConnectionStatus_Disconnected:
statusText.text = qsTr("Wallet is not connected to daemon.") + "<br>" + root.startLinkText
diff --git a/qml.qrc b/qml.qrc
index a952af0f..a4a76ce5 100644
--- a/qml.qrc
+++ b/qml.qrc
@@ -122,5 +122,7 @@
<file>pages/Sign.qml</file>
<file>components/DaemonManagerDialog.qml</file>
<file>version.js</file>
+ <file>wizard/WizardPasswordUI.qml</file>
+ <file>wizard/WizardCreateViewOnlyWallet.qml</file>
</qresource>
</RCC>
diff --git a/src/libwalletqt/PendingTransaction.cpp b/src/libwalletqt/PendingTransaction.cpp
index 8df7ad28..bd621d6c 100644
--- a/src/libwalletqt/PendingTransaction.cpp
+++ b/src/libwalletqt/PendingTransaction.cpp
@@ -13,7 +13,10 @@ QString PendingTransaction::errorString() const
bool PendingTransaction::commit()
{
- return m_pimpl->commit();
+ // Save transaction to file if fileName is set.
+ if(!m_fileName.isEmpty())
+ return m_pimpl->commit(m_fileName.toStdString());
+ return m_pimpl->commit(m_fileName.toStdString());
}
quint64 PendingTransaction::amount() const
@@ -47,6 +50,11 @@ quint64 PendingTransaction::txCount() const
return m_pimpl->txCount();
}
+void PendingTransaction::setFilename(const QString &fileName)
+{
+ m_fileName = fileName;
+}
+
PendingTransaction::PendingTransaction(Monero::PendingTransaction *pt, QObject *parent)
: QObject(parent), m_pimpl(pt)
{
diff --git a/src/libwalletqt/PendingTransaction.h b/src/libwalletqt/PendingTransaction.h
index ad2cb275..a20264e6 100644
--- a/src/libwalletqt/PendingTransaction.h
+++ b/src/libwalletqt/PendingTransaction.h
@@ -44,6 +44,7 @@ public:
quint64 fee() const;
QStringList txid() const;
quint64 txCount() const;
+ Q_INVOKABLE void setFilename(const QString &fileName);
private:
explicit PendingTransaction(Monero::PendingTransaction * pt, QObject *parent = 0);
@@ -51,6 +52,7 @@ private:
private:
friend class Wallet;
Monero::PendingTransaction * m_pimpl;
+ QString m_fileName;
};
#endif // PENDINGTRANSACTION_H
diff --git a/src/libwalletqt/UnsignedTransaction.cpp b/src/libwalletqt/UnsignedTransaction.cpp
new file mode 100644
index 00000000..47dca57b
--- /dev/null
+++ b/src/libwalletqt/UnsignedTransaction.cpp
@@ -0,0 +1,92 @@
+#include "UnsignedTransaction.h"
+#include <QVector>
+#include <QDebug>
+
+UnsignedTransaction::Status UnsignedTransaction::status() const
+{
+ return static_cast<Status>(m_pimpl->status());
+}
+
+QString UnsignedTransaction::errorString() const
+{
+ return QString::fromStdString(m_pimpl->errorString());
+}
+
+quint64 UnsignedTransaction::amount(int index) const
+{
+ std::vector<uint64_t> arr = m_pimpl->amount();
+ if(index > arr.size() - 1)
+ return 0;
+ return arr[index];
+}
+
+quint64 UnsignedTransaction::fee(int index) const
+{
+ std::vector<uint64_t> arr = m_pimpl->fee();
+ if(index > arr.size() - 1)
+ return 0;
+ return arr[index];
+}
+
+quint64 UnsignedTransaction::mixin(int index) const
+{
+ std::vector<uint64_t> arr = m_pimpl->mixin();
+ if(index > arr.size() - 1)
+ return 0;
+ return arr[index];
+}
+
+quint64 UnsignedTransaction::txCount() const
+{
+ return m_pimpl->txCount();
+}
+
+quint64 UnsignedTransaction::minMixinCount() const
+{
+ return m_pimpl->minMixinCount();
+}
+
+QString UnsignedTransaction::confirmationMessage() const
+{
+ return QString::fromStdString(m_pimpl->confirmationMessage());
+}
+
+QStringList UnsignedTransaction::paymentId() const
+{
+ QList<QString> list;
+ for (const auto &t: m_pimpl->paymentId())
+ list.append(QString::fromStdString(t));
+ return list;
+}
+
+QStringList UnsignedTransaction::recipientAddress() const
+{
+ QList<QString> list;
+ for (const auto &t: m_pimpl->recipientAddress())
+ list.append(QString::fromStdString(t));
+ return list;
+}
+
+bool UnsignedTransaction::sign(const QString &fileName) const
+{
+ if(!m_pimpl->sign(fileName.toStdString()))
+ return false;
+ // export key images
+ return m_walletImpl->exportKeyImages(fileName.toStdString() + "_keyImages");
+}
+
+void UnsignedTransaction::setFilename(const QString &fileName)
+{
+ m_fileName = fileName;
+}
+
+UnsignedTransaction::UnsignedTransaction(Monero::UnsignedTransaction *pt, Monero::Wallet *walletImpl, QObject *parent)
+ : QObject(parent), m_pimpl(pt), m_walletImpl(walletImpl)
+{
+
+}
+
+UnsignedTransaction::~UnsignedTransaction()
+{
+ delete m_pimpl;
+}
diff --git a/src/libwalletqt/UnsignedTransaction.h b/src/libwalletqt/UnsignedTransaction.h
new file mode 100644
index 00000000..749445a2
--- /dev/null
+++ b/src/libwalletqt/UnsignedTransaction.h
@@ -0,0 +1,59 @@
+#ifndef UNSIGNEDTRANSACTION_H
+#define UNSIGNEDTRANSACTION_H
+
+#include <QObject>
+
+#include <wallet/wallet2_api.h>
+
+class UnsignedTransaction : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(Status status READ status)
+ Q_PROPERTY(QString errorString READ errorString)
+ // Q_PROPERTY(QList<qulonglong> amount READ amount)
+ // Q_PROPERTY(QList<qulonglong> fee READ fee)
+ Q_PROPERTY(quint64 txCount READ txCount)
+ Q_PROPERTY(QString confirmationMessage READ confirmationMessage)
+ Q_PROPERTY(QStringList recipientAddress READ recipientAddress)
+ Q_PROPERTY(QStringList paymentId READ paymentId)
+ Q_PROPERTY(quint64 minMixinCount READ minMixinCount)
+
+public:
+ enum Status {
+ Status_Ok = Monero::UnsignedTransaction::Status_Ok,
+ Status_Error = Monero::UnsignedTransaction::Status_Error,
+ Status_Critical = Monero::UnsignedTransaction::Status_Critical
+ };
+ Q_ENUM(Status)
+
+ enum Priority {
+ Priority_Low = Monero::UnsignedTransaction::Priority_Low,
+ Priority_Medium = Monero::UnsignedTransaction::Priority_Medium,
+ Priority_High = Monero::UnsignedTransaction::Priority_High
+ };
+ Q_ENUM(Priority)
+
+ Status status() const;
+ QString errorString() const;
+ Q_INVOKABLE quint64 amount(int index) const;
+ Q_INVOKABLE quint64 fee(int index) const;
+ Q_INVOKABLE quint64 mixin(int index) const;
+ QStringList recipientAddress() const;
+ QStringList paymentId() const;
+ quint64 txCount() const;
+ QString confirmationMessage() const;
+ quint64 minMixinCount() const;
+ Q_INVOKABLE bool sign(const QString &fileName) const;
+ Q_INVOKABLE void setFilename(const QString &fileName);
+
+private:
+ explicit UnsignedTransaction(Monero::UnsignedTransaction * pt, Monero::Wallet *walletImpl, QObject *parent = 0);
+ ~UnsignedTransaction();
+private:
+ friend class Wallet;
+ Monero::UnsignedTransaction * m_pimpl;
+ QString m_fileName;
+ Monero::Wallet * m_walletImpl;
+};
+
+#endif // UNSIGNEDTRANSACTION_H
diff --git a/src/libwalletqt/Wallet.cpp b/src/libwalletqt/Wallet.cpp
index 47f4a06a..6147e1cb 100644
--- a/src/libwalletqt/Wallet.cpp
+++ b/src/libwalletqt/Wallet.cpp
@@ -1,5 +1,6 @@
#include "Wallet.h"
#include "PendingTransaction.h"
+#include "UnsignedTransaction.h"
#include "TransactionHistory.h"
#include "AddressBook.h"
#include "model/TransactionHistoryModel.h"
@@ -158,6 +159,15 @@ void Wallet::initAsync(const QString &daemonAddress, quint64 upperTransactionLim
m_walletImpl->initAsync(daemonAddress.toStdString(), upperTransactionLimit);
}
+//! create a view only wallet
+bool Wallet::createViewOnly(const QString &path, const QString &password) const
+{
+ // Create path
+ QDir d = QFileInfo(path).absoluteDir();
+ d.mkpath(d.absolutePath());
+ return m_walletImpl->createWatchOnly(path.toStdString(),password.toStdString(),m_walletImpl->getSeedLanguage());
+}
+
bool Wallet::connectToDaemon()
{
return m_walletImpl->connectToDaemon();
@@ -168,6 +178,11 @@ void Wallet::setTrustedDaemon(bool arg)
m_walletImpl->setTrustedDaemon(arg);
}
+bool Wallet::viewOnly() const
+{
+ return m_walletImpl->watchOnly();
+}
+
quint64 Wallet::balance() const
{
return m_walletImpl->balance();
@@ -197,7 +212,6 @@ quint64 Wallet::daemonBlockChainHeight() const
quint64 Wallet::daemonBlockChainTargetHeight() const
{
-
if (m_daemonBlockChainTargetHeight == 0
|| m_daemonBlockChainTargetHeightTime.elapsed() / 1000 > m_daemonBlockChainTargetHeightTtl) {
m_daemonBlockChainTargetHeight = m_walletImpl->daemonBlockChainTargetHeight();
@@ -309,12 +323,34 @@ void Wallet::createSweepUnmixableTransactionAsync()
});
}
+UnsignedTransaction * Wallet::loadTxFile(const QString &fileName)
+{
+ qDebug() << "Trying to sign " << fileName;
+ Monero::UnsignedTransaction * ptImpl = m_walletImpl->loadUnsignedTx(fileName.toStdString());
+ UnsignedTransaction * result = new UnsignedTransaction(ptImpl, m_walletImpl, this);
+ return result;
+}
+
+bool Wallet::submitTxFile(const QString &fileName) const
+{
+ qDebug() << "Trying to submit " << fileName;
+ if (!m_walletImpl->submitTransaction(fileName.toStdString()))
+ return false;
+ // import key images
+ return m_walletImpl->importKeyImages(fileName.toStdString() + "_keyImages");
+}
+
void Wallet::disposeTransaction(PendingTransaction *t)
{
m_walletImpl->disposeTransaction(t->m_pimpl);
delete t;
}
+void Wallet::disposeTransaction(UnsignedTransaction *t)
+{
+ delete t;
+}
+
TransactionHistory *Wallet::history() const
{
return m_history;
@@ -474,6 +510,11 @@ bool Wallet::parse_uri(const QString &uri, QString &address, QString &payment_id
return res;
}
+bool Wallet::rescanSpent()
+{
+ return m_walletImpl->rescanSpent();
+}
+
Wallet::Wallet(Monero::Wallet *w, QObject *parent)
: QObject(parent)
, m_walletImpl(w)
diff --git a/src/libwalletqt/Wallet.h b/src/libwalletqt/Wallet.h
index 63d3b1a3..2fb0ba6e 100644
--- a/src/libwalletqt/Wallet.h
+++ b/src/libwalletqt/Wallet.h
@@ -6,6 +6,7 @@
#include "wallet/wallet2_api.h" // we need to have an access to the Monero::Wallet::Status enum here;
#include "PendingTransaction.h" // we need to have an access to the PendingTransaction::Priority enum here;
+#include "UnsignedTransaction.h"
namespace Monero {
class Wallet; // forward declaration
@@ -36,7 +37,7 @@ class Wallet : public QObject
Q_PROPERTY(QString path READ path)
Q_PROPERTY(AddressBookModel * addressBookModel READ addressBookModel)
Q_PROPERTY(AddressBook * addressBook READ addressBook)
-
+ Q_PROPERTY(bool viewOnly READ viewOnly)
public:
@@ -98,6 +99,9 @@ public:
//! initializes wallet asynchronously
Q_INVOKABLE void initAsync(const QString &daemonAddress, quint64 upperTransactionLimit, bool isRecovering = false, quint64 restoreHeight = 0);
+ //! create a view only wallet
+ Q_INVOKABLE bool createViewOnly(const QString &path, const QString &password) const;
+
//! connects to daemon
Q_INVOKABLE bool connectToDaemon();
@@ -110,6 +114,9 @@ public:
//! returns unlocked balance
Q_INVOKABLE quint64 unlockedBalance() const;
+ //! returns if view only wallet
+ Q_INVOKABLE bool viewOnly() const;
+
//! returns current wallet's block height
//! (can be less than daemon's blockchain height when wallet sync in progress)
Q_INVOKABLE quint64 blockChainHeight() const;
@@ -156,9 +163,19 @@ public:
//! creates async sweep unmixable transaction
Q_INVOKABLE void createSweepUnmixableTransactionAsync();
+ //! Sign a transfer from file
+ Q_INVOKABLE UnsignedTransaction * loadTxFile(const QString &fileName);
+
+ //! Submit a transfer from file
+ Q_INVOKABLE bool submitTxFile(const QString &fileName) const;
+
+
//! deletes transaction and frees memory
Q_INVOKABLE void disposeTransaction(PendingTransaction * t);
+ //! deletes unsigned transaction and frees memory
+ Q_INVOKABLE void disposeTransaction(UnsignedTransaction * t);
+
//! returns transaction history
TransactionHistory * history() const;
@@ -193,8 +210,9 @@ public:
Q_INVOKABLE bool setUserNote(const QString &txid, const QString &note);
Q_INVOKABLE QString getUserNote(const QString &txid) const;
-
Q_INVOKABLE QString getTxKey(const QString &txid) const;
+ // Rescan spent outputs
+ Q_INVOKABLE bool rescanSpent();
// TODO: setListenter() when it implemented in API
signals:
diff --git a/wizard/WizardCreateViewOnlyWallet.qml b/wizard/WizardCreateViewOnlyWallet.qml
new file mode 100644
index 00000000..dc1b813e
--- /dev/null
+++ b/wizard/WizardCreateViewOnlyWallet.qml
@@ -0,0 +1,94 @@
+// Copyright (c) 2014-2015, 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 moneroComponents.WalletManager 1.0
+import QtQuick 2.2
+import "../components"
+import "utils.js" as Utils
+
+Item {
+
+ id: passwordPage
+ opacity: 0
+ visible: false
+
+ Behavior on opacity {
+ NumberAnimation { duration: 100; easing.type: Easing.InQuad }
+ }
+
+ onOpacityChanged: visible = opacity !== 0
+
+
+ function onPageOpened(settingsObject) {
+ wizard.nextButton.enabled = true
+ }
+
+ function onPageClosed(settingsObject) {
+ var walletFullPath = wizard.createWalletPath(uiItem.walletPath,uiItem.accountNameText);
+ settingsObject['view_only_wallet_path'] = walletFullPath
+ console.log("wallet path", walletFullPath)
+ return wizard.walletPathValid(walletFullPath);
+ }
+
+ Row {
+ id: dotsRow
+ anchors.top: parent.top
+ anchors.right: parent.right
+ anchors.topMargin: 85
+ spacing: 6
+
+ ListModel {
+ id: dotsModel
+ ListElement { dotColor: "#FFE00A" }
+ ListElement { dotColor: "#DBDBDB" }
+ }
+
+ Repeater {
+ model: dotsModel
+ delegate: Rectangle {
+ width: 12; height: 12
+ radius: 6
+ color: dotColor
+ }
+ }
+ }
+
+ WizardManageWalletUI {
+ id: uiItem
+ titleText: qsTr("Give your view only wallet a name") + translationManager.emptyString
+ wordsTextItem.visible: false
+ restoreHeightVisible:false
+ walletName: appWindow.walletName + "-viewonly"
+ progressDotsModel: dotsModel
+ }
+
+
+ Component.onCompleted: {
+ //parent.wizardRestarted.connect(onWizardRestarted)
+ }
+}
diff --git a/wizard/WizardMain.qml b/wizard/WizardMain.qml
index ec720026..124a5784 100644
--- a/wizard/WizardMain.qml
+++ b/wizard/WizardMain.qml
@@ -44,6 +44,7 @@ Rectangle {
// disable donation page
"create_wallet" : [welcomePage, optionsPage, createWalletPage, passwordPage, finishPage ],
"recovery_wallet" : [welcomePage, optionsPage, recoveryWalletPage, passwordPage, finishPage ],
+ "create_view_only_wallet" : [ createViewOnlyWalletPage, passwordPage ],
}
property string currentPath: "create_wallet"
@@ -89,15 +90,12 @@ Rectangle {
currentPage += step_value
pages[currentPage].opacity = 1;
- var nextButtonVisible = pages[currentPage] !== optionsPage;
+ var nextButtonVisible = pages[currentPage] !== optionsPage && currentPage < pages.length - 1;
nextButton.visible = nextButtonVisible;
if (typeof pages[currentPage].onPageOpened !== 'undefined') {
pages[currentPage].onPageOpened(settings,next)
}
-
-
-
}
}
@@ -130,6 +128,16 @@ Rectangle {
wizard.openWalletFromFileClicked();
}
+ function openCreateViewOnlyWalletPage(){
+ pages[currentPage].opacity = 0
+ currentPath = "create_view_only_wallet"
+ pages = paths[currentPath]
+ currentPage = pages.indexOf(createViewOnlyWalletPage)
+ createViewOnlyWalletPage.opacity = 1
+ nextButton.visible = true
+ rootItem.state = "wizard";
+ }
+
function createWalletPath(folder_path,account_name){
// Remove trailing slash - (default on windows and mac)
@@ -274,6 +282,16 @@ Rectangle {
anchors.rightMargin: 50
}
+ WizardCreateViewOnlyWallet {
+ id: createViewOnlyWalletPage
+ anchors.top: parent.top
+ anchors.bottom: parent.bottom
+ anchors.right: nextButton.left
+ anchors.left: prevButton.right
+ anchors.leftMargin: 50
+ anchors.rightMargin: 50
+ }
+
WizardRecoveryWallet {
id: recoveryWalletPage
anchors.top: parent.top
@@ -356,4 +374,59 @@ Rectangle {
wizard.useMoneroClicked();
}
}
+
+ StandardButton {
+ id: createViewOnlyWalletButton
+ anchors.right: parent.right
+ anchors.bottom: parent.bottom
+ anchors.margins: 50
+ width: 110
+ text: qsTr("Create wallet") + translationManager.emptyString
+ shadowReleasedColor: "#FF4304"
+ shadowPressedColor: "#B32D00"
+ releasedColor: "#FF6C3C"
+ pressedColor: "#FF4304"
+ visible: currentPath === "create_view_only_wallet" && parent.paths[currentPath][currentPage] === passwordPage
+ enabled: passwordPage.passwordsMatch
+ onClicked: {
+ if (currentWallet.createViewOnly(settings['view_only_wallet_path'],passwordPage.password)) {
+ console.log("view only wallet created in ",settings['view_only_wallet_path']);
+ informationPopup.title = qsTr("Success") + translationManager.emptyString;
+ informationPopup.text = qsTr('The view only wallet has been created. You can open it by closing this current wallet, clicking the "Open wallet from file" option, and selecting the view wallet in: \n%1')
+ .arg(settings['view_only_wallet_path']);
+ informationPopup.open()
+ informationPopup.onCloseCallback = null
+ rootItem.state = "normal"
+ wizard.restart();
+
+ } else {
+ informationPopup.title = qsTr("Error") + translationManager.emptyString;
+ informationPopup.text = currentWallet.errorString;
+ informationPopup.open()
+ }
+
+ }
+ }
+
+ StandardButton {
+ id: abortViewOnlyButton
+ anchors.right: createViewOnlyWalletButton.left
+ anchors.bottom: parent.bottom
+ anchors.margins: 50
+ width: 110
+ text: qsTr("Abort") + translationManager.emptyString
+ shadowReleasedColor: "#FF4304"
+ shadowPressedColor: "#B32D00"
+ releasedColor: "#FF6C3C"
+ pressedColor: "#FF4304"
+ visible: currentPath === "create_view_only_wallet" && parent.paths[currentPath][currentPage] === passwordPage
+ onClicked: {
+ wizard.restart();
+ rootItem.state = "normal"
+ }
+ }
+
+
+
+
}
diff --git a/wizard/WizardManageWalletUI.qml b/wizard/WizardManageWalletUI.qml
index 4420de2d..d3bfb558 100644
--- a/wizard/WizardManageWalletUI.qml
+++ b/wizard/WizardManageWalletUI.qml
@@ -43,7 +43,8 @@ Item {
property alias wordsTextItem : memoTextItem
property alias restoreHeight : restoreHeightItem.text
property alias restoreHeightVisible: restoreHeightItem.visible
-
+ property alias walletName : accountName.text
+ property alias progressDotsModel : progressDots.model
// TODO extend properties if needed
@@ -64,6 +65,7 @@ Item {
}
Repeater {
+ id: progressDots
model: dotsModel
delegate: Rectangle {
width: 12; height: 12
@@ -184,7 +186,7 @@ Item {
Row {
anchors.left: parent.left
anchors.right: parent.right
- anchors.top: (restoreHeightItem.visible)? restoreHeightItem.bottom : memoTextItem.bottom
+ anchors.top: (restoreHeightItem.visible)? restoreHeightItem.bottom : (memoTextItem.visible)? memoTextItem.bottom : frameHeader.bottom
anchors.topMargin: 24
spacing: 16
diff --git a/wizard/WizardPassword.qml b/wizard/WizardPassword.qml
index c26a7407..adba7a94 100644
--- a/wizard/WizardPassword.qml
+++ b/wizard/WizardPassword.qml
@@ -36,8 +36,9 @@ Item {
id: passwordPage
opacity: 0
visible: false
-
property alias titleText: titleText.text
+ property alias passwordsMatch: passwordUI.passwordsMatch
+ property alias password: passwordUI.password
Behavior on opacity {
NumberAnimation { duration: 100; easing.type: Easing.InQuad }
}
@@ -47,7 +48,7 @@ Item {
function onPageOpened(settingsObject) {
wizard.nextButton.enabled = true
- handlePassword();
+ passwordUI.handlePassword();
if (wizard.currentPath === "create_wallet") {
passwordPage.titleText = qsTr("Give your wallet a password") + translationManager.emptyString
@@ -55,44 +56,22 @@ Item {
passwordPage.titleText = qsTr("Give your wallet a password") + translationManager.emptyString
}
- passwordItem.focus = true;
+ passwordUI.focus = true;
}
function onPageClosed(settingsObject) {
// TODO: set password on the final page
// settingsObject.wallet.setPassword(passwordItem.password)
- settingsObject['wallet_password'] = passwordItem.password
+ settingsObject['wallet_password'] = passwordUI.password
return true
}
function onWizardRestarted(){
// Reset password fields
- passwordItem.password = "";
- retypePasswordItem.password = "";
- }
-
- function handlePassword() {
- // allow to forward step only if passwords match
-
- wizard.nextButton.enabled = passwordItem.password === retypePasswordItem.password
-
- // scorePassword returns value from 0 to... lots
- var strength = walletManager.getPasswordStrength(passwordItem.password);
- // consider anything below 10 bits as dire
- strength -= 10
- if (strength < 0)
- strength = 0
- // use a slight parabola to discourage short passwords
- strength = strength ^ 1.2 / 3
- // mapScope does not clamp
- if (strength > 100)
- strength = 100
- // privacyLevel component uses 1..13 scale
- privacyLevel.fillLevel = Utils.mapScope(1, 100, 1, 13, strength)
+ passwordUI.password = "";
+ passwordUI.confirmPassword = "";
}
-
-
Row {
id: dotsRow
anchors.top: parent.top
@@ -111,6 +90,9 @@ Item {
Repeater {
model: dotsModel
delegate: Rectangle {
+ // Password page is last page when creating view only wallet
+ // TODO: make this dynamic for all pages in wizard
+ visible: (wizard.currentPath != "create_view_only_wallet" || index < 2)
width: 12; height: 12
radius: 6
color: dotColor
@@ -157,39 +139,12 @@ Item {
}
- WizardPasswordInput {
- id: passwordItem
- anchors.top: headerColumn.bottom
- anchors.horizontalCenter: parent.horizontalCenter
- anchors.topMargin: 24
- width: 300
- height: 62
- placeholderText : qsTr("Password") + translationManager.emptyString;
- KeyNavigation.tab: retypePasswordItem
- onChanged: handlePassword()
-
- }
-
- WizardPasswordInput {
- id: retypePasswordItem
- anchors.top: passwordItem.bottom
- anchors.horizontalCenter: parent.horizontalCenter
- anchors.topMargin: 24
- width: 300
- height: 62
- placeholderText : qsTr("Confirm password") + translationManager.emptyString;
- KeyNavigation.tab: passwordItem
- onChanged: handlePassword()
- }
-
- PrivacyLevelSmall {
- id: privacyLevel
- anchors.left: parent.left
+ WizardPasswordUI {
+ id: passwordUI
anchors.right: parent.right
- anchors.top: retypePasswordItem.bottom
- anchors.topMargin: 60
- background: "#F0EEEE"
- interactive: false
+ anchors.left: parent.left
+ anchors.top: headerColumn.bottom
+ anchors.topMargin: 30
}
Component.onCompleted: {
diff --git a/wizard/WizardPasswordUI.qml b/wizard/WizardPasswordUI.qml
new file mode 100644
index 00000000..c9a4b0c8
--- /dev/null
+++ b/wizard/WizardPasswordUI.qml
@@ -0,0 +1,96 @@
+// Copyright (c) 2014-2015, 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 moneroComponents.WalletManager 1.0
+import QtQuick 2.2
+import "../components"
+import "utils.js" as Utils
+
+FocusScope {
+ property alias password: passwordItem.password
+ property alias confirmPassword: retypePasswordItem.password
+ property bool passwordsMatch: passwordItem.password === retypePasswordItem.password
+
+ function handlePassword() {
+ // allow to forward step only if passwords match
+
+ wizard.nextButton.enabled = passwordItem.password === retypePasswordItem.password
+
+ // scorePassword returns value from 0 to... lots
+ var strength = walletManager.getPasswordStrength(passwordItem.password);
+ // consider anything below 10 bits as dire
+ strength -= 10
+ if (strength < 0)
+ strength = 0
+ // use a slight parabola to discourage short passwords
+ strength = strength ^ 1.2 / 3
+ // mapScope does not clamp
+ if (strength > 100)
+ strength = 100
+ // privacyLevel component uses 1..13 scale
+ privacyLevel.fillLevel = Utils.mapScope(1, 100, 1, 13, strength)
+ }
+
+ WizardPasswordInput {
+ id: passwordItem
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.topMargin: 24
+ width: 300
+ height: 62
+ placeholderText : qsTr("Password") + translationManager.emptyString;
+ KeyNavigation.tab: retypePasswordItem
+ onChanged: handlePassword()
+ focus: true
+ }
+
+ WizardPasswordInput {
+ id: retypePasswordItem
+ anchors.top: passwordItem.bottom
+ anchors.horizontalCenter: parent.horizontalCenter
+ anchors.topMargin: 24
+ width: 300
+ height: 62
+ placeholderText : qsTr("Confirm password") + translationManager.emptyString;
+ KeyNavigation.tab: passwordItem
+ onChanged: handlePassword()
+ }
+
+ PrivacyLevelSmall {
+ id: privacyLevel
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.top: retypePasswordItem.bottom
+ anchors.topMargin: 60
+ background: "#F0EEEE"
+ interactive: false
+ }
+
+ Component.onCompleted: {
+ //parent.wizardRestarted.connect(onWizardRestarted)
+ }
+}
diff --git a/wizard/utils.js b/wizard/utils.js
index 7b9fc241..65409046 100644
--- a/wizard/utils.js
+++ b/wizard/utils.js
@@ -15,3 +15,10 @@ function tr(text) {
function lineBreaksToSpaces(text) {
return text.trim().replace(/(\r\n|\n|\r)/gm, " ");
}
+
+function usefulName(path) {
+ // arbitrary "short enough" limit
+ if (path.length < 32)
+ return path
+ return path.replace(/.*[\/\\]/, '').replace(/\.keys$/, '')
+}