From 46db7715f9d37be244c0cb8dd8f9e1b48f2bcbd0 Mon Sep 17 00:00:00 2001 From: Ilya Kitaev Date: Fri, 29 Jan 2016 22:01:52 +0300 Subject: [PATCH 01/87] German and English translation files --- monero-core.pro | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/monero-core.pro b/monero-core.pro index 75812a7e..2d608aa8 100644 --- a/monero-core.pro +++ b/monero-core.pro @@ -11,6 +11,30 @@ SOURCES += main.cpp \ filter.cpp \ clipboardAdapter.cpp +lupdate_only { +SOURCES = *.qml \ + components/*.qml \ + pages/*.qml \ + wizard/*.qml +} + +# translations files; +TRANSLATIONS = monero-core_en.ts \ # English (could be untranslated) + monero-core_de.ts # Deutsch + + +# extra make targets for lupdate and lrelease invocation +lupdate.commands = lupdate $$_PRO_FILE_ +lupdate.depends = $$SOURCES $$HEADERS $$TRANSLATIONS +lrelease.commands = lrelease $$_PRO_FILE_ +lrelease.depends = lupdate +translate.commands = $(COPY) *.qm ${DESTDIR} +translate.depends = lrelease + +QMAKE_EXTRA_TARGETS += lupdate lrelease + + + CONFIG(release, debug|release) { DESTDIR=release } From bbc35ff484fc99e9d6efcb67eb88d1e9c05843bc Mon Sep 17 00:00:00 2001 From: Ilya Kitaev Date: Fri, 29 Jan 2016 22:08:17 +0300 Subject: [PATCH 02/87] Next button is in "disabled" state on password page --- wizard/WizardMain.qml | 9 +++++---- wizard/WizardPassword.qml | 4 +--- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/wizard/WizardMain.qml b/wizard/WizardMain.qml index 6cb26356..6a856e9b 100644 --- a/wizard/WizardMain.qml +++ b/wizard/WizardMain.qml @@ -60,15 +60,16 @@ Rectangle { } } - // disallow "next" button until passwords match + // disable "next" button until passwords match if (pages[currentPage] === passwordPage) { - nextButton.visible = passwordPage.passwordValid; + nextButton.enabled = passwordPage.passwordValid; } else if (pages[currentPage] === finishPage) { // display settings summary finishPage.updateSettingsSummary(); nextButton.visible = false } else { nextButton.visible = true + nextButton.enabled = true } } @@ -79,10 +80,10 @@ Rectangle { anchors.right: parent.right anchors.rightMargin: 50 visible: wizard.currentPage !== 1 && wizard.currentPage !== 6 - width: 50; height: 50 radius: 25 - color: nextArea.containsMouse ? "#FF4304" : "#FF6C3C" + color: enabled ? nextArea.containsMouse ? "#FF4304" : "#FF6C3C" : "#DBDBDB" + Image { anchors.centerIn: parent diff --git a/wizard/WizardPassword.qml b/wizard/WizardPassword.qml index 2926a9e9..d12409ea 100644 --- a/wizard/WizardPassword.qml +++ b/wizard/WizardPassword.qml @@ -40,10 +40,8 @@ Item { function handlePassword() { // allow to forward step only if passwords match - // print("pass1: ", passwordItem.password) - // print("pass2: ", retypePasswordItem.password) // TODO: update password strength - wizard.nextButton.visible = passwordItem.password === retypePasswordItem.password + wizard.nextButton.enabled = passwordItem.password === retypePasswordItem.password } property bool passwordValid : passwordItem.password != '' From fce88a8120182e9cf550393fd87b7541b3b558c7 Mon Sep 17 00:00:00 2001 From: Ilya Kitaev Date: Wed, 3 Feb 2016 00:09:45 +0300 Subject: [PATCH 03/87] Selection (branching) for recovery path --- monero-core.pro | 3 + qml.qrc | 2 + wizard/WizardCreateWallet.qml | 242 +-------------------------- wizard/WizardMain.qml | 75 +++++++-- wizard/WizardManageWalletUI.qml | 284 ++++++++++++++++++++++++++++++++ wizard/WizardOptions.qml | 2 + wizard/WizardPasswordInput.qml | 28 +++- wizard/WizardRecoveryWallet.qml | 55 +++++++ 8 files changed, 442 insertions(+), 249 deletions(-) create mode 100644 wizard/WizardManageWalletUI.qml create mode 100644 wizard/WizardRecoveryWallet.qml diff --git a/monero-core.pro b/monero-core.pro index 2d608aa8..3f92b5dc 100644 --- a/monero-core.pro +++ b/monero-core.pro @@ -52,3 +52,6 @@ QML_IMPORT_PATH = # Default rules for deployment. include(deployment.pri) +DISTFILES += \ + wizard/WizardManageWalletUI.qml + diff --git a/qml.qrc b/qml.qrc index e2e774af..24f3b6b2 100644 --- a/qml.qrc +++ b/qml.qrc @@ -107,5 +107,7 @@ lang/flags/russia.png lang/flags/uk.png lang/flags/usa.png + wizard/WizardManageWalletUI.qml + wizard/WizardRecoveryWallet.qml diff --git a/wizard/WizardCreateWallet.qml b/wizard/WizardCreateWallet.qml index 15e5899d..3b7bcf75 100644 --- a/wizard/WizardCreateWallet.qml +++ b/wizard/WizardCreateWallet.qml @@ -33,6 +33,7 @@ import QtQuick.Dialogs 1.2 Item { opacity: 0 visible: false + Behavior on opacity { NumberAnimation { duration: 100; easing.type: Easing.InQuad } } @@ -40,241 +41,14 @@ Item { onOpacityChanged: visible = opacity !== 0 function saveSettings(settingsObject) { - settingsObject['account_name'] = accountName.text - settingsObject['words'] = wordsText.text - settingsObject['wallet_path'] = fileUrlInput.text + settingsObject['account_name'] = uiItem.accountNameText + settingsObject['words'] = uiItem.wordsTexttext + settingsObject['wallet_path'] = uiItem.walletPath } - 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" } - ListElement { dotColor: "#DBDBDB" } - ListElement { dotColor: "#DBDBDB" } - } - - Repeater { - model: dotsModel - delegate: Rectangle { - width: 12; height: 12 - radius: 6 - color: dotColor - } - } - } - - Column { - id: headerColumn - anchors.left: parent.left - anchors.right: parent.right - anchors.leftMargin: 16 - anchors.rightMargin: 16 - anchors.top: parent.top - anchors.topMargin: 74 - spacing: 24 - - Text { - anchors.left: parent.left - width: headerColumn.width - dotsRow.width - 16 - font.family: "Arial" - font.pixelSize: 28 - wrapMode: Text.Wrap - //renderType: Text.NativeRendering - color: "#3F3F3F" - text: qsTr("A new wallet has been created for you") - } - - Text { - anchors.left: parent.left - anchors.right: parent.right - font.family: "Arial" - font.pixelSize: 18 - wrapMode: Text.Wrap - //renderType: Text.NativeRendering - color: "#4A4646" - text: qsTr("This is the name of your wallet. You can change it to a different name if you’d like:") - } - } - - Item { - id: walletNameItem - anchors.top: headerColumn.bottom - anchors.horizontalCenter: parent.horizontalCenter - anchors.topMargin: 24 - width: 300 - height: 62 - - TextInput { - id: accountName - anchors.fill: parent - horizontalAlignment: TextInput.AlignHCenter - verticalAlignment: TextInput.AlignVCenter - font.family: "Arial" - font.pixelSize: 32 - renderType: Text.NativeRendering - color: "#FF6C3C" - text: qsTr("My account name") - focus: true - } - - Rectangle { - anchors.left: parent.left - anchors.right: parent.right - anchors.bottom: parent.bottom - height: 1 - color: "#DBDBDB" - } - } - - Text { - id: frameHeader - anchors.left: parent.left - anchors.right: parent.right - anchors.leftMargin: 16 - anchors.rightMargin: 16 - anchors.top: walletNameItem.bottom - anchors.topMargin: 24 - font.family: "Arial" - font.pixelSize: 18 - //renderType: Text.NativeRendering - color: "#4A4646" - elide: Text.ElideRight - text: qsTr("This is the 24 word mnemonic for your wallet") - } - - Rectangle { - id: wordsRect - anchors.left: parent.left - anchors.right: parent.right - anchors.top: frameHeader.bottom - anchors.topMargin: 16 - height: 182 - border.width: 1 - border.color: "#DBDBDB" - - TextEdit { - id: wordsText - anchors.left: parent.left - anchors.right: parent.right - anchors.top: parent.top - anchors.bottom: tipRect.top - anchors.margins: 16 - font.family: "Arial" - font.pixelSize: 24 - wrapMode: Text.Wrap - selectByMouse: true - readOnly: true - color: "#3F3F3F" - text: "bound class paint gasp task soul forgot past pleasure physical circle appear shore bathroom glove women crap busy beauty bliss idea give needle burden" - } - - Image { - anchors.right: parent.right - anchors.bottom: tipRect.top - source: "qrc:///images/greyTriangle.png" - - Image { - anchors.centerIn: parent - source: "qrc:///images/copyToClipboard.png" - } - - Clipboard { id: clipboard } - MouseArea { - anchors.fill: parent - cursorShape: Qt.PointingHandCursor - onClicked: clipboard.setText(wordsText.text) - } - } - - Rectangle { - id: tipRect - anchors.left: parent.left - anchors.right: parent.right - anchors.bottom: parent.bottom - height: 65 - color: "#DBDBDB" - - Text { - anchors.fill: parent - anchors.margins: 16 - verticalAlignment: Text.AlignVCenter - horizontalAlignment: Text.AlignHCenter - font.family: "Arial" - font.pixelSize: 15 - color: "#4A4646" - wrapMode: Text.Wrap - text: qsTr("It is very important to write it down as this is the only backup you will need for your wallet. You will be asked to confirm the seed in the next screen to ensure it has copied down correctly.") - } - } - } - - Row { - anchors.left: parent.left - anchors.right: parent.right - anchors.top: wordsRect.bottom - anchors.topMargin: 24 - spacing: 16 - - Text { - anchors.verticalCenter: parent.verticalCenter - font.family: "Arial" - font.pixelSize: 18 - //renderType: Text.NativeRendering - color: "#4A4646" - text: qsTr("Your wallet is stored in") - } - - Item { - anchors.verticalCenter: parent.verticalCenter - width: parent.width - x - height: 34 - - FileDialog { - id: fileDialog - selectMultiple: false - title: "Please choose a file" - onAccepted: { - fileUrlInput.text = fileDialog.fileUrl - fileDialog.visible = false - } - onRejected: { - fileDialog.visible = false - } - } - - TextInput { - id: fileUrlInput - anchors.fill: parent - anchors.leftMargin: 5 - anchors.rightMargin: 5 - clip: true - font.family: "Arial" - font.pixelSize: 18 - color: "#6B0072" - verticalAlignment: Text.AlignVCenter - selectByMouse: true - text: "~/.monero/mywallet/" - onFocusChanged: { - if(focus) { - fileDialog.visible = true - } - } - } - - Rectangle { - anchors.left: parent.left - anchors.right: parent.right - anchors.bottom: parent.bottom - height: 1 - color: "#DBDBDB" - } - } + WizardManageWalletUI { + id: uiItem + titleText: qsTr("A new wallet has been created for you") + wordsTextTitle: qsTr("This is the 24 word mnemonic for your wallet") } } diff --git a/wizard/WizardMain.qml b/wizard/WizardMain.qml index 6a856e9b..a52c219f 100644 --- a/wizard/WizardMain.qml +++ b/wizard/WizardMain.qml @@ -34,7 +34,9 @@ Rectangle { property alias nextButton : nextButton property var settings : ({}) property int currentPage: 0 - property var pages: [welcomePage, optionsPage, createWalletPage, passwordPage, /*configurePage,*/ donationPage, finishPage ] + property var pages: [welcomePage, optionsPage, createWalletPage, recoveryWalletPage, + passwordPage,/*configurePage,*/ donationPage, finishPage ] + property string path; signal useMoneroClicked() border.color: "#DBDBDB" @@ -42,24 +44,35 @@ Rectangle { color: "#FFFFFF" function switchPage(next) { - // save settings for current page; if (typeof pages[currentPage].saveSettings !== 'undefined') { pages[currentPage].saveSettings(settings); } + print ("switchpage: start: currentPage: ", currentPage); - if(next === false) { - if(currentPage > 0) { - pages[currentPage].opacity = 0 - pages[--currentPage].opacity = 1 - } - } else { - if(currentPage < pages.length - 1) { - pages[currentPage].opacity = 0 - pages[++currentPage].opacity = 1 + if (currentPage > 0 || currentPage < pages.length - 1) { + + pages[currentPage].opacity = 0 + + var step_value = next ? 1 : -1 + // special case - we stepping backward from password page: + // previous page "createWallet" or "recoveryWallet" + if (!next) { + print ("stepping back: current page: ", currentPage); + if ((pages[currentPage] === passwordPage && path === "create_walled") + || (pages[currentPage] === recoveryWalletPage) ) { + step_value *= 2; + } } + + currentPage += step_value + pages[currentPage].opacity = 1; + handlePageChanged(); } + } + + function handlePageChanged() { // disable "next" button until passwords match if (pages[currentPage] === passwordPage) { nextButton.enabled = passwordPage.passwordValid; @@ -68,11 +81,32 @@ Rectangle { finishPage.updateSettingsSummary(); nextButton.visible = false } else { - nextButton.visible = true - nextButton.enabled = true + var enableButton = pages[currentPage] !== optionsPage; + nextButton.visible = nextButton.enabled = enableButton + print ("nextButtonVisible: ", enableButton) } } + function openCreateWalletPage() { + print ("show create wallet page"); + pages[currentPage].opacity = 0; + createWalletPage.opacity = 1 + path = "create_wallet"; + currentPage = pages.indexOf(createWalletPage) + handlePageChanged() + } + + function openRecoveryWalletPage() { + print ("show recovery wallet page"); + pages[currentPage].opacity = 0 + recoveryWalletPage.opacity = 1 + path = "recovery_wallet" + currentPage = pages.indexOf(recoveryWalletPage) + handlePageChanged() + } + + + Rectangle { id: nextButton @@ -120,7 +154,8 @@ Rectangle { anchors.left: prevButton.right anchors.leftMargin: 50 anchors.rightMargin: 50 - onCreateWalletClicked: wizard.switchPage(true) + onCreateWalletClicked: wizard.openCreateWalletPage() + onRecoveryWalletClicked: wizard.openRecoveryWalletPage() } WizardCreateWallet { @@ -133,6 +168,18 @@ Rectangle { anchors.rightMargin: 50 } + WizardRecoveryWallet { + id: recoveryWalletPage + anchors.top: parent.top + anchors.bottom: parent.bottom + anchors.right: nextButton.left + anchors.left: prevButton.right + anchors.leftMargin: 50 + anchors.rightMargin: 50 + } + + + WizardPassword { id: passwordPage anchors.top: parent.top diff --git a/wizard/WizardManageWalletUI.qml b/wizard/WizardManageWalletUI.qml new file mode 100644 index 00000000..242145a6 --- /dev/null +++ b/wizard/WizardManageWalletUI.qml @@ -0,0 +1,284 @@ +// 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 QtQuick 2.2 +import moneroComponents 1.0 +import QtQuick.Dialogs 1.2 + +// Reusable component for managing wallet (account name, path, private key) + +Item { + + property alias titleText: titleText.text + property alias accountNameText: accountName.text + property alias wordsTextTitle: frameHeader.text + property alias wordsText: wordsText.text + property alias wordsTextTip: tipRect + property alias walletPath: fileUrlInput.text + + + // TODO extend properties if needed + + anchors.fill: parent + 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" } + ListElement { dotColor: "#DBDBDB" } + ListElement { dotColor: "#DBDBDB" } + } + + Repeater { + model: dotsModel + delegate: Rectangle { + width: 12; height: 12 + radius: 6 + color: dotColor + } + } + } + + Column { + id: headerColumn + anchors.left: parent.left + anchors.right: parent.right + anchors.leftMargin: 16 + anchors.rightMargin: 16 + anchors.top: parent.top + anchors.topMargin: 74 + spacing: 24 + + Text { + id: titleText + anchors.left: parent.left + width: headerColumn.width - dotsRow.width - 16 + font.family: "Arial" + font.pixelSize: 28 + wrapMode: Text.Wrap + //renderType: Text.NativeRendering + color: "#3F3F3F" + } + + Text { + anchors.left: parent.left + anchors.right: parent.right + font.family: "Arial" + font.pixelSize: 18 + wrapMode: Text.Wrap + //renderType: Text.NativeRendering + color: "#4A4646" + text: qsTr("This is the name of your wallet. You can change it to a different name if you’d like:") + } + } + + Item { + id: walletNameItem + anchors.top: headerColumn.bottom + anchors.horizontalCenter: parent.horizontalCenter + anchors.topMargin: 24 + width: 300 + height: 62 + + TextInput { + id: accountName + anchors.fill: parent + horizontalAlignment: TextInput.AlignHCenter + verticalAlignment: TextInput.AlignVCenter + font.family: "Arial" + font.pixelSize: 32 + renderType: Text.NativeRendering + color: "#FF6C3C" + focus: true + text: qsTr("My account name") + } + + Rectangle { + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + height: 1 + color: "#DBDBDB" + } + } + + Text { + id: frameHeader + anchors.left: parent.left + anchors.right: parent.right + anchors.leftMargin: 16 + anchors.rightMargin: 16 + anchors.top: walletNameItem.bottom + anchors.topMargin: 24 + font.family: "Arial" + font.pixelSize: 24 + font.bold: true + //renderType: Text.NativeRendering + color: "#4A4646" + elide: Text.ElideRight + + } + + Rectangle { + id: wordsRect + anchors.left: parent.left + anchors.right: parent.right + anchors.top: frameHeader.bottom + anchors.topMargin: 16 + height: 182 + border.width: 1 + border.color: "#DBDBDB" + + TextEdit { + id: wordsText + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + anchors.bottom: tipRect.top + anchors.margins: 16 + font.family: "Arial" + font.pixelSize: 24 + wrapMode: Text.Wrap + selectByMouse: true + readOnly: true + color: "#3F3F3F" + text: "bound class paint gasp task soul forgot past pleasure physical circle appear shore bathroom glove women crap busy beauty bliss idea give needle burden" + } + + Image { + anchors.right: parent.right + anchors.bottom: tipRect.top + source: "qrc:///images/greyTriangle.png" + + Image { + anchors.centerIn: parent + source: "qrc:///images/copyToClipboard.png" + } + + Clipboard { id: clipboard } + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: clipboard.setText(wordsText.text) + } + } + + Rectangle { + id: tipRect + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + height: 65 + color: "#DBDBDB" + property alias text: wordsTipText.text + + Text { + id: wordsTipText + anchors.fill: parent + anchors.margins: 16 + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + font.family: "Arial" + font.pixelSize: 15 + color: "#4A4646" + wrapMode: Text.Wrap + text: qsTr("It is very important to write it down as this is the only backup you will need for your wallet. You will be asked to confirm the seed in the next screen to ensure it has copied down correctly.") + } + } + } + + Row { + anchors.left: parent.left + anchors.right: parent.right + anchors.top: wordsRect.bottom + anchors.topMargin: 24 + spacing: 16 + + Text { + anchors.verticalCenter: parent.verticalCenter + font.family: "Arial" + font.pixelSize: 18 + //renderType: Text.NativeRendering + color: "#4A4646" + text: qsTr("Your wallet is stored in") + } + + Item { + anchors.verticalCenter: parent.verticalCenter + width: parent.width - x + height: 34 + + FileDialog { + id: fileDialog + selectMultiple: false + title: "Please choose a file" + onAccepted: { + fileUrlInput.text = fileDialog.fileUrl + fileDialog.visible = false + } + onRejected: { + fileDialog.visible = false + } + } + + TextInput { + id: fileUrlInput + anchors.fill: parent + anchors.leftMargin: 5 + anchors.rightMargin: 5 + clip: true + font.family: "Arial" + font.pixelSize: 18 + color: "#6B0072" + verticalAlignment: Text.AlignVCenter + selectByMouse: true + text: "~/.monero/mywallet/" + onFocusChanged: { + if(focus) { + fileDialog.visible = true + } + } + } + + Rectangle { + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: parent.bottom + height: 1 + color: "#DBDBDB" + } + } + } +} + diff --git a/wizard/WizardOptions.qml b/wizard/WizardOptions.qml index debdee88..a6718853 100644 --- a/wizard/WizardOptions.qml +++ b/wizard/WizardOptions.qml @@ -31,6 +31,7 @@ import QtQuick 2.2 Item { id: page signal createWalletClicked() + signal recoveryWalletClicked() opacity: 0 visible: false Behavior on opacity { @@ -126,6 +127,7 @@ Item { id: recoverWalletArea anchors.fill: parent hoverEnabled: true + onClicked: page.recoveryWalletClicked() } } diff --git a/wizard/WizardPasswordInput.qml b/wizard/WizardPasswordInput.qml index d2bbf44a..82597421 100644 --- a/wizard/WizardPasswordInput.qml +++ b/wizard/WizardPasswordInput.qml @@ -1,4 +1,30 @@ -// WizardPasswordInput.qml +// 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 QtQuick 2.0 diff --git a/wizard/WizardRecoveryWallet.qml b/wizard/WizardRecoveryWallet.qml new file mode 100644 index 00000000..0eee3491 --- /dev/null +++ b/wizard/WizardRecoveryWallet.qml @@ -0,0 +1,55 @@ +// 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 QtQuick 2.2 +import moneroComponents 1.0 +import QtQuick.Dialogs 1.2 + +Item { + opacity: 0 + visible: false + + Behavior on opacity { + NumberAnimation { duration: 100; easing.type: Easing.InQuad } + } + + onOpacityChanged: visible = opacity !== 0 + + function saveSettings(settingsObject) { + settingsObject['account_name'] = uiItem.accountNameText + settingsObject['words'] = uiItem.wordsTexttext + settingsObject['wallet_path'] = uiItem.walletPath + } + + WizardManageWalletUI { + id: uiItem + accountNameText: qsTr("My account name") + titleText: qsTr("We're ready to recover your account") + wordsTextTitle: qsTr("This is the 25 word mnemonic for your wallet") + } +} From 6289e18f6cf7cc7c85c799940099b90081273c4c Mon Sep 17 00:00:00 2001 From: unknown Date: Wed, 3 Feb 2016 16:37:10 +0100 Subject: [PATCH 04/87] fix for window drag bug on ubuntu, only tested on windows --- .gitignore | 2 + main.cpp | 5 + main.qml | 22 ++-- monero-core.pro | 9 +- monero-core.pro.user | 251 ------------------------------------------- oscursor.cpp | 10 ++ oscursor.h | 25 +++++ 7 files changed, 61 insertions(+), 263 deletions(-) create mode 100644 .gitignore delete mode 100644 monero-core.pro.user create mode 100644 oscursor.cpp create mode 100644 oscursor.h diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..a2ce1444 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.user +*.user.* diff --git a/main.cpp b/main.cpp index b1bd1a72..ecc8b626 100644 --- a/main.cpp +++ b/main.cpp @@ -31,6 +31,7 @@ #include #include "clipboardAdapter.h" #include "filter.h" +#include "oscursor.h" int main(int argc, char *argv[]) { @@ -41,6 +42,10 @@ int main(int argc, char *argv[]) qmlRegisterType("moneroComponents", 1, 0, "Clipboard"); QQmlApplicationEngine engine; + + OSCursor cursor; + engine.rootContext()->setContextProperty("globalCursor", &cursor); + engine.rootContext()->setContextProperty("applicationDirectory", QApplication::applicationDirPath()); engine.load(QUrl(QStringLiteral("qrc:///main.qml"))); QObject *rootObject = engine.rootObjects().first(); diff --git a/main.qml b/main.qml index d629a248..c1a1d5ec 100644 --- a/main.qml +++ b/main.qml @@ -355,17 +355,17 @@ ApplicationWindow { "images/resize.png" } - property int previousX: 0 - property int previousY: 0 + property var previousPosition onPressed: { - previousX = mouseX - previousY = mouseY + previousPosition = globalCursor.getPosition() } onPositionChanged: { if(!pressed) return - var dx = previousX - mouseX - var dy = previousY - mouseY + var pos = globalCursor.getPosition() + //var delta = previousPosition - pos + var dx = previousPosition.x - pos.x + var dy = previousPosition.y - pos.y if(appWindow.width - dx > parent.maxWidth) appWindow.width -= dx @@ -374,6 +374,7 @@ ApplicationWindow { if(appWindow.height - dy > parent.maxHeight) appWindow.height -= dy else appWindow.height = parent.maxHeight + previousPosition = pos } } @@ -390,13 +391,16 @@ ApplicationWindow { property var previousPosition anchors.fill: parent propagateComposedEvents: true - onPressed: previousPosition = Qt.point(mouseX, mouseY) + onPressed: previousPosition = globalCursor.getPosition() onPositionChanged: { if (pressedButtons == Qt.LeftButton) { - var dx = mouseX - previousPosition.x - var dy = mouseY - previousPosition.y + var pos = globalCursor.getPosition() + var dx = pos.x - previousPosition.x + var dy = pos.y - previousPosition.y + appWindow.x += dx appWindow.y += dy + previousPosition = pos } } } diff --git a/monero-core.pro b/monero-core.pro index 3f92b5dc..de48d650 100644 --- a/monero-core.pro +++ b/monero-core.pro @@ -4,12 +4,14 @@ QT += qml quick widgets HEADERS += \ filter.h \ - clipboardAdapter.h + clipboardAdapter.h \ + oscursor.h SOURCES += main.cpp \ filter.cpp \ - clipboardAdapter.cpp + clipboardAdapter.cpp \ + oscursor.cpp lupdate_only { SOURCES = *.qml \ @@ -53,5 +55,6 @@ QML_IMPORT_PATH = include(deployment.pri) DISTFILES += \ - wizard/WizardManageWalletUI.qml + wizard/WizardManageWalletUI.qml \ + .gitignore diff --git a/monero-core.pro.user b/monero-core.pro.user deleted file mode 100644 index 7c1cd126..00000000 --- a/monero-core.pro.user +++ /dev/null @@ -1,251 +0,0 @@ - - - - - - ProjectExplorer.Project.ActiveTarget - 0 - - - ProjectExplorer.Project.EditorSettings - - true - false - true - - Cpp - - CppGlobal - - - - QmlJS - - QmlJSGlobal - - - 2 - UTF-8 - false - 4 - false - 80 - true - true - 1 - true - false - 0 - true - 0 - 8 - true - 1 - true - true - true - false - - - - ProjectExplorer.Project.PluginSettings - - - - ProjectExplorer.Project.Target.0 - - Desktop Qt 5.3 MinGW 32bit - Desktop Qt 5.3 MinGW 32bit - qt.53.win32_mingw482_kit - 0 - 0 - 0 - - G:/RPA/build-bitmonero-Desktop_Qt_5_3_0_MinGW_32bit-Debug - - - true - qmake - - QtProjectManager.QMakeBuildStep - false - true - - false - - - true - Make - - Qt4ProjectManager.MakeStep - - false - - - - 2 - budowania - - ProjectExplorer.BuildSteps.Build - - - - true - Make - - Qt4ProjectManager.MakeStep - - true - clean - - - 1 - czyszczenia - - ProjectExplorer.BuildSteps.Clean - - 2 - false - - Debug - - Qt4ProjectManager.Qt4BuildConfiguration - 2 - true - - - G:/RPA/build-bitmonero-Desktop_Qt_5_3_0_MinGW_32bit-Release - - - true - qmake - - QtProjectManager.QMakeBuildStep - false - true - - false - - - true - Make - - Qt4ProjectManager.MakeStep - - false - - - - 2 - budowania - - ProjectExplorer.BuildSteps.Build - - - - true - Make - - Qt4ProjectManager.MakeStep - - true - clean - - - 1 - czyszczenia - - ProjectExplorer.BuildSteps.Clean - - 2 - false - - Release - - Qt4ProjectManager.Qt4BuildConfiguration - 0 - true - - 2 - - - 0 - instalacji - - ProjectExplorer.BuildSteps.Deploy - - 1 - Zainstaluj lokalnie - - ProjectExplorer.DefaultDeployConfiguration - - 1 - - - - false - false - false - false - true - 0.01 - 10 - true - 1 - 25 - - 1 - true - false - true - valgrind - - 0 - 1 - 2 - 3 - 4 - 5 - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - - 2 - - monero-core - - Qt4ProjectManager.Qt4RunConfiguration:G:/RPA/bitmonero/monero-core.pro - - monero-core.pro - false - false - - 3768 - false - true - false - false - true - - 1 - - - - ProjectExplorer.Project.TargetCount - 1 - - - ProjectExplorer.Project.Updater.EnvironmentId - {20382c58-78e1-43a4-9d27-354b0656be87} - - - ProjectExplorer.Project.Updater.FileVersion - 15 - - diff --git a/oscursor.cpp b/oscursor.cpp new file mode 100644 index 00000000..0d9c1acc --- /dev/null +++ b/oscursor.cpp @@ -0,0 +1,10 @@ +#include "oscursor.h" +#include +OSCursor::OSCursor(QObject *parent) + : QObject(parent) +{ +} +QPoint OSCursor::getPosition() const +{ + return QCursor::pos(); +} diff --git a/oscursor.h b/oscursor.h new file mode 100644 index 00000000..db19227d --- /dev/null +++ b/oscursor.h @@ -0,0 +1,25 @@ +#ifndef OSCURSOR_H +#define OSCURSOR_H + + +#include +#include +#include +class OSCursor : public QObject +{ + Q_OBJECT + //QObject(); +public: + //QObject(QObject* aParent); + //OSCursor(); + explicit OSCursor(QObject *parent = 0); + Q_INVOKABLE QPoint getPosition() const; +}; + +//OSCursor::OSCursor() : QObject(NULL){ + +//} + + +//Q_DECLARE_METATYPE(OSCursor) +#endif // OSCURSOR_H From e3bea0dd4a3cc1b50ac5d1fe40f335d574fa2412 Mon Sep 17 00:00:00 2001 From: Ilya Kitaev Date: Sat, 6 Feb 2016 15:49:31 +0300 Subject: [PATCH 05/87] "word mnemonic input" with clipboard button and hint is reusable component; --- monero-core.pro | 5 ++- qml.qrc | 1 + wizard/WizardCreateWallet.qml | 6 ++- wizard/WizardManageWalletUI.qml | 77 ++++--------------------------- wizard/WizardMemoTextInput.qml | 80 +++++++++++++++++++++++++++++++++ wizard/WizardRecoveryWallet.qml | 6 ++- 6 files changed, 102 insertions(+), 73 deletions(-) create mode 100644 wizard/WizardMemoTextInput.qml diff --git a/monero-core.pro b/monero-core.pro index de48d650..2f7164b1 100644 --- a/monero-core.pro +++ b/monero-core.pro @@ -27,7 +27,7 @@ TRANSLATIONS = monero-core_en.ts \ # English (could be untranslated) # extra make targets for lupdate and lrelease invocation lupdate.commands = lupdate $$_PRO_FILE_ -lupdate.depends = $$SOURCES $$HEADERS $$TRANSLATIONS +lupdate.depends = $$SOURCES $$HEADERS lrelease.commands = lrelease $$_PRO_FILE_ lrelease.depends = lupdate translate.commands = $(COPY) *.qm ${DESTDIR} @@ -56,5 +56,6 @@ include(deployment.pri) DISTFILES += \ wizard/WizardManageWalletUI.qml \ - .gitignore + .gitignore \ + wizard/WizardMemoTextInput.qml diff --git a/qml.qrc b/qml.qrc index 24f3b6b2..dbd862f1 100644 --- a/qml.qrc +++ b/qml.qrc @@ -109,5 +109,6 @@ lang/flags/usa.png wizard/WizardManageWalletUI.qml wizard/WizardRecoveryWallet.qml + wizard/WizardMemoTextInput.qml diff --git a/wizard/WizardCreateWallet.qml b/wizard/WizardCreateWallet.qml index 3b7bcf75..65e2e989 100644 --- a/wizard/WizardCreateWallet.qml +++ b/wizard/WizardCreateWallet.qml @@ -49,6 +49,10 @@ Item { WizardManageWalletUI { id: uiItem titleText: qsTr("A new wallet has been created for you") - wordsTextTitle: qsTr("This is the 24 word mnemonic for your wallet") + wordsTextTitle: qsTr("This is the 25 word mnemonic for your wallet") + wordsTextItem.clipboardButtonVisible: true + wordsTextItem.tipTextVisible: true + wordsTextItem.memoTextReadOnly: true + } } diff --git a/wizard/WizardManageWalletUI.qml b/wizard/WizardManageWalletUI.qml index 242145a6..a00814a9 100644 --- a/wizard/WizardManageWalletUI.qml +++ b/wizard/WizardManageWalletUI.qml @@ -37,9 +37,8 @@ Item { property alias titleText: titleText.text property alias accountNameText: accountName.text property alias wordsTextTitle: frameHeader.text - property alias wordsText: wordsText.text - property alias wordsTextTip: tipRect property alias walletPath: fileUrlInput.text + property alias wordsTextItem : memoTextItem // TODO extend properties if needed @@ -147,81 +146,21 @@ Item { //renderType: Text.NativeRendering color: "#4A4646" elide: Text.ElideRight - + horizontalAlignment: Text.AlignHCenter } - Rectangle { - id: wordsRect - anchors.left: parent.left - anchors.right: parent.right - anchors.top: frameHeader.bottom + + WizardMemoTextInput { + id : memoTextItem + width: parent.width + anchors.top : frameHeader.bottom anchors.topMargin: 16 - height: 182 - border.width: 1 - border.color: "#DBDBDB" - - TextEdit { - id: wordsText - anchors.left: parent.left - anchors.right: parent.right - anchors.top: parent.top - anchors.bottom: tipRect.top - anchors.margins: 16 - font.family: "Arial" - font.pixelSize: 24 - wrapMode: Text.Wrap - selectByMouse: true - readOnly: true - color: "#3F3F3F" - text: "bound class paint gasp task soul forgot past pleasure physical circle appear shore bathroom glove women crap busy beauty bliss idea give needle burden" - } - - Image { - anchors.right: parent.right - anchors.bottom: tipRect.top - source: "qrc:///images/greyTriangle.png" - - Image { - anchors.centerIn: parent - source: "qrc:///images/copyToClipboard.png" - } - - Clipboard { id: clipboard } - MouseArea { - anchors.fill: parent - cursorShape: Qt.PointingHandCursor - onClicked: clipboard.setText(wordsText.text) - } - } - - Rectangle { - id: tipRect - anchors.left: parent.left - anchors.right: parent.right - anchors.bottom: parent.bottom - height: 65 - color: "#DBDBDB" - property alias text: wordsTipText.text - - Text { - id: wordsTipText - anchors.fill: parent - anchors.margins: 16 - verticalAlignment: Text.AlignVCenter - horizontalAlignment: Text.AlignHCenter - font.family: "Arial" - font.pixelSize: 15 - color: "#4A4646" - wrapMode: Text.Wrap - text: qsTr("It is very important to write it down as this is the only backup you will need for your wallet. You will be asked to confirm the seed in the next screen to ensure it has copied down correctly.") - } - } } Row { anchors.left: parent.left anchors.right: parent.right - anchors.top: wordsRect.bottom + anchors.top: memoTextItem.bottom anchors.topMargin: 24 spacing: 16 diff --git a/wizard/WizardMemoTextInput.qml b/wizard/WizardMemoTextInput.qml new file mode 100644 index 00000000..89eadb15 --- /dev/null +++ b/wizard/WizardMemoTextInput.qml @@ -0,0 +1,80 @@ +import QtQuick 2.0 +import moneroComponents 1.0 + +Column { + + property alias memoText : memoTextInput.text + property alias tipText: wordsTipText.text + property alias tipTextVisible: tipRect.visible + property alias memoTextReadOnly : memoTextInput.readOnly + property alias clipboardButtonVisible: clipboardButton.visible + + + Rectangle { + id: memoTextRect + width: parent.width + height: { + memoTextInput.height + // to have less gap between button and text input we reduce overall height by button height + //+ (clipboardButton.visible ? clipboardButton.height : 0) + + (tipRect.visible ? tipRect.height : 0) + } + border.width: 1 + border.color: "#DBDBDB" + + TextEdit { + id: memoTextInput + textMargin: 8 + text: "bound class paint gasp task soul forgot past pleasure physical circle appear shore bathroom glove women crap busy beauty bliss idea give needle burden" + font.family: "Arial" + font.pointSize: 16 + wrapMode: TextInput.Wrap + width: parent.width + selectByMouse: true + property int minimumHeight: 100 + height: contentHeight > minimumHeight ? contentHeight : minimumHeight + } + Image { + id : clipboardButton + anchors.right: parent.right + anchors.bottom: tipRect.top + source: "qrc:///images/greyTriangle.png" + Image { + anchors.centerIn: parent + source: "qrc:///images/copyToClipboard.png" + } + Clipboard { id: clipboard } + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: clipboard.setText(memoTextInput.text) + } + } + Rectangle { + id: tipRect + visible: true + anchors.left: parent.left + anchors.right: parent.right + anchors.bottom: memoTextRect.bottom + height: wordsTipText.contentHeight + wordsTipText.anchors.topMargin + color: "#DBDBDB" + property alias text: wordsTipText.text + + Text { + id: wordsTipText + anchors.fill: parent + anchors.topMargin : 16 + anchors.bottomMargin: 16 + anchors.leftMargin: 16 + anchors.rightMargin: 16 + verticalAlignment: Text.AlignVCenter + horizontalAlignment: Text.AlignHCenter + font.family: "Arial" + font.pixelSize: 15 + color: "#4A4646" + wrapMode: Text.Wrap + text: qsTr("It is very important to write it down as this is the only backup you will need for your wallet. You will be asked to confirm the seed in the next screen to ensure it has copied down correctly.") + } + } + } +} diff --git a/wizard/WizardRecoveryWallet.qml b/wizard/WizardRecoveryWallet.qml index 0eee3491..46af302b 100644 --- a/wizard/WizardRecoveryWallet.qml +++ b/wizard/WizardRecoveryWallet.qml @@ -50,6 +50,10 @@ Item { id: uiItem accountNameText: qsTr("My account name") titleText: qsTr("We're ready to recover your account") - wordsTextTitle: qsTr("This is the 25 word mnemonic for your wallet") + wordsTextTitle: qsTr("Please enter your 25 word private key") + wordsTextItem.clipboardButtonVisible: false + wordsTextItem.tipTextVisible: false + wordsTextItem.memoTextReadOnly: false + wordsTextItem.memoText: "" } } From 496a34c54a46550922a252e56306e23f1c8e0148 Mon Sep 17 00:00:00 2001 From: Ilya Kitaev Date: Sat, 6 Feb 2016 15:58:55 +0300 Subject: [PATCH 06/87] Language (qt linguist) files actually added to project. --- monero-core.pro | 14 +- monero-core_de.ts | 688 ++++++++++++++++++++++++++++++++++++++++++++++ monero-core_en.ts | 688 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 1387 insertions(+), 3 deletions(-) create mode 100644 monero-core_de.ts create mode 100644 monero-core_en.ts diff --git a/monero-core.pro b/monero-core.pro index 2f7164b1..fece659e 100644 --- a/monero-core.pro +++ b/monero-core.pro @@ -26,6 +26,7 @@ TRANSLATIONS = monero-core_en.ts \ # English (could be untranslated) # extra make targets for lupdate and lrelease invocation +# use "make lupdate" to update *.ts files and "make lrelease" to generate *.qm files lupdate.commands = lupdate $$_PRO_FILE_ lupdate.depends = $$SOURCES $$HEADERS lrelease.commands = lrelease $$_PRO_FILE_ @@ -55,7 +56,14 @@ QML_IMPORT_PATH = include(deployment.pri) DISTFILES += \ - wizard/WizardManageWalletUI.qml \ - .gitignore \ - wizard/WizardMemoTextInput.qml + + +OTHER_FILES += \ + .gitignore \ + monero-core_de.ts \ + monero-core_en.ts + + + + diff --git a/monero-core_de.ts b/monero-core_de.ts new file mode 100644 index 00000000..0a99e2fb --- /dev/null +++ b/monero-core_de.ts @@ -0,0 +1,688 @@ + + + + + AddressBook + + + Add new entry + + + + + Address + + + + + <b>Tip tekst test</b> + + + + + Payment ID <font size='2'>(Optional)</font> + + + + + <b>Payment ID</b><br/><br/>A unique user name used in<br/>the address book. It is not a<br/>transfer of information sent<br/>during thevtransfer + + + + + Description <font size='2'>(Local database)</font> + + + + + <b>Tip tekst test</b><br/><br/>test line 2 + + + + + ADD + + + + + AddressBookTable + + + No more results + + + + + Payment ID: + + + + + BasicPanel + + + Locked Balance: + + + + + 78.9239845 + + + + + Availible Balance: + + + + + 2324.9239845 + + + + + amount... + + + + + SEND + + + + + destination... + + + + + Privacy level + + + + + payment ID (optional)... + + + + + Dashboard + + + Quick transfer + + + + + SEND + + + + + <style type='text/css'>a {text-decoration: none; color: #FF6C3C; font-size: 14px;}</style> lookng for security level and address book? go to <a href='#'>Transfer</a> tab + + + + + DashboardTable + + + No more results + + + + + Date + + + + + Balance + + + + + Amount + + + + + History + + + Filter trasactions history + + + + + Address + + + + + + + + + + <b>Tip tekst test</b> + + + + + Payment ID <font size='2'>(Optional)</font> + + + + + <b>Payment ID</b><br/><br/>A unique user name used in<br/>the address book. It is not a<br/>transfer of information sent<br/>during thevtransfer + + + + + Description <font size='2'>(Local database)</font> + + + + + <b>Tip tekst test</b><br/><br/>test line 2 + + + + + Date from + + + + + + To + + + + + FILTER + + + + + Advance filtering + + + + + Type of transation + + + + + Amount from + + + + + HistoryTable + + + No more results + + + + + Payment ID: + + + + + Date + + + + + Balance + + + + + Amount + + + + + LeftPanel + + + Locked balance + + + + + Test tip 1<br/><br/>line 2 + + + + + Unlocked + + + + + Test tip 2<br/><br/>line 2 + + + + + Dashboard + + + + + D + + + + + Transfer + + + + + T + + + + + History + + + + + H + + + + + Address book + + + + + B + + + + + Mining + + + + + M + + + + + Settings + + + + + S + + + + + NetworkStatusItem + + + Network status + + + + + Connected + + + + + Disconnected + + + + + PrivacyLevelSmall + + + LOW + + + + + MEDIUM + + + + + HIGH + + + + + RightPanel + + + Twitter + + + + + SearchInput + + + Search by... + + + + + SEARCH + + + + + TickDelegate + + + LOW + + + + + MEDIUM + + + + + HIGH + + + + + Transfer + + + Amount + + + + + Transaction prority + + + + + Amount... + + + + + Privacy Level + + + + + <style type='text/css'>a {text-decoration: none; color: #FF6C3C; font-size: 14px;}</style> Address <font size='2'> ( Type in or select from </font> <a href='#'>Address</a><font size='2'> book )</font> + + + + + Payment ID <font size='2'>( Optional )</font> + + + + + Description <font size='2'>( An optional description that will be saved to the local address book if entered )</font> + + + + + SEND + + + + + WizardConfigure + + + We’re almost there - let’s just configure some Monero preferences + + + + + Kickstart the Monero blockchain? + + + + + It is very important to write it down as this is the only backup you will need for your wallet. You will be asked to confirm the seed in the next screen to ensure it has copied down correctly. + + + + + Enable disk conservation mode? + + + + + Disk conservation mode uses substantially less disk-space, but the same amount of bandwidth as a regular Monero instance. However, storing the full blockchain is beneficial to the security of the Monero network. If you are on a device with limited disk space, then this option is appropriate for you. + + + + + Allow background mining? + + + + + Mining secures the Monero network, and also pays a small reward for the work done. This option will let Monero mine when your computer is on mains power and is idle. It will stop mining when you continue working. + + + + + WizardCreateWallet + + + A new wallet has been created for you + + + + + This is the 25 word mnemonic for your wallet + + + + + WizardDonation + + + Monero development is solely supported by donations + + + + + Enable auto-donations of? + + + + + % of my fee added to each transaction + + + + + For every transaction, a small transaction fee is charged. This option lets you add an additional amount, as a percentage of that fee, to your transaction to support Monero development. For instance, a 50% autodonation take a transaction fee of 0.005 XMR and add a 0.0025 XMR to support Monero development. + + + + + Allow background mining? + + + + + Mining secures the Monero network, and also pays a small reward for the work done. This option will let Monero mine when your computer is on mains power and is idle. It will stop mining when you continue working. + + + + + WizardFinish + + + <b>Language:</b> + + + + + <b>Account name:</b> + + + + + <b>Words:</b> + + + + + <b>Wallet Path: </b> + + + + + <b>Enable auto donation: </b> + + + + + <b>Auto donation amount: </b> + + + + + <b>Allow background mining: </b> + + + + + An overview of your Monero configuration is below: + + + + + You’re all setup! + + + + + WizardMain + + + USE MONERO + + + + + WizardManageWalletUI + + + This is the name of your wallet. You can change it to a different name if you’d like: + + + + + My account name + + + + + Your wallet is stored in + + + + + WizardMemoTextInput + + + It is very important to write it down as this is the only backup you will need for your wallet. You will be asked to confirm the seed in the next screen to ensure it has copied down correctly. + + + + + WizardOptions + + + I want + + + + + Please select one of the following options: + + + + + This is my first time, I want to<br/>create a new account + + + + + I want to recover my account<br/>from my 24 work seed + + + + + WizardPassword + + + Now that your wallet has been created, please set a password for the wallet + + + + + Note that this password cannot be recovered, and if forgotten you will need to restore your wallet from the mnemonic seed you were just given<br/><br/> + Your password will be used to protect your wallet and to confirm actions, so make sure that your password is sufficiently secure. + Note that this password cannot be recovered, and if forgotten you will need to restore your wallet from the mnemonic seed you were just given<br/><br/> + Your password will be used to protect your wallet and to confirm actions, so make sure that your password is sufficiently secure. + + + + + WizardRecoveryWallet + + + My account name + + + + + We're ready to recover your account + + + + + Please enter your 25 word private key + + + + + WizardWelcome + + + Welcome + + + + + Please choose a language and regional format. + + + + diff --git a/monero-core_en.ts b/monero-core_en.ts new file mode 100644 index 00000000..e59eb72d --- /dev/null +++ b/monero-core_en.ts @@ -0,0 +1,688 @@ + + + + + AddressBook + + + Add new entry + + + + + Address + + + + + <b>Tip tekst test</b> + + + + + Payment ID <font size='2'>(Optional)</font> + + + + + <b>Payment ID</b><br/><br/>A unique user name used in<br/>the address book. It is not a<br/>transfer of information sent<br/>during thevtransfer + + + + + Description <font size='2'>(Local database)</font> + + + + + <b>Tip tekst test</b><br/><br/>test line 2 + + + + + ADD + + + + + AddressBookTable + + + No more results + + + + + Payment ID: + + + + + BasicPanel + + + Locked Balance: + + + + + 78.9239845 + + + + + Availible Balance: + + + + + 2324.9239845 + + + + + amount... + + + + + SEND + + + + + destination... + + + + + Privacy level + + + + + payment ID (optional)... + + + + + Dashboard + + + Quick transfer + + + + + SEND + + + + + <style type='text/css'>a {text-decoration: none; color: #FF6C3C; font-size: 14px;}</style> lookng for security level and address book? go to <a href='#'>Transfer</a> tab + + + + + DashboardTable + + + No more results + + + + + Date + + + + + Balance + + + + + Amount + + + + + History + + + Filter trasactions history + + + + + Address + + + + + + + + + + <b>Tip tekst test</b> + + + + + Payment ID <font size='2'>(Optional)</font> + + + + + <b>Payment ID</b><br/><br/>A unique user name used in<br/>the address book. It is not a<br/>transfer of information sent<br/>during thevtransfer + + + + + Description <font size='2'>(Local database)</font> + + + + + <b>Tip tekst test</b><br/><br/>test line 2 + + + + + Date from + + + + + + To + + + + + FILTER + + + + + Advance filtering + + + + + Type of transation + + + + + Amount from + + + + + HistoryTable + + + No more results + + + + + Payment ID: + + + + + Date + + + + + Balance + + + + + Amount + + + + + LeftPanel + + + Locked balance + + + + + Test tip 1<br/><br/>line 2 + + + + + Unlocked + + + + + Test tip 2<br/><br/>line 2 + + + + + Dashboard + + + + + D + + + + + Transfer + + + + + T + + + + + History + + + + + H + + + + + Address book + + + + + B + + + + + Mining + + + + + M + + + + + Settings + + + + + S + + + + + NetworkStatusItem + + + Network status + + + + + Connected + + + + + Disconnected + + + + + PrivacyLevelSmall + + + LOW + + + + + MEDIUM + + + + + HIGH + + + + + RightPanel + + + Twitter + + + + + SearchInput + + + Search by... + + + + + SEARCH + + + + + TickDelegate + + + LOW + + + + + MEDIUM + + + + + HIGH + + + + + Transfer + + + Amount + + + + + Transaction prority + + + + + Amount... + + + + + Privacy Level + + + + + <style type='text/css'>a {text-decoration: none; color: #FF6C3C; font-size: 14px;}</style> Address <font size='2'> ( Type in or select from </font> <a href='#'>Address</a><font size='2'> book )</font> + + + + + Payment ID <font size='2'>( Optional )</font> + + + + + Description <font size='2'>( An optional description that will be saved to the local address book if entered )</font> + + + + + SEND + + + + + WizardConfigure + + + We’re almost there - let’s just configure some Monero preferences + + + + + Kickstart the Monero blockchain? + + + + + It is very important to write it down as this is the only backup you will need for your wallet. You will be asked to confirm the seed in the next screen to ensure it has copied down correctly. + + + + + Enable disk conservation mode? + + + + + Disk conservation mode uses substantially less disk-space, but the same amount of bandwidth as a regular Monero instance. However, storing the full blockchain is beneficial to the security of the Monero network. If you are on a device with limited disk space, then this option is appropriate for you. + + + + + Allow background mining? + + + + + Mining secures the Monero network, and also pays a small reward for the work done. This option will let Monero mine when your computer is on mains power and is idle. It will stop mining when you continue working. + + + + + WizardCreateWallet + + + A new wallet has been created for you + + + + + This is the 25 word mnemonic for your wallet + + + + + WizardDonation + + + Monero development is solely supported by donations + + + + + Enable auto-donations of? + + + + + % of my fee added to each transaction + + + + + For every transaction, a small transaction fee is charged. This option lets you add an additional amount, as a percentage of that fee, to your transaction to support Monero development. For instance, a 50% autodonation take a transaction fee of 0.005 XMR and add a 0.0025 XMR to support Monero development. + + + + + Allow background mining? + + + + + Mining secures the Monero network, and also pays a small reward for the work done. This option will let Monero mine when your computer is on mains power and is idle. It will stop mining when you continue working. + + + + + WizardFinish + + + <b>Language:</b> + + + + + <b>Account name:</b> + + + + + <b>Words:</b> + + + + + <b>Wallet Path: </b> + + + + + <b>Enable auto donation: </b> + + + + + <b>Auto donation amount: </b> + + + + + <b>Allow background mining: </b> + + + + + An overview of your Monero configuration is below: + + + + + You’re all setup! + + + + + WizardMain + + + USE MONERO + + + + + WizardManageWalletUI + + + This is the name of your wallet. You can change it to a different name if you’d like: + + + + + My account name + + + + + Your wallet is stored in + + + + + WizardMemoTextInput + + + It is very important to write it down as this is the only backup you will need for your wallet. You will be asked to confirm the seed in the next screen to ensure it has copied down correctly. + + + + + WizardOptions + + + I want + + + + + Please select one of the following options: + + + + + This is my first time, I want to<br/>create a new account + + + + + I want to recover my account<br/>from my 24 work seed + + + + + WizardPassword + + + Now that your wallet has been created, please set a password for the wallet + + + + + Note that this password cannot be recovered, and if forgotten you will need to restore your wallet from the mnemonic seed you were just given<br/><br/> + Your password will be used to protect your wallet and to confirm actions, so make sure that your password is sufficiently secure. + Note that this password cannot be recovered, and if forgotten you will need to restore your wallet from the mnemonic seed you were just given<br/><br/> + Your password will be used to protect your wallet and to confirm actions, so make sure that your password is sufficiently secure. + + + + + WizardRecoveryWallet + + + My account name + + + + + We're ready to recover your account + + + + + Please enter your 25 word private key + + + + + WizardWelcome + + + Welcome + + + + + Please choose a language and regional format. + + + + From 1571118e7b59779629afdbe5d69e0c69ec75f1aa Mon Sep 17 00:00:00 2001 From: Ilya Kitaev Date: Sat, 6 Feb 2016 18:27:47 +0300 Subject: [PATCH 07/87] Refactored wizard paths internal handling --- qml.qrc | 1 + 1 file changed, 1 insertion(+) diff --git a/qml.qrc b/qml.qrc index dbd862f1..bfa55389 100644 --- a/qml.qrc +++ b/qml.qrc @@ -110,5 +110,6 @@ wizard/WizardManageWalletUI.qml wizard/WizardRecoveryWallet.qml wizard/WizardMemoTextInput.qml + wizard/utils.js From 1364c2b4988d0fad4ef236ca09bff226432764ce Mon Sep 17 00:00:00 2001 From: Ilya Kitaev Date: Sat, 6 Feb 2016 19:19:54 +0300 Subject: [PATCH 08/87] Password strength level updated --- components/PrivacyLevelSmall.qml | 10 +++++- monero-core.pro | 3 +- wizard/WizardMain.qml | 60 +++++++++++++++++--------------- wizard/WizardPassword.qml | 17 ++++++--- wizard/WizardRecoveryWallet.qml | 4 +++ 5 files changed, 60 insertions(+), 34 deletions(-) diff --git a/components/PrivacyLevelSmall.qml b/components/PrivacyLevelSmall.qml index 21c794ef..9321ffbd 100644 --- a/components/PrivacyLevelSmall.qml +++ b/components/PrivacyLevelSmall.qml @@ -36,6 +36,13 @@ Item { height: 40 clip: true + onFillLevelChanged: { + if (!interactive) { + //print("fillLevel: " + fillLevel) + fillRect.width = row.positions[fillLevel].currentX + row.x + } + } + Rectangle { anchors.left: parent.left anchors.right: parent.right @@ -134,6 +141,7 @@ Item { if(index !== -1) { fillRect.width = Qt.binding(function(){ return row.positions[index].currentX + row.x }) item.fillLevel = index + print ("fillLevel: " + item.fillLevel) } } @@ -148,7 +156,7 @@ Item { anchors.rightMargin: 8 anchors.top: bar.bottom anchors.topMargin: 5 - property var positions: new Array() + property var positions: [] Row { id: row2 diff --git a/monero-core.pro b/monero-core.pro index fece659e..dc2a500a 100644 --- a/monero-core.pro +++ b/monero-core.pro @@ -55,7 +55,7 @@ QML_IMPORT_PATH = # Default rules for deployment. include(deployment.pri) -DISTFILES += \ + OTHER_FILES += \ @@ -67,3 +67,4 @@ OTHER_FILES += \ + diff --git a/wizard/WizardMain.qml b/wizard/WizardMain.qml index a52c219f..6b25be1d 100644 --- a/wizard/WizardMain.qml +++ b/wizard/WizardMain.qml @@ -34,9 +34,13 @@ Rectangle { property alias nextButton : nextButton property var settings : ({}) property int currentPage: 0 - property var pages: [welcomePage, optionsPage, createWalletPage, recoveryWalletPage, - passwordPage,/*configurePage,*/ donationPage, finishPage ] - property string path; + + property var paths: { + "create_wallet" : [welcomePage, optionsPage, createWalletPage, passwordPage, donationPage, finishPage ], + "recovery_wallet" : [welcomePage, optionsPage, recoveryWalletPage, passwordPage, donationPage, finishPage ] + } + property string currentPath: "create_wallet" + property var pages: paths[currentPath] signal useMoneroClicked() border.color: "#DBDBDB" @@ -51,20 +55,8 @@ Rectangle { print ("switchpage: start: currentPage: ", currentPage); if (currentPage > 0 || currentPage < pages.length - 1) { - pages[currentPage].opacity = 0 - var step_value = next ? 1 : -1 - // special case - we stepping backward from password page: - // previous page "createWallet" or "recoveryWallet" - if (!next) { - print ("stepping back: current page: ", currentPage); - if ((pages[currentPage] === passwordPage && path === "create_walled") - || (pages[currentPage] === recoveryWalletPage) ) { - step_value *= 2; - } - } - currentPage += step_value pages[currentPage].opacity = 1; handlePageChanged(); @@ -73,25 +65,38 @@ Rectangle { } function handlePageChanged() { - // disable "next" button until passwords match - if (pages[currentPage] === passwordPage) { + switch (pages[currentPage]) { + case passwordPage: + // disable "next" button until passwords match nextButton.enabled = passwordPage.passwordValid; - } else if (pages[currentPage] === finishPage) { + if (currentPath === "create_wallet") { + passwordPage.titleText = qsTr("Now that your wallet has been created, please set a password for the wallet") + } else { + passwordPage.titleText = qsTr("Now that your wallet has been restored, please set a password for the wallet") + } + break; + case finishPage: // display settings summary finishPage.updateSettingsSummary(); - nextButton.visible = false - } else { - var enableButton = pages[currentPage] !== optionsPage; - nextButton.visible = nextButton.enabled = enableButton - print ("nextButtonVisible: ", enableButton) + nextButton.visible = false; + break; + case recoveryWalletPage: + // TODO: disable "next button" until 25 words private key entered + // nextButton.enabled = false; + break + default: + var nextButtonVisible = pages[currentPage] !== optionsPage; + //nextButton.visible = nextButton.enabled = nextButtonVisible; } + } function openCreateWalletPage() { print ("show create wallet page"); pages[currentPage].opacity = 0; createWalletPage.opacity = 1 - path = "create_wallet"; + currentPath = "create_wallet" + pages = paths[currentPath] currentPage = pages.indexOf(createWalletPage) handlePageChanged() } @@ -100,7 +105,8 @@ Rectangle { print ("show recovery wallet page"); pages[currentPage].opacity = 0 recoveryWalletPage.opacity = 1 - path = "recovery_wallet" + currentPath = "recovery_wallet" + pages = paths[currentPath] currentPage = pages.indexOf(recoveryWalletPage) handlePageChanged() } @@ -134,8 +140,6 @@ Rectangle { } - - WizardWelcome { id: welcomePage anchors.top: parent.top @@ -246,7 +250,7 @@ Rectangle { shadowPressedColor: "#B32D00" releasedColor: "#FF6C3C" pressedColor: "#FF4304" - visible: parent.pages[currentPage] === finishPage + visible: parent.paths[currentPath][currentPage] === finishPage onClicked: wizard.useMoneroClicked() } } diff --git a/wizard/WizardPassword.qml b/wizard/WizardPassword.qml index d12409ea..eb9d9c2c 100644 --- a/wizard/WizardPassword.qml +++ b/wizard/WizardPassword.qml @@ -28,10 +28,15 @@ import QtQuick 2.2 import "../components" +import "utils.js" as Utils Item { opacity: 0 visible: false + property bool passwordValid : passwordItem.password != '' + && passwordItem.password === retypePasswordItem.password + + property alias titleText: titleText.text Behavior on opacity { NumberAnimation { duration: 100; easing.type: Easing.InQuad } } @@ -40,12 +45,15 @@ Item { function handlePassword() { // allow to forward step only if passwords match - // TODO: update password strength wizard.nextButton.enabled = passwordItem.password === retypePasswordItem.password + // scorePassword returns value from 1..100 + var strength = Utils.scorePassword(passwordItem.password) + // privacyLevel component uses 1..13 scale + privacyLevel.fillLevel = Utils.mapScope(1, 100, 1, 13, strength) } - property bool passwordValid : passwordItem.password != '' - && passwordItem.password === retypePasswordItem.password + + Row { @@ -84,6 +92,7 @@ Item { spacing: 24 Text { + id: titleText anchors.left: parent.left width: headerColumn.width - dotsRow.width - 16 font.family: "Arial" @@ -91,7 +100,7 @@ Item { wrapMode: Text.Wrap //renderType: Text.NativeRendering color: "#3F3F3F" - text: qsTr("Now that your wallet has been created, please set a password for the wallet") + } Text { diff --git a/wizard/WizardRecoveryWallet.qml b/wizard/WizardRecoveryWallet.qml index 46af302b..9093c59a 100644 --- a/wizard/WizardRecoveryWallet.qml +++ b/wizard/WizardRecoveryWallet.qml @@ -55,5 +55,9 @@ Item { wordsTextItem.tipTextVisible: false wordsTextItem.memoTextReadOnly: false wordsTextItem.memoText: "" + wordsTextItem.onMemoTextChanged: { + var wordsArray = wordsTextItem.memoText.trim().split(" ") + //wizard.nextButton.enabled = wordsArray.length === 25 + } } } From 8bc411c598801642d6f39bc9a797c7c547c3c707 Mon Sep 17 00:00:00 2001 From: Ilya Kitaev Date: Mon, 8 Feb 2016 10:12:12 +0300 Subject: [PATCH 09/87] wordings and text alignment according to design --- wizard/WizardFinish.qml | 2 ++ wizard/WizardManageWalletUI.qml | 2 ++ wizard/WizardOptions.qml | 4 +++- wizard/WizardPassword.qml | 2 ++ 4 files changed, 9 insertions(+), 1 deletion(-) diff --git a/wizard/WizardFinish.qml b/wizard/WizardFinish.qml index 7902c0b2..62975d8c 100644 --- a/wizard/WizardFinish.qml +++ b/wizard/WizardFinish.qml @@ -94,6 +94,7 @@ Item { font.family: "Arial" font.pixelSize: 28 wrapMode: Text.Wrap + horizontalAlignment: Text.AlignHCenter //renderType: Text.NativeRendering color: "#3F3F3F" text: qsTr("You’re all setup!") @@ -107,6 +108,7 @@ Item { font.pixelSize: 18 wrapMode: Text.Wrap textFormat: Text.RichText + horizontalAlignment: Text.AlignHCenter //renderType: Text.NativeRendering color: "#4A4646" } diff --git a/wizard/WizardManageWalletUI.qml b/wizard/WizardManageWalletUI.qml index a00814a9..651c7081 100644 --- a/wizard/WizardManageWalletUI.qml +++ b/wizard/WizardManageWalletUI.qml @@ -86,6 +86,7 @@ Item { font.family: "Arial" font.pixelSize: 28 wrapMode: Text.Wrap + horizontalAlignment: Text.AlignHCenter //renderType: Text.NativeRendering color: "#3F3F3F" } @@ -96,6 +97,7 @@ Item { font.family: "Arial" font.pixelSize: 18 wrapMode: Text.Wrap + horizontalAlignment: Text.AlignHCenter //renderType: Text.NativeRendering color: "#4A4646" text: qsTr("This is the name of your wallet. You can change it to a different name if you’d like:") diff --git a/wizard/WizardOptions.qml b/wizard/WizardOptions.qml index a6718853..621cac78 100644 --- a/wizard/WizardOptions.qml +++ b/wizard/WizardOptions.qml @@ -58,7 +58,8 @@ Item { //renderType: Text.NativeRendering color: "#3F3F3F" wrapMode: Text.Wrap - text: qsTr("I want") + horizontalAlignment: Text.AlignHCenter + text: qsTr("Welcome to Monero!") } Text { @@ -69,6 +70,7 @@ Item { //renderType: Text.NativeRendering color: "#4A4646" wrapMode: Text.Wrap + horizontalAlignment: Text.AlignHCenter text: qsTr("Please select one of the following options:") } } diff --git a/wizard/WizardPassword.qml b/wizard/WizardPassword.qml index eb9d9c2c..d3d033aa 100644 --- a/wizard/WizardPassword.qml +++ b/wizard/WizardPassword.qml @@ -98,6 +98,7 @@ Item { font.family: "Arial" font.pixelSize: 28 wrapMode: Text.Wrap + horizontalAlignment: Text.AlignHCenter //renderType: Text.NativeRendering color: "#3F3F3F" @@ -111,6 +112,7 @@ Item { wrapMode: Text.Wrap //renderType: Text.NativeRendering color: "#4A4646" + horizontalAlignment: Text.AlignHCenter text: qsTr("Note that this password cannot be recovered, and if forgotten you will need to restore your wallet from the mnemonic seed you were just given

Your password will be used to protect your wallet and to confirm actions, so make sure that your password is sufficiently secure.") } From 16020ae2a896633bc197cb3aca624c72329dd994 Mon Sep 17 00:00:00 2001 From: Ilya Kitaev Date: Mon, 8 Feb 2016 10:15:25 +0300 Subject: [PATCH 10/87] Wizard: "next button" visibility in backward direction fixed --- wizard/WizardMain.qml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/wizard/WizardMain.qml b/wizard/WizardMain.qml index 6b25be1d..eeefbf17 100644 --- a/wizard/WizardMain.qml +++ b/wizard/WizardMain.qml @@ -65,6 +65,8 @@ Rectangle { } function handlePageChanged() { + var nextButtonVisible = pages[currentPage] !== optionsPage; + nextButton.visible = nextButtonVisible; switch (pages[currentPage]) { case passwordPage: // disable "next" button until passwords match @@ -85,8 +87,7 @@ Rectangle { // nextButton.enabled = false; break default: - var nextButtonVisible = pages[currentPage] !== optionsPage; - //nextButton.visible = nextButton.enabled = nextButtonVisible; + } } From e555631b40972b0264962f5bbd780ddd3aef5a7c Mon Sep 17 00:00:00 2001 From: Ilya Kitaev Date: Mon, 8 Feb 2016 10:58:01 +0300 Subject: [PATCH 11/87] wallet directrory in "Documents" for Windows and in "home" for *nix --- main.cpp | 15 +++++++++++++++ wizard/WizardManageWalletUI.qml | 6 ++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/main.cpp b/main.cpp index ecc8b626..af429df3 100644 --- a/main.cpp +++ b/main.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include "clipboardAdapter.h" #include "filter.h" #include "oscursor.h" @@ -46,6 +47,20 @@ int main(int argc, char *argv[]) OSCursor cursor; engine.rootContext()->setContextProperty("globalCursor", &cursor); +// export to QML monero accounts root directory +// wizard is talking about where +// to save the wallet file (.keys, .bin), they have to be user-accessible for +// backups - I reckon we save that in My Documents\Monero Accounts\ on +// Windows, ~/Monero Accounts/ on nix / osx +#ifdef Q_OS_WIN + QStringList moneroAccountsRootDir = QStandardPaths::standardLocations(QStandardPaths::DocumentsLocation); +#elif defined(Q_OS_UNIX) + QStringList moneroAccountsRootDir = QStandardPaths::standardLocations(QStandardPaths::HomeLocation); +#endif + if (!moneroAccountsRootDir.empty()) { + engine.rootContext()->setContextProperty("moneroAccountsDir", moneroAccountsRootDir.at(0) + "/Monero Accounts"); + } + engine.rootContext()->setContextProperty("applicationDirectory", QApplication::applicationDirPath()); engine.load(QUrl(QStringLiteral("qrc:///main.qml"))); QObject *rootObject = engine.rootObjects().first(); diff --git a/wizard/WizardManageWalletUI.qml b/wizard/WizardManageWalletUI.qml index 651c7081..6ea9ea44 100644 --- a/wizard/WizardManageWalletUI.qml +++ b/wizard/WizardManageWalletUI.qml @@ -204,10 +204,12 @@ Item { color: "#6B0072" verticalAlignment: Text.AlignVCenter selectByMouse: true - text: "~/.monero/mywallet/" + + text: moneroAccountsDir + "/My Wallet" onFocusChanged: { if(focus) { - fileDialog.visible = true + fileDialog.folder = text + fileDialog.open() } } } From 921d16a458581d01f55a7ba2f6076d1ba6daaacf Mon Sep 17 00:00:00 2001 From: Ilya Kitaev Date: Wed, 10 Feb 2016 21:36:35 +0300 Subject: [PATCH 12/87] Forgotten file with password strength related functions --- wizard/utils.js | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 wizard/utils.js diff --git a/wizard/utils.js b/wizard/utils.js new file mode 100644 index 00000000..6d16951d --- /dev/null +++ b/wizard/utils.js @@ -0,0 +1,39 @@ + +.pragma library + +// grabbed from SO answer page: http://stackoverflow.com/questions/948172/password-strength-meter + +function scorePassword(pass) { + var score = 0; + if (!pass) + return score; + + // award every unique letter until 5 repetitions + var letters = {}; + for (var i=0; i Date: Tue, 23 Feb 2016 18:59:26 +0300 Subject: [PATCH 13/87] started integrating wallet library --- Wallet.cpp | 6 ++++++ Wallet.h | 17 +++++++++++++++++ Wallet2Service.cpp | 6 ++++++ Wallet2Service.h | 17 +++++++++++++++++ WalletManager.cpp | 6 ++++++ WalletManager.h | 17 +++++++++++++++++ lang/languages.xml | 22 +++++++++++----------- main.cpp | 5 +++++ monero-core.pro | 13 +++++++++++-- wizard/WizardCreateWallet.qml | 7 ++++++- wizard/WizardMain.qml | 2 ++ wizard/WizardMemoTextInput.qml | 2 +- 12 files changed, 105 insertions(+), 15 deletions(-) create mode 100644 Wallet.cpp create mode 100644 Wallet.h create mode 100644 Wallet2Service.cpp create mode 100644 Wallet2Service.h create mode 100644 WalletManager.cpp create mode 100644 WalletManager.h diff --git a/Wallet.cpp b/Wallet.cpp new file mode 100644 index 00000000..89e448fd --- /dev/null +++ b/Wallet.cpp @@ -0,0 +1,6 @@ +#include "Wallet.h" + +Wallet::Wallet(QObject *parent) : QObject(parent) +{ + +} diff --git a/Wallet.h b/Wallet.h new file mode 100644 index 00000000..130712e6 --- /dev/null +++ b/Wallet.h @@ -0,0 +1,17 @@ +#ifndef WALLET_H +#define WALLET_H + +#include + +class Wallet : public QObject +{ + Q_OBJECT +public: + explicit Wallet(QObject *parent = 0); + +signals: + +public slots: +}; + +#endif // WALLET_H \ No newline at end of file diff --git a/Wallet2Service.cpp b/Wallet2Service.cpp new file mode 100644 index 00000000..9637d31a --- /dev/null +++ b/Wallet2Service.cpp @@ -0,0 +1,6 @@ +#include "Wallet2Service.h" + +Wallet2Service::Wallet2Service(QObject *parent) : QObject(parent) +{ + +} diff --git a/Wallet2Service.h b/Wallet2Service.h new file mode 100644 index 00000000..9ed5ad9b --- /dev/null +++ b/Wallet2Service.h @@ -0,0 +1,17 @@ +#ifndef WALLET2SERVICE_H +#define WALLET2SERVICE_H + +#include + +class Wallet2Service : public QObject +{ + Q_OBJECT +public: + explicit Wallet2Service(QObject *parent = 0); + +signals: + +public slots: +}; + +#endif // WALLET2SERVICE_H \ No newline at end of file diff --git a/WalletManager.cpp b/WalletManager.cpp new file mode 100644 index 00000000..7812ed34 --- /dev/null +++ b/WalletManager.cpp @@ -0,0 +1,6 @@ +#include "WalletManager.h" + +WalletManager::WalletManager(QObject *parent) : QObject(parent) +{ + +} diff --git a/WalletManager.h b/WalletManager.h new file mode 100644 index 00000000..b6153385 --- /dev/null +++ b/WalletManager.h @@ -0,0 +1,17 @@ +#ifndef WALLETMANAGER_H +#define WALLETMANAGER_H + +#include + +class WalletManager : public QObject +{ + Q_OBJECT +public: + explicit WalletManager(QObject *parent = 0); + +signals: + +public slots: +}; + +#endif // WALLETMANAGER_H \ No newline at end of file diff --git a/lang/languages.xml b/lang/languages.xml index eacfcad5..efb84c9c 100644 --- a/lang/languages.xml +++ b/lang/languages.xml @@ -1,13 +1,13 @@ - - - - - - - - - - - \ No newline at end of file + + + + + + + + + + + diff --git a/main.cpp b/main.cpp index af429df3..e3917f80 100644 --- a/main.cpp +++ b/main.cpp @@ -33,6 +33,9 @@ #include "clipboardAdapter.h" #include "filter.h" #include "oscursor.h" +#include "WalletManager.h" +#include "Wallet.h" + int main(int argc, char *argv[]) { @@ -41,11 +44,13 @@ int main(int argc, char *argv[]) app.installEventFilter(eventFilter); qmlRegisterType("moneroComponents", 1, 0, "Clipboard"); + qmlRegisterType("moneroWallet", 1, 0, "Wallet"); QQmlApplicationEngine engine; OSCursor cursor; engine.rootContext()->setContextProperty("globalCursor", &cursor); + engine.rootContext()->setContextProperty("walletManager", WalletManager::instance()); // export to QML monero accounts root directory // wizard is talking about where diff --git a/monero-core.pro b/monero-core.pro index dc2a500a..f021449c 100644 --- a/monero-core.pro +++ b/monero-core.pro @@ -2,16 +2,22 @@ TEMPLATE = app QT += qml quick widgets + HEADERS += \ filter.h \ clipboardAdapter.h \ - oscursor.h + oscursor.h \ + Wallet2Adaptor.h \ + WalletManager.h \ + Wallet.h SOURCES += main.cpp \ filter.cpp \ clipboardAdapter.cpp \ - oscursor.cpp + oscursor.cpp \ + WalletManager.cpp \ + Wallet.cpp lupdate_only { SOURCES = *.qml \ @@ -63,6 +69,9 @@ OTHER_FILES += \ monero-core_de.ts \ monero-core_en.ts +DISTFILES += \ + notes.txt + diff --git a/wizard/WizardCreateWallet.qml b/wizard/WizardCreateWallet.qml index 65e2e989..8ce4e760 100644 --- a/wizard/WizardCreateWallet.qml +++ b/wizard/WizardCreateWallet.qml @@ -46,6 +46,12 @@ Item { settingsObject['wallet_path'] = uiItem.walletPath } + function createWallet(settingsObject) { + // print ("Language: " + settingsObject.language); + var wallet = walletManager.createWallet(uiItem.accountNameText, "", settingsObject.language); + uiItem.wordsTextItem.memoText = wallet.seed + } + WizardManageWalletUI { id: uiItem titleText: qsTr("A new wallet has been created for you") @@ -53,6 +59,5 @@ Item { wordsTextItem.clipboardButtonVisible: true wordsTextItem.tipTextVisible: true wordsTextItem.memoTextReadOnly: true - } } diff --git a/wizard/WizardMain.qml b/wizard/WizardMain.qml index eeefbf17..5f51cec8 100644 --- a/wizard/WizardMain.qml +++ b/wizard/WizardMain.qml @@ -99,7 +99,9 @@ Rectangle { currentPath = "create_wallet" pages = paths[currentPath] currentPage = pages.indexOf(createWalletPage) + createWalletPage.createWallet(settings) handlePageChanged() + } function openRecoveryWalletPage() { diff --git a/wizard/WizardMemoTextInput.qml b/wizard/WizardMemoTextInput.qml index 89eadb15..39f9211b 100644 --- a/wizard/WizardMemoTextInput.qml +++ b/wizard/WizardMemoTextInput.qml @@ -25,7 +25,7 @@ Column { TextEdit { id: memoTextInput textMargin: 8 - text: "bound class paint gasp task soul forgot past pleasure physical circle appear shore bathroom glove women crap busy beauty bliss idea give needle burden" + text: "" font.family: "Arial" font.pointSize: 16 wrapMode: TextInput.Wrap From 625041df1823f2ce98b53fa054bab47876717c15 Mon Sep 17 00:00:00 2001 From: Ilya Kitaev Date: Wed, 24 Feb 2016 13:25:20 +0300 Subject: [PATCH 14/87] integrating cpp wallet mockups with QML --- Wallet.cpp | 27 +++++++++- Wallet.h | 18 +++++-- Wallet2Service.cpp | 6 --- Wallet2Service.h | 17 ------- WalletManager.cpp | 87 +++++++++++++++++++++++++++++++++ WalletManager.h | 15 +++++- main.cpp | 7 ++- monero-core.pro | 1 - wizard/WizardCreateWallet.qml | 23 +++++++-- wizard/WizardMain.qml | 2 + wizard/WizardManageWalletUI.qml | 5 +- 11 files changed, 172 insertions(+), 36 deletions(-) delete mode 100644 Wallet2Service.cpp delete mode 100644 Wallet2Service.h diff --git a/Wallet.cpp b/Wallet.cpp index 89e448fd..5507f531 100644 --- a/Wallet.cpp +++ b/Wallet.cpp @@ -1,6 +1,31 @@ #include "Wallet.h" -Wallet::Wallet(QObject *parent) : QObject(parent) +struct WalletImpl +{ + // TODO +}; + + + +Wallet::Wallet(QObject *parent) + : QObject(parent) { } + + +QString Wallet::getSeed() const +{ + return "bound class paint gasp task soul forgot past pleasure physical circle " + " appear shore bathroom glove women crap busy beauty bliss idea give needle burden"; +} + +QString Wallet::getSeedLanguage() const +{ + return "English"; +} + +void Wallet::setSeedLaguage(const QString &lang) +{ + // TODO; +} diff --git a/Wallet.h b/Wallet.h index 130712e6..14018a66 100644 --- a/Wallet.h +++ b/Wallet.h @@ -3,15 +3,27 @@ #include +struct WalletImpl; + class Wallet : public QObject { Q_OBJECT + Q_PROPERTY(QString seed READ getSeed) public: explicit Wallet(QObject *parent = 0); - + QString getSeed() const; + QString getSeedLanguage() const; + void setSeedLaguage(const QString &lang); signals: - public slots: + +private: + + + friend class WalletManager; + WalletImpl * m_pimpl; + + }; -#endif // WALLET_H \ No newline at end of file +#endif // WALLET_H diff --git a/Wallet2Service.cpp b/Wallet2Service.cpp deleted file mode 100644 index 9637d31a..00000000 --- a/Wallet2Service.cpp +++ /dev/null @@ -1,6 +0,0 @@ -#include "Wallet2Service.h" - -Wallet2Service::Wallet2Service(QObject *parent) : QObject(parent) -{ - -} diff --git a/Wallet2Service.h b/Wallet2Service.h deleted file mode 100644 index 9ed5ad9b..00000000 --- a/Wallet2Service.h +++ /dev/null @@ -1,17 +0,0 @@ -#ifndef WALLET2SERVICE_H -#define WALLET2SERVICE_H - -#include - -class Wallet2Service : public QObject -{ - Q_OBJECT -public: - explicit Wallet2Service(QObject *parent = 0); - -signals: - -public slots: -}; - -#endif // WALLET2SERVICE_H \ No newline at end of file diff --git a/WalletManager.cpp b/WalletManager.cpp index 7812ed34..992dfa4d 100644 --- a/WalletManager.cpp +++ b/WalletManager.cpp @@ -1,6 +1,93 @@ #include "WalletManager.h" +#include "Wallet.h" +#include +#include +#include +#include +#include + +WalletManager * WalletManager::m_instance = nullptr; + + +namespace { + bool createFileWrapper(const QString &filename) + { + QFile file(filename); + // qDebug("%s: about to create file: %s", __FUNCTION__, qPrintable(filename)); + bool result = file.open(QIODevice::WriteOnly); + if (!result ){ + qWarning("%s: error creating file '%s' : '%s'", + __FUNCTION__, + qPrintable(filename), + qPrintable(file.errorString())); + } + return result; + } +} + + +WalletManager *WalletManager::instance() +{ + if (!m_instance) { + m_instance = new WalletManager; + } + + return m_instance; +} + +Wallet *WalletManager::createWallet(const QString &path, const QString &password, + const QString &language) +{ + Wallet * wallet = new Wallet(this); + // Create dummy files for testing + QFileInfo fi(path); + QDir tempDir; + tempDir.mkpath(fi.absolutePath()); + createFileWrapper(path); + createFileWrapper(path + ".keys"); + createFileWrapper(path + ".address.txt"); + return wallet; +} + +Wallet *WalletManager::openWallet(const QString &path, const QString &language) +{ + return nullptr; +} + +bool WalletManager::moveWallet(const QString &src, const QString &dst_) +{ + QFile walletFile(src); + if (!walletFile.exists()) { + qWarning("%s: source file [%s] doesn't exits", __FUNCTION__, + qPrintable(src)); + return false; + } + QString dst = QUrl(dst_).toLocalFile(); + QString walletKeysFile = src + ".keys"; + QString walletAddressFile = src + ".address.txt"; + + QString dstWalletKeysFile = dst + ".keys"; + QString dstWalletAddressFile = dst + ".address.txt"; + + if (!walletFile.rename(dst)) { + qWarning("Error renaming file: '%s' to '%s' : (%s)", + qPrintable(src), + qPrintable(dst), + qPrintable(walletFile.errorString())); + return false; + } + QFile::rename(walletKeysFile, dstWalletKeysFile); + QFile::rename(walletAddressFile, dstWalletAddressFile); + + return QFile::exists(dst) && QFile::exists(dstWalletKeysFile) + && QFile::exists(dstWalletAddressFile); + + +} WalletManager::WalletManager(QObject *parent) : QObject(parent) { } + + diff --git a/WalletManager.h b/WalletManager.h index b6153385..ffb498d7 100644 --- a/WalletManager.h +++ b/WalletManager.h @@ -3,15 +3,26 @@ #include +class Wallet; + class WalletManager : public QObject { Q_OBJECT public: - explicit WalletManager(QObject *parent = 0); + static WalletManager * instance(); + Q_INVOKABLE Wallet * createWallet(const QString &path, const QString &password, + const QString &language); + Q_INVOKABLE Wallet * openWallet(const QString &path, const QString &language); + Q_INVOKABLE bool moveWallet(const QString &src, const QString &dst); signals: public slots: + +private: + explicit WalletManager(QObject *parent = 0); + static WalletManager * m_instance; + }; -#endif // WALLETMANAGER_H \ No newline at end of file +#endif // WALLETMANAGER_H diff --git a/main.cpp b/main.cpp index e3917f80..0e996b84 100644 --- a/main.cpp +++ b/main.cpp @@ -57,13 +57,18 @@ int main(int argc, char *argv[]) // to save the wallet file (.keys, .bin), they have to be user-accessible for // backups - I reckon we save that in My Documents\Monero Accounts\ on // Windows, ~/Monero Accounts/ on nix / osx + #ifdef Q_OS_WIN QStringList moneroAccountsRootDir = QStandardPaths::standardLocations(QStandardPaths::DocumentsLocation); #elif defined(Q_OS_UNIX) QStringList moneroAccountsRootDir = QStandardPaths::standardLocations(QStandardPaths::HomeLocation); #endif + if (!moneroAccountsRootDir.empty()) { - engine.rootContext()->setContextProperty("moneroAccountsDir", moneroAccountsRootDir.at(0) + "/Monero Accounts"); + QString moneroAccountsDir = moneroAccountsRootDir.at(0) + "/Monero Accounts"; + QDir tempDir; + tempDir.mkpath(moneroAccountsDir); + engine.rootContext()->setContextProperty("moneroAccountsDir", moneroAccountsDir); } engine.rootContext()->setContextProperty("applicationDirectory", QApplication::applicationDirPath()); diff --git a/monero-core.pro b/monero-core.pro index f021449c..bee7eb17 100644 --- a/monero-core.pro +++ b/monero-core.pro @@ -7,7 +7,6 @@ HEADERS += \ filter.h \ clipboardAdapter.h \ oscursor.h \ - Wallet2Adaptor.h \ WalletManager.h \ Wallet.h diff --git a/wizard/WizardCreateWallet.qml b/wizard/WizardCreateWallet.qml index 8ce4e760..91b7447e 100644 --- a/wizard/WizardCreateWallet.qml +++ b/wizard/WizardCreateWallet.qml @@ -44,12 +44,29 @@ Item { settingsObject['account_name'] = uiItem.accountNameText settingsObject['words'] = uiItem.wordsTexttext settingsObject['wallet_path'] = uiItem.walletPath + + + var new_wallet_filename = settingsObject.wallet_path + "/" + + settingsObject.account_name; + + // moving wallet files to the new destination, if user changed it + if (new_wallet_filename !== settingsObject.wallet_filename) { + walletManager.moveWallet(settingsObject.wallet_filename, new_wallet_filename); + } } function createWallet(settingsObject) { - // print ("Language: " + settingsObject.language); - var wallet = walletManager.createWallet(uiItem.accountNameText, "", settingsObject.language); - uiItem.wordsTextItem.memoText = wallet.seed + var wallet_filename = uiItem.walletPath + "/" + uiItem.accountNameText + if (typeof settingsObject.wallet === 'undefined') { + var wallet = walletManager.createWallet(wallet_filename, "", settingsObject.language) + uiItem.wordsTextItem.memoText = wallet.seed + // saving wallet in "global" settings object + // TODO: wallet should have a property pointing to the file where it stored or loaded from + settingsObject.wallet = wallet + } else { + print("wallet already created. we just stepping back"); + } + settingsObject.wallet_filename = wallet_filename } WizardManageWalletUI { diff --git a/wizard/WizardMain.qml b/wizard/WizardMain.qml index 5f51cec8..51e0baf3 100644 --- a/wizard/WizardMain.qml +++ b/wizard/WizardMain.qml @@ -67,6 +67,7 @@ Rectangle { function handlePageChanged() { var nextButtonVisible = pages[currentPage] !== optionsPage; nextButton.visible = nextButtonVisible; + print ("next button visible: " + nextButtonVisible); switch (pages[currentPage]) { case passwordPage: // disable "next" button until passwords match @@ -87,6 +88,7 @@ Rectangle { // nextButton.enabled = false; break default: + nextButton.enabled = true } diff --git a/wizard/WizardManageWalletUI.qml b/wizard/WizardManageWalletUI.qml index 6ea9ea44..ce035f0a 100644 --- a/wizard/WizardManageWalletUI.qml +++ b/wizard/WizardManageWalletUI.qml @@ -183,9 +183,10 @@ Item { FileDialog { id: fileDialog selectMultiple: false - title: "Please choose a file" + selectFolder: true + title: "Please choose a directory" onAccepted: { - fileUrlInput.text = fileDialog.fileUrl + fileUrlInput.text = fileDialog.folder fileDialog.visible = false } onRejected: { From 7d9306ca1a661da015c59a0a991e028aeec0ee44 Mon Sep 17 00:00:00 2001 From: Ilya Kitaev Date: Mon, 29 Feb 2016 17:39:39 +0300 Subject: [PATCH 15/87] Qt wrapper for libwallet - in-progress --- Wallet.cpp | 175 ++++++++++++++++++++++++++++++-- Wallet.h | 33 ++++-- WalletManager.cpp | 66 +++++++----- WalletManager.h | 24 ++++- wizard/WizardCreateWallet.qml | 9 +- wizard/WizardFinish.qml | 2 +- wizard/WizardPassword.qml | 6 +- wizard/WizardRecoveryWallet.qml | 4 + 8 files changed, 275 insertions(+), 44 deletions(-) diff --git a/Wallet.cpp b/Wallet.cpp index 5507f531..61998ab5 100644 --- a/Wallet.cpp +++ b/Wallet.cpp @@ -1,11 +1,82 @@ #include "Wallet.h" +#include +#include +#include +#include + +namespace { + QString TEST_SEED = "bound class paint gasp task soul forgot past pleasure physical circle " + " appear shore bathroom glove women crap busy beauty bliss idea give needle burden"; + + namespace { + bool createFileWrapper(const QString &filename) + { + QFile file(filename); + // qDebug("%s: about to create file: %s", __FUNCTION__, qPrintable(filename)); + bool result = file.open(QIODevice::WriteOnly); + if (!result ){ + qWarning("%s: error creating file '%s' : '%s'", + __FUNCTION__, + qPrintable(filename), + qPrintable(file.errorString())); + } + return result; + } + } + +} struct WalletImpl { - // TODO + + QString basename() const; + void setBasename(const QString &name); + + QString keysName() const; + QString addressName() const; + +// Bitmonero::Wallet * m_walletImpl; + QString m_basename; + QString m_seed; + QString m_password; + QString m_language; + + static QString keysName(const QString &basename); + static QString addressName(const QString &basename); + }; +QString WalletImpl::basename() const +{ + return m_basename; +} + +void WalletImpl::setBasename(const QString &name) +{ + m_basename = name; +} + +QString WalletImpl::keysName() const +{ + return keysName(m_basename); +} + +QString WalletImpl::addressName() const +{ + return addressName(m_basename); +} + +QString WalletImpl::keysName(const QString &basename) +{ + return basename + ".keys"; +} + +QString WalletImpl::addressName(const QString &basename) +{ + return basename + ".address.txt"; +} + Wallet::Wallet(QObject *parent) : QObject(parent) @@ -13,11 +84,9 @@ Wallet::Wallet(QObject *parent) } - QString Wallet::getSeed() const { - return "bound class paint gasp task soul forgot past pleasure physical circle " - " appear shore bathroom glove women crap busy beauty bliss idea give needle burden"; + return m_pimpl->m_seed; } QString Wallet::getSeedLanguage() const @@ -25,7 +94,101 @@ QString Wallet::getSeedLanguage() const return "English"; } -void Wallet::setSeedLaguage(const QString &lang) +//void Wallet::setSeedLaguage(const QString &lang) +//{ +// // TODO: call libwallet's appropriate method +//} + +bool Wallet::setPassword(const QString &password) { - // TODO; + // set/change password implies: + // recovery wallet with existing path, seed and lang + qDebug("%s: recovering wallet with path=%s, seed=%s, lang=%s and new password=%s", + __FUNCTION__, + qPrintable(this->getBasename()), + qPrintable(this->getSeed()), + qPrintable(this->getSeedLanguage()), + qPrintable(password)); + return true; } + +QString Wallet::getPassword() const +{ + return m_pimpl->m_password; +} + +bool Wallet::rename(const QString &name) +{ + + QString dst = QUrl(name).toLocalFile(); + + if (dst.isEmpty()) + dst = name; + + qDebug("%s: renaming '%s' to '%s'", + __FUNCTION__, + qPrintable(m_pimpl->basename()), + qPrintable(dst)); + + QString walletKeysFile = m_pimpl->keysName(); + QString walletAddressFile = m_pimpl->addressName(); + + QString dstWalletKeysFile = WalletImpl::keysName(dst); + QString dstWalletAddressFile = WalletImpl::addressName(dst); + + QFile walletFile(this->getBasename()); + + if (!walletFile.rename(dst)) { + qWarning("Error renaming file: '%s' to '%s' : (%s)", + qPrintable(m_pimpl->basename()), + qPrintable(dst), + qPrintable(walletFile.errorString())); + return false; + } + QFile::rename(walletKeysFile, dstWalletKeysFile); + QFile::rename(walletAddressFile, dstWalletAddressFile); + + bool result = QFile::exists(dst) && QFile::exists(dstWalletKeysFile) + && QFile::exists(dstWalletAddressFile); + + if (result) { + m_pimpl->m_basename = dst; + } + + return result; +} + +QString Wallet::getBasename() const +{ + return m_pimpl->basename(); +} + +int Wallet::error() const +{ + return 0; +} + +QString Wallet::errorString() const +{ + return m_pimpl->m_seed; +} + +Wallet::Wallet(const QString &path, const QString &password, const QString &language) +{ + m_pimpl = new WalletImpl; + m_pimpl->m_basename = path; + m_pimpl->m_password = password; + m_pimpl->m_language = language; + m_pimpl->m_seed = TEST_SEED; + + // Create dummy files for testing + QFileInfo fi(path); + QDir tempDir; + tempDir.mkpath(fi.absolutePath()); + createFileWrapper(m_pimpl->basename()); + createFileWrapper(m_pimpl->keysName()); + createFileWrapper(m_pimpl->addressName()); +} + + + diff --git a/Wallet.h b/Wallet.h index 14018a66..221d3d43 100644 --- a/Wallet.h +++ b/Wallet.h @@ -4,26 +4,41 @@ #include struct WalletImpl; - class Wallet : public QObject { Q_OBJECT Q_PROPERTY(QString seed READ getSeed) public: explicit Wallet(QObject *parent = 0); - QString getSeed() const; - QString getSeedLanguage() const; - void setSeedLaguage(const QString &lang); -signals: -public slots: + + //! returns mnemonic seed + Q_INVOKABLE QString getSeed() const; + + //! returns seed language + Q_INVOKABLE QString getSeedLanguage() const; + + + //! changes the password using existing parameters (path, seed, seed lang) + Q_INVOKABLE bool setPassword(const QString &password); + //! returns curret wallet password + Q_INVOKABLE QString getPassword() const; + + //! renames/moves wallet files + Q_INVOKABLE bool rename(const QString &name); + + //! returns current wallet name (basename, as wallet consists of several files) + Q_INVOKABLE QString getBasename() const; + + Q_INVOKABLE int error() const; + Q_INVOKABLE QString errorString() const; private: + Wallet(const QString &path, const QString &password, const QString &language); - +private: friend class WalletManager; + //! pimpl wrapper for libwallet; WalletImpl * m_pimpl; - - }; #endif // WALLET_H diff --git a/WalletManager.cpp b/WalletManager.cpp index 992dfa4d..4a2f0c8f 100644 --- a/WalletManager.cpp +++ b/WalletManager.cpp @@ -9,21 +9,7 @@ WalletManager * WalletManager::m_instance = nullptr; -namespace { - bool createFileWrapper(const QString &filename) - { - QFile file(filename); - // qDebug("%s: about to create file: %s", __FUNCTION__, qPrintable(filename)); - bool result = file.open(QIODevice::WriteOnly); - if (!result ){ - qWarning("%s: error creating file '%s' : '%s'", - __FUNCTION__, - qPrintable(filename), - qPrintable(file.errorString())); - } - return result; - } -} + WalletManager *WalletManager::instance() @@ -38,24 +24,40 @@ WalletManager *WalletManager::instance() Wallet *WalletManager::createWallet(const QString &path, const QString &password, const QString &language) { - Wallet * wallet = new Wallet(this); - // Create dummy files for testing QFileInfo fi(path); - QDir tempDir; - tempDir.mkpath(fi.absolutePath()); - createFileWrapper(path); - createFileWrapper(path + ".keys"); - createFileWrapper(path + ".address.txt"); + if (fi.exists()) { + qCritical("%s: already exists", __FUNCTION__); + // TODO: set error and error string + // return nullptr; + } + Wallet * wallet = new Wallet(path, password, language); return wallet; } -Wallet *WalletManager::openWallet(const QString &path, const QString &language) +Wallet *WalletManager::openWallet(const QString &path, const QString &language, const QString &password) { + QFileInfo fi(path); + if (fi.exists()) { + qCritical("%s: not exists", __FUNCTION__); + // TODO: set error and error string + // return nullptr; + } + // TODO: call the libwallet api here; + Wallet * wallet = new Wallet(path, password, language); + + return wallet; +} + +Wallet *WalletManager::recoveryWallet(const QString &path, const QString &memo, const QString &language) +{ + // TODO: call the libwallet api here; + return nullptr; } bool WalletManager::moveWallet(const QString &src, const QString &dst_) { + // TODO: move this to libwallet; QFile walletFile(src); if (!walletFile.exists()) { qWarning("%s: source file [%s] doesn't exits", __FUNCTION__, @@ -81,8 +83,26 @@ bool WalletManager::moveWallet(const QString &src, const QString &dst_) return QFile::exists(dst) && QFile::exists(dstWalletKeysFile) && QFile::exists(dstWalletAddressFile); +} +void WalletManager::closeWallet(Wallet *wallet) +{ + delete wallet; +} +QString WalletManager::walletLanguage(const QString &locale) +{ + return "English"; +} + +int WalletManager::error() const +{ + return 0; +} + +QString WalletManager::errorString() const +{ + return tr("Unknown error"); } WalletManager::WalletManager(QObject *parent) : QObject(parent) diff --git a/WalletManager.h b/WalletManager.h index ffb498d7..231f104c 100644 --- a/WalletManager.h +++ b/WalletManager.h @@ -10,11 +10,32 @@ class WalletManager : public QObject Q_OBJECT public: static WalletManager * instance(); + // wizard: createWallet path; Q_INVOKABLE Wallet * createWallet(const QString &path, const QString &password, const QString &language); - Q_INVOKABLE Wallet * openWallet(const QString &path, const QString &language); + // just for future use + Q_INVOKABLE Wallet * openWallet(const QString &path, const QString &language, + const QString &password); + + // wizard: recoveryWallet path; hint: internally it recorvers wallet and set password = "" + Q_INVOKABLE Wallet * recoveryWallet(const QString &path, const QString &memo, + const QString &language); + + // wizard: both "create" and "recovery" paths. + // TODO: probably move it to "Wallet" interface Q_INVOKABLE bool moveWallet(const QString &src, const QString &dst); + //! utils: close wallet to free memory + Q_INVOKABLE void closeWallet(Wallet * wallet); + + //! returns libwallet language name for given locale + Q_INVOKABLE QString walletLanguage(const QString &locale); + + //! returns last error happened in WalletManager + Q_INVOKABLE int error() const; + + //! returns error description in human language + Q_INVOKABLE QString errorString() const; signals: public slots: @@ -22,7 +43,6 @@ public slots: private: explicit WalletManager(QObject *parent = 0); static WalletManager * m_instance; - }; #endif // WALLETMANAGER_H diff --git a/wizard/WizardCreateWallet.qml b/wizard/WizardCreateWallet.qml index 91b7447e..ce573e86 100644 --- a/wizard/WizardCreateWallet.qml +++ b/wizard/WizardCreateWallet.qml @@ -46,13 +46,19 @@ Item { settingsObject['wallet_path'] = uiItem.walletPath + var new_wallet_filename = settingsObject.wallet_path + "/" + settingsObject.account_name; // moving wallet files to the new destination, if user changed it if (new_wallet_filename !== settingsObject.wallet_filename) { - walletManager.moveWallet(settingsObject.wallet_filename, new_wallet_filename); + // using previously saved wallet; + settingsObject.wallet.rename(new_wallet_filename); + //walletManager.moveWallet(settingsObject.wallet_filename, new_wallet_filename); } + + // saving wallet_filename; + settingsObject['wallet_filename'] = new_wallet_filename; } function createWallet(settingsObject) { @@ -66,6 +72,7 @@ Item { } else { print("wallet already created. we just stepping back"); } + settingsObject.wallet_filename = wallet_filename } diff --git a/wizard/WizardFinish.qml b/wizard/WizardFinish.qml index 62975d8c..311dfd62 100644 --- a/wizard/WizardFinish.qml +++ b/wizard/WizardFinish.qml @@ -40,7 +40,7 @@ Item { function buildSettingsString() { var str = "
" + qsTr("Language: ") + wizard.settings['language'] + "
" + qsTr("Account name: ") + wizard.settings['account_name'] + "
" - + qsTr("Words: ") + wizard.settings['words'] + "
" + + qsTr("Words: ") + wizard.settings['wallet'].seed + "
" + qsTr("Wallet Path: ") + wizard.settings['wallet_path'] + "
" + qsTr("Enable auto donation: ") + wizard.settings['auto_donations_enabled'] + "
" + qsTr("Auto donation amount: ") + wizard.settings['auto_donations_amount'] + "
" diff --git a/wizard/WizardPassword.qml b/wizard/WizardPassword.qml index d3d033aa..1bcc6213 100644 --- a/wizard/WizardPassword.qml +++ b/wizard/WizardPassword.qml @@ -43,6 +43,10 @@ Item { onOpacityChanged: visible = opacity !== 0 + function saveSettings(settingsObject) { + settingsObject.wallet.setPassword(passwordItem.password) + } + function handlePassword() { // allow to forward step only if passwords match wizard.nextButton.enabled = passwordItem.password === retypePasswordItem.password @@ -54,8 +58,6 @@ Item { - - Row { id: dotsRow anchors.top: parent.top diff --git a/wizard/WizardRecoveryWallet.qml b/wizard/WizardRecoveryWallet.qml index 9093c59a..572d776b 100644 --- a/wizard/WizardRecoveryWallet.qml +++ b/wizard/WizardRecoveryWallet.qml @@ -46,6 +46,10 @@ Item { settingsObject['wallet_path'] = uiItem.walletPath } + function recoveryWallet() { + + } + WizardManageWalletUI { id: uiItem accountNameText: qsTr("My account name") From 6d0179f1a7e28d8a858ba569df1dae7c199ea059 Mon Sep 17 00:00:00 2001 From: Ilya Kitaev Date: Tue, 1 Mar 2016 18:41:30 +0300 Subject: [PATCH 16/87] wizard: saving locale instead of language; wallet interface continued --- wizard/WizardCreateWallet.qml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/wizard/WizardCreateWallet.qml b/wizard/WizardCreateWallet.qml index ce573e86..346627dc 100644 --- a/wizard/WizardCreateWallet.qml +++ b/wizard/WizardCreateWallet.qml @@ -45,8 +45,6 @@ Item { settingsObject['words'] = uiItem.wordsTexttext settingsObject['wallet_path'] = uiItem.walletPath - - var new_wallet_filename = settingsObject.wallet_path + "/" + settingsObject.account_name; @@ -64,7 +62,7 @@ Item { function createWallet(settingsObject) { var wallet_filename = uiItem.walletPath + "/" + uiItem.accountNameText if (typeof settingsObject.wallet === 'undefined') { - var wallet = walletManager.createWallet(wallet_filename, "", settingsObject.language) + var wallet = walletManager.createWallet(wallet_filename, "", settingsObject.locale) uiItem.wordsTextItem.memoText = wallet.seed // saving wallet in "global" settings object // TODO: wallet should have a property pointing to the file where it stored or loaded from From 382fa30283155f94654a7b62902136dfc4609b94 Mon Sep 17 00:00:00 2001 From: Ilya Kitaev Date: Tue, 8 Mar 2016 12:08:24 +0300 Subject: [PATCH 17/87] explicitely enabled c++11 for g++ compiler --- monero-core.pro | 1 + 1 file changed, 1 insertion(+) diff --git a/monero-core.pro b/monero-core.pro index bee7eb17..1f4d97d4 100644 --- a/monero-core.pro +++ b/monero-core.pro @@ -2,6 +2,7 @@ TEMPLATE = app QT += qml quick widgets +CONFIG += c++11 HEADERS += \ filter.h \ From f9e091677698d27e3e5c94dab5435d1320959ad7 Mon Sep 17 00:00:00 2001 From: Ilya Kitaev Date: Tue, 17 May 2016 16:03:59 +0300 Subject: [PATCH 18/87] integrating libwallet --- WalletManager.cpp | 3 +++ get_libwallet_api.sh | 39 +++++++++++++++++++++++++++++++++++++++ monero-core.pro | 14 ++++++++------ 3 files changed, 50 insertions(+), 6 deletions(-) create mode 100755 get_libwallet_api.sh diff --git a/WalletManager.cpp b/WalletManager.cpp index 4a2f0c8f..cb0ffbac 100644 --- a/WalletManager.cpp +++ b/WalletManager.cpp @@ -1,5 +1,6 @@ #include "WalletManager.h" #include "Wallet.h" +#include "wallet/wallet2_api.h" #include #include #include @@ -17,6 +18,8 @@ WalletManager *WalletManager::instance() if (!m_instance) { m_instance = new WalletManager; } + // Checking linkage (doesn't work, TODO: have every dependencies linked statically into libwallet) + Bitmonero::WalletManager * wallet_manager_impl = Bitmonero::WalletManagerFactory::getWalletManager(); return m_instance; } diff --git a/get_libwallet_api.sh b/get_libwallet_api.sh new file mode 100755 index 00000000..9e2f65ce --- /dev/null +++ b/get_libwallet_api.sh @@ -0,0 +1,39 @@ +#!/bin/bash + + +BITMONERO_URL=https://github.com/mbg033/bitmonero +CPU_CORE_COUNT=$(grep -c ^processor /proc/cpuinfo) +pushd $(pwd) +ROOT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + + +INSTALL_DIR=$ROOT_DIR/wallet +BITMONERO_DIR=$ROOT_DIR/bitmonero + + +if [ ! -d $BITMONERO_DIR ]; then + git clone --depth=1 $BITMONERO_URL $BITMONERO_DIR +fi + +rm -fr $BITMONERO_DIR/build +mkdir -p $BITMONERO_DIR/build/release +pushd $BITMONERO_DIR/build/release + +cmake -D CMAKE_BUILD_TYPE=Release -D CMAKE_INSTALL_PREFIX="$BITMONERO_DIR" ../.. + +pushd $BITMONERO_DIR/build/release/src/wallet +make -j$CPU_CORE_COUNT +make install -j$CPU_CORE_COUNT +popd +popd + + + + + + + + + + + diff --git a/monero-core.pro b/monero-core.pro index 1f4d97d4..8574205f 100644 --- a/monero-core.pro +++ b/monero-core.pro @@ -2,8 +2,14 @@ TEMPLATE = app QT += qml quick widgets +WALLET_ROOT=$$PWD/bitmonero + CONFIG += c++11 +INCLUDEPATH += $$WALLET_ROOT/include + +message($$INCLUDEPATH) + HEADERS += \ filter.h \ clipboardAdapter.h \ @@ -26,6 +32,8 @@ SOURCES = *.qml \ wizard/*.qml } +LIBS += -L$$WALLET_ROOT/lib -lwallet + # translations files; TRANSLATIONS = monero-core_en.ts \ # English (could be untranslated) monero-core_de.ts # Deutsch @@ -71,9 +79,3 @@ OTHER_FILES += \ DISTFILES += \ notes.txt - - - - - - From 238d582b1741b885408f62bdeb94a6e2ecef0bd9 Mon Sep 17 00:00:00 2001 From: Ilya Kitaev Date: Fri, 27 May 2016 11:00:26 +0300 Subject: [PATCH 19/87] build against libwallet_merged and boost libs --- get_libwallet_api.sh | 2 +- monero-core.pro | 12 ++++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/get_libwallet_api.sh b/get_libwallet_api.sh index 9e2f65ce..34c2e6b3 100755 --- a/get_libwallet_api.sh +++ b/get_libwallet_api.sh @@ -19,7 +19,7 @@ rm -fr $BITMONERO_DIR/build mkdir -p $BITMONERO_DIR/build/release pushd $BITMONERO_DIR/build/release -cmake -D CMAKE_BUILD_TYPE=Release -D CMAKE_INSTALL_PREFIX="$BITMONERO_DIR" ../.. +cmake -D CMAKE_BUILD_TYPE=Release -D STATIC=ON -D CMAKE_INSTALL_PREFIX="$BITMONERO_DIR" ../.. pushd $BITMONERO_DIR/build/release/src/wallet make -j$CPU_CORE_COUNT diff --git a/monero-core.pro b/monero-core.pro index 8574205f..4b071fb1 100644 --- a/monero-core.pro +++ b/monero-core.pro @@ -8,7 +8,6 @@ CONFIG += c++11 INCLUDEPATH += $$WALLET_ROOT/include -message($$INCLUDEPATH) HEADERS += \ filter.h \ @@ -32,7 +31,16 @@ SOURCES = *.qml \ wizard/*.qml } -LIBS += -L$$WALLET_ROOT/lib -lwallet +LIBS += -L$$WALLET_ROOT/lib \ + -lwallet_merged \ + -lboost_serialization \ + -lboost_thread \ + -lboost_system \ + -lboost_date_time \ + -lboost_filesystem \ + -lboost_regex + + # translations files; TRANSLATIONS = monero-core_en.ts \ # English (could be untranslated) From 493e290956453ad068aa43b9a2fbefacd3550192 Mon Sep 17 00:00:00 2001 From: Ilya Kitaev Date: Fri, 3 Jun 2016 17:30:19 +0300 Subject: [PATCH 20/87] libwallet integration --- main.cpp | 3 ++- monero-core.pro | 11 ++++++----- Wallet.cpp => src/libwalletqt/Wallet.cpp | 0 Wallet.h => src/libwalletqt/Wallet.h | 0 .../libwalletqt/WalletManager.cpp | 0 WalletManager.h => src/libwalletqt/WalletManager.h | 0 6 files changed, 8 insertions(+), 6 deletions(-) rename Wallet.cpp => src/libwalletqt/Wallet.cpp (100%) rename Wallet.h => src/libwalletqt/Wallet.h (100%) rename WalletManager.cpp => src/libwalletqt/WalletManager.cpp (100%) rename WalletManager.h => src/libwalletqt/WalletManager.h (100%) diff --git a/main.cpp b/main.cpp index 0e996b84..b486c5f0 100644 --- a/main.cpp +++ b/main.cpp @@ -44,7 +44,8 @@ int main(int argc, char *argv[]) app.installEventFilter(eventFilter); qmlRegisterType("moneroComponents", 1, 0, "Clipboard"); - qmlRegisterType("moneroWallet", 1, 0, "Wallet"); + //qmlRegisterType("moneroWallet", 1, 0, "Wallet"); + qmlRegisterType(); QQmlApplicationEngine engine; diff --git a/monero-core.pro b/monero-core.pro index 4b071fb1..7b05829f 100644 --- a/monero-core.pro +++ b/monero-core.pro @@ -6,23 +6,24 @@ WALLET_ROOT=$$PWD/bitmonero CONFIG += c++11 -INCLUDEPATH += $$WALLET_ROOT/include +INCLUDEPATH += $$WALLET_ROOT/include \ + $$PWD/src/libwalletqt HEADERS += \ filter.h \ clipboardAdapter.h \ oscursor.h \ - WalletManager.h \ - Wallet.h + src/libwalletqt/WalletManager.h \ + src/libwalletqt/Wallet.h SOURCES += main.cpp \ filter.cpp \ clipboardAdapter.cpp \ oscursor.cpp \ - WalletManager.cpp \ - Wallet.cpp + src/libwalletqt/WalletManager.cpp \ + src/libwalletqt/Wallet.cpp lupdate_only { SOURCES = *.qml \ diff --git a/Wallet.cpp b/src/libwalletqt/Wallet.cpp similarity index 100% rename from Wallet.cpp rename to src/libwalletqt/Wallet.cpp diff --git a/Wallet.h b/src/libwalletqt/Wallet.h similarity index 100% rename from Wallet.h rename to src/libwalletqt/Wallet.h diff --git a/WalletManager.cpp b/src/libwalletqt/WalletManager.cpp similarity index 100% rename from WalletManager.cpp rename to src/libwalletqt/WalletManager.cpp diff --git a/WalletManager.h b/src/libwalletqt/WalletManager.h similarity index 100% rename from WalletManager.h rename to src/libwalletqt/WalletManager.h From da1b74a707da4e21a1c749c372ab9c8b1df1e5e1 Mon Sep 17 00:00:00 2001 From: Ilya Kitaev Date: Tue, 7 Jun 2016 16:26:25 +0300 Subject: [PATCH 21/87] Start in normal mode if wallet exists. Resolves #9 --- main.cpp | 4 +- main.qml | 11 ++ src/libwalletqt/Wallet.cpp | 189 +++++------------------------- src/libwalletqt/Wallet.h | 34 ++++-- src/libwalletqt/WalletManager.cpp | 94 ++++++--------- src/libwalletqt/WalletManager.h | 33 ++++-- 6 files changed, 120 insertions(+), 245 deletions(-) diff --git a/main.cpp b/main.cpp index b486c5f0..6c529ed0 100644 --- a/main.cpp +++ b/main.cpp @@ -44,8 +44,8 @@ int main(int argc, char *argv[]) app.installEventFilter(eventFilter); qmlRegisterType("moneroComponents", 1, 0, "Clipboard"); - //qmlRegisterType("moneroWallet", 1, 0, "Wallet"); - qmlRegisterType(); + qmlRegisterInterface("Wallet"); + QQmlApplicationEngine engine; diff --git a/main.qml b/main.qml index c1a1d5ec..a36a3755 100644 --- a/main.qml +++ b/main.qml @@ -110,6 +110,15 @@ ApplicationWindow { } + function walletsFound() { + var wallets = walletManager.findWallets(moneroAccountsDir); + if (wallets.length === 0) { + wallets = walletManager.findWallets(applicationDirectory); + } + print(wallets); + return wallets.length > 0; + } + visible: true width: rightPanelExpanded ? 1269 : 1269 - 300 height: 800 @@ -120,6 +129,8 @@ ApplicationWindow { Component.onCompleted: { x = (Screen.width - width) / 2 y = (Screen.height - height) / 2 + // + rootItem.state = walletsFound() ? "normal" : "wizard"; } Item { diff --git a/src/libwalletqt/Wallet.cpp b/src/libwalletqt/Wallet.cpp index 61998ab5..2ede062c 100644 --- a/src/libwalletqt/Wallet.cpp +++ b/src/libwalletqt/Wallet.cpp @@ -1,194 +1,61 @@ #include "Wallet.h" +#include "wallet/wallet2_api.h" + #include #include #include #include namespace { - QString TEST_SEED = "bound class paint gasp task soul forgot past pleasure physical circle " - " appear shore bathroom glove women crap busy beauty bliss idea give needle burden"; - namespace { - bool createFileWrapper(const QString &filename) - { - QFile file(filename); - // qDebug("%s: about to create file: %s", __FUNCTION__, qPrintable(filename)); - bool result = file.open(QIODevice::WriteOnly); - if (!result ){ - qWarning("%s: error creating file '%s' : '%s'", - __FUNCTION__, - qPrintable(filename), - qPrintable(file.errorString())); - } - return result; - } - } } -struct WalletImpl -{ - - QString basename() const; - void setBasename(const QString &name); - - QString keysName() const; - QString addressName() const; - -// Bitmonero::Wallet * m_walletImpl; - QString m_basename; - QString m_seed; - QString m_password; - QString m_language; - - static QString keysName(const QString &basename); - static QString addressName(const QString &basename); - -}; - - -QString WalletImpl::basename() const -{ - return m_basename; -} - -void WalletImpl::setBasename(const QString &name) -{ - m_basename = name; -} - -QString WalletImpl::keysName() const -{ - return keysName(m_basename); -} - -QString WalletImpl::addressName() const -{ - return addressName(m_basename); -} - -QString WalletImpl::keysName(const QString &basename) -{ - return basename + ".keys"; -} - -QString WalletImpl::addressName(const QString &basename) -{ - return basename + ".address.txt"; -} - - -Wallet::Wallet(QObject *parent) - : QObject(parent) -{ - -} QString Wallet::getSeed() const { - return m_pimpl->m_seed; + return QString::fromStdString(m_walletImpl->seed()); } QString Wallet::getSeedLanguage() const { - return "English"; + return QString::fromStdString(m_walletImpl->getSeedLanguage()); } -//void Wallet::setSeedLaguage(const QString &lang) -//{ -// // TODO: call libwallet's appropriate method -//} - -bool Wallet::setPassword(const QString &password) +int Wallet::status() const { - // set/change password implies: - // recovery wallet with existing path, seed and lang - qDebug("%s: recovering wallet with path=%s, seed=%s, lang=%s and new password=%s", - __FUNCTION__, - qPrintable(this->getBasename()), - qPrintable(this->getSeed()), - qPrintable(this->getSeedLanguage()), - qPrintable(password)); - return true; -} - -QString Wallet::getPassword() const -{ - return m_pimpl->m_password; -} - -bool Wallet::rename(const QString &name) -{ - - QString dst = QUrl(name).toLocalFile(); - - if (dst.isEmpty()) - dst = name; - - qDebug("%s: renaming '%s' to '%s'", - __FUNCTION__, - qPrintable(m_pimpl->basename()), - qPrintable(dst)); - - QString walletKeysFile = m_pimpl->keysName(); - QString walletAddressFile = m_pimpl->addressName(); - - QString dstWalletKeysFile = WalletImpl::keysName(dst); - QString dstWalletAddressFile = WalletImpl::addressName(dst); - - QFile walletFile(this->getBasename()); - - if (!walletFile.rename(dst)) { - qWarning("Error renaming file: '%s' to '%s' : (%s)", - qPrintable(m_pimpl->basename()), - qPrintable(dst), - qPrintable(walletFile.errorString())); - return false; - } - QFile::rename(walletKeysFile, dstWalletKeysFile); - QFile::rename(walletAddressFile, dstWalletAddressFile); - - bool result = QFile::exists(dst) && QFile::exists(dstWalletKeysFile) - && QFile::exists(dstWalletAddressFile); - - if (result) { - m_pimpl->m_basename = dst; - } - - return result; -} - -QString Wallet::getBasename() const -{ - return m_pimpl->basename(); -} - -int Wallet::error() const -{ - return 0; + return m_walletImpl->status(); } QString Wallet::errorString() const { - return m_pimpl->m_seed; + return QString::fromStdString(m_walletImpl->errorString()); } -Wallet::Wallet(const QString &path, const QString &password, const QString &language) +bool Wallet::setPassword(const QString &password) { - m_pimpl = new WalletImpl; - m_pimpl->m_basename = path; - m_pimpl->m_password = password; - m_pimpl->m_language = language; - m_pimpl->m_seed = TEST_SEED; + return m_walletImpl->setPassword(password.toStdString()); +} - // Create dummy files for testing - QFileInfo fi(path); - QDir tempDir; - tempDir.mkpath(fi.absolutePath()); - createFileWrapper(m_pimpl->basename()); - createFileWrapper(m_pimpl->keysName()); - createFileWrapper(m_pimpl->addressName()); +QString Wallet::address() const +{ + return QString::fromStdString(m_walletImpl->address()); +} + +bool Wallet::store(const QString &path) +{ + return m_walletImpl->store(path.toStdString()); } +Wallet::Wallet(Bitmonero::Wallet *w, QObject *parent) + : QObject(parent), m_walletImpl(w) +{ + +} + +Wallet::~Wallet() +{ + Bitmonero::WalletManagerFactory::getWalletManager()->closeWallet(m_walletImpl); +} diff --git a/src/libwalletqt/Wallet.h b/src/libwalletqt/Wallet.h index 221d3d43..79655cb4 100644 --- a/src/libwalletqt/Wallet.h +++ b/src/libwalletqt/Wallet.h @@ -3,13 +3,18 @@ #include -struct WalletImpl; +namespace Bitmonero { + class Wallet; // forward declaration +} class Wallet : public QObject { Q_OBJECT Q_PROPERTY(QString seed READ getSeed) public: - explicit Wallet(QObject *parent = 0); + enum Status { + Status_Ok = 0, + Status_Error = 1 + }; //! returns mnemonic seed Q_INVOKABLE QString getSeed() const; @@ -17,28 +22,31 @@ public: //! returns seed language Q_INVOKABLE QString getSeedLanguage() const; + //! returns last operation's status + Q_INVOKABLE int status() const; + + //! returns last operation's error message + Q_INVOKABLE QString errorString() const; //! changes the password using existing parameters (path, seed, seed lang) Q_INVOKABLE bool setPassword(const QString &password); - //! returns curret wallet password - Q_INVOKABLE QString getPassword() const; - //! renames/moves wallet files - Q_INVOKABLE bool rename(const QString &name); + //! returns wallet's public address + Q_INVOKABLE QString address() const; + + //! saves wallet to the file by given path + Q_INVOKABLE bool store(const QString &path); - //! returns current wallet name (basename, as wallet consists of several files) - Q_INVOKABLE QString getBasename() const; - Q_INVOKABLE int error() const; - Q_INVOKABLE QString errorString() const; private: - Wallet(const QString &path, const QString &password, const QString &language); + Wallet(Bitmonero::Wallet *w, QObject * parent = 0); + ~Wallet(); private: friend class WalletManager; - //! pimpl wrapper for libwallet; - WalletImpl * m_pimpl; + //! libwallet's + Bitmonero::Wallet * m_walletImpl; }; #endif // WALLET_H diff --git a/src/libwalletqt/WalletManager.cpp b/src/libwalletqt/WalletManager.cpp index cb0ffbac..646e47f5 100644 --- a/src/libwalletqt/WalletManager.cpp +++ b/src/libwalletqt/WalletManager.cpp @@ -7,6 +7,8 @@ #include #include + + WalletManager * WalletManager::m_instance = nullptr; @@ -18,89 +20,55 @@ WalletManager *WalletManager::instance() if (!m_instance) { m_instance = new WalletManager; } - // Checking linkage (doesn't work, TODO: have every dependencies linked statically into libwallet) - Bitmonero::WalletManager * wallet_manager_impl = Bitmonero::WalletManagerFactory::getWalletManager(); return m_instance; } Wallet *WalletManager::createWallet(const QString &path, const QString &password, - const QString &language) + const QString &language, bool testnet) { - QFileInfo fi(path); - if (fi.exists()) { - qCritical("%s: already exists", __FUNCTION__); - // TODO: set error and error string - // return nullptr; - } - Wallet * wallet = new Wallet(path, password, language); + Bitmonero::Wallet * w = m_pimpl->createWallet(path.toStdString(), password.toStdString(), + language.toStdString(), testnet); + Wallet * wallet = new Wallet(w); return wallet; } -Wallet *WalletManager::openWallet(const QString &path, const QString &language, const QString &password) +Wallet *WalletManager::openWallet(const QString &path, const QString &password, bool testnet) { - QFileInfo fi(path); - if (fi.exists()) { - qCritical("%s: not exists", __FUNCTION__); - // TODO: set error and error string - // return nullptr; - } // TODO: call the libwallet api here; - Wallet * wallet = new Wallet(path, password, language); + Bitmonero::Wallet * w = m_pimpl->openWallet(path.toStdString(), password.toStdString(), testnet); + Wallet * wallet = new Wallet(w); return wallet; } -Wallet *WalletManager::recoveryWallet(const QString &path, const QString &memo, const QString &language) -{ - // TODO: call the libwallet api here; - return nullptr; +Wallet *WalletManager::recoveryWallet(const QString &path, const QString &memo, bool testnet) +{ + Bitmonero::Wallet * w = m_pimpl->recoveryWallet(path.toStdString(), memo.toStdString(), testnet); + Wallet * wallet = new Wallet(w); + return wallet; } -bool WalletManager::moveWallet(const QString &src, const QString &dst_) -{ - // TODO: move this to libwallet; - QFile walletFile(src); - if (!walletFile.exists()) { - qWarning("%s: source file [%s] doesn't exits", __FUNCTION__, - qPrintable(src)); - return false; - } - QString dst = QUrl(dst_).toLocalFile(); - QString walletKeysFile = src + ".keys"; - QString walletAddressFile = src + ".address.txt"; - - QString dstWalletKeysFile = dst + ".keys"; - QString dstWalletAddressFile = dst + ".address.txt"; - - if (!walletFile.rename(dst)) { - qWarning("Error renaming file: '%s' to '%s' : (%s)", - qPrintable(src), - qPrintable(dst), - qPrintable(walletFile.errorString())); - return false; - } - QFile::rename(walletKeysFile, dstWalletKeysFile); - QFile::rename(walletAddressFile, dstWalletAddressFile); - - return QFile::exists(dst) && QFile::exists(dstWalletKeysFile) - && QFile::exists(dstWalletAddressFile); -} void WalletManager::closeWallet(Wallet *wallet) { delete wallet; } -QString WalletManager::walletLanguage(const QString &locale) +bool WalletManager::walletExists(const QString &path) const { - return "English"; + return m_pimpl->walletExists(path.toStdString()); } -int WalletManager::error() const +QStringList WalletManager::findWallets(const QString &path) { - return 0; + std::vector found_wallets = m_pimpl->findWallets(path.toStdString()); + QStringList result; + for (const auto &w : found_wallets) { + result.append(QString::fromStdString(w)); + } + return result; } QString WalletManager::errorString() const @@ -108,9 +76,21 @@ QString WalletManager::errorString() const return tr("Unknown error"); } -WalletManager::WalletManager(QObject *parent) : QObject(parent) +bool WalletManager::moveWallet(const QString &src, const QString &dst) { - + return true; +} + + +QString WalletManager::walletLanguage(const QString &locale) +{ + return "English"; +} + + +WalletManager::WalletManager(QObject *parent) : QObject(parent) +{ + m_pimpl = Bitmonero::WalletManagerFactory::getWalletManager(); } diff --git a/src/libwalletqt/WalletManager.h b/src/libwalletqt/WalletManager.h index 231f104c..c40fec54 100644 --- a/src/libwalletqt/WalletManager.h +++ b/src/libwalletqt/WalletManager.h @@ -4,6 +4,9 @@ #include class Wallet; +namespace Bitmonero { + class WalletManager; +} class WalletManager : public QObject { @@ -12,37 +15,43 @@ public: static WalletManager * instance(); // wizard: createWallet path; Q_INVOKABLE Wallet * createWallet(const QString &path, const QString &password, - const QString &language); + const QString &language, bool testnet = false); // just for future use - Q_INVOKABLE Wallet * openWallet(const QString &path, const QString &language, - const QString &password); + Q_INVOKABLE Wallet * openWallet(const QString &path, const QString &password, bool testnet = false); // wizard: recoveryWallet path; hint: internally it recorvers wallet and set password = "" Q_INVOKABLE Wallet * recoveryWallet(const QString &path, const QString &memo, - const QString &language); - - // wizard: both "create" and "recovery" paths. - // TODO: probably move it to "Wallet" interface - Q_INVOKABLE bool moveWallet(const QString &src, const QString &dst); + bool testnet = false); //! utils: close wallet to free memory Q_INVOKABLE void closeWallet(Wallet * wallet); - //! returns libwallet language name for given locale - Q_INVOKABLE QString walletLanguage(const QString &locale); + //! checks is given filename is a wallet; + Q_INVOKABLE bool walletExists(const QString &path) const; - //! returns last error happened in WalletManager - Q_INVOKABLE int error() const; + //! returns list with wallet's filenames, if found by given path + Q_INVOKABLE QStringList findWallets(const QString &path); //! returns error description in human language Q_INVOKABLE QString errorString() const; + + + // wizard: both "create" and "recovery" paths. + // TODO: probably move it to "Wallet" interface + Q_INVOKABLE bool moveWallet(const QString &src, const QString &dst); + //! returns libwallet language name for given locale + Q_INVOKABLE QString walletLanguage(const QString &locale); + signals: public slots: private: + explicit WalletManager(QObject *parent = 0); static WalletManager * m_instance; + Bitmonero::WalletManager * m_pimpl; + }; #endif // WALLETMANAGER_H From 5c10be325103d668884fc5e463aaa59c726d4f8e Mon Sep 17 00:00:00 2001 From: Ilya Kitaev Date: Wed, 8 Jun 2016 13:53:24 +0300 Subject: [PATCH 22/87] Qt wrappers for libwallet API classes --- monero-core.pro | 10 +++- src/libwalletqt/PendingTransaction.cpp | 37 +++++++++++++++ src/libwalletqt/PendingTransaction.h | 41 ++++++++++++++++ src/libwalletqt/TransactionHistory.cpp | 50 +++++++++++++++++++ src/libwalletqt/TransactionHistory.h | 42 ++++++++++++++++ src/libwalletqt/TransactionInfo.cpp | 55 +++++++++++++++++++++ src/libwalletqt/TransactionInfo.h | 54 +++++++++++++++++++++ src/libwalletqt/Wallet.cpp | 66 ++++++++++++++++++++++++-- src/libwalletqt/Wallet.h | 63 +++++++++++++++++++++--- src/libwalletqt/WalletManager.cpp | 5 ++ src/libwalletqt/WalletManager.h | 3 ++ 11 files changed, 414 insertions(+), 12 deletions(-) create mode 100644 src/libwalletqt/PendingTransaction.cpp create mode 100644 src/libwalletqt/PendingTransaction.h create mode 100644 src/libwalletqt/TransactionHistory.cpp create mode 100644 src/libwalletqt/TransactionHistory.h create mode 100644 src/libwalletqt/TransactionInfo.cpp create mode 100644 src/libwalletqt/TransactionInfo.h diff --git a/monero-core.pro b/monero-core.pro index 7b05829f..f521c9ec 100644 --- a/monero-core.pro +++ b/monero-core.pro @@ -15,7 +15,10 @@ HEADERS += \ clipboardAdapter.h \ oscursor.h \ src/libwalletqt/WalletManager.h \ - src/libwalletqt/Wallet.h + src/libwalletqt/Wallet.h \ + src/libwalletqt/PendingTransaction.h \ + src/libwalletqt/TransactionHistory.h \ + src/libwalletqt/TransactionInfo.h SOURCES += main.cpp \ @@ -23,7 +26,10 @@ SOURCES += main.cpp \ clipboardAdapter.cpp \ oscursor.cpp \ src/libwalletqt/WalletManager.cpp \ - src/libwalletqt/Wallet.cpp + src/libwalletqt/Wallet.cpp \ + src/libwalletqt/PendingTransaction.cpp \ + src/libwalletqt/TransactionHistory.cpp \ + src/libwalletqt/TransactionInfo.cpp lupdate_only { SOURCES = *.qml \ diff --git a/src/libwalletqt/PendingTransaction.cpp b/src/libwalletqt/PendingTransaction.cpp new file mode 100644 index 00000000..a5dc458d --- /dev/null +++ b/src/libwalletqt/PendingTransaction.cpp @@ -0,0 +1,37 @@ +#include "PendingTransaction.h" + +PendingTransaction::Status PendingTransaction::status() const +{ + return static_cast(m_pimpl->status()); +} + +QString PendingTransaction::errorString() const +{ + return QString::fromStdString(m_pimpl->errorString()); +} + +bool PendingTransaction::commit() +{ + return m_pimpl->commit(); +} + +quint64 PendingTransaction::amount() const +{ + return m_pimpl->amount(); +} + +quint64 PendingTransaction::dust() const +{ + return m_pimpl->dust(); +} + +quint64 PendingTransaction::fee() const +{ + return m_pimpl->fee(); +} + +PendingTransaction::PendingTransaction(Bitmonero::PendingTransaction *pt, QObject *parent) + : QObject(parent), m_pimpl(pt) +{ + +} diff --git a/src/libwalletqt/PendingTransaction.h b/src/libwalletqt/PendingTransaction.h new file mode 100644 index 00000000..29fa7cb4 --- /dev/null +++ b/src/libwalletqt/PendingTransaction.h @@ -0,0 +1,41 @@ +#ifndef PENDINGTRANSACTION_H +#define PENDINGTRANSACTION_H + +#include + +#include + +//namespace Bitmonero { +//class PendingTransaction; +//} + +class PendingTransaction : public QObject +{ + Q_OBJECT + Q_PROPERTY(Status status READ status) + Q_PROPERTY(QString errorString READ errorString) + Q_PROPERTY(quint64 amount READ amount) + Q_PROPERTY(quint64 dust READ dust) + Q_PROPERTY(quint64 fee READ fee) + +public: + enum Status { + Status_Ok = Bitmonero::PendingTransaction::Status_Ok, + Status_Error = Bitmonero::PendingTransaction::Status_Error + }; + + Status status() const; + QString errorString() const; + Q_INVOKABLE bool commit(); + quint64 amount() const; + quint64 dust() const; + quint64 fee() const; +private: + explicit PendingTransaction(Bitmonero::PendingTransaction * pt, QObject *parent = 0); + +private: + friend class Wallet; + Bitmonero::PendingTransaction * m_pimpl; +}; + +#endif // PENDINGTRANSACTION_H diff --git a/src/libwalletqt/TransactionHistory.cpp b/src/libwalletqt/TransactionHistory.cpp new file mode 100644 index 00000000..2c2c9f73 --- /dev/null +++ b/src/libwalletqt/TransactionHistory.cpp @@ -0,0 +1,50 @@ +#include "TransactionHistory.h" +#include "TransactionInfo.h" +#include + + +int TransactionHistory::count() const +{ + return m_pimpl->count(); +} + +TransactionInfo *TransactionHistory::transaction(int index) +{ + // box up Bitmonero::TransactionInfo + Bitmonero::TransactionInfo * impl = m_pimpl->transaction(index); + TransactionInfo * result = new TransactionInfo(impl, this); + return result; +} + +TransactionInfo *TransactionHistory::transaction(const QString &id) +{ + // box up Bitmonero::TransactionInfo + Bitmonero::TransactionInfo * impl = m_pimpl->transaction(id.toStdString()); + TransactionInfo * result = new TransactionInfo(impl, this); + return result; +} + +QList TransactionHistory::getAll() const +{ + qDeleteAll(m_tinfo); + m_tinfo.clear(); + TransactionHistory * parent = const_cast(this); + for (const auto i : m_pimpl->getAll()) { + TransactionInfo * ti = new TransactionInfo(i, parent); + m_tinfo.append(ti); + } + return m_tinfo; +} + +void TransactionHistory::refresh() +{ + // XXX this invalidates previously saved history that might be used by clients + m_pimpl->refresh(); + emit invalidated(); +} + +TransactionHistory::TransactionHistory(Bitmonero::TransactionHistory *pimpl, QObject *parent) + : QObject(parent), m_pimpl(pimpl) +{ + +} diff --git a/src/libwalletqt/TransactionHistory.h b/src/libwalletqt/TransactionHistory.h new file mode 100644 index 00000000..a6287506 --- /dev/null +++ b/src/libwalletqt/TransactionHistory.h @@ -0,0 +1,42 @@ +#ifndef TRANSACTIONHISTORY_H +#define TRANSACTIONHISTORY_H + +#include +#include + +namespace Bitmonero { +class TransactionHistory; +} + +class TransactionInfo; + +class TransactionHistory : public QObject +{ + Q_OBJECT + Q_PROPERTY(int count READ count) + +public: + int count() const; + Q_INVOKABLE TransactionInfo *transaction(int index); + Q_INVOKABLE TransactionInfo * transaction(const QString &id); + Q_INVOKABLE QList getAll() const; + Q_INVOKABLE void refresh(); + +signals: + void invalidated(); + +public slots: + + +private: + explicit TransactionHistory(Bitmonero::TransactionHistory * pimpl, QObject *parent = 0); + +private: + friend class Wallet; + + Bitmonero::TransactionHistory * m_pimpl; + mutable QList m_tinfo; + +}; + +#endif // TRANSACTIONHISTORY_H diff --git a/src/libwalletqt/TransactionInfo.cpp b/src/libwalletqt/TransactionInfo.cpp new file mode 100644 index 00000000..192e85e5 --- /dev/null +++ b/src/libwalletqt/TransactionInfo.cpp @@ -0,0 +1,55 @@ +#include "TransactionInfo.h" +#include + +TransactionInfo::Direction TransactionInfo::direction() const +{ + return static_cast(m_pimpl->direction()); +} + +bool TransactionInfo::isPending() const +{ + return m_pimpl->isPending(); +} + +bool TransactionInfo::isFailed() const +{ + return m_pimpl->isFailed(); +} + +quint64 TransactionInfo::amount() const +{ + return m_pimpl->amount(); +} + +quint64 TransactionInfo::fee() const +{ + return m_pimpl->fee(); + +} + +quint64 TransactionInfo::blockHeight() const +{ + return m_pimpl->blockHeight(); +} + +QString TransactionInfo::hash() const +{ + return QString::fromStdString(m_pimpl->hash()); +} + +QString TransactionInfo::timestamp() +{ + QString result = QDateTime::fromTime_t(m_pimpl->timestamp()).toString(Qt::ISODate); + return result; +} + +QString TransactionInfo::paymentId() +{ + return QString::fromStdString(m_pimpl->paymentId()); +} + +TransactionInfo::TransactionInfo(Bitmonero::TransactionInfo *pimpl, QObject *parent) + : QObject(parent), m_pimpl(pimpl) +{ + +} diff --git a/src/libwalletqt/TransactionInfo.h b/src/libwalletqt/TransactionInfo.h new file mode 100644 index 00000000..62c26e2f --- /dev/null +++ b/src/libwalletqt/TransactionInfo.h @@ -0,0 +1,54 @@ +#ifndef TRANSACTIONINFO_H +#define TRANSACTIONINFO_H + +#include +#include + +class TransactionInfo : public QObject +{ + Q_OBJECT + Q_PROPERTY(Direction direction READ direction) + Q_PROPERTY(bool isPending READ isPending) + Q_PROPERTY(bool isFailed READ isFailed) + Q_PROPERTY(quint64 amount READ amount) + Q_PROPERTY(quint64 fee READ fee) + Q_PROPERTY(quint64 blockHeight READ blockHeight) + Q_PROPERTY(QString hash READ hash) + Q_PROPERTY(QString timestamp READ timestamp) + Q_PROPERTY(QString paymentId READ paymentId) + +public: + enum Direction { + Direction_In = Bitmonero::TransactionInfo::Direction_In, + Direction_Out = Bitmonero::TransactionInfo::Direction_Out + }; + +// TODO: implement as separate class; + +// struct Transfer { +// Transfer(uint64_t _amount, const std::string &address); +// const uint64_t amount; +// const std::string address; +// }; + Direction direction() const; + bool isPending() const; + bool isFailed() const; + quint64 amount() const; + quint64 fee() const; + quint64 blockHeight() const; + //! transaction_id + QString hash() const; + QString timestamp(); + QString paymentId(); + + // TODO: implement it + //! only applicable for output transactions + // virtual const std::vector & transfers() const = 0; +private: + explicit TransactionInfo(Bitmonero::TransactionInfo * pimpl, QObject *parent = 0); +private: + friend class TransactionHistory; + Bitmonero::TransactionInfo * m_pimpl; +}; + +#endif // TRANSACTIONINFO_H diff --git a/src/libwalletqt/Wallet.cpp b/src/libwalletqt/Wallet.cpp index 2ede062c..77901b0c 100644 --- a/src/libwalletqt/Wallet.cpp +++ b/src/libwalletqt/Wallet.cpp @@ -1,4 +1,6 @@ #include "Wallet.h" +#include "PendingTransaction.h" +#include "TransactionHistory.h" #include "wallet/wallet2_api.h" #include @@ -22,9 +24,14 @@ QString Wallet::getSeedLanguage() const return QString::fromStdString(m_walletImpl->getSeedLanguage()); } -int Wallet::status() const +void Wallet::setSeedLanguage(const QString &lang) { - return m_walletImpl->status(); + m_walletImpl->setSeedLanguage(lang.toStdString()); +} + +Wallet::Status Wallet::status() const +{ + return static_cast(m_walletImpl->status()); } QString Wallet::errorString() const @@ -47,10 +54,63 @@ bool Wallet::store(const QString &path) return m_walletImpl->store(path.toStdString()); } +bool Wallet::init(const QString &daemonAddress, quint64 upperTransactionLimit) +{ + return m_walletImpl->init(daemonAddress.toStdString(), upperTransactionLimit); +} + +bool Wallet::connectToDaemon() +{ + return m_walletImpl->connectToDaemon(); +} + +void Wallet::setTrustedDaemon(bool arg) +{ + m_walletImpl->setTrustedDaemon(arg); +} + +quint64 Wallet::balance() const +{ + return m_walletImpl->balance(); +} + +quint64 Wallet::unlockedBalance() const +{ + return m_walletImpl->unlockedBalance(); +} + +bool Wallet::refresh() +{ + return m_walletImpl->refresh(); +} + +PendingTransaction *Wallet::createTransaction(const QString &dst_addr, quint64 amount) +{ + Bitmonero::PendingTransaction * ptImpl = m_walletImpl->createTransaction( + dst_addr.toStdString(), amount); + PendingTransaction * result = new PendingTransaction(ptImpl, this); + return result; +} + +void Wallet::disposeTransaction(PendingTransaction *t) +{ + m_walletImpl->disposeTransaction(t->m_pimpl); + delete t; +} + +TransactionHistory *Wallet::history() +{ + if (!m_history) { + Bitmonero::TransactionHistory * impl = m_walletImpl->history(); + m_history = new TransactionHistory(impl, this); + } + return m_history; +} + Wallet::Wallet(Bitmonero::Wallet *w, QObject *parent) - : QObject(parent), m_walletImpl(w) + : QObject(parent), m_walletImpl(w), m_history(nullptr) { } diff --git a/src/libwalletqt/Wallet.h b/src/libwalletqt/Wallet.h index 79655cb4..7d391429 100644 --- a/src/libwalletqt/Wallet.h +++ b/src/libwalletqt/Wallet.h @@ -3,41 +3,88 @@ #include +#include "wallet/wallet2_api.h" // we need to access Status enum here; + namespace Bitmonero { class Wallet; // forward declaration } + +class PendingTransaction; +class TransactionHistory; + class Wallet : public QObject { Q_OBJECT Q_PROPERTY(QString seed READ getSeed) + Q_PROPERTY(QString seedLanguage READ getSeedLanguage) + Q_PROPERTY(Status status READ status) + Q_PROPERTY(QString errorString READ errorString) + Q_PROPERTY(QString address READ address) + Q_PROPERTY(quint64 balance READ balance) + Q_PROPERTY(quint64 unlockedBalance READ unlockedBalance) + Q_PROPERTY(TransactionHistory * history READ history) + public: enum Status { - Status_Ok = 0, - Status_Error = 1 + Status_Ok = Bitmonero::Wallet::Status_Ok, + Status_Error = Bitmonero::Wallet::Status_Error }; + Q_ENUM(Status) + //! returns mnemonic seed - Q_INVOKABLE QString getSeed() const; + QString getSeed() const; //! returns seed language - Q_INVOKABLE QString getSeedLanguage() const; + QString getSeedLanguage() const; + + //! set seed language + Q_INVOKABLE void setSeedLanguage(const QString &lang); //! returns last operation's status - Q_INVOKABLE int status() const; + Status status() const; //! returns last operation's error message - Q_INVOKABLE QString errorString() const; + QString errorString() const; //! changes the password using existing parameters (path, seed, seed lang) Q_INVOKABLE bool setPassword(const QString &password); //! returns wallet's public address - Q_INVOKABLE QString address() const; + QString address() const; //! saves wallet to the file by given path Q_INVOKABLE bool store(const QString &path); + //! initializes wallet + Q_INVOKABLE bool init(const QString &daemonAddress, quint64 upperTransactionLimit); + //! connects to daemon + Q_INVOKABLE bool connectToDaemon(); + + //! indicates id daemon is trusted + Q_INVOKABLE void setTrustedDaemon(bool arg); + + //! returns balance + quint64 balance() const; + + //! returns unlocked balance + quint64 unlockedBalance() const; + + //! refreshes the wallet + Q_INVOKABLE bool refresh(); + + //! creates transaction + Q_INVOKABLE PendingTransaction * createTransaction(const QString &dst_addr, + quint64 amount); + + //! deletes transaction and frees memory + Q_INVOKABLE void disposeTransaction(PendingTransaction * t); + + //! returns transaction history + TransactionHistory * history(); + + // TODO: setListenter() when it implemented in API private: Wallet(Bitmonero::Wallet *w, QObject * parent = 0); @@ -47,6 +94,8 @@ private: friend class WalletManager; //! libwallet's Bitmonero::Wallet * m_walletImpl; + // history lifetime managed by wallet; + TransactionHistory * m_history; }; #endif // WALLET_H diff --git a/src/libwalletqt/WalletManager.cpp b/src/libwalletqt/WalletManager.cpp index 646e47f5..6ad81e2a 100644 --- a/src/libwalletqt/WalletManager.cpp +++ b/src/libwalletqt/WalletManager.cpp @@ -87,6 +87,11 @@ QString WalletManager::walletLanguage(const QString &locale) return "English"; } +QString WalletManager::displayAmount(quint64 amount) +{ + return QString::fromStdString(Bitmonero::Wallet::displayAmount(amount)); +} + WalletManager::WalletManager(QObject *parent) : QObject(parent) { diff --git a/src/libwalletqt/WalletManager.h b/src/libwalletqt/WalletManager.h index c40fec54..30e6b02a 100644 --- a/src/libwalletqt/WalletManager.h +++ b/src/libwalletqt/WalletManager.h @@ -42,6 +42,9 @@ public: //! returns libwallet language name for given locale Q_INVOKABLE QString walletLanguage(const QString &locale); + + //! since we can't call static method from QML, move it to this class + Q_INVOKABLE QString displayAmount(quint64 amount); signals: public slots: From fd50e6f9a3bc445cb3c0d21b7a0f584e95b44450 Mon Sep 17 00:00:00 2001 From: Ilya Kitaev Date: Fri, 10 Jun 2016 16:41:13 +0300 Subject: [PATCH 23/87] new wallet wizard: wallet created in temporary directory and moved to the destination at the final step --- get_libwallet_api.sh | 3 +++ lang/languages.xml | 20 ++++++++++---------- main.cpp | 6 ++++++ monero-core.pro | 6 ++++-- oshelper.cpp | 24 ++++++++++++++++++++++++ oshelper.h | 22 ++++++++++++++++++++++ wizard/WizardCreateWallet.qml | 25 ++++++++++++++++++++----- wizard/WizardDonation.qml | 2 +- wizard/WizardMain.qml | 7 +++---- wizard/WizardManageWalletUI.qml | 2 +- wizard/WizardPassword.qml | 2 +- wizard/WizardRecoveryWallet.qml | 2 +- wizard/WizardWelcome.qml | 13 +++++++++---- 13 files changed, 105 insertions(+), 29 deletions(-) create mode 100644 oshelper.cpp create mode 100644 oshelper.h diff --git a/get_libwallet_api.sh b/get_libwallet_api.sh index 34c2e6b3..c9f67ff8 100755 --- a/get_libwallet_api.sh +++ b/get_libwallet_api.sh @@ -13,6 +13,9 @@ BITMONERO_DIR=$ROOT_DIR/bitmonero if [ ! -d $BITMONERO_DIR ]; then git clone --depth=1 $BITMONERO_URL $BITMONERO_DIR +else + cd $BITMONERO_DIR; + git pull; fi rm -fr $BITMONERO_DIR/build diff --git a/lang/languages.xml b/lang/languages.xml index efb84c9c..449d415c 100644 --- a/lang/languages.xml +++ b/lang/languages.xml @@ -1,13 +1,13 @@ - - - - - - - - - - + + + + + + + + + + diff --git a/main.cpp b/main.cpp index 6c529ed0..39e4635d 100644 --- a/main.cpp +++ b/main.cpp @@ -33,10 +33,13 @@ #include "clipboardAdapter.h" #include "filter.h" #include "oscursor.h" +#include "oshelper.h" #include "WalletManager.h" #include "Wallet.h" + + int main(int argc, char *argv[]) { QApplication app(argc, argv); @@ -51,6 +54,9 @@ int main(int argc, char *argv[]) OSCursor cursor; engine.rootContext()->setContextProperty("globalCursor", &cursor); + OSHelper osHelper; + engine.rootContext()->setContextProperty("oshelper", &osHelper); + engine.rootContext()->setContextProperty("walletManager", WalletManager::instance()); // export to QML monero accounts root directory diff --git a/monero-core.pro b/monero-core.pro index f521c9ec..8ce4a16a 100644 --- a/monero-core.pro +++ b/monero-core.pro @@ -18,7 +18,8 @@ HEADERS += \ src/libwalletqt/Wallet.h \ src/libwalletqt/PendingTransaction.h \ src/libwalletqt/TransactionHistory.h \ - src/libwalletqt/TransactionInfo.h + src/libwalletqt/TransactionInfo.h \ + oshelper.h SOURCES += main.cpp \ @@ -29,7 +30,8 @@ SOURCES += main.cpp \ src/libwalletqt/Wallet.cpp \ src/libwalletqt/PendingTransaction.cpp \ src/libwalletqt/TransactionHistory.cpp \ - src/libwalletqt/TransactionInfo.cpp + src/libwalletqt/TransactionInfo.cpp \ + oshelper.cpp lupdate_only { SOURCES = *.qml \ diff --git a/oshelper.cpp b/oshelper.cpp new file mode 100644 index 00000000..cecae38e --- /dev/null +++ b/oshelper.cpp @@ -0,0 +1,24 @@ +#include "oshelper.h" +#include +#include + +OSHelper::OSHelper(QObject *parent) : QObject(parent) +{ + +} + +QString OSHelper::temporaryFilename() const +{ + QString tempFileName; + { + QTemporaryFile f; + f.open(); + tempFileName = f.fileName(); + } + return tempFileName; +} + +QString OSHelper::temporaryPath() const +{ + return QDir::tempPath(); +} diff --git a/oshelper.h b/oshelper.h new file mode 100644 index 00000000..809058cb --- /dev/null +++ b/oshelper.h @@ -0,0 +1,22 @@ +#ifndef OSHELPER_H +#define OSHELPER_H + +#include +/** + * @brief The OSHelper class - exports to QML some OS-related functions + */ +class OSHelper : public QObject +{ + Q_OBJECT +public: + explicit OSHelper(QObject *parent = 0); + + Q_INVOKABLE QString temporaryFilename() const; + Q_INVOKABLE QString temporaryPath() const; + +signals: + +public slots: +}; + +#endif // OSHELPER_H diff --git a/wizard/WizardCreateWallet.qml b/wizard/WizardCreateWallet.qml index 346627dc..653db251 100644 --- a/wizard/WizardCreateWallet.qml +++ b/wizard/WizardCreateWallet.qml @@ -40,18 +40,23 @@ Item { onOpacityChanged: visible = opacity !== 0 - function saveSettings(settingsObject) { + //! function called each time we display this page + + function onPageClosed(settingsObject) { settingsObject['account_name'] = uiItem.accountNameText settingsObject['words'] = uiItem.wordsTexttext settingsObject['wallet_path'] = uiItem.walletPath + // put wallet files to the subdirectory with the same name as + // wallet name var new_wallet_filename = settingsObject.wallet_path + "/" + + settingsObject.account_name + "/" + settingsObject.account_name; // moving wallet files to the new destination, if user changed it if (new_wallet_filename !== settingsObject.wallet_filename) { // using previously saved wallet; - settingsObject.wallet.rename(new_wallet_filename); + settingsObject.wallet.store(new_wallet_filename); //walletManager.moveWallet(settingsObject.wallet_filename, new_wallet_filename); } @@ -59,10 +64,18 @@ Item { settingsObject['wallet_filename'] = new_wallet_filename; } + //! function called each time we hide this page + // + + function createWallet(settingsObject) { - var wallet_filename = uiItem.walletPath + "/" + uiItem.accountNameText + // TODO: create wallet in temporary filename and a) move it to the path specified by user after the final + // page submitted or b) delete it when program closed before reaching final page + + var wallet_filename = oshelper.temporaryFilename(); if (typeof settingsObject.wallet === 'undefined') { - var wallet = walletManager.createWallet(wallet_filename, "", settingsObject.locale) + //var wallet = walletManager.createWallet(wallet_filename, "", settingsObject.language) + var wallet = walletManager.createWallet(wallet_filename, "", settingsObject.wallet_language) uiItem.wordsTextItem.memoText = wallet.seed // saving wallet in "global" settings object // TODO: wallet should have a property pointing to the file where it stored or loaded from @@ -70,10 +83,12 @@ Item { } else { print("wallet already created. we just stepping back"); } - settingsObject.wallet_filename = wallet_filename } + + + WizardManageWalletUI { id: uiItem titleText: qsTr("A new wallet has been created for you") diff --git a/wizard/WizardDonation.qml b/wizard/WizardDonation.qml index d42c266e..51a28029 100644 --- a/wizard/WizardDonation.qml +++ b/wizard/WizardDonation.qml @@ -38,7 +38,7 @@ Item { onOpacityChanged: visible = opacity !== 0 - function saveSettings(settingsObject) { + function onPageClosed(settingsObject) { settingsObject['auto_donations_enabled'] = enableAutoDonationCheckBox.checked; settingsObject['auto_donations_amount'] = autoDonationAmountText.text; settingsObject['allow_background_mining'] = allowBackgroundMiningCheckBox.checked; diff --git a/wizard/WizardMain.qml b/wizard/WizardMain.qml index 51e0baf3..a19c622c 100644 --- a/wizard/WizardMain.qml +++ b/wizard/WizardMain.qml @@ -49,8 +49,8 @@ Rectangle { function switchPage(next) { // save settings for current page; - if (typeof pages[currentPage].saveSettings !== 'undefined') { - pages[currentPage].saveSettings(settings); + if (typeof pages[currentPage].onPageClosed !== 'undefined') { + pages[currentPage].onPageClosed(settings); } print ("switchpage: start: currentPage: ", currentPage); @@ -61,7 +61,6 @@ Rectangle { pages[currentPage].opacity = 1; handlePageChanged(); } - } function handlePageChanged() { @@ -91,9 +90,9 @@ Rectangle { nextButton.enabled = true } - } + function openCreateWalletPage() { print ("show create wallet page"); pages[currentPage].opacity = 0; diff --git a/wizard/WizardManageWalletUI.qml b/wizard/WizardManageWalletUI.qml index ce035f0a..58f4e59a 100644 --- a/wizard/WizardManageWalletUI.qml +++ b/wizard/WizardManageWalletUI.qml @@ -206,7 +206,7 @@ Item { verticalAlignment: Text.AlignVCenter selectByMouse: true - text: moneroAccountsDir + "/My Wallet" + text: moneroAccountsDir + "/" onFocusChanged: { if(focus) { fileDialog.folder = text diff --git a/wizard/WizardPassword.qml b/wizard/WizardPassword.qml index 1bcc6213..aca45d4d 100644 --- a/wizard/WizardPassword.qml +++ b/wizard/WizardPassword.qml @@ -43,7 +43,7 @@ Item { onOpacityChanged: visible = opacity !== 0 - function saveSettings(settingsObject) { + function onPageClosed(settingsObject) { settingsObject.wallet.setPassword(passwordItem.password) } diff --git a/wizard/WizardRecoveryWallet.qml b/wizard/WizardRecoveryWallet.qml index 572d776b..e7fd6c0d 100644 --- a/wizard/WizardRecoveryWallet.qml +++ b/wizard/WizardRecoveryWallet.qml @@ -40,7 +40,7 @@ Item { onOpacityChanged: visible = opacity !== 0 - function saveSettings(settingsObject) { + function onPageClosed(settingsObject) { settingsObject['account_name'] = uiItem.accountNameText settingsObject['words'] = uiItem.wordsTexttext settingsObject['wallet_path'] = uiItem.walletPath diff --git a/wizard/WizardWelcome.qml b/wizard/WizardWelcome.qml index 208a7994..bdd88b70 100644 --- a/wizard/WizardWelcome.qml +++ b/wizard/WizardWelcome.qml @@ -36,8 +36,11 @@ Item { onOpacityChanged: visible = opacity !== 0 - function saveSettings(settingsObject) { - settingsObject['language'] = languagesModel.get(gridView.currentIndex).name + function onPageClosed(settingsObject) { + var lang = languagesModel.get(gridView.currentIndex); + settingsObject['language'] = lang.display_name; + settingsObject['wallet_language'] = lang.wallet_name; + settingsObject['locale'] = lang.locale; } Column { @@ -78,7 +81,9 @@ Item { source: "/lang/languages.xml" query: "/languages/language" - XmlRole { name: "name"; query: "@name/string()" } + XmlRole { name: "display_name"; query: "@display_name/string()" } + XmlRole { name: "locale"; query: "@locale/string()" } + XmlRole { name: "wallet_name"; query: "@wallet_name/string()" } XmlRole { name: "flag"; query: "@flag/string()" } // TODO: XmlListModel is read only, we should store current language somewhere else // and set current language accordingly @@ -126,7 +131,7 @@ Item { font.bold: gridView.currentIndex === index elide: Text.ElideRight color: "#3F3F3F" - text: name + text: display_name } MouseArea { id: delegateArea From b7787dc6704ee8395ba20adf0f09af6fc3eb9f8a Mon Sep 17 00:00:00 2001 From: Ilya Kitaev Date: Tue, 14 Jun 2016 12:40:29 +0300 Subject: [PATCH 24/87] Moving wallet to the user defined location at the "donation" page (pre- final) --- wizard/WizardCreateWallet.qml | 12 ------------ wizard/WizardDonation.qml | 17 +++++++++++++++++ 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/wizard/WizardCreateWallet.qml b/wizard/WizardCreateWallet.qml index 653db251..2332dd5c 100644 --- a/wizard/WizardCreateWallet.qml +++ b/wizard/WizardCreateWallet.qml @@ -49,19 +49,7 @@ Item { // put wallet files to the subdirectory with the same name as // wallet name - var new_wallet_filename = settingsObject.wallet_path + "/" - + settingsObject.account_name + "/" - + settingsObject.account_name; - // moving wallet files to the new destination, if user changed it - if (new_wallet_filename !== settingsObject.wallet_filename) { - // using previously saved wallet; - settingsObject.wallet.store(new_wallet_filename); - //walletManager.moveWallet(settingsObject.wallet_filename, new_wallet_filename); - } - - // saving wallet_filename; - settingsObject['wallet_filename'] = new_wallet_filename; } //! function called each time we hide this page diff --git a/wizard/WizardDonation.qml b/wizard/WizardDonation.qml index 51a28029..8cfc68f2 100644 --- a/wizard/WizardDonation.qml +++ b/wizard/WizardDonation.qml @@ -42,6 +42,23 @@ Item { settingsObject['auto_donations_enabled'] = enableAutoDonationCheckBox.checked; settingsObject['auto_donations_amount'] = autoDonationAmountText.text; settingsObject['allow_background_mining'] = allowBackgroundMiningCheckBox.checked; + + // here we need to actually move wallet to the new location + // put wallet files to the subdirectory with the same name as + // wallet name + var new_wallet_filename = settingsObject.wallet_path + "/" + + settingsObject.account_name + "/" + + settingsObject.account_name; + + // moving wallet files to the new destination, if user changed it + if (new_wallet_filename !== settingsObject.wallet_filename) { + // using previously saved wallet; + settingsObject.wallet.store(new_wallet_filename); + //walletManager.moveWallet(settingsObject.wallet_filename, new_wallet_filename); + } + + // saving wallet_filename; + settingsObject['wallet_filename'] = new_wallet_filename; } Row { From 1eac46ae734d31df568cf0f51dd1d5b7e7244cdc Mon Sep 17 00:00:00 2001 From: Ilya Kitaev Date: Tue, 14 Jun 2016 16:09:14 +0300 Subject: [PATCH 25/87] "new wallet" and "recovery wallet" flows are implemented using libwallet api --- main.cpp | 2 +- wizard/WizardCreateWallet.qml | 5 +---- wizard/WizardDonation.qml | 15 +------------ wizard/WizardFinish.qml | 2 ++ wizard/WizardMain.qml | 40 +++++++++++++++++++++++++++++---- wizard/WizardPassword.qml | 1 + wizard/WizardRecoveryWallet.qml | 22 ++++++++++++++---- wizard/WizardWelcome.qml | 1 + 8 files changed, 61 insertions(+), 27 deletions(-) diff --git a/main.cpp b/main.cpp index 39e4635d..a4a79947 100644 --- a/main.cpp +++ b/main.cpp @@ -47,7 +47,7 @@ int main(int argc, char *argv[]) app.installEventFilter(eventFilter); qmlRegisterType("moneroComponents", 1, 0, "Clipboard"); - qmlRegisterInterface("Wallet"); + qmlRegisterUncreatableType("Bitmonero.Wallet", 1, 0, "Wallet", "Wallet can't be instantiated directly"); QQmlApplicationEngine engine; diff --git a/wizard/WizardCreateWallet.qml b/wizard/WizardCreateWallet.qml index 2332dd5c..0aad0052 100644 --- a/wizard/WizardCreateWallet.qml +++ b/wizard/WizardCreateWallet.qml @@ -46,10 +46,7 @@ Item { settingsObject['account_name'] = uiItem.accountNameText settingsObject['words'] = uiItem.wordsTexttext settingsObject['wallet_path'] = uiItem.walletPath - - // put wallet files to the subdirectory with the same name as - // wallet name - + return true; } //! function called each time we hide this page diff --git a/wizard/WizardDonation.qml b/wizard/WizardDonation.qml index 8cfc68f2..e0b73df8 100644 --- a/wizard/WizardDonation.qml +++ b/wizard/WizardDonation.qml @@ -43,22 +43,9 @@ Item { settingsObject['auto_donations_amount'] = autoDonationAmountText.text; settingsObject['allow_background_mining'] = allowBackgroundMiningCheckBox.checked; - // here we need to actually move wallet to the new location - // put wallet files to the subdirectory with the same name as - // wallet name - var new_wallet_filename = settingsObject.wallet_path + "/" - + settingsObject.account_name + "/" - + settingsObject.account_name; - // moving wallet files to the new destination, if user changed it - if (new_wallet_filename !== settingsObject.wallet_filename) { - // using previously saved wallet; - settingsObject.wallet.store(new_wallet_filename); - //walletManager.moveWallet(settingsObject.wallet_filename, new_wallet_filename); - } - // saving wallet_filename; - settingsObject['wallet_filename'] = new_wallet_filename; + return true; } Row { diff --git a/wizard/WizardFinish.qml b/wizard/WizardFinish.qml index 311dfd62..427a2340 100644 --- a/wizard/WizardFinish.qml +++ b/wizard/WizardFinish.qml @@ -53,6 +53,8 @@ Item { + buildSettingsString(); } + + Row { id: dotsRow anchors.top: parent.top diff --git a/wizard/WizardMain.qml b/wizard/WizardMain.qml index a19c622c..da3a9824 100644 --- a/wizard/WizardMain.qml +++ b/wizard/WizardMain.qml @@ -49,8 +49,12 @@ Rectangle { function switchPage(next) { // save settings for current page; - if (typeof pages[currentPage].onPageClosed !== 'undefined') { - pages[currentPage].onPageClosed(settings); + if (next && typeof pages[currentPage].onPageClosed !== 'undefined') { + if (pages[currentPage].onPageClosed(settings) !== true) { + print ("Can't go to the next page"); + return; + }; + } print ("switchpage: start: currentPage: ", currentPage); @@ -59,6 +63,10 @@ Rectangle { var step_value = next ? 1 : -1 currentPage += step_value pages[currentPage].opacity = 1; + + if (next && typeof pages[currentPage].onPageOpened !== 'undefined') { + pages[currentPage].onPageOpened(settings) + } handlePageChanged(); } } @@ -84,7 +92,7 @@ Rectangle { break; case recoveryWalletPage: // TODO: disable "next button" until 25 words private key entered - // nextButton.enabled = false; + nextButton.enabled = false break default: nextButton.enabled = true @@ -115,6 +123,27 @@ Rectangle { handlePageChanged() } + //! actually writes the wallet + function applySettings() { + print ("Here we apply the settings"); + // here we need to actually move wallet to the new location + // put wallet files to the subdirectory with the same name as + // wallet name + var new_wallet_filename = settings.wallet_path + "/" + + settings.account_name + "/" + + settings.account_name; + + // moving wallet files to the new destination, if user changed it + if (new_wallet_filename !== settings.wallet_filename) { + // using previously saved wallet; + settings.wallet.store(new_wallet_filename); + //walletManager.moveWallet(settingsObject.wallet_filename, new_wallet_filename); + } + + // saving wallet_filename; + settings['wallet_filename'] = new_wallet_filename; + } + @@ -255,6 +284,9 @@ Rectangle { releasedColor: "#FF6C3C" pressedColor: "#FF4304" visible: parent.paths[currentPath][currentPage] === finishPage - onClicked: wizard.useMoneroClicked() + onClicked: { + wizard.applySettings(); + wizard.useMoneroClicked() + } } } diff --git a/wizard/WizardPassword.qml b/wizard/WizardPassword.qml index aca45d4d..6b14dae9 100644 --- a/wizard/WizardPassword.qml +++ b/wizard/WizardPassword.qml @@ -45,6 +45,7 @@ Item { function onPageClosed(settingsObject) { settingsObject.wallet.setPassword(passwordItem.password) + return true } function handlePassword() { diff --git a/wizard/WizardRecoveryWallet.qml b/wizard/WizardRecoveryWallet.qml index e7fd6c0d..19530157 100644 --- a/wizard/WizardRecoveryWallet.qml +++ b/wizard/WizardRecoveryWallet.qml @@ -29,6 +29,7 @@ import QtQuick 2.2 import moneroComponents 1.0 import QtQuick.Dialogs 1.2 +import Bitmonero.Wallet 1.0 Item { opacity: 0 @@ -42,12 +43,25 @@ Item { function onPageClosed(settingsObject) { settingsObject['account_name'] = uiItem.accountNameText - settingsObject['words'] = uiItem.wordsTexttext + settingsObject['words'] = cleanWordsInput(uiItem.wordsTextItem.memoText) settingsObject['wallet_path'] = uiItem.walletPath + return recoveryWallet(settingsObject) } - function recoveryWallet() { + function recoveryWallet(settingsObject) { + var testnet = true; + var wallet = walletManager.recoveryWallet(oshelper.temporaryFilename(), settingsObject.words, testnet); + var success = wallet.status === Wallet.Status_Ok; + if (success) { + settingsObject['wallet'] = wallet; + } else { + walletManager.closeWallet(wallet); + } + return success; + } + function cleanWordsInput(text) { + return text.trim().replace(/(\r\n|\n|\r)/gm, " "); } WizardManageWalletUI { @@ -60,8 +74,8 @@ Item { wordsTextItem.memoTextReadOnly: false wordsTextItem.memoText: "" wordsTextItem.onMemoTextChanged: { - var wordsArray = wordsTextItem.memoText.trim().split(" ") - //wizard.nextButton.enabled = wordsArray.length === 25 + var wordsArray = cleanWordsInput(wordsTextItem.memoText).split(" "); + wizard.nextButton.enabled = wordsArray.length === 25 } } } diff --git a/wizard/WizardWelcome.qml b/wizard/WizardWelcome.qml index bdd88b70..8917d212 100644 --- a/wizard/WizardWelcome.qml +++ b/wizard/WizardWelcome.qml @@ -41,6 +41,7 @@ Item { settingsObject['language'] = lang.display_name; settingsObject['wallet_language'] = lang.wallet_name; settingsObject['locale'] = lang.locale; + return true } Column { From 2151f1395c28027f867d8e640b0c0be4cd4e0a23 Mon Sep 17 00:00:00 2001 From: Ilya Kitaev Date: Wed, 15 Jun 2016 13:25:45 +0300 Subject: [PATCH 26/87] Persistent storage for common settings. closes #10 --- main.cpp | 5 +++++ main.qml | 2 ++ wizard/WizardDonation.qml | 12 ++++++++---- wizard/WizardMain.qml | 26 ++++++++++++++++++++++++++ 4 files changed, 41 insertions(+), 4 deletions(-) diff --git a/main.cpp b/main.cpp index a4a79947..7331a4e4 100644 --- a/main.cpp +++ b/main.cpp @@ -43,6 +43,11 @@ int main(int argc, char *argv[]) { QApplication app(argc, argv); + + app.setApplicationName("monero-core"); + app.setOrganizationDomain("getmonero.org"); + app.setOrganizationName("The Monero Project"); + filter *eventFilter = new filter; app.installEventFilter(eventFilter); diff --git a/main.qml b/main.qml index a36a3755..0eb87b2e 100644 --- a/main.qml +++ b/main.qml @@ -30,6 +30,8 @@ import QtQuick 2.2 import QtQuick.Window 2.0 import QtQuick.Controls 1.1 import QtQuick.Controls.Styles 1.1 + + import "components" import "wizard" diff --git a/wizard/WizardDonation.qml b/wizard/WizardDonation.qml index e0b73df8..80ebd78b 100644 --- a/wizard/WizardDonation.qml +++ b/wizard/WizardDonation.qml @@ -38,13 +38,17 @@ Item { onOpacityChanged: visible = opacity !== 0 + function onPageOpened(settingsObject) { + enableAutoDonationCheckBox.checked = settingsObject.auto_donations_enabled + autoDonationAmountText.text = settingsObject.auto_donations_amount + allowBackgroundMiningCheckBox.checked = settingsObject.allow_background_mining + + } + function onPageClosed(settingsObject) { settingsObject['auto_donations_enabled'] = enableAutoDonationCheckBox.checked; - settingsObject['auto_donations_amount'] = autoDonationAmountText.text; + settingsObject['auto_donations_amount'] = parseInt(autoDonationAmountText.text); settingsObject['allow_background_mining'] = allowBackgroundMiningCheckBox.checked; - - - return true; } diff --git a/wizard/WizardMain.qml b/wizard/WizardMain.qml index da3a9824..940243cb 100644 --- a/wizard/WizardMain.qml +++ b/wizard/WizardMain.qml @@ -27,6 +27,8 @@ // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. import QtQuick 2.2 +import Qt.labs.settings 1.0 + import "../components" Rectangle { @@ -142,10 +144,34 @@ Rectangle { // saving wallet_filename; settings['wallet_filename'] = new_wallet_filename; + + // persist settings + persistentSettings.language = settings.language + persistentSettings.account_name = settings.account_name + persistentSettings.wallet_path = settings.wallet_path + persistentSettings.allow_background_mining = settings.allow_background_mining + persistentSettings.auto_donations_enabled = settings.auto_donations_enabled + persistentSettings.auto_donations_amount = settings.auto_donations_amount + } + + // reading settings from persistent storage + Component.onCompleted: { + settings['allow_background_mining'] = persistentSettings.allow_background_mining + settings['auto_donations_enabled'] = persistentSettings.auto_donations_enabled + settings['auto_donations_amount'] = persistentSettings.auto_donations_amount } + Settings { + id: persistentSettings + property string language + property string account_name + property string wallet_path + property bool auto_donations_enabled : true + property int auto_donations_amount : 50 + property bool allow_background_mining : true + } Rectangle { id: nextButton From 3ddd9bed72da0e039f44b64a4cc9fff679dd40ed Mon Sep 17 00:00:00 2001 From: Ilya Kitaev Date: Wed, 15 Jun 2016 16:34:55 +0300 Subject: [PATCH 27/87] main application: initialize wallet, display balance --- BasicPanel.qml | 3 ++ LeftPanel.qml | 14 +++++++--- main.qml | 58 +++++++++++++++++++++++++++++++++++++-- wizard/WizardMain.qml | 37 +++++++++++++------------ wizard/WizardPassword.qml | 4 ++- 5 files changed, 91 insertions(+), 25 deletions(-) diff --git a/BasicPanel.qml b/BasicPanel.qml index 20a07d64..5698316f 100644 --- a/BasicPanel.qml +++ b/BasicPanel.qml @@ -35,6 +35,8 @@ Rectangle { color: "#F0EEEE" border.width: 1 border.color: "#DBDBDB" + property alias balanceText : balanceText.text; + property alias unlockedBalanceText : availableBalanceText.text; Rectangle { id: header @@ -63,6 +65,7 @@ Rectangle { columns: 3 Text { + width: 116 height: 20 font.family: "Arial" diff --git a/LeftPanel.qml b/LeftPanel.qml index 6bd400a8..1eddf019 100644 --- a/LeftPanel.qml +++ b/LeftPanel.qml @@ -31,6 +31,10 @@ import "components" Rectangle { id: panel + + property alias unlockedBalanceText: unlockedBalanceText.text + property alias balanceText: balanceText.text + signal dashboardClicked() signal historyClicked() signal transferClicked() @@ -90,7 +94,7 @@ Rectangle { spacing: 6 Label { - text: qsTr("Locked balance") + text: qsTr("Balance") anchors.left: parent.left anchors.leftMargin: 50 tipText: qsTr("Test tip 1

line 2") @@ -109,11 +113,12 @@ Rectangle { } Text { + id: balanceText anchors.verticalCenter: parent.verticalCenter font.family: "Arial" font.pixelSize: 26 color: "#000000" - text: "78.9239845" + text: "78.9245" } } @@ -124,19 +129,20 @@ Rectangle { } Label { - text: qsTr("Unlocked") + text: qsTr("Unlocked balance") anchors.left: parent.left anchors.leftMargin: 50 tipText: qsTr("Test tip 2

line 2") } Text { + id: unlockedBalanceText anchors.left: parent.left anchors.leftMargin: 50 font.family: "Arial" font.pixelSize: 18 color: "#000000" - text: "2324.9239845" + text: "2324.9245" } } diff --git a/main.qml b/main.qml index 0eb87b2e..3b79d1ec 100644 --- a/main.qml +++ b/main.qml @@ -30,7 +30,8 @@ import QtQuick 2.2 import QtQuick.Window 2.0 import QtQuick.Controls 1.1 import QtQuick.Controls.Styles 1.1 - +import Qt.labs.settings 1.0 +import Bitmonero.Wallet 1.0 import "components" import "wizard" @@ -43,12 +44,16 @@ ApplicationWindow { property bool ctrlPressed: false property bool rightPanelExpanded: true property bool osx: false + property alias persistentSettings : persistentSettings + property var wallet; function altKeyReleased() { ctrlPressed = false; } + function showPageRequest(page) { middlePanel.state = page leftPanel.selectItem(page) } + function sequencePressed(obj, seq) { if(seq === undefined) return @@ -112,6 +117,37 @@ ApplicationWindow { } + + function initialize() { + + if (typeof wizard.settings['wallet'] !== 'undefined') { + wallet = wizard.settings['wallet']; + } else { + var wallet_path = persistentSettings.wallet_path + "/" + persistentSettings.account_name + "/" + + persistentSettings.account_name; + console.log("opening wallet at: ", wallet_path); + // TODO: wallet password dialog + wallet = walletManager.openWallet(wallet_path, "", persistentSettings.testnet); + if (wallet.status !== Wallet.Status_Ok) { + console.log("Error opening wallet: ", wallet.errorString); + return; + } + console.log("Wallet opened successfully: ", wallet.errorString); + } + + if (!wallet.init(persistentSettings.daemon_address, 0)) { + console.log("Error initialize wallet: ", wallet.errorString); + return + } + // TODO: refresh asynchronously without blocking UI, implement signal(s) + wallet.refresh(); + + console.log("wallet balance: ", wallet.balance) + leftPanel.unlockedBalanceText = walletManager.displayAmount(wallet.unlockedBalance); + leftPanel.balanceText = walletManager.displayAmount(wallet.balance); + } + + function walletsFound() { var wallets = walletManager.findWallets(moneroAccountsDir); if (wallets.length === 0) { @@ -133,6 +169,21 @@ ApplicationWindow { y = (Screen.height - height) / 2 // rootItem.state = walletsFound() ? "normal" : "wizard"; + if (rootItem.state === "normal") { + initialize(persistentSettings) + } + } + + Settings { + id: persistentSettings + property string language + property string account_name + property string wallet_path + property bool auto_donations_enabled : true + property int auto_donations_amount : 50 + property bool allow_background_mining : true + property bool testnet: true + property string daemon_address: "localhost:38081" } Item { @@ -344,7 +395,10 @@ ApplicationWindow { WizardMain { id: wizard anchors.fill: parent - onUseMoneroClicked: rootItem.state = "normal" + onUseMoneroClicked: { + rootItem.state = "normal" // TODO: listen for this state change in appWindow; + appWindow.initialize(); + } } property int maxWidth: leftPanel.width + 655 + rightPanel.width diff --git a/wizard/WizardMain.qml b/wizard/WizardMain.qml index 940243cb..3c4103ce 100644 --- a/wizard/WizardMain.qml +++ b/wizard/WizardMain.qml @@ -146,32 +146,33 @@ Rectangle { settings['wallet_filename'] = new_wallet_filename; // persist settings - persistentSettings.language = settings.language - persistentSettings.account_name = settings.account_name - persistentSettings.wallet_path = settings.wallet_path - persistentSettings.allow_background_mining = settings.allow_background_mining - persistentSettings.auto_donations_enabled = settings.auto_donations_enabled - persistentSettings.auto_donations_amount = settings.auto_donations_amount + appWindow.persistentSettings.language = settings.language + appWindow.persistentSettings.account_name = settings.account_name + appWindow.persistentSettings.wallet_path = settings.wallet_path + appWindow.persistentSettings.allow_background_mining = settings.allow_background_mining + appWindow.persistentSettings.auto_donations_enabled = settings.auto_donations_enabled + appWindow.persistentSettings.auto_donations_amount = settings.auto_donations_amount } // reading settings from persistent storage Component.onCompleted: { - settings['allow_background_mining'] = persistentSettings.allow_background_mining - settings['auto_donations_enabled'] = persistentSettings.auto_donations_enabled - settings['auto_donations_amount'] = persistentSettings.auto_donations_amount + console.log("rootItem: ", appWindow); + settings['allow_background_mining'] = appWindow.persistentSettings.allow_background_mining + settings['auto_donations_enabled'] = appWindow.persistentSettings.auto_donations_enabled + settings['auto_donations_amount'] = appWindow.persistentSettings.auto_donations_amount } - Settings { - id: persistentSettings +// Settings { +// id: persistentSettings - property string language - property string account_name - property string wallet_path - property bool auto_donations_enabled : true - property int auto_donations_amount : 50 - property bool allow_background_mining : true - } +// property string language +// property string account_name +// property string wallet_path +// property bool auto_donations_enabled : true +// property int auto_donations_amount : 50 +// property bool allow_background_mining : true +// } Rectangle { id: nextButton diff --git a/wizard/WizardPassword.qml b/wizard/WizardPassword.qml index 6b14dae9..4825c02d 100644 --- a/wizard/WizardPassword.qml +++ b/wizard/WizardPassword.qml @@ -44,7 +44,9 @@ Item { onOpacityChanged: visible = opacity !== 0 function onPageClosed(settingsObject) { - settingsObject.wallet.setPassword(passwordItem.password) + // TODO: set password on the final page + // settingsObject.wallet.setPassword(passwordItem.password) + settingsObject['wallet_password'] = passwordItem.password return true } From eaf59243b2cbe3d7ea6de1d8dcd18493df9f365d Mon Sep 17 00:00:00 2001 From: Ilya Kitaev Date: Thu, 16 Jun 2016 17:13:46 +0300 Subject: [PATCH 28/87] basic "send money" functionality implemented in GUI --- LeftPanel.qml | 2 ++ MiddlePanel.qml | 14 ++++++++++++++ components/LineEdit.qml | 3 +++ main.cpp | 3 +++ main.qml | 25 +++++++++++++++++++++++++ pages/Transfer.qml | 27 +++++++++++++++++++++------ src/libwalletqt/PendingTransaction.h | 2 ++ src/libwalletqt/WalletManager.cpp | 9 +++++++++ src/libwalletqt/WalletManager.h | 3 +++ 9 files changed, 82 insertions(+), 6 deletions(-) diff --git a/LeftPanel.qml b/LeftPanel.qml index 1eddf019..95b5aa6f 100644 --- a/LeftPanel.qml +++ b/LeftPanel.qml @@ -56,6 +56,7 @@ Rectangle { width: 260 color: "#FFFFFF" + // Item with monero logo Item { id: logoItem anchors.left: parent.left @@ -85,6 +86,7 @@ Rectangle { } } + Column { id: column1 anchors.left: parent.left diff --git a/MiddlePanel.qml b/MiddlePanel.qml index 62072af9..28d6f137 100644 --- a/MiddlePanel.qml +++ b/MiddlePanel.qml @@ -30,6 +30,7 @@ import QtQuick 2.2 Rectangle { color: "#F0EEEE" + signal paymentClicked(string address, string paymentId, double amount, double fee, int privacyLevel) states: [ State { @@ -72,6 +73,19 @@ Rectangle { anchors.right: parent.right anchors.top: styledRow.bottom anchors.bottom: parent.bottom + onLoaded: { + console.log("Loaded " + item); + } + + } + + Connections { + ignoreUnknownSignals: false + target: loader.item + onPaymentClicked : { + console.log("MiddlePanel: paymentClicked") + paymentClicked(address, paymentId, amount, fee, privacyLevel) + } } Rectangle { diff --git a/components/LineEdit.qml b/components/LineEdit.qml index 6994b75f..37c2390d 100644 --- a/components/LineEdit.qml +++ b/components/LineEdit.qml @@ -31,7 +31,9 @@ import QtQuick 2.0 Item { property alias placeholderText: input.placeholderText property alias text: input.text + property alias validator: input.validator property int fontSize: 18 + height: 37 Rectangle { @@ -54,5 +56,6 @@ Item { anchors.leftMargin: 4 anchors.rightMargin: 4 font.pixelSize: parent.fontSize + } } diff --git a/main.cpp b/main.cpp index 7331a4e4..70390a80 100644 --- a/main.cpp +++ b/main.cpp @@ -36,6 +36,7 @@ #include "oshelper.h" #include "WalletManager.h" #include "Wallet.h" +#include "PendingTransaction.h" @@ -53,6 +54,8 @@ int main(int argc, char *argv[]) qmlRegisterType("moneroComponents", 1, 0, "Clipboard"); qmlRegisterUncreatableType("Bitmonero.Wallet", 1, 0, "Wallet", "Wallet can't be instantiated directly"); + qmlRegisterUncreatableType("Bitmonero.PendingTransaction", 1, 0, "PendingTransaction", + "PendingTransaction can't be instantiated directly"); QQmlApplicationEngine engine; diff --git a/main.qml b/main.qml index 3b79d1ec..d6109339 100644 --- a/main.qml +++ b/main.qml @@ -32,6 +32,7 @@ import QtQuick.Controls 1.1 import QtQuick.Controls.Styles 1.1 import Qt.labs.settings 1.0 import Bitmonero.Wallet 1.0 +import Bitmonero.PendingTransaction 1.0 import "components" import "wizard" @@ -120,6 +121,8 @@ ApplicationWindow { function initialize() { + middlePanel.paymentClicked.connect(handlePayment); + if (typeof wizard.settings['wallet'] !== 'undefined') { wallet = wizard.settings['wallet']; } else { @@ -157,6 +160,27 @@ ApplicationWindow { return wallets.length > 0; } + function handlePayment(address, paymentId, amount, fee, privacyLevel) { + console.log("Process payment here: ", address, paymentId, amount, fee, privacyLevel) + // TODO: handle payment id + // TODO: handle fee; + // TODO: handle mixins + var amountxmr = walletManager.amountFromString(amount); + + console.log("integer amount: ", amountxmr); + var pendingTransaction = wallet.createTransaction(address, amountxmr); + if (pendingTransaction.status !== PendingTransaction.Status_Ok) { + console.error("Can't create transaction: ", pendingTransaction.errorString); + } else { + console.log("Transaction created, amount: " + walletManager.displayAmount(pendingTransaction.amount) + + ", fee: " + walletManager.displayAmount(pendingTransaction.fee)); + if (!pendingTransaction.commit()) { + console.log("Error committing transaction: " + pendingTransaction.errorString); + } + } + wallet.disposeTransaction(pendingTransaction); + } + visible: true width: rightPanelExpanded ? 1269 : 1269 - 300 height: 800 @@ -423,6 +447,7 @@ ApplicationWindow { } property var previousPosition + onPressed: { previousPosition = globalCursor.getPosition() } diff --git a/pages/Transfer.qml b/pages/Transfer.qml index 2535b8fe..df0a5b20 100644 --- a/pages/Transfer.qml +++ b/pages/Transfer.qml @@ -30,8 +30,11 @@ import QtQuick 2.0 import "../components" Rectangle { + signal paymentClicked(string address, string paymentId, double amount, double fee, int privacyLevel) + color: "#F0EEEE" + Label { id: amountLabel anchors.left: parent.left @@ -67,8 +70,9 @@ Rectangle { source: "../images/moneroIcon.png" } } - + // Amount input LineEdit { + id: amountLine placeholderText: qsTr("Amount...") width: parent.width - 37 - 17 } @@ -133,7 +137,7 @@ Rectangle { onLinkActivated: appWindow.showPageRequest("AddressBook") } - + // recipient address input LineEdit { id: addressLine anchors.left: parent.left @@ -142,10 +146,11 @@ Rectangle { anchors.leftMargin: 17 anchors.rightMargin: 17 anchors.topMargin: 5 + // validator: RegExpValidator { regExp: /[0-9A-Fa-f]{95}/g } } Label { - id: paymentLabel + id: paymentIdLabel anchors.left: parent.left anchors.right: parent.right anchors.top: addressLine.bottom @@ -156,21 +161,23 @@ Rectangle { text: qsTr("Payment ID ( Optional )") } + // payment id input LineEdit { - id: paymentLine + id: paymentIdLine anchors.left: parent.left anchors.right: parent.right - anchors.top: paymentLabel.bottom + anchors.top: paymentIdLabel.bottom anchors.leftMargin: 17 anchors.rightMargin: 17 anchors.topMargin: 5 + // validator: DoubleValidator { top: 0.0 } } Label { id: descriptionLabel anchors.left: parent.left anchors.right: parent.right - anchors.top: paymentLine.bottom + anchors.top: paymentIdLine.bottom anchors.leftMargin: 17 anchors.rightMargin: 17 anchors.topMargin: 17 @@ -200,5 +207,13 @@ Rectangle { shadowPressedColor: "#B32D00" releasedColor: "#FF6C3C" pressedColor: "#FF4304" + onClicked: { + // do more smart validation + + if (addressLine.text.length > 0 && amountLine.text.length > 0) { + console.log("paymentClicked") + paymentClicked(addressLine.text, paymentIdLine.text, amountLine.text, 0.0002, 1) + } + } } } diff --git a/src/libwalletqt/PendingTransaction.h b/src/libwalletqt/PendingTransaction.h index 29fa7cb4..d8c4ec1e 100644 --- a/src/libwalletqt/PendingTransaction.h +++ b/src/libwalletqt/PendingTransaction.h @@ -24,6 +24,8 @@ public: Status_Error = Bitmonero::PendingTransaction::Status_Error }; + Q_ENUM(Status) + Status status() const; QString errorString() const; Q_INVOKABLE bool commit(); diff --git a/src/libwalletqt/WalletManager.cpp b/src/libwalletqt/WalletManager.cpp index 6ad81e2a..a7742bca 100644 --- a/src/libwalletqt/WalletManager.cpp +++ b/src/libwalletqt/WalletManager.cpp @@ -92,6 +92,15 @@ QString WalletManager::displayAmount(quint64 amount) return QString::fromStdString(Bitmonero::Wallet::displayAmount(amount)); } +quint64 WalletManager::amountFromString(const QString &amount) +{ + return Bitmonero::Wallet::amountFromString(amount.toStdString()); +} + +quint64 WalletManager::amountFromDouble(double amount) +{ + return Bitmonero::Wallet::amountFromDouble(amount); +} WalletManager::WalletManager(QObject *parent) : QObject(parent) { diff --git a/src/libwalletqt/WalletManager.h b/src/libwalletqt/WalletManager.h index 30e6b02a..29df9048 100644 --- a/src/libwalletqt/WalletManager.h +++ b/src/libwalletqt/WalletManager.h @@ -45,6 +45,9 @@ public: //! since we can't call static method from QML, move it to this class Q_INVOKABLE QString displayAmount(quint64 amount); + Q_INVOKABLE quint64 amountFromString(const QString &amount); + Q_INVOKABLE quint64 amountFromDouble(double amount); + signals: public slots: From 8ac86a8042f23e524f1f5245ada9ac7a1c2fc191 Mon Sep 17 00:00:00 2001 From: Ilya Kitaev Date: Fri, 17 Jun 2016 16:35:07 +0300 Subject: [PATCH 29/87] Balance on UI updated by the signal --- main.qml | 12 ++++++++++++ src/libwalletqt/Wallet.cpp | 6 +++++- src/libwalletqt/Wallet.h | 3 +++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/main.qml b/main.qml index d6109339..e193ae29 100644 --- a/main.qml +++ b/main.qml @@ -142,10 +142,19 @@ ApplicationWindow { console.log("Error initialize wallet: ", wallet.errorString); return } + + // subscribing for wallet updates + wallet.updated.connect(onWalletUpdate); + // TODO: refresh asynchronously without blocking UI, implement signal(s) wallet.refresh(); console.log("wallet balance: ", wallet.balance) + + } + + function onWalletUpdate() { + console.log("wallet updated") leftPanel.unlockedBalanceText = walletManager.displayAmount(wallet.unlockedBalance); leftPanel.balanceText = walletManager.displayAmount(wallet.balance); } @@ -176,8 +185,11 @@ ApplicationWindow { + ", fee: " + walletManager.displayAmount(pendingTransaction.fee)); if (!pendingTransaction.commit()) { console.log("Error committing transaction: " + pendingTransaction.errorString); + } else { + wallet.refresh(); } } + wallet.disposeTransaction(pendingTransaction); } diff --git a/src/libwalletqt/Wallet.cpp b/src/libwalletqt/Wallet.cpp index 77901b0c..901075b6 100644 --- a/src/libwalletqt/Wallet.cpp +++ b/src/libwalletqt/Wallet.cpp @@ -7,6 +7,7 @@ #include #include #include +#include namespace { @@ -81,7 +82,10 @@ quint64 Wallet::unlockedBalance() const bool Wallet::refresh() { - return m_walletImpl->refresh(); + bool result = m_walletImpl->refresh(); + if (result) + emit updated(); + return result; } PendingTransaction *Wallet::createTransaction(const QString &dst_addr, quint64 amount) diff --git a/src/libwalletqt/Wallet.h b/src/libwalletqt/Wallet.h index 7d391429..f8263040 100644 --- a/src/libwalletqt/Wallet.h +++ b/src/libwalletqt/Wallet.h @@ -85,6 +85,9 @@ public: TransactionHistory * history(); // TODO: setListenter() when it implemented in API +signals: + void updated(); + private: Wallet(Bitmonero::Wallet *w, QObject * parent = 0); From e3985da4e1c95e21716fbee4824ba9ca471ef220 Mon Sep 17 00:00:00 2001 From: Ilya Kitaev Date: Mon, 20 Jun 2016 15:58:13 +0300 Subject: [PATCH 30/87] Transfer page: priority dropdown hidded --- pages/Transfer.qml | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/pages/Transfer.qml b/pages/Transfer.qml index df0a5b20..f215ab27 100644 --- a/pages/Transfer.qml +++ b/pages/Transfer.qml @@ -45,7 +45,7 @@ Rectangle { text: qsTr("Amount") fontSize: 14 } - + /* Label { id: transactionPriority anchors.top: parent.top @@ -54,8 +54,11 @@ Rectangle { x: (parent.width - 17) / 2 + 17 text: qsTr("Transaction prority") } + */ + Row { + id: amountRow anchors.top: amountLabel.bottom anchors.topMargin: 5 anchors.left: parent.left @@ -84,7 +87,7 @@ Rectangle { ListElement { column1: "MEDIUM"; column2: "( fee: 0.0004 )" } ListElement { column1: "HIGH"; column2: "( fee: 0.0008 )" } } - + /* StandardDropdown { id: priorityDropdown anchors.top: transactionPriority.bottom @@ -99,12 +102,14 @@ Rectangle { dataModel: priorityModel z: 1 } + */ + Label { id: privacyLabel anchors.left: parent.left anchors.right: parent.right - anchors.top: priorityDropdown.bottom + anchors.top: amountRow.bottom anchors.leftMargin: 17 anchors.rightMargin: 17 anchors.topMargin: 30 @@ -122,6 +127,19 @@ Rectangle { anchors.topMargin: 5 } + + Label { + id: costLabel + anchors.right: parent.right + anchors.top: amountRow.bottom + anchors.leftMargin: 17 + anchors.rightMargin: 17 + anchors.topMargin: 30 + fontSize: 14 + text: qsTr("Cost") + } + + Label { id: addressLabel anchors.left: parent.left From 37c1c0bb7904c6b83f8a2e37049ece5af499cce1 Mon Sep 17 00:00:00 2001 From: Riccardo Spagni Date: Mon, 20 Jun 2016 21:08:09 +0200 Subject: [PATCH 31/87] disable right panel by default, don't perform Twitter https call if panel is disabled --- main.qml | 2 +- tabs/TweetsModel.qml | 41 ++++++++++++++++++++++------------------- 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/main.qml b/main.qml index e193ae29..0ec6dafc 100644 --- a/main.qml +++ b/main.qml @@ -43,7 +43,7 @@ ApplicationWindow { property var currentItem property bool whatIsEnable: false property bool ctrlPressed: false - property bool rightPanelExpanded: true + property bool rightPanelExpanded: false property bool osx: false property alias persistentSettings : persistentSettings property var wallet; diff --git a/tabs/TweetsModel.qml b/tabs/TweetsModel.qml index 0e9fe004..a6b40aa6 100644 --- a/tabs/TweetsModel.qml +++ b/tabs/TweetsModel.qml @@ -60,30 +60,33 @@ Item { if (from == "" && phrase == "") return; + if (appWindow.rightPanelExpanded) { + //! [requesting] - var req = new XMLHttpRequest; - req.open("GET", "https://api.twitter.com/1.1/search/tweets.json?from=" + from + - "&count=" + tweetsMaxCount + "&q=" + encodePhrase(phrase)); - req.setRequestHeader("Authorization", "Bearer " + bearerToken); - req.onreadystatechange = function() { - status = req.readyState; - if (status === XMLHttpRequest.DONE) { - var objectArray = JSON.parse(req.responseText); - if (objectArray.errors !== undefined) - console.log("Error fetching tweets: " + objectArray.errors[0].message) - else { - for (var key in objectArray.statuses) { - var jsonObject = objectArray.statuses[key]; - tweets.append(jsonObject); + var req = new XMLHttpRequest; + req.open("GET", "https://api.twitter.com/1.1/search/tweets.json?from=" + from + + "&count=" + tweetsMaxCount + "&q=" + encodePhrase(phrase)); + req.setRequestHeader("Authorization", "Bearer " + bearerToken); + req.onreadystatechange = function() { + status = req.readyState; + if (status === XMLHttpRequest.DONE) { + var objectArray = JSON.parse(req.responseText); + if (objectArray.errors !== undefined) + console.log("Error fetching tweets: " + objectArray.errors[0].message) + else { + for (var key in objectArray.statuses) { + var jsonObject = objectArray.statuses[key]; + tweets.append(jsonObject); + } } + if (wasLoading == true) + wrapper.isLoaded() } - if (wasLoading == true) - wrapper.isLoaded() + wasLoading = (status === XMLHttpRequest.LOADING); } - wasLoading = (status === XMLHttpRequest.LOADING); - } - req.send(); + req.send(); //! [requesting] + } } From 2696e49a54c5ef028f7e4e74ffe3b18e205cd28f Mon Sep 17 00:00:00 2001 From: Ilya Kitaev Date: Mon, 20 Jun 2016 23:36:56 +0300 Subject: [PATCH 32/87] mixin count for Wallet::createTransaction --- MiddlePanel.qml | 4 ++-- main.qml | 6 +++--- pages/Transfer.qml | 17 +++++++++++++++-- src/libwalletqt/Wallet.cpp | 4 ++-- src/libwalletqt/Wallet.h | 2 +- 5 files changed, 23 insertions(+), 10 deletions(-) diff --git a/MiddlePanel.qml b/MiddlePanel.qml index 28d6f137..edb4963d 100644 --- a/MiddlePanel.qml +++ b/MiddlePanel.qml @@ -30,7 +30,7 @@ import QtQuick 2.2 Rectangle { color: "#F0EEEE" - signal paymentClicked(string address, string paymentId, double amount, double fee, int privacyLevel) + signal paymentClicked(string address, string paymentId, double amount, int mixinCount) states: [ State { @@ -84,7 +84,7 @@ Rectangle { target: loader.item onPaymentClicked : { console.log("MiddlePanel: paymentClicked") - paymentClicked(address, paymentId, amount, fee, privacyLevel) + paymentClicked(address, paymentId, amount, mixinCount) } } diff --git a/main.qml b/main.qml index e193ae29..e6dcef64 100644 --- a/main.qml +++ b/main.qml @@ -169,15 +169,15 @@ ApplicationWindow { return wallets.length > 0; } - function handlePayment(address, paymentId, amount, fee, privacyLevel) { - console.log("Process payment here: ", address, paymentId, amount, fee, privacyLevel) + function handlePayment(address, paymentId, amount, mixinCount) { + console.log("Process payment here: ", address, paymentId, amount, mixinCount) // TODO: handle payment id // TODO: handle fee; // TODO: handle mixins var amountxmr = walletManager.amountFromString(amount); console.log("integer amount: ", amountxmr); - var pendingTransaction = wallet.createTransaction(address, amountxmr); + var pendingTransaction = wallet.createTransaction(address, amountxmr, mixinCount); if (pendingTransaction.status !== PendingTransaction.Status_Ok) { console.error("Can't create transaction: ", pendingTransaction.errorString); } else { diff --git a/pages/Transfer.qml b/pages/Transfer.qml index f215ab27..e3f96997 100644 --- a/pages/Transfer.qml +++ b/pages/Transfer.qml @@ -30,10 +30,19 @@ import QtQuick 2.0 import "../components" Rectangle { - signal paymentClicked(string address, string paymentId, double amount, double fee, int privacyLevel) + signal paymentClicked(string address, string paymentId, double amount, int mixinCount) color: "#F0EEEE" + function scaleValueToMixinCount(scaleValue) { + var scaleToMixinCount = [2,3,4,5,5,5,6,7,8,9,10,15,20,25]; + if (scaleValue < scaleToMixinCount.length) { + return scaleToMixinCount[scaleValue]; + } else { + return 0; + } + } + Label { id: amountLabel @@ -125,6 +134,10 @@ Rectangle { anchors.leftMargin: 17 anchors.rightMargin: 17 anchors.topMargin: 5 + onFillLevelChanged: { + print ("PrivacyLevel changed:" + fillLevel) + print ("mixin count:" + scaleValueToMixinCount(fillLevel)) + } } @@ -230,7 +243,7 @@ Rectangle { if (addressLine.text.length > 0 && amountLine.text.length > 0) { console.log("paymentClicked") - paymentClicked(addressLine.text, paymentIdLine.text, amountLine.text, 0.0002, 1) + paymentClicked(addressLine.text, paymentIdLine.text, amountLine.text, scaleValueToMixinCount(privacyLevelItem.fillLevel)) } } } diff --git a/src/libwalletqt/Wallet.cpp b/src/libwalletqt/Wallet.cpp index 901075b6..4e6b6787 100644 --- a/src/libwalletqt/Wallet.cpp +++ b/src/libwalletqt/Wallet.cpp @@ -88,10 +88,10 @@ bool Wallet::refresh() return result; } -PendingTransaction *Wallet::createTransaction(const QString &dst_addr, quint64 amount) +PendingTransaction *Wallet::createTransaction(const QString &dst_addr, quint64 amount, quint32 mixin_count) { Bitmonero::PendingTransaction * ptImpl = m_walletImpl->createTransaction( - dst_addr.toStdString(), amount); + dst_addr.toStdString(), amount, mixin_count); PendingTransaction * result = new PendingTransaction(ptImpl, this); return result; } diff --git a/src/libwalletqt/Wallet.h b/src/libwalletqt/Wallet.h index f8263040..4991476a 100644 --- a/src/libwalletqt/Wallet.h +++ b/src/libwalletqt/Wallet.h @@ -76,7 +76,7 @@ public: //! creates transaction Q_INVOKABLE PendingTransaction * createTransaction(const QString &dst_addr, - quint64 amount); + quint64 amount, quint32 mixin_count); //! deletes transaction and frees memory Q_INVOKABLE void disposeTransaction(PendingTransaction * t); From 88d9be953babda68cb5325258569578c11fa4438 Mon Sep 17 00:00:00 2001 From: Ilya Kitaev Date: Tue, 21 Jun 2016 11:58:06 +0300 Subject: [PATCH 33/87] Lazy loading for the tweets --- RightPanel.qml | 13 +++++++++++- main.qml | 6 ++++++ tabs/TweetsModel.qml | 48 +++++++++++++++++++------------------------- tabs/Twitter.qml | 8 ++++++++ 4 files changed, 47 insertions(+), 28 deletions(-) diff --git a/RightPanel.qml b/RightPanel.qml index b29cb28a..c5878146 100644 --- a/RightPanel.qml +++ b/RightPanel.qml @@ -36,6 +36,11 @@ Rectangle { width: 330 color: "#FFFFFF" + function updateTweets() { + tabView.twitter.item.updateTweets() + } + + TabView { id: tabView anchors.left: parent.left @@ -45,12 +50,18 @@ Rectangle { anchors.leftMargin: 14 anchors.rightMargin: 14 anchors.topMargin: 40 + property alias twitter: twitter - Tab { title: qsTr("Twitter"); source: "tabs/Twitter.qml" } + + + + Tab { id: twitter; title: qsTr("Twitter"); source: "tabs/Twitter.qml" } Tab { title: "News" } Tab { title: "Help" } Tab { title: "About" } + + style: TabViewStyle { frameOverlap: 0 tabOverlap: 0 diff --git a/main.qml b/main.qml index 7101d096..6f90bcf8 100644 --- a/main.qml +++ b/main.qml @@ -210,6 +210,12 @@ ApplicationWindow { } } + onRightPanelExpandedChanged: { + if (rightPanelExpanded) { + rightPanel.updateTweets() + } + } + Settings { id: persistentSettings property string language diff --git a/tabs/TweetsModel.qml b/tabs/TweetsModel.qml index a6b40aa6..55a7c74d 100644 --- a/tabs/TweetsModel.qml +++ b/tabs/TweetsModel.qml @@ -56,47 +56,41 @@ Item { function reload() { tweets.clear() - if (from == "" && phrase == "") return; - - if (appWindow.rightPanelExpanded) { - -//! [requesting] - var req = new XMLHttpRequest; - req.open("GET", "https://api.twitter.com/1.1/search/tweets.json?from=" + from + - "&count=" + tweetsMaxCount + "&q=" + encodePhrase(phrase)); - req.setRequestHeader("Authorization", "Bearer " + bearerToken); - req.onreadystatechange = function() { - status = req.readyState; - if (status === XMLHttpRequest.DONE) { - var objectArray = JSON.parse(req.responseText); - if (objectArray.errors !== undefined) - console.log("Error fetching tweets: " + objectArray.errors[0].message) - else { - for (var key in objectArray.statuses) { - var jsonObject = objectArray.statuses[key]; - tweets.append(jsonObject); - } + //! [requesting] + var req = new XMLHttpRequest; + req.open("GET", "https://api.twitter.com/1.1/search/tweets.json?from=" + from + + "&count=" + tweetsMaxCount + "&q=" + encodePhrase(phrase)); + req.setRequestHeader("Authorization", "Bearer " + bearerToken); + req.onreadystatechange = function() { + status = req.readyState; + if (status === XMLHttpRequest.DONE) { + var objectArray = JSON.parse(req.responseText); + if (objectArray.errors !== undefined) + console.log("Error fetching tweets: " + objectArray.errors[0].message) + else { + for (var key in objectArray.statuses) { + var jsonObject = objectArray.statuses[key]; + tweets.append(jsonObject); } - if (wasLoading == true) - wrapper.isLoaded() } - wasLoading = (status === XMLHttpRequest.LOADING); + if (wasLoading == true) + wrapper.isLoaded() } - req.send(); -//! [requesting] + wasLoading = (status === XMLHttpRequest.LOADING); } + req.send(); + //! [requesting] } - Component.onCompleted: { if (consumerKey === "" || consumerSecret == "") { console.log("setting demo token") bearerToken = encodeURIComponent(Helper.demoToken()) tweetsModel.phrase = "" tweetsModel.from = "@monerocurrency" - reload() + // reload() return; } diff --git a/tabs/Twitter.qml b/tabs/Twitter.qml index 553e7da6..bd597287 100644 --- a/tabs/Twitter.qml +++ b/tabs/Twitter.qml @@ -34,6 +34,8 @@ import "../components" Item { id: tab + + ListModel { id: testModel ListElement { head: "Monero || #xmr"; foot: "@btcplanet Duis turpis arcu, varius nec rutrum in, adipiscing at enim. Donec quis consequat ipsum," } @@ -56,10 +58,16 @@ Item { property var idx property var ids + function updateTweets() { + tweetsModel.reload() + } + + Component.onCompleted: { ids = new Array() } + function idInModel(id) { for (var j = 0; j < ids.length; j++) if (ids[j] === id) From 17f38a930e33c25392dd565f484009e5eddba56a Mon Sep 17 00:00:00 2001 From: Ilya Kitaev Date: Sun, 26 Jun 2016 18:04:45 +0300 Subject: [PATCH 34/87] Added "Receive" page. Hide all pages except "Transfer" and "Receive". --- LeftPanel.qml | 50 ++++++++- MiddlePanel.qml | 5 + components/IconButton.qml | 72 +++++++++++++ components/Input.qml | 1 + components/LineEdit.qml | 3 +- get_libwallet_api.sh | 2 + main.qml | 4 +- pages/Receive.qml | 184 ++++++++++++++++++++++++++++++++ qml.qrc | 2 + src/libwalletqt/Wallet.cpp | 25 ++++- src/libwalletqt/Wallet.h | 16 ++- wizard/WizardCreateWallet.qml | 4 +- wizard/WizardRecoveryWallet.qml | 2 +- 13 files changed, 359 insertions(+), 11 deletions(-) create mode 100644 components/IconButton.qml create mode 100644 pages/Receive.qml diff --git a/LeftPanel.qml b/LeftPanel.qml index 95b5aa6f..0c1ccd12 100644 --- a/LeftPanel.qml +++ b/LeftPanel.qml @@ -34,10 +34,12 @@ Rectangle { property alias unlockedBalanceText: unlockedBalanceText.text property alias balanceText: balanceText.text + property alias networkStatus : networkStatus signal dashboardClicked() signal historyClicked() signal transferClicked() + signal receiveClicked() signal settingsClicked() signal addressBookClicked() signal miningClicked() @@ -47,9 +49,11 @@ Rectangle { if(pos === "Dashboard") menuColumn.previousButton = dashboardButton else if(pos === "History") menuColumn.previousButton = historyButton else if(pos === "Transfer") menuColumn.previousButton = transferButton + else if(pos === "Receive") menuColumn.previousButton = receiveButton else if(pos === "AddressBook") menuColumn.previousButton = addressBookButton else if(pos === "Mining") menuColumn.previousButton = miningButton else if(pos === "Settings") menuColumn.previousButton = settingsButton + menuColumn.previousButton.checked = true } @@ -120,7 +124,7 @@ Rectangle { font.family: "Arial" font.pixelSize: 26 color: "#000000" - text: "78.9245" + text: "N/A" } } @@ -144,7 +148,7 @@ Rectangle { font.family: "Arial" font.pixelSize: 18 color: "#000000" - text: "2324.9245" + text: "N/A" } } @@ -179,7 +183,11 @@ Rectangle { anchors.right: parent.right anchors.top: parent.top - property var previousButton: dashboardButton + property var previousButton: transferButton + + // ------------- Dashboard tab --------------- + + /* MenuButton { id: dashboardButton anchors.left: parent.left @@ -195,6 +203,7 @@ Rectangle { } } + Rectangle { anchors.left: parent.left anchors.right: parent.right @@ -202,7 +211,10 @@ Rectangle { color: dashboardButton.checked || transferButton.checked ? "#1C1C1C" : "#505050" height: 1 } + */ + + // ------------- Transfer tab --------------- MenuButton { id: transferButton anchors.left: parent.left @@ -221,10 +233,35 @@ Rectangle { anchors.left: parent.left anchors.right: parent.right anchors.leftMargin: 16 - color: transferButton.checked || historyButton.checked ? "#1C1C1C" : "#505050" + color: transferButton.checked || receiveButton.checked ? "#1C1C1C" : "#505050" height: 1 } + // ------------- Receive tab --------------- + MenuButton { + id: receiveButton + anchors.left: parent.left + anchors.right: parent.right + text: qsTr("Receive") + symbol: qsTr("R") + dotColor: "#AAFFBB" + onClicked: { + parent.previousButton.checked = false + parent.previousButton = receiveButton + panel.receiveClicked() + } + } + /* + Rectangle { + anchors.left: parent.left + anchors.right: parent.right + anchors.leftMargin: 16 + color: transferButton.checked || historyButton.checked ? "#1C1C1C" : "#505050" + height: 1 + }*/ + + // ------------- History tab --------------- + /* MenuButton { id: historyButton anchors.left: parent.left @@ -246,6 +283,7 @@ Rectangle { color: historyButton.checked || addressBookButton.checked ? "#1C1C1C" : "#505050" height: 1 } + // ------------- AddressBook tab --------------- MenuButton { id: addressBookButton @@ -269,6 +307,7 @@ Rectangle { height: 1 } + // ------------- Mining tab --------------- MenuButton { id: miningButton anchors.left: parent.left @@ -291,6 +330,7 @@ Rectangle { height: 1 } + // ------------- Settings tab --------------- MenuButton { id: settingsButton anchors.left: parent.left @@ -304,9 +344,11 @@ Rectangle { panel.settingsClicked() } } + */ } NetworkStatusItem { + id: networkStatus anchors.left: parent.left anchors.right: parent.right anchors.bottom: parent.bottom diff --git a/MiddlePanel.qml b/MiddlePanel.qml index edb4963d..4d65636c 100644 --- a/MiddlePanel.qml +++ b/MiddlePanel.qml @@ -31,6 +31,7 @@ import QtQuick 2.2 Rectangle { color: "#F0EEEE" signal paymentClicked(string address, string paymentId, double amount, int mixinCount) + signal generatePaymentIdInvoked() states: [ State { @@ -42,6 +43,9 @@ Rectangle { }, State { name: "Transfer" PropertyChanges { target: loader; source: "pages/Transfer.qml" } + }, State { + name: "Receive" + PropertyChanges { target: loader; source: "pages/Receive.qml" } }, State { name: "AddressBook" PropertyChanges { target: loader; source: "pages/AddressBook.qml" } @@ -79,6 +83,7 @@ Rectangle { } + /* connect "payment" click */ Connections { ignoreUnknownSignals: false target: loader.item diff --git a/components/IconButton.qml b/components/IconButton.qml new file mode 100644 index 00000000..042439f1 --- /dev/null +++ b/components/IconButton.qml @@ -0,0 +1,72 @@ +// 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 QtQuick 2.0 + +Item { + property alias imageSource : buttonImage.source + + signal clicked(var mouse) + + + id: button + width: parent.height + height: parent.height + anchors.right: parent.right + anchors.top: parent.top + anchors.bottom: parent.bottom + + Image { + id: buttonImage + source: "" + x : (parent.width - width) / 2 + y : (parent.height - height) /2 + z: 100 + } + + MouseArea { + id: buttonArea + anchors.fill: parent + + + onPressed: { + buttonImage.x = buttonImage.x + 2 + buttonImage.y = buttonImage.y + 2 + } + onReleased: { + buttonImage.x = buttonImage.x - 2 + buttonImage.y = buttonImage.y - 2 + } + + onClicked: { + parent.clicked(mouse) + } + } + +} diff --git a/components/Input.qml b/components/Input.qml index c10994d0..78bec8ea 100644 --- a/components/Input.qml +++ b/components/Input.qml @@ -32,6 +32,7 @@ import QtQuick 2.2 TextField { font.family: "Arial" + horizontalAlignment: TextInput.AlignLeft style: TextFieldStyle { textColor: "#3F3F3F" diff --git a/components/LineEdit.qml b/components/LineEdit.qml index 37c2390d..81786881 100644 --- a/components/LineEdit.qml +++ b/components/LineEdit.qml @@ -32,8 +32,10 @@ Item { property alias placeholderText: input.placeholderText property alias text: input.text property alias validator: input.validator + property alias readOnly : input.readOnly property int fontSize: 18 + height: 37 Rectangle { @@ -56,6 +58,5 @@ Item { anchors.leftMargin: 4 anchors.rightMargin: 4 font.pixelSize: parent.fontSize - } } diff --git a/get_libwallet_api.sh b/get_libwallet_api.sh index c9f67ff8..9f8750d0 100755 --- a/get_libwallet_api.sh +++ b/get_libwallet_api.sh @@ -2,6 +2,7 @@ BITMONERO_URL=https://github.com/mbg033/bitmonero +BITMONERO_BRANCH=fee-mul CPU_CORE_COUNT=$(grep -c ^processor /proc/cpuinfo) pushd $(pwd) ROOT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" @@ -13,6 +14,7 @@ BITMONERO_DIR=$ROOT_DIR/bitmonero if [ ! -d $BITMONERO_DIR ]; then git clone --depth=1 $BITMONERO_URL $BITMONERO_DIR + git checkout $BITMONERO_BRANCH else cd $BITMONERO_DIR; git pull; diff --git a/main.qml b/main.qml index 6f90bcf8..c2292798 100644 --- a/main.qml +++ b/main.qml @@ -226,6 +226,7 @@ ApplicationWindow { property bool allow_background_mining : true property bool testnet: true property string daemon_address: "localhost:38081" + property string payment_id } Item { @@ -274,6 +275,7 @@ ApplicationWindow { onDashboardClicked: middlePanel.state = "Dashboard" onHistoryClicked: middlePanel.state = "History" onTransferClicked: middlePanel.state = "Transfer" + onReceiveClicked: middlePanel.state = "Receive" onAddressBookClicked: middlePanel.state = "AddressBook" onMiningClicked: middlePanel.state = "Minning" onSettingsClicked: middlePanel.state = "Settings" @@ -294,7 +296,7 @@ ApplicationWindow { anchors.left: leftPanel.right anchors.right: rightPanel.left height: parent.height - state: "Dashboard" + state: "Transfer" } TipItem { diff --git a/pages/Receive.qml b/pages/Receive.qml new file mode 100644 index 00000000..623c629d --- /dev/null +++ b/pages/Receive.qml @@ -0,0 +1,184 @@ +// 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 QtQuick 2.0 +import QtQuick.Controls 1.4 +import QtQuick.Controls.Styles 1.4 +import QtQuick.Layouts 1.1 + +import "../components" +import moneroComponents 1.0 + +Rectangle { + + color: "#F0EEEE" + property alias addressText : addressLine.text + property alias paymentIdText : paymentIdLine.text + property alias integratedAddressText : integratedAddressLine.text + + function updatePaymentId() { + var payment_id = appWindow.persistentSettings.payment_id + if (payment_id.length === 0) { + payment_id = appWindow.wallet.generatePaymentId() + appWindow.persistentSettings.payment_id = payment_id + appWindow.wallet.payment_id = payment_id + } + paymentIdLine.text = payment_id + addressLine.text = appWindow.wallet.address + integratedAddressLine.text = appWindow.wallet.integratedAddress(payment_id) + } + + Clipboard { id: clipboard } + + + /* main layout */ + ColumnLayout { + id: mainLayout + anchors.margins: 40 + anchors.left: parent.left + anchors.top: parent.top + anchors.right: parent.right + + spacing: 20 + property int labelWidth: 120 + property int editWidth: 400 + property int lineEditFontSize: 12 + + + RowLayout { + id: addressRow + + Label { + id: addressLabel + fontSize: 14 + text: qsTr("Address") + width: mainLayout.labelWidth + } + + LineEdit { + id: addressLine + fontSize: mainLayout.lineEditFontSize + placeholderText: "ReadOnly wallet address displayed here"; + readOnly: true + width: mainLayout.editWidth + Layout.fillWidth: true + IconButton { + imageSource: "../images/copyToClipboard.png" + onClicked: { + if (addressLine.text.length > 0) { + clipboard.setText(addressLine.text) + } + } + } + } + } + + RowLayout { + id: integratedAddressRow + Label { + id: integratedAddressLabel + fontSize: 14 + text: qsTr("Integrated address") + width: mainLayout.labelWidth + } + + + LineEdit { + + id: integratedAddressLine + fontSize: mainLayout.lineEditFontSize + placeholderText: "ReadOnly wallet integrated address displayed here"; + readOnly: true + width: mainLayout.editWidth + Layout.fillWidth: true + IconButton { + imageSource: "../images/copyToClipboard.png" + onClicked: { + if (integratedAddressLine.text.length > 0) { + clipboard.setText(integratedAddressLine.text) + } + } + } + + } + } + + RowLayout { + id: paymentIdRow + Label { + id: paymentIdLabel + fontSize: 14 + text: qsTr("Payment ID") + width: mainLayout.labelWidth + } + + + LineEdit { + id: paymentIdLine + fontSize: mainLayout.lineEditFontSize + placeholderText: "PaymentID here"; + readOnly: false + + width: mainLayout.editWidth + Layout.fillWidth: true + + IconButton { + imageSource: "../images/copyToClipboard.png" + onClicked: { + if (paymentIdLine.text.length > 0) { + clipboard.setText(paymentIdLine.text) + } + } + } + } + + StandardButton { + id: generatePaymentId + width: 80 + fontSize: 14 + shadowReleasedColor: "#FF4304" + shadowPressedColor: "#B32D00" + releasedColor: "#FF6C3C" + pressedColor: "#FF4304" + text: qsTr("Generate") + anchors.right: parent.right + onClicked: { + appWindow.persistentSettings.payment_id = appWindow.wallet.generatePaymentId(); + updatePaymentId() + } + } + } + + } + + Component.onCompleted: { + console.log("Receive page loaded"); + updatePaymentId() + } + +} diff --git a/qml.qrc b/qml.qrc index bfa55389..36861fe4 100644 --- a/qml.qrc +++ b/qml.qrc @@ -111,5 +111,7 @@ wizard/WizardRecoveryWallet.qml wizard/WizardMemoTextInput.qml wizard/utils.js + pages/Receive.qml + components/IconButton.qml diff --git a/src/libwalletqt/Wallet.cpp b/src/libwalletqt/Wallet.cpp index 4e6b6787..da9cba01 100644 --- a/src/libwalletqt/Wallet.cpp +++ b/src/libwalletqt/Wallet.cpp @@ -88,10 +88,11 @@ bool Wallet::refresh() return result; } -PendingTransaction *Wallet::createTransaction(const QString &dst_addr, quint64 amount, quint32 mixin_count) +PendingTransaction *Wallet::createTransaction(const QString &dst_addr, const QString &payment_id, + quint64 amount, quint32 mixin_count) { Bitmonero::PendingTransaction * ptImpl = m_walletImpl->createTransaction( - dst_addr.toStdString(), amount, mixin_count); + dst_addr.toStdString(), payment_id.toStdString(), amount, mixin_count); PendingTransaction * result = new PendingTransaction(ptImpl, this); return result; } @@ -112,6 +113,26 @@ TransactionHistory *Wallet::history() } +QString Wallet::generatePaymentId() const +{ + return QString::fromStdString(Bitmonero::Wallet::genPaymentId()); +} + +QString Wallet::integratedAddress(const QString &paymentId) const +{ + return QString::fromStdString(m_walletImpl->integratedAddress(paymentId.toStdString())); +} + +QString Wallet::paymentId() const +{ + return m_paymentId; +} + +void Wallet::setPaymentId(const QString &paymentId) +{ + m_paymentId = paymentId; +} + Wallet::Wallet(Bitmonero::Wallet *w, QObject *parent) : QObject(parent), m_walletImpl(w), m_history(nullptr) diff --git a/src/libwalletqt/Wallet.h b/src/libwalletqt/Wallet.h index 4991476a..675ff52e 100644 --- a/src/libwalletqt/Wallet.h +++ b/src/libwalletqt/Wallet.h @@ -23,6 +23,7 @@ class Wallet : public QObject Q_PROPERTY(quint64 balance READ balance) Q_PROPERTY(quint64 unlockedBalance READ unlockedBalance) Q_PROPERTY(TransactionHistory * history READ history) + Q_PROPERTY(QString paymentId READ paymentId WRITE setPaymentId) public: enum Status { @@ -75,7 +76,7 @@ public: Q_INVOKABLE bool refresh(); //! creates transaction - Q_INVOKABLE PendingTransaction * createTransaction(const QString &dst_addr, + Q_INVOKABLE PendingTransaction * createTransaction(const QString &dst_addr, const QString &payment_id, quint64 amount, quint32 mixin_count); //! deletes transaction and frees memory @@ -84,6 +85,18 @@ public: //! returns transaction history TransactionHistory * history(); + //! generate payment id + Q_INVOKABLE QString generatePaymentId() const; + + //! integrated address + Q_INVOKABLE QString integratedAddress(const QString &paymentId) const; + + + //! saved payment id + QString paymentId() const; + + void setPaymentId(const QString &paymentId); + // TODO: setListenter() when it implemented in API signals: void updated(); @@ -99,6 +112,7 @@ private: Bitmonero::Wallet * m_walletImpl; // history lifetime managed by wallet; TransactionHistory * m_history; + QString m_paymentId; }; #endif // WALLET_H diff --git a/wizard/WizardCreateWallet.qml b/wizard/WizardCreateWallet.qml index 0aad0052..d39d52d2 100644 --- a/wizard/WizardCreateWallet.qml +++ b/wizard/WizardCreateWallet.qml @@ -60,7 +60,9 @@ Item { var wallet_filename = oshelper.temporaryFilename(); if (typeof settingsObject.wallet === 'undefined') { //var wallet = walletManager.createWallet(wallet_filename, "", settingsObject.language) - var wallet = walletManager.createWallet(wallet_filename, "", settingsObject.wallet_language) + var testnet = appWindow.persistentSettings.testnet; + var wallet = walletManager.createWallet(wallet_filename, "", settingsObject.wallet_language, + testnet) uiItem.wordsTextItem.memoText = wallet.seed // saving wallet in "global" settings object // TODO: wallet should have a property pointing to the file where it stored or loaded from diff --git a/wizard/WizardRecoveryWallet.qml b/wizard/WizardRecoveryWallet.qml index 19530157..aded7661 100644 --- a/wizard/WizardRecoveryWallet.qml +++ b/wizard/WizardRecoveryWallet.qml @@ -49,7 +49,7 @@ Item { } function recoveryWallet(settingsObject) { - var testnet = true; + var testnet = appWindow.persistentSettings.testnet; var wallet = walletManager.recoveryWallet(oshelper.temporaryFilename(), settingsObject.words, testnet); var success = wallet.status === Wallet.Status_Ok; if (success) { From 409c5701e2004023db58af7575959de4b8256001 Mon Sep 17 00:00:00 2001 From: Ilya Kitaev Date: Mon, 27 Jun 2016 15:45:48 +0300 Subject: [PATCH 35/87] Priority aka fee multiplier integrated --- MiddlePanel.qml | 4 ++-- main.qml | 15 +++++++++------ pages/Transfer.qml | 24 ++++++++++++++---------- src/libwalletqt/PendingTransaction.h | 9 ++++++++- src/libwalletqt/Wallet.cpp | 6 ++++-- src/libwalletqt/Wallet.h | 10 ++++++---- 6 files changed, 43 insertions(+), 25 deletions(-) diff --git a/MiddlePanel.qml b/MiddlePanel.qml index 4d65636c..cb1c74d6 100644 --- a/MiddlePanel.qml +++ b/MiddlePanel.qml @@ -30,7 +30,7 @@ import QtQuick 2.2 Rectangle { color: "#F0EEEE" - signal paymentClicked(string address, string paymentId, double amount, int mixinCount) + signal paymentClicked(string address, string paymentId, double amount, int mixinCount, int priority) signal generatePaymentIdInvoked() states: [ @@ -89,7 +89,7 @@ Rectangle { target: loader.item onPaymentClicked : { console.log("MiddlePanel: paymentClicked") - paymentClicked(address, paymentId, amount, mixinCount) + paymentClicked(address, paymentId, amount, mixinCount, priority) } } diff --git a/main.qml b/main.qml index c2292798..a4a8b3ff 100644 --- a/main.qml +++ b/main.qml @@ -169,15 +169,18 @@ ApplicationWindow { return wallets.length > 0; } - function handlePayment(address, paymentId, amount, mixinCount) { - console.log("Process payment here: ", address, paymentId, amount, mixinCount) - // TODO: handle payment id - // TODO: handle fee; - // TODO: handle mixins + function handlePayment(address, paymentId, amount, mixinCount, priority) { + console.log("Creating transaction: ") + console.log("\taddress: ", address, + ", payment_id: ", paymentId, + ", amount: ", amount, + ", mixins: ", mixinCount, + ", priority: ", priority); + var amountxmr = walletManager.amountFromString(amount); console.log("integer amount: ", amountxmr); - var pendingTransaction = wallet.createTransaction(address, amountxmr, mixinCount); + var pendingTransaction = wallet.createTransaction(address, paymentId, amountxmr, mixinCount, priority); if (pendingTransaction.status !== PendingTransaction.Status_Ok) { console.error("Can't create transaction: ", pendingTransaction.errorString); } else { diff --git a/pages/Transfer.qml b/pages/Transfer.qml index e3f96997..5e683f73 100644 --- a/pages/Transfer.qml +++ b/pages/Transfer.qml @@ -27,10 +27,13 @@ // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. import QtQuick 2.0 +import Bitmonero.PendingTransaction 1.0 import "../components" + Rectangle { - signal paymentClicked(string address, string paymentId, double amount, int mixinCount) + signal paymentClicked(string address, string paymentId, double amount, int mixinCount, + int priority) color: "#F0EEEE" @@ -54,7 +57,7 @@ Rectangle { text: qsTr("Amount") fontSize: 14 } - /* + Label { id: transactionPriority anchors.top: parent.top @@ -63,8 +66,6 @@ Rectangle { x: (parent.width - 17) / 2 + 17 text: qsTr("Transaction prority") } - */ - Row { id: amountRow @@ -92,11 +93,11 @@ Rectangle { ListModel { id: priorityModel - ListElement { column1: "LOW"; column2: "( fee: 0.0002 )" } - ListElement { column1: "MEDIUM"; column2: "( fee: 0.0004 )" } - ListElement { column1: "HIGH"; column2: "( fee: 0.0008 )" } + ListElement { column1: "LOW"; column2: ""; priority: PendingTransaction.Priority_Low } + ListElement { column1: "MEDIUM"; column2: ""; priority: PendingTransaction.Priority_Medium } + ListElement { column1: "HIGH"; column2: ""; priority: PendingTransaction.Priority_High } } - /* + StandardDropdown { id: priorityDropdown anchors.top: transactionPriority.bottom @@ -111,7 +112,7 @@ Rectangle { dataModel: priorityModel z: 1 } - */ + Label { @@ -243,7 +244,10 @@ Rectangle { if (addressLine.text.length > 0 && amountLine.text.length > 0) { console.log("paymentClicked") - paymentClicked(addressLine.text, paymentIdLine.text, amountLine.text, scaleValueToMixinCount(privacyLevelItem.fillLevel)) + var priority = priorityModel.get(priorityDropdown.currentIndex).priority + console.log("priority: " + priority) + paymentClicked(addressLine.text, paymentIdLine.text, amountLine.text, scaleValueToMixinCount(privacyLevelItem.fillLevel), + priority) } } } diff --git a/src/libwalletqt/PendingTransaction.h b/src/libwalletqt/PendingTransaction.h index d8c4ec1e..8f5fca1c 100644 --- a/src/libwalletqt/PendingTransaction.h +++ b/src/libwalletqt/PendingTransaction.h @@ -23,9 +23,16 @@ public: Status_Ok = Bitmonero::PendingTransaction::Status_Ok, Status_Error = Bitmonero::PendingTransaction::Status_Error }; - Q_ENUM(Status) + enum Priority { + Priority_Low = Bitmonero::PendingTransaction::Priority_Low, + Priority_Medium = Bitmonero::PendingTransaction::Priority_Medium, + Priority_High = Bitmonero::PendingTransaction::Priority_High + }; + Q_ENUM(Priority) + + Status status() const; QString errorString() const; Q_INVOKABLE bool commit(); diff --git a/src/libwalletqt/Wallet.cpp b/src/libwalletqt/Wallet.cpp index da9cba01..e6368ad2 100644 --- a/src/libwalletqt/Wallet.cpp +++ b/src/libwalletqt/Wallet.cpp @@ -89,10 +89,12 @@ bool Wallet::refresh() } PendingTransaction *Wallet::createTransaction(const QString &dst_addr, const QString &payment_id, - quint64 amount, quint32 mixin_count) + quint64 amount, quint32 mixin_count, + PendingTransaction::Priority priority) { Bitmonero::PendingTransaction * ptImpl = m_walletImpl->createTransaction( - dst_addr.toStdString(), payment_id.toStdString(), amount, mixin_count); + dst_addr.toStdString(), payment_id.toStdString(), amount, mixin_count, + static_cast(priority)); PendingTransaction * result = new PendingTransaction(ptImpl, this); return result; } diff --git a/src/libwalletqt/Wallet.h b/src/libwalletqt/Wallet.h index 675ff52e..a62c0f14 100644 --- a/src/libwalletqt/Wallet.h +++ b/src/libwalletqt/Wallet.h @@ -3,13 +3,15 @@ #include -#include "wallet/wallet2_api.h" // we need to access Status enum here; +#include "wallet/wallet2_api.h" // we need to have an access to the Bitmonero::Wallet::Status enum here; +#include "PendingTransaction.h" // we need to have an access to the PendingTransaction::Priority enum here; + namespace Bitmonero { class Wallet; // forward declaration } -class PendingTransaction; + class TransactionHistory; class Wallet : public QObject @@ -77,8 +79,8 @@ public: //! creates transaction Q_INVOKABLE PendingTransaction * createTransaction(const QString &dst_addr, const QString &payment_id, - quint64 amount, quint32 mixin_count); - + quint64 amount, quint32 mixin_count, + PendingTransaction::Priority priority = PendingTransaction::Priority_Low); //! deletes transaction and frees memory Q_INVOKABLE void disposeTransaction(PendingTransaction * t); From 39cb75e58c2823e6a25e229596af4e276d77b2ae Mon Sep 17 00:00:00 2001 From: Ilya Kitaev Date: Tue, 28 Jun 2016 22:37:14 +0300 Subject: [PATCH 36/87] Send money: confirmation popups added --- main.cpp | 1 + main.qml | 79 ++++++++++++++++++++++---- src/libwalletqt/PendingTransaction.cpp | 1 + src/libwalletqt/Wallet.h | 2 +- 4 files changed, 71 insertions(+), 12 deletions(-) diff --git a/main.cpp b/main.cpp index 70390a80..7b2799d4 100644 --- a/main.cpp +++ b/main.cpp @@ -56,6 +56,7 @@ int main(int argc, char *argv[]) qmlRegisterUncreatableType("Bitmonero.Wallet", 1, 0, "Wallet", "Wallet can't be instantiated directly"); qmlRegisterUncreatableType("Bitmonero.PendingTransaction", 1, 0, "PendingTransaction", "PendingTransaction can't be instantiated directly"); + qRegisterMetaType(); QQmlApplicationEngine engine; diff --git a/main.qml b/main.qml index a4a8b3ff..721c69e4 100644 --- a/main.qml +++ b/main.qml @@ -30,6 +30,7 @@ import QtQuick 2.2 import QtQuick.Window 2.0 import QtQuick.Controls 1.1 import QtQuick.Controls.Styles 1.1 +import QtQuick.Dialogs 1.2 import Qt.labs.settings 1.0 import Bitmonero.Wallet 1.0 import Bitmonero.PendingTransaction 1.0 @@ -47,6 +48,7 @@ ApplicationWindow { property bool osx: false property alias persistentSettings : persistentSettings property var wallet; + property var transaction; function altKeyReleased() { ctrlPressed = false; } @@ -133,6 +135,10 @@ ApplicationWindow { wallet = walletManager.openWallet(wallet_path, "", persistentSettings.testnet); if (wallet.status !== Wallet.Status_Ok) { console.log("Error opening wallet: ", wallet.errorString); + informationPopup.title = qsTr("Error"); + informationPopup.text = qsTr("Couldn't open wallet: ") + wallet.errorString; + informationPopup.icon = StandardIcon.Critical + informationPopup.open() return; } console.log("Wallet opened successfully: ", wallet.errorString); @@ -169,6 +175,8 @@ ApplicationWindow { return wallets.length > 0; } + + // called on "transfer" function handlePayment(address, paymentId, amount, mixinCount, priority) { console.log("Creating transaction: ") console.log("\taddress: ", address, @@ -180,22 +188,53 @@ ApplicationWindow { var amountxmr = walletManager.amountFromString(amount); console.log("integer amount: ", amountxmr); - var pendingTransaction = wallet.createTransaction(address, paymentId, amountxmr, mixinCount, priority); - if (pendingTransaction.status !== PendingTransaction.Status_Ok) { - console.error("Can't create transaction: ", pendingTransaction.errorString); + transaction = wallet.createTransaction(address, paymentId, amountxmr, mixinCount, priority); + if (transaction.status !== PendingTransaction.Status_Ok) { + console.error("Can't create transaction: ", transaction.errorString); + informationPopup.title = qsTr("Error"); + informationPopup.text = qsTr("Can't create transaction: ") + transaction.errorString + informationPopup.icon = StandardIcon.Critical + informationPopup.open(); + // deleting transaction object, we don't want memleaks + wallet.disposeTransaction(transaction); + } else { - console.log("Transaction created, amount: " + walletManager.displayAmount(pendingTransaction.amount) - + ", fee: " + walletManager.displayAmount(pendingTransaction.fee)); - if (!pendingTransaction.commit()) { - console.log("Error committing transaction: " + pendingTransaction.errorString); - } else { - wallet.refresh(); - } + console.log("Transaction created, amount: " + walletManager.displayAmount(transaction.amount) + + ", fee: " + walletManager.displayAmount(transaction.fee)); + + // here we show confirmation popup; + + transactionConfirmationPopup.title = qsTr("Confirmation") + transactionConfirmationPopup.text = qsTr("Please confirm transaction:\n\n") + + "\naddress: " + address + + "\npayment id: " + paymentId + + "\namount: " + walletManager.displayAmount(transaction.amount) + + "\nfee: " + walletManager.displayAmount(transaction.fee) + transactionConfirmationPopup.icon = StandardIcon.Question + transactionConfirmationPopup.open() + // committing transaction + } + } + + // called after user confirms transaction + function handleTransactionConfirmed() { + if (!transaction.commit()) { + console.log("Error committing transaction: " + transaction.errorString); + informationPopup.title = qsTr("Error"); + informationPopup.text = qsTr("Couldn't send the money: ") + transaction.errorString + informationPopup.icon = StandardIcon.Critical + } else { + informationPopup.title = qsTr("Information") + informationPopup.text = qsTr("Money sent successfully") + informationPopup.icon = StandardIcon.Information } - wallet.disposeTransaction(pendingTransaction); + informationPopup.open() + wallet.refresh() + wallet.disposeTransaction(transaction) } + visible: true width: rightPanelExpanded ? 1269 : 1269 - 300 height: 800 @@ -232,6 +271,24 @@ ApplicationWindow { property string payment_id } + // TODO: replace with customized popups + + // Information dialog + MessageDialog { + id: informationPopup + standardButtons: StandardButton.Ok + } + + // Confrirmation aka question dialog + MessageDialog { + id: transactionConfirmationPopup + standardButtons: StandardButton.Ok + StandardButton.Cancel + onAccepted: { + handleTransactionConfirmed() + } + } + + Item { id: rootItem anchors.fill: parent diff --git a/src/libwalletqt/PendingTransaction.cpp b/src/libwalletqt/PendingTransaction.cpp index a5dc458d..8c0d8c29 100644 --- a/src/libwalletqt/PendingTransaction.cpp +++ b/src/libwalletqt/PendingTransaction.cpp @@ -1,5 +1,6 @@ #include "PendingTransaction.h" + PendingTransaction::Status PendingTransaction::status() const { return static_cast(m_pimpl->status()); diff --git a/src/libwalletqt/Wallet.h b/src/libwalletqt/Wallet.h index a62c0f14..241b89d8 100644 --- a/src/libwalletqt/Wallet.h +++ b/src/libwalletqt/Wallet.h @@ -80,7 +80,7 @@ public: //! creates transaction Q_INVOKABLE PendingTransaction * createTransaction(const QString &dst_addr, const QString &payment_id, quint64 amount, quint32 mixin_count, - PendingTransaction::Priority priority = PendingTransaction::Priority_Low); + PendingTransaction::Priority priority); //! deletes transaction and frees memory Q_INVOKABLE void disposeTransaction(PendingTransaction * t); From a594e25b26aff323daaca21c580e5163cc2a2a1b Mon Sep 17 00:00:00 2001 From: dEBRUYNE-1 Date: Tue, 28 Jun 2016 22:14:30 +0200 Subject: [PATCH 37/87] Clean up text --- main.qml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/main.qml b/main.qml index 721c69e4..e890a75b 100644 --- a/main.qml +++ b/main.qml @@ -206,10 +206,10 @@ ApplicationWindow { transactionConfirmationPopup.title = qsTr("Confirmation") transactionConfirmationPopup.text = qsTr("Please confirm transaction:\n\n") - + "\naddress: " + address - + "\npayment id: " + paymentId - + "\namount: " + walletManager.displayAmount(transaction.amount) - + "\nfee: " + walletManager.displayAmount(transaction.fee) + + "\nAddress: " + address + + "\nPayment ID: " + paymentId + + "\nAmount: " + walletManager.displayAmount(transaction.amount) + + "\nFee: " + walletManager.displayAmount(transaction.fee) transactionConfirmationPopup.icon = StandardIcon.Question transactionConfirmationPopup.open() // committing transaction From f846935f95abd892b3bcb708632d827a8fb29f7d Mon Sep 17 00:00:00 2001 From: dEBRUYNE-1 Date: Tue, 28 Jun 2016 22:21:02 +0200 Subject: [PATCH 38/87] Fix typo --- pages/Transfer.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pages/Transfer.qml b/pages/Transfer.qml index 5e683f73..aa79c18f 100644 --- a/pages/Transfer.qml +++ b/pages/Transfer.qml @@ -64,7 +64,7 @@ Rectangle { anchors.topMargin: 17 fontSize: 14 x: (parent.width - 17) / 2 + 17 - text: qsTr("Transaction prority") + text: qsTr("Transaction priority") } Row { From 0c093423f1da576d8b92363cc1e2f6dded8e907a Mon Sep 17 00:00:00 2001 From: Ilya Kitaev Date: Fri, 1 Jul 2016 15:02:19 +0300 Subject: [PATCH 39/87] Fixed for MSYS2 build --- get_libwallet_api.sh | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/get_libwallet_api.sh b/get_libwallet_api.sh index 9f8750d0..7410365a 100755 --- a/get_libwallet_api.sh +++ b/get_libwallet_api.sh @@ -1,7 +1,7 @@ #!/bin/bash -BITMONERO_URL=https://github.com/mbg033/bitmonero +BITMONERO_URL=https://github.com/mbg033/bitmonero.git BITMONERO_BRANCH=fee-mul CPU_CORE_COUNT=$(grep -c ^processor /proc/cpuinfo) pushd $(pwd) @@ -13,8 +13,7 @@ BITMONERO_DIR=$ROOT_DIR/bitmonero if [ ! -d $BITMONERO_DIR ]; then - git clone --depth=1 $BITMONERO_URL $BITMONERO_DIR - git checkout $BITMONERO_BRANCH + git clone --depth=1 $BITMONERO_URL $BITMONERO_DIR --branch $BITMONERO_BRANCH --single-branch else cd $BITMONERO_DIR; git pull; @@ -24,7 +23,21 @@ rm -fr $BITMONERO_DIR/build mkdir -p $BITMONERO_DIR/build/release pushd $BITMONERO_DIR/build/release -cmake -D CMAKE_BUILD_TYPE=Release -D STATIC=ON -D CMAKE_INSTALL_PREFIX="$BITMONERO_DIR" ../.. + +if [ "$(uname)" == "Darwin" ]; then + # Do something under Mac OS X platform + cmake -D CMAKE_BUILD_TYPE=Release -D STATIC=ON -D CMAKE_INSTALL_PREFIX="$BITMONERO_DIR" ../.. +elif [ "$(expr substr $(uname -s) 1 5)" == "Linux" ]; then + # Do something under GNU/Linux platform + cmake -D CMAKE_BUILD_TYPE=Release -D STATIC=ON -D CMAKE_INSTALL_PREFIX="$BITMONERO_DIR" ../.. +elif [ "$(expr substr $(uname -s) 1 10)" == "MINGW64_NT" ]; then + # Do something under Windows NT platform + cmake -D CMAKE_BUILD_TYPE=Release -D STATIC=ON -D CMAKE_INSTALL_PREFIX="$BITMONERO_DIR" -G "MinGW Makefiles" ../.. + +elif [ "$(expr substr $(uname -s) 1 10)" == "MINGW32_NT" ]; then + # Do something under Windows NT platform +fi + pushd $BITMONERO_DIR/build/release/src/wallet make -j$CPU_CORE_COUNT From d880441f7e2631854803dd95458d11bef1a145a3 Mon Sep 17 00:00:00 2001 From: Ilya Kitaev Date: Mon, 4 Jul 2016 18:17:26 +0300 Subject: [PATCH 40/87] MSYS2/Win64 build fixed --- get_libwallet_api.sh | 4 ++-- monero-core.pro | 31 ++++++++++++++++++++++++++++++- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/get_libwallet_api.sh b/get_libwallet_api.sh index 7410365a..60f86d32 100755 --- a/get_libwallet_api.sh +++ b/get_libwallet_api.sh @@ -32,10 +32,10 @@ elif [ "$(expr substr $(uname -s) 1 5)" == "Linux" ]; then cmake -D CMAKE_BUILD_TYPE=Release -D STATIC=ON -D CMAKE_INSTALL_PREFIX="$BITMONERO_DIR" ../.. elif [ "$(expr substr $(uname -s) 1 10)" == "MINGW64_NT" ]; then # Do something under Windows NT platform - cmake -D CMAKE_BUILD_TYPE=Release -D STATIC=ON -D CMAKE_INSTALL_PREFIX="$BITMONERO_DIR" -G "MinGW Makefiles" ../.. - + cmake -D CMAKE_BUILD_TYPE=Release -D STATIC=ON -D CMAKE_INSTALL_PREFIX="$BITMONERO_DIR" -G "MSYS Makefiles" ../.. elif [ "$(expr substr $(uname -s) 1 10)" == "MINGW32_NT" ]; then # Do something under Windows NT platform + cmake -D CMAKE_BUILD_TYPE=Release -D STATIC=ON -D CMAKE_INSTALL_PREFIX="$BITMONERO_DIR" -G "MSYS Makefiles" ../.. fi diff --git a/monero-core.pro b/monero-core.pro index 8ce4a16a..b345a3d7 100644 --- a/monero-core.pro +++ b/monero-core.pro @@ -42,12 +42,41 @@ SOURCES = *.qml \ LIBS += -L$$WALLET_ROOT/lib \ -lwallet_merged \ + -lwallet_merged2 + +win32 { + #QMAKE_LFLAGS += -static + LIBS+= \ + -Wl,-Bstatic \ + -lboost_serialization-mt \ + -lboost_thread-mt \ + -lboost_system-mt \ + -lboost_date_time-mt \ + -lboost_filesystem-mt \ + -lboost_regex-mt \ + -lboost_chrono-mt \ + -lboost_program_options-mt \ + -lssl \ + -lcrypto \ + -Wl,-Bdynamic \ + -lws2_32 \ + -lwsock32 \ + -lIphlpapi \ + -lgdi32 +} + +unix { + LIBS+= \ -lboost_serialization \ -lboost_thread \ -lboost_system \ -lboost_date_time \ -lboost_filesystem \ - -lboost_regex + -lboost_regex \ + -lboost_chrono \ + -lboost_program_options + +} From da43e9a93a8cac57a8c993ba823dddddefa4af3f Mon Sep 17 00:00:00 2001 From: Ilya Kitaev Date: Mon, 4 Jul 2016 18:25:03 +0300 Subject: [PATCH 41/87] "Compiling" section stub added to documentation --- README.md | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b10c94cf..091fb303 100644 --- a/README.md +++ b/README.md @@ -61,4 +61,22 @@ Redistribution and use in source and binary forms, with or without modification, 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. -Parts of the project are originally copyright (c) 2012-2013 The Cryptonote developers \ No newline at end of file +Parts of the project are originally copyright (c) 2012-2013 The Cryptonote developers + +## Compiling Monero-core + +### Overview: + +Dependencies: TODO + +Process: TODO + + +### On Linux: +TODO + +### On OS X: +TODO + +### On Windows: +TODO From fca82f347d29429e3ad662897a38923e3258a643 Mon Sep 17 00:00:00 2001 From: Ilya Kitaev Date: Wed, 6 Jul 2016 16:24:14 +0300 Subject: [PATCH 42/87] Cross-plaftorm number of cpu cores --- get_libwallet_api.sh | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/get_libwallet_api.sh b/get_libwallet_api.sh index 60f86d32..520bba17 100755 --- a/get_libwallet_api.sh +++ b/get_libwallet_api.sh @@ -3,7 +3,8 @@ BITMONERO_URL=https://github.com/mbg033/bitmonero.git BITMONERO_BRANCH=fee-mul -CPU_CORE_COUNT=$(grep -c ^processor /proc/cpuinfo) +# thanks to SO: http://stackoverflow.com/a/20283965/4118915 +CPU_CORE_COUNTS=$(grep -c ^processor /proc/cpuinfo 2>/dev/null || sysctl -n hw.ncpu) pushd $(pwd) ROOT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" From 5b1ab69d70a29c4ddf66d5f107f621ed3340d066 Mon Sep 17 00:00:00 2001 From: Ilya Kitaev Date: Wed, 6 Jul 2016 16:25:41 +0300 Subject: [PATCH 43/87] Mac OSX build steps added --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 091fb303..25caa911 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,11 @@ Process: TODO TODO ### On OS X: -TODO +1. install homebrew +2. install dependencies: +`brew install boost --c++11` +`brew install pkgconfig` + ### On Windows: TODO From c027922cb710ca78e400c30268f5ceaec10a7e49 Mon Sep 17 00:00:00 2001 From: Ilya Kitaev Date: Wed, 13 Jul 2016 10:30:12 +0300 Subject: [PATCH 44/87] fixed multicore build; statically link boost, libcrypto and libssl for Linux; --- get_libwallet_api.sh | 4 ++-- monero-core.pro | 8 ++++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/get_libwallet_api.sh b/get_libwallet_api.sh index 520bba17..9e69235e 100755 --- a/get_libwallet_api.sh +++ b/get_libwallet_api.sh @@ -2,9 +2,9 @@ BITMONERO_URL=https://github.com/mbg033/bitmonero.git -BITMONERO_BRANCH=fee-mul +BITMONERO_BRANCH=devel # thanks to SO: http://stackoverflow.com/a/20283965/4118915 -CPU_CORE_COUNTS=$(grep -c ^processor /proc/cpuinfo 2>/dev/null || sysctl -n hw.ncpu) +CPU_CORE_COUNT=$(grep -c ^processor /proc/cpuinfo 2>/dev/null || sysctl -n hw.ncpu) pushd $(pwd) ROOT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" diff --git a/monero-core.pro b/monero-core.pro index b345a3d7..84d4f35a 100644 --- a/monero-core.pro +++ b/monero-core.pro @@ -67,6 +67,7 @@ win32 { unix { LIBS+= \ + -Wl,-Bstatic \ -lboost_serialization \ -lboost_thread \ -lboost_system \ @@ -74,8 +75,11 @@ unix { -lboost_filesystem \ -lboost_regex \ -lboost_chrono \ - -lboost_program_options - + -lboost_program_options \ + -lssl \ + -lcrypto \ + -Wl,-Bdynamic \ + -ldl } From d9f031ec2a3b6dc256ea138a8aa8326e41130e4b Mon Sep 17 00:00:00 2001 From: Ilya Kitaev Date: Wed, 13 Jul 2016 15:24:40 +0300 Subject: [PATCH 45/87] Async API integration in progress --- main.cpp | 1 + main.qml | 43 +++++++++++++++++++++++++++++-------- src/libwalletqt/Wallet.cpp | 44 ++++++++++++++++++++++++++++++++++++++ src/libwalletqt/Wallet.h | 9 ++++++++ 4 files changed, 88 insertions(+), 9 deletions(-) diff --git a/main.cpp b/main.cpp index 7b2799d4..43ef8186 100644 --- a/main.cpp +++ b/main.cpp @@ -56,6 +56,7 @@ int main(int argc, char *argv[]) qmlRegisterUncreatableType("Bitmonero.Wallet", 1, 0, "Wallet", "Wallet can't be instantiated directly"); qmlRegisterUncreatableType("Bitmonero.PendingTransaction", 1, 0, "PendingTransaction", "PendingTransaction can't be instantiated directly"); + qRegisterMetaType(); diff --git a/main.qml b/main.qml index e890a75b..b96b5dea 100644 --- a/main.qml +++ b/main.qml @@ -35,6 +35,7 @@ import Qt.labs.settings 1.0 import Bitmonero.Wallet 1.0 import Bitmonero.PendingTransaction 1.0 + import "components" import "wizard" @@ -50,6 +51,7 @@ ApplicationWindow { property var wallet; property var transaction; + function altKeyReleased() { ctrlPressed = false; } function showPageRequest(page) { @@ -122,7 +124,7 @@ ApplicationWindow { function initialize() { - + console.log("initializing..") middlePanel.paymentClicked.connect(handlePayment); if (typeof wizard.settings['wallet'] !== 'undefined') { @@ -143,24 +145,34 @@ ApplicationWindow { } console.log("Wallet opened successfully: ", wallet.errorString); } + // display splash screen... + console.log("initializing with daemon address..") if (!wallet.init(persistentSettings.daemon_address, 0)) { console.log("Error initialize wallet: ", wallet.errorString); return } + console.log("Wallet initialized successfully") + // TODO: update network indicator // subscribing for wallet updates wallet.updated.connect(onWalletUpdate); - + wallet.refreshed.connect(onWalletRefresh); + console.log("refreshing wallet async") // TODO: refresh asynchronously without blocking UI, implement signal(s) - wallet.refresh(); - + wallet.refreshAsync(); console.log("wallet balance: ", wallet.balance) } function onWalletUpdate() { - console.log("wallet updated") + console.log(">>> wallet updated") + leftPanel.unlockedBalanceText = walletManager.displayAmount(wallet.unlockedBalance); + leftPanel.balanceText = walletManager.displayAmount(wallet.balance); + } + + function onWalletRefresh() { + console.log(">>> wallet refreshed") leftPanel.unlockedBalanceText = walletManager.displayAmount(wallet.unlockedBalance); leftPanel.balanceText = walletManager.displayAmount(wallet.balance); } @@ -206,10 +218,10 @@ ApplicationWindow { transactionConfirmationPopup.title = qsTr("Confirmation") transactionConfirmationPopup.text = qsTr("Please confirm transaction:\n\n") - + "\nAddress: " + address - + "\nPayment ID: " + paymentId - + "\nAmount: " + walletManager.displayAmount(transaction.amount) - + "\nFee: " + walletManager.displayAmount(transaction.fee) + + qsTr("\nAddress: ") + address + + qsTr("\nPayment ID: ") + paymentId + + qsTr("\nAmount: ") + walletManager.displayAmount(transaction.amount) + + qsTr("\nFee: ") + walletManager.displayAmount(transaction.fee) transactionConfirmationPopup.icon = StandardIcon.Question transactionConfirmationPopup.open() // committing transaction @@ -288,6 +300,19 @@ ApplicationWindow { } } + Window { + id: walletInitializationSplash + modality: Qt.ApplicationModal + flags: Qt.SplashScreen + height: 100 + width: 250 + Text { + anchors.fill: parent + text: qsTr("Initializing Wallet..."); + } + + } + Item { id: rootItem diff --git a/src/libwalletqt/Wallet.cpp b/src/libwalletqt/Wallet.cpp index e6368ad2..3697984e 100644 --- a/src/libwalletqt/Wallet.cpp +++ b/src/libwalletqt/Wallet.cpp @@ -14,6 +14,45 @@ namespace { } +class WalletListenerImpl : public Bitmonero::WalletListener +{ +public: + WalletListenerImpl(Wallet * w) + : m_wallet(w) + { + + } + + virtual void moneySpent(const std::string &txId, uint64_t amount) + { + // TODO + Q_UNUSED(txId) + Q_UNUSED(amount) + } + + virtual void moneyReceived(const std::string &txId, uint64_t amount) + { + // TODO + Q_UNUSED(txId) + Q_UNUSED(amount) + } + + virtual void updated() + { + emit m_wallet->updated(); + } + + // called when wallet refreshed by background thread or explicitly + virtual void refreshed() + { + emit m_wallet->refreshed(); + } + +private: + Wallet * m_wallet; +}; + + QString Wallet::getSeed() const { @@ -88,6 +127,11 @@ bool Wallet::refresh() return result; } +void Wallet::refreshAsync() +{ + m_walletImpl->refreshAsync(); +} + PendingTransaction *Wallet::createTransaction(const QString &dst_addr, const QString &payment_id, quint64 amount, quint32 mixin_count, PendingTransaction::Priority priority) diff --git a/src/libwalletqt/Wallet.h b/src/libwalletqt/Wallet.h index 241b89d8..4d6a1ea0 100644 --- a/src/libwalletqt/Wallet.h +++ b/src/libwalletqt/Wallet.h @@ -77,6 +77,10 @@ public: //! refreshes the wallet Q_INVOKABLE bool refresh(); + + //! refreshes the wallet asynchronously + Q_INVOKABLE void refreshAsync(); + //! creates transaction Q_INVOKABLE PendingTransaction * createTransaction(const QString &dst_addr, const QString &payment_id, quint64 amount, quint32 mixin_count, @@ -103,6 +107,10 @@ public: signals: void updated(); + // emitted when refresh process finished (could take a long time) + // signalling only after we + void refreshed(); + private: Wallet(Bitmonero::Wallet *w, QObject * parent = 0); @@ -110,6 +118,7 @@ private: private: friend class WalletManager; + friend class WalletListenerImpl; //! libwallet's Bitmonero::Wallet * m_walletImpl; // history lifetime managed by wallet; From 7df82af75d66a66b2c156f74d3a9fee45a5c178a Mon Sep 17 00:00:00 2001 From: Ilya Kitaev Date: Wed, 13 Jul 2016 16:01:56 +0300 Subject: [PATCH 46/87] New flags images, added IT,RU,ZH translation files --- lang/flags/bangladesh.png | Bin 1340 -> 9356 bytes lang/flags/brazil.png | Bin 2110 -> 12687 bytes lang/flags/china.png | Bin 1282 -> 9669 bytes lang/flags/german.png | Bin 731 -> 7150 bytes lang/flags/india.png | Bin 846 -> 10228 bytes lang/flags/italy.png | Bin 0 -> 7974 bytes lang/flags/palestine.png | Bin 1173 -> 7164 bytes lang/flags/rpa.png | Bin 2177 -> 11279 bytes lang/flags/russia.png | Bin 805 -> 8911 bytes lang/flags/uk.png | Bin 1995 -> 12940 bytes lang/flags/usa.png | Bin 1716 -> 11316 bytes lang/languages.xml | 3 +- monero-core.pro | 8 +- monero-core_de.ts | 273 +++++++++----- monero-core_en.ts | 273 +++++++++----- monero-core_it.ts | 755 ++++++++++++++++++++++++++++++++++++++ monero-core_ru.ts | 755 ++++++++++++++++++++++++++++++++++++++ monero-core_zh.ts | 755 ++++++++++++++++++++++++++++++++++++++ qml.qrc | 1 + wizard/WizardWelcome.qml | 2 +- 20 files changed, 2617 insertions(+), 208 deletions(-) create mode 100644 lang/flags/italy.png create mode 100644 monero-core_it.ts create mode 100644 monero-core_ru.ts create mode 100644 monero-core_zh.ts diff --git a/lang/flags/bangladesh.png b/lang/flags/bangladesh.png index 099b7eee38bf31b3ef01524ccc76322204cf63c8..6fc33574df0723f2adbbc8b5cae5227f2cec27ce 100644 GIT binary patch literal 9356 zcmV;7By-z|P)004R>004l5008;`004mK004C`008P>0026e000+ooVrmw00006 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 literal 1340 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 JPDHLkV1kW4v-to3 literal 2110 zcmV-E2*LM>P)m=cNr00)*y zL_t(&fyJ6_Ok39($A1_wF?I}iiR*=o*cDl-6*fQ(L?c@uofI-{p_4WRv=2?UqG?km zB2CpgWm{PH5;fAKFs5dc8r5B9t#T-4y_q439*R{1>0dC z?!Dw<-|PG0X!}b?h&pz?*3Gr_~a6v2)vlFqH?B@l#fDrH&`bj9ERU9KEj_EZsh*2V?qOnPYH(FxG zs0vENF(*YErA9g%A<~4fzE5bhdlEPy+G^6J`hk|z-M&5>-4w1`~50r0$QiSDT~hNCt! z!@bC3g;Yd-#X+B1ogX-C)S}y(q_g4yZJtF6Eo991f*J}fYt%VErrNQ>o<{BIx7}%TT_blfuM_tkds(kFyXK#sq`*VQ?-!M zw#~E1epj6CDHp+5ksk9U;#`kO59A*4BAlvOAxY|2x5gAFfB9NDm#>v)9iyc=OiOcE zk<@$h4kJ?SLtJ8VB7QW^i*!kq5Q`UbvZtD%QMVrB>u9W><451Wg~Ps49F|fRy;A)) z;`t&G@sho;a~ElQagy$zk5cK&+-D&^yAzXr=;6c>mlL)xDAqQK|>??hWn?VIW`%1YI2aX}#X&JoouZcJJNE z{x5%aomgyfnJXXM;*C=uD0=DiYonT8NyFW|dxJZ*Z+SBK&sX9&-4&+Z%Ek`+rJAj{d$;4%Bt=#;HrJt-&vPzR7RSb+Ea_R0R3|BO>9@U7M!&i!tHZJ%=S>}T{Ts0@+(RMV#q z);U+aRvnQY2yw-q-ZlQl^kC@fKbih;fiG6~Q<$$V&vL|57Jhx#HDVc#+5#P89xl#1 zbaiyt6ExP(XpKGcX*xN9h(}b0q z4~YjX2v_)!{Au5elVjsgWsRR-JsQ+~jnD&!J;AF-gR1qxSP{p@pQ11GC5Mm&Rz6^l zyf%`|=jwu;c*?>q`zq;TV6{G?zV<#>hdit%45f1vNt}M|1|E0j29KvK{NX<(ocX9s zEp7BRa9JX98(Bs|j(;s#`CG(>+nQqgrsRe?SJ_I=WF!(wq3&?h#>vS~aP#4&Y(umI z5s63yAujPMwT!1MT#pnJS}vr{xw64KuCf&l9r%c;X**M4eaa_cZ^NvjCqzl5Pdl43 z0CdB~EJ_nX8b2%S`HyJxEOJOK9W+qR<*OR$p#5Q5nv~N)7v~*%2uNr~6sz=1d$oBMmEvea=$&yYsZlmWfLdf~o1Vmu0JG@y3J9_|61A~?Q&Kb5SL*w#$t_%u zD98R;@Lkd}@=DqG0D@=KzLRG_ORCeMLsGNB5Bx0sVfxaN<0b~kNN`(|e7ii#9)+wV z2$FhMJA0^x0K$^WK(;Sd=Ml1x9wV02q}qU_sv&@&QXeZ-W_v9WvEwyNcxE_iRTq}538D|$&RrhhA90uh;AO&IaDAK`N8 zH#{ZdFl@`{qLtoKtq~&z;78aChP`ig_XR{_J|6!{$SgwQ7(Q{#0W*VSJ4cMdi||{l o8nJlEyiJG15)ezqXJ)hbFG|_nN)*YrZU6uP07*qoM6N<$g0j@+CjbBd diff --git a/lang/flags/china.png b/lang/flags/china.png index 5f3c6fc846589c8342bf9e84b4829830e0bd7163..dfe1fb66dc002c3ab14e027718d07d8fd4fa5442 100644 GIT binary patch literal 9669 zcmV;$B|6%PP)004R>004l5008;`004mK004C`008P>0026e000+ooVrmw00006 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 Hu0mjfc(^rr literal 1282 zcmV+d1^xPoP)m=cNr00de| zL_t(&f!&%tY}-Z{h98o$AykS?!3f|KMr#L}x|G>tM41{-&CsGl2ggIV21(JWQ{)0g zyVXIaPUfBt$>c4yVNdSju7v@+MT$BGT+6l$II<~rI8tIL{vE04Cl7G2WF7DBz3=Y3 z@9wBZKTCpq3Bg7o4CM`3G5FKIi6<{K1!BLD)DO{Ozs4*pzVlE4aQ4va5%~B!&uFR=uh0tLVh}96uC0|jQ{w_zsP{~&m zfA&Z*VYQk_h$uNCKv*UV`W@i}E*z`*SRiE11i9abz|Ub7Ih0FlEaShXoF!gng@_cmfpYPbb{PB|W zoxt7=65*!s%vI#c6=+w9eq4pDv?%v==ns{*nQBkLR#-tFr=Qlix_5YArI44s?glG*?n-yfoXBJ-Qv$L znw)I3DBMWy)j)Wyf}wKlzD9XorCsw{nB=K~!K2mi7%6Vu@MAULWYcDB%0^M( zWFtOibGQZQu8^VNbfd-LQ;U2>K`SP=CQuzJA#N}!vw%7nIh<6oG)Rrr5Zl|y&ooLm#s4yj(R00Mm@N5+gyXRK)qws8d%FN0p<_wu*1>JEFWZLe^X%J-MS8 z5bHQ|1jHT1sA58Ahqyerc4VTCb0+Kxaj(HfjpsPi!n+_Y4W=!bsJS1!Le>~9t1RKU zqVtC+i+ycMdiL3L>`i-8Q6qsjO|nC*I|X%linq)jq7xG>)G}LL z6LH^d%C;y4HQ)vk3$*N>-L=l!5L8FF{#k?-q^#eI*PK2@yr*KMEW>@psT}~OA*&EC sI^u}m|Kgb|aLxJaeerCa9?iS|0giG;PJue>S^xk507*qoM6N<$f`6!1lK=n! diff --git a/lang/flags/german.png b/lang/flags/german.png index 7756bf592a23d2bc31cd951f3ee6e12fdfa5af8b..320b24ef97237f3fdc51b55d10a72b2de7f291e5 100644 GIT binary patch literal 7150 zcmV004R>004l5008;`004mK004C`008P>0026e000+ooVrmw00006 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 delta 727 zcmV;|0x13NH`@hDiBL{Q4GJ0x0000DNk~Le0000o0000o2nGNE03JVxu>b%71am@3 zR0s$N2z&@+hyVZp32;bRa{vGf5&!@T5&_cPe*6Fc034IS8xwyw(EZ*psMAVX6& z=)AIw0000MbVXQnLvm$dbZKvHAXI5>WdJZZFEKGMF*LTA5{du-0y9ZOK~!jg?V7)C z+b|G^KNA_+DFnK=%G9xbgCs+NjOi1!-a5vQ(aqU1%5R`YaHp)w)~)IraOvC*M+K@Z z>kmbVRFMz3!H|DI{m#cD?uI}g$K@6Xa&A_aU?brksMi$#3dx-v z_?;~W9&1xeQqAovuDWKJx6qQ_F26boS}yg2Kk3Bm?n2pJNA}=(Xa39 zL(#xz;0x|2mO0|Uo)J@btRq!4aLMt9XQhKV#~+@3$5Y<@1y>1Yx+}4ZDgXcg07*qo JM6LruV1i{YKwkg= diff --git a/lang/flags/india.png b/lang/flags/india.png index bf27a1737beea297facfe93afc859f33b1603902..1e56cb3bb7be123a859676e5438f44a8122a25e2 100644 GIT binary patch literal 10228 zcmV004R>004l5008;`004mK004C`008P>0026e000+ooVrmw00006 zVoOIv0RI600RN!9r;`8x00(qQO+^Rb2Mr80DRV;>Q2+oY(@8`@RCwC$oq3ET*L~kV z@2Kiy?qg?WclO}kJeTBB5^2e_L@J6Ui;@i6vQ0TLjMy<8z=(l3h+_eglfXb=ibzgSWu+by=s!1D5j!+9ft zYz84Ww#t?Os7};_!2_eg=vd7TAKO=*9v;%WLhWjyv|%8v1caqiFGz=ns%e@8f#5f7U$zDb4%{h%a`Ks zzIfH1`qsJD*=r3uXPj-20!rurD#LkQ!Mxr;{1}icL6fSWe*1W3@54vK`%fIy@49VT z-#a;`_6_xm;aVuAbg9W)EU<|KBXF+#)viD+6vzOC76|P~fM(m;#bvv6eJ;6p{<8Vb zbLZo)eeLD=nHT5VmjEA*ubkHv%IgKB9E@{U6xNg)dGE2xJwJFn_(yl%rr)!7QXd}b zm$g6$Tnx4iHU?(}LJDLkk)c2a5)q{P5CS2xx`T{lj;k#-N{?Hxz zNAEbO-aR#@c2$)WE)v*w1^|Ij5?NK~evRta$WS6=9>-l_BIf|>$YtXkIBRi{!?rD^ zkzgZ>vks&K4JvS;E$dC|E?rAzUpSw9S38d_v zG*FR348HG3^?}Fk4S(j|BkIw~F*O=EiERro0;wcID`c2L(1QW8FGT7rsVdAOQmfm{ z@R>T&_gwZbe{FHbVcQ0?mSEck=Un=}w4fY9T3T(xKYm~E(RbaUfAZjNy*CsR8wsS7sH#R)6*5q$nnn#( zQ1t+$1SdQQ9dkJAQdQW_&Xh+9jO%2%&RA?KAz6!YvBin(bHaeIIA>T}OE_~Ox%BnZ z$)A4iRP<+O=A)NMV@$->Y4fWt>NNx*NL5kOQjY!9iP{JM*}K9|{Ll&gk?GN3ShOV~ zk%aY-aJYi5YJ`yJfe<}ZK?WLOL?=OY0-WFU5C|kfDunVDsX*y;zf=e%5JG~G2q9A! z7;0oyrzhz^2!WKTD-=o)RumJ%a`3is@%AHw;{K}C>t`0sb?cm;iTT(3rUX)UQ6W&D z=nHQ9r3b6O_@gKFuim*|AE~0HYfFMkKsZ!ERRV-cl@W~9(8G0PB}C`|sRPgfLI((? zkxBy!Arw;TZWsjsl^Ql%ke&BZC@CPRR0>51Fb*LFLQ14iNS!7PeRU~!kBI5%KKHK4 zs;Zt_WM)0GO*X3ImO;5yKztYqr~v)%n65tX3vUm8{XNIkW?oHQ8s6O;*GSW8~$R7R+_J82TxqaV7RkKZw=4unn)~cxz z$PKDeBY3Vw;??N9_G*n(9*k_=<+7ykfOsm&8j|G7A%hi2gau4$TOo6R0wEky34%Z{ zT4iKd+WV_Y&42HPxxmJh_!UEWwLp9~HT+|DR3H1$G4b)^6KZehG-06Vo2pV74YBJW zlp+|bq{gg-sCq3mq>vzWCle}sepZWB9y2myLRNcKp%%IVxcB)SptnvH(lK@pMF@e= zG6iDuzmtPYUrPyEONK%>IDoxZ3VrQ`xn!mXl-CuA-^Qo_gYP?7dGsSk`NSQQYJWv& z!cfz{yGl3`V%A&%Ln@670#siep;XrCq_PZ0c7k=T5+ySwWb169sO71Zget+vVn~r$ zc`gf?_5yU=5`_ZEz+r4*TH#-@O%G8@Fc{jwK4EGMhYdGO8qvV_te2$k;d<&va?tnJWgdb=$C=uRBLcTcjo z64BYnic6Isv+jLU3JLj33nJ?~3zehd%mL-3d1lsr59eB$Qf@k)+zgO%+!%iBaQN8! z5Ae&kjp+W0($t4S`gT_kQesve!DtoL7i31s0(m9$+=0=z<^$QOFJh&%ubIztVO(FN?1JPB%Z+r7Cic znzS`#-1PZ1`Se25%<~E|ubl$va#aX9@zcljFTP`n_m9>BQ4KYH6BWXd5Ze-91;IoO zsddq|kYRwRBSJ$aY3j~kT2k5z$hz0ic;e(ul=2M0ma#_ z^w*W4C}&+d-ft~fP{#Bp3HqC} zeA8ODb~7s3DUiH7eegZ|!$;pU<$iw8psdz|v;nAe+VxS?`Xn945F*Q%!T=eDB^X(I zp>R!kyF(Ys=>&3Br~#3IECRUSzK~t(PVn{`cQ%c)`7zE@gYY4h<3(c9J~c0}cT_?e zM~uySjUBdaCRY=6`rMK^pQ&U=P~`X`T8`g8%41V?hC`_cD~jq!fUe{jkwoas z7zSL+;+jbo^m3zkDQZ_=Q|h+xVR*f+#pgD}64_Bqaf~P$gu+u$-cT$?y)w6$CJ*^% zN)1pcGqtMX@A&;}#XcpGTB5b05-N5Nis6%EJf^iA&nmO>?D>j;=>JF z=MD4xIO#3L#3JwW**;P1x1H~EQMw41sW$)Hwm7dM1j+oG(zafH?xHv*-IfBBbOaKH zYJ!9P=Aj2B)V)B>1F~h=YUW~jrpVB}w~aqCHLb_1!y#e4O1O6rF%-f|La?ib>I-qL z^dx1VkfBc9)j$mq1fV9Fh?xi2`(i&`%AXOV1Mi3`p48j;>lBBPb1lkF%9;50JK|P{cZ!sks z&RNfQmJ$(w&>*R?!J&RlZJ0x(dqn4UzSs$PMs-YLw1Rst8}GH&H{Hf+e8$z zQe5%u@GPSOYc-@GgUEEW&1htVPBVI*te%O@`;^IsEMNKd}GO z9~_vOnVBb@y<%ReLh?)1K<&gm_uN0Z|LB2m>;S>=5aHkuqFN;}397Er&cZACBZayHqv< zvcX*)K79BcLqmhrFwg`#OLemvrj*Ps$g7oEKani*`>ZCqR6SD242A3Y$9oke1;#nf zu0}jH*Wf#|>&!=n^(19Ds035}0S`{}ao=c-UD;5vb>+XY@wM~nLB=|pK9s&My4LA? z>0z$u5lF8w^}I?+!ay@TJX}3;b^Gph5~Ij@FtW z2vAC4t?dl*cr{vVa5bB6JI`foR&uxW+m1OfGWo!z^4 z-!(Ngwfow&YuD+t-g}nqg1Q1K$BrF4GBPro_}w$}j8_BpRW-kM?{0SY2h<(3m10oBuiQCKqy$e~UE|X)FY-SxuJC^@ zF7t0rO!9$!LtRb5Ud4y79klX$WiV1olu}eG6-Gx#_Z&TX^vJbq*IvkMx~bdRD(x-e z$Mp2{@qvMX{v3ujtyE#riUu57|YKe9ph8)-p{{#U^nf=^0Qw$$L~FVoy58A^Uh7qzn9TB z+n+B=DS{wiU|^ts-@bjvNz-4=7U^#

?a8t9*#oIF;m)e0C&siKDDK(?#tH(P{& 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 Y07LMPM?*$k-T(jq07*qoM6N<$g1n+^F8}}l diff --git a/lang/flags/italy.png b/lang/flags/italy.png new file mode 100644 index 0000000000000000000000000000000000000000..177716ad302eef13af440c4942410239201af5c4 GIT binary patch literal 7974 zcmV+>AKBoEP)004R>004l5008;`004mK004C`008P>0026e000+ooVrmw00006 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 literal 0 HcmV?d00001 diff --git a/lang/flags/palestine.png b/lang/flags/palestine.png index 78f5b1d420cb9fb1b8e21957b4114e9a7cd39042..66b5939ed7cf06e3f949153061b7546205a47b22 100644 GIT binary patch literal 7164 zcmV004R>004l5008;`004mK004C`008P>0026e000+ooVrmw00006 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 diff --git a/lang/flags/rpa.png b/lang/flags/rpa.png index 61de692d71c85ab90c5dafda09ec5023c925921c..07c4655f2606cb977ecfaca7177b815e72179fec 100644 GIT binary patch literal 11279 zcmV+qEb!BbP)004R>004l5008;`004mK004C`008P>0026e000+ooVrmw00006 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 literal 2177 zcmV-{2!8j8P)m=cNr00-Gg zL_t(&fz6t0R8vmybh<`6t#)R_+jPlH zM-;oN)UkDn)wa-zZJk=%+OaNX6?;Lz3W6jGL109J+#%da0wIZHe$>!hAtaIb{7UxT z=R4oK-t+D3owJG1eQUwbE`TPtLc%dqd~^P_SXy2EvZAj)Qr_9=qf#iC6QiS)u{(_< z0H6mfm>@YgC)xEdZD$#`nZ~krMvR%#qTo zt7J`0cIG5N^Yc?M!a_@(mM{CeaL3L{b7BA*Le!Wd$md{Y^3avyoRN}Jwz)Spgj3A& zPUn>?(pwK6x-ipg^@IS%#rV9L6(lYz|8DU6LO(NeH3^qBiA~HU&unPjf8e&M*BBI{ zMn}*erTm*GTKrGcIHN+g#i!0ca`9Aiaou&d zOT3?9PoaYAvv9JX%4WlLeG*i1IYtEn>nqSe-qA@{_77}#OL_j^#>5zWwD{Qt4tKhQ z70seH8URRSQXDJD!z;NN=p5{s97kp{G4J)g@c%d$RJK(oFg;|=&9srytJTIHGlPx@ zds=ILt}Wx}ws#GRU`uxty}kC*8o>+lw)Py|K0P6Sh}m@V+seW<#)8R}3Y6X9qe@tV z2wyH{yLRMl?JlfmH*&`68@|(Q*AjB*xjjL7MPHtlD;30a0d47yysbUNd;c5X3|}ek zRkxAnEe(L`!54c9wY;vQ(beRDHgZQLmlMjNp^b{}?sq1IRP83AsGo_w8}}rCa^?JX za{}AK9YuFHleH+BV1^|!`q`G30=MWS$uS+E@y{_X*g;{}Z;?Bgx)5^a7qX0nu zTM7&IPwtI(Zh3##diE$fsKIC%95g%jY`UZD>!*9B@3Tg~ebs4`#*Bl@voN%-!8YN9l7Tt(JbHw)eRHQE5U{RbYPy(k=E;XrKJvW1_YpC78(nzbVqJ68{ZP-@RG)~X$A6XhS$NG6lXZ+CTd(KI`z zb4TSIidxaYtS4*?Cpv>dX0cdribNv4Rq%*JhMX@OV5oK?sEr8o;hxt_k zQ;gdaz~lgM2)f9sUgCUU1yKZp?+)U zXEIan*>xcZ0#NDeCO|-CpnWzm$q^s63yMxIhdWWH)h;ER@!3c;&euKyj~189RR{zE zim?HQ(&LbxFaYAdM5|pugyZmSJ9G`(e)eM09aR)6l_~`=N4HbUh5Ns?O9^M38i>Y! z8*J}_8ZTGR5z?YXi-fuzhtlIv^U-|0N2y#DjTGMeM@j?$IMEp*3YAI~00;%9cHwCE z(9<+J)$LI zWLS=@{pEQ6*#fJI0D$07_e}L?B7EOkOg#9K7#wrAu)1W%Z=+_@1vQaT9$l3c-IM>E zlO{Aemjs4yVA35;Es+zGwEN7i(>f48B85;4ZEIQSghwe+XuCMl!^TSZ`r*QJdc5Z*V{I zc3MXG%AjXNy6v`f0RVzS+=oB?B9UvT&*1VsEMwj1w)oVjmfntfp{SK+YZm}uL4b=a zHr^+Mt$9VUQjhRvi@ay|T0J#9t zfZ25c0M@PdZ8(`57jDc6HB;`{9~>AM1@Iz(S1K#IWwTKWz^_y6YkvtoRd7l8#B?G6 zBQ>IXgoERe2LK+4=hJQu7bhm?A)z{Ijbgt15dkfBK%{t~3 zxX?whILf0WF2U!GEiYcS?3j}|B6K3 z1ps_czV%5bO37*K1mHi?#r3cA3uIXy#1h>^({BI+w<=7;P&s{ zq$!eaC~~d2s?-|3tFr%}2lO5w}RA&IARiN)`hLtWp# zee2xW+0n9mp={L>2iWu=X~Q)wK7M5**a5;xik0^D>sJyIk{Y8n&x1}(67~-Ni(exP z2~-k6m#KOtLcS-ft+O#4Pho7~ws0w0gm^Hgs|2eKSRpmm~Gh^S-YGx}s7*Bx8MmIrBSsrr~`2&#Ggmb(UIQn2PSSaxBGE*Vp z{p0OcU)1yOJ&EUA7hPRl;t-lf2l7;khh!84YW}O}lm7nxKOU1}{}!p9 hA@@1lO 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 O0F>lZWvivl!u|(q%v^E+ literal 805 zcmV+=1KRwFP)m=cNr00MnU zL_t(&f$f?xZrd;vhCd7K9A2QPymXCwfTHP^E$ImwZ{1Sgz(-Jk4j$zb#7A(40!1fz z>Sz_cfC68j!+Q!GS&~Ibmc$?*Kp=)`@%?#^yWNhKFu(;c(Pp%r>-QTV(H3c2 z>*vOd8VWJMjkfdBp2ci{P+M(Lb%prA9Qaaq-~yKV+}QSqo@IXn1i+T|MWD~z>KMl6 zhc+I$M%;g9Ge83#kdR$f>0H$#0dP-uBG9q!b?l=Y6JiiA+%~hR5w~hXX{eQjmO+-s zx~oy!Y>?$WcV#Q{NXT$RfY=fieOV+V0Ip79O+&6Im@|2yb&X82A@iBcYL*pZkj<#e z)u|2-$fo=_U$UVUFnkdpHa-cleUJFauIdtFG@aS^$h;3r8ZuA+NIl}?o;Om)K>X_G zuYUlSNPXZUm%w85{=;{WID6ZxIraGq_#P5gu8IJ$-S?3E#dR(`q`r2Y$%~MIG4XIC zB{{~K?cu{Z0&aRG_c$oVEw4O!1D<*W9dlNtvrWyuLe}ms zyc6hHl@}b_!-oX<4~;p^+zIvBGc3KI-Pvt1$cr#H)IYI|5CKb?yynzU$UX-6$l;4; jslbTC7tems(!Bc*)(Ms2!I@1i00000NkvXXu0mjfihO1~ diff --git a/lang/flags/uk.png b/lang/flags/uk.png index cb68609c395e2e08910fae776ba36cf78e7798cd..cbd99556ef9ad5edc7064252ba5511cf42f294f6 100644 GIT binary patch literal 12940 zcmV;7GIPy|P)004R>004l5008;`004mK004C`008P>0026e000+ooVrmw00006 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 - + + diff --git a/monero-core.pro b/monero-core.pro index 84d4f35a..f11cff8a 100644 --- a/monero-core.pro +++ b/monero-core.pro @@ -86,13 +86,17 @@ unix { # translations files; TRANSLATIONS = monero-core_en.ts \ # English (could be untranslated) - monero-core_de.ts # Deutsch + monero-core_de.ts \ # Deutsch + monero-core_zh.ts \ # Chineese + monero-core_ru.ts \ # Russian + monero-core_it.ts \ # Italy + # extra make targets for lupdate and lrelease invocation # use "make lupdate" to update *.ts files and "make lrelease" to generate *.qm files lupdate.commands = lupdate $$_PRO_FILE_ -lupdate.depends = $$SOURCES $$HEADERS +lupdate.depends = $$SOURCES $$HEADERS lrelease.commands = lrelease $$_PRO_FILE_ lrelease.depends = lupdate translate.commands = $(COPY) *.qm ${DESTDIR} diff --git a/monero-core_de.ts b/monero-core_de.ts index 0a99e2fb..4b16cc31 100644 --- a/monero-core_de.ts +++ b/monero-core_de.ts @@ -60,47 +60,47 @@ BasicPanel - + Locked Balance: - + 78.9239845 - + Availible Balance: - + 2324.9239845 - + amount... - + SEND - + destination... - + Privacy level - + payment ID (optional)... @@ -251,83 +251,43 @@ LeftPanel - - Locked balance - - - - + Test tip 1<br/><br/>line 2 - - Unlocked - - - - + Test tip 2<br/><br/>line 2 - - Dashboard + + Balance - - D + + Unlocked balance - + Transfer - + T - - History - - - - - H + + Receive - Address book - - - - - B - - - - - Mining - - - - - M - - - - - Settings - - - - - S + R @@ -352,25 +312,48 @@ PrivacyLevelSmall - + LOW - + MEDIUM - + HIGH + + Receive + + + Address + + + + + Integrated address + + + + + Payment ID + + + + + Generate + + + RightPanel - + Twitter @@ -409,42 +392,47 @@ Transfer - + Amount - - Transaction prority + + Transaction priority - + Amount... - + Privacy Level - + + Cost + + + + <style type='text/css'>a {text-decoration: none; color: #FF6C3C; font-size: 14px;}</style> Address <font size='2'> ( Type in or select from </font> <a href='#'>Address</a><font size='2'> book )</font> - + Payment ID <font size='2'>( Optional )</font> - + Description <font size='2'>( An optional description that will be saved to the local address book if entered )</font> - + SEND @@ -490,12 +478,12 @@ WizardCreateWallet - + A new wallet has been created for you - + This is the 25 word mnemonic for your wallet @@ -503,32 +491,32 @@ WizardDonation - + Monero development is solely supported by donations - + Enable auto-donations of? - + % of my fee added to each transaction - + For every transaction, a small transaction fee is charged. This option lets you add an additional amount, as a percentage of that fee, to your transaction to support Monero development. For instance, a 50% autodonation take a transaction fee of 0.005 XMR and add a 0.0025 XMR to support Monero development. - + Allow background mining? - + Mining secures the Monero network, and also pays a small reward for the work done. This option will let Monero mine when your computer is on mains power and is idle. It will stop mining when you continue working. @@ -576,7 +564,7 @@ - + You’re all setup! @@ -584,7 +572,17 @@ WizardMain - + + Now that your wallet has been created, please set a password for the wallet + + + + + Now that your wallet has been restored, please set a password for the wallet + + + + USE MONERO @@ -592,17 +590,17 @@ WizardManageWalletUI - + This is the name of your wallet. You can change it to a different name if you’d like: - + My account name - + Your wallet is stored in @@ -618,22 +616,22 @@ WizardOptions - - I want + + Welcome to Monero! - + Please select one of the following options: - + This is my first time, I want to<br/>create a new account - + I want to recover my account<br/>from my 24 work seed @@ -641,12 +639,7 @@ WizardPassword - - Now that your wallet has been created, please set a password for the wallet - - - - + Note that this password cannot be recovered, and if forgotten you will need to restore your wallet from the mnemonic seed you were just given<br/><br/> Your password will be used to protect your wallet and to confirm actions, so make sure that your password is sufficiently secure. Note that this password cannot be recovered, and if forgotten you will need to restore your wallet from the mnemonic seed you were just given<br/><br/> @@ -657,17 +650,17 @@ WizardRecoveryWallet - + My account name - + We're ready to recover your account - + Please enter your 25 word private key @@ -675,14 +668,90 @@ WizardWelcome - + Welcome - + Please choose a language and regional format. + + main + + + + + Error + + + + + Couldn't open wallet: + + + + + Can't create transaction: + + + + + Confirmation + + + + + Please confirm transaction: + + + + + + + +Address: + + + + + +Payment ID: + + + + + +Amount: + + + + + +Fee: + + + + + Couldn't send the money: + + + + + Information + + + + + Money sent successfully + + + + + Initializing Wallet... + + + diff --git a/monero-core_en.ts b/monero-core_en.ts index e59eb72d..bae9d7a9 100644 --- a/monero-core_en.ts +++ b/monero-core_en.ts @@ -60,47 +60,47 @@ BasicPanel - + Locked Balance: - + 78.9239845 - + Availible Balance: - + 2324.9239845 - + amount... - + SEND - + destination... - + Privacy level - + payment ID (optional)... @@ -251,83 +251,43 @@ LeftPanel - - Locked balance - - - - + Test tip 1<br/><br/>line 2 - - Unlocked - - - - + Test tip 2<br/><br/>line 2 - - Dashboard + + Balance - - D + + Unlocked balance - + Transfer - + T - - History - - - - - H + + Receive - Address book - - - - - B - - - - - Mining - - - - - M - - - - - Settings - - - - - S + R @@ -352,25 +312,48 @@ PrivacyLevelSmall - + LOW - + MEDIUM - + HIGH + + Receive + + + Address + + + + + Integrated address + + + + + Payment ID + + + + + Generate + + + RightPanel - + Twitter @@ -409,42 +392,47 @@ Transfer - + Amount - - Transaction prority + + Transaction priority - + Amount... - + Privacy Level - + + Cost + + + + <style type='text/css'>a {text-decoration: none; color: #FF6C3C; font-size: 14px;}</style> Address <font size='2'> ( Type in or select from </font> <a href='#'>Address</a><font size='2'> book )</font> - + Payment ID <font size='2'>( Optional )</font> - + Description <font size='2'>( An optional description that will be saved to the local address book if entered )</font> - + SEND @@ -490,12 +478,12 @@ WizardCreateWallet - + A new wallet has been created for you - + This is the 25 word mnemonic for your wallet @@ -503,32 +491,32 @@ WizardDonation - + Monero development is solely supported by donations - + Enable auto-donations of? - + % of my fee added to each transaction - + For every transaction, a small transaction fee is charged. This option lets you add an additional amount, as a percentage of that fee, to your transaction to support Monero development. For instance, a 50% autodonation take a transaction fee of 0.005 XMR and add a 0.0025 XMR to support Monero development. - + Allow background mining? - + Mining secures the Monero network, and also pays a small reward for the work done. This option will let Monero mine when your computer is on mains power and is idle. It will stop mining when you continue working. @@ -576,7 +564,7 @@ - + You’re all setup! @@ -584,7 +572,17 @@ WizardMain - + + Now that your wallet has been created, please set a password for the wallet + + + + + Now that your wallet has been restored, please set a password for the wallet + + + + USE MONERO @@ -592,17 +590,17 @@ WizardManageWalletUI - + This is the name of your wallet. You can change it to a different name if you’d like: - + My account name - + Your wallet is stored in @@ -618,22 +616,22 @@ WizardOptions - - I want + + Welcome to Monero! - + Please select one of the following options: - + This is my first time, I want to<br/>create a new account - + I want to recover my account<br/>from my 24 work seed @@ -641,12 +639,7 @@ WizardPassword - - Now that your wallet has been created, please set a password for the wallet - - - - + Note that this password cannot be recovered, and if forgotten you will need to restore your wallet from the mnemonic seed you were just given<br/><br/> Your password will be used to protect your wallet and to confirm actions, so make sure that your password is sufficiently secure. Note that this password cannot be recovered, and if forgotten you will need to restore your wallet from the mnemonic seed you were just given<br/><br/> @@ -657,17 +650,17 @@ WizardRecoveryWallet - + My account name - + We're ready to recover your account - + Please enter your 25 word private key @@ -675,14 +668,90 @@ WizardWelcome - + Welcome - + Please choose a language and regional format. + + main + + + + + Error + + + + + Couldn't open wallet: + + + + + Can't create transaction: + + + + + Confirmation + + + + + Please confirm transaction: + + + + + + + +Address: + + + + + +Payment ID: + + + + + +Amount: + + + + + +Fee: + + + + + Couldn't send the money: + + + + + Information + + + + + Money sent successfully + + + + + Initializing Wallet... + + + diff --git a/monero-core_it.ts b/monero-core_it.ts new file mode 100644 index 00000000..b6bec1a6 --- /dev/null +++ b/monero-core_it.ts @@ -0,0 +1,755 @@ + + + + + AddressBook + + + Add new entry + + + + + Address + + + + + <b>Tip tekst test</b> + + + + + Payment ID <font size='2'>(Optional)</font> + + + + + <b>Payment ID</b><br/><br/>A unique user name used in<br/>the address book. It is not a<br/>transfer of information sent<br/>during thevtransfer + + + + + Description <font size='2'>(Local database)</font> + + + + + <b>Tip tekst test</b><br/><br/>test line 2 + + + + + ADD + + + + + AddressBookTable + + + No more results + + + + + Payment ID: + + + + + BasicPanel + + + Locked Balance: + + + + + 78.9239845 + + + + + Availible Balance: + + + + + 2324.9239845 + + + + + amount... + + + + + SEND + + + + + destination... + + + + + Privacy level + + + + + payment ID (optional)... + + + + + Dashboard + + + Quick transfer + + + + + SEND + + + + + <style type='text/css'>a {text-decoration: none; color: #FF6C3C; font-size: 14px;}</style> lookng for security level and address book? go to <a href='#'>Transfer</a> tab + + + + + DashboardTable + + + No more results + + + + + Date + + + + + Balance + + + + + Amount + + + + + History + + + Filter trasactions history + + + + + Address + + + + + + + + + + <b>Tip tekst test</b> + + + + + Payment ID <font size='2'>(Optional)</font> + + + + + <b>Payment ID</b><br/><br/>A unique user name used in<br/>the address book. It is not a<br/>transfer of information sent<br/>during thevtransfer + + + + + Description <font size='2'>(Local database)</font> + + + + + <b>Tip tekst test</b><br/><br/>test line 2 + + + + + Date from + + + + + + To + + + + + FILTER + + + + + Advance filtering + + + + + Type of transation + + + + + Amount from + + + + + HistoryTable + + + No more results + + + + + Payment ID: + + + + + Date + + + + + Balance + + + + + Amount + + + + + LeftPanel + + + Balance + + + + + Test tip 1<br/><br/>line 2 + + + + + Unlocked balance + + + + + Test tip 2<br/><br/>line 2 + + + + + Transfer + + + + + T + + + + + Receive + + + + + R + + + + + NetworkStatusItem + + + Network status + + + + + Connected + + + + + Disconnected + + + + + PrivacyLevelSmall + + + LOW + + + + + MEDIUM + + + + + HIGH + + + + + Receive + + + Address + + + + + Integrated address + + + + + Payment ID + + + + + Generate + + + + + RightPanel + + + Twitter + + + + + SearchInput + + + Search by... + + + + + SEARCH + + + + + TickDelegate + + + LOW + + + + + MEDIUM + + + + + HIGH + + + + + Transfer + + + Amount + + + + + Transaction priority + + + + + Amount... + + + + + Privacy Level + + + + + Cost + + + + + <style type='text/css'>a {text-decoration: none; color: #FF6C3C; font-size: 14px;}</style> Address <font size='2'> ( Type in or select from </font> <a href='#'>Address</a><font size='2'> book )</font> + + + + + Payment ID <font size='2'>( Optional )</font> + + + + + Description <font size='2'>( An optional description that will be saved to the local address book if entered )</font> + + + + + SEND + + + + + WizardConfigure + + + We’re almost there - let’s just configure some Monero preferences + + + + + Kickstart the Monero blockchain? + + + + + It is very important to write it down as this is the only backup you will need for your wallet. You will be asked to confirm the seed in the next screen to ensure it has copied down correctly. + + + + + Enable disk conservation mode? + + + + + Disk conservation mode uses substantially less disk-space, but the same amount of bandwidth as a regular Monero instance. However, storing the full blockchain is beneficial to the security of the Monero network. If you are on a device with limited disk space, then this option is appropriate for you. + + + + + Allow background mining? + + + + + Mining secures the Monero network, and also pays a small reward for the work done. This option will let Monero mine when your computer is on mains power and is idle. It will stop mining when you continue working. + + + + + WizardCreateWallet + + + A new wallet has been created for you + + + + + This is the 25 word mnemonic for your wallet + + + + + WizardDonation + + + Monero development is solely supported by donations + + + + + Enable auto-donations of? + + + + + % of my fee added to each transaction + + + + + For every transaction, a small transaction fee is charged. This option lets you add an additional amount, as a percentage of that fee, to your transaction to support Monero development. For instance, a 50% autodonation take a transaction fee of 0.005 XMR and add a 0.0025 XMR to support Monero development. + + + + + Allow background mining? + + + + + Mining secures the Monero network, and also pays a small reward for the work done. This option will let Monero mine when your computer is on mains power and is idle. It will stop mining when you continue working. + + + + + WizardFinish + + + <b>Language:</b> + + + + + <b>Account name:</b> + + + + + <b>Words:</b> + + + + + <b>Wallet Path: </b> + + + + + <b>Enable auto donation: </b> + + + + + <b>Auto donation amount: </b> + + + + + <b>Allow background mining: </b> + + + + + An overview of your Monero configuration is below: + + + + + You’re all setup! + + + + + WizardMain + + + Now that your wallet has been created, please set a password for the wallet + + + + + Now that your wallet has been restored, please set a password for the wallet + + + + + USE MONERO + + + + + WizardManageWalletUI + + + This is the name of your wallet. You can change it to a different name if you’d like: + + + + + My account name + + + + + Your wallet is stored in + + + + + WizardMemoTextInput + + + It is very important to write it down as this is the only backup you will need for your wallet. You will be asked to confirm the seed in the next screen to ensure it has copied down correctly. + + + + + WizardOptions + + + Welcome to Monero! + + + + + Please select one of the following options: + + + + + This is my first time, I want to<br/>create a new account + + + + + I want to recover my account<br/>from my 24 work seed + + + + + WizardPassword + + + Note that this password cannot be recovered, and if forgotten you will need to restore your wallet from the mnemonic seed you were just given<br/><br/> + Your password will be used to protect your wallet and to confirm actions, so make sure that your password is sufficiently secure. + + + + + WizardRecoveryWallet + + + My account name + + + + + We're ready to recover your account + + + + + Please enter your 25 word private key + + + + + WizardWelcome + + + Welcome + + + + + Please choose a language and regional format. + + + + + main + + + + + Error + + + + + Couldn't open wallet: + + + + + Can't create transaction: + + + + + Confirmation + + + + + Please confirm transaction: + + + + + + + +Address: + + + + + +Payment ID: + + + + + +Amount: + + + + + +Fee: + + + + + Couldn't send the money: + + + + + Information + + + + + Money sent successfully + + + + + Initializing Wallet... + + + + diff --git a/monero-core_ru.ts b/monero-core_ru.ts new file mode 100644 index 00000000..8682ecb9 --- /dev/null +++ b/monero-core_ru.ts @@ -0,0 +1,755 @@ + + + + + AddressBook + + + Add new entry + + + + + Address + + + + + <b>Tip tekst test</b> + + + + + Payment ID <font size='2'>(Optional)</font> + + + + + <b>Payment ID</b><br/><br/>A unique user name used in<br/>the address book. It is not a<br/>transfer of information sent<br/>during thevtransfer + + + + + Description <font size='2'>(Local database)</font> + + + + + <b>Tip tekst test</b><br/><br/>test line 2 + + + + + ADD + + + + + AddressBookTable + + + No more results + + + + + Payment ID: + + + + + BasicPanel + + + Locked Balance: + + + + + 78.9239845 + + + + + Availible Balance: + + + + + 2324.9239845 + + + + + amount... + + + + + SEND + + + + + destination... + + + + + Privacy level + + + + + payment ID (optional)... + + + + + Dashboard + + + Quick transfer + + + + + SEND + + + + + <style type='text/css'>a {text-decoration: none; color: #FF6C3C; font-size: 14px;}</style> lookng for security level and address book? go to <a href='#'>Transfer</a> tab + + + + + DashboardTable + + + No more results + + + + + Date + + + + + Balance + + + + + Amount + + + + + History + + + Filter trasactions history + + + + + Address + + + + + + + + + + <b>Tip tekst test</b> + + + + + Payment ID <font size='2'>(Optional)</font> + + + + + <b>Payment ID</b><br/><br/>A unique user name used in<br/>the address book. It is not a<br/>transfer of information sent<br/>during thevtransfer + + + + + Description <font size='2'>(Local database)</font> + + + + + <b>Tip tekst test</b><br/><br/>test line 2 + + + + + Date from + + + + + + To + + + + + FILTER + + + + + Advance filtering + + + + + Type of transation + + + + + Amount from + + + + + HistoryTable + + + No more results + + + + + Payment ID: + + + + + Date + + + + + Balance + + + + + Amount + + + + + LeftPanel + + + Balance + + + + + Test tip 1<br/><br/>line 2 + + + + + Unlocked balance + + + + + Test tip 2<br/><br/>line 2 + + + + + Transfer + + + + + T + + + + + Receive + + + + + R + + + + + NetworkStatusItem + + + Network status + + + + + Connected + + + + + Disconnected + + + + + PrivacyLevelSmall + + + LOW + + + + + MEDIUM + + + + + HIGH + + + + + Receive + + + Address + + + + + Integrated address + + + + + Payment ID + + + + + Generate + + + + + RightPanel + + + Twitter + + + + + SearchInput + + + Search by... + + + + + SEARCH + + + + + TickDelegate + + + LOW + + + + + MEDIUM + + + + + HIGH + + + + + Transfer + + + Amount + + + + + Transaction priority + + + + + Amount... + + + + + Privacy Level + + + + + Cost + + + + + <style type='text/css'>a {text-decoration: none; color: #FF6C3C; font-size: 14px;}</style> Address <font size='2'> ( Type in or select from </font> <a href='#'>Address</a><font size='2'> book )</font> + + + + + Payment ID <font size='2'>( Optional )</font> + + + + + Description <font size='2'>( An optional description that will be saved to the local address book if entered )</font> + + + + + SEND + + + + + WizardConfigure + + + We’re almost there - let’s just configure some Monero preferences + + + + + Kickstart the Monero blockchain? + + + + + It is very important to write it down as this is the only backup you will need for your wallet. You will be asked to confirm the seed in the next screen to ensure it has copied down correctly. + + + + + Enable disk conservation mode? + + + + + Disk conservation mode uses substantially less disk-space, but the same amount of bandwidth as a regular Monero instance. However, storing the full blockchain is beneficial to the security of the Monero network. If you are on a device with limited disk space, then this option is appropriate for you. + + + + + Allow background mining? + + + + + Mining secures the Monero network, and also pays a small reward for the work done. This option will let Monero mine when your computer is on mains power and is idle. It will stop mining when you continue working. + + + + + WizardCreateWallet + + + A new wallet has been created for you + + + + + This is the 25 word mnemonic for your wallet + + + + + WizardDonation + + + Monero development is solely supported by donations + + + + + Enable auto-donations of? + + + + + % of my fee added to each transaction + + + + + For every transaction, a small transaction fee is charged. This option lets you add an additional amount, as a percentage of that fee, to your transaction to support Monero development. For instance, a 50% autodonation take a transaction fee of 0.005 XMR and add a 0.0025 XMR to support Monero development. + + + + + Allow background mining? + + + + + Mining secures the Monero network, and also pays a small reward for the work done. This option will let Monero mine when your computer is on mains power and is idle. It will stop mining when you continue working. + + + + + WizardFinish + + + <b>Language:</b> + + + + + <b>Account name:</b> + + + + + <b>Words:</b> + + + + + <b>Wallet Path: </b> + + + + + <b>Enable auto donation: </b> + + + + + <b>Auto donation amount: </b> + + + + + <b>Allow background mining: </b> + + + + + An overview of your Monero configuration is below: + + + + + You’re all setup! + + + + + WizardMain + + + Now that your wallet has been created, please set a password for the wallet + + + + + Now that your wallet has been restored, please set a password for the wallet + + + + + USE MONERO + + + + + WizardManageWalletUI + + + This is the name of your wallet. You can change it to a different name if you’d like: + + + + + My account name + + + + + Your wallet is stored in + + + + + WizardMemoTextInput + + + It is very important to write it down as this is the only backup you will need for your wallet. You will be asked to confirm the seed in the next screen to ensure it has copied down correctly. + + + + + WizardOptions + + + Welcome to Monero! + + + + + Please select one of the following options: + + + + + This is my first time, I want to<br/>create a new account + + + + + I want to recover my account<br/>from my 24 work seed + + + + + WizardPassword + + + Note that this password cannot be recovered, and if forgotten you will need to restore your wallet from the mnemonic seed you were just given<br/><br/> + Your password will be used to protect your wallet and to confirm actions, so make sure that your password is sufficiently secure. + + + + + WizardRecoveryWallet + + + My account name + + + + + We're ready to recover your account + + + + + Please enter your 25 word private key + + + + + WizardWelcome + + + Welcome + + + + + Please choose a language and regional format. + + + + + main + + + + + Error + + + + + Couldn't open wallet: + + + + + Can't create transaction: + + + + + Confirmation + + + + + Please confirm transaction: + + + + + + + +Address: + + + + + +Payment ID: + + + + + +Amount: + + + + + +Fee: + + + + + Couldn't send the money: + + + + + Information + + + + + Money sent successfully + + + + + Initializing Wallet... + + + + diff --git a/monero-core_zh.ts b/monero-core_zh.ts new file mode 100644 index 00000000..411a8768 --- /dev/null +++ b/monero-core_zh.ts @@ -0,0 +1,755 @@ + + + + + AddressBook + + + Add new entry + + + + + Address + + + + + <b>Tip tekst test</b> + + + + + Payment ID <font size='2'>(Optional)</font> + + + + + <b>Payment ID</b><br/><br/>A unique user name used in<br/>the address book. It is not a<br/>transfer of information sent<br/>during thevtransfer + + + + + Description <font size='2'>(Local database)</font> + + + + + <b>Tip tekst test</b><br/><br/>test line 2 + + + + + ADD + + + + + AddressBookTable + + + No more results + + + + + Payment ID: + + + + + BasicPanel + + + Locked Balance: + + + + + 78.9239845 + + + + + Availible Balance: + + + + + 2324.9239845 + + + + + amount... + + + + + SEND + + + + + destination... + + + + + Privacy level + + + + + payment ID (optional)... + + + + + Dashboard + + + Quick transfer + + + + + SEND + + + + + <style type='text/css'>a {text-decoration: none; color: #FF6C3C; font-size: 14px;}</style> lookng for security level and address book? go to <a href='#'>Transfer</a> tab + + + + + DashboardTable + + + No more results + + + + + Date + + + + + Balance + + + + + Amount + + + + + History + + + Filter trasactions history + + + + + Address + + + + + + + + + + <b>Tip tekst test</b> + + + + + Payment ID <font size='2'>(Optional)</font> + + + + + <b>Payment ID</b><br/><br/>A unique user name used in<br/>the address book. It is not a<br/>transfer of information sent<br/>during thevtransfer + + + + + Description <font size='2'>(Local database)</font> + + + + + <b>Tip tekst test</b><br/><br/>test line 2 + + + + + Date from + + + + + + To + + + + + FILTER + + + + + Advance filtering + + + + + Type of transation + + + + + Amount from + + + + + HistoryTable + + + No more results + + + + + Payment ID: + + + + + Date + + + + + Balance + + + + + Amount + + + + + LeftPanel + + + Balance + + + + + Test tip 1<br/><br/>line 2 + + + + + Unlocked balance + + + + + Test tip 2<br/><br/>line 2 + + + + + Transfer + + + + + T + + + + + Receive + + + + + R + + + + + NetworkStatusItem + + + Network status + + + + + Connected + + + + + Disconnected + + + + + PrivacyLevelSmall + + + LOW + + + + + MEDIUM + + + + + HIGH + + + + + Receive + + + Address + + + + + Integrated address + + + + + Payment ID + + + + + Generate + + + + + RightPanel + + + Twitter + + + + + SearchInput + + + Search by... + + + + + SEARCH + + + + + TickDelegate + + + LOW + + + + + MEDIUM + + + + + HIGH + + + + + Transfer + + + Amount + + + + + Transaction priority + + + + + Amount... + + + + + Privacy Level + + + + + Cost + + + + + <style type='text/css'>a {text-decoration: none; color: #FF6C3C; font-size: 14px;}</style> Address <font size='2'> ( Type in or select from </font> <a href='#'>Address</a><font size='2'> book )</font> + + + + + Payment ID <font size='2'>( Optional )</font> + + + + + Description <font size='2'>( An optional description that will be saved to the local address book if entered )</font> + + + + + SEND + + + + + WizardConfigure + + + We’re almost there - let’s just configure some Monero preferences + + + + + Kickstart the Monero blockchain? + + + + + It is very important to write it down as this is the only backup you will need for your wallet. You will be asked to confirm the seed in the next screen to ensure it has copied down correctly. + + + + + Enable disk conservation mode? + + + + + Disk conservation mode uses substantially less disk-space, but the same amount of bandwidth as a regular Monero instance. However, storing the full blockchain is beneficial to the security of the Monero network. If you are on a device with limited disk space, then this option is appropriate for you. + + + + + Allow background mining? + + + + + Mining secures the Monero network, and also pays a small reward for the work done. This option will let Monero mine when your computer is on mains power and is idle. It will stop mining when you continue working. + + + + + WizardCreateWallet + + + A new wallet has been created for you + + + + + This is the 25 word mnemonic for your wallet + + + + + WizardDonation + + + Monero development is solely supported by donations + + + + + Enable auto-donations of? + + + + + % of my fee added to each transaction + + + + + For every transaction, a small transaction fee is charged. This option lets you add an additional amount, as a percentage of that fee, to your transaction to support Monero development. For instance, a 50% autodonation take a transaction fee of 0.005 XMR and add a 0.0025 XMR to support Monero development. + + + + + Allow background mining? + + + + + Mining secures the Monero network, and also pays a small reward for the work done. This option will let Monero mine when your computer is on mains power and is idle. It will stop mining when you continue working. + + + + + WizardFinish + + + <b>Language:</b> + + + + + <b>Account name:</b> + + + + + <b>Words:</b> + + + + + <b>Wallet Path: </b> + + + + + <b>Enable auto donation: </b> + + + + + <b>Auto donation amount: </b> + + + + + <b>Allow background mining: </b> + + + + + An overview of your Monero configuration is below: + + + + + You’re all setup! + + + + + WizardMain + + + Now that your wallet has been created, please set a password for the wallet + + + + + Now that your wallet has been restored, please set a password for the wallet + + + + + USE MONERO + + + + + WizardManageWalletUI + + + This is the name of your wallet. You can change it to a different name if you’d like: + + + + + My account name + + + + + Your wallet is stored in + + + + + WizardMemoTextInput + + + It is very important to write it down as this is the only backup you will need for your wallet. You will be asked to confirm the seed in the next screen to ensure it has copied down correctly. + + + + + WizardOptions + + + Welcome to Monero! + + + + + Please select one of the following options: + + + + + This is my first time, I want to<br/>create a new account + + + + + I want to recover my account<br/>from my 24 work seed + + + + + WizardPassword + + + Note that this password cannot be recovered, and if forgotten you will need to restore your wallet from the mnemonic seed you were just given<br/><br/> + Your password will be used to protect your wallet and to confirm actions, so make sure that your password is sufficiently secure. + + + + + WizardRecoveryWallet + + + My account name + + + + + We're ready to recover your account + + + + + Please enter your 25 word private key + + + + + WizardWelcome + + + Welcome + + + + + Please choose a language and regional format. + + + + + main + + + + + Error + + + + + Couldn't open wallet: + + + + + Can't create transaction: + + + + + Confirmation + + + + + Please confirm transaction: + + + + + + + +Address: + + + + + +Payment ID: + + + + + +Amount: + + + + + +Fee: + + + + + Couldn't send the money: + + + + + Information + + + + + Money sent successfully + + + + + Initializing Wallet... + + + + diff --git a/qml.qrc b/qml.qrc index 36861fe4..eca9f561 100644 --- a/qml.qrc +++ b/qml.qrc @@ -113,5 +113,6 @@ wizard/utils.js pages/Receive.qml components/IconButton.qml + lang/flags/italy.png diff --git a/wizard/WizardWelcome.qml b/wizard/WizardWelcome.qml index 8917d212..31bdbcb7 100644 --- a/wizard/WizardWelcome.qml +++ b/wizard/WizardWelcome.qml @@ -118,7 +118,7 @@ Item { radius: 30 color: gridView.currentIndex === index ? "#DBDBDB" : "#FFFFFF" Image { - anchors.centerIn: parent + anchors.fill: parent source: flag } } From 71da777375d41f0b57003d0d28edd83611136d42 Mon Sep 17 00:00:00 2001 From: Ilya Kitaev Date: Thu, 14 Jul 2016 13:09:39 +0300 Subject: [PATCH 47/87] Init wallet asynchronously --- main.qml | 20 ++++---------------- src/libwalletqt/Wallet.cpp | 16 +++++++++++++++- src/libwalletqt/Wallet.h | 9 +++++++++ 3 files changed, 28 insertions(+), 17 deletions(-) diff --git a/main.qml b/main.qml index b96b5dea..168591df 100644 --- a/main.qml +++ b/main.qml @@ -145,23 +145,12 @@ ApplicationWindow { } console.log("Wallet opened successfully: ", wallet.errorString); } - // display splash screen... - - console.log("initializing with daemon address..") - if (!wallet.init(persistentSettings.daemon_address, 0)) { - console.log("Error initialize wallet: ", wallet.errorString); - return - } - console.log("Wallet initialized successfully") - // TODO: update network indicator - // subscribing for wallet updates wallet.updated.connect(onWalletUpdate); wallet.refreshed.connect(onWalletRefresh); - console.log("refreshing wallet async") - // TODO: refresh asynchronously without blocking UI, implement signal(s) - wallet.refreshAsync(); - console.log("wallet balance: ", wallet.balance) + + console.log("initializing with daemon address..") + wallet.initAsync(persistentSettings.daemon_address, 0); } @@ -173,8 +162,7 @@ ApplicationWindow { function onWalletRefresh() { console.log(">>> wallet refreshed") - leftPanel.unlockedBalanceText = walletManager.displayAmount(wallet.unlockedBalance); - leftPanel.balanceText = walletManager.displayAmount(wallet.balance); + onWalletUpdate(); } diff --git a/src/libwalletqt/Wallet.cpp b/src/libwalletqt/Wallet.cpp index 3697984e..4c6995cb 100644 --- a/src/libwalletqt/Wallet.cpp +++ b/src/libwalletqt/Wallet.cpp @@ -28,6 +28,7 @@ public: // TODO Q_UNUSED(txId) Q_UNUSED(amount) + qDebug() << __FUNCTION__; } virtual void moneyReceived(const std::string &txId, uint64_t amount) @@ -35,16 +36,19 @@ public: // TODO Q_UNUSED(txId) Q_UNUSED(amount) + qDebug() << __FUNCTION__; } virtual void updated() { + qDebug() << __FUNCTION__; emit m_wallet->updated(); } // called when wallet refreshed by background thread or explicitly virtual void refreshed() { + qDebug() << __FUNCTION__; emit m_wallet->refreshed(); } @@ -74,6 +78,11 @@ Wallet::Status Wallet::status() const return static_cast(m_walletImpl->status()); } +bool Wallet::connected() const +{ + return m_walletImpl->connected(); +} + QString Wallet::errorString() const { return QString::fromStdString(m_walletImpl->errorString()); @@ -99,6 +108,11 @@ bool Wallet::init(const QString &daemonAddress, quint64 upperTransactionLimit) return m_walletImpl->init(daemonAddress.toStdString(), upperTransactionLimit); } +void Wallet::initAsync(const QString &daemonAddress, quint64 upperTransactionLimit) +{ + m_walletImpl->initAsync(daemonAddress.toStdString(), upperTransactionLimit); +} + bool Wallet::connectToDaemon() { return m_walletImpl->connectToDaemon(); @@ -183,7 +197,7 @@ void Wallet::setPaymentId(const QString &paymentId) Wallet::Wallet(Bitmonero::Wallet *w, QObject *parent) : QObject(parent), m_walletImpl(w), m_history(nullptr) { - + m_walletImpl->setListener(new WalletListenerImpl(this)); } Wallet::~Wallet() diff --git a/src/libwalletqt/Wallet.h b/src/libwalletqt/Wallet.h index 4d6a1ea0..feba75e0 100644 --- a/src/libwalletqt/Wallet.h +++ b/src/libwalletqt/Wallet.h @@ -20,6 +20,7 @@ class Wallet : public QObject Q_PROPERTY(QString seed READ getSeed) Q_PROPERTY(QString seedLanguage READ getSeedLanguage) Q_PROPERTY(Status status READ status) + Q_PROPERTY(bool connected READ connected) Q_PROPERTY(QString errorString READ errorString) Q_PROPERTY(QString address READ address) Q_PROPERTY(quint64 balance READ balance) @@ -27,6 +28,7 @@ class Wallet : public QObject Q_PROPERTY(TransactionHistory * history READ history) Q_PROPERTY(QString paymentId READ paymentId WRITE setPaymentId) + public: enum Status { Status_Ok = Bitmonero::Wallet::Status_Ok, @@ -47,6 +49,9 @@ public: //! returns last operation's status Status status() const; + //! returns of wallet connected + bool connected() const; + //! returns last operation's error message QString errorString() const; @@ -62,6 +67,9 @@ public: //! initializes wallet Q_INVOKABLE bool init(const QString &daemonAddress, quint64 upperTransactionLimit); + //! initializes wallet asynchronously + Q_INVOKABLE void initAsync(const QString &daemonAddress, quint64 upperTransactionLimit); + //! connects to daemon Q_INVOKABLE bool connectToDaemon(); @@ -124,6 +132,7 @@ private: // history lifetime managed by wallet; TransactionHistory * m_history; QString m_paymentId; + }; #endif // WALLET_H From 8b26bc4a4d57e68f2229a4fa7f9d1d5b5acd9004 Mon Sep 17 00:00:00 2001 From: Ilya Kitaev Date: Thu, 14 Jul 2016 13:40:27 +0300 Subject: [PATCH 48/87] Update network status dynamically (closes #17) --- LeftPanel.qml | 2 +- main.qml | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/LeftPanel.qml b/LeftPanel.qml index 0c1ccd12..e23d353c 100644 --- a/LeftPanel.qml +++ b/LeftPanel.qml @@ -352,7 +352,7 @@ Rectangle { anchors.left: parent.left anchors.right: parent.right anchors.bottom: parent.bottom - connected: true + connected: false } } } diff --git a/main.qml b/main.qml index 168591df..b55ace8c 100644 --- a/main.qml +++ b/main.qml @@ -162,6 +162,7 @@ ApplicationWindow { function onWalletRefresh() { console.log(">>> wallet refreshed") + leftPanel.networkStatus.connected = wallet.connected onWalletUpdate(); } @@ -363,6 +364,7 @@ ApplicationWindow { visible: appWindow.rightPanelExpanded } + MiddlePanel { id: middlePanel anchors.bottom: parent.bottom From b0f77c817ca8a04c8d3684f980f7c0b9a72f68f6 Mon Sep 17 00:00:00 2001 From: Ilya Kitaev Date: Thu, 14 Jul 2016 16:05:40 +0300 Subject: [PATCH 49/87] separate LIBS sections for macx, linux and win32 --- monero-core.pro | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/monero-core.pro b/monero-core.pro index f11cff8a..11e610b9 100644 --- a/monero-core.pro +++ b/monero-core.pro @@ -65,7 +65,7 @@ win32 { -lgdi32 } -unix { +linux { LIBS+= \ -Wl,-Bstatic \ -lboost_serialization \ @@ -82,6 +82,21 @@ unix { -ldl } +macx { + LIBS+= \ + -lboost_serialization \ + -lboost_thread \ + -lboost_system \ + -lboost_date_time \ + -lboost_filesystem \ + -lboost_regex \ + -lboost_chrono \ + -lboost_program_options \ + -lssl \ + -lcrypto \ + -ldl +} + # translations files; From 573711473aaa0af7c71cd2984c22ce042775c469 Mon Sep 17 00:00:00 2001 From: Ilya Kitaev Date: Fri, 15 Jul 2016 11:03:18 +0300 Subject: [PATCH 50/87] update/build transations with release build. closes #23 --- monero-core.pro | 33 +- .../monero-core_de.ts | 296 +++++++++--------- .../monero-core_en.ts | 296 +++++++++--------- .../monero-core_it.ts | 278 ++++++++-------- .../monero-core_ru.ts | 278 ++++++++-------- .../monero-core_zh.ts | 278 ++++++++-------- 6 files changed, 730 insertions(+), 729 deletions(-) rename monero-core_de.ts => translations/monero-core_de.ts (70%) rename monero-core_en.ts => translations/monero-core_en.ts (70%) rename monero-core_it.ts => translations/monero-core_it.ts (70%) rename monero-core_ru.ts => translations/monero-core_ru.ts (70%) rename monero-core_zh.ts => translations/monero-core_zh.ts (70%) diff --git a/monero-core.pro b/monero-core.pro index 11e610b9..e03c31aa 100644 --- a/monero-core.pro +++ b/monero-core.pro @@ -100,29 +100,35 @@ macx { # translations files; -TRANSLATIONS = monero-core_en.ts \ # English (could be untranslated) - monero-core_de.ts \ # Deutsch - monero-core_zh.ts \ # Chineese - monero-core_ru.ts \ # Russian - monero-core_it.ts \ # Italy +TRANSLATIONS = $$PWD/translations/monero-core_en.ts \ # English (could be untranslated) + $$PWD/translations/monero-core_de.ts \ # Deutsch + $$PWD/translations/monero-core_zh.ts \ # Chineese + $$PWD/translations/monero-core_ru.ts \ # Russian + $$PWD/translations/monero-core_it.ts \ # Italy # extra make targets for lupdate and lrelease invocation # use "make lupdate" to update *.ts files and "make lrelease" to generate *.qm files -lupdate.commands = lupdate $$_PRO_FILE_ -lupdate.depends = $$SOURCES $$HEADERS -lrelease.commands = lrelease $$_PRO_FILE_ -lrelease.depends = lupdate -translate.commands = $(COPY) *.qm ${DESTDIR} -translate.depends = lrelease +trans_update.commands = lupdate $$_PRO_FILE_ +trans_update.depends = $$_PRO_FILE_ -QMAKE_EXTRA_TARGETS += lupdate lrelease +trans_release.commands = lrelease $$_PRO_FILE_ +trans_release.depends = trans_update $$TRANSLATIONS +translate.commands = $(COPY) $$PWD/*.qm ${DESTDIR} +translate.depends = trans_release +QMAKE_EXTRA_TARGETS += trans_update trans_release translate + +# updating transations only in release mode as this is requires to re-link project +# even if no changes were made. + +#PRE_TARGETDEPS += translate CONFIG(release, debug|release) { DESTDIR=release + PRE_TARGETDEPS += translate } CONFIG(debug, debug|release) { @@ -143,8 +149,7 @@ include(deployment.pri) OTHER_FILES += \ .gitignore \ - monero-core_de.ts \ - monero-core_en.ts + $$TRANSLATIONS DISTFILES += \ notes.txt diff --git a/monero-core_de.ts b/translations/monero-core_de.ts similarity index 70% rename from monero-core_de.ts rename to translations/monero-core_de.ts index 4b16cc31..09e96e70 100644 --- a/monero-core_de.ts +++ b/translations/monero-core_de.ts @@ -4,42 +4,42 @@ AddressBook - + Add new entry - + Address - + <b>Tip tekst test</b> - + Payment ID <font size='2'>(Optional)</font> - + <b>Payment ID</b><br/><br/>A unique user name used in<br/>the address book. It is not a<br/>transfer of information sent<br/>during thevtransfer - + Description <font size='2'>(Local database)</font> - + <b>Tip tekst test</b><br/><br/>test line 2 - + ADD @@ -47,12 +47,12 @@ AddressBookTable - + No more results - + Payment ID: @@ -60,47 +60,47 @@ BasicPanel - + Locked Balance: - + 78.9239845 - + Availible Balance: - + 2324.9239845 - + amount... - + SEND - + destination... - + Privacy level - + payment ID (optional)... @@ -108,17 +108,17 @@ Dashboard - + Quick transfer - + SEND - + <style type='text/css'>a {text-decoration: none; color: #FF6C3C; font-size: 14px;}</style> lookng for security level and address book? go to <a href='#'>Transfer</a> tab @@ -126,22 +126,22 @@ DashboardTable - + No more results - + Date - + Balance - + Amount @@ -149,73 +149,73 @@ History - + Filter trasactions history - + Address - - - - - - + + + + + + <b>Tip tekst test</b> - + Payment ID <font size='2'>(Optional)</font> - + <b>Payment ID</b><br/><br/>A unique user name used in<br/>the address book. It is not a<br/>transfer of information sent<br/>during thevtransfer - + Description <font size='2'>(Local database)</font> - + <b>Tip tekst test</b><br/><br/>test line 2 - + Date from - - + + To - + FILTER - + Advance filtering - + Type of transation - + Amount from @@ -223,27 +223,27 @@ HistoryTable - + No more results - + Payment ID: - + Date - + Balance - + Amount @@ -251,42 +251,42 @@ LeftPanel - - Test tip 1<br/><br/>line 2 - - - - - Test tip 2<br/><br/>line 2 - - - - + Balance - + + Test tip 1<br/><br/>line 2 + + + + Unlocked balance - + + Test tip 2<br/><br/>line 2 + + + + Transfer - + T - + Receive - + R @@ -294,17 +294,17 @@ NetworkStatusItem - + Network status - + Connected - + Disconnected @@ -312,17 +312,17 @@ PrivacyLevelSmall - + LOW - + MEDIUM - + HIGH @@ -330,22 +330,22 @@ Receive - + Address - + Integrated address - + Payment ID - + Generate @@ -353,7 +353,7 @@ RightPanel - + Twitter @@ -361,12 +361,12 @@ SearchInput - + Search by... - + SEARCH @@ -374,17 +374,17 @@ TickDelegate - + LOW - + MEDIUM - + HIGH @@ -392,47 +392,47 @@ Transfer - + Amount - + Transaction priority - + Amount... - + Privacy Level - + Cost - + <style type='text/css'>a {text-decoration: none; color: #FF6C3C; font-size: 14px;}</style> Address <font size='2'> ( Type in or select from </font> <a href='#'>Address</a><font size='2'> book )</font> - + Payment ID <font size='2'>( Optional )</font> - + Description <font size='2'>( An optional description that will be saved to the local address book if entered )</font> - + SEND @@ -440,37 +440,37 @@ WizardConfigure - + We’re almost there - let’s just configure some Monero preferences - + Kickstart the Monero blockchain? - + It is very important to write it down as this is the only backup you will need for your wallet. You will be asked to confirm the seed in the next screen to ensure it has copied down correctly. - + Enable disk conservation mode? - + Disk conservation mode uses substantially less disk-space, but the same amount of bandwidth as a regular Monero instance. However, storing the full blockchain is beneficial to the security of the Monero network. If you are on a device with limited disk space, then this option is appropriate for you. - + Allow background mining? - + Mining secures the Monero network, and also pays a small reward for the work done. This option will let Monero mine when your computer is on mains power and is idle. It will stop mining when you continue working. @@ -478,12 +478,12 @@ WizardCreateWallet - + A new wallet has been created for you - + This is the 25 word mnemonic for your wallet @@ -491,32 +491,32 @@ WizardDonation - + Monero development is solely supported by donations - + Enable auto-donations of? - + % of my fee added to each transaction - + For every transaction, a small transaction fee is charged. This option lets you add an additional amount, as a percentage of that fee, to your transaction to support Monero development. For instance, a 50% autodonation take a transaction fee of 0.005 XMR and add a 0.0025 XMR to support Monero development. - + Allow background mining? - + Mining secures the Monero network, and also pays a small reward for the work done. This option will let Monero mine when your computer is on mains power and is idle. It will stop mining when you continue working. @@ -524,47 +524,47 @@ WizardFinish - + <b>Language:</b> - + <b>Account name:</b> - + <b>Words:</b> - + <b>Wallet Path: </b> - + <b>Enable auto donation: </b> - + <b>Auto donation amount: </b> - + <b>Allow background mining: </b> - + An overview of your Monero configuration is below: - + You’re all setup! @@ -572,17 +572,17 @@ WizardMain - + Now that your wallet has been created, please set a password for the wallet - + Now that your wallet has been restored, please set a password for the wallet - + USE MONERO @@ -590,17 +590,17 @@ WizardManageWalletUI - + This is the name of your wallet. You can change it to a different name if you’d like: - + My account name - + Your wallet is stored in @@ -608,7 +608,7 @@ WizardMemoTextInput - + It is very important to write it down as this is the only backup you will need for your wallet. You will be asked to confirm the seed in the next screen to ensure it has copied down correctly. @@ -616,22 +616,22 @@ WizardOptions - + Welcome to Monero! - + Please select one of the following options: - + This is my first time, I want to<br/>create a new account - + I want to recover my account<br/>from my 24 work seed @@ -639,28 +639,26 @@ WizardPassword - + Note that this password cannot be recovered, and if forgotten you will need to restore your wallet from the mnemonic seed you were just given<br/><br/> Your password will be used to protect your wallet and to confirm actions, so make sure that your password is sufficiently secure. - Note that this password cannot be recovered, and if forgotten you will need to restore your wallet from the mnemonic seed you were just given<br/><br/> - Your password will be used to protect your wallet and to confirm actions, so make sure that your password is sufficiently secure. WizardRecoveryWallet - + My account name - + We're ready to recover your account - + Please enter your 25 word private key @@ -668,12 +666,12 @@ WizardWelcome - + Welcome - + Please choose a language and regional format. @@ -681,75 +679,75 @@ main - - - + + + Error - + Couldn't open wallet: - + Can't create transaction: - + Confirmation - + Please confirm transaction: - + Address: - + Payment ID: - + Amount: - + Fee: - + Couldn't send the money: - + Information - + Money sent successfully - + Initializing Wallet... diff --git a/monero-core_en.ts b/translations/monero-core_en.ts similarity index 70% rename from monero-core_en.ts rename to translations/monero-core_en.ts index bae9d7a9..307233ba 100644 --- a/monero-core_en.ts +++ b/translations/monero-core_en.ts @@ -4,42 +4,42 @@ AddressBook - + Add new entry - + Address - + <b>Tip tekst test</b> - + Payment ID <font size='2'>(Optional)</font> - + <b>Payment ID</b><br/><br/>A unique user name used in<br/>the address book. It is not a<br/>transfer of information sent<br/>during thevtransfer - + Description <font size='2'>(Local database)</font> - + <b>Tip tekst test</b><br/><br/>test line 2 - + ADD @@ -47,12 +47,12 @@ AddressBookTable - + No more results - + Payment ID: @@ -60,47 +60,47 @@ BasicPanel - + Locked Balance: - + 78.9239845 - + Availible Balance: - + 2324.9239845 - + amount... - + SEND - + destination... - + Privacy level - + payment ID (optional)... @@ -108,17 +108,17 @@ Dashboard - + Quick transfer - + SEND - + <style type='text/css'>a {text-decoration: none; color: #FF6C3C; font-size: 14px;}</style> lookng for security level and address book? go to <a href='#'>Transfer</a> tab @@ -126,22 +126,22 @@ DashboardTable - + No more results - + Date - + Balance - + Amount @@ -149,73 +149,73 @@ History - + Filter trasactions history - + Address - - - - - - + + + + + + <b>Tip tekst test</b> - + Payment ID <font size='2'>(Optional)</font> - + <b>Payment ID</b><br/><br/>A unique user name used in<br/>the address book. It is not a<br/>transfer of information sent<br/>during thevtransfer - + Description <font size='2'>(Local database)</font> - + <b>Tip tekst test</b><br/><br/>test line 2 - + Date from - - + + To - + FILTER - + Advance filtering - + Type of transation - + Amount from @@ -223,27 +223,27 @@ HistoryTable - + No more results - + Payment ID: - + Date - + Balance - + Amount @@ -251,42 +251,42 @@ LeftPanel - - Test tip 1<br/><br/>line 2 - - - - - Test tip 2<br/><br/>line 2 - - - - + Balance - + + Test tip 1<br/><br/>line 2 + + + + Unlocked balance - + + Test tip 2<br/><br/>line 2 + + + + Transfer - + T - + Receive - + R @@ -294,17 +294,17 @@ NetworkStatusItem - + Network status - + Connected - + Disconnected @@ -312,17 +312,17 @@ PrivacyLevelSmall - + LOW - + MEDIUM - + HIGH @@ -330,22 +330,22 @@ Receive - + Address - + Integrated address - + Payment ID - + Generate @@ -353,7 +353,7 @@ RightPanel - + Twitter @@ -361,12 +361,12 @@ SearchInput - + Search by... - + SEARCH @@ -374,17 +374,17 @@ TickDelegate - + LOW - + MEDIUM - + HIGH @@ -392,47 +392,47 @@ Transfer - + Amount - + Transaction priority - + Amount... - + Privacy Level - + Cost - + <style type='text/css'>a {text-decoration: none; color: #FF6C3C; font-size: 14px;}</style> Address <font size='2'> ( Type in or select from </font> <a href='#'>Address</a><font size='2'> book )</font> - + Payment ID <font size='2'>( Optional )</font> - + Description <font size='2'>( An optional description that will be saved to the local address book if entered )</font> - + SEND @@ -440,37 +440,37 @@ WizardConfigure - + We’re almost there - let’s just configure some Monero preferences - + Kickstart the Monero blockchain? - + It is very important to write it down as this is the only backup you will need for your wallet. You will be asked to confirm the seed in the next screen to ensure it has copied down correctly. - + Enable disk conservation mode? - + Disk conservation mode uses substantially less disk-space, but the same amount of bandwidth as a regular Monero instance. However, storing the full blockchain is beneficial to the security of the Monero network. If you are on a device with limited disk space, then this option is appropriate for you. - + Allow background mining? - + Mining secures the Monero network, and also pays a small reward for the work done. This option will let Monero mine when your computer is on mains power and is idle. It will stop mining when you continue working. @@ -478,12 +478,12 @@ WizardCreateWallet - + A new wallet has been created for you - + This is the 25 word mnemonic for your wallet @@ -491,32 +491,32 @@ WizardDonation - + Monero development is solely supported by donations - + Enable auto-donations of? - + % of my fee added to each transaction - + For every transaction, a small transaction fee is charged. This option lets you add an additional amount, as a percentage of that fee, to your transaction to support Monero development. For instance, a 50% autodonation take a transaction fee of 0.005 XMR and add a 0.0025 XMR to support Monero development. - + Allow background mining? - + Mining secures the Monero network, and also pays a small reward for the work done. This option will let Monero mine when your computer is on mains power and is idle. It will stop mining when you continue working. @@ -524,47 +524,47 @@ WizardFinish - + <b>Language:</b> - + <b>Account name:</b> - + <b>Words:</b> - + <b>Wallet Path: </b> - + <b>Enable auto donation: </b> - + <b>Auto donation amount: </b> - + <b>Allow background mining: </b> - + An overview of your Monero configuration is below: - + You’re all setup! @@ -572,17 +572,17 @@ WizardMain - + Now that your wallet has been created, please set a password for the wallet - + Now that your wallet has been restored, please set a password for the wallet - + USE MONERO @@ -590,17 +590,17 @@ WizardManageWalletUI - + This is the name of your wallet. You can change it to a different name if you’d like: - + My account name - + Your wallet is stored in @@ -608,7 +608,7 @@ WizardMemoTextInput - + It is very important to write it down as this is the only backup you will need for your wallet. You will be asked to confirm the seed in the next screen to ensure it has copied down correctly. @@ -616,22 +616,22 @@ WizardOptions - + Welcome to Monero! - + Please select one of the following options: - + This is my first time, I want to<br/>create a new account - + I want to recover my account<br/>from my 24 work seed @@ -639,28 +639,26 @@ WizardPassword - + Note that this password cannot be recovered, and if forgotten you will need to restore your wallet from the mnemonic seed you were just given<br/><br/> Your password will be used to protect your wallet and to confirm actions, so make sure that your password is sufficiently secure. - Note that this password cannot be recovered, and if forgotten you will need to restore your wallet from the mnemonic seed you were just given<br/><br/> - Your password will be used to protect your wallet and to confirm actions, so make sure that your password is sufficiently secure. WizardRecoveryWallet - + My account name - + We're ready to recover your account - + Please enter your 25 word private key @@ -668,12 +666,12 @@ WizardWelcome - + Welcome - + Please choose a language and regional format. @@ -681,75 +679,75 @@ main - - - + + + Error - + Couldn't open wallet: - + Can't create transaction: - + Confirmation - + Please confirm transaction: - + Address: - + Payment ID: - + Amount: - + Fee: - + Couldn't send the money: - + Information - + Money sent successfully - + Initializing Wallet... diff --git a/monero-core_it.ts b/translations/monero-core_it.ts similarity index 70% rename from monero-core_it.ts rename to translations/monero-core_it.ts index b6bec1a6..e59d65eb 100644 --- a/monero-core_it.ts +++ b/translations/monero-core_it.ts @@ -4,42 +4,42 @@ AddressBook - + Add new entry - + Address - + <b>Tip tekst test</b> - + Payment ID <font size='2'>(Optional)</font> - + <b>Payment ID</b><br/><br/>A unique user name used in<br/>the address book. It is not a<br/>transfer of information sent<br/>during thevtransfer - + Description <font size='2'>(Local database)</font> - + <b>Tip tekst test</b><br/><br/>test line 2 - + ADD @@ -47,12 +47,12 @@ AddressBookTable - + No more results - + Payment ID: @@ -60,47 +60,47 @@ BasicPanel - + Locked Balance: - + 78.9239845 - + Availible Balance: - + 2324.9239845 - + amount... - + SEND - + destination... - + Privacy level - + payment ID (optional)... @@ -108,17 +108,17 @@ Dashboard - + Quick transfer - + SEND - + <style type='text/css'>a {text-decoration: none; color: #FF6C3C; font-size: 14px;}</style> lookng for security level and address book? go to <a href='#'>Transfer</a> tab @@ -126,22 +126,22 @@ DashboardTable - + No more results - + Date - + Balance - + Amount @@ -149,73 +149,73 @@ History - + Filter trasactions history - + Address - - - - - - + + + + + + <b>Tip tekst test</b> - + Payment ID <font size='2'>(Optional)</font> - + <b>Payment ID</b><br/><br/>A unique user name used in<br/>the address book. It is not a<br/>transfer of information sent<br/>during thevtransfer - + Description <font size='2'>(Local database)</font> - + <b>Tip tekst test</b><br/><br/>test line 2 - + Date from - - + + To - + FILTER - + Advance filtering - + Type of transation - + Amount from @@ -223,27 +223,27 @@ HistoryTable - + No more results - + Payment ID: - + Date - + Balance - + Amount @@ -251,42 +251,42 @@ LeftPanel - + Balance - + Test tip 1<br/><br/>line 2 - + Unlocked balance - + Test tip 2<br/><br/>line 2 - + Transfer - + T - + Receive - + R @@ -294,17 +294,17 @@ NetworkStatusItem - + Network status - + Connected - + Disconnected @@ -312,17 +312,17 @@ PrivacyLevelSmall - + LOW - + MEDIUM - + HIGH @@ -330,22 +330,22 @@ Receive - + Address - + Integrated address - + Payment ID - + Generate @@ -353,7 +353,7 @@ RightPanel - + Twitter @@ -361,12 +361,12 @@ SearchInput - + Search by... - + SEARCH @@ -374,17 +374,17 @@ TickDelegate - + LOW - + MEDIUM - + HIGH @@ -392,47 +392,47 @@ Transfer - + Amount - + Transaction priority - + Amount... - + Privacy Level - + Cost - + <style type='text/css'>a {text-decoration: none; color: #FF6C3C; font-size: 14px;}</style> Address <font size='2'> ( Type in or select from </font> <a href='#'>Address</a><font size='2'> book )</font> - + Payment ID <font size='2'>( Optional )</font> - + Description <font size='2'>( An optional description that will be saved to the local address book if entered )</font> - + SEND @@ -440,37 +440,37 @@ WizardConfigure - + We’re almost there - let’s just configure some Monero preferences - + Kickstart the Monero blockchain? - + It is very important to write it down as this is the only backup you will need for your wallet. You will be asked to confirm the seed in the next screen to ensure it has copied down correctly. - + Enable disk conservation mode? - + Disk conservation mode uses substantially less disk-space, but the same amount of bandwidth as a regular Monero instance. However, storing the full blockchain is beneficial to the security of the Monero network. If you are on a device with limited disk space, then this option is appropriate for you. - + Allow background mining? - + Mining secures the Monero network, and also pays a small reward for the work done. This option will let Monero mine when your computer is on mains power and is idle. It will stop mining when you continue working. @@ -478,12 +478,12 @@ WizardCreateWallet - + A new wallet has been created for you - + This is the 25 word mnemonic for your wallet @@ -491,32 +491,32 @@ WizardDonation - + Monero development is solely supported by donations - + Enable auto-donations of? - + % of my fee added to each transaction - + For every transaction, a small transaction fee is charged. This option lets you add an additional amount, as a percentage of that fee, to your transaction to support Monero development. For instance, a 50% autodonation take a transaction fee of 0.005 XMR and add a 0.0025 XMR to support Monero development. - + Allow background mining? - + Mining secures the Monero network, and also pays a small reward for the work done. This option will let Monero mine when your computer is on mains power and is idle. It will stop mining when you continue working. @@ -524,47 +524,47 @@ WizardFinish - + <b>Language:</b> - + <b>Account name:</b> - + <b>Words:</b> - + <b>Wallet Path: </b> - + <b>Enable auto donation: </b> - + <b>Auto donation amount: </b> - + <b>Allow background mining: </b> - + An overview of your Monero configuration is below: - + You’re all setup! @@ -572,17 +572,17 @@ WizardMain - + Now that your wallet has been created, please set a password for the wallet - + Now that your wallet has been restored, please set a password for the wallet - + USE MONERO @@ -590,17 +590,17 @@ WizardManageWalletUI - + This is the name of your wallet. You can change it to a different name if you’d like: - + My account name - + Your wallet is stored in @@ -608,7 +608,7 @@ WizardMemoTextInput - + It is very important to write it down as this is the only backup you will need for your wallet. You will be asked to confirm the seed in the next screen to ensure it has copied down correctly. @@ -616,22 +616,22 @@ WizardOptions - + Welcome to Monero! - + Please select one of the following options: - + This is my first time, I want to<br/>create a new account - + I want to recover my account<br/>from my 24 work seed @@ -639,7 +639,7 @@ WizardPassword - + Note that this password cannot be recovered, and if forgotten you will need to restore your wallet from the mnemonic seed you were just given<br/><br/> Your password will be used to protect your wallet and to confirm actions, so make sure that your password is sufficiently secure. @@ -648,17 +648,17 @@ WizardRecoveryWallet - + My account name - + We're ready to recover your account - + Please enter your 25 word private key @@ -666,12 +666,12 @@ WizardWelcome - + Welcome - + Please choose a language and regional format. @@ -679,75 +679,75 @@ main - - - + + + Error - + Couldn't open wallet: - + Can't create transaction: - + Confirmation - + Please confirm transaction: - + Address: - + Payment ID: - + Amount: - + Fee: - + Couldn't send the money: - + Information - + Money sent successfully - + Initializing Wallet... diff --git a/monero-core_ru.ts b/translations/monero-core_ru.ts similarity index 70% rename from monero-core_ru.ts rename to translations/monero-core_ru.ts index 8682ecb9..a5c61585 100644 --- a/monero-core_ru.ts +++ b/translations/monero-core_ru.ts @@ -4,42 +4,42 @@ AddressBook - + Add new entry - + Address - + <b>Tip tekst test</b> - + Payment ID <font size='2'>(Optional)</font> - + <b>Payment ID</b><br/><br/>A unique user name used in<br/>the address book. It is not a<br/>transfer of information sent<br/>during thevtransfer - + Description <font size='2'>(Local database)</font> - + <b>Tip tekst test</b><br/><br/>test line 2 - + ADD @@ -47,12 +47,12 @@ AddressBookTable - + No more results - + Payment ID: @@ -60,47 +60,47 @@ BasicPanel - + Locked Balance: - + 78.9239845 - + Availible Balance: - + 2324.9239845 - + amount... - + SEND - + destination... - + Privacy level - + payment ID (optional)... @@ -108,17 +108,17 @@ Dashboard - + Quick transfer - + SEND - + <style type='text/css'>a {text-decoration: none; color: #FF6C3C; font-size: 14px;}</style> lookng for security level and address book? go to <a href='#'>Transfer</a> tab @@ -126,22 +126,22 @@ DashboardTable - + No more results - + Date - + Balance - + Amount @@ -149,73 +149,73 @@ History - + Filter trasactions history - + Address - - - - - - + + + + + + <b>Tip tekst test</b> - + Payment ID <font size='2'>(Optional)</font> - + <b>Payment ID</b><br/><br/>A unique user name used in<br/>the address book. It is not a<br/>transfer of information sent<br/>during thevtransfer - + Description <font size='2'>(Local database)</font> - + <b>Tip tekst test</b><br/><br/>test line 2 - + Date from - - + + To - + FILTER - + Advance filtering - + Type of transation - + Amount from @@ -223,27 +223,27 @@ HistoryTable - + No more results - + Payment ID: - + Date - + Balance - + Amount @@ -251,42 +251,42 @@ LeftPanel - + Balance - + Test tip 1<br/><br/>line 2 - + Unlocked balance - + Test tip 2<br/><br/>line 2 - + Transfer - + T - + Receive - + R @@ -294,17 +294,17 @@ NetworkStatusItem - + Network status - + Connected - + Disconnected @@ -312,17 +312,17 @@ PrivacyLevelSmall - + LOW - + MEDIUM - + HIGH @@ -330,22 +330,22 @@ Receive - + Address - + Integrated address - + Payment ID - + Generate @@ -353,7 +353,7 @@ RightPanel - + Twitter @@ -361,12 +361,12 @@ SearchInput - + Search by... - + SEARCH @@ -374,17 +374,17 @@ TickDelegate - + LOW - + MEDIUM - + HIGH @@ -392,47 +392,47 @@ Transfer - + Amount - + Transaction priority - + Amount... - + Privacy Level - + Cost - + <style type='text/css'>a {text-decoration: none; color: #FF6C3C; font-size: 14px;}</style> Address <font size='2'> ( Type in or select from </font> <a href='#'>Address</a><font size='2'> book )</font> - + Payment ID <font size='2'>( Optional )</font> - + Description <font size='2'>( An optional description that will be saved to the local address book if entered )</font> - + SEND @@ -440,37 +440,37 @@ WizardConfigure - + We’re almost there - let’s just configure some Monero preferences - + Kickstart the Monero blockchain? - + It is very important to write it down as this is the only backup you will need for your wallet. You will be asked to confirm the seed in the next screen to ensure it has copied down correctly. - + Enable disk conservation mode? - + Disk conservation mode uses substantially less disk-space, but the same amount of bandwidth as a regular Monero instance. However, storing the full blockchain is beneficial to the security of the Monero network. If you are on a device with limited disk space, then this option is appropriate for you. - + Allow background mining? - + Mining secures the Monero network, and also pays a small reward for the work done. This option will let Monero mine when your computer is on mains power and is idle. It will stop mining when you continue working. @@ -478,12 +478,12 @@ WizardCreateWallet - + A new wallet has been created for you - + This is the 25 word mnemonic for your wallet @@ -491,32 +491,32 @@ WizardDonation - + Monero development is solely supported by donations - + Enable auto-donations of? - + % of my fee added to each transaction - + For every transaction, a small transaction fee is charged. This option lets you add an additional amount, as a percentage of that fee, to your transaction to support Monero development. For instance, a 50% autodonation take a transaction fee of 0.005 XMR and add a 0.0025 XMR to support Monero development. - + Allow background mining? - + Mining secures the Monero network, and also pays a small reward for the work done. This option will let Monero mine when your computer is on mains power and is idle. It will stop mining when you continue working. @@ -524,47 +524,47 @@ WizardFinish - + <b>Language:</b> - + <b>Account name:</b> - + <b>Words:</b> - + <b>Wallet Path: </b> - + <b>Enable auto donation: </b> - + <b>Auto donation amount: </b> - + <b>Allow background mining: </b> - + An overview of your Monero configuration is below: - + You’re all setup! @@ -572,17 +572,17 @@ WizardMain - + Now that your wallet has been created, please set a password for the wallet - + Now that your wallet has been restored, please set a password for the wallet - + USE MONERO @@ -590,17 +590,17 @@ WizardManageWalletUI - + This is the name of your wallet. You can change it to a different name if you’d like: - + My account name - + Your wallet is stored in @@ -608,7 +608,7 @@ WizardMemoTextInput - + It is very important to write it down as this is the only backup you will need for your wallet. You will be asked to confirm the seed in the next screen to ensure it has copied down correctly. @@ -616,22 +616,22 @@ WizardOptions - + Welcome to Monero! - + Please select one of the following options: - + This is my first time, I want to<br/>create a new account - + I want to recover my account<br/>from my 24 work seed @@ -639,7 +639,7 @@ WizardPassword - + Note that this password cannot be recovered, and if forgotten you will need to restore your wallet from the mnemonic seed you were just given<br/><br/> Your password will be used to protect your wallet and to confirm actions, so make sure that your password is sufficiently secure. @@ -648,17 +648,17 @@ WizardRecoveryWallet - + My account name - + We're ready to recover your account - + Please enter your 25 word private key @@ -666,12 +666,12 @@ WizardWelcome - + Welcome - + Please choose a language and regional format. @@ -679,75 +679,75 @@ main - - - + + + Error - + Couldn't open wallet: - + Can't create transaction: - + Confirmation - + Please confirm transaction: - + Address: - + Payment ID: - + Amount: - + Fee: - + Couldn't send the money: - + Information - + Money sent successfully - + Initializing Wallet... diff --git a/monero-core_zh.ts b/translations/monero-core_zh.ts similarity index 70% rename from monero-core_zh.ts rename to translations/monero-core_zh.ts index 411a8768..5dc6440a 100644 --- a/monero-core_zh.ts +++ b/translations/monero-core_zh.ts @@ -4,42 +4,42 @@ AddressBook - + Add new entry - + Address - + <b>Tip tekst test</b> - + Payment ID <font size='2'>(Optional)</font> - + <b>Payment ID</b><br/><br/>A unique user name used in<br/>the address book. It is not a<br/>transfer of information sent<br/>during thevtransfer - + Description <font size='2'>(Local database)</font> - + <b>Tip tekst test</b><br/><br/>test line 2 - + ADD @@ -47,12 +47,12 @@ AddressBookTable - + No more results - + Payment ID: @@ -60,47 +60,47 @@ BasicPanel - + Locked Balance: - + 78.9239845 - + Availible Balance: - + 2324.9239845 - + amount... - + SEND - + destination... - + Privacy level - + payment ID (optional)... @@ -108,17 +108,17 @@ Dashboard - + Quick transfer - + SEND - + <style type='text/css'>a {text-decoration: none; color: #FF6C3C; font-size: 14px;}</style> lookng for security level and address book? go to <a href='#'>Transfer</a> tab @@ -126,22 +126,22 @@ DashboardTable - + No more results - + Date - + Balance - + Amount @@ -149,73 +149,73 @@ History - + Filter trasactions history - + Address - - - - - - + + + + + + <b>Tip tekst test</b> - + Payment ID <font size='2'>(Optional)</font> - + <b>Payment ID</b><br/><br/>A unique user name used in<br/>the address book. It is not a<br/>transfer of information sent<br/>during thevtransfer - + Description <font size='2'>(Local database)</font> - + <b>Tip tekst test</b><br/><br/>test line 2 - + Date from - - + + To - + FILTER - + Advance filtering - + Type of transation - + Amount from @@ -223,27 +223,27 @@ HistoryTable - + No more results - + Payment ID: - + Date - + Balance - + Amount @@ -251,42 +251,42 @@ LeftPanel - + Balance - + Test tip 1<br/><br/>line 2 - + Unlocked balance - + Test tip 2<br/><br/>line 2 - + Transfer - + T - + Receive - + R @@ -294,17 +294,17 @@ NetworkStatusItem - + Network status - + Connected - + Disconnected @@ -312,17 +312,17 @@ PrivacyLevelSmall - + LOW - + MEDIUM - + HIGH @@ -330,22 +330,22 @@ Receive - + Address - + Integrated address - + Payment ID - + Generate @@ -353,7 +353,7 @@ RightPanel - + Twitter @@ -361,12 +361,12 @@ SearchInput - + Search by... - + SEARCH @@ -374,17 +374,17 @@ TickDelegate - + LOW - + MEDIUM - + HIGH @@ -392,47 +392,47 @@ Transfer - + Amount - + Transaction priority - + Amount... - + Privacy Level - + Cost - + <style type='text/css'>a {text-decoration: none; color: #FF6C3C; font-size: 14px;}</style> Address <font size='2'> ( Type in or select from </font> <a href='#'>Address</a><font size='2'> book )</font> - + Payment ID <font size='2'>( Optional )</font> - + Description <font size='2'>( An optional description that will be saved to the local address book if entered )</font> - + SEND @@ -440,37 +440,37 @@ WizardConfigure - + We’re almost there - let’s just configure some Monero preferences - + Kickstart the Monero blockchain? - + It is very important to write it down as this is the only backup you will need for your wallet. You will be asked to confirm the seed in the next screen to ensure it has copied down correctly. - + Enable disk conservation mode? - + Disk conservation mode uses substantially less disk-space, but the same amount of bandwidth as a regular Monero instance. However, storing the full blockchain is beneficial to the security of the Monero network. If you are on a device with limited disk space, then this option is appropriate for you. - + Allow background mining? - + Mining secures the Monero network, and also pays a small reward for the work done. This option will let Monero mine when your computer is on mains power and is idle. It will stop mining when you continue working. @@ -478,12 +478,12 @@ WizardCreateWallet - + A new wallet has been created for you - + This is the 25 word mnemonic for your wallet @@ -491,32 +491,32 @@ WizardDonation - + Monero development is solely supported by donations - + Enable auto-donations of? - + % of my fee added to each transaction - + For every transaction, a small transaction fee is charged. This option lets you add an additional amount, as a percentage of that fee, to your transaction to support Monero development. For instance, a 50% autodonation take a transaction fee of 0.005 XMR and add a 0.0025 XMR to support Monero development. - + Allow background mining? - + Mining secures the Monero network, and also pays a small reward for the work done. This option will let Monero mine when your computer is on mains power and is idle. It will stop mining when you continue working. @@ -524,47 +524,47 @@ WizardFinish - + <b>Language:</b> - + <b>Account name:</b> - + <b>Words:</b> - + <b>Wallet Path: </b> - + <b>Enable auto donation: </b> - + <b>Auto donation amount: </b> - + <b>Allow background mining: </b> - + An overview of your Monero configuration is below: - + You’re all setup! @@ -572,17 +572,17 @@ WizardMain - + Now that your wallet has been created, please set a password for the wallet - + Now that your wallet has been restored, please set a password for the wallet - + USE MONERO @@ -590,17 +590,17 @@ WizardManageWalletUI - + This is the name of your wallet. You can change it to a different name if you’d like: - + My account name - + Your wallet is stored in @@ -608,7 +608,7 @@ WizardMemoTextInput - + It is very important to write it down as this is the only backup you will need for your wallet. You will be asked to confirm the seed in the next screen to ensure it has copied down correctly. @@ -616,22 +616,22 @@ WizardOptions - + Welcome to Monero! - + Please select one of the following options: - + This is my first time, I want to<br/>create a new account - + I want to recover my account<br/>from my 24 work seed @@ -639,7 +639,7 @@ WizardPassword - + Note that this password cannot be recovered, and if forgotten you will need to restore your wallet from the mnemonic seed you were just given<br/><br/> Your password will be used to protect your wallet and to confirm actions, so make sure that your password is sufficiently secure. @@ -648,17 +648,17 @@ WizardRecoveryWallet - + My account name - + We're ready to recover your account - + Please enter your 25 word private key @@ -666,12 +666,12 @@ WizardWelcome - + Welcome - + Please choose a language and regional format. @@ -679,75 +679,75 @@ main - - - + + + Error - + Couldn't open wallet: - + Can't create transaction: - + Confirmation - + Please confirm transaction: - + Address: - + Payment ID: - + Amount: - + Fee: - + Couldn't send the money: - + Information - + Money sent successfully - + Initializing Wallet... From de7bd2eb971e9a0a796728599478330454bc13a6 Mon Sep 17 00:00:00 2001 From: Ilya Kitaev Date: Fri, 15 Jul 2016 11:17:09 +0300 Subject: [PATCH 51/87] Polish translation file --- monero-core.pro | 3 +- translations/monero-core_pl.ts | 755 +++++++++++++++++++++++++++++++++ 2 files changed, 757 insertions(+), 1 deletion(-) create mode 100644 translations/monero-core_pl.ts diff --git a/monero-core.pro b/monero-core.pro index e03c31aa..845e0501 100644 --- a/monero-core.pro +++ b/monero-core.pro @@ -104,7 +104,8 @@ TRANSLATIONS = $$PWD/translations/monero-core_en.ts \ # English (could be untran $$PWD/translations/monero-core_de.ts \ # Deutsch $$PWD/translations/monero-core_zh.ts \ # Chineese $$PWD/translations/monero-core_ru.ts \ # Russian - $$PWD/translations/monero-core_it.ts \ # Italy + $$PWD/translations/monero-core_it.ts \ # Italian + $$PWD/translations/monero-core_pl.ts \ # Polish diff --git a/translations/monero-core_pl.ts b/translations/monero-core_pl.ts new file mode 100644 index 00000000..237bf62b --- /dev/null +++ b/translations/monero-core_pl.ts @@ -0,0 +1,755 @@ + + + + + AddressBook + + + Add new entry + + + + + Address + + + + + <b>Tip tekst test</b> + + + + + Payment ID <font size='2'>(Optional)</font> + + + + + <b>Payment ID</b><br/><br/>A unique user name used in<br/>the address book. It is not a<br/>transfer of information sent<br/>during thevtransfer + + + + + Description <font size='2'>(Local database)</font> + + + + + <b>Tip tekst test</b><br/><br/>test line 2 + + + + + ADD + + + + + AddressBookTable + + + No more results + + + + + Payment ID: + + + + + BasicPanel + + + Locked Balance: + + + + + 78.9239845 + + + + + Availible Balance: + + + + + 2324.9239845 + + + + + amount... + + + + + SEND + + + + + destination... + + + + + Privacy level + + + + + payment ID (optional)... + + + + + Dashboard + + + Quick transfer + + + + + SEND + + + + + <style type='text/css'>a {text-decoration: none; color: #FF6C3C; font-size: 14px;}</style> lookng for security level and address book? go to <a href='#'>Transfer</a> tab + + + + + DashboardTable + + + No more results + + + + + Date + + + + + Balance + + + + + Amount + + + + + History + + + Filter trasactions history + + + + + Address + + + + + + + + + + <b>Tip tekst test</b> + + + + + Payment ID <font size='2'>(Optional)</font> + + + + + <b>Payment ID</b><br/><br/>A unique user name used in<br/>the address book. It is not a<br/>transfer of information sent<br/>during thevtransfer + + + + + Description <font size='2'>(Local database)</font> + + + + + <b>Tip tekst test</b><br/><br/>test line 2 + + + + + Date from + + + + + + To + + + + + FILTER + + + + + Advance filtering + + + + + Type of transation + + + + + Amount from + + + + + HistoryTable + + + No more results + + + + + Payment ID: + + + + + Date + + + + + Balance + + + + + Amount + + + + + LeftPanel + + + Balance + + + + + Test tip 1<br/><br/>line 2 + + + + + Unlocked balance + + + + + Test tip 2<br/><br/>line 2 + + + + + Transfer + + + + + T + + + + + Receive + + + + + R + + + + + NetworkStatusItem + + + Network status + + + + + Connected + + + + + Disconnected + + + + + PrivacyLevelSmall + + + LOW + + + + + MEDIUM + + + + + HIGH + + + + + Receive + + + Address + + + + + Integrated address + + + + + Payment ID + + + + + Generate + + + + + RightPanel + + + Twitter + + + + + SearchInput + + + Search by... + + + + + SEARCH + + + + + TickDelegate + + + LOW + + + + + MEDIUM + + + + + HIGH + + + + + Transfer + + + Amount + + + + + Transaction priority + + + + + Amount... + + + + + Privacy Level + + + + + Cost + + + + + <style type='text/css'>a {text-decoration: none; color: #FF6C3C; font-size: 14px;}</style> Address <font size='2'> ( Type in or select from </font> <a href='#'>Address</a><font size='2'> book )</font> + + + + + Payment ID <font size='2'>( Optional )</font> + + + + + Description <font size='2'>( An optional description that will be saved to the local address book if entered )</font> + + + + + SEND + + + + + WizardConfigure + + + We’re almost there - let’s just configure some Monero preferences + + + + + Kickstart the Monero blockchain? + + + + + It is very important to write it down as this is the only backup you will need for your wallet. You will be asked to confirm the seed in the next screen to ensure it has copied down correctly. + + + + + Enable disk conservation mode? + + + + + Disk conservation mode uses substantially less disk-space, but the same amount of bandwidth as a regular Monero instance. However, storing the full blockchain is beneficial to the security of the Monero network. If you are on a device with limited disk space, then this option is appropriate for you. + + + + + Allow background mining? + + + + + Mining secures the Monero network, and also pays a small reward for the work done. This option will let Monero mine when your computer is on mains power and is idle. It will stop mining when you continue working. + + + + + WizardCreateWallet + + + A new wallet has been created for you + + + + + This is the 25 word mnemonic for your wallet + + + + + WizardDonation + + + Monero development is solely supported by donations + + + + + Enable auto-donations of? + + + + + % of my fee added to each transaction + + + + + For every transaction, a small transaction fee is charged. This option lets you add an additional amount, as a percentage of that fee, to your transaction to support Monero development. For instance, a 50% autodonation take a transaction fee of 0.005 XMR and add a 0.0025 XMR to support Monero development. + + + + + Allow background mining? + + + + + Mining secures the Monero network, and also pays a small reward for the work done. This option will let Monero mine when your computer is on mains power and is idle. It will stop mining when you continue working. + + + + + WizardFinish + + + <b>Language:</b> + + + + + <b>Account name:</b> + + + + + <b>Words:</b> + + + + + <b>Wallet Path: </b> + + + + + <b>Enable auto donation: </b> + + + + + <b>Auto donation amount: </b> + + + + + <b>Allow background mining: </b> + + + + + An overview of your Monero configuration is below: + + + + + You’re all setup! + + + + + WizardMain + + + Now that your wallet has been created, please set a password for the wallet + + + + + Now that your wallet has been restored, please set a password for the wallet + + + + + USE MONERO + + + + + WizardManageWalletUI + + + This is the name of your wallet. You can change it to a different name if you’d like: + + + + + My account name + + + + + Your wallet is stored in + + + + + WizardMemoTextInput + + + It is very important to write it down as this is the only backup you will need for your wallet. You will be asked to confirm the seed in the next screen to ensure it has copied down correctly. + + + + + WizardOptions + + + Welcome to Monero! + + + + + Please select one of the following options: + + + + + This is my first time, I want to<br/>create a new account + + + + + I want to recover my account<br/>from my 24 work seed + + + + + WizardPassword + + + Note that this password cannot be recovered, and if forgotten you will need to restore your wallet from the mnemonic seed you were just given<br/><br/> + Your password will be used to protect your wallet and to confirm actions, so make sure that your password is sufficiently secure. + + + + + WizardRecoveryWallet + + + My account name + + + + + We're ready to recover your account + + + + + Please enter your 25 word private key + + + + + WizardWelcome + + + Welcome + + + + + Please choose a language and regional format. + + + + + main + + + + + Error + + + + + Couldn't open wallet: + + + + + Can't create transaction: + + + + + Confirmation + + + + + Please confirm transaction: + + + + + + + +Address: + + + + + +Payment ID: + + + + + +Amount: + + + + + +Fee: + + + + + Couldn't send the money: + + + + + Information + + + + + Money sent successfully + + + + + Initializing Wallet... + + + + From a9339838ac295a4d045433c1b20fd5666fb35575 Mon Sep 17 00:00:00 2001 From: Ilya Kitaev Date: Tue, 19 Jul 2016 23:31:09 +0300 Subject: [PATCH 52/87] TranslationManager, Russian translation example --- TranslationManager.cpp | 63 ++++++++++++++++++++++++++++++++++ TranslationManager.h | 29 ++++++++++++++++ get_libwallet_api.sh | 2 +- lang/languages.xml | 33 ++++++++++++------ main.cpp | 11 ++++++ monero-core.pro | 18 +++++----- translations/monero-core_de.ts | 5 +-- translations/monero-core_en.ts | 5 +-- translations/monero-core_it.ts | 5 +-- translations/monero-core_pl.ts | 5 +-- translations/monero-core_ru.ts | 11 +++--- translations/monero-core_zh.ts | 5 +-- wizard/WizardWelcome.qml | 39 ++++++++++++++++++--- 13 files changed, 192 insertions(+), 39 deletions(-) create mode 100644 TranslationManager.cpp create mode 100644 TranslationManager.h diff --git a/TranslationManager.cpp b/TranslationManager.cpp new file mode 100644 index 00000000..69448ce2 --- /dev/null +++ b/TranslationManager.cpp @@ -0,0 +1,63 @@ +#include "TranslationManager.h" + +#include +#include +#include +#include +#include + + +TranslationManager * TranslationManager::m_instance = nullptr; + + +TranslationManager::TranslationManager(QObject *parent) : QObject(parent) +{ + m_translator = new QTranslator(this); +} + +bool TranslationManager::setLanguage(const QString &language) +{ + qDebug() << __FUNCTION__ << " " << language; + // if language is "en", remove translator + if (language.toLower() == "en") { + qApp->removeTranslator(m_translator); + emit languageChanged(); + return true; + } + + // we expecting to have translation files in "i18n" directory + QString dir = qApp->applicationDirPath() + QDir::separator() + "i18n"; + + QString filename = "monero-core_" + language; + + qDebug("%s: loading translation file '%s' from '%s", + __FUNCTION__, qPrintable(filename), qPrintable(dir)); + + + if (m_translator->load(filename, dir)) { + qDebug("%s: translation for language '%s' loaded successfully", + __FUNCTION__, qPrintable(language)); + // TODO: apply locale? + qApp->installTranslator(m_translator); + emit languageChanged(); + return true; + } else { + qCritical("%s: error loading translation for language '%s'", + __FUNCTION__, qPrintable(language)); + return false; + } +} + +TranslationManager *TranslationManager::instance() +{ + if (!m_instance) { + m_instance = new TranslationManager(); + } + return m_instance; +} + +QString TranslationManager::emptyString() +{ + return ""; +} + diff --git a/TranslationManager.h b/TranslationManager.h new file mode 100644 index 00000000..94d0e50e --- /dev/null +++ b/TranslationManager.h @@ -0,0 +1,29 @@ +#ifndef TRANSLATIONMANAGER_H +#define TRANSLATIONMANAGER_H + +#include + +class QTranslator; +class TranslationManager : public QObject +{ + Q_OBJECT + Q_PROPERTY(QString emptyString READ emptyString NOTIFY languageChanged) +public: + Q_INVOKABLE bool setLanguage(const QString &language); + static TranslationManager *instance(); + + QString emptyString(); + +signals: + void languageChanged(); + +private: + explicit TranslationManager(QObject *parent = 0); + +private: + static TranslationManager * m_instance; + QTranslator * m_translator; + +}; + +#endif // TRANSLATIONMANAGER_H diff --git a/get_libwallet_api.sh b/get_libwallet_api.sh index 9e69235e..53ae9268 100755 --- a/get_libwallet_api.sh +++ b/get_libwallet_api.sh @@ -2,7 +2,7 @@ BITMONERO_URL=https://github.com/mbg033/bitmonero.git -BITMONERO_BRANCH=devel +BITMONERO_BRANCH=master # thanks to SO: http://stackoverflow.com/a/20283965/4118915 CPU_CORE_COUNT=$(grep -c ^processor /proc/cpuinfo 2>/dev/null || sysctl -n hw.ncpu) pushd $(pwd) diff --git a/lang/languages.xml b/lang/languages.xml index 8f5120a7..cc47eaa1 100644 --- a/lang/languages.xml +++ b/lang/languages.xml @@ -1,14 +1,25 @@ - - - - - - - - - - - + + + + + + + + + + + + + diff --git a/main.cpp b/main.cpp index 43ef8186..030a21c3 100644 --- a/main.cpp +++ b/main.cpp @@ -37,6 +37,7 @@ #include "WalletManager.h" #include "Wallet.h" #include "PendingTransaction.h" +#include "TranslationManager.h" @@ -53,10 +54,18 @@ int main(int argc, char *argv[]) app.installEventFilter(eventFilter); qmlRegisterType("moneroComponents", 1, 0, "Clipboard"); + qmlRegisterUncreatableType("Bitmonero.Wallet", 1, 0, "Wallet", "Wallet can't be instantiated directly"); + qmlRegisterUncreatableType("Bitmonero.PendingTransaction", 1, 0, "PendingTransaction", "PendingTransaction can't be instantiated directly"); + qmlRegisterUncreatableType("Bitmonero.WalletManager", 1, 0, "WalletManager", + "WalletManager can't be instantiated directly"); + + qmlRegisterUncreatableType("moneroComponents", 1, 0, "TranslationManager", + "TranslationManager can't be instantiated directly"); + qRegisterMetaType(); @@ -69,6 +78,8 @@ int main(int argc, char *argv[]) engine.rootContext()->setContextProperty("walletManager", WalletManager::instance()); + engine.rootContext()->setContextProperty("translationManager", TranslationManager::instance()); + // export to QML monero accounts root directory // wizard is talking about where // to save the wallet file (.keys, .bin), they have to be user-accessible for diff --git a/monero-core.pro b/monero-core.pro index 845e0501..a5b170e5 100644 --- a/monero-core.pro +++ b/monero-core.pro @@ -19,7 +19,8 @@ HEADERS += \ src/libwalletqt/PendingTransaction.h \ src/libwalletqt/TransactionHistory.h \ src/libwalletqt/TransactionInfo.h \ - oshelper.h + oshelper.h \ + TranslationManager.h SOURCES += main.cpp \ @@ -31,7 +32,8 @@ SOURCES += main.cpp \ src/libwalletqt/PendingTransaction.cpp \ src/libwalletqt/TransactionHistory.cpp \ src/libwalletqt/TransactionInfo.cpp \ - oshelper.cpp + oshelper.cpp \ + TranslationManager.cpp lupdate_only { SOURCES = *.qml \ @@ -101,11 +103,11 @@ macx { # translations files; TRANSLATIONS = $$PWD/translations/monero-core_en.ts \ # English (could be untranslated) - $$PWD/translations/monero-core_de.ts \ # Deutsch - $$PWD/translations/monero-core_zh.ts \ # Chineese - $$PWD/translations/monero-core_ru.ts \ # Russian - $$PWD/translations/monero-core_it.ts \ # Italian - $$PWD/translations/monero-core_pl.ts \ # Polish + $$PWD/translations/monero-core_de.ts \ # Deutsch + $$PWD/translations/monero-core_zh.ts \ # Chineese + $$PWD/translations/monero-core_ru.ts \ # Russian + $$PWD/translations/monero-core_it.ts \ # Italian + $$PWD/translations/monero-core_pl.ts \ # Polish @@ -117,7 +119,7 @@ trans_update.depends = $$_PRO_FILE_ trans_release.commands = lrelease $$_PRO_FILE_ trans_release.depends = trans_update $$TRANSLATIONS -translate.commands = $(COPY) $$PWD/*.qm ${DESTDIR} +translate.commands = $(MKDIR) ${DESTDIR}/i18n && $(COPY) $$PWD/translations/*.qm ${DESTDIR}/i18n translate.depends = trans_release QMAKE_EXTRA_TARGETS += trans_update trans_release translate diff --git a/translations/monero-core_de.ts b/translations/monero-core_de.ts index 09e96e70..53c8bef3 100644 --- a/translations/monero-core_de.ts +++ b/translations/monero-core_de.ts @@ -666,12 +666,13 @@ WizardWelcome - + + Welcome - + Please choose a language and regional format. diff --git a/translations/monero-core_en.ts b/translations/monero-core_en.ts index 307233ba..957bfdd0 100644 --- a/translations/monero-core_en.ts +++ b/translations/monero-core_en.ts @@ -666,12 +666,13 @@ WizardWelcome - + + Welcome - + Please choose a language and regional format. diff --git a/translations/monero-core_it.ts b/translations/monero-core_it.ts index e59d65eb..a24a0501 100644 --- a/translations/monero-core_it.ts +++ b/translations/monero-core_it.ts @@ -666,12 +666,13 @@ WizardWelcome - + + Welcome - + Please choose a language and regional format. diff --git a/translations/monero-core_pl.ts b/translations/monero-core_pl.ts index 237bf62b..67e8a0aa 100644 --- a/translations/monero-core_pl.ts +++ b/translations/monero-core_pl.ts @@ -666,12 +666,13 @@ WizardWelcome - + + Welcome - + Please choose a language and regional format. diff --git a/translations/monero-core_ru.ts b/translations/monero-core_ru.ts index a5c61585..ab0d509c 100644 --- a/translations/monero-core_ru.ts +++ b/translations/monero-core_ru.ts @@ -6,7 +6,7 @@ Add new entry - + Новая запись @@ -666,14 +666,15 @@ WizardWelcome - + + Welcome - + Добро пожаловать - + Please choose a language and regional format. - + Пожалуйста выберите язык и региональный формат. diff --git a/translations/monero-core_zh.ts b/translations/monero-core_zh.ts index 5dc6440a..d47f1c24 100644 --- a/translations/monero-core_zh.ts +++ b/translations/monero-core_zh.ts @@ -666,12 +666,13 @@ WizardWelcome - + + Welcome - + Please choose a language and regional format. diff --git a/wizard/WizardWelcome.qml b/wizard/WizardWelcome.qml index 31bdbcb7..72bf27cc 100644 --- a/wizard/WizardWelcome.qml +++ b/wizard/WizardWelcome.qml @@ -39,11 +39,25 @@ Item { function onPageClosed(settingsObject) { var lang = languagesModel.get(gridView.currentIndex); settingsObject['language'] = lang.display_name; - settingsObject['wallet_language'] = lang.wallet_name; + settingsObject['wallet_language'] = lang.wallet_language; settingsObject['locale'] = lang.locale; return true } + +// function retranslateUi() { +// welcomeText.text = qsTr("Welcome") +// } + + +// Connections { +// target: translationManager +// onEmptyStringChanged: { +// console.log("languageChanged") +// retranslateUi() +// } +// } + Column { id: headerColumn anchors.left: parent.left @@ -55,6 +69,7 @@ Item { spacing: 24 Text { + id: welcomeText anchors.left: parent.left anchors.right: parent.right font.family: "Arial" @@ -62,10 +77,14 @@ Item { //renderType: Text.NativeRendering color: "#3F3F3F" wrapMode: Text.Wrap - text: qsTr("Welcome") + // hack to implement dynamic translation + // https://wiki.qt.io/How_to_do_dynamic_translation_in_QML + text: qsTr("Welcome") + + translationManager.emptyString } Text { + id: selectLanguageText anchors.left: parent.left anchors.right: parent.right font.family: "Arial" @@ -74,6 +93,7 @@ Item { color: "#4A4646" wrapMode: Text.Wrap text: qsTr("Please choose a language and regional format.") + + translationManager.emptyString } } @@ -84,11 +104,13 @@ Item { XmlRole { name: "display_name"; query: "@display_name/string()" } XmlRole { name: "locale"; query: "@locale/string()" } - XmlRole { name: "wallet_name"; query: "@wallet_name/string()" } + XmlRole { name: "wallet_language"; query: "@wallet_language/string()" } XmlRole { name: "flag"; query: "@flag/string()" } // TODO: XmlListModel is read only, we should store current language somewhere else // and set current language accordingly XmlRole { name: "isCurrent"; query: "@enabled/string()" } + + } GridView { @@ -111,6 +133,8 @@ Item { height: gridView.cellHeight + + Rectangle { id: flagRect width: 60; height: 60 @@ -139,8 +163,15 @@ Item { anchors.fill: parent onClicked: { gridView.currentIndex = index + var data = languagesModel.get(gridView.currentIndex); + if (data !== null || data !== undefined) { + var locale = data.locale + translationManager.setLanguage(locale.split("_")[0]); + } } } - } + } // delegate + + } } From 39b88daf326cdbbbac5f757769168d41e6d3517b Mon Sep 17 00:00:00 2001 From: Ilya Kitaev Date: Tue, 19 Jul 2016 23:45:12 +0300 Subject: [PATCH 53/87] Apply translation for "normal" mode --- LeftPanel.qml | 2 +- main.qml | 9 +++++++ translations/monero-core_de.ts | 37 ++++++++++++++--------------- translations/monero-core_en.ts | 37 ++++++++++++++--------------- translations/monero-core_it.ts | 37 ++++++++++++++--------------- translations/monero-core_pl.ts | 37 ++++++++++++++--------------- translations/monero-core_ru.ts | 43 +++++++++++++++++----------------- translations/monero-core_zh.ts | 37 ++++++++++++++--------------- wizard/WizardMain.qml | 1 + wizard/WizardWelcome.qml | 16 ------------- 10 files changed, 122 insertions(+), 134 deletions(-) diff --git a/LeftPanel.qml b/LeftPanel.qml index e23d353c..6e067d03 100644 --- a/LeftPanel.qml +++ b/LeftPanel.qml @@ -100,7 +100,7 @@ Rectangle { spacing: 6 Label { - text: qsTr("Balance") + text: qsTr("Balance") + translationManager.emptyString anchors.left: parent.left anchors.leftMargin: 50 tipText: qsTr("Test tip 1

line 2") diff --git a/main.qml b/main.qml index b55ace8c..7316fc56 100644 --- a/main.qml +++ b/main.qml @@ -125,8 +125,16 @@ ApplicationWindow { function initialize() { console.log("initializing..") + + // setup language + var locale = persistentSettings.locale + if (locale !== "") { + translationManager.setLanguage(locale.split("_")[0]); + } + middlePanel.paymentClicked.connect(handlePayment); + if (typeof wizard.settings['wallet'] !== 'undefined') { wallet = wizard.settings['wallet']; } else { @@ -262,6 +270,7 @@ ApplicationWindow { Settings { id: persistentSettings property string language + property string locale property string account_name property string wallet_path property bool auto_donations_enabled : true diff --git a/translations/monero-core_de.ts b/translations/monero-core_de.ts index 53c8bef3..f2c59688 100644 --- a/translations/monero-core_de.ts +++ b/translations/monero-core_de.ts @@ -582,7 +582,7 @@
- + USE MONERO @@ -666,13 +666,12 @@ WizardWelcome - - + Welcome - + Please choose a language and regional format. @@ -680,75 +679,75 @@ main - - - + + + Error - + Couldn't open wallet: - + Can't create transaction: - + Confirmation - + Please confirm transaction: - + Address: - + Payment ID: - + Amount: - + Fee: - + Couldn't send the money: - + Information - + Money sent successfully - + Initializing Wallet... diff --git a/translations/monero-core_en.ts b/translations/monero-core_en.ts index 957bfdd0..f8f7b3ea 100644 --- a/translations/monero-core_en.ts +++ b/translations/monero-core_en.ts @@ -582,7 +582,7 @@
- + USE MONERO @@ -666,13 +666,12 @@ WizardWelcome - - + Welcome - + Please choose a language and regional format. @@ -680,75 +679,75 @@ main - - - + + + Error - + Couldn't open wallet: - + Can't create transaction: - + Confirmation - + Please confirm transaction: - + Address: - + Payment ID: - + Amount: - + Fee: - + Couldn't send the money: - + Information - + Money sent successfully - + Initializing Wallet... diff --git a/translations/monero-core_it.ts b/translations/monero-core_it.ts index a24a0501..3b4e2760 100644 --- a/translations/monero-core_it.ts +++ b/translations/monero-core_it.ts @@ -582,7 +582,7 @@
- + USE MONERO @@ -666,13 +666,12 @@ WizardWelcome - - + Welcome - + Please choose a language and regional format. @@ -680,75 +679,75 @@ main - - - + + + Error - + Couldn't open wallet: - + Can't create transaction: - + Confirmation - + Please confirm transaction: - + Address: - + Payment ID: - + Amount: - + Fee: - + Couldn't send the money: - + Information - + Money sent successfully - + Initializing Wallet... diff --git a/translations/monero-core_pl.ts b/translations/monero-core_pl.ts index 67e8a0aa..6c15a0b0 100644 --- a/translations/monero-core_pl.ts +++ b/translations/monero-core_pl.ts @@ -582,7 +582,7 @@
- + USE MONERO @@ -666,13 +666,12 @@ WizardWelcome - - + Welcome - + Please choose a language and regional format. @@ -680,75 +679,75 @@ main - - - + + + Error - + Couldn't open wallet: - + Can't create transaction: - + Confirmation - + Please confirm transaction: - + Address: - + Payment ID: - + Amount: - + Fee: - + Couldn't send the money: - + Information - + Money sent successfully - + Initializing Wallet... diff --git a/translations/monero-core_ru.ts b/translations/monero-core_ru.ts index ab0d509c..7921567d 100644 --- a/translations/monero-core_ru.ts +++ b/translations/monero-core_ru.ts @@ -138,7 +138,7 @@ Balance - + Баланс @@ -240,7 +240,7 @@ Balance - + Баланс @@ -253,7 +253,7 @@ Balance - + Баланс @@ -582,7 +582,7 @@ - + USE MONERO @@ -666,13 +666,12 @@ WizardWelcome - - + Welcome Добро пожаловать - + Please choose a language and regional format. Пожалуйста выберите язык и региональный формат. @@ -680,75 +679,75 @@ main - - - + + + Error - + Couldn't open wallet: - + Can't create transaction: - + Confirmation - + Please confirm transaction: - + Address: - + Payment ID: - + Amount: - + Fee: - + Couldn't send the money: - + Information - + Money sent successfully - + Initializing Wallet... diff --git a/translations/monero-core_zh.ts b/translations/monero-core_zh.ts index d47f1c24..0a2faf01 100644 --- a/translations/monero-core_zh.ts +++ b/translations/monero-core_zh.ts @@ -582,7 +582,7 @@ - + USE MONERO @@ -666,13 +666,12 @@ WizardWelcome - - + Welcome - + Please choose a language and regional format. @@ -680,75 +679,75 @@ main - - - + + + Error - + Couldn't open wallet: - + Can't create transaction: - + Confirmation - + Please confirm transaction: - + Address: - + Payment ID: - + Amount: - + Fee: - + Couldn't send the money: - + Information - + Money sent successfully - + Initializing Wallet... diff --git a/wizard/WizardMain.qml b/wizard/WizardMain.qml index 3c4103ce..987ac897 100644 --- a/wizard/WizardMain.qml +++ b/wizard/WizardMain.qml @@ -147,6 +147,7 @@ Rectangle { // persist settings appWindow.persistentSettings.language = settings.language + appWindow.persistentSettings.locale = settings.locale appWindow.persistentSettings.account_name = settings.account_name appWindow.persistentSettings.wallet_path = settings.wallet_path appWindow.persistentSettings.allow_background_mining = settings.allow_background_mining diff --git a/wizard/WizardWelcome.qml b/wizard/WizardWelcome.qml index 72bf27cc..bc4c5a1b 100644 --- a/wizard/WizardWelcome.qml +++ b/wizard/WizardWelcome.qml @@ -45,19 +45,6 @@ Item { } -// function retranslateUi() { -// welcomeText.text = qsTr("Welcome") -// } - - -// Connections { -// target: translationManager -// onEmptyStringChanged: { -// console.log("languageChanged") -// retranslateUi() -// } -// } - Column { id: headerColumn anchors.left: parent.left @@ -132,9 +119,6 @@ Item { width: gridView.cellWidth height: gridView.cellHeight - - - Rectangle { id: flagRect width: 60; height: 60 From 32ebf180acce258f8efb9bcaeca945a1fdd311ce Mon Sep 17 00:00:00 2001 From: Ilya Kitaev Date: Wed, 20 Jul 2016 22:28:11 +0300 Subject: [PATCH 54/87] dynamic translation support. closes #24 --- BasicPanel.qml | 8 +- LeftPanel.qml | 34 +++---- RightPanel.qml | 6 +- components/AddressBookTable.qml | 4 +- components/DashboardTable.qml | 8 +- components/HistoryTable.qml | 10 +-- components/NetworkStatusItem.qml | 2 +- components/PrivacyLevelSmall.qml | 6 +- components/SearchInput.qml | 2 +- components/TickDelegate.qml | 6 +- components/TitleBar.qml | 2 +- main.qml | 19 ++-- pages/AddressBook.qml | 17 ++-- pages/Dashboard.qml | 3 +- pages/History.qml | 27 +++--- pages/Receive.qml | 12 +-- pages/Transfer.qml | 20 +++-- translations/monero-core_de.ts | 150 ++++++++++++++++++++++++------- translations/monero-core_en.ts | 150 ++++++++++++++++++++++++------- translations/monero-core_it.ts | 150 ++++++++++++++++++++++++------- translations/monero-core_pl.ts | 150 ++++++++++++++++++++++++------- translations/monero-core_ru.ts | 150 ++++++++++++++++++++++++------- translations/monero-core_zh.ts | 150 ++++++++++++++++++++++++------- wizard/WizardConfigure.qml | 11 ++- wizard/WizardCreateWallet.qml | 4 +- wizard/WizardDonation.qml | 10 ++- wizard/WizardFinish.qml | 5 +- wizard/WizardMain.qml | 6 +- wizard/WizardManageWalletUI.qml | 8 +- wizard/WizardMemoTextInput.qml | 1 + wizard/WizardOptions.qml | 8 +- wizard/WizardPassword.qml | 1 + wizard/WizardRecoveryWallet.qml | 6 +- wizard/utils.js | 5 ++ 34 files changed, 837 insertions(+), 314 deletions(-) diff --git a/BasicPanel.qml b/BasicPanel.qml index 5698316f..3c8de955 100644 --- a/BasicPanel.qml +++ b/BasicPanel.qml @@ -152,7 +152,7 @@ Rectangle { height: 32 fontSize: 15 width: parent.width - sendButton.width - row.spacing - placeholderText: qsTr("amount...") + placeholderText: qsTr("amount...") + translationManager.emptyString } StandardButton { @@ -176,7 +176,7 @@ Rectangle { anchors.margins: 12 fontSize: 15 height: 32 - placeholderText: qsTr("destination...") + placeholderText: qsTr("destination...") + translationManager.emptyString } Text { @@ -188,7 +188,7 @@ Rectangle { font.family: "Arial" font.pixelSize: 12 color: "#535353" - text: qsTr("Privacy level") + text: qsTr("Privacy level") + translationManager.emptyString } PrivacyLevelSmall { @@ -209,6 +209,6 @@ Rectangle { anchors.margins: 12 fontSize: 15 height: 32 - placeholderText: qsTr("payment ID (optional)...") + placeholderText: qsTr("payment ID (optional)...") + translationManager.emptyString } } diff --git a/LeftPanel.qml b/LeftPanel.qml index 6e067d03..604f2d6e 100644 --- a/LeftPanel.qml +++ b/LeftPanel.qml @@ -103,7 +103,7 @@ Rectangle { text: qsTr("Balance") + translationManager.emptyString anchors.left: parent.left anchors.leftMargin: 50 - tipText: qsTr("Test tip 1

line 2") + tipText: qsTr("Test tip 1

line 2") + translationManager.emptyString } Row { @@ -135,10 +135,10 @@ Rectangle { } Label { - text: qsTr("Unlocked balance") + text: qsTr("Unlocked balance") + translationManager.emptyString anchors.left: parent.left anchors.leftMargin: 50 - tipText: qsTr("Test tip 2

line 2") + tipText: qsTr("Test tip 2

line 2") + translationManager.emptyString } Text { @@ -192,8 +192,8 @@ Rectangle { id: dashboardButton anchors.left: parent.left anchors.right: parent.right - text: qsTr("Dashboard") - symbol: qsTr("D") + text: qsTr("Dashboard") + translationManager.emptyString + symbol: qsTr("D") + translationManager.emptyString dotColor: "#FFE00A" checked: true onClicked: { @@ -219,8 +219,8 @@ Rectangle { id: transferButton anchors.left: parent.left anchors.right: parent.right - text: qsTr("Transfer") - symbol: qsTr("T") + text: qsTr("Transfer") + translationManager.emptyString + symbol: qsTr("T") + translationManager.emptyString dotColor: "#FF6C3C" onClicked: { parent.previousButton.checked = false @@ -242,8 +242,8 @@ Rectangle { id: receiveButton anchors.left: parent.left anchors.right: parent.right - text: qsTr("Receive") - symbol: qsTr("R") + text: qsTr("Receive") + translationManager.emptyString + symbol: qsTr("R") + translationManager.emptyString dotColor: "#AAFFBB" onClicked: { parent.previousButton.checked = false @@ -266,8 +266,8 @@ Rectangle { id: historyButton anchors.left: parent.left anchors.right: parent.right - text: qsTr("History") - symbol: qsTr("H") + text: qsTr("History") + translationManager.emptyString + symbol: qsTr("H") + translationManager.emptyString dotColor: "#6B0072" onClicked: { parent.previousButton.checked = false @@ -289,8 +289,8 @@ Rectangle { id: addressBookButton anchors.left: parent.left anchors.right: parent.right - text: qsTr("Address book") - symbol: qsTr("B") + text: qsTr("Address book") + translationManager.emptyString + symbol: qsTr("B") + translationManager.emptyString dotColor: "#FF4F41" onClicked: { parent.previousButton.checked = false @@ -312,8 +312,8 @@ Rectangle { id: miningButton anchors.left: parent.left anchors.right: parent.right - text: qsTr("Mining") - symbol: qsTr("M") + text: qsTr("Mining") + translationManager.emptyString + symbol: qsTr("M") + translationManager.emptyString dotColor: "#FFD781" onClicked: { parent.previousButton.checked = false @@ -335,8 +335,8 @@ Rectangle { id: settingsButton anchors.left: parent.left anchors.right: parent.right - text: qsTr("Settings") - symbol: qsTr("S") + text: qsTr("Settings") + translationManager.emptyString + symbol: qsTr("S") + translationManager.emptyString dotColor: "#36B25C" onClicked: { parent.previousButton.checked = false diff --git a/RightPanel.qml b/RightPanel.qml index c5878146..932b3916 100644 --- a/RightPanel.qml +++ b/RightPanel.qml @@ -56,9 +56,9 @@ Rectangle { Tab { id: twitter; title: qsTr("Twitter"); source: "tabs/Twitter.qml" } - Tab { title: "News" } - Tab { title: "Help" } - Tab { title: "About" } + Tab { title: qsTr("News") + translationManager.emptyString } + Tab { title: qsTr("Help") + translationManager.emptyString } + Tab { title: qsTr("About") + translationManager.emptyString } diff --git a/components/AddressBookTable.qml b/components/AddressBookTable.qml index ab4bf61d..756445f7 100644 --- a/components/AddressBookTable.qml +++ b/components/AddressBookTable.qml @@ -44,7 +44,7 @@ ListView { font.family: "Arial" font.pixelSize: 14 color: "#545454" - text: qsTr("No more results") + text: qsTr("No more results") + translationManager.emptyString } } @@ -103,7 +103,7 @@ ListView { font.pixelSize: 12 font.letterSpacing: -1 color: "#535353" - text: qsTr("Payment ID:") + text: qsTr("Payment ID:") + + translationManager.emptyString } Text { diff --git a/components/DashboardTable.qml b/components/DashboardTable.qml index 07fe6ac2..05b23c6e 100644 --- a/components/DashboardTable.qml +++ b/components/DashboardTable.qml @@ -44,7 +44,7 @@ ListView { font.family: "Arial" font.pixelSize: 14 color: "#545454" - text: qsTr("No more results") + text: qsTr("No more results") + translationManager.emptyString } } @@ -134,7 +134,7 @@ ListView { font.family: "Arial" font.pixelSize: 12 color: "#545454" - text: qsTr("Date") + text: qsTr("Date") + translationManager.emptyString } Row { @@ -169,7 +169,7 @@ ListView { font.family: "Arial" font.pixelSize: 12 color: "#545454" - text: qsTr("Balance") + text: qsTr("Balance") + translationManager.emptyString } Text { @@ -190,7 +190,7 @@ ListView { font.family: "Arial" font.pixelSize: 12 color: "#545454" - text: qsTr("Amount") + text: qsTr("Amount") + translationManager.emptyString } Row { diff --git a/components/HistoryTable.qml b/components/HistoryTable.qml index 3ee8c82c..db92aa69 100644 --- a/components/HistoryTable.qml +++ b/components/HistoryTable.qml @@ -44,7 +44,7 @@ ListView { font.family: "Arial" font.pixelSize: 14 color: "#545454" - text: qsTr("No more results") + text: qsTr("No more results") + translationManager.emptyString } } @@ -126,7 +126,7 @@ ListView { font.pixelSize: 12 font.letterSpacing: -1 color: "#535353" - text: paymentId !== "" ? qsTr("Payment ID:") : "" + text: paymentId !== "" ? qsTr("Payment ID:") + translationManager.emptyString : "" } Text { @@ -164,7 +164,7 @@ ListView { font.family: "Arial" font.pixelSize: 12 color: "#545454" - text: qsTr("Date") + text: qsTr("Date") + translationManager.emptyString } Row { @@ -199,7 +199,7 @@ ListView { font.family: "Arial" font.pixelSize: 12 color: "#545454" - text: qsTr("Balance") + text: qsTr("Balance") + translationManager.emptyString } Text { @@ -220,7 +220,7 @@ ListView { font.family: "Arial" font.pixelSize: 12 color: "#545454" - text: qsTr("Amount") + text: qsTr("Amount") + translationManager.emptyString } Row { diff --git a/components/NetworkStatusItem.qml b/components/NetworkStatusItem.qml index 720fe536..6fb1d2eb 100644 --- a/components/NetworkStatusItem.qml +++ b/components/NetworkStatusItem.qml @@ -63,7 +63,7 @@ Row { font.family: "Arial" font.pixelSize: 18 color: item.connected ? "#FF6C3B" : "#AAAAAA" - text: item.connected ? qsTr("Connected") : qsTr("Disconnected") + text: (item.connected ? qsTr("Connected") : qsTr("Disconnected")) + translationManager.emptyString } } } diff --git a/components/PrivacyLevelSmall.qml b/components/PrivacyLevelSmall.qml index 9321ffbd..cb1cd36e 100644 --- a/components/PrivacyLevelSmall.qml +++ b/components/PrivacyLevelSmall.qml @@ -99,7 +99,7 @@ Item { font.bold: true color: "#000000" x: row.x + (row.positions[0] !== undefined ? row.positions[0].currentX - 3 : 0) - width - text: qsTr("LOW") + text: qsTr("LOW") + translationManager.emptyString } Text { @@ -110,7 +110,7 @@ Item { font.bold: true color: "#000000" x: row.x + (row.positions[4] !== undefined ? row.positions[4].currentX - 3 : 0) - width - text: qsTr("MEDIUM") + text: qsTr("MEDIUM") + translationManager.emptyString } Text { @@ -121,7 +121,7 @@ Item { font.bold: true color: "#000000" x: row.x + (row.positions[13] !== undefined ? row.positions[13].currentX - 3 : 0) - width - text: qsTr("HIGH") + text: qsTr("HIGH") + translationManager.emptyString } MouseArea { diff --git a/components/SearchInput.qml b/components/SearchInput.qml index b104247c..35be896f 100644 --- a/components/SearchInput.qml +++ b/components/SearchInput.qml @@ -66,7 +66,7 @@ Item { anchors.leftMargin: 45 font.pixelSize: 18 verticalAlignment: TextInput.AlignVCenter - placeholderText: qsTr("Search by...") + placeholderText: qsTr("Search by...") + translationManager.emptyString } Item { diff --git a/components/TickDelegate.qml b/components/TickDelegate.qml index 70ef1b50..2163d3e7 100644 --- a/components/TickDelegate.qml +++ b/components/TickDelegate.qml @@ -52,9 +52,9 @@ Item { font.pixelSize: 12 color: "#4A4949" text: { - if(currentIndex === 0) return qsTr("LOW") - if(currentIndex === 3) return qsTr("MEDIUM") - if(currentIndex === 13) return qsTr("HIGH") + if(currentIndex === 0) return qsTr("LOW") + translationManager.emptyString + if(currentIndex === 3) return qsTr("MEDIUM") + translationManager.emptyString + if(currentIndex === 13) return qsTr("HIGH") + translationManager.emptyString return "" } } diff --git a/components/TitleBar.qml b/components/TitleBar.qml index f27caf73..e0baf8f1 100644 --- a/components/TitleBar.qml +++ b/components/TitleBar.qml @@ -35,7 +35,7 @@ Rectangle { color: "#000000" y: -height property int mouseX: 0 - property string title: "Monero - Donations" + property string title: qsTr("Monero - Donations") + translationManager.emptyString property bool containsMouse: false property alias maximizeButtonVisible: maximizeButton.visible property alias basicButtonVisible: goToBasicVersionButton.visible diff --git a/main.qml b/main.qml index 7316fc56..da8545af 100644 --- a/main.qml +++ b/main.qml @@ -145,7 +145,7 @@ ApplicationWindow { wallet = walletManager.openWallet(wallet_path, "", persistentSettings.testnet); if (wallet.status !== Wallet.Status_Ok) { console.log("Error opening wallet: ", wallet.errorString); - informationPopup.title = qsTr("Error"); + informationPopup.title = qsTr("Error") + translationManager.emptyString; informationPopup.text = qsTr("Couldn't open wallet: ") + wallet.errorString; informationPopup.icon = StandardIcon.Critical informationPopup.open() @@ -200,7 +200,7 @@ ApplicationWindow { transaction = wallet.createTransaction(address, paymentId, amountxmr, mixinCount, priority); if (transaction.status !== PendingTransaction.Status_Ok) { console.error("Can't create transaction: ", transaction.errorString); - informationPopup.title = qsTr("Error"); + informationPopup.title = qsTr("Error") + translationManager.emptyString; informationPopup.text = qsTr("Can't create transaction: ") + transaction.errorString informationPopup.icon = StandardIcon.Critical informationPopup.open(); @@ -213,12 +213,13 @@ ApplicationWindow { // here we show confirmation popup; - transactionConfirmationPopup.title = qsTr("Confirmation") + transactionConfirmationPopup.title = qsTr("Confirmation") + translationManager.emptyString transactionConfirmationPopup.text = qsTr("Please confirm transaction:\n\n") + qsTr("\nAddress: ") + address + qsTr("\nPayment ID: ") + paymentId + qsTr("\nAmount: ") + walletManager.displayAmount(transaction.amount) + qsTr("\nFee: ") + walletManager.displayAmount(transaction.fee) + + translationManager.emptyString transactionConfirmationPopup.icon = StandardIcon.Question transactionConfirmationPopup.open() // committing transaction @@ -229,12 +230,12 @@ ApplicationWindow { function handleTransactionConfirmed() { if (!transaction.commit()) { console.log("Error committing transaction: " + transaction.errorString); - informationPopup.title = qsTr("Error"); + informationPopup.title = qsTr("Error") + translationManager.emptyString informationPopup.text = qsTr("Couldn't send the money: ") + transaction.errorString informationPopup.icon = StandardIcon.Critical } else { - informationPopup.title = qsTr("Information") - informationPopup.text = qsTr("Money sent successfully") + informationPopup.title = qsTr("Information") + translationManager.emptyString + informationPopup.text = qsTr("Money sent successfully") + translationManager.emptyString informationPopup.icon = StandardIcon.Information } @@ -332,7 +333,7 @@ ApplicationWindow { PropertyChanges { target: titleBar; maximizeButtonVisible: false } PropertyChanges { target: frameArea; blocked: true } PropertyChanges { target: titleBar; y: 0 } - PropertyChanges { target: titleBar; title: "Program setup wizard" } + PropertyChanges { target: titleBar; title: qsTr("Program setup wizard") + translationManager.emptyString } }, State { name: "normal" PropertyChanges { target: leftPanel; visible: true } @@ -346,7 +347,7 @@ ApplicationWindow { PropertyChanges { target: titleBar; maximizeButtonVisible: true } PropertyChanges { target: frameArea; blocked: false } PropertyChanges { target: titleBar; y: -titleBar.height } - PropertyChanges { target: titleBar; title: "Monero - Donations" } + PropertyChanges { target: titleBar; title: qsTr("Monero - Donations") + translationManager.emptyString } } ] @@ -385,7 +386,7 @@ ApplicationWindow { TipItem { id: tipItem - text: "send to the same destination" + text: qsTr("send to the same destination") + translationManager.emptyString visible: false } diff --git a/pages/AddressBook.qml b/pages/AddressBook.qml index 2fcd939a..7ca79f35 100644 --- a/pages/AddressBook.qml +++ b/pages/AddressBook.qml @@ -44,7 +44,7 @@ Rectangle { font.family: "Arial" font.pixelSize: 18 color: "#4A4949" - text: qsTr("Add new entry") + text: qsTr("Add new entry") + translationManager.emptyString } Label { @@ -55,7 +55,7 @@ Rectangle { anchors.topMargin: 17 text: qsTr("Address") fontSize: 14 - tipText: qsTr("Tip tekst test") + tipText: qsTr("Tip tekst test") + translationManager.emptyString } LineEdit { @@ -74,9 +74,10 @@ Rectangle { anchors.top: addressLine.bottom anchors.leftMargin: 17 anchors.topMargin: 17 - text: qsTr("Payment ID (Optional)") + text: qsTr("Payment ID (Optional)") + translationManager.emptyString fontSize: 14 tipText: qsTr("Payment ID

A unique user name used in
the address book. It is not a
transfer of information sent
during thevtransfer") + + translationManager.emptyString } LineEdit { @@ -95,9 +96,9 @@ Rectangle { anchors.top: paymentIdLine.bottom anchors.leftMargin: 17 anchors.topMargin: 17 - text: qsTr("Description (Local database)") + text: qsTr("Description (Local database)") + translationManager.emptyString fontSize: 14 - tipText: qsTr("Tip tekst test

test line 2") + tipText: qsTr("Tip tekst test

test line 2") + translationManager.emptyString } LineEdit { @@ -169,9 +170,9 @@ Rectangle { ListModel { id: columnsModel - ListElement { columnName: "Address"; columnWidth: 148 } - ListElement { columnName: "Payment ID"; columnWidth: 148 } - ListElement { columnName: "Description"; columnWidth: 148 } + ListElement { columnName: qsTr("Address") + translationManager.emptyString; columnWidth: 148 } + ListElement { columnName: qsTr("Payment ID") + translationManager.emptyString; columnWidth: 148 } + ListElement { columnName: qsTr("Description") + translationManager.emptyString; columnWidth: 148 } } TableHeader { diff --git a/pages/Dashboard.qml b/pages/Dashboard.qml index da8642ec..3f1621ed 100644 --- a/pages/Dashboard.qml +++ b/pages/Dashboard.qml @@ -54,7 +54,7 @@ Rectangle { font.family: "Arial" font.pixelSize: 18 color: "#4A4949" - text: qsTr("Quick transfer") + text: qsTr("Quick transfer") + translationManager.emptyString } LineEdit { @@ -101,6 +101,7 @@ Rectangle { textFormat: Text.RichText text: qsTr("\ lookng for security level and address book? go to Transfer tab") + + translationManager.emptyString font.underline: false onLinkActivated: appWindow.showPageRequest("Transfer") } diff --git a/pages/History.qml b/pages/History.qml index 6e67d04e..9ec2dd2b 100644 --- a/pages/History.qml +++ b/pages/History.qml @@ -44,7 +44,7 @@ Rectangle { font.family: "Arial" font.pixelSize: 18 color: "#4A4949" - text: qsTr("Filter trasactions history") + text: qsTr("Filter trasactions history") + translationManager.emptyString } Label { @@ -55,7 +55,7 @@ Rectangle { anchors.topMargin: 17 text: qsTr("Address") fontSize: 14 - tipText: qsTr("Tip tekst test") + tipText: qsTr("Tip tekst test") + translationManager.emptyString } LineEdit { @@ -74,9 +74,10 @@ Rectangle { anchors.top: addressLine.bottom anchors.leftMargin: 17 anchors.topMargin: 17 - text: qsTr("Payment ID (Optional)") + text: qsTr("Payment ID (Optional)") + translationManager.emptyString fontSize: 14 tipText: qsTr("Payment ID

A unique user name used in
the address book. It is not a
transfer of information sent
during thevtransfer") + + translationManager.emptyString } LineEdit { @@ -95,9 +96,9 @@ Rectangle { anchors.top: paymentIdLine.bottom anchors.leftMargin: 17 anchors.topMargin: 17 - text: qsTr("Description (Local database)") + text: qsTr("Description (Local database)") + translationManager.emptyString fontSize: 14 - tipText: qsTr("Tip tekst test

test line 2") + tipText: qsTr("Tip tekst test

test line 2") + translationManager.emptyString } LineEdit { @@ -117,9 +118,9 @@ Rectangle { anchors.leftMargin: 17 anchors.topMargin: 17 width: 156 - text: qsTr("Date from") + text: qsTr("Date from") + translationManager.emptyString fontSize: 14 - tipText: qsTr("Tip tekst test") + tipText: qsTr("Tip tekst test") + translationManager.emptyString } DatePicker { @@ -139,7 +140,7 @@ Rectangle { anchors.topMargin: 17 text: qsTr("To") fontSize: 14 - tipText: qsTr("Tip tekst test") + tipText: qsTr("Tip tekst test") + translationManager.emptyString } DatePicker { @@ -185,9 +186,9 @@ Rectangle { anchors.leftMargin: 17 anchors.topMargin: 17 width: 156 - text: qsTr("Type of transation") + text: qsTr("Type of transation") + translationManager.emptyString fontSize: 14 - tipText: qsTr("Tip tekst test") + tipText: qsTr("Tip tekst test") + translationManager.emptyString } ListModel { @@ -219,9 +220,9 @@ Rectangle { anchors.leftMargin: 17 anchors.topMargin: 17 width: 156 - text: qsTr("Amount from") + text: qsTr("Amount from") + translationManager.emptyString fontSize: 14 - tipText: qsTr("Tip tekst test") + tipText: qsTr("Tip tekst test") + translationManager.emptyString } LineEdit { @@ -242,7 +243,7 @@ Rectangle { width: 156 text: qsTr("To") fontSize: 14 - tipText: qsTr("Tip tekst test") + tipText: qsTr("Tip tekst test") + translationManager.emptyString } LineEdit { diff --git a/pages/Receive.qml b/pages/Receive.qml index 623c629d..1218ed7c 100644 --- a/pages/Receive.qml +++ b/pages/Receive.qml @@ -76,14 +76,14 @@ Rectangle { Label { id: addressLabel fontSize: 14 - text: qsTr("Address") + text: qsTr("Address") + translationManager.emptyString width: mainLayout.labelWidth } LineEdit { id: addressLine fontSize: mainLayout.lineEditFontSize - placeholderText: "ReadOnly wallet address displayed here"; + placeholderText: qsTr("ReadOnly wallet address displayed here") + translationManager.emptyString; readOnly: true width: mainLayout.editWidth Layout.fillWidth: true @@ -103,7 +103,7 @@ Rectangle { Label { id: integratedAddressLabel fontSize: 14 - text: qsTr("Integrated address") + text: qsTr("Integrated address") + translationManager.emptyString width: mainLayout.labelWidth } @@ -112,7 +112,7 @@ Rectangle { id: integratedAddressLine fontSize: mainLayout.lineEditFontSize - placeholderText: "ReadOnly wallet integrated address displayed here"; + placeholderText: qsTr("ReadOnly wallet integrated address displayed here") + translationManager.emptyString readOnly: true width: mainLayout.editWidth Layout.fillWidth: true @@ -133,7 +133,7 @@ Rectangle { Label { id: paymentIdLabel fontSize: 14 - text: qsTr("Payment ID") + text: qsTr("Payment ID") + translationManager.emptyString width: mainLayout.labelWidth } @@ -141,7 +141,7 @@ Rectangle { LineEdit { id: paymentIdLine fontSize: mainLayout.lineEditFontSize - placeholderText: "PaymentID here"; + placeholderText: qsTr("PaymentID here") + translationManager.emptyString; readOnly: false width: mainLayout.editWidth diff --git a/pages/Transfer.qml b/pages/Transfer.qml index aa79c18f..342e73cb 100644 --- a/pages/Transfer.qml +++ b/pages/Transfer.qml @@ -54,7 +54,7 @@ Rectangle { anchors.leftMargin: 17 anchors.rightMargin: 17 anchors.topMargin: 17 - text: qsTr("Amount") + text: qsTr("Amount") + translationManager.emptyString fontSize: 14 } @@ -64,7 +64,7 @@ Rectangle { anchors.topMargin: 17 fontSize: 14 x: (parent.width - 17) / 2 + 17 - text: qsTr("Transaction priority") + text: qsTr("Transaction priority") + translationManager.emptyString } Row { @@ -86,16 +86,16 @@ Rectangle { // Amount input LineEdit { id: amountLine - placeholderText: qsTr("Amount...") + placeholderText: qsTr("Amount...") + translationManager.emptyString width: parent.width - 37 - 17 } } ListModel { id: priorityModel - ListElement { column1: "LOW"; column2: ""; priority: PendingTransaction.Priority_Low } - ListElement { column1: "MEDIUM"; column2: ""; priority: PendingTransaction.Priority_Medium } - ListElement { column1: "HIGH"; column2: ""; priority: PendingTransaction.Priority_High } + ListElement { column1: qsTr("LOW") + translationManager.emptyString; column2: ""; priority: PendingTransaction.Priority_Low } + ListElement { column1: qsTr("MEDIUM") + translationManager.emptyString; column2: ""; priority: PendingTransaction.Priority_Medium } + ListElement { column1: qsTr("HIGH") + translationManager.emptyString; column2: ""; priority: PendingTransaction.Priority_High } } StandardDropdown { @@ -124,7 +124,7 @@ Rectangle { anchors.rightMargin: 17 anchors.topMargin: 30 fontSize: 14 - text: qsTr("Privacy Level") + text: qsTr("Privacy Level") + translationManager.emptyString } PrivacyLevel { @@ -166,6 +166,7 @@ Rectangle { textFormat: Text.RichText text: qsTr("\ Address ( Type in or select from Address book )") + + translationManager.emptyString onLinkActivated: appWindow.showPageRequest("AddressBook") } @@ -190,7 +191,7 @@ Rectangle { anchors.rightMargin: 17 anchors.topMargin: 17 fontSize: 14 - text: qsTr("Payment ID ( Optional )") + text: qsTr("Payment ID ( Optional )") + translationManager.emptyString } // payment id input @@ -215,6 +216,7 @@ Rectangle { anchors.topMargin: 17 fontSize: 14 text: qsTr("Description ( An optional description that will be saved to the local address book if entered )") + + translationManager.emptyString } LineEdit { @@ -234,7 +236,7 @@ Rectangle { anchors.leftMargin: 17 anchors.topMargin: 17 width: 60 - text: qsTr("SEND") + text: qsTr("SEND") + translationManager.emptyString shadowReleasedColor: "#FF4304" shadowPressedColor: "#B32D00" releasedColor: "#FF6C3C" diff --git a/translations/monero-core_de.ts b/translations/monero-core_de.ts index f2c59688..ea08d710 100644 --- a/translations/monero-core_de.ts +++ b/translations/monero-core_de.ts @@ -10,6 +10,7 @@
+ Address @@ -29,20 +30,30 @@
- + Description <font size='2'>(Local database)</font> - + <b>Tip tekst test</b><br/><br/>test line 2 - + ADD + + + Payment ID + + + + + Description + +
AddressBookTable @@ -160,11 +171,11 @@ - - - - - + + + + + <b>Tip tekst test</b> @@ -179,43 +190,43 @@ - + Description <font size='2'>(Local database)</font> - + <b>Tip tekst test</b><br/><br/>test line 2 - + Date from - - + + To - + FILTER - + Advance filtering - + Type of transation - + Amount from @@ -334,16 +345,31 @@ Address + + + ReadOnly wallet address displayed here + + Integrated address + + + ReadOnly wallet integrated address displayed here + + Payment ID + + + PaymentID here + + Generate @@ -357,6 +383,21 @@ Twitter + + + News + + + + + Help + + + + + About + + SearchInput @@ -389,6 +430,14 @@ + + TitleBar + + + Monero - Donations + + + Transfer @@ -406,6 +455,21 @@ Amount... + + + LOW + + + + + MEDIUM + + + + + HIGH + + Privacy Level @@ -422,17 +486,17 @@ - + Payment ID <font size='2'>( Optional )</font> - + Description <font size='2'>( An optional description that will be saved to the local address book if entered )</font> - + SEND @@ -455,22 +519,22 @@ - + Enable disk conservation mode? - + Disk conservation mode uses substantially less disk-space, but the same amount of bandwidth as a regular Monero instance. However, storing the full blockchain is beneficial to the security of the Monero network. If you are on a device with limited disk space, then this option is appropriate for you. - + Allow background mining? - + Mining secures the Monero network, and also pays a small reward for the work done. This option will let Monero mine when your computer is on mains power and is idle. It will stop mining when you continue working. @@ -511,12 +575,12 @@ - + Allow background mining? - + Mining secures the Monero network, and also pays a small reward for the work done. This option will let Monero mine when your computer is on mains power and is idle. It will stop mining when you continue working. @@ -559,12 +623,12 @@ - + An overview of your Monero configuration is below: - + You’re all setup! @@ -604,6 +668,11 @@ Your wallet is stored in + + + Please choose a directory + + WizardMemoTextInput @@ -681,7 +750,7 @@ - + Error @@ -732,24 +801,39 @@ Fee: - + Couldn't send the money: - + Information - + Money sent successfully - + Initializing Wallet... + + + Program setup wizard + + + + + Monero - Donations + + + + + send to the same destination + + diff --git a/translations/monero-core_en.ts b/translations/monero-core_en.ts index f8f7b3ea..dca4c577 100644 --- a/translations/monero-core_en.ts +++ b/translations/monero-core_en.ts @@ -10,6 +10,7 @@ + Address @@ -29,20 +30,30 @@ - + Description <font size='2'>(Local database)</font> - + <b>Tip tekst test</b><br/><br/>test line 2 - + ADD + + + Payment ID + + + + + Description + +
AddressBookTable @@ -160,11 +171,11 @@ - - - - - + + + + + <b>Tip tekst test</b> @@ -179,43 +190,43 @@ - + Description <font size='2'>(Local database)</font> - + <b>Tip tekst test</b><br/><br/>test line 2 - + Date from - - + + To - + FILTER - + Advance filtering - + Type of transation - + Amount from @@ -334,16 +345,31 @@ Address + + + ReadOnly wallet address displayed here + + Integrated address + + + ReadOnly wallet integrated address displayed here + + Payment ID + + + PaymentID here + + Generate @@ -357,6 +383,21 @@ Twitter + + + News + + + + + Help + + + + + About + + SearchInput @@ -389,6 +430,14 @@ + + TitleBar + + + Monero - Donations + + + Transfer @@ -406,6 +455,21 @@ Amount... + + + LOW + + + + + MEDIUM + + + + + HIGH + + Privacy Level @@ -422,17 +486,17 @@ - + Payment ID <font size='2'>( Optional )</font> - + Description <font size='2'>( An optional description that will be saved to the local address book if entered )</font> - + SEND @@ -455,22 +519,22 @@ - + Enable disk conservation mode? - + Disk conservation mode uses substantially less disk-space, but the same amount of bandwidth as a regular Monero instance. However, storing the full blockchain is beneficial to the security of the Monero network. If you are on a device with limited disk space, then this option is appropriate for you. - + Allow background mining? - + Mining secures the Monero network, and also pays a small reward for the work done. This option will let Monero mine when your computer is on mains power and is idle. It will stop mining when you continue working. @@ -511,12 +575,12 @@ - + Allow background mining? - + Mining secures the Monero network, and also pays a small reward for the work done. This option will let Monero mine when your computer is on mains power and is idle. It will stop mining when you continue working. @@ -559,12 +623,12 @@ - + An overview of your Monero configuration is below: - + You’re all setup! @@ -604,6 +668,11 @@ Your wallet is stored in + + + Please choose a directory + + WizardMemoTextInput @@ -681,7 +750,7 @@ - + Error @@ -732,24 +801,39 @@ Fee: - + Couldn't send the money: - + Information - + Money sent successfully - + Initializing Wallet... + + + Program setup wizard + + + + + Monero - Donations + + + + + send to the same destination + + diff --git a/translations/monero-core_it.ts b/translations/monero-core_it.ts index 3b4e2760..2c9c4b78 100644 --- a/translations/monero-core_it.ts +++ b/translations/monero-core_it.ts @@ -10,6 +10,7 @@ + Address @@ -29,20 +30,30 @@ - + Description <font size='2'>(Local database)</font> - + <b>Tip tekst test</b><br/><br/>test line 2 - + ADD + + + Payment ID + + + + + Description + +
AddressBookTable @@ -160,11 +171,11 @@ - - - - - + + + + + <b>Tip tekst test</b> @@ -179,43 +190,43 @@ - + Description <font size='2'>(Local database)</font> - + <b>Tip tekst test</b><br/><br/>test line 2 - + Date from - - + + To - + FILTER - + Advance filtering - + Type of transation - + Amount from @@ -334,16 +345,31 @@ Address + + + ReadOnly wallet address displayed here + + Integrated address + + + ReadOnly wallet integrated address displayed here + + Payment ID + + + PaymentID here + + Generate @@ -357,6 +383,21 @@ Twitter + + + News + + + + + Help + + + + + About + + SearchInput @@ -389,6 +430,14 @@ + + TitleBar + + + Monero - Donations + + + Transfer @@ -406,6 +455,21 @@ Amount... + + + LOW + + + + + MEDIUM + + + + + HIGH + + Privacy Level @@ -422,17 +486,17 @@ - + Payment ID <font size='2'>( Optional )</font> - + Description <font size='2'>( An optional description that will be saved to the local address book if entered )</font> - + SEND @@ -455,22 +519,22 @@ - + Enable disk conservation mode? - + Disk conservation mode uses substantially less disk-space, but the same amount of bandwidth as a regular Monero instance. However, storing the full blockchain is beneficial to the security of the Monero network. If you are on a device with limited disk space, then this option is appropriate for you. - + Allow background mining? - + Mining secures the Monero network, and also pays a small reward for the work done. This option will let Monero mine when your computer is on mains power and is idle. It will stop mining when you continue working. @@ -511,12 +575,12 @@ - + Allow background mining? - + Mining secures the Monero network, and also pays a small reward for the work done. This option will let Monero mine when your computer is on mains power and is idle. It will stop mining when you continue working. @@ -559,12 +623,12 @@ - + An overview of your Monero configuration is below: - + You’re all setup! @@ -604,6 +668,11 @@ Your wallet is stored in + + + Please choose a directory + + WizardMemoTextInput @@ -681,7 +750,7 @@ - + Error @@ -732,24 +801,39 @@ Fee: - + Couldn't send the money: - + Information - + Money sent successfully - + Initializing Wallet... + + + Program setup wizard + + + + + Monero - Donations + + + + + send to the same destination + + diff --git a/translations/monero-core_pl.ts b/translations/monero-core_pl.ts index 6c15a0b0..a3496ddb 100644 --- a/translations/monero-core_pl.ts +++ b/translations/monero-core_pl.ts @@ -10,6 +10,7 @@ + Address @@ -29,20 +30,30 @@ - + Description <font size='2'>(Local database)</font> - + <b>Tip tekst test</b><br/><br/>test line 2 - + ADD + + + Payment ID + + + + + Description + +
AddressBookTable @@ -160,11 +171,11 @@ - - - - - + + + + + <b>Tip tekst test</b> @@ -179,43 +190,43 @@ - + Description <font size='2'>(Local database)</font> - + <b>Tip tekst test</b><br/><br/>test line 2 - + Date from - - + + To - + FILTER - + Advance filtering - + Type of transation - + Amount from @@ -334,16 +345,31 @@ Address + + + ReadOnly wallet address displayed here + + Integrated address + + + ReadOnly wallet integrated address displayed here + + Payment ID + + + PaymentID here + + Generate @@ -357,6 +383,21 @@ Twitter + + + News + + + + + Help + + + + + About + + SearchInput @@ -389,6 +430,14 @@ + + TitleBar + + + Monero - Donations + + + Transfer @@ -406,6 +455,21 @@ Amount... + + + LOW + + + + + MEDIUM + + + + + HIGH + + Privacy Level @@ -422,17 +486,17 @@ - + Payment ID <font size='2'>( Optional )</font> - + Description <font size='2'>( An optional description that will be saved to the local address book if entered )</font> - + SEND @@ -455,22 +519,22 @@ - + Enable disk conservation mode? - + Disk conservation mode uses substantially less disk-space, but the same amount of bandwidth as a regular Monero instance. However, storing the full blockchain is beneficial to the security of the Monero network. If you are on a device with limited disk space, then this option is appropriate for you. - + Allow background mining? - + Mining secures the Monero network, and also pays a small reward for the work done. This option will let Monero mine when your computer is on mains power and is idle. It will stop mining when you continue working. @@ -511,12 +575,12 @@ - + Allow background mining? - + Mining secures the Monero network, and also pays a small reward for the work done. This option will let Monero mine when your computer is on mains power and is idle. It will stop mining when you continue working. @@ -559,12 +623,12 @@ - + An overview of your Monero configuration is below: - + You’re all setup! @@ -604,6 +668,11 @@ Your wallet is stored in + + + Please choose a directory + + WizardMemoTextInput @@ -681,7 +750,7 @@ - + Error @@ -732,24 +801,39 @@ Fee: - + Couldn't send the money: - + Information - + Money sent successfully - + Initializing Wallet... + + + Program setup wizard + + + + + Monero - Donations + + + + + send to the same destination + + diff --git a/translations/monero-core_ru.ts b/translations/monero-core_ru.ts index 7921567d..29bf9b7f 100644 --- a/translations/monero-core_ru.ts +++ b/translations/monero-core_ru.ts @@ -10,6 +10,7 @@ + Address @@ -29,20 +30,30 @@ - + Description <font size='2'>(Local database)</font> - + <b>Tip tekst test</b><br/><br/>test line 2 - + ADD + + + Payment ID + + + + + Description + +
AddressBookTable @@ -160,11 +171,11 @@ - - - - - + + + + + <b>Tip tekst test</b> @@ -179,43 +190,43 @@ - + Description <font size='2'>(Local database)</font> - + <b>Tip tekst test</b><br/><br/>test line 2 - + Date from - - + + To - + FILTER - + Advance filtering - + Type of transation - + Amount from @@ -334,16 +345,31 @@ Address + + + ReadOnly wallet address displayed here + + Integrated address + + + ReadOnly wallet integrated address displayed here + + Payment ID + + + PaymentID here + + Generate @@ -357,6 +383,21 @@ Twitter + + + News + + + + + Help + + + + + About + + SearchInput @@ -389,6 +430,14 @@ + + TitleBar + + + Monero - Donations + + + Transfer @@ -406,6 +455,21 @@ Amount... + + + LOW + + + + + MEDIUM + + + + + HIGH + + Privacy Level @@ -422,17 +486,17 @@ - + Payment ID <font size='2'>( Optional )</font> - + Description <font size='2'>( An optional description that will be saved to the local address book if entered )</font> - + SEND @@ -455,22 +519,22 @@ - + Enable disk conservation mode? - + Disk conservation mode uses substantially less disk-space, but the same amount of bandwidth as a regular Monero instance. However, storing the full blockchain is beneficial to the security of the Monero network. If you are on a device with limited disk space, then this option is appropriate for you. - + Allow background mining? - + Mining secures the Monero network, and also pays a small reward for the work done. This option will let Monero mine when your computer is on mains power and is idle. It will stop mining when you continue working. @@ -511,12 +575,12 @@ - + Allow background mining? - + Mining secures the Monero network, and also pays a small reward for the work done. This option will let Monero mine when your computer is on mains power and is idle. It will stop mining when you continue working. @@ -559,12 +623,12 @@ - + An overview of your Monero configuration is below: - + You’re all setup! @@ -604,6 +668,11 @@ Your wallet is stored in + + + Please choose a directory + + WizardMemoTextInput @@ -681,7 +750,7 @@ - + Error @@ -732,24 +801,39 @@ Fee: - + Couldn't send the money: - + Information - + Money sent successfully - + Initializing Wallet... + + + Program setup wizard + + + + + Monero - Donations + + + + + send to the same destination + + diff --git a/translations/monero-core_zh.ts b/translations/monero-core_zh.ts index 0a2faf01..e5e60dab 100644 --- a/translations/monero-core_zh.ts +++ b/translations/monero-core_zh.ts @@ -10,6 +10,7 @@ + Address @@ -29,20 +30,30 @@ - + Description <font size='2'>(Local database)</font> - + <b>Tip tekst test</b><br/><br/>test line 2 - + ADD + + + Payment ID + + + + + Description + +
AddressBookTable @@ -160,11 +171,11 @@ - - - - - + + + + + <b>Tip tekst test</b> @@ -179,43 +190,43 @@ - + Description <font size='2'>(Local database)</font> - + <b>Tip tekst test</b><br/><br/>test line 2 - + Date from - - + + To - + FILTER - + Advance filtering - + Type of transation - + Amount from @@ -334,16 +345,31 @@ Address + + + ReadOnly wallet address displayed here + + Integrated address + + + ReadOnly wallet integrated address displayed here + + Payment ID + + + PaymentID here + + Generate @@ -357,6 +383,21 @@ Twitter + + + News + + + + + Help + + + + + About + + SearchInput @@ -389,6 +430,14 @@ + + TitleBar + + + Monero - Donations + + + Transfer @@ -406,6 +455,21 @@ Amount... + + + LOW + + + + + MEDIUM + + + + + HIGH + + Privacy Level @@ -422,17 +486,17 @@ - + Payment ID <font size='2'>( Optional )</font> - + Description <font size='2'>( An optional description that will be saved to the local address book if entered )</font> - + SEND @@ -455,22 +519,22 @@ - + Enable disk conservation mode? - + Disk conservation mode uses substantially less disk-space, but the same amount of bandwidth as a regular Monero instance. However, storing the full blockchain is beneficial to the security of the Monero network. If you are on a device with limited disk space, then this option is appropriate for you. - + Allow background mining? - + Mining secures the Monero network, and also pays a small reward for the work done. This option will let Monero mine when your computer is on mains power and is idle. It will stop mining when you continue working. @@ -511,12 +575,12 @@ - + Allow background mining? - + Mining secures the Monero network, and also pays a small reward for the work done. This option will let Monero mine when your computer is on mains power and is idle. It will stop mining when you continue working. @@ -559,12 +623,12 @@ - + An overview of your Monero configuration is below: - + You’re all setup! @@ -604,6 +668,11 @@ Your wallet is stored in + + + Please choose a directory + + WizardMemoTextInput @@ -681,7 +750,7 @@ - + Error @@ -732,24 +801,39 @@ Fee: - + Couldn't send the money: - + Information - + Money sent successfully - + Initializing Wallet... + + + Program setup wizard + + + + + Monero - Donations + + + + + send to the same destination + + diff --git a/wizard/WizardConfigure.qml b/wizard/WizardConfigure.qml index acfae0bd..343f9c88 100644 --- a/wizard/WizardConfigure.qml +++ b/wizard/WizardConfigure.qml @@ -76,7 +76,7 @@ Item { wrapMode: Text.Wrap //renderType: Text.NativeRendering color: "#3F3F3F" - text: qsTr("We’re almost there - let’s just configure some Monero preferences") + text: qsTr("We’re almost there - let’s just configure some Monero preferences") + translationManager.emptyString } Column { @@ -94,7 +94,7 @@ Item { spacing: 12 CheckBox { - text: qsTr("Kickstart the Monero blockchain?") + text: qsTr("Kickstart the Monero blockchain?") + translationManager.emptyString anchors.left: parent.left anchors.right: parent.right background: "#F0EEEE" @@ -114,6 +114,7 @@ Item { wrapMode: Text.Wrap text: qsTr("It is very important to write it down as this is the only backup you will need for your wallet. " + "You will be asked to confirm the seed in the next screen to ensure it has copied down correctly.") + + translationManager.emptyString } } @@ -123,7 +124,7 @@ Item { spacing: 12 CheckBox { - text: qsTr("Enable disk conservation mode?") + text: qsTr("Enable disk conservation mode?") + translationManager.emptyString anchors.left: parent.left anchors.right: parent.right background: "#F0EEEE" @@ -144,6 +145,7 @@ Item { text: qsTr("Disk conservation mode uses substantially less disk-space, but the same amount of bandwidth as " + "a regular Monero instance. However, storing the full blockchain is beneficial to the security " + "of the Monero network. If you are on a device with limited disk space, then this option is appropriate for you.") + + translationManager.emptyString } } @@ -153,7 +155,7 @@ Item { spacing: 12 CheckBox { - text: qsTr("Allow background mining?") + text: qsTr("Allow background mining?") + translationManager.emptyString anchors.left: parent.left anchors.right: parent.right background: "#F0EEEE" @@ -173,6 +175,7 @@ Item { wrapMode: Text.Wrap text: qsTr("Mining secures the Monero network, and also pays a small reward for the work done. This option " + "will let Monero mine when your computer is on mains power and is idle. It will stop mining when you continue working.") + + translationManager.emptyString } } } diff --git a/wizard/WizardCreateWallet.qml b/wizard/WizardCreateWallet.qml index d39d52d2..2181a359 100644 --- a/wizard/WizardCreateWallet.qml +++ b/wizard/WizardCreateWallet.qml @@ -78,8 +78,8 @@ Item { WizardManageWalletUI { id: uiItem - titleText: qsTr("A new wallet has been created for you") - wordsTextTitle: qsTr("This is the 25 word mnemonic for your wallet") + titleText: qsTr("A new wallet has been created for you") + translationManager.emptyString + wordsTextTitle: qsTr("This is the 25 word mnemonic for your wallet") + translationManager.emptyString wordsTextItem.clipboardButtonVisible: true wordsTextItem.tipTextVisible: true wordsTextItem.memoTextReadOnly: true diff --git a/wizard/WizardDonation.qml b/wizard/WizardDonation.qml index 80ebd78b..57e03524 100644 --- a/wizard/WizardDonation.qml +++ b/wizard/WizardDonation.qml @@ -90,7 +90,7 @@ Item { wrapMode: Text.Wrap //renderType: Text.NativeRendering color: "#3F3F3F" - text: qsTr("Monero development is solely supported by donations") + text: qsTr("Monero development is solely supported by donations") + translationManager.emptyString } Column { @@ -110,7 +110,7 @@ Item { CheckBox { id: enableAutoDonationCheckBox anchors.verticalCenter: parent.verticalCenter - text: qsTr("Enable auto-donations of?") + text: qsTr("Enable auto-donations of?") + translationManager.emptyString background: "#F0EEEE" fontColor: "#4A4646" fontSize: 18 @@ -150,7 +150,7 @@ Item { font.family: "Arial" font.pixelSize: 18 color: "#4A4646" - text: qsTr("% of my fee added to each transaction") + text: qsTr("% of my fee added to each transaction") + translationManager.emptyString } } @@ -164,6 +164,7 @@ Item { text: qsTr("For every transaction, a small transaction fee is charged. This option lets you add an additional amount, " + "as a percentage of that fee, to your transaction to support Monero development. For instance, a 50% " + "autodonation take a transaction fee of 0.005 XMR and add a 0.0025 XMR to support Monero development.") + + translationManager.emptyString } Column { anchors.left: parent.left @@ -172,7 +173,7 @@ Item { CheckBox { id: allowBackgroundMiningCheckBox - text: qsTr("Allow background mining?") + text: qsTr("Allow background mining?") + translationManager.emptyString anchors.left: parent.left anchors.right: parent.right background: "#F0EEEE" @@ -192,6 +193,7 @@ Item { wrapMode: Text.Wrap text: qsTr("Mining secures the Monero network, and also pays a small reward for the work done. This option " + "will let Monero mine when your computer is on mains power and is idle. It will stop mining when you continue working.") + + translationManager.emptyString } } } diff --git a/wizard/WizardFinish.qml b/wizard/WizardFinish.qml index 427a2340..7c9c89e7 100644 --- a/wizard/WizardFinish.qml +++ b/wizard/WizardFinish.qml @@ -45,10 +45,11 @@ Item { + qsTr("Enable auto donation: ") + wizard.settings['auto_donations_enabled'] + "
" + qsTr("Auto donation amount: ") + wizard.settings['auto_donations_amount'] + "
" + qsTr("Allow background mining: ") + wizard.settings['allow_background_mining'] + "
" + + translationManager.emptyString return str; } function updateSettingsSummary() { - settingsText.text = qsTr("An overview of your Monero configuration is below:") + settingsText.text = qsTr("An overview of your Monero configuration is below:") + translationManager.emptyString + "
" + buildSettingsString(); } @@ -99,7 +100,7 @@ Item { horizontalAlignment: Text.AlignHCenter //renderType: Text.NativeRendering color: "#3F3F3F" - text: qsTr("You’re all setup!") + text: qsTr("You’re all setup!") + translationManager.emptyString } Text { diff --git a/wizard/WizardMain.qml b/wizard/WizardMain.qml index 987ac897..1103c2ff 100644 --- a/wizard/WizardMain.qml +++ b/wizard/WizardMain.qml @@ -82,9 +82,9 @@ Rectangle { // disable "next" button until passwords match nextButton.enabled = passwordPage.passwordValid; if (currentPath === "create_wallet") { - passwordPage.titleText = qsTr("Now that your wallet has been created, please set a password for the wallet") + passwordPage.titleText = qsTr("Now that your wallet has been created, please set a password for the wallet") + translationManager.emptyString } else { - passwordPage.titleText = qsTr("Now that your wallet has been restored, please set a password for the wallet") + passwordPage.titleText = qsTr("Now that your wallet has been restored, please set a password for the wallet") + translationManager.emptyString } break; case finishPage: @@ -306,7 +306,7 @@ Rectangle { anchors.bottom: parent.bottom anchors.margins: 50 width: 110 - text: qsTr("USE MONERO") + text: qsTr("USE MONERO") + translationManager.emptyString shadowReleasedColor: "#FF4304" shadowPressedColor: "#B32D00" releasedColor: "#FF6C3C" diff --git a/wizard/WizardManageWalletUI.qml b/wizard/WizardManageWalletUI.qml index 58f4e59a..a11ee094 100644 --- a/wizard/WizardManageWalletUI.qml +++ b/wizard/WizardManageWalletUI.qml @@ -100,7 +100,7 @@ Item { horizontalAlignment: Text.AlignHCenter //renderType: Text.NativeRendering color: "#4A4646" - text: qsTr("This is the name of your wallet. You can change it to a different name if you’d like:") + text: qsTr("This is the name of your wallet. You can change it to a different name if you’d like:") + translationManager.emptyString } } @@ -122,7 +122,7 @@ Item { renderType: Text.NativeRendering color: "#FF6C3C" focus: true - text: qsTr("My account name") + text: qsTr("My account name") + translationManager.emptyString } Rectangle { @@ -172,7 +172,7 @@ Item { font.pixelSize: 18 //renderType: Text.NativeRendering color: "#4A4646" - text: qsTr("Your wallet is stored in") + text: qsTr("Your wallet is stored in") + translationManager.emptyString } Item { @@ -184,7 +184,7 @@ Item { id: fileDialog selectMultiple: false selectFolder: true - title: "Please choose a directory" + title: qsTr("Please choose a directory") + translationManager.emptyString onAccepted: { fileUrlInput.text = fileDialog.folder fileDialog.visible = false diff --git a/wizard/WizardMemoTextInput.qml b/wizard/WizardMemoTextInput.qml index 39f9211b..9497d9e5 100644 --- a/wizard/WizardMemoTextInput.qml +++ b/wizard/WizardMemoTextInput.qml @@ -74,6 +74,7 @@ Column { color: "#4A4646" wrapMode: Text.Wrap text: qsTr("It is very important to write it down as this is the only backup you will need for your wallet. You will be asked to confirm the seed in the next screen to ensure it has copied down correctly.") + + translationManager.emptyString } } } diff --git a/wizard/WizardOptions.qml b/wizard/WizardOptions.qml index 621cac78..7aa3be2f 100644 --- a/wizard/WizardOptions.qml +++ b/wizard/WizardOptions.qml @@ -59,7 +59,7 @@ Item { color: "#3F3F3F" wrapMode: Text.Wrap horizontalAlignment: Text.AlignHCenter - text: qsTr("Welcome to Monero!") + text: qsTr("Welcome to Monero!") + translationManager.emptyString } Text { @@ -71,7 +71,7 @@ Item { color: "#4A4646" wrapMode: Text.Wrap horizontalAlignment: Text.AlignHCenter - text: qsTr("Please select one of the following options:") + text: qsTr("Please select one of the following options:") + translationManager.emptyString } } @@ -107,7 +107,7 @@ Item { font.pixelSize: 16 color: "#4A4949" horizontalAlignment: Text.AlignHCenter - text: qsTr("This is my first time, I want to
create a new account") + text: qsTr("This is my first time, I want to
create a new account") + translationManager.emptyString } } @@ -138,7 +138,7 @@ Item { font.pixelSize: 16 color: "#4A4949" horizontalAlignment: Text.AlignHCenter - text: qsTr("I want to recover my account
from my 24 work seed") + text: qsTr("I want to recover my account
from my 24 work seed") + translationManager.emptyString } } } diff --git a/wizard/WizardPassword.qml b/wizard/WizardPassword.qml index 4825c02d..0e79d57c 100644 --- a/wizard/WizardPassword.qml +++ b/wizard/WizardPassword.qml @@ -120,6 +120,7 @@ Item { horizontalAlignment: Text.AlignHCenter text: qsTr("Note that this password cannot be recovered, and if forgotten you will need to restore your wallet from the mnemonic seed you were just given

Your password will be used to protect your wallet and to confirm actions, so make sure that your password is sufficiently secure.") + + translationManager.emptyString } } diff --git a/wizard/WizardRecoveryWallet.qml b/wizard/WizardRecoveryWallet.qml index aded7661..972ee6a1 100644 --- a/wizard/WizardRecoveryWallet.qml +++ b/wizard/WizardRecoveryWallet.qml @@ -66,9 +66,9 @@ Item { WizardManageWalletUI { id: uiItem - accountNameText: qsTr("My account name") - titleText: qsTr("We're ready to recover your account") - wordsTextTitle: qsTr("Please enter your 25 word private key") + accountNameText: qsTr("My account name") + translationManager.emptyString + titleText: qsTr("We're ready to recover your account") + translationManager.emptyString + wordsTextTitle: qsTr("Please enter your 25 word private key") + translationManager.emptyString wordsTextItem.clipboardButtonVisible: false wordsTextItem.tipTextVisible: false wordsTextItem.memoTextReadOnly: false diff --git a/wizard/utils.js b/wizard/utils.js index 6d16951d..0cc910c6 100644 --- a/wizard/utils.js +++ b/wizard/utils.js @@ -37,3 +37,8 @@ function mapScope (inputScopeFrom, inputScopeTo, outputScopeFrom, outputScopeTo, var result = outputScopeFrom + ((outputScopeTo - outputScopeFrom) * x); return result; } + + +function tr(text) { + return qsTr(text) + translationManager.emptyString +} From ecf844120b62e688e0527316d8f1322bfea06fe0 Mon Sep 17 00:00:00 2001 From: Ilya Kitaev Date: Thu, 21 Jul 2016 12:47:53 +0300 Subject: [PATCH 55/87] cleaning "auto-generated" bitmonero directory on "make clean". closes #22 --- monero-core.pro | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/monero-core.pro b/monero-core.pro index a5b170e5..877019d0 100644 --- a/monero-core.pro +++ b/monero-core.pro @@ -6,10 +6,14 @@ WALLET_ROOT=$$PWD/bitmonero CONFIG += c++11 +# cleaning "auto-generated" bitmonero directory on "make clean" +QMAKE_CLEAN += -r $$WALLET_ROOT + INCLUDEPATH += $$WALLET_ROOT/include \ $$PWD/src/libwalletqt + HEADERS += \ filter.h \ clipboardAdapter.h \ From e5d5a6082fdcacfd9f1de1b2144101e705f2d322 Mon Sep 17 00:00:00 2001 From: Ilya Kitaev Date: Thu, 21 Jul 2016 13:56:17 +0300 Subject: [PATCH 56/87] Wizard refactoring --- translations/monero-core_de.ts | 36 ++++++++++----------- translations/monero-core_en.ts | 36 ++++++++++----------- translations/monero-core_it.ts | 36 ++++++++++----------- translations/monero-core_pl.ts | 36 ++++++++++----------- translations/monero-core_ru.ts | 36 ++++++++++----------- translations/monero-core_zh.ts | 36 ++++++++++----------- wizard/WizardCreateWallet.qml | 9 ++++++ wizard/WizardFinish.qml | 4 +++ wizard/WizardMain.qml | 57 ++++++++++++++++----------------- wizard/WizardPassword.qml | 22 +++++++++++-- wizard/WizardRecoveryWallet.qml | 12 +++++-- 11 files changed, 179 insertions(+), 141 deletions(-) diff --git a/translations/monero-core_de.ts b/translations/monero-core_de.ts index ea08d710..24e76b60 100644 --- a/translations/monero-core_de.ts +++ b/translations/monero-core_de.ts @@ -542,12 +542,12 @@ WizardCreateWallet - + A new wallet has been created for you - + This is the 25 word mnemonic for your wallet @@ -628,7 +628,7 @@ - + You’re all setup! @@ -636,17 +636,7 @@ WizardMain - - Now that your wallet has been created, please set a password for the wallet - - - - - Now that your wallet has been restored, please set a password for the wallet - - - - + USE MONERO @@ -708,7 +698,17 @@ WizardPassword - + + Now that your wallet has been created, please set a password for the wallet + + + + + Now that your wallet has been restored, please set a password for the wallet + + + + Note that this password cannot be recovered, and if forgotten you will need to restore your wallet from the mnemonic seed you were just given<br/><br/> Your password will be used to protect your wallet and to confirm actions, so make sure that your password is sufficiently secure. @@ -717,17 +717,17 @@ WizardRecoveryWallet - + My account name - + We're ready to recover your account - + Please enter your 25 word private key diff --git a/translations/monero-core_en.ts b/translations/monero-core_en.ts index dca4c577..6e8d5bcc 100644 --- a/translations/monero-core_en.ts +++ b/translations/monero-core_en.ts @@ -542,12 +542,12 @@ WizardCreateWallet - + A new wallet has been created for you - + This is the 25 word mnemonic for your wallet @@ -628,7 +628,7 @@ - + You’re all setup! @@ -636,17 +636,7 @@ WizardMain - - Now that your wallet has been created, please set a password for the wallet - - - - - Now that your wallet has been restored, please set a password for the wallet - - - - + USE MONERO @@ -708,7 +698,17 @@ WizardPassword - + + Now that your wallet has been created, please set a password for the wallet + + + + + Now that your wallet has been restored, please set a password for the wallet + + + + Note that this password cannot be recovered, and if forgotten you will need to restore your wallet from the mnemonic seed you were just given<br/><br/> Your password will be used to protect your wallet and to confirm actions, so make sure that your password is sufficiently secure. @@ -717,17 +717,17 @@ WizardRecoveryWallet - + My account name - + We're ready to recover your account - + Please enter your 25 word private key diff --git a/translations/monero-core_it.ts b/translations/monero-core_it.ts index 2c9c4b78..403d3859 100644 --- a/translations/monero-core_it.ts +++ b/translations/monero-core_it.ts @@ -542,12 +542,12 @@ WizardCreateWallet - + A new wallet has been created for you - + This is the 25 word mnemonic for your wallet @@ -628,7 +628,7 @@ - + You’re all setup! @@ -636,17 +636,7 @@ WizardMain - - Now that your wallet has been created, please set a password for the wallet - - - - - Now that your wallet has been restored, please set a password for the wallet - - - - + USE MONERO @@ -708,7 +698,17 @@ WizardPassword - + + Now that your wallet has been created, please set a password for the wallet + + + + + Now that your wallet has been restored, please set a password for the wallet + + + + Note that this password cannot be recovered, and if forgotten you will need to restore your wallet from the mnemonic seed you were just given<br/><br/> Your password will be used to protect your wallet and to confirm actions, so make sure that your password is sufficiently secure. @@ -717,17 +717,17 @@ WizardRecoveryWallet - + My account name - + We're ready to recover your account - + Please enter your 25 word private key diff --git a/translations/monero-core_pl.ts b/translations/monero-core_pl.ts index a3496ddb..bb152986 100644 --- a/translations/monero-core_pl.ts +++ b/translations/monero-core_pl.ts @@ -542,12 +542,12 @@ WizardCreateWallet - + A new wallet has been created for you - + This is the 25 word mnemonic for your wallet @@ -628,7 +628,7 @@ - + You’re all setup! @@ -636,17 +636,7 @@ WizardMain - - Now that your wallet has been created, please set a password for the wallet - - - - - Now that your wallet has been restored, please set a password for the wallet - - - - + USE MONERO @@ -708,7 +698,17 @@ WizardPassword - + + Now that your wallet has been created, please set a password for the wallet + + + + + Now that your wallet has been restored, please set a password for the wallet + + + + Note that this password cannot be recovered, and if forgotten you will need to restore your wallet from the mnemonic seed you were just given<br/><br/> Your password will be used to protect your wallet and to confirm actions, so make sure that your password is sufficiently secure. @@ -717,17 +717,17 @@ WizardRecoveryWallet - + My account name - + We're ready to recover your account - + Please enter your 25 word private key diff --git a/translations/monero-core_ru.ts b/translations/monero-core_ru.ts index 29bf9b7f..5ca9c499 100644 --- a/translations/monero-core_ru.ts +++ b/translations/monero-core_ru.ts @@ -542,12 +542,12 @@ WizardCreateWallet - + A new wallet has been created for you - + This is the 25 word mnemonic for your wallet @@ -628,7 +628,7 @@ - + You’re all setup! @@ -636,17 +636,7 @@ WizardMain - - Now that your wallet has been created, please set a password for the wallet - - - - - Now that your wallet has been restored, please set a password for the wallet - - - - + USE MONERO @@ -708,7 +698,17 @@ WizardPassword - + + Now that your wallet has been created, please set a password for the wallet + + + + + Now that your wallet has been restored, please set a password for the wallet + + + + Note that this password cannot be recovered, and if forgotten you will need to restore your wallet from the mnemonic seed you were just given<br/><br/> Your password will be used to protect your wallet and to confirm actions, so make sure that your password is sufficiently secure. @@ -717,17 +717,17 @@ WizardRecoveryWallet - + My account name - + We're ready to recover your account - + Please enter your 25 word private key diff --git a/translations/monero-core_zh.ts b/translations/monero-core_zh.ts index e5e60dab..14f60021 100644 --- a/translations/monero-core_zh.ts +++ b/translations/monero-core_zh.ts @@ -542,12 +542,12 @@ WizardCreateWallet - + A new wallet has been created for you - + This is the 25 word mnemonic for your wallet @@ -628,7 +628,7 @@ - + You’re all setup! @@ -636,17 +636,7 @@ WizardMain - - Now that your wallet has been created, please set a password for the wallet - - - - - Now that your wallet has been restored, please set a password for the wallet - - - - + USE MONERO @@ -708,7 +698,17 @@ WizardPassword - + + Now that your wallet has been created, please set a password for the wallet + + + + + Now that your wallet has been restored, please set a password for the wallet + + + + Note that this password cannot be recovered, and if forgotten you will need to restore your wallet from the mnemonic seed you were just given<br/><br/> Your password will be used to protect your wallet and to confirm actions, so make sure that your password is sufficiently secure. @@ -717,17 +717,17 @@ WizardRecoveryWallet - + My account name - + We're ready to recover your account - + Please enter your 25 word private key diff --git a/wizard/WizardCreateWallet.qml b/wizard/WizardCreateWallet.qml index 2181a359..771a13ec 100644 --- a/wizard/WizardCreateWallet.qml +++ b/wizard/WizardCreateWallet.qml @@ -42,6 +42,10 @@ Item { //! function called each time we display this page + function onPageOpened(settingsOblect) { + checkNextButton() + } + function onPageClosed(settingsObject) { settingsObject['account_name'] = uiItem.accountNameText settingsObject['words'] = uiItem.wordsTexttext @@ -49,6 +53,11 @@ Item { return true; } + function checkNextButton() { + var wordsArray = cleanWordsInput(uiItem.wordsTextItem.memoText).split(" "); + wizard.nextButton.enabled = wordsArray.length === 25; + } + //! function called each time we hide this page // diff --git a/wizard/WizardFinish.qml b/wizard/WizardFinish.qml index 7c9c89e7..431b5a89 100644 --- a/wizard/WizardFinish.qml +++ b/wizard/WizardFinish.qml @@ -54,6 +54,10 @@ Item { + buildSettingsString(); } + function onPageOpened(settings) { + updateSettingsSummary(); + wizard.nextButton.visible = false; + } Row { diff --git a/wizard/WizardMain.qml b/wizard/WizardMain.qml index 1103c2ff..f0556efc 100644 --- a/wizard/WizardMain.qml +++ b/wizard/WizardMain.qml @@ -58,7 +58,7 @@ Rectangle { }; } - print ("switchpage: start: currentPage: ", currentPage); + print ("switchpage: currentPage: ", currentPage); if (currentPage > 0 || currentPage < pages.length - 1) { pages[currentPage].opacity = 0 @@ -66,40 +66,35 @@ Rectangle { currentPage += step_value pages[currentPage].opacity = 1; + var nextButtonVisible = pages[currentPage] !== optionsPage; + nextButton.visible = nextButtonVisible; + if (next && typeof pages[currentPage].onPageOpened !== 'undefined') { pages[currentPage].onPageOpened(settings) } - handlePageChanged(); + + + } } + // TODO: remove it function handlePageChanged() { - var nextButtonVisible = pages[currentPage] !== optionsPage; - nextButton.visible = nextButtonVisible; - print ("next button visible: " + nextButtonVisible); - switch (pages[currentPage]) { - case passwordPage: - // disable "next" button until passwords match - nextButton.enabled = passwordPage.passwordValid; - if (currentPath === "create_wallet") { - passwordPage.titleText = qsTr("Now that your wallet has been created, please set a password for the wallet") + translationManager.emptyString - } else { - passwordPage.titleText = qsTr("Now that your wallet has been restored, please set a password for the wallet") + translationManager.emptyString - } - break; - case finishPage: - // display settings summary - finishPage.updateSettingsSummary(); - nextButton.visible = false; - break; - case recoveryWalletPage: - // TODO: disable "next button" until 25 words private key entered - nextButton.enabled = false - break - default: - nextButton.enabled = true - } +// switch (pages[currentPage]) { +//// case finishPage: +//// // display settings summary +//// finishPage.updateSettingsSummary(); +//// nextButton.visible = false; +//// break; +// case recoveryWalletPage: +// // disable "next button" until 25 words private key entered +// nextButton.enabled = false +// break +// default: +// nextButton.enabled = true + +// } } @@ -111,7 +106,9 @@ Rectangle { pages = paths[currentPath] currentPage = pages.indexOf(createWalletPage) createWalletPage.createWallet(settings) - handlePageChanged() + wizard.nextButton.visible = true + createWalletPage.onPageOpened(settings); + } @@ -122,7 +119,9 @@ Rectangle { currentPath = "recovery_wallet" pages = paths[currentPath] currentPage = pages.indexOf(recoveryWalletPage) - handlePageChanged() + wizard.nextButton.visible = true + recoveryWalletPage.onPageOpened(settings); + } //! actually writes the wallet diff --git a/wizard/WizardPassword.qml b/wizard/WizardPassword.qml index 0e79d57c..8318a785 100644 --- a/wizard/WizardPassword.qml +++ b/wizard/WizardPassword.qml @@ -31,10 +31,10 @@ import "../components" import "utils.js" as Utils Item { + + id: passwordPage opacity: 0 visible: false - property bool passwordValid : passwordItem.password != '' - && passwordItem.password === retypePasswordItem.password property alias titleText: titleText.text Behavior on opacity { @@ -43,6 +43,17 @@ Item { onOpacityChanged: visible = opacity !== 0 + + function onPageOpened(settingsObject) { + wizard.nextButton.enabled = true + + if (wizard.currentPath === "create_wallet") { + passwordPage.titleText = qsTr("Now that your wallet has been created, please set a password for the wallet") + translationManager.emptyString + } else { + passwordPage.titleText = qsTr("Now that your wallet has been restored, please set a password for the wallet") + translationManager.emptyString + } + } + function onPageClosed(settingsObject) { // TODO: set password on the final page // settingsObject.wallet.setPassword(passwordItem.password) @@ -52,11 +63,14 @@ Item { function handlePassword() { // allow to forward step only if passwords match + wizard.nextButton.enabled = passwordItem.password === retypePasswordItem.password + // scorePassword returns value from 1..100 var strength = Utils.scorePassword(passwordItem.password) // privacyLevel component uses 1..13 scale privacyLevel.fillLevel = Utils.mapScope(1, 100, 1, 13, strength) + } @@ -155,4 +169,8 @@ Item { height: 62 onChanged: handlePassword() } + + Component.onCompleted: { + console.log + } } diff --git a/wizard/WizardRecoveryWallet.qml b/wizard/WizardRecoveryWallet.qml index 972ee6a1..2e6fe5f6 100644 --- a/wizard/WizardRecoveryWallet.qml +++ b/wizard/WizardRecoveryWallet.qml @@ -41,6 +41,15 @@ Item { onOpacityChanged: visible = opacity !== 0 + function onPageOpened(settingsObject) { + checkNextButton(); + } + + function checkNextButton() { + var wordsArray = cleanWordsInput(uiItem.wordsTextItem.memoText).split(" "); + wizard.nextButton.enabled = wordsArray.length === 25; + } + function onPageClosed(settingsObject) { settingsObject['account_name'] = uiItem.accountNameText settingsObject['words'] = cleanWordsInput(uiItem.wordsTextItem.memoText) @@ -74,8 +83,7 @@ Item { wordsTextItem.memoTextReadOnly: false wordsTextItem.memoText: "" wordsTextItem.onMemoTextChanged: { - var wordsArray = cleanWordsInput(wordsTextItem.memoText).split(" "); - wizard.nextButton.enabled = wordsArray.length === 25 + checkNextButton(); } } } From aa0ea18356ca2435aac7fd0d2ae8f9e638bdaa46 Mon Sep 17 00:00:00 2001 From: Ilya Kitaev Date: Thu, 21 Jul 2016 16:29:37 +0300 Subject: [PATCH 57/87] translation files moved to resources --- TranslationManager.cpp | 4 ++-- monero-core.pro | 7 ++++--- qml.qrc | 6 ++++++ 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/TranslationManager.cpp b/TranslationManager.cpp index 69448ce2..afd04ef6 100644 --- a/TranslationManager.cpp +++ b/TranslationManager.cpp @@ -25,8 +25,8 @@ bool TranslationManager::setLanguage(const QString &language) return true; } - // we expecting to have translation files in "i18n" directory - QString dir = qApp->applicationDirPath() + QDir::separator() + "i18n"; + // translations are compiled into app binary + QString dir = ":/translations"; QString filename = "monero-core_" + language; diff --git a/monero-core.pro b/monero-core.pro index 877019d0..0c24ea0d 100644 --- a/monero-core.pro +++ b/monero-core.pro @@ -90,8 +90,9 @@ linux { macx { LIBS+= \ - -lboost_serialization \ - -lboost_thread \ + -L/usr/local/lib \ + -lboost_serialization \ + -lboost_thread-mt \ -lboost_system \ -lboost_date_time \ -lboost_filesystem \ @@ -123,7 +124,7 @@ trans_update.depends = $$_PRO_FILE_ trans_release.commands = lrelease $$_PRO_FILE_ trans_release.depends = trans_update $$TRANSLATIONS -translate.commands = $(MKDIR) ${DESTDIR}/i18n && $(COPY) $$PWD/translations/*.qm ${DESTDIR}/i18n +#translate.commands = $(MKDIR) ${DESTDIR}/i18n && $(COPY) $$PWD/translations/*.qm ${DESTDIR}/i18n translate.depends = trans_release QMAKE_EXTRA_TARGETS += trans_update trans_release translate diff --git a/qml.qrc b/qml.qrc index eca9f561..152ea8f2 100644 --- a/qml.qrc +++ b/qml.qrc @@ -114,5 +114,11 @@ pages/Receive.qml components/IconButton.qml lang/flags/italy.png + translations/monero-core_de.qm + translations/monero-core_en.qm + translations/monero-core_it.qm + translations/monero-core_pl.qm + translations/monero-core_ru.qm + translations/monero-core_zh.qm From c7c06a5893f008e1118444297d2a331849444385 Mon Sep 17 00:00:00 2001 From: Ilya Kitaev Date: Fri, 22 Jul 2016 15:50:51 +0300 Subject: [PATCH 58/87] removing "bitmonero" directory in "distclean" target --- monero-core.pro | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/monero-core.pro b/monero-core.pro index 0c24ea0d..db5a618f 100644 --- a/monero-core.pro +++ b/monero-core.pro @@ -6,8 +6,8 @@ WALLET_ROOT=$$PWD/bitmonero CONFIG += c++11 -# cleaning "auto-generated" bitmonero directory on "make clean" -QMAKE_CLEAN += -r $$WALLET_ROOT +# cleaning "auto-generated" bitmonero directory on "make distclean" +QMAKE_DISTCLEAN += -r $$WALLET_ROOT INCLUDEPATH += $$WALLET_ROOT/include \ $$PWD/src/libwalletqt @@ -127,6 +127,8 @@ trans_release.depends = trans_update $$TRANSLATIONS #translate.commands = $(MKDIR) ${DESTDIR}/i18n && $(COPY) $$PWD/translations/*.qm ${DESTDIR}/i18n translate.depends = trans_release +deploy.commands = pushd $QMAKE_ + QMAKE_EXTRA_TARGETS += trans_update trans_release translate # updating transations only in release mode as this is requires to re-link project From 1b35a1ae4b01c05224fdc3b1acf70b7afdd6328c Mon Sep 17 00:00:00 2001 From: Ilya Kitaev Date: Mon, 25 Jul 2016 16:24:07 +0300 Subject: [PATCH 59/87] build automation script. tested on macos --- build.sh | 28 ++++++++++++++++++++++++++++ monero-core.pro | 8 ++++++-- 2 files changed, 34 insertions(+), 2 deletions(-) create mode 100755 build.sh diff --git a/build.sh b/build.sh new file mode 100755 index 00000000..5cfea74d --- /dev/null +++ b/build.sh @@ -0,0 +1,28 @@ +#!/bin/bash + +pushd $(pwd) +ROOT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" + +#$SHELL get_libwallet_api.sh + +if [ ! -d build ]; then mkdir build; fi +cd build +echo $(pwd) +qmake ../monero-core.pro "CONFIG += release" +make release +make deploy +popd + + + + + + + + + + + + + + diff --git a/monero-core.pro b/monero-core.pro index db5a618f..0997d2bd 100644 --- a/monero-core.pro +++ b/monero-core.pro @@ -5,6 +5,7 @@ QT += qml quick widgets WALLET_ROOT=$$PWD/bitmonero CONFIG += c++11 +CONFIG += debug_and_release # cleaning "auto-generated" bitmonero directory on "make distclean" QMAKE_DISTCLEAN += -r $$WALLET_ROOT @@ -102,9 +103,12 @@ macx { -lssl \ -lcrypto \ -ldl + + deploy.commands += macdeployqt $$sprintf("%1/release/%2.app", $$OUT_PWD,$$TARGET) } +deploy.commands += # translations files; TRANSLATIONS = $$PWD/translations/monero-core_en.ts \ # English (could be untranslated) @@ -127,9 +131,9 @@ trans_release.depends = trans_update $$TRANSLATIONS #translate.commands = $(MKDIR) ${DESTDIR}/i18n && $(COPY) $$PWD/translations/*.qm ${DESTDIR}/i18n translate.depends = trans_release -deploy.commands = pushd $QMAKE_ -QMAKE_EXTRA_TARGETS += trans_update trans_release translate + +QMAKE_EXTRA_TARGETS += trans_update trans_release translate deploy # updating transations only in release mode as this is requires to re-link project # even if no changes were made. From 9cd73dfbbed6e200852ca9e1c1494ea3e5eae6c3 Mon Sep 17 00:00:00 2001 From: Ilya Kitaev Date: Wed, 27 Jul 2016 22:32:33 +0300 Subject: [PATCH 60/87] Translations are separate qm files --- .gitignore | 1 + TranslationManager.cpp | 6 ++- build.sh | 9 +++-- main.cpp | 3 ++ monero-core.pro | 92 ++++++++++++++++++++++++------------------ qml.qrc | 6 --- 6 files changed, 68 insertions(+), 49 deletions(-) diff --git a/.gitignore b/.gitignore index a2ce1444..f2bec9c8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ *.user *.user.* +translations/*.qm diff --git a/TranslationManager.cpp b/TranslationManager.cpp index afd04ef6..fa39d35a 100644 --- a/TranslationManager.cpp +++ b/TranslationManager.cpp @@ -26,7 +26,11 @@ bool TranslationManager::setLanguage(const QString &language) } // translations are compiled into app binary - QString dir = ":/translations"; +#ifdef Q_OS_MACX + QString dir = qApp->applicationDirPath() + "/../Resources/translations"; +#else + QString dir = qApp->applicationDirPath() + "/translations"; +#endif QString filename = "monero-core_" + language; diff --git a/build.sh b/build.sh index 5cfea74d..3c3260f5 100755 --- a/build.sh +++ b/build.sh @@ -2,14 +2,17 @@ pushd $(pwd) ROOT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +BITMOMERO_DIR=bitmonero -#$SHELL get_libwallet_api.sh +if [ ! -d $BITMOMERO_DIR ]; then + $SHELL get_libwallet_api.sh +fi if [ ! -d build ]; then mkdir build; fi cd build echo $(pwd) -qmake ../monero-core.pro "CONFIG += release" -make release +qmake ../monero-core.pro "CONFIG+=release" +make make deploy popd diff --git a/main.cpp b/main.cpp index 030a21c3..9f109e01 100644 --- a/main.cpp +++ b/main.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include "clipboardAdapter.h" #include "filter.h" #include "oscursor.h" @@ -46,6 +47,8 @@ int main(int argc, char *argv[]) { QApplication app(argc, argv); + qDebug() << "app startd"; + app.setApplicationName("monero-core"); app.setOrganizationDomain("getmonero.org"); app.setOrganizationName("The Monero Project"); diff --git a/monero-core.pro b/monero-core.pro index 0997d2bd..668cecdb 100644 --- a/monero-core.pro +++ b/monero-core.pro @@ -5,7 +5,6 @@ QT += qml quick widgets WALLET_ROOT=$$PWD/bitmonero CONFIG += c++11 -CONFIG += debug_and_release # cleaning "auto-generated" bitmonero directory on "make distclean" QMAKE_DISTCLEAN += -r $$WALLET_ROOT @@ -13,8 +12,6 @@ QMAKE_DISTCLEAN += -r $$WALLET_ROOT INCLUDEPATH += $$WALLET_ROOT/include \ $$PWD/src/libwalletqt - - HEADERS += \ filter.h \ clipboardAdapter.h \ @@ -104,51 +101,59 @@ macx { -lcrypto \ -ldl - deploy.commands += macdeployqt $$sprintf("%1/release/%2.app", $$OUT_PWD,$$TARGET) } -deploy.commands += - -# translations files; -TRANSLATIONS = $$PWD/translations/monero-core_en.ts \ # English (could be untranslated) - $$PWD/translations/monero-core_de.ts \ # Deutsch - $$PWD/translations/monero-core_zh.ts \ # Chineese - $$PWD/translations/monero-core_ru.ts \ # Russian - $$PWD/translations/monero-core_it.ts \ # Italian - $$PWD/translations/monero-core_pl.ts \ # Polish - - - -# extra make targets for lupdate and lrelease invocation -# use "make lupdate" to update *.ts files and "make lrelease" to generate *.qm files -trans_update.commands = lupdate $$_PRO_FILE_ -trans_update.depends = $$_PRO_FILE_ - -trans_release.commands = lrelease $$_PRO_FILE_ -trans_release.depends = trans_update $$TRANSLATIONS - -#translate.commands = $(MKDIR) ${DESTDIR}/i18n && $(COPY) $$PWD/translations/*.qm ${DESTDIR}/i18n -translate.depends = trans_release - - - -QMAKE_EXTRA_TARGETS += trans_update trans_release translate deploy - -# updating transations only in release mode as this is requires to re-link project -# even if no changes were made. - -#PRE_TARGETDEPS += translate +# translation stuff +TRANSLATIONS = \ # English is default language, no explicit translation file + $$PWD/translations/monero-core_de.ts \ # Deutsch + $$PWD/translations/monero-core_zh.ts \ # Chineese + $$PWD/translations/monero-core_ru.ts \ # Russian + $$PWD/translations/monero-core_it.ts \ # Italian + $$PWD/translations/monero-core_pl.ts \ # Polish CONFIG(release, debug|release) { - DESTDIR=release - PRE_TARGETDEPS += translate + DESTDIR = release + LANGUPD_OPTIONS = -locations relative -no-ui-lines + LANGREL_OPTIONS = -compress -nounfinished -removeidentical + +} else { + DESTDIR = debug + LANGUPD_OPTIONS = + LANGREL_OPTIONS = -markuntranslated "MISS_TR " } -CONFIG(debug, debug|release) { - DESTDIR=debug +TARGET_FULL_PATH = $$OUT_PWD/$$DESTDIR + +macx { + TARGET_FULL_PATH = $$sprintf("%1/%2/%3.app", $$OUT_PWD, $$DESTDIR, $$TARGET) } +TRANSLATION_TARGET_DIR = $$TARGET_FULL_PATH/Contents/Resources/translations + +isEmpty(QMAKE_LUPDATE) { + win32:LANGUPD = $$[QT_INSTALL_BINS]\lupdate.exe + else:LANGUPD = $$[QT_INSTALL_BINS]/lupdate +} + +isEmpty(QMAKE_LRELEASE) { + win32:LANGREL = $$[QT_INSTALL_BINS]\lrelease.exe + else:LANGREL = $$[QT_INSTALL_BINS]/lrelease +} + +langupd.command = \ + $$LANGUPD $$LANGUPD_OPTIONS $$shell_path($$_PRO_FILE) -ts $$_PRO_FILE_PWD/$$TRANSLATIONS + +langrel.depends = langupd +langrel.input = TRANSLATIONS +langrel.output = $$TRANSLATION_TARGET_DIR/${QMAKE_FILE_BASE}.qm +langrel.commands = \ + $$LANGREL $$LANGREL_OPTIONS ${QMAKE_FILE_IN} -qm $$TRANSLATION_TARGET_DIR/${QMAKE_FILE_BASE}.qm +langrel.CONFIG += no_link + +QMAKE_EXTRA_TARGETS += langupd deploy +QMAKE_EXTRA_COMPILERS += langrel +PRE_TARGETDEPS += langupd compiler_langrel_make_all RESOURCES += qml.qrc @@ -157,6 +162,14 @@ QML_IMPORT_PATH = # Default rules for deployment. include(deployment.pri) +macx { + deploy.commands += macdeployqt $$sprintf("%1/%2/%3.app", $$OUT_PWD, $$DESTDIR, $$TARGET) +} + +win32 { + deploy.commands += windeployqt $$sprintf("%1/%2/%3", $$OUT_PWD, $$DESTDIR, $$TARGET) +} + @@ -167,3 +180,4 @@ OTHER_FILES += \ DISTFILES += \ notes.txt + diff --git a/qml.qrc b/qml.qrc index 152ea8f2..eca9f561 100644 --- a/qml.qrc +++ b/qml.qrc @@ -114,11 +114,5 @@ pages/Receive.qml components/IconButton.qml lang/flags/italy.png - translations/monero-core_de.qm - translations/monero-core_en.qm - translations/monero-core_it.qm - translations/monero-core_pl.qm - translations/monero-core_ru.qm - translations/monero-core_zh.qm From f640809d2574a19ff8ba97a0922669765e201367 Mon Sep 17 00:00:00 2001 From: Ilya Kitaev Date: Wed, 27 Jul 2016 23:17:21 +0300 Subject: [PATCH 61/87] macdeployqt app bundle crash fixed. --- monero-core.pro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/monero-core.pro b/monero-core.pro index 668cecdb..b0d72c7e 100644 --- a/monero-core.pro +++ b/monero-core.pro @@ -163,7 +163,7 @@ QML_IMPORT_PATH = # Default rules for deployment. include(deployment.pri) macx { - deploy.commands += macdeployqt $$sprintf("%1/%2/%3.app", $$OUT_PWD, $$DESTDIR, $$TARGET) + deploy.commands += macdeployqt $$sprintf("%1/%2/%3.app", $$OUT_PWD, $$DESTDIR, $$TARGET) -qmldir=$$PWD } win32 { From 7c33ea931a22498a638a380de105a6f295b75101 Mon Sep 17 00:00:00 2001 From: Ilya Kitaev Date: Wed, 3 Aug 2016 15:59:42 +0300 Subject: [PATCH 62/87] msys64 deploy fix --- build.sh | 2 +- monero-core.pro | 13 ++++++------- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/build.sh b/build.sh index 3c3260f5..7b0024d5 100755 --- a/build.sh +++ b/build.sh @@ -10,7 +10,7 @@ fi if [ ! -d build ]; then mkdir build; fi cd build -echo $(pwd) + qmake ../monero-core.pro "CONFIG+=release" make make deploy diff --git a/monero-core.pro b/monero-core.pro index b0d72c7e..4d3a556a 100644 --- a/monero-core.pro +++ b/monero-core.pro @@ -113,23 +113,25 @@ TRANSLATIONS = \ # English is default language, no explicit translation file $$PWD/translations/monero-core_pl.ts \ # Polish CONFIG(release, debug|release) { - DESTDIR = release + DESTDIR = release/bin LANGUPD_OPTIONS = -locations relative -no-ui-lines LANGREL_OPTIONS = -compress -nounfinished -removeidentical } else { - DESTDIR = debug + DESTDIR = debug/bin LANGUPD_OPTIONS = LANGREL_OPTIONS = -markuntranslated "MISS_TR " } TARGET_FULL_PATH = $$OUT_PWD/$$DESTDIR +TRANSLATION_TARGET_DIR = $$TARGET_FULL_PATH/translations macx { TARGET_FULL_PATH = $$sprintf("%1/%2/%3.app", $$OUT_PWD, $$DESTDIR, $$TARGET) + TRANSLATION_TARGET_DIR = $$TARGET_FULL_PATH/Contents/Resources/translations } -TRANSLATION_TARGET_DIR = $$TARGET_FULL_PATH/Contents/Resources/translations + isEmpty(QMAKE_LUPDATE) { win32:LANGUPD = $$[QT_INSTALL_BINS]\lupdate.exe @@ -167,17 +169,14 @@ macx { } win32 { - deploy.commands += windeployqt $$sprintf("%1/%2/%3", $$OUT_PWD, $$DESTDIR, $$TARGET) + deploy.commands += windeployqt $$sprintf("%1/%2/%3.exe", $$OUT_PWD, $$DESTDIR, $$TARGET) -qmldir=$$PWD } - - OTHER_FILES += \ .gitignore \ $$TRANSLATIONS DISTFILES += \ notes.txt - From 4ee5d785c5883ad219336a835a5cf2ed874fad96 Mon Sep 17 00:00:00 2001 From: Ilya Kitaev Date: Thu, 4 Aug 2016 13:56:49 +0300 Subject: [PATCH 63/87] Deploy additional mingw dependencies --- monero-core.pro | 3 ++- windeploy_helper.sh | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 windeploy_helper.sh diff --git a/monero-core.pro b/monero-core.pro index 4d3a556a..29fedea8 100644 --- a/monero-core.pro +++ b/monero-core.pro @@ -153,7 +153,7 @@ langrel.commands = \ $$LANGREL $$LANGREL_OPTIONS ${QMAKE_FILE_IN} -qm $$TRANSLATION_TARGET_DIR/${QMAKE_FILE_BASE}.qm langrel.CONFIG += no_link -QMAKE_EXTRA_TARGETS += langupd deploy +QMAKE_EXTRA_TARGETS += langupd deploy deploy_win QMAKE_EXTRA_COMPILERS += langrel PRE_TARGETDEPS += langupd compiler_langrel_make_all @@ -170,6 +170,7 @@ macx { win32 { deploy.commands += windeployqt $$sprintf("%1/%2/%3.exe", $$OUT_PWD, $$DESTDIR, $$TARGET) -qmldir=$$PWD + deploy.commands += $$escape_expand(\n\t) $$PWD/windeploy_helper.sh $$DESTDIR } diff --git a/windeploy_helper.sh b/windeploy_helper.sh new file mode 100644 index 00000000..7649db64 --- /dev/null +++ b/windeploy_helper.sh @@ -0,0 +1,20 @@ +#!/bin/bash + +TARGET=$1 + +FILES="zlib1.dll libwinpthread-1.dll libtiff-5.dll libstdc++-6.dll libpng16-16.dll libpcre16-0.dll libpcre-1.dll libmng-2.dll liblzma-5.dll liblcms2-2.dll libjpeg-8.dll libjasper-1.dll libintl-8.dll libicuuc57.dll libicuin57.dll libicudt57.dll libiconv-2.dll libharfbuzz-0.dll libgraphite2.dll libglib-2.0-0.dll libgcc_s_seh-1.dll libfreetype-6.dll libbz2-1.dll" + +for f in $FILES; do cp $MSYSTEM_PREFIX/bin/$f $TARGET; done + + + + + + + + + + + + + From 35d7661f0c56dfba5727984c32ae937a27ffeeca Mon Sep 17 00:00:00 2001 From: Ilya Kitaev Date: Thu, 4 Aug 2016 14:16:38 +0300 Subject: [PATCH 64/87] Windows application icon --- images/appicon.ico | Bin 0 -> 370070 bytes monero-core.pro | 2 ++ monero-core.rc | 1 + 3 files changed, 3 insertions(+) create mode 100644 images/appicon.ico create mode 100644 monero-core.rc diff --git a/images/appicon.ico b/images/appicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..abb4d57ab65209eccfd02a3b3ae33205102c495c GIT binary patch literal 370070 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 Date: Thu, 4 Aug 2016 14:52:33 +0300 Subject: [PATCH 65/87] mac application icon --- images/appicon.icns | Bin 0 -> 71517 bytes images/appicons/128x128.png | Bin 0 -> 6941 bytes images/appicons/16x16.png | Bin 0 -> 745 bytes images/appicons/24x24.png | Bin 0 -> 1238 bytes images/appicons/256x256.png | Bin 0 -> 10406 bytes images/appicons/32x32.png | Bin 0 -> 1695 bytes images/appicons/48x48.png | Bin 0 -> 2572 bytes images/appicons/64x64.png | Bin 0 -> 3436 bytes images/appicons/96x96.png | Bin 0 -> 5207 bytes monero-core.pro | 4 ++++ 10 files changed, 4 insertions(+) create mode 100755 images/appicon.icns create mode 100755 images/appicons/128x128.png create mode 100755 images/appicons/16x16.png create mode 100755 images/appicons/24x24.png create mode 100755 images/appicons/256x256.png create mode 100755 images/appicons/32x32.png create mode 100755 images/appicons/48x48.png create mode 100755 images/appicons/64x64.png create mode 100755 images/appicons/96x96.png diff --git a/images/appicon.icns b/images/appicon.icns new file mode 100755 index 0000000000000000000000000000000000000000..b87cae95af218e26c4080c1feae4979a465da6be GIT binary patch literal 71517 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

Twg}b7X3u!zSU_F5@8A;*cC_Y<|l0X|V5P?ykdO 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 MW2#6b^R4rnu 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 A*Z=?k literal 0 HcmV?d00001 diff --git a/images/appicons/256x256.png b/images/appicons/256x256.png new file mode 100755 index 0000000000000000000000000000000000000000..9293a3d60569bbbd02a0cdff1f7806393b62dd52 GIT binary patch literal 10406 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 E2Ma4dDgXcg literal 0 HcmV?d00001 diff --git a/images/appicons/32x32.png b/images/appicons/32x32.png new file mode 100755 index 0000000000000000000000000000000000000000..d547a46908d9e166effac3de4896c8366325f06d GIT binary patch literal 1695 zcmV;Q24MM#P)(P6-Q7FKGjlKNUAEm=!Xzi1o%z0V&dm4CZiRc` z>G1-U0|lxE2-MtfU{$P%Os?k<4I{^*BC!_UxR00jv?wC^h{ki@nIbV~nXl z)95iMWKNc_IcmvOlSJM@qSt|SV+2<5Z?PdzKEOj!4|4dqcIZ?RAErpBBPa<(z`a*y z-eAs~%$$o`SWp7hIx&2+`<} zD#ytMxqd+X0~1&ZTVr*MKq)n!dz=O;C|H)LH|*LEd6HN7w@Qp6+*czkOH2p3r=n^g zwGvEt6R4d;1I-Vz@sd2Kp^>^_Qz-ry))+vkq21pLkSm}#$$b^|!yiW7P+PNUBn>nU zH?Sjx*YQ{quc}=q6pJ96MJ3(~aj^NoMhrOR_zF8`_s5ydZ2XaiYCh9$zWA3JYzJiUJbD|lLcF*;@pl+ZOCC*7A4QQf3Akec3 zK2buSJR~0I217$REJ5!EczAUKH?XD`12U+I_V3>>uV23|wI>Sb@zbY3Nt`kw@yq97 zd)KaYs2q!fuA>=<{aqQj)N2Wx1}rWvhR^4dw{G1EH*xMb@cqYOYd3G+L|IuGN=iyB zUAe%}F z`fvlU7h5P0poXnQP0o+92jX8+8$=MrbeF~X+jU_EDk>^4Z{9qlrlz8E8qwrXU^0yj8(U08*n&qqE%vBimkD0F0doD8ym(=6#x{zgd>T_p$*KQ zJsS%aEWpBr3$+Of=$`26>Oxgjl@{loj-i3j4RG8^=QzRHz9Oz(`OOfGjg6+)>lItKZq4CSLwR|5fPf3SuIcINArZG2 z5wWgf7cX9H=Z7Px4zp#;mUO-r(cIi@3N^NG-|k6CNfC4A%sE7;Q~3!v2#NOrI>*JK zITbgtTWPVZw)okzXT{Q`OAR$YaNs~(R#w(&Hj+A--*v*ff|D;5m*OP%Q2?H{wl;$| zWE?+!T!{Sqe36@*t6;RZw+q&80I&RnLlPi@1nv?+hXdsNVBNZPaYATKqp+}0tX#QL z=&@|sGLfB~Emo{p;Tb%5u(5C7z8JdQP1i0W4f#Jrr7IrJ$+rS9CQh8_$;rtvmMmGK zHNytWAQ-4j7_67cXC{^}U;Y%I+&M+!61Q9emPVvO>3nB=O};1C4AS>92k&t01@2|Dt|RzcAk?Y( pyWruTIc?fBF=^5yK@$;r{{pw2Qq7>L1xx?{002ovPDHLkV1g#?CLRC) literal 0 HcmV?d00001 diff --git a/images/appicons/48x48.png b/images/appicons/48x48.png new file mode 100755 index 0000000000000000000000000000000000000000..17a5d04091d7e83c673c2fc6bd87a7a504b2f6a2 GIT binary patch literal 2572 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 O00003E|6 literal 0 HcmV?d00001 diff --git a/images/appicons/96x96.png b/images/appicons/96x96.png new file mode 100755 index 0000000000000000000000000000000000000000..f01ea8c440973dba00c967163314e375aa98d7a6 GIT binary patch literal 5207 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 RE*k&<002ovPDHLkV1mjg(tH2_ literal 0 HcmV?d00001 diff --git a/monero-core.pro b/monero-core.pro index 24ffa8ff..c247a802 100644 --- a/monero-core.pro +++ b/monero-core.pro @@ -182,4 +182,8 @@ OTHER_FILES += \ DISTFILES += \ notes.txt +# windows application icon RC_FILE = monero-core.rc + +# mac application icon +ICON = $$PWD/images/appicon.icns From b199c5fe0a6d7998bd7d263f98ee2a36c78e6552 Mon Sep 17 00:00:00 2001 From: Ilya Kitaev Date: Tue, 9 Aug 2016 15:05:53 +0300 Subject: [PATCH 66/87] bugfix: empty transfer page --- pages/Transfer.qml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/pages/Transfer.qml b/pages/Transfer.qml index 342e73cb..70fde050 100644 --- a/pages/Transfer.qml +++ b/pages/Transfer.qml @@ -93,9 +93,13 @@ Rectangle { ListModel { id: priorityModel - ListElement { column1: qsTr("LOW") + translationManager.emptyString; column2: ""; priority: PendingTransaction.Priority_Low } - ListElement { column1: qsTr("MEDIUM") + translationManager.emptyString; column2: ""; priority: PendingTransaction.Priority_Medium } - ListElement { column1: qsTr("HIGH") + translationManager.emptyString; column2: ""; priority: PendingTransaction.Priority_High } + // ListElement: cannot use script for property value, so + // code like this wont work: + // ListElement { column1: qsTr("LOW") + translationManager.emptyString ; column2: ""; priority: PendingTransaction.Priority_Low } + + ListElement { column1: qsTr("LOW") ; column2: ""; priority: PendingTransaction.Priority_Low } + ListElement { column1: qsTr("MEDIUM") ; column2: ""; priority: PendingTransaction.Priority_Medium } + ListElement { column1: qsTr("HIGH") ; column2: ""; priority: PendingTransaction.Priority_High } } StandardDropdown { From d0a5339289e6465c5ead8b73973b0840cfbb279b Mon Sep 17 00:00:00 2001 From: Ilya Kitaev Date: Wed, 10 Aug 2016 15:09:05 +0300 Subject: [PATCH 67/87] Removed: hardcoded "Monero - Donations" --- components/TitleBar.qml | 2 +- main.qml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/components/TitleBar.qml b/components/TitleBar.qml index e0baf8f1..8e1c9e32 100644 --- a/components/TitleBar.qml +++ b/components/TitleBar.qml @@ -35,7 +35,7 @@ Rectangle { color: "#000000" y: -height property int mouseX: 0 - property string title: qsTr("Monero - Donations") + translationManager.emptyString + property string title property bool containsMouse: false property alias maximizeButtonVisible: maximizeButton.visible property alias basicButtonVisible: goToBasicVersionButton.visible diff --git a/main.qml b/main.qml index da8545af..a7f2deb9 100644 --- a/main.qml +++ b/main.qml @@ -347,7 +347,7 @@ ApplicationWindow { PropertyChanges { target: titleBar; maximizeButtonVisible: true } PropertyChanges { target: frameArea; blocked: false } PropertyChanges { target: titleBar; y: -titleBar.height } - PropertyChanges { target: titleBar; title: qsTr("Monero - Donations") + translationManager.emptyString } + PropertyChanges { target: titleBar; title: qsTr("Monero") + translationManager.emptyString } } ] From 983317b4496eddf1083461b9c0ddf07261a6e9b2 Mon Sep 17 00:00:00 2001 From: Ilya Kitaev Date: Wed, 10 Aug 2016 15:18:56 +0300 Subject: [PATCH 68/87] Basic view: real wallet's balance --- main.qml | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/main.qml b/main.qml index a7f2deb9..46506b87 100644 --- a/main.qml +++ b/main.qml @@ -143,6 +143,8 @@ ApplicationWindow { console.log("opening wallet at: ", wallet_path); // TODO: wallet password dialog wallet = walletManager.openWallet(wallet_path, "", persistentSettings.testnet); + + if (wallet.status !== Wallet.Status_Ok) { console.log("Error opening wallet: ", wallet.errorString); informationPopup.title = qsTr("Error") + translationManager.emptyString; @@ -164,8 +166,9 @@ ApplicationWindow { function onWalletUpdate() { console.log(">>> wallet updated") - leftPanel.unlockedBalanceText = walletManager.displayAmount(wallet.unlockedBalance); - leftPanel.balanceText = walletManager.displayAmount(wallet.balance); + basicPanel.unlockedBalanceText = leftPanel.unlockedBalanceText = walletManager.displayAmount(wallet.unlockedBalance); + basicPanel.balanceText = leftPanel.balanceText = walletManager.displayAmount(wallet.balance); + } function onWalletRefresh() { From fc7a7ddf25d0a3cd7c7e6b7d07897435e1a2a43e Mon Sep 17 00:00:00 2001 From: Ilya Kitaev Date: Wed, 10 Aug 2016 16:21:58 +0300 Subject: [PATCH 69/87] BasicPanel: embedding/reusing "Transfer" page --- BasicPanel.qml | 93 ++++++++++++++------------------------------------ main.qml | 4 +++ 2 files changed, 29 insertions(+), 68 deletions(-) diff --git a/BasicPanel.qml b/BasicPanel.qml index 3c8de955..481fe8c4 100644 --- a/BasicPanel.qml +++ b/BasicPanel.qml @@ -28,15 +28,32 @@ import QtQuick 2.0 import "components" +import "pages" Rectangle { + id: root width: 470 - height: paymentId.y + paymentId.height + 12 + // height: paymentId.y + paymentId.height + 12 + height: header.height + header.anchors.topMargin + transferBasic.height color: "#F0EEEE" + border.width: 1 border.color: "#DBDBDB" + property alias balanceText : balanceText.text; property alias unlockedBalanceText : availableBalanceText.text; + // repeating signal to the outside world + signal paymentClicked(string address, string paymentId, double amount, int mixinCount, + int priority) + + Connections { + target: transferBasic + onPaymentClicked: { + console.log("BasicPanel: paymentClicked") + root.paymentClicked(address, paymentId, amount, mixinCount, priority) + } + } + Rectangle { id: header @@ -139,76 +156,16 @@ Rectangle { color: "#DBDBDB" } } - - Row { - id: row - anchors.left: parent.left - anchors.right: parent.right + Item { anchors.top: header.bottom - anchors.margins: 12 - spacing: 12 - - LineEdit { - height: 32 - fontSize: 15 - width: parent.width - sendButton.width - row.spacing - placeholderText: qsTr("amount...") + translationManager.emptyString - } - - StandardButton { - id: sendButton - width: 60 - height: 32 - fontSize: 11 - text: qsTr("SEND") - shadowReleasedColor: "#FF4304" - shadowPressedColor: "#B32D00" - releasedColor: "#FF6C3C" - pressedColor: "#FF4304" + anchors.bottom: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + Transfer { + id : transferBasic + anchors.fill: parent } } - LineEdit { - id: destinationLine - anchors.left: parent.left - anchors.right: parent.right - anchors.top: row.bottom - anchors.margins: 12 - fontSize: 15 - height: 32 - placeholderText: qsTr("destination...") + translationManager.emptyString - } - Text { - id: privacyLevelText - anchors.horizontalCenter: parent.horizontalCenter - anchors.top: destinationLine.bottom - anchors.topMargin: 12 - - font.family: "Arial" - font.pixelSize: 12 - color: "#535353" - text: qsTr("Privacy level") + translationManager.emptyString - } - - PrivacyLevelSmall { - id: privacyLevel - anchors.left: parent.left - anchors.right: parent.right - anchors.top: privacyLevelText.bottom - anchors.leftMargin: 12 - anchors.rightMargin: 12 - anchors.topMargin: 12 - } - - LineEdit { - id: paymentId - anchors.left: parent.left - anchors.right: parent.right - anchors.top: privacyLevel.bottom - anchors.margins: 12 - fontSize: 15 - height: 32 - placeholderText: qsTr("payment ID (optional)...") + translationManager.emptyString - } } diff --git a/main.qml b/main.qml index 46506b87..919d0a8b 100644 --- a/main.qml +++ b/main.qml @@ -133,6 +133,7 @@ ApplicationWindow { } middlePanel.paymentClicked.connect(handlePayment); + basicPanel.paymentClicked.connect(handlePayment); if (typeof wizard.settings['wallet'] !== 'undefined') { @@ -397,6 +398,9 @@ ApplicationWindow { id: basicPanel x: 0 anchors.bottom: parent.bottom + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right visible: false } From 6f1343aaa04bf2e4f959b5d3201239fe7fc85398 Mon Sep 17 00:00:00 2001 From: Ilya Kitaev Date: Tue, 16 Aug 2016 23:21:46 +0300 Subject: [PATCH 70/87] ask user for the password if wallet is password-protected --- components/PasswordDialog.qml | 5 +++++ main.qml | 40 +++++++++++++++++++++++++++++------ monero-core.pro | 5 ++++- qml.qrc | 1 + wizard/WizardMain.qml | 39 ++++++---------------------------- 5 files changed, 49 insertions(+), 41 deletions(-) create mode 100644 components/PasswordDialog.qml diff --git a/components/PasswordDialog.qml b/components/PasswordDialog.qml new file mode 100644 index 00000000..9c36e13c --- /dev/null +++ b/components/PasswordDialog.qml @@ -0,0 +1,5 @@ +import QtQuick 2.0 + +Item { + +} diff --git a/main.qml b/main.qml index 919d0a8b..7320d3af 100644 --- a/main.qml +++ b/main.qml @@ -139,19 +139,18 @@ ApplicationWindow { if (typeof wizard.settings['wallet'] !== 'undefined') { wallet = wizard.settings['wallet']; } else { - var wallet_path = persistentSettings.wallet_path + "/" + persistentSettings.account_name + "/" - + persistentSettings.account_name; + var wallet_path = walletPath(); + console.log("opening wallet at: ", wallet_path); // TODO: wallet password dialog wallet = walletManager.openWallet(wallet_path, "", persistentSettings.testnet); if (wallet.status !== Wallet.Status_Ok) { - console.log("Error opening wallet: ", wallet.errorString); - informationPopup.title = qsTr("Error") + translationManager.emptyString; - informationPopup.text = qsTr("Couldn't open wallet: ") + wallet.errorString; - informationPopup.icon = StandardIcon.Critical - informationPopup.open() + console.error("Error opening wallet with empty password: ", wallet.errorString); + + // try to open wallet with password; + passwordDialog.open(); return; } console.log("Wallet opened successfully: ", wallet.errorString); @@ -165,6 +164,13 @@ ApplicationWindow { } + function walletPath() { + var wallet_path = persistentSettings.wallet_path + "/" + persistentSettings.account_name + "/" + + persistentSettings.account_name; + return wallet_path; + } + + function onWalletUpdate() { console.log(">>> wallet updated") basicPanel.unlockedBalanceText = leftPanel.unlockedBalanceText = walletManager.displayAmount(wallet.unlockedBalance); @@ -291,6 +297,7 @@ ApplicationWindow { // Information dialog MessageDialog { id: informationPopup + standardButtons: StandardButton.Ok } @@ -303,6 +310,25 @@ ApplicationWindow { } } + PasswordDialog { + id: passwordDialog + standardButtons: StandardButton.Ok + StandardButton.Cancel + onAccepted: { + + var wallet_path = walletPath(); + console.log("opening wallet with password: ", wallet_path); + wallet = walletManager.openWallet(wallet_path, password, persistentSettings.testnet); + if (wallet.status !== Wallet.Status_Ok) { + console.error("Error opening wallet with password: ", wallet.errorString); + informationPopup.title = qsTr("Error") + translationManager.emptyString; + informationPopup.text = qsTr("Couldn't open wallet: ") + wallet.errorString; + informationPopup.icon = StandardIcon.Critical + informationPopup.open() + + } + } + } + Window { id: walletInitializationSplash modality: Qt.ApplicationModal diff --git a/monero-core.pro b/monero-core.pro index c247a802..9520cd37 100644 --- a/monero-core.pro +++ b/monero-core.pro @@ -155,6 +155,8 @@ langrel.CONFIG += no_link QMAKE_EXTRA_TARGETS += langupd deploy deploy_win QMAKE_EXTRA_COMPILERS += langrel + + PRE_TARGETDEPS += langupd compiler_langrel_make_all RESOURCES += qml.qrc @@ -180,7 +182,8 @@ OTHER_FILES += \ $$TRANSLATIONS DISTFILES += \ - notes.txt + notes.txt \ + components/PasswordDialog.qml # windows application icon RC_FILE = monero-core.rc diff --git a/qml.qrc b/qml.qrc index eca9f561..dcf4b9b1 100644 --- a/qml.qrc +++ b/qml.qrc @@ -114,5 +114,6 @@ pages/Receive.qml components/IconButton.qml lang/flags/italy.png + components/PasswordDialog.qml diff --git a/wizard/WizardMain.qml b/wizard/WizardMain.qml index f0556efc..488bd959 100644 --- a/wizard/WizardMain.qml +++ b/wizard/WizardMain.qml @@ -78,24 +78,6 @@ Rectangle { } } - // TODO: remove it - function handlePageChanged() { - -// switch (pages[currentPage]) { -//// case finishPage: -//// // display settings summary -//// finishPage.updateSettingsSummary(); -//// nextButton.visible = false; -//// break; -// case recoveryWalletPage: -// // disable "next button" until 25 words private key entered -// nextButton.enabled = false -// break -// default: -// nextButton.enabled = true - -// } - } function openCreateWalletPage() { @@ -126,10 +108,9 @@ Rectangle { //! actually writes the wallet function applySettings() { - print ("Here we apply the settings"); + console.log("Here we apply the settings"); // here we need to actually move wallet to the new location - // put wallet files to the subdirectory with the same name as - // wallet name + var new_wallet_filename = settings.wallet_path + "/" + settings.account_name + "/" + settings.account_name; @@ -138,9 +119,12 @@ Rectangle { if (new_wallet_filename !== settings.wallet_filename) { // using previously saved wallet; settings.wallet.store(new_wallet_filename); - //walletManager.moveWallet(settingsObject.wallet_filename, new_wallet_filename); } + // protecting wallet with password + console.log("Protecting wallet with password: " + settings.wallet_password) + settings.wallet.setPassword(settings.wallet_password); + // saving wallet_filename; settings['wallet_filename'] = new_wallet_filename; @@ -163,17 +147,6 @@ Rectangle { } -// Settings { -// id: persistentSettings - -// property string language -// property string account_name -// property string wallet_path -// property bool auto_donations_enabled : true -// property int auto_donations_amount : 50 -// property bool allow_background_mining : true -// } - Rectangle { id: nextButton anchors.verticalCenter: parent.verticalCenter From c1269301f7c19a849b0fae7ddbe9afd38aaf71d3 Mon Sep 17 00:00:00 2001 From: Ilya Kitaev Date: Wed, 17 Aug 2016 15:14:43 +0300 Subject: [PATCH 71/87] Ask for password in wallet is password protected. closes #26 --- BasicPanel.qml | 8 ++++ LeftPanel.qml | 9 ++++ MiddlePanel.qml | 9 ++++ RightPanel.qml | 10 +++++ components/PasswordDialog.qml | 37 +++++++++++++++- main.qml | 81 ++++++++++++++++++++++++----------- monero-core.pro | 13 ++++-- 7 files changed, 138 insertions(+), 29 deletions(-) diff --git a/BasicPanel.qml b/BasicPanel.qml index 481fe8c4..abeb135c 100644 --- a/BasicPanel.qml +++ b/BasicPanel.qml @@ -27,6 +27,7 @@ // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. import QtQuick 2.0 +import QtGraphicalEffects 1.0 import "components" import "pages" @@ -167,5 +168,12 @@ Rectangle { } } + // indicate disabled state + Desaturate { + anchors.fill: parent + source: parent + desaturation: root.enabled ? 0.0 : 1.0 + } + } diff --git a/LeftPanel.qml b/LeftPanel.qml index 604f2d6e..851db047 100644 --- a/LeftPanel.qml +++ b/LeftPanel.qml @@ -27,6 +27,7 @@ // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. import QtQuick 2.2 +import QtGraphicalEffects 1.0 import "components" Rectangle { @@ -355,4 +356,12 @@ Rectangle { connected: false } } + // indicate disabled state + Desaturate { + anchors.fill: parent + source: parent + desaturation: panel.enabled ? 0.0 : 1.0 + } + + } diff --git a/MiddlePanel.qml b/MiddlePanel.qml index cb1c74d6..a2cabc1c 100644 --- a/MiddlePanel.qml +++ b/MiddlePanel.qml @@ -27,8 +27,10 @@ // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. import QtQuick 2.2 +import QtGraphicalEffects 1.0 Rectangle { + id: root color: "#F0EEEE" signal paymentClicked(string address, string paymentId, double amount, int mixinCount, int priority) signal generatePaymentIdInvoked() @@ -116,4 +118,11 @@ Rectangle { height: 1 color: "#DBDBDB" } + + // indicate disabled state + Desaturate { + anchors.fill: parent + source: parent + desaturation: root.enabled ? 0.0 : 1.0 + } } diff --git a/RightPanel.qml b/RightPanel.qml index 932b3916..d27b8b73 100644 --- a/RightPanel.qml +++ b/RightPanel.qml @@ -29,10 +29,13 @@ import QtQuick 2.2 import QtQuick.Controls 1.2 import QtQuick.Controls.Styles 1.2 +import QtGraphicalEffects 1.0 + import "tabs" import "components" Rectangle { + id: root width: 330 color: "#FFFFFF" @@ -145,4 +148,11 @@ Rectangle { width: 1 color: "#DBDBDB" } + + // indicate disabled state + Desaturate { + anchors.fill: parent + source: parent + desaturation: root.enabled ? 0.0 : 1.0 + } } diff --git a/components/PasswordDialog.qml b/components/PasswordDialog.qml index 9c36e13c..cd66d461 100644 --- a/components/PasswordDialog.qml +++ b/components/PasswordDialog.qml @@ -1,5 +1,40 @@ import QtQuick 2.0 +import QtQuick.Controls 1.4 +import QtQuick.Dialogs 1.2 +import QtQuick.Layouts 1.1 +import QtQuick.Controls.Styles 1.4 -Item { +// import "../components" +Dialog { + id: root + readonly property alias password: passwordInput.text + standardButtons: StandardButton.Ok + StandardButton.Cancel + ColumnLayout { + id: column + height: 40 + anchors.fill: parent + + Label { + text: qsTr("Please enter wallet password") + Layout.columnSpan: 2 + Layout.fillWidth: true + font.family: "Arial" + font.pixelSize: 32 + } + + TextField { + id : passwordInput + + echoMode: TextInput.Password + focus: true + Layout.fillWidth: true + font.family: "Arial" + font.pixelSize: 24 + style: TextFieldStyle { + passwordCharacter: "•" + } + } + } } + diff --git a/main.qml b/main.qml index 7320d3af..faeaf123 100644 --- a/main.qml +++ b/main.qml @@ -41,7 +41,8 @@ import "wizard" ApplicationWindow { id: appWindow - objectName: "appWindow" + + property var currentItem property bool whatIsEnable: false property bool ctrlPressed: false @@ -50,6 +51,8 @@ ApplicationWindow { property alias persistentSettings : persistentSettings property var wallet; property var transaction; + property alias password : passwordDialog.password + function altKeyReleased() { ctrlPressed = false; } @@ -98,24 +101,24 @@ ApplicationWindow { } function mousePressed(obj, mouseX, mouseY) { - if(obj.objectName === "appWindow") - obj = rootItem +// if(obj.objectName === "appWindow") +// obj = rootItem - var tmp = rootItem.mapFromItem(obj, mouseX, mouseY) - if(tmp !== undefined) { - mouseX = tmp.x - mouseY = tmp.y - } +// var tmp = rootItem.mapFromItem(obj, mouseX, mouseY) +// if(tmp !== undefined) { +// mouseX = tmp.x +// mouseY = tmp.y +// } - if(currentItem !== undefined) { - var tmp_x = rootItem.mapToItem(currentItem, mouseX, mouseY).x - var tmp_y = rootItem.mapToItem(currentItem, mouseX, mouseY).y +// if(currentItem !== undefined) { +// var tmp_x = rootItem.mapToItem(currentItem, mouseX, mouseY).x +// var tmp_y = rootItem.mapToItem(currentItem, mouseX, mouseY).y - if(!currentItem.containsPoint(tmp_x, tmp_y)) { - currentItem.hide() - currentItem = undefined - } - } +// if(!currentItem.containsPoint(tmp_x, tmp_y)) { +// currentItem.hide() +// currentItem = undefined +// } +// } } function mouseReleased(obj, mouseX, mouseY) { @@ -142,17 +145,18 @@ ApplicationWindow { var wallet_path = walletPath(); console.log("opening wallet at: ", wallet_path); - // TODO: wallet password dialog - wallet = walletManager.openWallet(wallet_path, "", persistentSettings.testnet); - - + wallet = walletManager.openWallet(wallet_path, appWindow.password, + persistentSettings.testnet); if (wallet.status !== Wallet.Status_Ok) { console.error("Error opening wallet with empty password: ", wallet.errorString); - + console.log("closing wallet...") + walletManager.closeWallet(wallet) + console.log("wallet closed") // try to open wallet with password; passwordDialog.open(); return; } + console.log("Wallet opened successfully: ", wallet.errorString); } // subscribing for wallet updates @@ -195,6 +199,8 @@ ApplicationWindow { } + + // called on "transfer" function handlePayment(address, paymentId, amount, mixinCount, priority) { console.log("Creating transaction: ") @@ -213,6 +219,7 @@ ApplicationWindow { informationPopup.title = qsTr("Error") + translationManager.emptyString; informationPopup.text = qsTr("Can't create transaction: ") + transaction.errorString informationPopup.icon = StandardIcon.Critical + informationPopup.onCloseCallback = null informationPopup.open(); // deleting transaction object, we don't want memleaks wallet.disposeTransaction(transaction); @@ -248,13 +255,22 @@ ApplicationWindow { informationPopup.text = qsTr("Money sent successfully") + translationManager.emptyString informationPopup.icon = StandardIcon.Information } - + informationPopup.onCloseCallback = null informationPopup.open() wallet.refresh() wallet.disposeTransaction(transaction) } + // blocks UI if wallet can't be opened or no connection to the daemon + function enableUI(enable) { + middlePanel.enabled = enable; + leftPanel.enabled = enable; + rightPanel.enabled = enable; + basicPanel.enabled = enable; + } + + objectName: "appWindow" visible: true width: rightPanelExpanded ? 1269 : 1269 - 300 height: 800 @@ -262,6 +278,7 @@ ApplicationWindow { flags: Qt.FramelessWindowHint | Qt.WindowSystemMenuHint | Qt.Window | Qt.WindowMinimizeButtonHint onWidthChanged: x -= 0 + Component.onCompleted: { x = (Screen.width - width) / 2 y = (Screen.height - height) / 2 @@ -278,6 +295,7 @@ ApplicationWindow { } } + Settings { id: persistentSettings property string language @@ -296,9 +314,15 @@ ApplicationWindow { // Information dialog MessageDialog { + // dynamically change onclose handler + property var onCloseCallback id: informationPopup - standardButtons: StandardButton.Ok + onAccepted: { + if (onCloseCallback) { + onCloseCallback() + } + } } // Confrirmation aka question dialog @@ -317,6 +341,7 @@ ApplicationWindow { var wallet_path = walletPath(); console.log("opening wallet with password: ", wallet_path); + wallet = walletManager.openWallet(wallet_path, password, persistentSettings.testnet); if (wallet.status !== Wallet.Status_Ok) { console.error("Error opening wallet with password: ", wallet.errorString); @@ -324,9 +349,16 @@ ApplicationWindow { informationPopup.text = qsTr("Couldn't open wallet: ") + wallet.errorString; informationPopup.icon = StandardIcon.Critical informationPopup.open() - + informationPopup.onCloseCallback = appWindow.initialize + walletManager.closeWallet(wallet); } } + onRejected: { + appWindow.enableUI(false) + } + onDiscard: { + appWindow.enableUI(false) + } } Window { @@ -339,7 +371,6 @@ ApplicationWindow { anchors.fill: parent text: qsTr("Initializing Wallet..."); } - } diff --git a/monero-core.pro b/monero-core.pro index 9520cd37..2dde61fe 100644 --- a/monero-core.pro +++ b/monero-core.pro @@ -146,6 +146,8 @@ isEmpty(QMAKE_LRELEASE) { langupd.command = \ $$LANGUPD $$LANGUPD_OPTIONS $$shell_path($$_PRO_FILE) -ts $$_PRO_FILE_PWD/$$TRANSLATIONS + + langrel.depends = langupd langrel.input = TRANSLATIONS langrel.output = $$TRANSLATION_TARGET_DIR/${QMAKE_FILE_BASE}.qm @@ -157,7 +159,12 @@ QMAKE_EXTRA_TARGETS += langupd deploy deploy_win QMAKE_EXTRA_COMPILERS += langrel -PRE_TARGETDEPS += langupd compiler_langrel_make_all + +# temporary: do not update/release translations for "Debug" build, +# as we have an issue with linking +CONFIG(release, debug|release) { + PRE_TARGETDEPS += langupd compiler_langrel_make_all +} RESOURCES += qml.qrc @@ -182,8 +189,8 @@ OTHER_FILES += \ $$TRANSLATIONS DISTFILES += \ - notes.txt \ - components/PasswordDialog.qml + notes.txt + # windows application icon RC_FILE = monero-core.rc From d3234bb91564ee8327b111e0acf2a83153929c25 Mon Sep 17 00:00:00 2001 From: Ilya Kitaev Date: Thu, 18 Aug 2016 21:55:34 +0300 Subject: [PATCH 72/87] WalletManager::openWalletAsync in progress --- main.qml | 81 ++++++++++++++++++++----------- src/libwalletqt/WalletManager.cpp | 34 ++++++++++++- src/libwalletqt/WalletManager.h | 33 +++++++++++-- 3 files changed, 114 insertions(+), 34 deletions(-) diff --git a/main.qml b/main.qml index faeaf123..bf30266d 100644 --- a/main.qml +++ b/main.qml @@ -49,9 +49,10 @@ ApplicationWindow { property bool rightPanelExpanded: false property bool osx: false property alias persistentSettings : persistentSettings - property var wallet; + property var currentWallet; property var transaction; property alias password : passwordDialog.password + property bool walletOpeningWithPassword: false @@ -145,26 +146,9 @@ ApplicationWindow { var wallet_path = walletPath(); console.log("opening wallet at: ", wallet_path); - wallet = walletManager.openWallet(wallet_path, appWindow.password, + walletManager.openWalletAsync(wallet_path, appWindow.password, persistentSettings.testnet); - if (wallet.status !== Wallet.Status_Ok) { - console.error("Error opening wallet with empty password: ", wallet.errorString); - console.log("closing wallet...") - walletManager.closeWallet(wallet) - console.log("wallet closed") - // try to open wallet with password; - passwordDialog.open(); - return; - } - - console.log("Wallet opened successfully: ", wallet.errorString); } - // subscribing for wallet updates - wallet.updated.connect(onWalletUpdate); - wallet.refreshed.connect(onWalletRefresh); - - console.log("initializing with daemon address..") - wallet.initAsync(persistentSettings.daemon_address, 0); } @@ -174,6 +158,50 @@ ApplicationWindow { return wallet_path; } + function onWalletOpened(wallet) { + console.log(">>> wallet opened: " + wallet) + + if (wallet.status !== Wallet.Status_Ok) { + if (!appWindow.walletOpeningWithPassword) { + console.error("Error opening wallet with empty password: ", wallet.errorString); + console.log("closing wallet async...") + walletManager.closeWalletAsync(wallet) + // try to open wallet with password; + appWindow.walletOpeningWithPassword = true + passwordDialog.open(); + } else { + // opening with password but password doesn't match + console.error("Error opening wallet with password: ", wallet.errorString); + informationPopup.title = qsTr("Error") + translationManager.emptyString; + informationPopup.text = qsTr("Couldn't open wallet: ") + wallet.errorString; + informationPopup.icon = StandardIcon.Critical + informationPopup.open() + informationPopup.onCloseCallback = appWindow.initialize + walletManager.closeWallet(wallet); + } + return; + } + + // wallet opened successfully, subscribing for wallet updates + currentWallet = wallet +// wallet.updated.connect(appWindow.onWalletUpdate) +// wallet.refreshed.connect(appWindow.onWalletRefresh) + // currentWallet.refreshed.connect(onWalletRefresh) + var connectResult = currentWallet.refreshed.connect(function() { + console.log("QML: refreshed") + }) + + console.log("connected to refreshed: " + connectResult); + currentWallet.updated.connect(onWalletUpdate) + console.log("initializing with daemon address: ", persistentSettings.daemon_address) + currentWallet.initAsync(persistentSettings.daemon_address, 0); + + } + + + function onWalletClosed(walletAddress) { + console.log(">>> wallet closed: " + walletAddress) + } function onWalletUpdate() { console.log(">>> wallet updated") @@ -283,6 +311,9 @@ ApplicationWindow { x = (Screen.width - width) / 2 y = (Screen.height - height) / 2 // + walletManager.walletOpened.connect(onWalletOpened); + walletManager.walletClosed.connect(onWalletClosed); + rootItem.state = walletsFound() ? "normal" : "wizard"; if (rootItem.state === "normal") { initialize(persistentSettings) @@ -342,16 +373,8 @@ ApplicationWindow { var wallet_path = walletPath(); console.log("opening wallet with password: ", wallet_path); - wallet = walletManager.openWallet(wallet_path, password, persistentSettings.testnet); - if (wallet.status !== Wallet.Status_Ok) { - console.error("Error opening wallet with password: ", wallet.errorString); - informationPopup.title = qsTr("Error") + translationManager.emptyString; - informationPopup.text = qsTr("Couldn't open wallet: ") + wallet.errorString; - informationPopup.icon = StandardIcon.Critical - informationPopup.open() - informationPopup.onCloseCallback = appWindow.initialize - walletManager.closeWallet(wallet); - } + walletManager.openWalletAsync(wallet_path, password, persistentSettings.testnet); + } onRejected: { appWindow.enableUI(false) diff --git a/src/libwalletqt/WalletManager.cpp b/src/libwalletqt/WalletManager.cpp index a7742bca..50a61553 100644 --- a/src/libwalletqt/WalletManager.cpp +++ b/src/libwalletqt/WalletManager.cpp @@ -6,6 +6,7 @@ #include #include #include +#include @@ -42,6 +43,20 @@ Wallet *WalletManager::openWallet(const QString &path, const QString &password, return wallet; } +void WalletManager::openWalletAsync(const QString &path, const QString &password, bool testnet) +{ + QFuture future = QtConcurrent::run(this, &WalletManager::openWallet, + path, password, testnet); + QFutureWatcher * watcher = new QFutureWatcher(); + watcher->setFuture(future); + connect(watcher, &QFutureWatcher::finished, + this, [this, watcher]() { + QFuture future = watcher->future(); + watcher->deleteLater(); + emit walletOpened(future.result()); + }); +} + Wallet *WalletManager::recoveryWallet(const QString &path, const QString &memo, bool testnet) { @@ -51,9 +66,26 @@ Wallet *WalletManager::recoveryWallet(const QString &path, const QString &memo, } -void WalletManager::closeWallet(Wallet *wallet) +QString WalletManager::closeWallet(Wallet *wallet) { + QString result = wallet->address(); delete wallet; + return result; +} + +void WalletManager::closeWalletAsync(Wallet *wallet) +{ + QFuture future = QtConcurrent::run(this, &WalletManager::closeWallet, + wallet); + QFutureWatcher * watcher = new QFutureWatcher(); + watcher->setFuture(future); + + connect(watcher, &QFutureWatcher::finished, + this, [this, watcher]() { + QFuture future = watcher->future(); + watcher->deleteLater(); + emit future.result(); + }); } bool WalletManager::walletExists(const QString &path) const diff --git a/src/libwalletqt/WalletManager.h b/src/libwalletqt/WalletManager.h index 29df9048..27224b5f 100644 --- a/src/libwalletqt/WalletManager.h +++ b/src/libwalletqt/WalletManager.h @@ -16,15 +16,38 @@ public: // wizard: createWallet path; Q_INVOKABLE Wallet * createWallet(const QString &path, const QString &password, const QString &language, bool testnet = false); - // just for future use + + /*! + * \brief openWallet - opens wallet by given path + * \param path - wallet filename + * \param password - wallet password. Empty string in wallet isn't password protected + * \param testnet - determines if we running testnet + * \return wallet object pointer + */ Q_INVOKABLE Wallet * openWallet(const QString &path, const QString &password, bool testnet = false); + /*! + * \brief openWalletAsync - asynchronous version of "openWallet". Returns immediately. "walletOpened" signal + * emitted when wallet opened; + */ + Q_INVOKABLE void openWalletAsync(const QString &path, const QString &password, bool testnet = false); + // wizard: recoveryWallet path; hint: internally it recorvers wallet and set password = "" Q_INVOKABLE Wallet * recoveryWallet(const QString &path, const QString &memo, bool testnet = false); - //! utils: close wallet to free memory - Q_INVOKABLE void closeWallet(Wallet * wallet); + /*! + * \brief closeWallet - closes wallet and frees memory + * \param wallet + * \return wallet address + */ + Q_INVOKABLE QString closeWallet(Wallet * wallet); + + /*! + * \brief closeWalletAsync - asynchronous version of "closeWallet" + * \param wallet - wallet pointer; + */ + Q_INVOKABLE void closeWalletAsync(Wallet * wallet); //! checks is given filename is a wallet; Q_INVOKABLE bool walletExists(const QString &path) const; @@ -50,8 +73,10 @@ public: signals: -public slots: + void walletOpened(Wallet * wallet); + void walletClosed(const QString &walletAddress); +public slots: private: explicit WalletManager(QObject *parent = 0); From 8d93f01db434ffd873e095d55abeeae7d8021f6f Mon Sep 17 00:00:00 2001 From: Ilya Kitaev Date: Fri, 19 Aug 2016 14:44:44 +0300 Subject: [PATCH 73/87] WalletManager::openWalletAsync integrating with UI --- main.qml | 62 +++++++++++++++---------------- src/libwalletqt/WalletManager.cpp | 9 ++++- wizard/WizardCreateWallet.qml | 3 +- wizard/WizardRecoveryWallet.qml | 9 ++--- wizard/utils.js | 5 +++ 5 files changed, 50 insertions(+), 38 deletions(-) diff --git a/main.qml b/main.qml index bf30266d..f589c666 100644 --- a/main.qml +++ b/main.qml @@ -52,8 +52,6 @@ ApplicationWindow { property var currentWallet; property var transaction; property alias password : passwordDialog.password - property bool walletOpeningWithPassword: false - function altKeyReleased() { ctrlPressed = false; } @@ -140,18 +138,27 @@ ApplicationWindow { basicPanel.paymentClicked.connect(handlePayment); + // wallet already opened with wizard, we just need to initialize it if (typeof wizard.settings['wallet'] !== 'undefined') { - wallet = wizard.settings['wallet']; + connectWallet(wizard.settings['wallet']) } else { var wallet_path = walletPath(); - - console.log("opening wallet at: ", wallet_path); + console.log("opening wallet at: ", wallet_path, "with password: ", appWindow.password); walletManager.openWalletAsync(wallet_path, appWindow.password, persistentSettings.testnet); } } + + function connectWallet(wallet) { + currentWallet = wallet + currentWallet.refreshed.connect(onWalletRefresh) + currentWallet.updated.connect(onWalletUpdate) + console.log("initializing with daemon address: ", persistentSettings.daemon_address) + currentWallet.initAsync(persistentSettings.daemon_address, 0); + } + function walletPath() { var wallet_path = persistentSettings.wallet_path + "/" + persistentSettings.account_name + "/" + persistentSettings.account_name; @@ -162,39 +169,32 @@ ApplicationWindow { console.log(">>> wallet opened: " + wallet) if (wallet.status !== Wallet.Status_Ok) { - if (!appWindow.walletOpeningWithPassword) { + if (appWindow.password === '') { console.error("Error opening wallet with empty password: ", wallet.errorString); - console.log("closing wallet async...") + console.log("closing wallet async : " + wallet.address) walletManager.closeWalletAsync(wallet) // try to open wallet with password; - appWindow.walletOpeningWithPassword = true passwordDialog.open(); } else { // opening with password but password doesn't match console.error("Error opening wallet with password: ", wallet.errorString); + informationPopup.title = qsTr("Error") + translationManager.emptyString; informationPopup.text = qsTr("Couldn't open wallet: ") + wallet.errorString; informationPopup.icon = StandardIcon.Critical + console.log("closing wallet async : " + wallet.address) + walletManager.closeWalletAsync(wallet); informationPopup.open() - informationPopup.onCloseCallback = appWindow.initialize - walletManager.closeWallet(wallet); + informationPopup.onCloseCallback = function() { + passwordDialog.open() + } + } return; } // wallet opened successfully, subscribing for wallet updates - currentWallet = wallet -// wallet.updated.connect(appWindow.onWalletUpdate) -// wallet.refreshed.connect(appWindow.onWalletRefresh) - // currentWallet.refreshed.connect(onWalletRefresh) - var connectResult = currentWallet.refreshed.connect(function() { - console.log("QML: refreshed") - }) - - console.log("connected to refreshed: " + connectResult); - currentWallet.updated.connect(onWalletUpdate) - console.log("initializing with daemon address: ", persistentSettings.daemon_address) - currentWallet.initAsync(persistentSettings.daemon_address, 0); + connectWallet(wallet) } @@ -205,14 +205,14 @@ ApplicationWindow { function onWalletUpdate() { console.log(">>> wallet updated") - basicPanel.unlockedBalanceText = leftPanel.unlockedBalanceText = walletManager.displayAmount(wallet.unlockedBalance); - basicPanel.balanceText = leftPanel.balanceText = walletManager.displayAmount(wallet.balance); - + basicPanel.unlockedBalanceText = leftPanel.unlockedBalanceText = + walletManager.displayAmount(currentWallet.unlockedBalance); + basicPanel.balanceText = leftPanel.balanceText = walletManager.displayAmount(currentWallet.balance); } function onWalletRefresh() { console.log(">>> wallet refreshed") - leftPanel.networkStatus.connected = wallet.connected + leftPanel.networkStatus.connected = currentWallet.connected onWalletUpdate(); } @@ -369,12 +369,12 @@ ApplicationWindow { id: passwordDialog standardButtons: StandardButton.Ok + StandardButton.Cancel onAccepted: { + appWindow.currentWallet = null + appWindow.initialize(); - var wallet_path = walletPath(); - console.log("opening wallet with password: ", wallet_path); - - walletManager.openWalletAsync(wallet_path, password, persistentSettings.testnet); - +// var wallet_path = walletPath(); +// console.log("opening wallet with password: ", wallet_path); +// walletManager.openWalletAsync(wallet_path, password, persistentSettings.testnet); } onRejected: { appWindow.enableUI(false) diff --git a/src/libwalletqt/WalletManager.cpp b/src/libwalletqt/WalletManager.cpp index 50a61553..da5c4cd7 100644 --- a/src/libwalletqt/WalletManager.cpp +++ b/src/libwalletqt/WalletManager.cpp @@ -40,6 +40,13 @@ Wallet *WalletManager::openWallet(const QString &path, const QString &password, Bitmonero::Wallet * w = m_pimpl->openWallet(path.toStdString(), password.toStdString(), testnet); Wallet * wallet = new Wallet(w); + + // move wallet to the GUI thread. Otherwise it wont be emitting signals + if (wallet->thread() != qApp->thread()) { + wallet->moveToThread(qApp->thread()); + } + + return wallet; } @@ -84,7 +91,7 @@ void WalletManager::closeWalletAsync(Wallet *wallet) this, [this, watcher]() { QFuture future = watcher->future(); watcher->deleteLater(); - emit future.result(); + emit walletClosed(future.result()); }); } diff --git a/wizard/WizardCreateWallet.qml b/wizard/WizardCreateWallet.qml index 771a13ec..43b8158f 100644 --- a/wizard/WizardCreateWallet.qml +++ b/wizard/WizardCreateWallet.qml @@ -29,6 +29,7 @@ import QtQuick 2.2 import moneroComponents 1.0 import QtQuick.Dialogs 1.2 +import 'utils.js' as Utils Item { opacity: 0 @@ -54,7 +55,7 @@ Item { } function checkNextButton() { - var wordsArray = cleanWordsInput(uiItem.wordsTextItem.memoText).split(" "); + var wordsArray = Utils.lineBreaksToSpaces(uiItem.wordsTextItem.memoText).split(" "); wizard.nextButton.enabled = wordsArray.length === 25; } diff --git a/wizard/WizardRecoveryWallet.qml b/wizard/WizardRecoveryWallet.qml index 2e6fe5f6..505bc239 100644 --- a/wizard/WizardRecoveryWallet.qml +++ b/wizard/WizardRecoveryWallet.qml @@ -30,6 +30,7 @@ import QtQuick 2.2 import moneroComponents 1.0 import QtQuick.Dialogs 1.2 import Bitmonero.Wallet 1.0 +import 'utils.js' as Utils Item { opacity: 0 @@ -46,13 +47,13 @@ Item { } function checkNextButton() { - var wordsArray = cleanWordsInput(uiItem.wordsTextItem.memoText).split(" "); + var wordsArray = Utils.lineBreaksToSpaces(uiItem.wordsTextItem.memoText).split(" "); wizard.nextButton.enabled = wordsArray.length === 25; } function onPageClosed(settingsObject) { settingsObject['account_name'] = uiItem.accountNameText - settingsObject['words'] = cleanWordsInput(uiItem.wordsTextItem.memoText) + settingsObject['words'] = Utils.lineBreaksToSpaces(uiItem.wordsTextItem.memoText) settingsObject['wallet_path'] = uiItem.walletPath return recoveryWallet(settingsObject) } @@ -69,9 +70,7 @@ Item { return success; } - function cleanWordsInput(text) { - return text.trim().replace(/(\r\n|\n|\r)/gm, " "); - } + WizardManageWalletUI { id: uiItem diff --git a/wizard/utils.js b/wizard/utils.js index 0cc910c6..be066598 100644 --- a/wizard/utils.js +++ b/wizard/utils.js @@ -42,3 +42,8 @@ function mapScope (inputScopeFrom, inputScopeTo, outputScopeFrom, outputScopeTo, function tr(text) { return qsTr(text) + translationManager.emptyString } + + +function lineBreaksToSpaces(text) { + return text.trim().replace(/(\r\n|\n|\r)/gm, " "); +} From 57ad0927085df2a9ffe7d59174bb9baf9b36430c Mon Sep 17 00:00:00 2001 From: ferretinjapan Date: Sun, 21 Aug 2016 03:29:52 +0930 Subject: [PATCH 74/87] Update README.md with linux build instructions Tested and confirmed to work on Ubuntu 16.04 i386 on virtual machine. x64 version has build dependency issues however, an issue has been filed, see https://github.com/mbg033/monero-core/issues/40 --- README.md | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 25caa911..7546a521 100644 --- a/README.md +++ b/README.md @@ -73,7 +73,42 @@ Process: TODO ### On Linux: -TODO + +(Tested on Ubuntu 16.04 i386) + +1. Install Bitmonero dependencies. + +`sudo apt install build-essential cmake libboost-all-dev miniupnpc libunbound-dev graphviz doxygen libunwind8-dev pkg-config libssl-dev` + +2. Go to the repository where the most recent version is. + +`git clone https://github.com/mbg033/monero-core.git` + +3. Go into the repository. + +`cd monero-core` + +4. Use the script to compile the bitmonero libs necessary to run the GUI. + +`./get_libwallet_api.sh` + +5. Install the GUI dependencies. + +`sudo apt-get install qtbase5-dev qt5-default qtdeclarative5-dev qml-module-qtquick-controls qml-module-qtquick-xmllistmodel qttools5-dev-tools qml-module-qtquick-dialogs` + +6. Build the GUI. + +`qmake` + +`make` + +7. Before running the GUI, it's recommended you have a copy of bitmonero running in the background. + +`./bitmonerod --rpc-bind-port 38081` + +8. Run the GUI client. + +`./release/bin/monero-core` ### On OS X: 1. install homebrew From 23c4c9550f10c438c6f3719c6872012cb6898726 Mon Sep 17 00:00:00 2001 From: ferretinjapan Date: Mon, 22 Aug 2016 04:33:26 +0930 Subject: [PATCH 75/87] Update README.md --- README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7546a521..29f6df9c 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ Process: TODO ### On Linux: -(Tested on Ubuntu 16.04 i386) +(Tested on Ubuntu 16.04 i386 and Linux Mint 18 "Sarah" - Cinnamon (64-bit)) 1. Install Bitmonero dependencies. @@ -94,8 +94,14 @@ Process: TODO 5. Install the GUI dependencies. + a) For Ubuntu 16.04 i386 + `sudo apt-get install qtbase5-dev qt5-default qtdeclarative5-dev qml-module-qtquick-controls qml-module-qtquick-xmllistmodel qttools5-dev-tools qml-module-qtquick-dialogs` + b) For Linux Mint 18 "Sarah" - Cinnamon (64-bit) + +`sudo apt install qml-module-qt-labs-settings qml-module-qtgraphicaleffects` + 6. Build the GUI. `qmake` From 6b9afcf291cb5ab5db78f358a6213b1afc6081af Mon Sep 17 00:00:00 2001 From: Ilya Kitaev Date: Tue, 23 Aug 2016 10:32:01 +0300 Subject: [PATCH 76/87] extra debug logging --- main.cpp | 2 ++ src/libwalletqt/WalletManager.cpp | 17 ++++++++--------- src/libwalletqt/WalletManager.h | 14 ++++++++++++++ 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/main.cpp b/main.cpp index 9f109e01..d0e9e085 100644 --- a/main.cpp +++ b/main.cpp @@ -111,5 +111,7 @@ int main(int argc, char *argv[]) QObject::connect(eventFilter, SIGNAL(mousePressed(QVariant,QVariant,QVariant)), rootObject, SLOT(mousePressed(QVariant,QVariant,QVariant))); QObject::connect(eventFilter, SIGNAL(mouseReleased(QVariant,QVariant,QVariant)), rootObject, SLOT(mouseReleased(QVariant,QVariant,QVariant))); + WalletManager::instance()->setLogLevel(WalletManager::LogLevel_Max); + return app.exec(); } diff --git a/src/libwalletqt/WalletManager.cpp b/src/libwalletqt/WalletManager.cpp index da5c4cd7..947ccbfb 100644 --- a/src/libwalletqt/WalletManager.cpp +++ b/src/libwalletqt/WalletManager.cpp @@ -8,14 +8,9 @@ #include #include - - WalletManager * WalletManager::m_instance = nullptr; - - - WalletManager *WalletManager::instance() { if (!m_instance) { @@ -36,9 +31,11 @@ Wallet *WalletManager::createWallet(const QString &path, const QString &password Wallet *WalletManager::openWallet(const QString &path, const QString &password, bool testnet) { - // TODO: call the libwallet api here; + qDebug("%s: opening wallet at %s, testnet = %d ", + __PRETTY_FUNCTION__, qPrintable(path), testnet); Bitmonero::Wallet * w = m_pimpl->openWallet(path.toStdString(), password.toStdString(), testnet); + qDebug("%s: opened wallet: %s, status: %d", __PRETTY_FUNCTION__, w->address().c_str(), w->status()); Wallet * wallet = new Wallet(w); // move wallet to the GUI thread. Otherwise it wont be emitting signals @@ -46,7 +43,6 @@ Wallet *WalletManager::openWallet(const QString &path, const QString &password, wallet->moveToThread(qApp->thread()); } - return wallet; } @@ -141,9 +137,12 @@ quint64 WalletManager::amountFromDouble(double amount) return Bitmonero::Wallet::amountFromDouble(amount); } +void WalletManager::setLogLevel(int logLevel) +{ + Bitmonero::WalletManagerFactory::setLogLevel(logLevel); +} + WalletManager::WalletManager(QObject *parent) : QObject(parent) { m_pimpl = Bitmonero::WalletManagerFactory::getWalletManager(); } - - diff --git a/src/libwalletqt/WalletManager.h b/src/libwalletqt/WalletManager.h index 27224b5f..1866b7cd 100644 --- a/src/libwalletqt/WalletManager.h +++ b/src/libwalletqt/WalletManager.h @@ -2,6 +2,7 @@ #define WALLETMANAGER_H #include +#include class Wallet; namespace Bitmonero { @@ -12,6 +13,17 @@ class WalletManager : public QObject { Q_OBJECT public: + enum LogLevel { + LogLevel_Silent = Bitmonero::WalletManagerFactory::LogLevel_Silent, + LogLevel_0 = Bitmonero::WalletManagerFactory::LogLevel_0, + LogLevel_1 = Bitmonero::WalletManagerFactory::LogLevel_1, + LogLevel_2 = Bitmonero::WalletManagerFactory::LogLevel_2, + LogLevel_3 = Bitmonero::WalletManagerFactory::LogLevel_3, + LogLevel_4 = Bitmonero::WalletManagerFactory::LogLevel_4, + LogLevel_Min = Bitmonero::WalletManagerFactory::LogLevel_Min, + LogLevel_Max = Bitmonero::WalletManagerFactory::LogLevel_Max, + }; + static WalletManager * instance(); // wizard: createWallet path; Q_INVOKABLE Wallet * createWallet(const QString &path, const QString &password, @@ -71,6 +83,8 @@ public: Q_INVOKABLE quint64 amountFromString(const QString &amount); Q_INVOKABLE quint64 amountFromDouble(double amount); + void setLogLevel(int logLevel); + signals: void walletOpened(Wallet * wallet); From 376db6cf16910b0facf10c90afcdfe2a50fc1045 Mon Sep 17 00:00:00 2001 From: Ilya Kitaev Date: Tue, 23 Aug 2016 11:55:51 +0300 Subject: [PATCH 77/87] Display "processing.." splashscreen while wallet initializing --- components/PasswordDialog.qml | 28 +++++++++++++++ components/ProcessingSplash.qml | 63 +++++++++++++++++++++++++++++++++ main.cpp | 3 ++ main.qml | 44 ++++++++++++++--------- qml.qrc | 1 + 5 files changed, 122 insertions(+), 17 deletions(-) create mode 100644 components/ProcessingSplash.qml diff --git a/components/PasswordDialog.qml b/components/PasswordDialog.qml index cd66d461..a8ff294a 100644 --- a/components/PasswordDialog.qml +++ b/components/PasswordDialog.qml @@ -1,3 +1,31 @@ +// 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 QtQuick 2.0 import QtQuick.Controls 1.4 import QtQuick.Dialogs 1.2 diff --git a/components/ProcessingSplash.qml b/components/ProcessingSplash.qml new file mode 100644 index 00000000..f98feb3b --- /dev/null +++ b/components/ProcessingSplash.qml @@ -0,0 +1,63 @@ +// 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 QtQuick 2.0 +import QtQuick.Window 2.1 +import QtQuick.Controls 1.4 +import QtQuick.Layouts 1.1 + +Window { + id: splash + modality: Qt.ApplicationModal + flags: Qt.SplashScreen + property alias message: message.text + width: 200 + height: 100 + opacity: 0.5 + + ColumnLayout { + anchors.horizontalCenter: parent.horizontalCenter + anchors.verticalCenter: parent.verticalCenter + + BusyIndicator { + running: parent.visible + Layout.alignment: Qt.AlignVCenter | Qt.AlignHCenter + } + + Text { + id: message + text: "Please wait..." + font { + pointSize: 22 + } + horizontalAlignment: Text.AlignHCenter + } + } + + +} diff --git a/main.cpp b/main.cpp index d0e9e085..387c4b13 100644 --- a/main.cpp +++ b/main.cpp @@ -72,6 +72,8 @@ int main(int argc, char *argv[]) qRegisterMetaType(); + + QQmlApplicationEngine engine; OSCursor cursor; @@ -83,6 +85,7 @@ int main(int argc, char *argv[]) engine.rootContext()->setContextProperty("translationManager", TranslationManager::instance()); + // export to QML monero accounts root directory // wizard is talking about where // to save the wallet file (.keys, .bin), they have to be user-accessible for diff --git a/main.qml b/main.qml index f589c666..636945e8 100644 --- a/main.qml +++ b/main.qml @@ -147,11 +147,11 @@ ApplicationWindow { walletManager.openWalletAsync(wallet_path, appWindow.password, persistentSettings.testnet); } - } function connectWallet(wallet) { + showProcessingSplash() currentWallet = wallet currentWallet.refreshed.connect(onWalletRefresh) currentWallet.updated.connect(onWalletUpdate) @@ -167,7 +167,6 @@ ApplicationWindow { function onWalletOpened(wallet) { console.log(">>> wallet opened: " + wallet) - if (wallet.status !== Wallet.Status_Ok) { if (appWindow.password === '') { console.error("Error opening wallet with empty password: ", wallet.errorString); @@ -188,7 +187,6 @@ ApplicationWindow { informationPopup.onCloseCallback = function() { passwordDialog.open() } - } return; } @@ -212,6 +210,10 @@ ApplicationWindow { function onWalletRefresh() { console.log(">>> wallet refreshed") + if (splash.visible) { + hideProcessingSplash() + } + leftPanel.networkStatus.connected = currentWallet.connected onWalletUpdate(); } @@ -297,6 +299,19 @@ ApplicationWindow { basicPanel.enabled = enable; } + function showProcessingSplash(message) { + console.log("Displaying processing splash") + if (typeof message != 'undefined') { + splash.message = message + } + splash.show() + } + + function hideProcessingSplash() { + console.log("Hiding processing splash") + splash.hide() + } + objectName: "appWindow" visible: true @@ -371,10 +386,6 @@ ApplicationWindow { onAccepted: { appWindow.currentWallet = null appWindow.initialize(); - -// var wallet_path = walletPath(); -// console.log("opening wallet with password: ", wallet_path); -// walletManager.openWalletAsync(wallet_path, password, persistentSettings.testnet); } onRejected: { appWindow.enableUI(false) @@ -384,19 +395,18 @@ ApplicationWindow { } } - Window { - id: walletInitializationSplash - modality: Qt.ApplicationModal - flags: Qt.SplashScreen - height: 100 - width: 250 - Text { - anchors.fill: parent - text: qsTr("Initializing Wallet..."); - } + + ProcessingSplash { + id: splash + width: appWindow.width / 2 + height: appWindow.height / 2 + x: (appWindow.width - width) / 2 + appWindow.x + y: (appWindow.height - height) / 2 + appWindow.y + message: qsTr("Please wait...") } + Item { id: rootItem anchors.fill: parent diff --git a/qml.qrc b/qml.qrc index dcf4b9b1..a44266b9 100644 --- a/qml.qrc +++ b/qml.qrc @@ -115,5 +115,6 @@ components/IconButton.qml lang/flags/italy.png components/PasswordDialog.qml + components/ProcessingSplash.qml From 4fa8ad3b199483094cf9988f33da1ab6343d7c8a Mon Sep 17 00:00:00 2001 From: Ilya Kitaev Date: Tue, 23 Aug 2016 16:07:52 +0300 Subject: [PATCH 78/87] Transfer page: validate amount --- components/StandardButton.qml | 11 +++++++++-- main.cpp | 2 +- main.qml | 27 +++++++++++++++++++++------ pages/Transfer.qml | 27 ++++++++++++++++----------- src/libwalletqt/WalletManager.cpp | 16 +++++++++++++--- src/libwalletqt/WalletManager.h | 11 ++++++++--- 6 files changed, 68 insertions(+), 26 deletions(-) diff --git a/components/StandardButton.qml b/components/StandardButton.qml index 208d90d3..5a088fbf 100644 --- a/components/StandardButton.qml +++ b/components/StandardButton.qml @@ -47,7 +47,10 @@ Item { height: parent.height - 1 y: buttonArea.pressed ? 0 : 1 //radius: 4 - color: buttonArea.pressed ? parent.shadowPressedColor : parent.shadowReleasedColor + color: { + parent.enabled ? (buttonArea.pressed ? parent.shadowPressedColor : parent.shadowReleasedColor) + : Qt.lighter(parent.shadowReleasedColor) + } } Rectangle { @@ -55,7 +58,11 @@ Item { anchors.right: parent.right height: parent.height - 1 y: buttonArea.pressed ? 1 : 0 - color: buttonArea.pressed ? parent.pressedColor : parent.releasedColor + color: { + parent.enabled ? (buttonArea.pressed ? parent.pressedColor : parent.releasedColor) + : Qt.lighter(parent.releasedColor) + + } //radius: 4 } diff --git a/main.cpp b/main.cpp index 387c4b13..e356659c 100644 --- a/main.cpp +++ b/main.cpp @@ -114,7 +114,7 @@ int main(int argc, char *argv[]) QObject::connect(eventFilter, SIGNAL(mousePressed(QVariant,QVariant,QVariant)), rootObject, SLOT(mousePressed(QVariant,QVariant,QVariant))); QObject::connect(eventFilter, SIGNAL(mouseReleased(QVariant,QVariant,QVariant)), rootObject, SLOT(mouseReleased(QVariant,QVariant,QVariant))); - WalletManager::instance()->setLogLevel(WalletManager::LogLevel_Max); + WalletManager::instance()->setLogLevel(WalletManager::LogLevel_Silent); return app.exec(); } diff --git a/main.qml b/main.qml index 636945e8..6d57b634 100644 --- a/main.qml +++ b/main.qml @@ -135,7 +135,7 @@ ApplicationWindow { } middlePanel.paymentClicked.connect(handlePayment); - basicPanel.paymentClicked.connect(handlePayment); + // basicPanel.paymentClicked.connect(handlePayment); // wallet already opened with wizard, we just need to initialize it @@ -240,10 +240,25 @@ ApplicationWindow { ", mixins: ", mixinCount, ", priority: ", priority); - var amountxmr = walletManager.amountFromString(amount); + // validate amount; + var amountxmr = walletManager.amountFromString(amount); console.log("integer amount: ", amountxmr); - transaction = wallet.createTransaction(address, paymentId, amountxmr, mixinCount, priority); + if (amountxmr <= 0) { + informationPopup.title = qsTr("Error") + translationManager.emptyString; + informationPopup.text = qsTr("Amount is wrong: expected number from %1 to %2") + .arg(walletManager.displayAmount(0)) + .arg(walletManager.maximumAllowedAmountAsSting()) + + translationManager.emptyString + + informationPopup.icon = StandardIcon.Critical + informationPopup.onCloseCallback = null + informationPopup.open() + return; + } + + // validate address; + transaction = currentWallet.createTransaction(address, paymentId, amountxmr, mixinCount, priority); if (transaction.status !== PendingTransaction.Status_Ok) { console.error("Can't create transaction: ", transaction.errorString); informationPopup.title = qsTr("Error") + translationManager.emptyString; @@ -252,7 +267,7 @@ ApplicationWindow { informationPopup.onCloseCallback = null informationPopup.open(); // deleting transaction object, we don't want memleaks - wallet.disposeTransaction(transaction); + currentWallet.disposeTransaction(transaction); } else { console.log("Transaction created, amount: " + walletManager.displayAmount(transaction.amount) @@ -287,8 +302,8 @@ ApplicationWindow { } informationPopup.onCloseCallback = null informationPopup.open() - wallet.refresh() - wallet.disposeTransaction(transaction) + currentWallet.refresh() + currentWallet.disposeTransaction(transaction) } // blocks UI if wallet can't be opened or no connection to the daemon diff --git a/pages/Transfer.qml b/pages/Transfer.qml index 70fde050..508b32af 100644 --- a/pages/Transfer.qml +++ b/pages/Transfer.qml @@ -32,6 +32,7 @@ import "../components" Rectangle { + id: root signal paymentClicked(string address, string paymentId, double amount, int mixinCount, int priority) @@ -88,6 +89,11 @@ Rectangle { id: amountLine placeholderText: qsTr("Amount...") + translationManager.emptyString width: parent.width - 37 - 17 + validator: DoubleValidator { + bottom: 0.0 + notation: DoubleValidator.StandardNotation + locale: "C" + } } } @@ -170,7 +176,7 @@ Rectangle { textFormat: Text.RichText text: qsTr("\ Address ( Type in or select from Address book )") - + translationManager.emptyString + + translationManager.emptyString onLinkActivated: appWindow.showPageRequest("AddressBook") } @@ -220,7 +226,7 @@ Rectangle { anchors.topMargin: 17 fontSize: 14 text: qsTr("Description ( An optional description that will be saved to the local address book if entered )") - + translationManager.emptyString + + translationManager.emptyString } LineEdit { @@ -245,16 +251,15 @@ Rectangle { shadowPressedColor: "#B32D00" releasedColor: "#FF6C3C" pressedColor: "#FF4304" + enabled : addressLine.text.length > 0 && amountLine.text.length > 0 onClicked: { - // do more smart validation - - if (addressLine.text.length > 0 && amountLine.text.length > 0) { - console.log("paymentClicked") - var priority = priorityModel.get(priorityDropdown.currentIndex).priority - console.log("priority: " + priority) - paymentClicked(addressLine.text, paymentIdLine.text, amountLine.text, scaleValueToMixinCount(privacyLevelItem.fillLevel), - priority) - } + console.log("Transfer: paymentClicked") + var priority = priorityModel.get(priorityDropdown.currentIndex).priority + console.log("priority: " + priority) + console.log("amount: " + amountLine.text) + root.paymentClicked(addressLine.text, paymentIdLine.text, amountLine.text, scaleValueToMixinCount(privacyLevelItem.fillLevel), + priority) } } } + diff --git a/src/libwalletqt/WalletManager.cpp b/src/libwalletqt/WalletManager.cpp index 947ccbfb..a9f332a3 100644 --- a/src/libwalletqt/WalletManager.cpp +++ b/src/libwalletqt/WalletManager.cpp @@ -122,17 +122,27 @@ QString WalletManager::walletLanguage(const QString &locale) return "English"; } -QString WalletManager::displayAmount(quint64 amount) +quint64 WalletManager::maximumAllowedAmount() const +{ + return Bitmonero::Wallet::maximumAllowedAmount(); +} + +QString WalletManager::maximumAllowedAmountAsSting() const +{ + return WalletManager::displayAmount(WalletManager::maximumAllowedAmount()); +} + +QString WalletManager::displayAmount(quint64 amount) const { return QString::fromStdString(Bitmonero::Wallet::displayAmount(amount)); } -quint64 WalletManager::amountFromString(const QString &amount) +quint64 WalletManager::amountFromString(const QString &amount) const { return Bitmonero::Wallet::amountFromString(amount.toStdString()); } -quint64 WalletManager::amountFromDouble(double amount) +quint64 WalletManager::amountFromDouble(double amount) const { return Bitmonero::Wallet::amountFromDouble(amount); } diff --git a/src/libwalletqt/WalletManager.h b/src/libwalletqt/WalletManager.h index 1866b7cd..3629242a 100644 --- a/src/libwalletqt/WalletManager.h +++ b/src/libwalletqt/WalletManager.h @@ -12,6 +12,7 @@ namespace Bitmonero { class WalletManager : public QObject { Q_OBJECT + public: enum LogLevel { LogLevel_Silent = Bitmonero::WalletManagerFactory::LogLevel_Silent, @@ -79,9 +80,13 @@ public: //! since we can't call static method from QML, move it to this class - Q_INVOKABLE QString displayAmount(quint64 amount); - Q_INVOKABLE quint64 amountFromString(const QString &amount); - Q_INVOKABLE quint64 amountFromDouble(double amount); + Q_INVOKABLE QString displayAmount(quint64 amount) const; + Q_INVOKABLE quint64 amountFromString(const QString &amount) const; + Q_INVOKABLE quint64 amountFromDouble(double amount) const; + Q_INVOKABLE quint64 maximumAllowedAmount() const; + + // QML JS engine doesn't support unsigned integers + Q_INVOKABLE QString maximumAllowedAmountAsSting() const; void setLogLevel(int logLevel); From 497a6b8de3bec6bc266246d0e02bdfbc37d64782 Mon Sep 17 00:00:00 2001 From: Ilya Kitaev Date: Wed, 24 Aug 2016 21:31:43 +0300 Subject: [PATCH 79/87] Restored build by using develop bitmonero wallet_merged2 related issue - temporary reverted bitmonero commit that removes wallet_merged2 target --- get_libwallet_api.sh | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/get_libwallet_api.sh b/get_libwallet_api.sh index 53ae9268..bbfdb04c 100755 --- a/get_libwallet_api.sh +++ b/get_libwallet_api.sh @@ -2,7 +2,7 @@ BITMONERO_URL=https://github.com/mbg033/bitmonero.git -BITMONERO_BRANCH=master +BITMONERO_BRANCH=develop # thanks to SO: http://stackoverflow.com/a/20283965/4118915 CPU_CORE_COUNT=$(grep -c ^processor /proc/cpuinfo 2>/dev/null || sysctl -n hw.ncpu) pushd $(pwd) @@ -17,10 +17,15 @@ if [ ! -d $BITMONERO_DIR ]; then git clone --depth=1 $BITMONERO_URL $BITMONERO_DIR --branch $BITMONERO_BRANCH --single-branch else cd $BITMONERO_DIR; + git checkout $BITMONERO_BRANCH git pull; fi +echo "cleaning up existing bitmonero build dir, libs and includes" rm -fr $BITMONERO_DIR/build +rm -fr $BITMONERO_DIR/lib +rm -fr $BITMONERO_DIR/include + mkdir -p $BITMONERO_DIR/build/release pushd $BITMONERO_DIR/build/release From 1cc7d35ab86ddd8baa8475ec1bf4fffc0cd9e004 Mon Sep 17 00:00:00 2001 From: dEBRUYNE-1 Date: Wed, 31 Aug 2016 18:39:44 +0200 Subject: [PATCH 80/87] Fix typo --- wizard/WizardOptions.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wizard/WizardOptions.qml b/wizard/WizardOptions.qml index 7aa3be2f..b35dbee4 100644 --- a/wizard/WizardOptions.qml +++ b/wizard/WizardOptions.qml @@ -138,7 +138,7 @@ Item { font.pixelSize: 16 color: "#4A4949" horizontalAlignment: Text.AlignHCenter - text: qsTr("I want to recover my account
from my 24 work seed") + translationManager.emptyString + text: qsTr("I want to recover my account
from my 25 word seed") + translationManager.emptyString } } } From efba4706ce81507322b66a5a69315c27ae0d0b27 Mon Sep 17 00:00:00 2001 From: dEBRUYNE-1 Date: Wed, 31 Aug 2016 18:44:10 +0200 Subject: [PATCH 81/87] Fix typo --- translations/monero-core_de.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/translations/monero-core_de.ts b/translations/monero-core_de.ts index 24e76b60..a5d31baa 100644 --- a/translations/monero-core_de.ts +++ b/translations/monero-core_de.ts @@ -82,7 +82,7 @@ - Availible Balance: + Available Balance: From cb6bcdf507c6cdacce537e8e9fc50cd1a2169fbd Mon Sep 17 00:00:00 2001 From: dEBRUYNE-1 Date: Wed, 31 Aug 2016 18:44:34 +0200 Subject: [PATCH 82/87] Fix typo --- translations/monero-core_en.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/translations/monero-core_en.ts b/translations/monero-core_en.ts index 6e8d5bcc..88083bef 100644 --- a/translations/monero-core_en.ts +++ b/translations/monero-core_en.ts @@ -82,7 +82,7 @@ - Availible Balance: + Available Balance: From f8f1d28d39f1cb8f0a8b73c7e604e72b309cbcc6 Mon Sep 17 00:00:00 2001 From: dEBRUYNE-1 Date: Wed, 31 Aug 2016 18:44:50 +0200 Subject: [PATCH 83/87] Fix typo --- translations/monero-core_it.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/translations/monero-core_it.ts b/translations/monero-core_it.ts index 403d3859..b9692f7c 100644 --- a/translations/monero-core_it.ts +++ b/translations/monero-core_it.ts @@ -82,7 +82,7 @@ - Availible Balance: + Available Balance: From f66a7651524ec671e74f785b8011b3b92bf676c7 Mon Sep 17 00:00:00 2001 From: dEBRUYNE-1 Date: Wed, 31 Aug 2016 18:45:12 +0200 Subject: [PATCH 84/87] Fix typo --- translations/monero-core_pl.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/translations/monero-core_pl.ts b/translations/monero-core_pl.ts index bb152986..c7c6c0cd 100644 --- a/translations/monero-core_pl.ts +++ b/translations/monero-core_pl.ts @@ -82,7 +82,7 @@ - Availible Balance: + Available Balance: From 93285c233095d8f4648bb02bac49d86b618f9ec0 Mon Sep 17 00:00:00 2001 From: dEBRUYNE-1 Date: Wed, 31 Aug 2016 18:45:35 +0200 Subject: [PATCH 85/87] Fix typo --- translations/monero-core_ru.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/translations/monero-core_ru.ts b/translations/monero-core_ru.ts index 5ca9c499..acce20ed 100644 --- a/translations/monero-core_ru.ts +++ b/translations/monero-core_ru.ts @@ -82,7 +82,7 @@ - Availible Balance: + Available Balance: From 5d17513ef29d4ec4ca6c9b3462311a1ec002436e Mon Sep 17 00:00:00 2001 From: dEBRUYNE-1 Date: Wed, 31 Aug 2016 18:46:58 +0200 Subject: [PATCH 86/87] Fix typo --- translations/monero-core_zh.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/translations/monero-core_zh.ts b/translations/monero-core_zh.ts index 14f60021..9d0645ae 100644 --- a/translations/monero-core_zh.ts +++ b/translations/monero-core_zh.ts @@ -82,7 +82,7 @@ - Availible Balance: + Available Balance: From c519c7efb187be26680588f276cbc6d78814f3d1 Mon Sep 17 00:00:00 2001 From: dEBRUYNE-1 Date: Wed, 31 Aug 2016 18:47:56 +0200 Subject: [PATCH 87/87] Fix typo --- BasicPanel.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/BasicPanel.qml b/BasicPanel.qml index abeb135c..07aeb080 100644 --- a/BasicPanel.qml +++ b/BasicPanel.qml @@ -131,7 +131,7 @@ Rectangle { horizontalAlignment: Text.AlignLeft verticalAlignment: Text.AlignBottom color: "#535353" - text: qsTr("Availible Balance:") + text: qsTr("Available Balance:") } Text {