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)
This commit is contained in:
Riccardo Spagni
2017-01-15 14:58:47 -05:00
20 changed files with 1006 additions and 273 deletions

View File

@@ -52,13 +52,6 @@ Window {
show() show()
} }
function usefulName(path) {
// arbitrary "short enough" limit
if (path.length < 32)
return path
return path.replace(/.*[\/\\]/, '').replace(/\.keys$/, '')
}
// TODO: implement without hardcoding sizes // TODO: implement without hardcoding sizes
width: 480 width: 480
height: walletName ? 240 : 200 height: walletName ? 240 : 200
@@ -74,7 +67,7 @@ Window {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
Label { 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.alignment: Qt.AlignHCenter
Layout.columnSpan: 2 Layout.columnSpan: 2
Layout.fillWidth: true Layout.fillWidth: true

View File

@@ -27,6 +27,7 @@
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import QtQuick 2.0 import QtQuick 2.0
import QtQuick.Layouts 1.1
Item { Item {
id: button id: button
@@ -41,6 +42,10 @@ Item {
property alias text: label.text property alias text: label.text
signal clicked() signal clicked()
// Dynamic label width
width: label.contentWidth + 20
Layout.minimumWidth: 100
Rectangle { Rectangle {
anchors.left: parent.left anchors.left: parent.left
@@ -78,13 +83,13 @@ Item {
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
elide: Text.ElideRight
font.family: "Arial" font.family: "Arial"
font.bold: true font.bold: true
font.letterSpacing: -1 font.letterSpacing: -1
font.pixelSize: button.fontSize font.pixelSize: button.fontSize
color: parent.textColor color: parent.textColor
visible: parent.icon === "" visible: parent.icon === ""
font.capitalization : Font.AllUppercase
} }
Image { Image {

View File

@@ -40,6 +40,7 @@
#include "Wallet.h" #include "Wallet.h"
#include "QRCodeImageProvider.h" #include "QRCodeImageProvider.h"
#include "PendingTransaction.h" #include "PendingTransaction.h"
#include "UnsignedTransaction.h"
#include "TranslationManager.h" #include "TranslationManager.h"
#include "TransactionInfo.h" #include "TransactionInfo.h"
#include "TransactionHistory.h" #include "TransactionHistory.h"
@@ -72,6 +73,9 @@ int main(int argc, char *argv[])
qmlRegisterUncreatableType<PendingTransaction>("moneroComponents.PendingTransaction", 1, 0, "PendingTransaction", qmlRegisterUncreatableType<PendingTransaction>("moneroComponents.PendingTransaction", 1, 0, "PendingTransaction",
"PendingTransaction can't be instantiated directly"); "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", qmlRegisterUncreatableType<WalletManager>("moneroComponents.WalletManager", 1, 0, "WalletManager",
"WalletManager can't be instantiated directly"); "WalletManager can't be instantiated directly");

View File

@@ -228,6 +228,8 @@ ApplicationWindow {
currentWallet = wallet currentWallet = wallet
updateSyncing(false) updateSyncing(false)
viewOnly = currentWallet.viewOnly;
// connect handlers // connect handlers
currentWallet.refreshed.connect(onWalletRefresh) currentWallet.refreshed.connect(onWalletRefresh)
currentWallet.updated.connect(onWalletUpdate) currentWallet.updated.connect(onWalletUpdate)
@@ -252,12 +254,20 @@ ApplicationWindow {
return wallet_path; return wallet_path;
} }
function usefulName(path) {
// arbitrary "short enough" limit
if (path.length < 32)
return path
return path.replace(/.*[\/\\]/, '').replace(/\.keys$/, '')
}
function onWalletConnectionStatusChanged(){ function onWalletConnectionStatusChanged(){
console.log("Wallet connection status changed") console.log("Wallet connection status changed")
middlePanel.updateStatus(); middlePanel.updateStatus();
} }
function onWalletOpened(wallet) { function onWalletOpened(wallet) {
walletName = usefulName(wallet.path)
console.log(">>> wallet opened: " + wallet) console.log(">>> wallet opened: " + wallet)
if (wallet.status !== Wallet.Status_Ok) { if (wallet.status !== Wallet.Status_Ok) {
if (appWindow.password === '') { if (appWindow.password === '') {
@@ -265,7 +275,7 @@ ApplicationWindow {
console.log("closing wallet async : " + wallet.address) console.log("closing wallet async : " + wallet.address)
closeWallet(); closeWallet();
// try to open wallet with password; // try to open wallet with password;
passwordDialog.open(wallet.path); passwordDialog.open(walletName);
} else { } else {
// opening with password but password doesn't match // opening with password but password doesn't match
console.error("Error opening wallet with password: ", wallet.errorString); console.error("Error opening wallet with password: ", wallet.errorString);
@@ -277,7 +287,7 @@ ApplicationWindow {
closeWallet(); closeWallet();
informationPopup.open() informationPopup.open()
informationPopup.onCloseCallback = function() { informationPopup.onCloseCallback = function() {
passwordDialog.open(wallet.path) passwordDialog.open(walletName)
} }
} }
return; return;
@@ -285,7 +295,6 @@ ApplicationWindow {
// wallet opened successfully, subscribing for wallet updates // wallet opened successfully, subscribing for wallet updates
connectWallet(wallet) connectWallet(wallet)
} }
@@ -466,7 +475,7 @@ ApplicationWindow {
// called on "transfer" // 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("Creating transaction: ")
console.log("\taddress: ", address, console.log("\taddress: ", address,
", payment_id: ", paymentId, ", payment_id: ", paymentId,
@@ -514,6 +523,24 @@ ApplicationWindow {
currentWallet.createTransactionAsync(address, paymentId, amountxmr, mixinCount, priority); 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() { function handleSweepUnmixable() {
console.log("Creating transaction: ") console.log("Creating transaction: ")
@@ -554,7 +581,7 @@ ApplicationWindow {
} }
// called after user confirms transaction // called after user confirms transaction
function handleTransactionConfirmed() { function handleTransactionConfirmed(fileName) {
// grab transaction.txid before commit, since it clears it. // grab transaction.txid before commit, since it clears it.
// we actually need to copy it, because QML will incredibly // we actually need to copy it, because QML will incredibly
// call the function multiple times when the variable is used // call the function multiple times when the variable is used
@@ -565,6 +592,20 @@ ApplicationWindow {
for (var i = 0; i < txid_org.length; ++i) for (var i = 0; i < txid_org.length; ++i)
txid[i] = txid_org[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()) { if (!transaction.commit()) {
console.log("Error committing transaction: " + transaction.errorString); console.log("Error committing transaction: " + transaction.errorString);
informationPopup.title = qsTr("Error") + translationManager.emptyString informationPopup.title = qsTr("Error") + translationManager.emptyString
@@ -577,7 +618,7 @@ ApplicationWindow {
txid_text += ", " txid_text += ", "
txid_text += txid[i] 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 informationPopup.icon = StandardIcon.Information
if (transactionDescription.length > 0) { if (transactionDescription.length > 0) {
for (var i = 0; i < txid.length; ++i) for (var i = 0; i < txid.length; ++i)
@@ -674,7 +715,6 @@ ApplicationWindow {
rootItem.state = "wizard" rootItem.state = "wizard"
} }
objectName: "appWindow" objectName: "appWindow"
visible: true visible: true
width: rightPanelExpanded ? 1269 : 1269 - 300 width: rightPanelExpanded ? 1269 : 1269 - 300
@@ -764,10 +804,31 @@ ApplicationWindow {
id: transactionConfirmationPopup id: transactionConfirmationPopup
onAccepted: { onAccepted: {
close(); 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 //Open Wallet from file
FileDialog { FileDialog {
id: fileDialog id: fileDialog

View File

@@ -9,7 +9,7 @@ CONFIG += c++11
# cleaning "auto-generated" bitmonero directory on "make distclean" # cleaning "auto-generated" bitmonero directory on "make distclean"
QMAKE_DISTCLEAN += -r $$WALLET_ROOT QMAKE_DISTCLEAN += -r $$WALLET_ROOT
INCLUDEPATH += $$WALLET_ROOT/include \ INCLUDEPATH += $$WALLET_ROOT/include \
$$PWD/src/libwalletqt \ $$PWD/src/libwalletqt \
$$PWD/src/QR-Code-generator \ $$PWD/src/QR-Code-generator \
$$PWD/src \ $$PWD/src \
@@ -36,7 +36,8 @@ HEADERS += \
src/daemon/DaemonManager.h \ src/daemon/DaemonManager.h \
src/model/AddressBookModel.h \ src/model/AddressBookModel.h \
src/libwalletqt/AddressBook.h \ src/libwalletqt/AddressBook.h \
src/zxcvbn-c/zxcvbn.h src/zxcvbn-c/zxcvbn.h \
src/libwalletqt/UnsignedTransaction.h
SOURCES += main.cpp \ SOURCES += main.cpp \
@@ -59,7 +60,8 @@ SOURCES += main.cpp \
src/daemon/DaemonManager.cpp \ src/daemon/DaemonManager.cpp \
src/model/AddressBookModel.cpp \ src/model/AddressBookModel.cpp \
src/libwalletqt/AddressBook.cpp \ src/libwalletqt/AddressBook.cpp \
src/zxcvbn-c/zxcvbn.c src/zxcvbn-c/zxcvbn.c \
src/libwalletqt/UnsignedTransaction.cpp
lupdate_only { lupdate_only {
SOURCES = *.qml \ SOURCES = *.qml \
@@ -289,7 +291,8 @@ OTHER_FILES += \
$$TRANSLATIONS $$TRANSLATIONS
DISTFILES += \ DISTFILES += \
notes.txt notes.txt \
monero/src/wallet/CMakeLists.txt
# windows application icon # windows application icon

View File

@@ -39,51 +39,26 @@ import moneroComponents.Clipboard 1.0
Rectangle { Rectangle {
property var daemonAddress property var daemonAddress
property bool viewOnly: false
color: "#F0EEEE" color: "#F0EEEE"
Clipboard { id: clipboard } Clipboard { id: clipboard }
function initSettings() { function initSettings() {
//runs on every page load
// Mnemonic seed setting
// Mnemonic seed settings memoTextInput.text = (viewOnly)? qsTr("View only wallets doesn't have a mnemonic seed") : qsTr("Click button to show seed") + translationManager.emptyString
memoTextInput.text = qsTr("Click button to show seed") + translationManager.emptyString showSeedButton.enabled = !viewOnly
showSeedButton.visible = true
// Daemon settings // Daemon settings
daemonAddress = persistentSettings.daemon_address.split(":"); daemonAddress = persistentSettings.daemon_address.split(":");
console.log("address: " + persistentSettings.daemon_address) console.log("address: " + persistentSettings.daemon_address)
// try connecting to daemon // 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 { ColumnLayout {
id: mainLayout id: mainLayout
anchors.margins: 40 anchors.margins: 40
@@ -92,17 +67,59 @@ Rectangle {
anchors.right: parent.right anchors.right: parent.right
spacing: 10 spacing: 10
//! Manage wallet
Label { RowLayout {
id: seedLabel Label {
color: "#4A4949" id: manageWalletLabel
fontSize: 16 Layout.fillWidth: true
text: qsTr("Mnemonic seed: ") + translationManager.emptyString color: "#4A4949"
Layout.preferredWidth: 100 text: qsTr("Manage wallet") + translationManager.emptyString
Layout.alignment: Qt.AlignLeft 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 { TextArea {
enabled: !viewOnly
id: memoTextInput id: memoTextInput
textMargin: 6 textMargin: 6
wrapMode: TextEdit.WordWrap wrapMode: TextEdit.WordWrap
@@ -113,7 +130,7 @@ Rectangle {
Layout.preferredHeight: 100 Layout.preferredHeight: 100
Layout.alignment: Qt.AlignHCenter 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 { style: TextAreaStyle {
backgroundColor: "#FFFFFF" backgroundColor: "#FFFFFF"
@@ -137,7 +154,9 @@ Rectangle {
} }
} }
RowLayout { RowLayout {
enabled: !viewOnly
Layout.fillWidth: true Layout.fillWidth: true
Text { Text {
id: wordsTipText id: wordsTipText
@@ -151,37 +170,99 @@ Rectangle {
} }
StandardButton { StandardButton {
id: showSeedButton id: showSeedButton
fontSize: 14
shadowReleasedColor: "#FF4304" shadowReleasedColor: "#FF4304"
shadowPressedColor: "#B32D00" shadowPressedColor: "#B32D00"
releasedColor: "#FF6C3C" releasedColor: "#FF6C3C"
pressedColor: "#FF4304" pressedColor: "#FF4304"
text: qsTr("Show seed") text: qsTr("Show seed")
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
Layout.preferredWidth: 100
onClicked: { onClicked: {
settingsPasswordDialog.open(); 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 { Rectangle {
Layout.fillWidth: true Layout.fillWidth: true
height: 1 height: 1
color: "#DEDEDE" 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 { RowLayout {
id: daemonAddrRow id: daemonAddrRow
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: 40
Layout.topMargin: 40
spacing: 10 spacing: 10
Label { Label {
@@ -213,12 +294,8 @@ Rectangle {
StandardButton { StandardButton {
id: daemonAddrSave id: daemonAddrSave
Layout.fillWidth: false Layout.fillWidth: false
Layout.leftMargin: 30 Layout.leftMargin: 30
Layout.minimumWidth: 100
width: 60
text: qsTr("Save") + translationManager.emptyString text: qsTr("Save") + translationManager.emptyString
shadowReleasedColor: "#FF4304" shadowReleasedColor: "#FF4304"
shadowPressedColor: "#B32D00" shadowPressedColor: "#B32D00"
@@ -238,120 +315,19 @@ Rectangle {
} }
RowLayout { RowLayout {
Label { Label {
id: closeWalletLabel
Layout.fillWidth: true
color: "#4A4949" color: "#4A4949"
text: qsTr("Manage wallet") + translationManager.emptyString text: qsTr("Layout settings") + translationManager.emptyString
fontSize: 16 fontSize: 16
anchors.topMargin: 30
Layout.topMargin: 30
} }
} }
RowLayout { Rectangle {
Layout.fillWidth: true
Text { height: 1
id: closeWalletTip color: "#DEDEDE"
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
}
} }
RowLayout { 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 { Label {
id: guiVersion id: guiVersion
Layout.topMargin: 8 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 // fires on every page load
function onPageCompleted() { function onPageCompleted() {
console.log("Settings page loaded"); console.log("Settings page loaded");
initSettings(); initSettings();
viewOnly = currentWallet.viewOnly;
} }
// fires only once // fires only once

View File

@@ -92,7 +92,10 @@ Rectangle {
Item { Item {
id: pageRoot id: pageRoot
anchors.fill: parent anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
height:550
Label { Label {
id: amountLabel id: amountLabel
anchors.left: parent.left anchors.left: parent.left
@@ -381,7 +384,7 @@ Rectangle {
shadowPressedColor: "#B32D00" shadowPressedColor: "#B32D00"
releasedColor: "#FF6C3C" releasedColor: "#FF6C3C"
pressedColor: "#FF4304" 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: { onClicked: {
console.log("Transfer: paymentClicked") console.log("Transfer: paymentClicked")
var priority = priorityModel.get(priorityDropdown.currentIndex).priority var priority = priorityModel.get(priorityDropdown.currentIndex).priority
@@ -395,25 +398,7 @@ Rectangle {
} }
} }
StandardButton { } // pageRoot
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()
}
}
Rectangle { Rectangle {
id:desaturate id:desaturate
@@ -422,7 +407,218 @@ Rectangle {
opacity: 0.1 opacity: 0.1
visible: (pageRoot.enabled)? 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 { Rectangle {
x: root.width/2 - width/2 x: root.width/2 - width/2
@@ -464,6 +660,12 @@ Rectangle {
return; return;
} }
if (currentWallet.viewOnly) {
// statusText.text = qsTr("Wallet is view only.")
//return;
}
pageRoot.enabled = false;
switch (currentWallet.connected) { switch (currentWallet.connected) {
case Wallet.ConnectionStatus_Disconnected: case Wallet.ConnectionStatus_Disconnected:
statusText.text = qsTr("Wallet is not connected to daemon.") + "<br>" + root.startLinkText statusText.text = qsTr("Wallet is not connected to daemon.") + "<br>" + root.startLinkText

View File

@@ -122,5 +122,7 @@
<file>pages/Sign.qml</file> <file>pages/Sign.qml</file>
<file>components/DaemonManagerDialog.qml</file> <file>components/DaemonManagerDialog.qml</file>
<file>version.js</file> <file>version.js</file>
<file>wizard/WizardPasswordUI.qml</file>
<file>wizard/WizardCreateViewOnlyWallet.qml</file>
</qresource> </qresource>
</RCC> </RCC>

View File

@@ -13,7 +13,10 @@ QString PendingTransaction::errorString() const
bool PendingTransaction::commit() 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 quint64 PendingTransaction::amount() const
@@ -47,6 +50,11 @@ quint64 PendingTransaction::txCount() const
return m_pimpl->txCount(); return m_pimpl->txCount();
} }
void PendingTransaction::setFilename(const QString &fileName)
{
m_fileName = fileName;
}
PendingTransaction::PendingTransaction(Monero::PendingTransaction *pt, QObject *parent) PendingTransaction::PendingTransaction(Monero::PendingTransaction *pt, QObject *parent)
: QObject(parent), m_pimpl(pt) : QObject(parent), m_pimpl(pt)
{ {

View File

@@ -44,6 +44,7 @@ public:
quint64 fee() const; quint64 fee() const;
QStringList txid() const; QStringList txid() const;
quint64 txCount() const; quint64 txCount() const;
Q_INVOKABLE void setFilename(const QString &fileName);
private: private:
explicit PendingTransaction(Monero::PendingTransaction * pt, QObject *parent = 0); explicit PendingTransaction(Monero::PendingTransaction * pt, QObject *parent = 0);
@@ -51,6 +52,7 @@ private:
private: private:
friend class Wallet; friend class Wallet;
Monero::PendingTransaction * m_pimpl; Monero::PendingTransaction * m_pimpl;
QString m_fileName;
}; };
#endif // PENDINGTRANSACTION_H #endif // PENDINGTRANSACTION_H

View File

@@ -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;
}

View File

@@ -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

View File

@@ -1,5 +1,6 @@
#include "Wallet.h" #include "Wallet.h"
#include "PendingTransaction.h" #include "PendingTransaction.h"
#include "UnsignedTransaction.h"
#include "TransactionHistory.h" #include "TransactionHistory.h"
#include "AddressBook.h" #include "AddressBook.h"
#include "model/TransactionHistoryModel.h" #include "model/TransactionHistoryModel.h"
@@ -158,6 +159,15 @@ void Wallet::initAsync(const QString &daemonAddress, quint64 upperTransactionLim
m_walletImpl->initAsync(daemonAddress.toStdString(), upperTransactionLimit); 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() bool Wallet::connectToDaemon()
{ {
return m_walletImpl->connectToDaemon(); return m_walletImpl->connectToDaemon();
@@ -168,6 +178,11 @@ void Wallet::setTrustedDaemon(bool arg)
m_walletImpl->setTrustedDaemon(arg); m_walletImpl->setTrustedDaemon(arg);
} }
bool Wallet::viewOnly() const
{
return m_walletImpl->watchOnly();
}
quint64 Wallet::balance() const quint64 Wallet::balance() const
{ {
return m_walletImpl->balance(); return m_walletImpl->balance();
@@ -197,7 +212,6 @@ quint64 Wallet::daemonBlockChainHeight() const
quint64 Wallet::daemonBlockChainTargetHeight() const quint64 Wallet::daemonBlockChainTargetHeight() const
{ {
if (m_daemonBlockChainTargetHeight == 0 if (m_daemonBlockChainTargetHeight == 0
|| m_daemonBlockChainTargetHeightTime.elapsed() / 1000 > m_daemonBlockChainTargetHeightTtl) { || m_daemonBlockChainTargetHeightTime.elapsed() / 1000 > m_daemonBlockChainTargetHeightTtl) {
m_daemonBlockChainTargetHeight = m_walletImpl->daemonBlockChainTargetHeight(); 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) void Wallet::disposeTransaction(PendingTransaction *t)
{ {
m_walletImpl->disposeTransaction(t->m_pimpl); m_walletImpl->disposeTransaction(t->m_pimpl);
delete t; delete t;
} }
void Wallet::disposeTransaction(UnsignedTransaction *t)
{
delete t;
}
TransactionHistory *Wallet::history() const TransactionHistory *Wallet::history() const
{ {
return m_history; return m_history;
@@ -474,6 +510,11 @@ bool Wallet::parse_uri(const QString &uri, QString &address, QString &payment_id
return res; return res;
} }
bool Wallet::rescanSpent()
{
return m_walletImpl->rescanSpent();
}
Wallet::Wallet(Monero::Wallet *w, QObject *parent) Wallet::Wallet(Monero::Wallet *w, QObject *parent)
: QObject(parent) : QObject(parent)
, m_walletImpl(w) , m_walletImpl(w)

View File

@@ -6,6 +6,7 @@
#include "wallet/wallet2_api.h" // we need to have an access to the Monero::Wallet::Status enum here; #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 "PendingTransaction.h" // we need to have an access to the PendingTransaction::Priority enum here;
#include "UnsignedTransaction.h"
namespace Monero { namespace Monero {
class Wallet; // forward declaration class Wallet; // forward declaration
@@ -36,7 +37,7 @@ class Wallet : public QObject
Q_PROPERTY(QString path READ path) Q_PROPERTY(QString path READ path)
Q_PROPERTY(AddressBookModel * addressBookModel READ addressBookModel) Q_PROPERTY(AddressBookModel * addressBookModel READ addressBookModel)
Q_PROPERTY(AddressBook * addressBook READ addressBook) Q_PROPERTY(AddressBook * addressBook READ addressBook)
Q_PROPERTY(bool viewOnly READ viewOnly)
public: public:
@@ -98,6 +99,9 @@ public:
//! initializes wallet asynchronously //! initializes wallet asynchronously
Q_INVOKABLE void initAsync(const QString &daemonAddress, quint64 upperTransactionLimit, bool isRecovering = false, quint64 restoreHeight = 0); 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 //! connects to daemon
Q_INVOKABLE bool connectToDaemon(); Q_INVOKABLE bool connectToDaemon();
@@ -110,6 +114,9 @@ public:
//! returns unlocked balance //! returns unlocked balance
Q_INVOKABLE quint64 unlockedBalance() const; Q_INVOKABLE quint64 unlockedBalance() const;
//! returns if view only wallet
Q_INVOKABLE bool viewOnly() const;
//! returns current wallet's block height //! returns current wallet's block height
//! (can be less than daemon's blockchain height when wallet sync in progress) //! (can be less than daemon's blockchain height when wallet sync in progress)
Q_INVOKABLE quint64 blockChainHeight() const; Q_INVOKABLE quint64 blockChainHeight() const;
@@ -156,9 +163,19 @@ public:
//! creates async sweep unmixable transaction //! creates async sweep unmixable transaction
Q_INVOKABLE void createSweepUnmixableTransactionAsync(); 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 //! deletes transaction and frees memory
Q_INVOKABLE void disposeTransaction(PendingTransaction * t); Q_INVOKABLE void disposeTransaction(PendingTransaction * t);
//! deletes unsigned transaction and frees memory
Q_INVOKABLE void disposeTransaction(UnsignedTransaction * t);
//! returns transaction history //! returns transaction history
TransactionHistory * history() const; TransactionHistory * history() const;
@@ -193,8 +210,9 @@ public:
Q_INVOKABLE bool setUserNote(const QString &txid, const QString &note); Q_INVOKABLE bool setUserNote(const QString &txid, const QString &note);
Q_INVOKABLE QString getUserNote(const QString &txid) const; Q_INVOKABLE QString getUserNote(const QString &txid) const;
Q_INVOKABLE QString getTxKey(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 // TODO: setListenter() when it implemented in API
signals: signals:

View File

@@ -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)
}
}

View File

@@ -44,6 +44,7 @@ Rectangle {
// disable donation page // disable donation page
"create_wallet" : [welcomePage, optionsPage, createWalletPage, passwordPage, finishPage ], "create_wallet" : [welcomePage, optionsPage, createWalletPage, passwordPage, finishPage ],
"recovery_wallet" : [welcomePage, optionsPage, recoveryWalletPage, passwordPage, finishPage ], "recovery_wallet" : [welcomePage, optionsPage, recoveryWalletPage, passwordPage, finishPage ],
"create_view_only_wallet" : [ createViewOnlyWalletPage, passwordPage ],
} }
property string currentPath: "create_wallet" property string currentPath: "create_wallet"
@@ -89,15 +90,12 @@ Rectangle {
currentPage += step_value currentPage += step_value
pages[currentPage].opacity = 1; pages[currentPage].opacity = 1;
var nextButtonVisible = pages[currentPage] !== optionsPage; var nextButtonVisible = pages[currentPage] !== optionsPage && currentPage < pages.length - 1;
nextButton.visible = nextButtonVisible; nextButton.visible = nextButtonVisible;
if (typeof pages[currentPage].onPageOpened !== 'undefined') { if (typeof pages[currentPage].onPageOpened !== 'undefined') {
pages[currentPage].onPageOpened(settings,next) pages[currentPage].onPageOpened(settings,next)
} }
} }
} }
@@ -130,6 +128,16 @@ Rectangle {
wizard.openWalletFromFileClicked(); 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){ function createWalletPath(folder_path,account_name){
// Remove trailing slash - (default on windows and mac) // Remove trailing slash - (default on windows and mac)
@@ -274,6 +282,16 @@ Rectangle {
anchors.rightMargin: 50 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 { WizardRecoveryWallet {
id: recoveryWalletPage id: recoveryWalletPage
anchors.top: parent.top anchors.top: parent.top
@@ -356,4 +374,59 @@ Rectangle {
wizard.useMoneroClicked(); 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"
}
}
} }

View File

@@ -43,7 +43,8 @@ Item {
property alias wordsTextItem : memoTextItem property alias wordsTextItem : memoTextItem
property alias restoreHeight : restoreHeightItem.text property alias restoreHeight : restoreHeightItem.text
property alias restoreHeightVisible: restoreHeightItem.visible property alias restoreHeightVisible: restoreHeightItem.visible
property alias walletName : accountName.text
property alias progressDotsModel : progressDots.model
// TODO extend properties if needed // TODO extend properties if needed
@@ -64,6 +65,7 @@ Item {
} }
Repeater { Repeater {
id: progressDots
model: dotsModel model: dotsModel
delegate: Rectangle { delegate: Rectangle {
width: 12; height: 12 width: 12; height: 12
@@ -184,7 +186,7 @@ Item {
Row { Row {
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right 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 anchors.topMargin: 24
spacing: 16 spacing: 16

View File

@@ -36,8 +36,9 @@ Item {
id: passwordPage id: passwordPage
opacity: 0 opacity: 0
visible: false visible: false
property alias titleText: titleText.text property alias titleText: titleText.text
property alias passwordsMatch: passwordUI.passwordsMatch
property alias password: passwordUI.password
Behavior on opacity { Behavior on opacity {
NumberAnimation { duration: 100; easing.type: Easing.InQuad } NumberAnimation { duration: 100; easing.type: Easing.InQuad }
} }
@@ -47,7 +48,7 @@ Item {
function onPageOpened(settingsObject) { function onPageOpened(settingsObject) {
wizard.nextButton.enabled = true wizard.nextButton.enabled = true
handlePassword(); passwordUI.handlePassword();
if (wizard.currentPath === "create_wallet") { if (wizard.currentPath === "create_wallet") {
passwordPage.titleText = qsTr("Give your wallet a password") + translationManager.emptyString 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 passwordPage.titleText = qsTr("Give your wallet a password") + translationManager.emptyString
} }
passwordItem.focus = true; passwordUI.focus = true;
} }
function onPageClosed(settingsObject) { function onPageClosed(settingsObject) {
// TODO: set password on the final page // TODO: set password on the final page
// settingsObject.wallet.setPassword(passwordItem.password) // settingsObject.wallet.setPassword(passwordItem.password)
settingsObject['wallet_password'] = passwordItem.password settingsObject['wallet_password'] = passwordUI.password
return true return true
} }
function onWizardRestarted(){ function onWizardRestarted(){
// Reset password fields // Reset password fields
passwordItem.password = ""; passwordUI.password = "";
retypePasswordItem.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 { Row {
id: dotsRow id: dotsRow
anchors.top: parent.top anchors.top: parent.top
@@ -111,6 +90,9 @@ Item {
Repeater { Repeater {
model: dotsModel model: dotsModel
delegate: Rectangle { 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 width: 12; height: 12
radius: 6 radius: 6
color: dotColor color: dotColor
@@ -157,39 +139,12 @@ Item {
} }
WizardPasswordInput { WizardPasswordUI {
id: passwordItem id: passwordUI
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
anchors.right: parent.right anchors.right: parent.right
anchors.top: retypePasswordItem.bottom anchors.left: parent.left
anchors.topMargin: 60 anchors.top: headerColumn.bottom
background: "#F0EEEE" anchors.topMargin: 30
interactive: false
} }
Component.onCompleted: { Component.onCompleted: {

View File

@@ -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)
}
}

View File

@@ -15,3 +15,10 @@ function tr(text) {
function lineBreaksToSpaces(text) { function lineBreaksToSpaces(text) {
return text.trim().replace(/(\r\n|\n|\r)/gm, " "); 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$/, '')
}