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:
") + usefulName(root.walletName) : qsTr("Please enter wallet password")
+ text: root.walletName.length > 0 ? qsTr("Please enter wallet password for:
") + 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("moneroComponents.PendingTransaction", 1, 0, "PendingTransaction",
"PendingTransaction can't be instantiated directly");
+ qmlRegisterUncreatableType("moneroComponents.UnsignedTransaction", 1, 0, "UnsignedTransaction",
+ "UnsignedTransaction can't be instantiated directly");
+
qmlRegisterUncreatableType("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
-
- Label {
- id: seedLabel
- color: "#4A4949"
- fontSize: 16
- text: qsTr("Mnemonic seed: ") + translationManager.emptyString
- Layout.preferredWidth: 100
- Layout.alignment: Qt.AlignLeft
+ //! Manage wallet
+ RowLayout {
+ Label {
+ id: manageWalletLabel
+ Layout.fillWidth: true
+ color: "#4A4949"
+ text: qsTr("Manage wallet") + translationManager.emptyString
+ fontSize: 16
+ Layout.topMargin: 10
+ }
}
+ 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,37 +170,99 @@ 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
color: "#DEDEDE"
}
+ 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.") + "
" + root.startLinkText
diff --git a/qml.qrc b/qml.qrc
index a952af0f..a4a76ce5 100644
--- a/qml.qrc
+++ b/qml.qrc
@@ -122,5 +122,7 @@
pages/Sign.qml
components/DaemonManagerDialog.qml
version.js
+ wizard/WizardPasswordUI.qml
+ wizard/WizardCreateViewOnlyWallet.qml
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
+#include
+
+UnsignedTransaction::Status UnsignedTransaction::status() const
+{
+ return static_cast(m_pimpl->status());
+}
+
+QString UnsignedTransaction::errorString() const
+{
+ return QString::fromStdString(m_pimpl->errorString());
+}
+
+quint64 UnsignedTransaction::amount(int index) const
+{
+ std::vector arr = m_pimpl->amount();
+ if(index > arr.size() - 1)
+ return 0;
+ return arr[index];
+}
+
+quint64 UnsignedTransaction::fee(int index) const
+{
+ std::vector arr = m_pimpl->fee();
+ if(index > arr.size() - 1)
+ return 0;
+ return arr[index];
+}
+
+quint64 UnsignedTransaction::mixin(int index) const
+{
+ std::vector 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 list;
+ for (const auto &t: m_pimpl->paymentId())
+ list.append(QString::fromStdString(t));
+ return list;
+}
+
+QStringList UnsignedTransaction::recipientAddress() const
+{
+ QList 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
+
+#include
+
+class UnsignedTransaction : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(Status status READ status)
+ Q_PROPERTY(QString errorString READ errorString)
+ // Q_PROPERTY(QList amount READ amount)
+ // Q_PROPERTY(QList 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 ¬e);
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 = "";
+ passwordUI.password = "";
+ passwordUI.confirmPassword = "";
}
- 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)
- }
-
-
-
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$/, '')
+}