From 04bc8b699fe39b9432efae8f6d66ccf6efab9fad Mon Sep 17 00:00:00 2001
From: cryptonotefoundation
Date: Wed, 29 Apr 2015 20:03:08 +0300
Subject: [PATCH] Initial commit
---
.gitignore | 3 +
CMakeLists.txt | 201 ++++++++
CryptoNoteWallet.cmake | 4 +
src/CommandLineParser.cpp | 56 +++
src/CommandLineParser.h | 35 ++
src/CryptoNote.cpp | 265 +++++++++++
src/CryptoNote.h | 56 +++
src/CryptoNoteWalletConfig.h.in | 9 +
src/CurrencyAdapter.cpp | 105 +++++
src/CurrencyAdapter.h | 35 ++
src/NodeAdapter.cpp | 216 +++++++++
src/NodeAdapter.h | 67 +++
src/Settings.cpp | 225 +++++++++
src/Settings.h | 57 +++
src/SignalHandler.cpp | 32 ++
src/SignalHandler.h | 31 ++
src/WalletAdapter.cpp | 432 +++++++++++++++++
src/WalletAdapter.h | 103 ++++
src/cryptonotewallet.rc | 1 +
src/gui/AboutDialog.cpp | 24 +
src/gui/AboutDialog.h | 28 ++
src/gui/AddressBookDialog.cpp | 28 ++
src/gui/AddressBookDialog.h | 30 ++
src/gui/AddressBookFrame.cpp | 58 +++
src/gui/AddressBookFrame.h | 33 ++
src/gui/AddressBookModel.cpp | 155 ++++++
src/gui/AddressBookModel.h | 45 ++
src/gui/AnimatedLabel.cpp | 46 ++
src/gui/AnimatedLabel.h | 35 ++
src/gui/ChangePasswordDialog.cpp | 36 ++
src/gui/ChangePasswordDialog.h | 33 ++
src/gui/ExitWidget.cpp | 29 ++
src/gui/ExitWidget.h | 31 ++
src/gui/MainWindow.cpp | 413 ++++++++++++++++
src/gui/MainWindow.h | 87 ++++
src/gui/MainWindow.mm | 30 ++
src/gui/NewAddressDialog.cpp | 28 ++
src/gui/NewAddressDialog.h | 31 ++
src/gui/NewPasswordDialog.cpp | 32 ++
src/gui/NewPasswordDialog.h | 32 ++
src/gui/OverviewFrame.cpp | 95 ++++
src/gui/OverviewFrame.h | 39 ++
src/gui/PasswordDialog.cpp | 28 ++
src/gui/PasswordDialog.h | 30 ++
src/gui/ReceiveFrame.cpp | 36 ++
src/gui/ReceiveFrame.h | 33 ++
src/gui/RecentTransactionsModel.cpp | 37 ++
src/gui/RecentTransactionsModel.h | 25 +
src/gui/SendFrame.cpp | 114 +++++
src/gui/SendFrame.h | 41 ++
src/gui/SortedTransactionsModel.cpp | 45 ++
src/gui/SortedTransactionsModel.h | 29 ++
src/gui/TransactionDetailsDialog.cpp | 46 ++
src/gui/TransactionDetailsDialog.h | 29 ++
src/gui/TransactionFrame.cpp | 68 +++
src/gui/TransactionFrame.h | 36 ++
src/gui/TransactionsFrame.cpp | 62 +++
src/gui/TransactionsFrame.h | 37 ++
src/gui/TransactionsListModel.cpp | 25 +
src/gui/TransactionsListModel.h | 23 +
src/gui/TransactionsModel.cpp | 394 ++++++++++++++++
src/gui/TransactionsModel.h | 67 +++
src/gui/TransferFrame.cpp | 58 +++
src/gui/TransferFrame.h | 38 ++
src/gui/WalletEvents.h | 37 ++
src/gui/ui/aboutdialog.ui | 102 ++++
src/gui/ui/addressbookdialog.ui | 81 ++++
src/gui/ui/addressbookframe.ui | 142 ++++++
src/gui/ui/changepassworddialog.ui | 207 ++++++++
src/gui/ui/exitwidget.ui | 36 ++
src/gui/ui/mainwindow.ui | 628 +++++++++++++++++++++++++
src/gui/ui/newaddressdialog.ui | 133 ++++++
src/gui/ui/newpassworddialog.ui | 180 +++++++
src/gui/ui/overviewframe.ui | 290 ++++++++++++
src/gui/ui/passworddialog.ui | 131 ++++++
src/gui/ui/receiveframe.ui | 91 ++++
src/gui/ui/sendframe.ui | 320 +++++++++++++
src/gui/ui/transactiondetailsdialog.ui | 65 +++
src/gui/ui/transactionframe.ui | 156 ++++++
src/gui/ui/transactionsframe.ui | 125 +++++
src/gui/ui/transferframe.ui | 195 ++++++++
src/icons/add.png | Bin 0 -> 1112 bytes
src/icons/address-book.png | Bin 0 -> 1690 bytes
src/icons/clock1.png | Bin 0 -> 864 bytes
src/icons/clock2.png | Bin 0 -> 863 bytes
src/icons/clock3.png | Bin 0 -> 856 bytes
src/icons/clock4.png | Bin 0 -> 869 bytes
src/icons/clock5.png | Bin 0 -> 858 bytes
src/icons/connected.png | Bin 0 -> 762 bytes
src/icons/disconnected.png | Bin 0 -> 538 bytes
src/icons/editcopy.png | Bin 0 -> 600 bytes
src/icons/editpaste.png | Bin 0 -> 1135 bytes
src/icons/export.png | Bin 0 -> 1931 bytes
src/icons/lock_closed.png | Bin 0 -> 1401 bytes
src/icons/lock_open.png | Bin 0 -> 1359 bytes
src/icons/overview.png | Bin 0 -> 6327 bytes
src/icons/receive.png | Bin 0 -> 1331 bytes
src/icons/remove.png | Bin 0 -> 649 bytes
src/icons/send.png | Bin 0 -> 1345 bytes
src/icons/sync_sprite.png | Bin 0 -> 43485 bytes
src/icons/synced.png | Bin 0 -> 560 bytes
src/icons/transaction.png | Bin 0 -> 211 bytes
src/icons/transactions.png | Bin 0 -> 1343 bytes
src/icons/tx_inout.png | Bin 0 -> 2442 bytes
src/icons/tx_input.png | Bin 0 -> 1114 bytes
src/icons/tx_mined.png | Bin 0 -> 1458 bytes
src/icons/tx_output.png | Bin 0 -> 1107 bytes
src/icons/unconfirmed.png | Bin 0 -> 291 bytes
src/images/clock.gif | Bin 0 -> 44663 bytes
src/images/cryptonote.icns | Bin 0 -> 199156 bytes
src/images/cryptonote.ico | Bin 0 -> 370070 bytes
src/images/cryptonote.png | Bin 0 -> 3961 bytes
src/images/splash.png | Bin 0 -> 7955 bytes
src/main.cpp | 94 ++++
src/miniupnpcstrings.h | 11 +
src/resources.qrc | 36 ++
116 files changed, 7692 insertions(+)
create mode 100644 .gitignore
create mode 100644 CMakeLists.txt
create mode 100644 CryptoNoteWallet.cmake
create mode 100644 src/CommandLineParser.cpp
create mode 100644 src/CommandLineParser.h
create mode 100644 src/CryptoNote.cpp
create mode 100644 src/CryptoNote.h
create mode 100644 src/CryptoNoteWalletConfig.h.in
create mode 100644 src/CurrencyAdapter.cpp
create mode 100644 src/CurrencyAdapter.h
create mode 100644 src/NodeAdapter.cpp
create mode 100644 src/NodeAdapter.h
create mode 100644 src/Settings.cpp
create mode 100644 src/Settings.h
create mode 100644 src/SignalHandler.cpp
create mode 100644 src/SignalHandler.h
create mode 100644 src/WalletAdapter.cpp
create mode 100644 src/WalletAdapter.h
create mode 100644 src/cryptonotewallet.rc
create mode 100644 src/gui/AboutDialog.cpp
create mode 100644 src/gui/AboutDialog.h
create mode 100644 src/gui/AddressBookDialog.cpp
create mode 100644 src/gui/AddressBookDialog.h
create mode 100644 src/gui/AddressBookFrame.cpp
create mode 100644 src/gui/AddressBookFrame.h
create mode 100644 src/gui/AddressBookModel.cpp
create mode 100644 src/gui/AddressBookModel.h
create mode 100644 src/gui/AnimatedLabel.cpp
create mode 100644 src/gui/AnimatedLabel.h
create mode 100644 src/gui/ChangePasswordDialog.cpp
create mode 100644 src/gui/ChangePasswordDialog.h
create mode 100644 src/gui/ExitWidget.cpp
create mode 100644 src/gui/ExitWidget.h
create mode 100644 src/gui/MainWindow.cpp
create mode 100644 src/gui/MainWindow.h
create mode 100644 src/gui/MainWindow.mm
create mode 100644 src/gui/NewAddressDialog.cpp
create mode 100644 src/gui/NewAddressDialog.h
create mode 100644 src/gui/NewPasswordDialog.cpp
create mode 100644 src/gui/NewPasswordDialog.h
create mode 100644 src/gui/OverviewFrame.cpp
create mode 100644 src/gui/OverviewFrame.h
create mode 100644 src/gui/PasswordDialog.cpp
create mode 100644 src/gui/PasswordDialog.h
create mode 100644 src/gui/ReceiveFrame.cpp
create mode 100644 src/gui/ReceiveFrame.h
create mode 100644 src/gui/RecentTransactionsModel.cpp
create mode 100644 src/gui/RecentTransactionsModel.h
create mode 100644 src/gui/SendFrame.cpp
create mode 100644 src/gui/SendFrame.h
create mode 100644 src/gui/SortedTransactionsModel.cpp
create mode 100644 src/gui/SortedTransactionsModel.h
create mode 100644 src/gui/TransactionDetailsDialog.cpp
create mode 100644 src/gui/TransactionDetailsDialog.h
create mode 100644 src/gui/TransactionFrame.cpp
create mode 100644 src/gui/TransactionFrame.h
create mode 100644 src/gui/TransactionsFrame.cpp
create mode 100644 src/gui/TransactionsFrame.h
create mode 100644 src/gui/TransactionsListModel.cpp
create mode 100644 src/gui/TransactionsListModel.h
create mode 100644 src/gui/TransactionsModel.cpp
create mode 100644 src/gui/TransactionsModel.h
create mode 100644 src/gui/TransferFrame.cpp
create mode 100644 src/gui/TransferFrame.h
create mode 100644 src/gui/WalletEvents.h
create mode 100644 src/gui/ui/aboutdialog.ui
create mode 100644 src/gui/ui/addressbookdialog.ui
create mode 100644 src/gui/ui/addressbookframe.ui
create mode 100644 src/gui/ui/changepassworddialog.ui
create mode 100644 src/gui/ui/exitwidget.ui
create mode 100644 src/gui/ui/mainwindow.ui
create mode 100644 src/gui/ui/newaddressdialog.ui
create mode 100644 src/gui/ui/newpassworddialog.ui
create mode 100644 src/gui/ui/overviewframe.ui
create mode 100644 src/gui/ui/passworddialog.ui
create mode 100644 src/gui/ui/receiveframe.ui
create mode 100644 src/gui/ui/sendframe.ui
create mode 100644 src/gui/ui/transactiondetailsdialog.ui
create mode 100644 src/gui/ui/transactionframe.ui
create mode 100644 src/gui/ui/transactionsframe.ui
create mode 100644 src/gui/ui/transferframe.ui
create mode 100644 src/icons/add.png
create mode 100644 src/icons/address-book.png
create mode 100644 src/icons/clock1.png
create mode 100644 src/icons/clock2.png
create mode 100644 src/icons/clock3.png
create mode 100644 src/icons/clock4.png
create mode 100644 src/icons/clock5.png
create mode 100644 src/icons/connected.png
create mode 100644 src/icons/disconnected.png
create mode 100644 src/icons/editcopy.png
create mode 100644 src/icons/editpaste.png
create mode 100644 src/icons/export.png
create mode 100644 src/icons/lock_closed.png
create mode 100644 src/icons/lock_open.png
create mode 100644 src/icons/overview.png
create mode 100644 src/icons/receive.png
create mode 100644 src/icons/remove.png
create mode 100644 src/icons/send.png
create mode 100644 src/icons/sync_sprite.png
create mode 100644 src/icons/synced.png
create mode 100644 src/icons/transaction.png
create mode 100644 src/icons/transactions.png
create mode 100644 src/icons/tx_inout.png
create mode 100644 src/icons/tx_input.png
create mode 100644 src/icons/tx_mined.png
create mode 100644 src/icons/tx_output.png
create mode 100644 src/icons/unconfirmed.png
create mode 100644 src/images/clock.gif
create mode 100644 src/images/cryptonote.icns
create mode 100644 src/images/cryptonote.ico
create mode 100644 src/images/cryptonote.png
create mode 100644 src/images/splash.png
create mode 100644 src/main.cpp
create mode 100644 src/miniupnpcstrings.h
create mode 100644 src/resources.qrc
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..20a3894
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+.DS_Store
+/build
+/tags
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..15636b1
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,201 @@
+cmake_minimum_required(VERSION 2.8)
+
+include(CryptoNoteWallet.cmake)
+
+project(${CN_PROJECT_NAME})
+
+execute_process(COMMAND git log -1 --pretty=format:%h
+WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
+OUTPUT_VARIABLE GIT_REVISION)
+
+set(CRYPTONOTE_LIB cryptonote)
+
+include_directories(${CMAKE_BINARY_DIR}
+ src
+ cryptonote/external
+ cryptonote/include
+ cryptonote/src
+ cryptonote/contrib/epee/include)
+
+find_package(Qt5Gui REQUIRED)
+find_package(Qt5Widgets REQUIRED)
+
+set(Boost_USE_STATIC_LIBS ON)
+if(WIN32)
+ set(Boost_USE_STATIC_RUNTIME OFF)
+else(WIN32)
+ set(Boost_USE_STATIC_RUNTIME ON)
+endif(WIN32)
+
+find_package(Boost 1.55 REQUIRED COMPONENTS date_time filesystem program_options regex serialization system thread chrono)
+if ((${Boost_MAJOR_VERSION} EQUAL 1) AND (${Boost_MINOR_VERSION} EQUAL 54))
+ message(SEND_ERROR "Boost version 1.54 is unsupported, more details are available here http://goo.gl/RrCFmA")
+endif ()
+
+include_directories(${Boost_INCLUDE_DIRS})
+link_directories(${Boost_LIBRARY_DIRS})
+
+set(VERSION "")
+configure_file("cryptonote/src/version.h.in" "version.h")
+configure_file("src/CryptoNoteWalletConfig.h.in" "CryptoNoteWalletConfig.h")
+
+add_definitions(-DGIT_REVISION=\"${GIT_REVISION}\")
+
+set(CMAKE_AUTOMOC ON)
+
+set(CRYPTONOTE_SOURCES
+ cryptonote/contrib/epee/include/misc_log_ex.cpp
+ cryptonote/contrib/epee/include/misc_os_dependent.cpp
+ cryptonote/contrib/epee/include/string_tools.cpp
+ cryptonote/external/miniupnpc/connecthostport.c
+ cryptonote/external/miniupnpc/igd_desc_parse.c
+ cryptonote/external/miniupnpc/minisoap.c
+ cryptonote/external/miniupnpc/miniupnpc.c
+ cryptonote/external/miniupnpc/miniwget.c
+ cryptonote/external/miniupnpc/minixml.c
+ cryptonote/external/miniupnpc/portlistingparse.c
+ cryptonote/external/miniupnpc/receivedata.c
+ cryptonote/external/miniupnpc/upnpcommands.c
+ cryptonote/external/miniupnpc/upnpreplyparse.c
+ cryptonote/src/common/base58.cpp
+ cryptonote/src/common/command_line.cpp
+ cryptonote/src/common/util.cpp
+ cryptonote/src/crypto/blake256.c
+ cryptonote/src/crypto/chacha8.c
+ cryptonote/src/crypto/crypto-ops-data.c
+ cryptonote/src/crypto/crypto-ops.c
+ cryptonote/src/crypto/crypto.cpp
+ cryptonote/src/crypto/groestl.c
+ cryptonote/src/crypto/hash-extra-blake.c
+ cryptonote/src/crypto/hash-extra-groestl.c
+ cryptonote/src/crypto/hash-extra-jh.c
+ cryptonote/src/crypto/hash-extra-skein.c
+ cryptonote/src/crypto/hash.c
+ cryptonote/src/crypto/jh.c
+ cryptonote/src/crypto/keccak.c
+ cryptonote/src/crypto/oaes_lib.c
+ cryptonote/src/crypto/random.c
+ cryptonote/src/crypto/skein.c
+ cryptonote/src/crypto/slow-hash.c
+ cryptonote/src/crypto/slow-hash.cpp
+ cryptonote/src/crypto/tree-hash.c
+ cryptonote/src/cryptonote_core/BlockIndex.cpp
+ cryptonote/src/cryptonote_core/CoreConfig.cpp
+ cryptonote/src/cryptonote_core/Currency.cpp
+ cryptonote/src/cryptonote_core/MinerConfig.cpp
+ cryptonote/src/cryptonote_core/Transaction.cpp
+ cryptonote/src/cryptonote_core/account.cpp
+ cryptonote/src/cryptonote_core/blockchain_storage.cpp
+ cryptonote/src/cryptonote_core/checkpoints.cpp
+ cryptonote/src/cryptonote_core/cryptonote_basic_impl.cpp
+ cryptonote/src/cryptonote_core/cryptonote_core.cpp
+ cryptonote/src/cryptonote_core/cryptonote_format_utils.cpp
+ cryptonote/src/cryptonote_core/cryptonote_serialization.cpp
+ cryptonote/src/cryptonote_core/difficulty.cpp
+ cryptonote/src/cryptonote_core/miner.cpp
+ cryptonote/src/cryptonote_core/tx_pool.cpp
+ cryptonote/src/inprocess_node/InProcessNode.cpp
+ cryptonote/src/inprocess_node/InProcessNodeErrors.cpp
+ cryptonote/src/node_rpc_proxy/NodeErrors.cpp
+ cryptonote/src/node_rpc_proxy/NodeRpcProxy.cpp
+ cryptonote/src/p2p/NetNodeConfig.cpp
+ cryptonote/src/serialization/BinaryInputStreamSerializer.cpp
+ cryptonote/src/serialization/BinaryOutputStreamSerializer.cpp
+ cryptonote/src/transfers/BlockchainSynchronizer.cpp
+ cryptonote/src/transfers/SynchronizationState.cpp
+ cryptonote/src/transfers/TransfersConsumer.cpp
+ cryptonote/src/transfers/TransfersContainer.cpp
+ cryptonote/src/transfers/TransfersSubscription.cpp
+ cryptonote/src/transfers/TransfersSynchronizer.cpp
+ cryptonote/src/wallet/KeysStorage.cpp
+ cryptonote/src/wallet/Wallet.cpp
+ cryptonote/src/wallet/WalletAsyncContextCounter.cpp
+ cryptonote/src/wallet/WalletErrors.cpp
+ cryptonote/src/wallet/WalletSerializer.cpp
+ cryptonote/src/wallet/WalletSerialization.cpp
+ cryptonote/src/wallet/WalletTransactionSender.cpp
+ cryptonote/src/wallet/WalletUnconfirmedTransactions.cpp
+ cryptonote/src/wallet/WalletUserTransactionsCache.cpp
+ cryptonote/src/wallet/LegacyKeysImporter.cpp
+)
+
+file(GLOB_RECURSE SOURCES src/*.cpp)
+file(GLOB_RECURSE HEADERS src/*.h)
+file(GLOB_RECURSE FORMS src/gui/ui/*.ui)
+
+set(QRC src/resources.qrc)
+
+qt5_wrap_ui(UIS ${FORMS})
+qt5_add_resources(RCC ${QRC})
+
+
+if (WIN32)
+ if (NOT MSVC)
+ message(FATAL_ERROR "Only MSVC is supported on this platform")
+ endif ()
+ add_definitions(/D_CRT_SECURE_NO_WARNINGS /D_WIN32_WINNT=0x0600 /DSTATICLIB)
+ include_directories(cryptonote/src/platform/msc)
+
+ set(PLATFORM_DIR Windows)
+ set(BUILD_PLATFORM WIN32)
+ set(BUILD_RESOURCES src/cryptonotewallet.rc)
+ set(QTMAIN Qt5::WinMain)
+
+elseif (UNIX)
+ set(CRYPTONOTE_SOURCES ${CRYPTONOTE_SOURCES} cryptonote/external/miniupnpc/minissdpc.c)
+ if (APPLE)
+ enable_language(ASM)
+ file(GLOB_RECURSE OBJC_SOURCES src/*.mm)
+ set(SOURCES ${SOURCES} ${OBJC_SOURCES})
+ set(PLATFORM_DIR OSX)
+ set(MACOSX_BUNDLE_INFO_STRING "Cryptonote GUI wallet")
+ set(MACOSX_BUNDLE_LONG_VERSION_STRING "${VERSION_VERSION}.${VERSION_MINOR}.${VERSION_PATCH}")
+ set(MACOSX_BUNDLE_BUNDLE_NAME CryptonoteWallet)
+ set(MACOSX_BUNDLE_SHORT_VERSION_STRING "${VERSION_VERSION}.${VERSION_MINOR}.${VERSION_PATCH}")
+ set(MACOSX_BUNDLE_BUNDLE_VERSION "$${VERSION_VERSION}.${VERSION_MINOR}.${VERSION_PATCH}")
+
+ find_package(Qt5PrintSupport REQUIRED)
+
+ include_directories(/usr/include/malloc)
+
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -maes -std=c++11 -stdlib=libc++")
+ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -std=c11 -maes -D_DARWIN_C_SOURCE")
+ set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -framework Cocoa -framework OpenGL -framework CoreFoundation -framework Carbon -framework IOKit -L/usr/lib")
+
+ set(MACOSX_BUNDLE_ICON_FILE cryptonote.icns)
+ set(APPLICATION_ICON src/images/cryptonote.icns)
+ set_source_files_properties(${APPLICATION_ICON} PROPERTIES MACOSX_PACKAGE_LOCATION "Resources")
+
+ set(BUILD_PLATFORM MACOSX_BUNDLE)
+ set(BUILD_RESOURCES ${APPLICATION_ICON})
+
+ GET_TARGET_PROPERTY(QT_LIB_DIR "${Qt5Widgets_LIBRARIES}" LOCATION)
+ GET_FILENAME_COMPONENT(QT_LIB_DIR "${QT_LIB_DIR}" PATH)
+ else(APPLE)
+ set(PLATFORM_DIR Linux)
+ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -maes -std=c++11")
+ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -maes -std=c11")
+ endif (APPLE)
+endif ()
+
+include_directories(cryptonote/src/Platform/${PLATFORM_DIR})
+file(GLOB PLATFORM_SOURCES cryptonote/src/Platform/${PLATFORM_DIR}/System/*)
+set(CRYPTONOTE_SOURCES ${CRYPTONOTE_SOURCES} ${PLATFORM_SOURCES})
+
+add_library(${CRYPTONOTE_LIB} STATIC ${CRYPTONOTE_SOURCES})
+set_target_properties(${CRYPTONOTE_LIB} PROPERTIES COMPILE_DEFINITIONS _GNU_SOURCE)
+target_link_libraries(${CRYPTONOTE_LIB} ${Boost_LIBRARIES})
+
+add_executable(${PROJECT_NAME} ${BUILD_PLATFORM} ${BUILD_RESOURCES} ${SOURCES} ${HEADERS} ${UIS} ${RCC})
+set_target_properties(${PROJECT_NAME} PROPERTIES COMPILE_DEFINITIONS _GNU_SOURCE)
+target_link_libraries(${PROJECT_NAME} ${Boost_LIBRARIES} ${QTMAIN} ${CRYPTONOTE_LIB})
+
+if (APPLE)
+ qt5_use_modules(${PROJECT_NAME} PrintSupport)
+elseif (UNIX)
+ target_link_libraries(${PROJECT_NAME} -lpthread)
+elseif (WIN32)
+ target_link_libraries(${PROJECT_NAME} Imm32 Iphlpapi Winmm)
+endif (APPLE)
+
+qt5_use_modules(${PROJECT_NAME} Widgets Gui)
diff --git a/CryptoNoteWallet.cmake b/CryptoNoteWallet.cmake
new file mode 100644
index 0000000..4af8821
--- /dev/null
+++ b/CryptoNoteWallet.cmake
@@ -0,0 +1,4 @@
+
+set(CN_PROJECT_NAME "")
+set(CN_CURRENCY_DISPLAY_NAME "")
+set(CN_CURRENCY_TICKER "")
diff --git a/src/CommandLineParser.cpp b/src/CommandLineParser.cpp
new file mode 100644
index 0000000..817e314
--- /dev/null
+++ b/src/CommandLineParser.cpp
@@ -0,0 +1,56 @@
+// Copyright (c) 2011-2015 The Cryptonote developers
+// Distributed under the MIT/X11 software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+
+#include
+
+#include
+
+#include "CommandLineParser.h"
+#include "CurrencyAdapter.h"
+
+namespace WalletGui {
+
+CommandLineParser::CommandLineParser() : QObject(), m_parser(), m_help_option(m_parser.addHelpOption()),
+ m_version_option(m_parser.addVersionOption()),
+ m_data_dir_option("data-dir", tr("Specify data directory"), tr("directory"), QString::fromStdString(tools::get_default_data_dir())) {
+ m_parser.setApplicationDescription(QString(tr("%1 wallet")).arg(CurrencyAdapter::instance().getCurrencyDisplayName()));
+ m_parser.addHelpOption();
+ m_parser.addVersionOption();
+ m_parser.addOption(m_data_dir_option);
+}
+
+CommandLineParser::~CommandLineParser() {
+}
+
+bool CommandLineParser::process() {
+#ifdef Q_OS_WIN
+ return m_parser.parse(QCoreApplication::arguments());
+#else
+ m_parser.process(*QCoreApplication::instance());
+ return true;
+#endif
+}
+
+bool CommandLineParser::hasHelpOption() const {
+ return m_parser.isSet(m_help_option);
+}
+
+bool CommandLineParser::hasVersionOption() const {
+ return m_parser.isSet(m_version_option);
+}
+
+QString CommandLineParser::errorText() const {
+ return m_parser.errorText();
+}
+
+QString CommandLineParser::helpText() const {
+ return m_parser.helpText();
+}
+
+QString CommandLineParser::getDataDir() const {
+ return m_parser.value(m_data_dir_option);
+}
+
+}
diff --git a/src/CommandLineParser.h b/src/CommandLineParser.h
new file mode 100644
index 0000000..e6930e2
--- /dev/null
+++ b/src/CommandLineParser.h
@@ -0,0 +1,35 @@
+// Copyright (c) 2011-2015 The Cryptonote developers
+// Distributed under the MIT/X11 software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+
+#pragma once
+
+#include
+#include
+
+namespace WalletGui {
+
+class CommandLineParser : public QObject {
+ Q_OBJECT
+ Q_DISABLE_COPY(CommandLineParser)
+
+public:
+ CommandLineParser();
+ ~CommandLineParser();
+
+ bool process();
+
+ bool hasHelpOption() const;
+ bool hasVersionOption() const;
+ QString errorText() const;
+ QString helpText() const;
+ QString getDataDir() const;
+private:
+ QCommandLineParser m_parser;
+ QCommandLineOption m_help_option;
+ QCommandLineOption m_version_option;
+ QCommandLineOption m_data_dir_option;
+};
+
+}
diff --git a/src/CryptoNote.cpp b/src/CryptoNote.cpp
new file mode 100644
index 0000000..440fb7a
--- /dev/null
+++ b/src/CryptoNote.cpp
@@ -0,0 +1,265 @@
+// Copyright (c) 2011-2015 The Cryptonote developers
+// Distributed under the MIT/X11 software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+
+#include "CryptoNote.h"
+#include "cryptonote_core/cryptonote_basic_impl.h"
+#include "cryptonote_core/cryptonote_format_utils.h"
+#include "cryptonote_core/Currency.h"
+#include "node_rpc_proxy/NodeRpcProxy.h"
+#include "cryptonote_core/CoreConfig.h"
+#include "cryptonote_core/cryptonote_core.h"
+#include "cryptonote_protocol/cryptonote_protocol_handler.h"
+#include "inprocess_node/InProcessNode.h"
+#include "p2p/net_node.h"
+#include "wallet/Wallet.h"
+
+namespace WalletGui {
+
+namespace {
+
+bool parsePaymentId(const std::string& payment_id_str, crypto::hash& payment_id) {
+ cryptonote::blobdata payment_id_data;
+ if (!epee::string_tools::parse_hexstr_to_binbuff(payment_id_str, payment_id_data)) {
+ return false;
+ }
+
+ if (sizeof(crypto::hash) != payment_id_data.size()) {
+ return false;
+ }
+
+ payment_id = *reinterpret_cast(payment_id_data.data());
+ return true;
+}
+
+std::string convertPaymentId(const std::string& paymentIdString) {
+ if (paymentIdString.empty()) {
+ return "";
+ }
+
+ crypto::hash paymentId;
+ if (!parsePaymentId(paymentIdString, paymentId)) {
+ std::stringstream errorStr;
+ errorStr << "Payment id has invalid format: \"" + paymentIdString + "\", expected 64-character string";
+ throw std::runtime_error(errorStr.str());
+ }
+
+ std::vector extra;
+ std::string extra_nonce;
+ cryptonote::set_payment_id_to_tx_extra_nonce(extra_nonce, paymentId);
+ if (!cryptonote::add_extra_nonce_to_tx_extra(extra, extra_nonce)) {
+ std::stringstream errorStr;
+ errorStr << "Something went wrong with payment_id. Please check its format: \"" + paymentIdString + "\", expected 64-character string";
+ throw std::runtime_error(errorStr.str());
+ }
+
+ return std::string(extra.begin(), extra.end());
+}
+
+std::string extractPaymentId(const std::string& extra) {
+ std::vector extraFields;
+ std::vector extraVector;
+ std::copy(extra.begin(), extra.end(), std::back_inserter(extraVector));
+
+ if (!cryptonote::parse_tx_extra(extraVector, extraFields)) {
+ throw std::runtime_error("Can't parse extra");
+ }
+
+ std::string result;
+ cryptonote::tx_extra_nonce extra_nonce;
+ if (cryptonote::find_tx_extra_field_by_type(extraFields, extra_nonce)) {
+ crypto::hash paymentIdHash;
+ if (cryptonote::get_payment_id_from_tx_extra_nonce(extra_nonce.nonce, paymentIdHash)) {
+ unsigned char* buff = reinterpret_cast(&paymentIdHash);
+ for (size_t i = 0; i < sizeof(paymentIdHash); ++i) {
+ result.push_back("0123456789ABCDEF"[buff[i] >> 4]);
+ result.push_back("0123456789ABCDEF"[buff[i] & 15]);
+ }
+ }
+ }
+
+ return result;
+}
+
+
+}
+
+Node::~Node() {
+}
+
+class RpcNode : CryptoNote::INodeObserver, public Node {
+public:
+ RpcNode(const cryptonote::Currency& currency, INodeCallback& callback, const std::string& nodeHost, unsigned short nodePort) :
+ m_callback(callback),
+ m_currency(currency),
+ m_node(nodeHost, nodePort) {
+ m_node.addObserver(this);
+ }
+
+ ~RpcNode() override {
+ }
+
+ void init(const std::function& callback) override {
+ m_node.init(callback);
+ }
+
+ void deinit() override {
+ }
+
+ std::string convertPaymentId(const std::string& paymentIdString) override {
+ return WalletGui::convertPaymentId(paymentIdString);
+ }
+
+ std::string extractPaymentId(const std::string& extra) override {
+ return WalletGui::extractPaymentId(extra);
+ }
+
+ uint64_t getLastKnownBlockHeight() const override {
+ return m_node.getLastKnownBlockHeight();
+ }
+
+ uint64_t getLastLocalBlockHeight() const override {
+ return m_node.getLastLocalBlockHeight();
+ }
+
+ uint64_t getLastLocalBlockTimestamp() const override {
+ return m_node.getLastLocalBlockTimestamp();
+ }
+
+ uint64_t getPeerCount() const override {
+ return m_node.getPeerCount();
+ }
+
+ CryptoNote::IWallet* createWallet() override {
+ return new CryptoNote::Wallet(m_currency, m_node);
+ }
+
+private:
+ INodeCallback& m_callback;
+ const cryptonote::Currency& m_currency;
+ cryptonote::NodeRpcProxy m_node;
+
+ void peerCountUpdated(size_t count) {
+ m_callback.peerCountUpdated(*this, count);
+ }
+
+ void localBlockchainUpdated(uint64_t height) {
+ m_callback.localBlockchainUpdated(*this, height);
+ }
+
+ void lastKnownBlockHeightUpdated(uint64_t height) {
+ m_callback.lastKnownBlockHeightUpdated(*this, height);
+ }
+};
+
+class InprocessNode : CryptoNote::INodeObserver, public Node {
+public:
+ InprocessNode(const cryptonote::Currency& currency, INodeCallback& callback, const cryptonote::CoreConfig& coreConfig) :
+ m_callback(callback),
+ m_currency(currency),
+ m_coreConfig(coreConfig),
+ m_core(m_currency, nullptr),
+ m_protocolHandler(m_core, nullptr),
+ m_nodeServer(m_protocolHandler),
+ m_node(m_core, m_protocolHandler) {
+
+ m_core.set_cryptonote_protocol(&m_protocolHandler);
+ m_protocolHandler.set_p2p_endpoint(&m_nodeServer);
+ cryptonote::checkpoints checkpoints;
+ for (const cryptonote::CheckpointData& checkpoint : cryptonote::CHECKPOINTS) {
+ checkpoints.add_checkpoint(checkpoint.height, checkpoint.blockId);
+ }
+
+ m_core.set_checkpoints(std::move(checkpoints));
+ }
+
+ ~InprocessNode() override {
+
+ }
+
+ void init(const std::function& callback) override {
+ if (!m_core.init(m_coreConfig, cryptonote::MinerConfig(), true)) {
+ callback(make_error_code(cryptonote::error::NOT_INITIALIZED));
+ return;
+ }
+ if (!m_nodeServer.init(nodetool::NetNodeConfig(), false)) {
+ m_core.deinit();
+ callback(make_error_code(cryptonote::error::NOT_INITIALIZED));
+ return;
+ }
+ m_node.init([this, callback](std::error_code ec) {
+ m_node.addObserver(this);
+ callback(ec);
+ });
+ m_nodeServer.run();
+
+ m_nodeServer.deinit();
+ m_core.deinit();
+ m_node.shutdown();
+ }
+
+ void deinit() override {
+ m_nodeServer.send_stop_signal();
+ }
+
+ std::string convertPaymentId(const std::string& paymentIdString) override {
+ return WalletGui::convertPaymentId(paymentIdString);
+ }
+
+ std::string extractPaymentId(const std::string& extra) override {
+ return WalletGui::extractPaymentId(extra);
+ }
+
+ uint64_t getLastKnownBlockHeight() const override {
+ return m_node.getLastKnownBlockHeight();
+ }
+
+ uint64_t getLastLocalBlockHeight() const override {
+ return m_node.getLastLocalBlockHeight();
+ }
+
+ uint64_t getLastLocalBlockTimestamp() const override {
+ return m_node.getLastLocalBlockTimestamp();
+ }
+
+ uint64_t getPeerCount() const override {
+ return m_node.getPeerCount();
+ }
+
+ CryptoNote::IWallet* createWallet() override {
+ return new CryptoNote::Wallet(m_currency, m_node);
+ }
+
+private:
+ INodeCallback& m_callback;
+ const cryptonote::Currency& m_currency;
+ cryptonote::CoreConfig m_coreConfig;
+ cryptonote::core m_core;
+ cryptonote::t_cryptonote_protocol_handler m_protocolHandler;
+ nodetool::node_server> m_nodeServer;
+ CryptoNote::InProcessNode m_node;
+ std::future m_nodeServerFuture;
+
+ void peerCountUpdated(size_t count) {
+ m_callback.peerCountUpdated(*this, count);
+ }
+
+ void localBlockchainUpdated(uint64_t height) {
+ m_callback.localBlockchainUpdated(*this, height);
+ }
+
+ void lastKnownBlockHeightUpdated(uint64_t height) {
+ m_callback.lastKnownBlockHeightUpdated(*this, height);
+ }
+};
+
+Node* createRpcNode(const cryptonote::Currency& currency, INodeCallback& callback, const std::string& nodeHost, unsigned short nodePort) {
+ return new RpcNode(currency, callback, nodeHost, nodePort);
+}
+
+Node* createInprocessNode(const cryptonote::Currency& currency, INodeCallback& callback, const cryptonote::CoreConfig& coreConfig) {
+ return new InprocessNode(currency, callback, coreConfig);
+}
+
+}
diff --git a/src/CryptoNote.h b/src/CryptoNote.h
new file mode 100644
index 0000000..ee3acdc
--- /dev/null
+++ b/src/CryptoNote.h
@@ -0,0 +1,56 @@
+// Copyright (c) 2011-2015 The Cryptonote developers
+// Distributed under the MIT/X11 software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+
+#pragma once
+
+#include
+#include
+#include
+#include
+
+#include
+
+namespace cryptonote {
+
+class Currency;
+
+}
+
+namespace CryptoNote {
+
+class INode;
+class IWallet;
+
+}
+
+namespace WalletGui {
+
+class Node {
+public:
+ virtual ~Node() = 0;
+ virtual void init(const std::function& callback) = 0;
+ virtual void deinit() = 0;
+
+ virtual std::string convertPaymentId(const std::string& paymentIdString) = 0;
+ virtual std::string extractPaymentId(const std::string& extra) = 0;
+ virtual uint64_t getLastKnownBlockHeight() const = 0;
+ virtual uint64_t getLastLocalBlockHeight() const = 0;
+ virtual uint64_t getLastLocalBlockTimestamp() const = 0;
+ virtual uint64_t getPeerCount() const = 0;
+
+ virtual CryptoNote::IWallet* createWallet() = 0;
+};
+
+class INodeCallback {
+public:
+ virtual void peerCountUpdated(Node& node, size_t count) = 0;
+ virtual void localBlockchainUpdated(Node& node, uint64_t height) = 0;
+ virtual void lastKnownBlockHeightUpdated(Node& node, uint64_t height) = 0;
+};
+
+Node* createRpcNode(const cryptonote::Currency& currency, INodeCallback& callback, const std::string& nodeHost, unsigned short nodePort);
+Node* createInprocessNode(const cryptonote::Currency& currency, INodeCallback& callback, const cryptonote::CoreConfig& coreConfig);
+
+}
diff --git a/src/CryptoNoteWalletConfig.h.in b/src/CryptoNoteWalletConfig.h.in
new file mode 100644
index 0000000..6e63995
--- /dev/null
+++ b/src/CryptoNoteWalletConfig.h.in
@@ -0,0 +1,9 @@
+
+#pragma once
+
+namespace WalletGui {
+
+const char WALLET_CURRENCY_DISPLAY_NAME[] = "@CN_CURRENCY_DISPLAY_NAME@";
+const char WALLET_CURRENCY_TICKER[] = "@CN_CURRENCY_TICKER@";
+
+}
diff --git a/src/CurrencyAdapter.cpp b/src/CurrencyAdapter.cpp
new file mode 100644
index 0000000..26d0514
--- /dev/null
+++ b/src/CurrencyAdapter.cpp
@@ -0,0 +1,105 @@
+// Copyright (c) 2011-2015 The Cryptonote developers
+// Distributed under the MIT/X11 software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+
+#include "CurrencyAdapter.h"
+#include "CryptoNoteWalletConfig.h"
+
+namespace WalletGui {
+
+CurrencyAdapter& CurrencyAdapter::instance() {
+ static CurrencyAdapter inst;
+ return inst;
+}
+
+CurrencyAdapter::CurrencyAdapter() : m_currency(cryptonote::CurrencyBuilder().currency()) {
+}
+
+CurrencyAdapter::~CurrencyAdapter() {
+}
+
+const cryptonote::Currency& CurrencyAdapter::getCurrency() {
+ return m_currency;
+}
+
+quintptr CurrencyAdapter::getNumberOfDecimalPlaces() const {
+ return m_currency.numberOfDecimalPlaces();
+}
+
+QString CurrencyAdapter::getCurrencyDisplayName() const {
+ return WALLET_CURRENCY_DISPLAY_NAME;
+}
+
+QString CurrencyAdapter::getCurrencyName() const {
+ return cryptonote::CRYPTONOTE_NAME;
+}
+
+QString CurrencyAdapter::getCurrencyTicker() const {
+ return WALLET_CURRENCY_TICKER;
+}
+
+QString CurrencyAdapter::formatAmount(quint64 _amount) const {
+ QString result = QString::number(_amount);
+ if (result.length() < getNumberOfDecimalPlaces() + 1) {
+ result = result.rightJustified(getNumberOfDecimalPlaces() + 1, '0');
+ }
+
+ quint32 dot_pos = result.length() - getNumberOfDecimalPlaces();
+ for (quint32 pos = result.length() - 1; pos > dot_pos + 1; --pos) {
+ if (result[pos] == '0') {
+ result.remove(pos, 1);
+ } else {
+ break;
+ }
+ }
+
+ result.insert(dot_pos, ".");
+ for (qint32 pos = dot_pos - 3; pos > 0; pos -= 3) {
+ if (result[pos - 1].isDigit()) {
+ result.insert(pos, ',');
+ }
+ }
+
+ return result;
+}
+
+quint64 CurrencyAdapter::parseAmount(const QString& _amountString) const {
+ QString amountString = _amountString.trimmed();
+ amountString.remove(',');
+
+ int pointIndex = amountString.indexOf('.');
+ int fractionSize;
+ if (pointIndex != -1) {
+ fractionSize = amountString.length() - pointIndex - 1;
+ while (getNumberOfDecimalPlaces() < fractionSize && amountString.right(1) == "0") {
+ amountString.remove(amountString.length() - 1, 1);
+ --fractionSize;
+ }
+
+ if (getNumberOfDecimalPlaces() < fractionSize) {
+ return 0;
+ }
+
+ amountString.remove(pointIndex, 1);
+ } else {
+ fractionSize = 0;
+ }
+
+ if (amountString.isEmpty()) {
+ return 0;
+ }
+
+ for (qint32 i = 0; i < getNumberOfDecimalPlaces() - fractionSize; ++i) {
+ amountString.append('0');
+ }
+
+ return amountString.toULongLong();
+}
+
+bool CurrencyAdapter::validateAddress(const QString& _address) const {
+ cryptonote::AccountPublicAddress internalAddress;
+ return m_currency.parseAccountAddressString(_address.toStdString(), internalAddress);
+}
+
+}
diff --git a/src/CurrencyAdapter.h b/src/CurrencyAdapter.h
new file mode 100644
index 0000000..3f9a720
--- /dev/null
+++ b/src/CurrencyAdapter.h
@@ -0,0 +1,35 @@
+// Copyright (c) 2011-2015 The Cryptonote developers
+// Distributed under the MIT/X11 software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+
+#pragma once
+
+#include
+
+#include "cryptonote_core/Currency.h"
+
+namespace WalletGui {
+
+class CurrencyAdapter {
+
+public:
+ static CurrencyAdapter& instance();
+
+ const cryptonote::Currency& getCurrency();
+ QString getCurrencyDisplayName() const;
+ QString getCurrencyName() const;
+ QString getCurrencyTicker() const;
+ quintptr getNumberOfDecimalPlaces() const;
+ QString formatAmount(quint64 _amount) const;
+ quint64 parseAmount(const QString& _amountString) const;
+ bool validateAddress(const QString& _address) const;
+
+private:
+ cryptonote::Currency m_currency;
+
+ CurrencyAdapter();
+ ~CurrencyAdapter();
+};
+
+}
diff --git a/src/NodeAdapter.cpp b/src/NodeAdapter.cpp
new file mode 100644
index 0000000..1f14ab8
--- /dev/null
+++ b/src/NodeAdapter.cpp
@@ -0,0 +1,216 @@
+// Copyright (c) 2011-2015 The Cryptonote developers
+// Distributed under the MIT/X11 software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+#include "CurrencyAdapter.h"
+#include "NodeAdapter.h"
+#include "Settings.h"
+
+namespace WalletGui {
+
+class InProcessNodeInitializer : public QObject {
+ Q_OBJECT
+ Q_DISABLE_COPY(InProcessNodeInitializer)
+
+Q_SIGNALS:
+ void nodeInitCompletedSignal();
+ void nodeInitFailedSignal(int _errorCode);
+ void nodeDeinitCompletedSignal();
+
+public:
+ InProcessNodeInitializer(QObject* _parent = nullptr) {
+ }
+
+ ~InProcessNodeInitializer() {
+ }
+
+ void start(Node** _node, const cryptonote::Currency* currency, INodeCallback* _callback, const cryptonote::CoreConfig& _coreConfig) {
+ (*_node) = createInprocessNode(*currency, *_callback, _coreConfig);
+ try {
+ (*_node)->init([this](std::error_code _err) {
+ if (_err) {
+ Q_EMIT nodeInitFailedSignal(_err.value());
+ QCoreApplication::processEvents();
+ return;
+ }
+
+ Q_EMIT nodeInitCompletedSignal();
+ QCoreApplication::processEvents();
+ });
+ } catch (std::runtime_error& err) {
+ Q_EMIT nodeInitFailedSignal(cryptonote::error::INTERNAL_WALLET_ERROR);
+ QCoreApplication::processEvents();
+ return;
+ }
+
+ delete *_node;
+ *_node = nullptr;
+ Q_EMIT nodeDeinitCompletedSignal();
+ }
+
+ void stop(Node** _node) {
+ Q_CHECK_PTR(*_node);
+ (*_node)->deinit();
+ }
+};
+
+NodeAdapter& NodeAdapter::instance() {
+ static NodeAdapter inst;
+ return inst;
+}
+
+NodeAdapter::NodeAdapter() : QObject(), m_node(nullptr), m_nodeInitializerThread(), m_nodeInitializer(new InProcessNodeInitializer) {
+ m_nodeInitializer->moveToThread(&m_nodeInitializerThread);
+
+ qRegisterMetaType("cryptonote::CoreConfig");
+
+ connect(m_nodeInitializer, &InProcessNodeInitializer::nodeInitCompletedSignal, this, &NodeAdapter::nodeInitCompletedSignal, Qt::QueuedConnection);
+ connect(this, &NodeAdapter::initNodeSignal, m_nodeInitializer, &InProcessNodeInitializer::start, Qt::QueuedConnection);
+ connect(this, &NodeAdapter::deinitNodeSignal, m_nodeInitializer, &InProcessNodeInitializer::stop, Qt::QueuedConnection);
+
+ QString logFileName = QCoreApplication::applicationName() + ".log";
+ epee::log_space::log_singletone::add_logger(LOGGER_FILE, logFileName.toLocal8Bit(),
+ Settings::instance().getDataDir().absolutePath().toLocal8Bit());
+}
+
+NodeAdapter::~NodeAdapter() {
+}
+
+quintptr NodeAdapter::getPeerCount() const {
+ Q_ASSERT(m_node != nullptr);
+ return m_node->getPeerCount();
+}
+
+std::string NodeAdapter::convertPaymentId(const QString& _paymentIdString) const {
+ Q_CHECK_PTR(m_node);
+ try {
+ return m_node->convertPaymentId(_paymentIdString.toStdString());
+ } catch (std::runtime_error& err) {
+ }
+ return std::string();
+}
+
+QString NodeAdapter::extractPaymentId(const std::string& _extra) const {
+ Q_CHECK_PTR(m_node);
+ return QString::fromStdString(m_node->extractPaymentId(_extra));
+}
+
+CryptoNote::IWallet* NodeAdapter::createWallet() const {
+ Q_CHECK_PTR(m_node);
+ return m_node->createWallet();
+}
+
+bool NodeAdapter::init() {
+ Q_ASSERT(m_node == nullptr);
+ // TODO Insert the right URL for the local daemon
+ QUrl localNodeUrl = QUrl::fromUserInput("");
+
+ m_node = createRpcNode(CurrencyAdapter::instance().getCurrency(), *this, localNodeUrl.host().toStdString(), localNodeUrl.port());
+
+ QTimer initTimer;
+ initTimer.setInterval(3000);
+ initTimer.setSingleShot(true);
+ initTimer.start();
+ m_node->init([this](std::error_code _err) {
+ Q_UNUSED(_err);
+ });
+ QEventLoop waitLoop;
+ connect(&initTimer, &QTimer::timeout, &waitLoop, &QEventLoop::quit);
+ connect(this, &NodeAdapter::peerCountUpdatedSignal, &waitLoop, &QEventLoop::quit);
+ connect(this, &NodeAdapter::localBlockchainUpdatedSignal, &waitLoop, &QEventLoop::quit);
+ waitLoop.exec();
+ if (initTimer.isActive()) {
+ initTimer.stop();
+ Q_EMIT nodeInitCompletedSignal();
+ return true;
+ }
+
+ delete m_node;
+ m_node = nullptr;
+ return initInProcessNode();
+}
+
+quint64 NodeAdapter::getLastKnownBlockHeight() const {
+ Q_CHECK_PTR(m_node);
+ return m_node->getLastKnownBlockHeight();
+}
+
+quint64 NodeAdapter::getLastLocalBlockHeight() const {
+ Q_CHECK_PTR(m_node);
+ return m_node->getLastLocalBlockHeight();
+}
+
+QDateTime NodeAdapter::getLastLocalBlockTimestamp() const {
+ Q_CHECK_PTR(m_node);
+ return QDateTime::fromTime_t(m_node->getLastLocalBlockTimestamp(), Qt::UTC);
+}
+
+void NodeAdapter::peerCountUpdated(Node& _node, size_t _count) {
+ Q_UNUSED(_node);
+ Q_EMIT peerCountUpdatedSignal(_count);
+}
+
+void NodeAdapter::localBlockchainUpdated(Node& _node, uint64_t _height) {
+ Q_UNUSED(_node);
+ Q_EMIT localBlockchainUpdatedSignal(_height);
+}
+
+void NodeAdapter::lastKnownBlockHeightUpdated(Node& _node, uint64_t _height) {
+ Q_UNUSED(_node);
+ Q_EMIT lastKnownBlockHeightUpdatedSignal(_height);
+}
+
+bool NodeAdapter::initInProcessNode() {
+ Q_ASSERT(m_node == nullptr);
+ m_nodeInitializerThread.start();
+ cryptonote::CoreConfig coreConfig = makeCoreConfig();
+ Q_EMIT initNodeSignal(&m_node, &CurrencyAdapter::instance().getCurrency(), this, coreConfig);
+ QEventLoop waitLoop;
+ connect(m_nodeInitializer, &InProcessNodeInitializer::nodeInitCompletedSignal, &waitLoop, &QEventLoop::quit);
+ connect(m_nodeInitializer, &InProcessNodeInitializer::nodeInitFailedSignal, &waitLoop, &QEventLoop::exit);
+ if (waitLoop.exec() != 0) {
+ return false;
+ }
+
+ Q_EMIT localBlockchainUpdatedSignal(getLastLocalBlockHeight());
+ Q_EMIT lastKnownBlockHeightUpdatedSignal(getLastKnownBlockHeight());
+ return true;
+}
+
+void NodeAdapter::deinit() {
+ if (m_node != nullptr) {
+ if (m_nodeInitializerThread.isRunning()) {
+ m_nodeInitializer->stop(&m_node);
+ QEventLoop waitLoop;
+ connect(m_nodeInitializer, &InProcessNodeInitializer::nodeDeinitCompletedSignal, &waitLoop, &QEventLoop::quit, Qt::QueuedConnection);
+ waitLoop.exec();
+ m_nodeInitializerThread.quit();
+ m_nodeInitializerThread.wait();
+ } else {
+ delete m_node;
+ m_node = nullptr;
+ }
+ }
+}
+
+cryptonote::CoreConfig NodeAdapter::makeCoreConfig() const {
+ cryptonote::CoreConfig config;
+ boost::program_options::variables_map options;
+ boost::any dataDir = Settings::instance().getDataDir().absolutePath().toStdString();
+ options.insert(std::make_pair("data-dir", boost::program_options::variable_value(dataDir, false)));
+ config.init(options);
+ return config;
+}
+
+}
+
+#include "NodeAdapter.moc"
diff --git a/src/NodeAdapter.h b/src/NodeAdapter.h
new file mode 100644
index 0000000..18467a4
--- /dev/null
+++ b/src/NodeAdapter.h
@@ -0,0 +1,67 @@
+// Copyright (c) 2011-2015 The Cryptonote developers
+// Distributed under the MIT/X11 software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+
+#pragma once
+
+#include
+#include
+
+#include
+#include
+
+#include "CryptoNote.h"
+
+namespace cryptonote {
+
+class Currency;
+
+}
+
+namespace WalletGui {
+
+class InProcessNodeInitializer;
+
+class NodeAdapter : public QObject, public INodeCallback {
+ Q_OBJECT
+ Q_DISABLE_COPY(NodeAdapter)
+
+public:
+ static NodeAdapter& instance();
+
+ quintptr getPeerCount() const;
+ std::string convertPaymentId(const QString& _payment_id_string) const;
+ QString extractPaymentId(const std::string& _extra) const;
+ CryptoNote::IWallet* createWallet() const;
+
+ bool init();
+ void deinit();
+ quint64 getLastKnownBlockHeight() const;
+ quint64 getLastLocalBlockHeight() const;
+ QDateTime getLastLocalBlockTimestamp() const;
+ void peerCountUpdated(Node& _node, size_t _count) Q_DECL_OVERRIDE;
+ void localBlockchainUpdated(Node& _node, uint64_t _height) Q_DECL_OVERRIDE;
+ void lastKnownBlockHeightUpdated(Node& _node, uint64_t _height) Q_DECL_OVERRIDE;
+
+private:
+ Node* m_node;
+ QThread m_nodeInitializerThread;
+ InProcessNodeInitializer* m_nodeInitializer;
+
+ NodeAdapter();
+ ~NodeAdapter();
+
+ bool initInProcessNode();
+ cryptonote::CoreConfig makeCoreConfig() const;
+
+Q_SIGNALS:
+ void localBlockchainUpdatedSignal(quint64 _height);
+ void lastKnownBlockHeightUpdatedSignal(quint64 _height);
+ void nodeInitCompletedSignal();
+ void peerCountUpdatedSignal(quintptr _count);
+ void initNodeSignal(Node** _node, const cryptonote::Currency* currency, INodeCallback* _callback, const cryptonote::CoreConfig& _core_config);
+ void deinitNodeSignal(Node** _node);
+};
+
+}
diff --git a/src/Settings.cpp b/src/Settings.cpp
new file mode 100644
index 0000000..547047b
--- /dev/null
+++ b/src/Settings.cpp
@@ -0,0 +1,225 @@
+// Copyright (c) 2011-2015 The Cryptonote developers
+// Distributed under the MIT/X11 software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+#include "CommandLineParser.h"
+#include "CurrencyAdapter.h"
+#include "Settings.h"
+
+namespace WalletGui {
+
+Settings& Settings::instance() {
+ static Settings inst;
+ return inst;
+}
+
+Settings::Settings() : QObject(), m_cmdLineParser(nullptr) {
+}
+
+Settings::~Settings() {
+}
+
+void Settings::setCommandLineParser(CommandLineParser* _cmdLineParser) {
+ Q_CHECK_PTR(_cmdLineParser);
+ m_cmdLineParser = _cmdLineParser;
+}
+
+void Settings::load() {
+ QFile cfgFile(getDataDir().absoluteFilePath(QCoreApplication::applicationName() + ".cfg"));
+ if (cfgFile.open(QIODevice::ReadOnly)) {
+ m_settings = QJsonDocument::fromJson(cfgFile.readAll()).object();
+ cfgFile.close();
+ if (!m_settings.contains("walletFile")) {
+ m_addressBookFile = getDataDir().absoluteFilePath(QCoreApplication::applicationName() + ".addressbook");
+ } else {
+ m_addressBookFile = m_settings.value("walletFile").toString();
+ m_addressBookFile.replace(m_addressBookFile.lastIndexOf(".wallet"), 7, ".addressbook");
+ }
+ } else {
+ m_addressBookFile = getDataDir().absoluteFilePath(QCoreApplication::applicationName() + ".addressbook");
+ }
+}
+
+QDir Settings::getDataDir() const {
+ Q_CHECK_PTR(m_cmdLineParser);
+ return QDir(m_cmdLineParser->getDataDir());
+}
+
+QString Settings::getWalletFile() const {
+ return m_settings.contains("walletFile") ? m_settings.value("walletFile").toString() :
+ getDataDir().absoluteFilePath(QCoreApplication::applicationName() + ".wallet");
+}
+
+QString Settings::getAddressBookFile() const {
+ return m_addressBookFile;
+}
+
+bool Settings::isEncrypted() const {
+ return m_settings.contains("encrypted") ? m_settings.value("encrypted").toBool() : false;
+}
+
+QString Settings::getVersion() const {
+ return GIT_REVISION;
+}
+
+bool Settings::isStartOnLoginEnabled() const {
+ bool res = false;
+#ifdef Q_OS_MAC
+ QDir autorunDir = QDir::home();
+ if (!autorunDir.cd("Library") || !autorunDir.cd("LaunchAgents")) {
+ return false;
+ }
+
+ QString autorunFilePath = autorunDir.absoluteFilePath(QCoreApplication::applicationName() + ".plist");
+ if (!QFile::exists(autorunFilePath)) {
+ return false;
+ }
+
+ QSettings autorunSettings(autorunFilePath, QSettings::NativeFormat);
+ res = autorunSettings.value("RunAtLoad", false).toBool();
+#elif defined(Q_OS_LINUX)
+ QString configPath = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation);
+ if (configPath.isEmpty()) {
+ return false;
+ }
+
+ QDir autorunDir(configPath);
+ if (!autorunDir.cd("autostart")) {
+ return false;
+ }
+
+ QString autorunFilePath = autorunDir.absoluteFilePath(QCoreApplication::applicationName() + ".desktop");
+ res = QFile::exists(autorunFilePath);
+#elif defined(Q_OS_WIN)
+ QSettings autorunSettings("HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Run", QSettings::NativeFormat);
+ QString keyName = QString("%1Wallet").arg(CurrencyAdapter::instance().getCurrencyDisplayName());
+ res = autorunSettings.contains(keyName) &&
+ !QDir::fromNativeSeparators(autorunSettings.value(keyName).toString()).compare(QApplication::applicationFilePath());
+#endif
+ return res;
+}
+
+#ifdef Q_OS_WIN
+bool Settings::isMinimizeToTrayEnabled() const {
+ return m_settings.contains("minimizeToTray") ? m_settings.value("minimizeToTray").toBool() : false;
+}
+
+bool Settings::isCloseToTrayEnabled() const {
+ return m_settings.contains("closeToTray") ? m_settings.value("closeToTray").toBool() : false;
+}
+#endif
+
+void Settings::setWalletFile(const QString& _file) {
+ if (_file.endsWith(".wallet") || _file.endsWith(".keys")) {
+ m_settings.insert("walletFile", _file);
+ } else {
+ m_settings.insert("walletFile", _file + ".wallet");
+ }
+
+ saveSettings();
+ m_addressBookFile = m_settings.value("walletFile").toString();
+ m_addressBookFile.replace(m_addressBookFile.lastIndexOf(".wallet"), 7, ".addressbook");
+}
+
+void Settings::setEncrypted(bool _encrypted) {
+ if (isEncrypted() != _encrypted) {
+ m_settings.insert("encrypted", _encrypted);
+ saveSettings();
+ }
+}
+
+void Settings::setCurrentTheme(const QString& _theme) {
+}
+
+void Settings::setStartOnLoginEnabled(bool _enable) {
+#ifdef Q_OS_MAC
+ QDir autorunDir = QDir::home();
+ if (!autorunDir.cd("Library") || !autorunDir.cd("LaunchAgents")) {
+ return;
+ }
+
+ QString autorunFilePath = autorunDir.absoluteFilePath(QCoreApplication::applicationName() + ".plist");
+ QSettings autorunSettings(autorunFilePath, QSettings::NativeFormat);
+ autorunSettings.setValue("Label", "org." + QCoreApplication::applicationName());
+ autorunSettings.setValue("Program", QApplication::applicationFilePath());
+ autorunSettings.setValue("RunAtLoad", _enable);
+ autorunSettings.setValue("ProcessType", "InterActive");
+#elif defined(Q_OS_LINUX)
+ QString configPath = QStandardPaths::writableLocation(QStandardPaths::ConfigLocation);
+ if (configPath.isEmpty()) {
+ return;
+ }
+
+ QDir autorunDir(configPath);
+ if(!autorunDir.exists("autostart")) {
+ autorunDir.mkdir("autostart");
+ }
+
+ if (!autorunDir.cd("autostart")) {
+ return;
+ }
+
+ QString autorunFilePath = autorunDir.absoluteFilePath(QCoreApplication::applicationName() + ".desktop");
+ QFile autorunFile(autorunFilePath);
+ if (!autorunFile.open(QFile::WriteOnly | QFile::Truncate)) {
+ return;
+ }
+
+ if (_enable) {
+ autorunFile.write("[Desktop Entry]\n");
+ autorunFile.write("Type=Application\n");
+ autorunFile.write(QString("Name=%1 Wallet\n").arg(CurrencyAdapter::instance().getCurrencyDisplayName()).toLocal8Bit());
+ autorunFile.write(QString("Exec=%1\n").arg(QApplication::applicationFilePath()).toLocal8Bit());
+ autorunFile.write("Terminal=false\n");
+ autorunFile.write("Hidden=false\n");
+ autorunFile.close();
+ } else {
+ QFile::remove(autorunFilePath);
+ }
+#elif defined(Q_OS_WIN)
+ QSettings autorunSettings("HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\CurrentVersion\\Run", QSettings::NativeFormat);
+ QString keyName = QString("%1Wallet").arg(CurrencyAdapter::instance().getCurrencyDisplayName());
+ if (_enable) {
+ autorunSettings.setValue(keyName, QDir::toNativeSeparators(QApplication::applicationFilePath()));
+ } else {
+ autorunSettings.remove(keyName);
+ }
+#endif
+}
+
+#ifdef Q_OS_WIN
+void Settings::setMinimizeToTrayEnabled(bool _enable) {
+ if (isMinimizeToTrayEnabled() != _enable) {
+ m_settings.insert("minimizeToTray", _enable);
+ saveSettings();
+ }
+}
+
+void Settings::setCloseToTrayEnabled(bool _enable) {
+ if (isCloseToTrayEnabled() != _enable) {
+ m_settings.insert("closeToTray", _enable);
+ saveSettings();
+ }
+}
+#endif
+
+void Settings::saveSettings() const {
+ QFile cfgFile(getDataDir().absoluteFilePath(QCoreApplication::applicationName() + ".cfg"));
+ if (cfgFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
+ QJsonDocument cfg_doc(m_settings);
+ cfgFile.write(cfg_doc.toJson());
+ cfgFile.close();
+ }
+}
+
+}
diff --git a/src/Settings.h b/src/Settings.h
new file mode 100644
index 0000000..20ffe4d
--- /dev/null
+++ b/src/Settings.h
@@ -0,0 +1,57 @@
+// Copyright (c) 2011-2015 The Cryptonote developers
+// Distributed under the MIT/X11 software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+
+#pragma once
+
+#include
+#include
+#include
+
+namespace WalletGui {
+
+class CommandLineParser;
+
+class Settings : public QObject {
+ Q_OBJECT
+ Q_DISABLE_COPY(Settings)
+
+public:
+ static Settings& instance();
+
+ void setCommandLineParser(CommandLineParser* _cmd_line_parser);
+ void load();
+
+ QDir getDataDir() const;
+ QString getWalletFile() const;
+ QString getAddressBookFile() const;
+ bool isEncrypted() const;
+ QString getVersion() const;
+ bool isStartOnLoginEnabled() const;
+#ifdef Q_OS_WIN
+ bool isMinimizeToTrayEnabled() const;
+ bool isCloseToTrayEnabled() const;
+#endif
+
+ void setWalletFile(const QString& _file);
+ void setEncrypted(bool _encrypted);
+ void setCurrentTheme(const QString& _theme);
+ void setStartOnLoginEnabled(bool _enable);
+#ifdef Q_OS_WIN
+ void setMinimizeToTrayEnabled(bool _enable);
+ void setCloseToTrayEnabled(bool _enable);
+#endif
+
+private:
+ QJsonObject m_settings;
+ QString m_addressBookFile;
+ CommandLineParser* m_cmdLineParser;
+
+ Settings();
+ ~Settings();
+
+ void saveSettings() const;
+};
+
+}
diff --git a/src/SignalHandler.cpp b/src/SignalHandler.cpp
new file mode 100644
index 0000000..445250f
--- /dev/null
+++ b/src/SignalHandler.cpp
@@ -0,0 +1,32 @@
+// Copyright (c) 2011-2015 The Cryptonote developers
+// Distributed under the MIT/X11 software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+
+#include
+
+#include "SignalHandler.h"
+
+namespace WalletGui {
+
+SignalHandler& SignalHandler::instance() {
+ static SignalHandler inst;
+ return inst;
+}
+
+SignalHandler::SignalHandler() : QObject() {
+}
+
+SignalHandler::~SignalHandler() {
+}
+
+void SignalHandler::init() {
+ std::signal(SIGINT, SignalHandler::sigHandler);
+ std::signal(SIGTERM, SignalHandler::sigHandler);
+}
+
+void SignalHandler::sigHandler(int _signal) {
+ Q_EMIT SignalHandler::instance().quitSignal();
+}
+
+}
diff --git a/src/SignalHandler.h b/src/SignalHandler.h
new file mode 100644
index 0000000..fac3966
--- /dev/null
+++ b/src/SignalHandler.h
@@ -0,0 +1,31 @@
+// Copyright (c) 2011-2015 The Cryptonote developers
+// Distributed under the MIT/X11 software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+
+#pragma once
+
+#include
+
+namespace WalletGui {
+
+class SignalHandler : public QObject {
+ Q_OBJECT
+ Q_DISABLE_COPY(SignalHandler)
+
+public:
+ static SignalHandler& instance();
+
+ void init();
+
+private:
+ SignalHandler();
+ ~SignalHandler();
+
+ static void sigHandler(int _signal);
+
+Q_SIGNALS:
+ void quitSignal();
+};
+
+}
diff --git a/src/WalletAdapter.cpp b/src/WalletAdapter.cpp
new file mode 100644
index 0000000..436e2cd
--- /dev/null
+++ b/src/WalletAdapter.cpp
@@ -0,0 +1,432 @@
+// Copyright (c) 2011-2015 The Cryptonote developers
+// Distributed under the MIT/X11 software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+
+#include "NodeAdapter.h"
+#include "Settings.h"
+#include "WalletAdapter.h"
+
+namespace WalletGui {
+
+const quint32 MSECS_IN_HOUR = 60 * 60 * 1000;
+const quint32 MSECS_IN_MINUTE = 60 * 1000;
+
+const quint32 LAST_BLOCK_INFO_UPDATING_INTERVAL = 1 * MSECS_IN_MINUTE;
+const quint32 LAST_BLOCK_INFO_WARNING_INTERVAL = 1 * MSECS_IN_HOUR;
+
+WalletAdapter& WalletAdapter::instance() {
+ static WalletAdapter inst;
+ return inst;
+}
+
+WalletAdapter::WalletAdapter() : QObject(), m_wallet(nullptr), m_mutex(), m_isBackupInProgress(false),
+ m_isSynchronized(false), m_newTransactionsNotificationTimer(),
+ m_lastWalletTransactionId(std::numeric_limits::max()) {
+ connect(this, &WalletAdapter::walletInitCompletedSignal, this, &WalletAdapter::onWalletInitCompleted, Qt::QueuedConnection);
+ connect(this, &WalletAdapter::walletSendTransactionCompletedSignal, this, &WalletAdapter::onWalletSendTransactionCompleted, Qt::QueuedConnection);
+ connect(this, &WalletAdapter::updateBlockStatusTextSignal, this, &WalletAdapter::updateBlockStatusText, Qt::QueuedConnection);
+ connect(this, &WalletAdapter::updateBlockStatusTextWithDelaySignal, this, &WalletAdapter::updateBlockStatusTextWithDelay, Qt::QueuedConnection);
+ connect(&m_newTransactionsNotificationTimer, &QTimer::timeout, this, &WalletAdapter::notifyAboutLastTransaction);
+ connect(this, &WalletAdapter::walletSynchronizationProgressUpdatedSignal, this, [&]() {
+ if (!m_newTransactionsNotificationTimer.isActive()) {
+ m_newTransactionsNotificationTimer.start();
+ }
+ }, Qt::QueuedConnection);
+
+ connect(this, &WalletAdapter::walletSynchronizationCompletedSignal, this, [&]() {
+ m_newTransactionsNotificationTimer.stop();
+ notifyAboutLastTransaction();
+ }, Qt::QueuedConnection);
+
+ m_newTransactionsNotificationTimer.setInterval(500);
+}
+
+WalletAdapter::~WalletAdapter() {
+}
+
+QString WalletAdapter::getAddress() const {
+ try {
+ return m_wallet == nullptr ? QString() : QString::fromStdString(m_wallet->getAddress());
+ } catch (std::system_error&) {
+ return QString();
+ }
+}
+
+quint64 WalletAdapter::getActualBalance() const {
+ try {
+ return m_wallet == nullptr ? 0 : m_wallet->actualBalance();
+ } catch (std::system_error&) {
+ return 0;
+ }
+}
+
+quint64 WalletAdapter::getPendingBalance() const {
+ try {
+ return m_wallet == nullptr ? 0 : m_wallet->pendingBalance();
+ } catch (std::system_error&) {
+ return 0;
+ }
+}
+
+void WalletAdapter::open(const QString& _password) {
+ Q_ASSERT(m_wallet == nullptr);
+ Settings::instance().setEncrypted(!_password.isEmpty());
+ Q_EMIT walletStateChangedSignal(tr("Opening wallet"));
+
+ m_wallet = NodeAdapter::instance().createWallet();
+ m_wallet->addObserver(this);
+
+ if (QFile::exists(Settings::instance().getWalletFile())) {
+ if (Settings::instance().getWalletFile().endsWith(".keys")) {
+ if(!importLegacyWallet(_password)) {
+ return;
+ }
+ }
+
+ if (openFile(Settings::instance().getWalletFile(), true)) {
+ try {
+ m_wallet->initAndLoad(m_file, _password.toStdString());
+ } catch (std::system_error&) {
+ closeFile();
+ delete m_wallet;
+ m_wallet = nullptr;
+ }
+ }
+ } else {
+ Settings::instance().setEncrypted(false);
+ try {
+ m_wallet->initAndGenerate("");
+ } catch (std::system_error&) {
+ delete m_wallet;
+ m_wallet = nullptr;
+ }
+ }
+}
+
+bool WalletAdapter::isOpen() const {
+ return m_wallet != nullptr;
+}
+
+bool WalletAdapter::importLegacyWallet(const QString &_password) {
+ QString fileName = Settings::instance().getWalletFile();
+ Settings::instance().setEncrypted(!_password.isEmpty());
+ try {
+ fileName.replace(fileName.lastIndexOf(".keys"), 5, ".wallet");
+ if (!openFile(fileName, false)) {
+ delete m_wallet;
+ m_wallet = nullptr;
+ return false;
+ }
+
+ cryptonote::importLegacyKeys(Settings::instance().getWalletFile().toStdString(), _password.toStdString(), m_file);
+ closeFile();
+ Settings::instance().setWalletFile(fileName);
+ return true;
+ } catch (std::system_error& _err) {
+ closeFile();
+ if (_err.code().value() == cryptonote::error::WRONG_PASSWORD) {
+ Settings::instance().setEncrypted(true);
+ Q_EMIT openWalletWithPasswordSignal(!_password.isEmpty());
+ }
+ } catch (std::runtime_error& _err) {
+ closeFile();
+ }
+
+ delete m_wallet;
+ m_wallet = nullptr;
+ return false;
+}
+
+void WalletAdapter::close() {
+ Q_CHECK_PTR(m_wallet);
+ save(true, true);
+ lock();
+ m_wallet->removeObserver(this);
+ m_isSynchronized = false;
+ m_newTransactionsNotificationTimer.stop();
+ m_lastWalletTransactionId = std::numeric_limits::max();
+ Q_EMIT walletCloseCompletedSignal();
+ QCoreApplication::processEvents();
+ delete m_wallet;
+ m_wallet = nullptr;
+ unlock();
+}
+
+bool WalletAdapter::save(bool _details, bool _cache) {
+ return save(Settings::instance().getWalletFile() + ".temp", _details, _cache);
+}
+
+bool WalletAdapter::save(const QString& _file, bool _details, bool _cache) {
+ Q_CHECK_PTR(m_wallet);
+ if (openFile(_file, false)) {
+ try {
+ m_wallet->save(m_file, _details, _cache);
+ } catch (std::system_error&) {
+ closeFile();
+ return false;
+ }
+ Q_EMIT walletStateChangedSignal(tr("Saving data"));
+ } else {
+ return false;
+ }
+
+ return true;
+}
+
+void WalletAdapter::backup(const QString& _file) {
+ if (save(_file.endsWith(".wallet") ? _file : _file + ".wallet", true, false)) {
+ m_isBackupInProgress = true;
+ }
+}
+
+quint64 WalletAdapter::getTransactionCount() const {
+ Q_CHECK_PTR(m_wallet);
+ try {
+ return m_wallet->getTransactionCount();
+ } catch (std::system_error&) {
+ }
+
+ return 0;
+}
+
+quint64 WalletAdapter::getTransferCount() const {
+ Q_CHECK_PTR(m_wallet);
+ try {
+ return m_wallet->getTransferCount();
+ } catch (std::system_error&) {
+ }
+
+ return 0;
+}
+
+bool WalletAdapter::getTransaction(CryptoNote::TransactionId& _id, CryptoNote::TransactionInfo& _transaction) {
+ Q_CHECK_PTR(m_wallet);
+ try {
+ return m_wallet->getTransaction(_id, _transaction);
+ } catch (std::system_error&) {
+ }
+
+ return false;
+}
+
+bool WalletAdapter::getTransfer(CryptoNote::TransferId& _id, CryptoNote::Transfer& _transfer) {
+ Q_CHECK_PTR(m_wallet);
+ try {
+ return m_wallet->getTransfer(_id, _transfer);
+ } catch (std::system_error&) {
+ }
+
+ return false;
+}
+
+void WalletAdapter::sendTransaction(const QVector& _transfers, quint64 _fee, const QString& _paymentId, quint64 _mixin) {
+ Q_CHECK_PTR(m_wallet);
+ try {
+ lock();
+ m_wallet->sendTransaction(_transfers.toStdVector(), _fee, NodeAdapter::instance().convertPaymentId(_paymentId), _mixin, 0);
+ Q_EMIT walletStateChangedSignal(tr("Sending transaction"));
+ } catch (std::system_error&) {
+ unlock();
+ }
+}
+
+bool WalletAdapter::changePassword(const QString& _oldPassword, const QString& _newPassword) {
+ Q_CHECK_PTR(m_wallet);
+ try {
+ if (m_wallet->changePassword(_oldPassword.toStdString(), _newPassword.toStdString()).value() == cryptonote::error::WRONG_PASSWORD) {
+ return false;
+ }
+ } catch (std::system_error&) {
+ return false;
+ }
+
+ Settings::instance().setEncrypted(!_newPassword.isEmpty());
+ return save(true, true);
+}
+
+void WalletAdapter::setWalletFile(const QString& _path) {
+ Q_ASSERT(m_wallet == nullptr);
+ Settings::instance().setWalletFile(_path);
+}
+
+void WalletAdapter::initCompleted(std::error_code _error) {
+ if (m_file.is_open()) {
+ closeFile();
+ }
+
+ Q_EMIT walletInitCompletedSignal(_error.value(), QString::fromStdString(_error.message()));
+}
+
+void WalletAdapter::onWalletInitCompleted(int _error, const QString& _errorText) {
+ switch(_error) {
+ case 0: {
+ Q_EMIT walletActualBalanceUpdatedSignal(m_wallet->actualBalance());
+ Q_EMIT walletPendingBalanceUpdatedSignal(m_wallet->pendingBalance());
+ Q_EMIT updateWalletAddressSignal(QString::fromStdString(m_wallet->getAddress()));
+ Q_EMIT reloadWalletTransactionsSignal();
+ Q_EMIT walletStateChangedSignal(tr("Ready"));
+ QTimer::singleShot(5000, this, SLOT(updateBlockStatusText()));
+ if (!QFile::exists(Settings::instance().getWalletFile())) {
+ save(true, true);
+ }
+
+ break;
+ }
+ case cryptonote::error::WRONG_PASSWORD:
+ Q_EMIT openWalletWithPasswordSignal(Settings::instance().isEncrypted());
+ Settings::instance().setEncrypted(true);
+ delete m_wallet;
+ m_wallet = nullptr;
+ break;
+ default: {
+ delete m_wallet;
+ m_wallet = nullptr;
+ break;
+ }
+ }
+}
+
+void WalletAdapter::saveCompleted(std::error_code _error) {
+ if (!_error && !m_isBackupInProgress) {
+ closeFile();
+ renameFile(Settings::instance().getWalletFile() + ".temp", Settings::instance().getWalletFile());
+ Q_EMIT walletStateChangedSignal(tr("Ready"));
+ Q_EMIT updateBlockStatusTextWithDelaySignal();
+ } else if (m_isBackupInProgress) {
+ m_isBackupInProgress = false;
+ closeFile();
+ } else {
+ closeFile();
+ }
+
+ Q_EMIT walletSaveCompletedSignal(_error.value(), QString::fromStdString(_error.message()));
+}
+
+void WalletAdapter::synchronizationProgressUpdated(uint64_t _current, uint64_t _total) {
+ m_isSynchronized = false;
+ Q_EMIT walletStateChangedSignal(QString("%1 %2/%3").arg(tr("Synchronizing")).arg(_current).arg(_total));
+ Q_EMIT walletSynchronizationProgressUpdatedSignal(_current, _total);
+}
+
+void WalletAdapter::synchronizationCompleted(std::error_code _error) {
+ if (!_error) {
+ m_isSynchronized = true;
+ Q_EMIT updateBlockStatusTextSignal();
+ Q_EMIT walletSynchronizationCompletedSignal(_error.value(), QString::fromStdString(_error.message()));
+ }
+}
+
+void WalletAdapter::actualBalanceUpdated(uint64_t _actual_balance) {
+ Q_EMIT walletActualBalanceUpdatedSignal(_actual_balance);
+}
+
+void WalletAdapter::pendingBalanceUpdated(uint64_t _pending_balance) {
+ Q_EMIT walletPendingBalanceUpdatedSignal(_pending_balance);
+}
+
+void WalletAdapter::externalTransactionCreated(CryptoNote::TransactionId _transactionId) {
+ if (!m_isSynchronized) {
+ m_lastWalletTransactionId = _transactionId;
+ } else {
+ Q_EMIT walletTransactionCreatedSignal(_transactionId);
+ }
+}
+
+void WalletAdapter::sendTransactionCompleted(CryptoNote::TransactionId _transaction_id, std::error_code _error) {
+ unlock();
+ Q_EMIT walletSendTransactionCompletedSignal(_transaction_id, _error.value(), QString::fromStdString(_error.message()));
+ Q_EMIT updateBlockStatusTextWithDelaySignal();
+}
+
+void WalletAdapter::onWalletSendTransactionCompleted(CryptoNote::TransactionId _transactionId, int _error, const QString& _errorText) {
+ if (_error) {
+ return;
+ }
+
+ CryptoNote::TransactionInfo transaction;
+ if (!this->getTransaction(_transactionId, transaction)) {
+ return;
+ }
+
+ if (transaction.transferCount == 0) {
+ return;
+ }
+
+ Q_EMIT walletTransactionCreatedSignal(_transactionId);
+
+ save(true, true);
+}
+
+void WalletAdapter::transactionUpdated(CryptoNote::TransactionId _transactionId) {
+ Q_EMIT walletTransactionUpdatedSignal(_transactionId);
+}
+
+void WalletAdapter::lock() {
+ m_mutex.lock();
+}
+
+void WalletAdapter::unlock() {
+ m_mutex.unlock();
+}
+
+bool WalletAdapter::openFile(const QString& _file, bool _readOnly) {
+ lock();
+ m_file.open(_file.toStdString(), std::ios::binary | (_readOnly ? std::ios::in : (std::ios::out | std::ios::trunc)));
+ if (!m_file.is_open()) {
+ unlock();
+ }
+
+ return m_file.is_open();
+}
+
+void WalletAdapter::closeFile() {
+ m_file.close();
+ unlock();
+}
+
+void WalletAdapter::notifyAboutLastTransaction() {
+ if (m_lastWalletTransactionId != std::numeric_limits::max()) {
+ Q_EMIT walletTransactionCreatedSignal(m_lastWalletTransactionId);
+ m_lastWalletTransactionId = std::numeric_limits::max();
+ }
+}
+
+void WalletAdapter::renameFile(const QString& _oldName, const QString& _newName) {
+ Q_ASSERT(QFile::exists(_oldName));
+ QFile::remove(_newName);
+ QFile::rename(_oldName, _newName);
+}
+
+void WalletAdapter::updateBlockStatusText() {
+ if (m_wallet == nullptr) {
+ return;
+ }
+
+ const QDateTime currentTime = QDateTime::currentDateTimeUtc();
+ const QDateTime blockTime = NodeAdapter::instance().getLastLocalBlockTimestamp();
+ quint64 blockAge = blockTime.msecsTo(currentTime);
+ const QString warningString = blockTime.msecsTo(currentTime) < LAST_BLOCK_INFO_WARNING_INTERVAL ? "" :
+ QString(" Warning: last block was received %1 hours %2 minutes ago").arg(blockAge / MSECS_IN_HOUR).arg(blockAge % MSECS_IN_HOUR / MSECS_IN_MINUTE);
+ Q_EMIT walletStateChangedSignal(QString(tr("Wallet synchronized. Height: %1 | Time (UTC): %2%3")).
+ arg(NodeAdapter::instance().getLastLocalBlockHeight()).
+ arg(QLocale(QLocale::English).toString(blockTime, "dd MMM yyyy, HH:mm:ss")).
+ arg(warningString));
+
+ QTimer::singleShot(LAST_BLOCK_INFO_UPDATING_INTERVAL, this, SLOT(updateBlockStatusText()));
+}
+
+void WalletAdapter::updateBlockStatusTextWithDelay() {
+ QTimer::singleShot(5000, this, SLOT(updateBlockStatusText()));
+}
+
+}
diff --git a/src/WalletAdapter.h b/src/WalletAdapter.h
new file mode 100644
index 0000000..615f819
--- /dev/null
+++ b/src/WalletAdapter.h
@@ -0,0 +1,103 @@
+// Copyright (c) 2011-2015 The Cryptonote developers
+// Distributed under the MIT/X11 software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+
+#pragma once
+
+#include
+#include
+#include
+
+#include
+#include
+
+#include
+
+namespace WalletGui {
+
+class WalletAdapter : public QObject, public CryptoNote::IWalletObserver {
+ Q_OBJECT
+ Q_DISABLE_COPY(WalletAdapter)
+
+public:
+ static WalletAdapter& instance();
+
+ void open(const QString& _password);
+ void close();
+ bool save(bool _details, bool _cache);
+ void backup(const QString& _file);
+
+ QString getAddress() const;
+ quint64 getActualBalance() const;
+ quint64 getPendingBalance() const;
+ quint64 getTransactionCount() const;
+ quint64 getTransferCount() const;
+ bool getTransaction(CryptoNote::TransactionId& _id, CryptoNote::TransactionInfo& _transaction);
+ bool getTransfer(CryptoNote::TransferId& _id, CryptoNote::Transfer& _transfer);
+ bool isOpen() const;
+ void sendTransaction(const QVector& _transfers, quint64 _fee, const QString& _payment_id, quint64 _mixin);
+ bool changePassword(const QString& _old_pass, const QString& _new_pass);
+ void setWalletFile(const QString& _path);
+
+ void initCompleted(std::error_code _result) Q_DECL_OVERRIDE;
+ void saveCompleted(std::error_code _result) Q_DECL_OVERRIDE;
+ void synchronizationProgressUpdated(uint64_t _current, uint64_t _total) Q_DECL_OVERRIDE;
+ void synchronizationCompleted(std::error_code _error) Q_DECL_OVERRIDE;
+ void actualBalanceUpdated(uint64_t _actual_balance) Q_DECL_OVERRIDE;
+ void pendingBalanceUpdated(uint64_t _pending_balance) Q_DECL_OVERRIDE;
+ void externalTransactionCreated(CryptoNote::TransactionId _transaction_id) Q_DECL_OVERRIDE;
+ void sendTransactionCompleted(CryptoNote::TransactionId _transaction_id, std::error_code _result) Q_DECL_OVERRIDE;
+ void transactionUpdated(CryptoNote::TransactionId _transaction_id) Q_DECL_OVERRIDE;
+
+private:
+ std::fstream m_file;
+ CryptoNote::IWallet* m_wallet;
+ QMutex m_mutex;
+ std::atomic m_isBackupInProgress;
+ std::atomic m_isSynchronized;
+ std::atomic m_lastWalletTransactionId;
+ QTimer m_newTransactionsNotificationTimer;
+
+ WalletAdapter();
+ ~WalletAdapter();
+
+ void onWalletInitCompleted(int _error, const QString& _error_text);
+ void onWalletSendTransactionCompleted(CryptoNote::TransactionId _transaction_id, int _error, const QString& _error_text);
+
+ bool importLegacyWallet(const QString &_password);
+ bool save(const QString& _file, bool _details, bool _cache);
+ void lock();
+ void unlock();
+ bool openFile(const QString& _file, bool _read_only);
+ void closeFile();
+ void notifyAboutLastTransaction();
+
+ static void renameFile(const QString& _old_name, const QString& _new_name);
+ Q_SLOT void updateBlockStatusText();
+ Q_SLOT void updateBlockStatusTextWithDelay();
+
+Q_SIGNALS:
+ void walletInitCompletedSignal(int _error, const QString& _error_text);
+ void walletCloseCompletedSignal();
+ void walletSaveCompletedSignal(int _error, const QString& _error_text);
+ void walletSynchronizationProgressUpdatedSignal(quint64 _current, quint64 _total);
+ void walletSynchronizationCompletedSignal(int _error, const QString& _error_text);
+ void walletActualBalanceUpdatedSignal(quint64 _actual_balance);
+ void walletPendingBalanceUpdatedSignal(quint64 _pending_balance);
+ void walletTransactionCreatedSignal(CryptoNote::TransactionId _transaction_id);
+ void walletSendTransactionCompletedSignal(CryptoNote::TransactionId _transaction_id, int _error, const QString& _error_text);
+ void walletTransactionUpdatedSignal(CryptoNote::TransactionId _transaction_id);
+ void walletStateChangedSignal(const QString &_state_text);
+
+ void openWalletWithPasswordSignal(bool _error);
+ void changeWalletPasswordSignal();
+ void updateWalletAddressSignal(const QString& _address);
+ void reloadWalletTransactionsSignal();
+ void updateBlockStatusTextSignal();
+ void updateBlockStatusTextWithDelaySignal();
+};
+
+
+
+}
diff --git a/src/cryptonotewallet.rc b/src/cryptonotewallet.rc
new file mode 100644
index 0000000..a790366
--- /dev/null
+++ b/src/cryptonotewallet.rc
@@ -0,0 +1 @@
+IDI_ICON1 ICON "images\cryptonote.ico"
diff --git a/src/gui/AboutDialog.cpp b/src/gui/AboutDialog.cpp
new file mode 100644
index 0000000..1e3418e
--- /dev/null
+++ b/src/gui/AboutDialog.cpp
@@ -0,0 +1,24 @@
+// Copyright (c) 2011-2015 The Cryptonote developers
+// Distributed under the MIT/X11 software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+
+#include "AboutDialog.h"
+#include "CurrencyAdapter.h"
+#include "Settings.h"
+
+#include "ui_aboutdialog.h"
+
+namespace WalletGui {
+
+AboutDialog::AboutDialog(QWidget* _parent) : QDialog(_parent), m_ui(new Ui::AboutDialog) {
+ m_ui->setupUi(this);
+ setWindowTitle(QString(tr("About %1 Wallet")).arg(CurrencyAdapter::instance().getCurrencyDisplayName()));
+ QString aboutText = m_ui->m_aboutLabel->text();
+ m_ui->m_aboutLabel->setText(aboutText.arg(GIT_REVISION));
+}
+
+AboutDialog::~AboutDialog() {
+}
+
+}
diff --git a/src/gui/AboutDialog.h b/src/gui/AboutDialog.h
new file mode 100644
index 0000000..fdb9775
--- /dev/null
+++ b/src/gui/AboutDialog.h
@@ -0,0 +1,28 @@
+// Copyright (c) 2011-2015 The Cryptonote developers
+// Distributed under the MIT/X11 software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+
+#pragma once
+
+#include
+
+namespace Ui {
+class AboutDialog;
+}
+
+namespace WalletGui {
+
+class AboutDialog : public QDialog {
+ Q_OBJECT
+ Q_DISABLE_COPY(AboutDialog)
+
+public:
+ AboutDialog(QWidget* _parent);
+ ~AboutDialog();
+
+private:
+ QScopedPointer m_ui;
+};
+
+}
diff --git a/src/gui/AddressBookDialog.cpp b/src/gui/AddressBookDialog.cpp
new file mode 100644
index 0000000..71c66b3
--- /dev/null
+++ b/src/gui/AddressBookDialog.cpp
@@ -0,0 +1,28 @@
+// Copyright (c) 2011-2015 The Cryptonote developers
+// Distributed under the MIT/X11 software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+
+#include "AddressBookDialog.h"
+#include "AddressBookModel.h"
+
+#include "ui_addressbookdialog.h"
+
+namespace WalletGui {
+
+AddressBookDialog::AddressBookDialog(QWidget* _parent) : QDialog(_parent), m_ui(new Ui::AddressBookDialog) {
+ m_ui->setupUi(this);
+ m_ui->m_addressBookView->setModel(&AddressBookModel::instance());
+ if (AddressBookModel::instance().rowCount() > 0) {
+ m_ui->m_addressBookView->setCurrentIndex(AddressBookModel::instance().index(0, 0));
+ }
+}
+
+AddressBookDialog::~AddressBookDialog() {
+}
+
+QString AddressBookDialog::getAddress() const {
+ return m_ui->m_addressBookView->currentIndex().data(AddressBookModel::ROLE_ADDRESS).toString();
+}
+
+}
diff --git a/src/gui/AddressBookDialog.h b/src/gui/AddressBookDialog.h
new file mode 100644
index 0000000..d9aba9f
--- /dev/null
+++ b/src/gui/AddressBookDialog.h
@@ -0,0 +1,30 @@
+// Copyright (c) 2011-2015 The Cryptonote developers
+// Distributed under the MIT/X11 software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+
+#pragma once
+
+#include
+
+namespace Ui {
+class AddressBookDialog;
+}
+
+namespace WalletGui {
+
+class AddressBookDialog : public QDialog {
+ Q_OBJECT
+ Q_DISABLE_COPY(AddressBookDialog)
+
+public:
+ AddressBookDialog(QWidget* _parent);
+ ~AddressBookDialog();
+
+ QString getAddress() const;
+
+private:
+ QScopedPointer m_ui;
+};
+
+}
diff --git a/src/gui/AddressBookFrame.cpp b/src/gui/AddressBookFrame.cpp
new file mode 100644
index 0000000..ca612aa
--- /dev/null
+++ b/src/gui/AddressBookFrame.cpp
@@ -0,0 +1,58 @@
+// Copyright (c) 2011-2015 The Cryptonote developers
+// Distributed under the MIT/X11 software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+
+#include
+#include
+
+#include "CurrencyAdapter.h"
+#include "AddressBookModel.h"
+#include "AddressBookFrame.h"
+#include "MainWindow.h"
+#include "NewAddressDialog.h"
+#include "WalletEvents.h"
+
+#include "ui_addressbookframe.h"
+
+namespace WalletGui {
+
+AddressBookFrame::AddressBookFrame(QWidget* _parent) : QFrame(_parent), m_ui(new Ui::AddressBookFrame) {
+ m_ui->setupUi(this);
+ m_ui->m_addressBookView->setModel(&AddressBookModel::instance());
+
+ connect(m_ui->m_addressBookView->selectionModel(), &QItemSelectionModel::currentChanged, this, &AddressBookFrame::currentAddressChanged);
+}
+
+AddressBookFrame::~AddressBookFrame() {
+}
+
+void AddressBookFrame::addClicked() {
+ NewAddressDialog dlg(&MainWindow::instance());
+ if (dlg.exec() == QDialog::Accepted) {
+ QString label = dlg.getLabel();
+ QString address = dlg.getAddress();
+ if (!CurrencyAdapter::instance().validateAddress(address)) {
+ QCoreApplication::postEvent(&MainWindow::instance(), new ShowMessageEvent(tr("Invalid address"), QtCriticalMsg));
+ return;
+ }
+
+ AddressBookModel::instance().addAddress(label, address);
+ }
+}
+
+void AddressBookFrame::copyClicked() {
+ QApplication::clipboard()->setText(m_ui->m_addressBookView->currentIndex().data(AddressBookModel::ROLE_ADDRESS).toString());
+}
+
+void AddressBookFrame::deleteClicked() {
+ int row = m_ui->m_addressBookView->currentIndex().row();
+ AddressBookModel::instance().removeAddress(row);
+}
+
+void AddressBookFrame::currentAddressChanged(const QModelIndex& _index) {
+ m_ui->m_copyAddressButton->setEnabled(_index.isValid());
+ m_ui->m_deleteAddressButton->setEnabled(_index.isValid());
+}
+
+}
diff --git a/src/gui/AddressBookFrame.h b/src/gui/AddressBookFrame.h
new file mode 100644
index 0000000..444d733
--- /dev/null
+++ b/src/gui/AddressBookFrame.h
@@ -0,0 +1,33 @@
+// Copyright (c) 2011-2015 The Cryptonote developers
+// Distributed under the MIT/X11 software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+
+#pragma once
+
+#include
+
+namespace Ui {
+class AddressBookFrame;
+}
+
+namespace WalletGui {
+
+class AddressBookFrame : public QFrame {
+ Q_OBJECT
+ Q_DISABLE_COPY(AddressBookFrame)
+
+public:
+ AddressBookFrame(QWidget* _parent);
+ ~AddressBookFrame();
+
+private:
+ QScopedPointer m_ui;
+
+ Q_SLOT void addClicked();
+ Q_SLOT void copyClicked();
+ Q_SLOT void deleteClicked();
+ Q_SLOT void currentAddressChanged(const QModelIndex& _index);
+};
+
+}
diff --git a/src/gui/AddressBookModel.cpp b/src/gui/AddressBookModel.cpp
new file mode 100644
index 0000000..7f9d457
--- /dev/null
+++ b/src/gui/AddressBookModel.cpp
@@ -0,0 +1,155 @@
+// Copyright (c) 2011-2015 The Cryptonote developers
+// Distributed under the MIT/X11 software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+
+#include
+#include
+#include
+
+#include "WalletAdapter.h"
+#include "AddressBookModel.h"
+#include "Settings.h"
+
+namespace WalletGui {
+
+AddressBookModel& AddressBookModel::instance() {
+ static AddressBookModel inst;
+ return inst;
+}
+
+AddressBookModel::AddressBookModel() : QAbstractItemModel() {
+ connect(&WalletAdapter::instance(), &WalletAdapter::walletInitCompletedSignal, this, &AddressBookModel::walletInitCompleted, Qt::QueuedConnection);
+ connect(&WalletAdapter::instance(), &WalletAdapter::walletCloseCompletedSignal, this, &AddressBookModel::reset, Qt::QueuedConnection);
+}
+
+AddressBookModel::~AddressBookModel() {
+}
+
+int AddressBookModel::columnCount(const QModelIndex& _parent) const {
+ return 2;
+}
+
+QVariant AddressBookModel::data(const QModelIndex& _index, int _role) const {
+ if (!_index.isValid()) {
+ return QVariant();
+ }
+
+ QJsonObject address = m_addressBook.at(_index.row()).toObject();
+
+ switch (_role) {
+ case Qt::DisplayRole:
+ switch (_index.column()) {
+ case COLUMN_LABEL:
+ return _index.data(ROLE_LABEL);
+ case COLUMN_ADDRESS:
+ return _index.data(ROLE_ADDRESS);
+ default:
+ return QVariant();
+ }
+
+ case ROLE_LABEL:
+ return address.value("label");
+ case ROLE_ADDRESS:
+ return address.value("address");
+ default:
+ return QVariant();
+ }
+
+ return QVariant();
+}
+
+Qt::ItemFlags AddressBookModel::flags(const QModelIndex& _index) const {
+ return (Qt::ItemIsEnabled | Qt::ItemNeverHasChildren | Qt::ItemIsSelectable);
+}
+
+QVariant AddressBookModel::headerData(int _section, Qt::Orientation _orientation, int _role) const {
+ if (_orientation != Qt::Horizontal || _role != Qt::DisplayRole) {
+ return QVariant();
+ }
+
+ switch (_section) {
+ case COLUMN_LABEL:
+ return tr("Label");
+ case COLUMN_ADDRESS:
+ return tr("Address");
+ }
+
+ return QVariant();
+}
+
+QModelIndex AddressBookModel::index(int _row, int _column, const QModelIndex& _parent) const {
+ if (_parent.isValid()) {
+ return QModelIndex();
+ }
+
+ return createIndex(_row, _column, _row);
+}
+
+QModelIndex AddressBookModel::parent(const QModelIndex& _index) const {
+ return QModelIndex();
+}
+
+int AddressBookModel::rowCount(const QModelIndex& _parent) const {
+ return m_addressBook.size();
+}
+
+void AddressBookModel::addAddress(const QString& _label, const QString& _address) {
+ beginInsertRows(QModelIndex(), m_addressBook.size(), m_addressBook.size());
+ QJsonObject newAddress;
+ newAddress.insert("label", _label);
+ newAddress.insert("address", _address);
+ m_addressBook.append(newAddress);
+ endInsertRows();
+ saveAddressBook();
+}
+
+void AddressBookModel::removeAddress(quint32 _row) {
+ if (_row > m_addressBook.size() - 1) {
+ return;
+ }
+
+ beginRemoveRows(QModelIndex(), _row, _row);
+ m_addressBook.removeAt(_row);
+ endRemoveRows();
+ saveAddressBook();
+}
+
+void AddressBookModel::reset() {
+ beginResetModel();
+ while (!m_addressBook.empty()) {
+ m_addressBook.removeFirst();
+ }
+
+ endResetModel();
+}
+
+void AddressBookModel::saveAddressBook() {
+ QFile addressBookFile(Settings::instance().getAddressBookFile());
+ if (addressBookFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
+ QByteArray file_content = QJsonDocument(m_addressBook).toJson(QJsonDocument::Compact);
+ addressBookFile.write(file_content);
+ addressBookFile.close();
+ }
+}
+
+void AddressBookModel::walletInitCompleted(int _error, const QString& _error_text) {
+ if (!_error) {
+ QFile addressBookFile(Settings::instance().getAddressBookFile());
+ if (addressBookFile.open(QIODevice::ReadOnly)) {
+ QByteArray file_content = addressBookFile.readAll();
+ QJsonDocument doc = QJsonDocument::fromJson(file_content);
+ if (!doc.isNull()) {
+ m_addressBook = doc.array();
+ }
+
+ addressBookFile.close();
+ if (!m_addressBook.isEmpty()) {
+ beginInsertRows(QModelIndex(), 0, m_addressBook.size() - 1);
+ endInsertRows();
+ }
+ }
+ }
+}
+
+}
diff --git a/src/gui/AddressBookModel.h b/src/gui/AddressBookModel.h
new file mode 100644
index 0000000..a07a3ab
--- /dev/null
+++ b/src/gui/AddressBookModel.h
@@ -0,0 +1,45 @@
+// Copyright (c) 2011-2015 The Cryptonote developers
+// Distributed under the MIT/X11 software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+
+#pragma once
+
+#include
+#include
+
+namespace WalletGui {
+
+class AddressBookModel : public QAbstractItemModel
+{
+ Q_OBJECT
+ Q_DISABLE_COPY(AddressBookModel)
+
+public:
+ enum Columns {COLUMN_LABEL = 0, COLUMN_ADDRESS};
+ enum Roles { ROLE_LABEL = Qt::UserRole, ROLE_ADDRESS };
+
+ static AddressBookModel& instance();
+ int columnCount(const QModelIndex& _parent = QModelIndex()) const Q_DECL_OVERRIDE;
+ QVariant data(const QModelIndex& _index, int _role = Qt::DisplayRole) const Q_DECL_OVERRIDE;
+ Qt::ItemFlags flags(const QModelIndex& _index) const Q_DECL_OVERRIDE;
+ QVariant headerData(int _section, Qt::Orientation _orientation, int _role = Qt::DisplayRole) const Q_DECL_OVERRIDE;
+ QModelIndex index(int _row, int _column, const QModelIndex& _parent = QModelIndex()) const Q_DECL_OVERRIDE;
+ QModelIndex parent(const QModelIndex& _index) const Q_DECL_OVERRIDE;
+ int rowCount(const QModelIndex& _parent = QModelIndex()) const Q_DECL_OVERRIDE;
+
+ void addAddress(const QString& _label, const QString& _address);
+ void removeAddress(quint32 _row);
+
+private:
+ QJsonArray m_addressBook;
+
+ AddressBookModel();
+ ~AddressBookModel();
+
+ void reset();
+ void saveAddressBook();
+ void walletInitCompleted(int _error, const QString& _error_text);
+};
+
+}
diff --git a/src/gui/AnimatedLabel.cpp b/src/gui/AnimatedLabel.cpp
new file mode 100644
index 0000000..88b919f
--- /dev/null
+++ b/src/gui/AnimatedLabel.cpp
@@ -0,0 +1,46 @@
+// Copyright (c) 2011-2015 The Cryptonote developers
+// Distributed under the MIT/X11 software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+
+#include "AnimatedLabel.h"
+
+namespace WalletGui {
+
+AnimatedLabel::AnimatedLabel(QWidget* _parent) : QLabel(_parent), m_spriteVerticalSpace(0) {
+ connect(&m_animationTimer, &QTimer::timeout, this, &AnimatedLabel::timeout);
+}
+
+AnimatedLabel::~AnimatedLabel() {
+}
+
+void AnimatedLabel::setSprite(const QPixmap& _spritePixmap, const QSize& _frameSize, quint32 _verticalSpace, quint32 _frequency) {
+ m_spritePixmap = _spritePixmap;
+ m_spriteFrameSize = _frameSize;
+ m_spriteVerticalSpace = _verticalSpace;
+ m_animationTimer.setInterval(1000 / _frequency);
+ m_frameRect.setTopLeft(QPoint(0, 0));
+ m_frameRect.setBottomRight(m_frameRect.topLeft() += QPoint(_frameSize.width(), _frameSize.height()));
+}
+
+void AnimatedLabel::startAnimation() {
+ if (m_animationTimer.isActive()) {
+ return;
+ }
+
+ m_animationTimer.start();
+}
+
+void AnimatedLabel::stopAnimation() {
+ m_animationTimer.stop();
+}
+
+void AnimatedLabel::timeout() {
+ setPixmap(m_spritePixmap.copy(m_frameRect));
+ m_frameRect.translate(QPoint(0, m_spriteVerticalSpace + m_spriteFrameSize.height()));
+ if (m_frameRect.bottom() >= m_spritePixmap.height()) {
+ m_frameRect.moveTop(0);
+ }
+}
+
+}
diff --git a/src/gui/AnimatedLabel.h b/src/gui/AnimatedLabel.h
new file mode 100644
index 0000000..42bd642
--- /dev/null
+++ b/src/gui/AnimatedLabel.h
@@ -0,0 +1,35 @@
+// Copyright (c) 2011-2015 The Cryptonote developers
+// Distributed under the MIT/X11 software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+
+#pragma once
+
+#include
+#include
+
+namespace WalletGui {
+
+class AnimatedLabel : public QLabel {
+ Q_OBJECT
+ Q_DISABLE_COPY(AnimatedLabel)
+
+public:
+ AnimatedLabel(QWidget* _parent);
+ ~AnimatedLabel();
+
+ void setSprite(const QPixmap& _sprite_pixmap, const QSize& _frame_size, quint32 _vertical_space, quint32 _frequency);
+ void startAnimation();
+ void stopAnimation();
+
+private:
+ QPixmap m_spritePixmap;
+ QSize m_spriteFrameSize;
+ quint32 m_spriteVerticalSpace;
+ QTimer m_animationTimer;
+ QRect m_frameRect;
+
+ void timeout();
+};
+
+}
diff --git a/src/gui/ChangePasswordDialog.cpp b/src/gui/ChangePasswordDialog.cpp
new file mode 100644
index 0000000..a4267e0
--- /dev/null
+++ b/src/gui/ChangePasswordDialog.cpp
@@ -0,0 +1,36 @@
+// Copyright (c) 2011-2015 The Cryptonote developers
+// Distributed under the MIT/X11 software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+
+#include "ChangePasswordDialog.h"
+
+#include "ui_changepassworddialog.h"
+
+namespace WalletGui {
+
+ChangePasswordDialog::ChangePasswordDialog(QWidget* _parent) : QDialog(_parent), m_ui(new Ui::ChangePasswordDialog) {
+ m_ui->setupUi(this);
+ m_ui->m_errorLabel->setText("");
+}
+
+ChangePasswordDialog::~ChangePasswordDialog() {
+}
+
+QString ChangePasswordDialog::getNewPassword() const {
+ return m_ui->m_newPasswordEdit->text();
+}
+
+QString ChangePasswordDialog::getOldPassword() const {
+ return m_ui->m_oldPasswordEdit->text();
+}
+
+void ChangePasswordDialog::checkPassword(const QString& _password) {
+ bool passwordIsConfirmed = !(m_ui->m_newPasswordEdit->text().trimmed().isEmpty() ||
+ m_ui->m_newPasswordConfirmationEdit->text().trimmed().isEmpty() ||
+ m_ui->m_newPasswordEdit->text().compare(m_ui->m_newPasswordConfirmationEdit->text()));
+ m_ui->m_errorLabel->setText(passwordIsConfirmed ? "" : tr("Password not confirmed"));
+ m_ui->m_okButton->setEnabled(passwordIsConfirmed);
+}
+
+}
diff --git a/src/gui/ChangePasswordDialog.h b/src/gui/ChangePasswordDialog.h
new file mode 100644
index 0000000..8e83d41
--- /dev/null
+++ b/src/gui/ChangePasswordDialog.h
@@ -0,0 +1,33 @@
+// Copyright (c) 2011-2015 The Cryptonote developers
+// Distributed under the MIT/X11 software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+
+#pragma once
+
+#include
+
+namespace Ui {
+class ChangePasswordDialog;
+}
+
+namespace WalletGui {
+
+class ChangePasswordDialog : public QDialog {
+ Q_OBJECT
+ Q_DISABLE_COPY(ChangePasswordDialog)
+
+public:
+ ChangePasswordDialog(QWidget* _parent);
+ ~ChangePasswordDialog();
+
+ QString getNewPassword() const;
+ QString getOldPassword() const;
+
+private:
+ QScopedPointer m_ui;
+
+ Q_SLOT void checkPassword(const QString& _password);
+};
+
+}
diff --git a/src/gui/ExitWidget.cpp b/src/gui/ExitWidget.cpp
new file mode 100644
index 0000000..6395905
--- /dev/null
+++ b/src/gui/ExitWidget.cpp
@@ -0,0 +1,29 @@
+// Copyright (c) 2011-2015 The Cryptonote developers
+// Distributed under the MIT/X11 software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+
+#include
+
+#include "CurrencyAdapter.h"
+#include "ExitWidget.h"
+
+#include "ui_exitwidget.h"
+
+namespace WalletGui {
+
+ExitWidget::ExitWidget(QWidget* _parent) : QWidget(_parent, Qt::Window), m_ui(new Ui::ExitWidget),
+ m_clockMovie(new QMovie(this)) {
+ m_ui->setupUi(this);
+ QString text = m_ui->m_label->text();
+ m_ui->m_label->setText(text.arg(CurrencyAdapter::instance().getCurrencyDisplayName()));
+ m_clockMovie->setFileName(":images/clock");
+ m_clockMovie->setScaledSize(QSize(48, 48));
+ m_ui->m_clockLabel->setMovie(m_clockMovie);
+ m_clockMovie->start();
+}
+
+ExitWidget::~ExitWidget() {
+}
+
+}
diff --git a/src/gui/ExitWidget.h b/src/gui/ExitWidget.h
new file mode 100644
index 0000000..dbe75b2
--- /dev/null
+++ b/src/gui/ExitWidget.h
@@ -0,0 +1,31 @@
+// Copyright (c) 2011-2015 The Cryptonote developers
+// Distributed under the MIT/X11 software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+
+#pragma once
+
+#include
+
+class QMovie;
+
+namespace Ui {
+class ExitWidget;
+}
+
+namespace WalletGui {
+
+class ExitWidget : public QWidget {
+ Q_OBJECT
+ Q_DISABLE_COPY(ExitWidget)
+
+public:
+ ExitWidget(QWidget* _parent);
+ ~ExitWidget();
+
+private:
+ QScopedPointer m_ui;
+ QMovie* m_clockMovie;
+};
+
+}
diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp
new file mode 100644
index 0000000..0bbb18f
--- /dev/null
+++ b/src/gui/MainWindow.cpp
@@ -0,0 +1,413 @@
+// Copyright (c) 2011-2015 The Cryptonote developers
+// Distributed under the MIT/X11 software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+
+#include "AboutDialog.h"
+#include "AnimatedLabel.h"
+#include "ChangePasswordDialog.h"
+#include "CurrencyAdapter.h"
+#include "ExitWidget.h"
+#include "MainWindow.h"
+#include "NewPasswordDialog.h"
+#include "NodeAdapter.h"
+#include "PasswordDialog.h"
+#include "Settings.h"
+#include "WalletAdapter.h"
+#include "WalletEvents.h"
+
+#include "ui_mainwindow.h"
+
+namespace WalletGui {
+
+MainWindow* MainWindow::m_instance = nullptr;
+
+MainWindow& MainWindow::instance() {
+ if (m_instance == nullptr) {
+ m_instance = new MainWindow;
+ }
+
+ return *m_instance;
+}
+
+MainWindow::MainWindow() : QMainWindow(), m_ui(new Ui::MainWindow), m_trayIcon(nullptr), m_tabActionGroup(new QActionGroup(this)),
+ m_isAboutToQuit(false) {
+ m_ui->setupUi(this);
+ m_connectionStateIconLabel = new QLabel(this);
+ m_encryptionStateIconLabel = new QLabel(this);
+ m_synchronizationStateIconLabel = new AnimatedLabel(this);
+
+ connectToSignals();
+ initUi();
+
+ walletClosed();
+}
+
+MainWindow::~MainWindow() {
+}
+
+void MainWindow::connectToSignals() {
+ connect(&WalletAdapter::instance(), &WalletAdapter::openWalletWithPasswordSignal, this, &MainWindow::askForWalletPassword, Qt::QueuedConnection);
+ connect(&WalletAdapter::instance(), &WalletAdapter::changeWalletPasswordSignal, this, &MainWindow::encryptWallet, Qt::QueuedConnection);
+ connect(&WalletAdapter::instance(), &WalletAdapter::walletSynchronizationProgressUpdatedSignal,
+ this, &MainWindow::walletSynchronizationInProgress, Qt::QueuedConnection);
+ connect(&WalletAdapter::instance(), &WalletAdapter::walletSynchronizationCompletedSignal, this, &MainWindow::walletSynchronized
+ , Qt::QueuedConnection);
+ connect(&WalletAdapter::instance(), &WalletAdapter::walletStateChangedSignal, this, &MainWindow::setStatusBarText);
+ connect(&WalletAdapter::instance(), &WalletAdapter::walletInitCompletedSignal, this, &MainWindow::walletOpened);
+ connect(&WalletAdapter::instance(), &WalletAdapter::walletCloseCompletedSignal, this, &MainWindow::walletClosed);
+ connect(&NodeAdapter::instance(), &NodeAdapter::peerCountUpdatedSignal, this, &MainWindow::peerCountUpdated, Qt::QueuedConnection);
+ connect(m_ui->m_exitAction, &QAction::triggered, qApp, &QApplication::quit);
+}
+
+void MainWindow::initUi() {
+ setWindowTitle(QString("%1 Wallet %2").arg(CurrencyAdapter::instance().getCurrencyDisplayName()).arg(Settings::instance().getVersion()));
+#ifdef Q_OS_WIN32
+ if (QSystemTrayIcon::isSystemTrayAvailable()) {
+ m_trayIcon = new QSystemTrayIcon(QPixmap(":images/cryptonote"), this);
+ connect(m_trayIcon, &QSystemTrayIcon::activated, this, &MainWindow::trayActivated);
+ }
+#endif
+ m_ui->m_aboutCryptonoteAction->setText(QString(tr("About %1 Wallet")).arg(CurrencyAdapter::instance().getCurrencyDisplayName()));
+
+ m_ui->m_overviewFrame->hide();
+ m_ui->m_sendFrame->hide();
+ m_ui->m_receiveFrame->hide();
+ m_ui->m_transactionsFrame->hide();
+ m_ui->m_addressBookFrame->hide();
+
+ m_tabActionGroup->addAction(m_ui->m_overviewAction);
+ m_tabActionGroup->addAction(m_ui->m_sendAction);
+ m_tabActionGroup->addAction(m_ui->m_receiveAction);
+ m_tabActionGroup->addAction(m_ui->m_transactionsAction);
+ m_tabActionGroup->addAction(m_ui->m_addressBookAction);
+
+ m_ui->m_overviewAction->toggle();
+ encryptedFlagChanged(false);
+ statusBar()->addPermanentWidget(m_connectionStateIconLabel);
+ statusBar()->addPermanentWidget(m_encryptionStateIconLabel);
+ statusBar()->addPermanentWidget(m_synchronizationStateIconLabel);
+ qobject_cast(m_synchronizationStateIconLabel)->setSprite(QPixmap(":icons/sync_sprite"), QSize(16, 16), 5, 24);
+ m_connectionStateIconLabel->setPixmap(QPixmap(":icons/disconnected").scaled(16, 16, Qt::IgnoreAspectRatio, Qt::SmoothTransformation));
+
+#ifdef Q_OS_MAC
+ installDockHandler();
+#endif
+
+#ifndef Q_OS_WIN
+ m_ui->m_minimizeToTrayAction->deleteLater();
+ m_ui->m_closeToTrayAction->deleteLater();
+#endif
+}
+
+#ifdef Q_OS_WIN
+void MainWindow::minimizeToTray(bool _on) {
+ if (_on) {
+ hide();
+ m_trayIcon->show();
+ } else {
+ showNormal();
+ activateWindow();
+ m_trayIcon->hide();
+ }
+}
+#endif
+
+void MainWindow::scrollToTransaction(const QModelIndex& _index) {
+ m_ui->m_transactionsAction->setChecked(true);
+ m_ui->m_transactionsFrame->scrollToTransaction(_index);
+}
+
+void MainWindow::quit() {
+ if (!m_isAboutToQuit) {
+ ExitWidget* exitWidget = new ExitWidget(nullptr);
+ exitWidget->show();
+ m_isAboutToQuit = true;
+ close();
+ }
+}
+
+#ifdef Q_OS_MAC
+void MainWindow::restoreFromDock() {
+ if (m_isAboutToQuit) {
+ return;
+ }
+
+ showNormal();
+}
+#endif
+
+void MainWindow::closeEvent(QCloseEvent* _event) {
+#ifdef Q_OS_WIN
+ if (m_isAboutToQuit) {
+ QMainWindow::closeEvent(_event);
+ return;
+ } else if (Settings::instance().isCloseToTrayEnabled()) {
+ minimizeToTray(true);
+ _event->ignore();
+ } else {
+ QApplication::quit();
+ return;
+ }
+#elif defined(Q_OS_LINUX)
+ if (!m_isAboutToQuit) {
+ QApplication::quit();
+ return;
+ }
+#endif
+ QMainWindow::closeEvent(_event);
+
+}
+
+#ifdef Q_OS_WIN
+void MainWindow::changeEvent(QEvent* _event) {
+ QMainWindow::changeEvent(_event);
+ if (!QSystemTrayIcon::isSystemTrayAvailable()) {
+ QMainWindow::changeEvent(_event);
+ return;
+ }
+
+ switch (_event->type()) {
+ case QEvent::WindowStateChange:
+ if(Settings::instance().isMinimizeToTrayEnabled()) {
+ minimizeToTray(isMinimized());
+ }
+ break;
+ default:
+ break;
+ }
+
+ QMainWindow::changeEvent(_event);
+}
+#endif
+
+bool MainWindow::event(QEvent* _event) {
+ switch (static_cast(_event->type())) {
+ case WalletEventType::ShowMessage:
+ showMessage(static_cast(_event)->messageText(), static_cast(_event)->messageType());
+ return true;
+ }
+
+ return QMainWindow::event(_event);
+}
+
+void MainWindow::createWallet() {
+ QString filePath = QFileDialog::getSaveFileName(this, tr("New wallet file"),
+ #ifdef Q_OS_WIN
+ QApplication::applicationDirPath(),
+ #else
+ QDir::homePath(),
+ #endif
+ tr("Wallets (*.wallet)")
+ );
+
+ if (!filePath.isEmpty() && !filePath.endsWith(".wallet")) {
+ filePath.append(".wallet");
+ }
+
+ if (!filePath.isEmpty() && !QFile::exists(filePath)) {
+ if (WalletAdapter::instance().isOpen()) {
+ WalletAdapter::instance().close();
+ }
+
+ WalletAdapter::instance().setWalletFile(filePath);
+ WalletAdapter::instance().open("");
+ }
+}
+
+void MainWindow::openWallet() {
+ QString filePath = QFileDialog::getOpenFileName(this, tr("Open .wallet/.keys file"),
+#ifdef Q_OS_WIN
+ QApplication::applicationDirPath(),
+#else
+ QDir::homePath(),
+#endif
+ tr("Wallet (*.wallet *.keys)"));
+
+ if (!filePath.isEmpty()) {
+ if (WalletAdapter::instance().isOpen()) {
+ WalletAdapter::instance().close();
+ }
+
+ WalletAdapter::instance().setWalletFile(filePath);
+ WalletAdapter::instance().open("");
+ }
+}
+
+void MainWindow::backupWallet() {
+ QString filePath = QFileDialog::getSaveFileName(this, tr("Backup wallet to..."),
+ #ifdef Q_OS_WIN
+ QApplication::applicationDirPath(),
+ #else
+ QDir::homePath(),
+ #endif
+ tr("Wallets (*.wallet)")
+ );
+ if (!filePath.isEmpty() && !filePath.endsWith(".wallet")) {
+ filePath.append(".wallet");
+ }
+
+ if (!filePath.isEmpty() && !QFile::exists(filePath)) {
+ WalletAdapter::instance().backup(filePath);
+ }
+}
+
+void MainWindow::encryptWallet() {
+ if (Settings::instance().isEncrypted()) {
+ bool error = false;
+ do {
+ ChangePasswordDialog dlg(this);
+ if (dlg.exec() == QDialog::Rejected) {
+ return;
+ }
+
+ QString oldPassword = dlg.getOldPassword();
+ QString newPassword = dlg.getNewPassword();
+ error = !WalletAdapter::instance().changePassword(oldPassword, newPassword);
+ } while (error);
+ } else {
+ NewPasswordDialog dlg(this);
+ if (dlg.exec() == QDialog::Accepted) {
+ QString password = dlg.getPassword();
+ if (password.isEmpty()) {
+ return;
+ }
+
+ encryptedFlagChanged(WalletAdapter::instance().changePassword("", password));
+ }
+ }
+}
+
+void MainWindow::aboutQt() {
+ QMessageBox::aboutQt(this);
+}
+
+void MainWindow::setStartOnLogin(bool _on) {
+ Settings::instance().setStartOnLoginEnabled(_on);
+ m_ui->m_startOnLoginAction->setChecked(Settings::instance().isStartOnLoginEnabled());
+}
+
+void MainWindow::setMinimizeToTray(bool _on) {
+#ifdef Q_OS_WIN
+ Settings::instance().setMinimizeToTrayEnabled(_on);
+#endif
+}
+
+void MainWindow::setCloseToTray(bool _on) {
+#ifdef Q_OS_WIN
+ Settings::instance().setCloseToTrayEnabled(_on);
+#endif
+}
+
+void MainWindow::about() {
+ AboutDialog dlg(this);
+ dlg.exec();
+}
+
+void MainWindow::setStatusBarText(const QString& _text) {
+ statusBar()->showMessage(_text);
+}
+
+void MainWindow::showMessage(const QString& _text, QtMsgType _type) {
+ switch (_type) {
+ case QtCriticalMsg:
+ QMessageBox::critical(this, tr("Wallet error"), _text);
+ break;
+ case QtDebugMsg:
+ QMessageBox::information(this, tr("Wallet"), _text);
+ break;
+ default:
+ break;
+ }
+}
+
+void MainWindow::askForWalletPassword(bool _error) {
+ PasswordDialog dlg(_error, this);
+ if (dlg.exec() == QDialog::Accepted) {
+ QString password = dlg.getPassword();
+ WalletAdapter::instance().open(password);
+ }
+}
+
+void MainWindow::encryptedFlagChanged(bool _encrypted) {
+ m_ui->m_encryptWalletAction->setEnabled(!_encrypted);
+ m_ui->m_changePasswordAction->setEnabled(_encrypted);
+ QString encryptionIconPath = _encrypted ? ":icons/encrypted" : ":icons/decrypted";
+ QPixmap encryptionIcon = QPixmap(encryptionIconPath).scaled(16, 16, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
+ m_encryptionStateIconLabel->setPixmap(encryptionIcon);
+ QString encryptionLabelTooltip = _encrypted ? tr("Encrypted") : tr("Not encrypted");
+ m_encryptionStateIconLabel->setToolTip(encryptionLabelTooltip);
+}
+
+void MainWindow::peerCountUpdated(quint64 _peerCount) {
+ QString connectionIconPath = _peerCount > 0 ? ":icons/connected" : ":icons/disconnected";
+ QPixmap connectionIcon = QPixmap(connectionIconPath).scaled(16, 16, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
+ m_connectionStateIconLabel->setPixmap(connectionIcon);
+ m_connectionStateIconLabel->setToolTip(QString(tr("%1 peers").arg(_peerCount)));
+}
+
+void MainWindow::walletSynchronizationInProgress() {
+ qobject_cast(m_synchronizationStateIconLabel)->startAnimation();
+ m_synchronizationStateIconLabel->setToolTip(tr("Synchronization in progress"));
+}
+
+void MainWindow::walletSynchronized(int _error, const QString& _error_text) {
+ QPixmap syncIcon = QPixmap(":icons/synced").scaled(16, 16, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
+ qobject_cast(m_synchronizationStateIconLabel)->stopAnimation();
+ m_synchronizationStateIconLabel->setPixmap(syncIcon);
+ QString syncLabelTooltip = _error > 0 ? tr("Not synchronized") : tr("Synchronized");
+ m_synchronizationStateIconLabel->setToolTip(syncLabelTooltip);
+}
+
+void MainWindow::walletOpened(bool _error, const QString& _error_text) {
+ if (!_error) {
+ m_encryptionStateIconLabel->show();
+ m_synchronizationStateIconLabel->show();
+ m_ui->m_backupWalletAction->setEnabled(true);
+ encryptedFlagChanged(Settings::instance().isEncrypted());
+
+ QList tabActions = m_tabActionGroup->actions();
+ Q_FOREACH(auto action, tabActions) {
+ action->setEnabled(true);
+ }
+
+ m_ui->m_overviewAction->trigger();
+ m_ui->m_overviewFrame->show();
+ } else {
+ walletClosed();
+ }
+}
+
+void MainWindow::walletClosed() {
+ m_ui->m_backupWalletAction->setEnabled(false);
+ m_ui->m_encryptWalletAction->setEnabled(false);
+ m_ui->m_changePasswordAction->setEnabled(false);
+ m_ui->m_overviewFrame->hide();
+ m_ui->m_sendFrame->hide();
+ m_ui->m_transactionsFrame->hide();
+ m_ui->m_addressBookFrame->hide();
+ m_encryptionStateIconLabel->hide();
+ m_synchronizationStateIconLabel->hide();
+ QList tabActions = m_tabActionGroup->actions();
+ Q_FOREACH(auto action, tabActions) {
+ action->setEnabled(false);
+ }
+}
+
+#ifdef Q_OS_WIN
+void MainWindow::trayActivated(QSystemTrayIcon::ActivationReason _reason) {
+ showNormal();
+ m_trayIcon->hide();
+}
+#endif
+
+}
diff --git a/src/gui/MainWindow.h b/src/gui/MainWindow.h
new file mode 100644
index 0000000..0ae5574
--- /dev/null
+++ b/src/gui/MainWindow.h
@@ -0,0 +1,87 @@
+// Copyright (c) 2011-2015 The Cryptonote developers
+// Distributed under the MIT/X11 software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+
+#pragma once
+
+#include
+#include
+#include
+#include
+
+class QActionGroup;
+
+namespace Ui {
+class MainWindow;
+}
+
+namespace WalletGui {
+
+class MainWindow : public QMainWindow {
+ Q_OBJECT
+ Q_DISABLE_COPY(MainWindow)
+
+public:
+ static MainWindow& instance();
+ void scrollToTransaction(const QModelIndex& _index);
+ void quit();
+
+protected:
+ void closeEvent(QCloseEvent* _event) Q_DECL_OVERRIDE;
+ bool event(QEvent* _event) Q_DECL_OVERRIDE;
+
+private:
+ QScopedPointer m_ui;
+ QLabel* m_connectionStateIconLabel;
+ QLabel* m_encryptionStateIconLabel;
+ QLabel* m_synchronizationStateIconLabel;
+ QSystemTrayIcon* m_trayIcon;
+ QActionGroup* m_tabActionGroup;
+ bool m_isAboutToQuit;
+
+ static MainWindow* m_instance;
+
+ MainWindow();
+ ~MainWindow();
+
+ void connectToSignals();
+ void initUi();
+
+ void minimizeToTray(bool _on);
+ void setStatusBarText(const QString& _text);
+ void showMessage(const QString& _text, QtMsgType _type);
+ void askForWalletPassword(bool _error);
+ void encryptedFlagChanged(bool _encrypted);
+ void peerCountUpdated(quint64 _peer_count);
+ void walletSynchronizationInProgress();
+ void walletSynchronized(int _error, const QString& _error_text);
+ void walletOpened(bool _error, const QString& _error_text);
+ void walletClosed();
+
+ Q_SLOT void createWallet();
+ Q_SLOT void openWallet();
+ Q_SLOT void backupWallet();
+ Q_SLOT void encryptWallet();
+ Q_SLOT void aboutQt();
+ Q_SLOT void about();
+ Q_SLOT void setStartOnLogin(bool _on);
+ Q_SLOT void setMinimizeToTray(bool _on);
+ Q_SLOT void setCloseToTray(bool _on);
+
+#ifdef Q_OS_MAC
+public:
+ void restoreFromDock();
+
+private:
+ void installDockHandler();
+#elif defined(Q_OS_WIN)
+protected:
+ void changeEvent(QEvent* _event) Q_DECL_OVERRIDE;
+
+private:
+ void trayActivated(QSystemTrayIcon::ActivationReason _reason);
+#endif
+};
+
+}
diff --git a/src/gui/MainWindow.mm b/src/gui/MainWindow.mm
new file mode 100644
index 0000000..146ff6e
--- /dev/null
+++ b/src/gui/MainWindow.mm
@@ -0,0 +1,30 @@
+// Copyright (c) 2011-2015 The Cryptonote developers
+// Distributed under the MIT/X11 software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+
+#import
+#import
+
+#include "mainwindow.h"
+
+namespace WalletGui {
+
+namespace {
+
+void dockClickHandler(id self, SEL _cmd) {
+ Q_UNUSED(self)
+ Q_UNUSED(_cmd)
+ MainWindow::instance().restoreFromDock();
+}
+
+}
+
+void MainWindow::installDockHandler() {
+ Class cls = [[[NSApplication sharedApplication] delegate] class];
+ if (!class_replaceMethod(cls, @selector(applicationShouldHandleReopen:hasVisibleWindows:), (IMP) dockClickHandler, "v@:")) {
+ NSLog(@"MainWindow::installDockHandler() : class_addMethod failed!");
+ }
+}
+
+}
diff --git a/src/gui/NewAddressDialog.cpp b/src/gui/NewAddressDialog.cpp
new file mode 100644
index 0000000..084d5cb
--- /dev/null
+++ b/src/gui/NewAddressDialog.cpp
@@ -0,0 +1,28 @@
+// Copyright (c) 2011-2015 The Cryptonote developers
+// Distributed under the MIT/X11 software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+
+#include "NewAddressDialog.h"
+
+#include "ui_newaddressdialog.h"
+
+namespace WalletGui {
+
+NewAddressDialog::NewAddressDialog(QWidget* _parent) : QDialog(_parent), m_ui(new Ui::NewAddressDialog) {
+ m_ui->setupUi(this);
+}
+
+NewAddressDialog::~NewAddressDialog() {
+}
+
+QString NewAddressDialog::getAddress() const {
+ return m_ui->m_addressEdit->text();
+}
+
+
+QString NewAddressDialog::getLabel() const {
+ return m_ui->m_labelEdit->text();
+}
+
+}
diff --git a/src/gui/NewAddressDialog.h b/src/gui/NewAddressDialog.h
new file mode 100644
index 0000000..152c377
--- /dev/null
+++ b/src/gui/NewAddressDialog.h
@@ -0,0 +1,31 @@
+// Copyright (c) 2011-2015 The Cryptonote developers
+// Distributed under the MIT/X11 software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+
+#pragma once
+
+#include
+
+namespace Ui {
+class NewAddressDialog;
+}
+
+namespace WalletGui {
+
+class NewAddressDialog : public QDialog {
+ Q_OBJECT
+ Q_DISABLE_COPY(NewAddressDialog)
+
+public:
+ NewAddressDialog(QWidget* _parent);
+ ~NewAddressDialog();
+
+ QString getAddress() const;
+ QString getLabel() const;
+
+private:
+ QScopedPointer m_ui;
+};
+
+}
diff --git a/src/gui/NewPasswordDialog.cpp b/src/gui/NewPasswordDialog.cpp
new file mode 100644
index 0000000..f169301
--- /dev/null
+++ b/src/gui/NewPasswordDialog.cpp
@@ -0,0 +1,32 @@
+// Copyright (c) 2011-2015 The Cryptonote developers
+// Distributed under the MIT/X11 software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+
+#include "NewPasswordDialog.h"
+
+#include "ui_newpassworddialog.h"
+
+namespace WalletGui {
+
+NewPasswordDialog::NewPasswordDialog(QWidget* _parent) : QDialog(_parent), m_ui(new Ui::NewPasswordDialog) {
+ m_ui->setupUi(this);
+ m_ui->m_errorLabel->setText("");
+}
+
+NewPasswordDialog::~NewPasswordDialog() {
+}
+
+QString NewPasswordDialog::getPassword() const {
+ return m_ui->m_passwordEdit->text();
+}
+
+void NewPasswordDialog::checkPassword(const QString& _password) {
+ bool passwordIsConfirmed = !(m_ui->m_passwordEdit->text().trimmed().isEmpty() ||
+ m_ui->m_passwordConfirmationEdit->text().trimmed().isEmpty() ||
+ m_ui->m_passwordEdit->text().compare(m_ui->m_passwordConfirmationEdit->text()));
+ m_ui->m_errorLabel->setText(passwordIsConfirmed ? "" : tr("Password not confirmed"));
+ m_ui->m_okButton->setEnabled(passwordIsConfirmed);
+}
+
+}
diff --git a/src/gui/NewPasswordDialog.h b/src/gui/NewPasswordDialog.h
new file mode 100644
index 0000000..f84e907
--- /dev/null
+++ b/src/gui/NewPasswordDialog.h
@@ -0,0 +1,32 @@
+// Copyright (c) 2011-2015 The Cryptonote developers
+// Distributed under the MIT/X11 software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+
+#pragma once
+
+#include
+
+namespace Ui {
+class NewPasswordDialog;
+}
+
+namespace WalletGui {
+
+class NewPasswordDialog : public QDialog {
+ Q_OBJECT
+ Q_DISABLE_COPY(NewPasswordDialog)
+
+public:
+ NewPasswordDialog(QWidget* _parent);
+ ~NewPasswordDialog();
+
+ QString getPassword() const;
+
+private:
+ QScopedPointer m_ui;
+
+ Q_SLOT void checkPassword(const QString& _password);
+};
+
+}
diff --git a/src/gui/OverviewFrame.cpp b/src/gui/OverviewFrame.cpp
new file mode 100644
index 0000000..0141677
--- /dev/null
+++ b/src/gui/OverviewFrame.cpp
@@ -0,0 +1,95 @@
+// Copyright (c) 2011-2015 The Cryptonote developers
+// Distributed under the MIT/X11 software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+
+#include "CurrencyAdapter.h"
+#include "OverviewFrame.h"
+#include "TransactionFrame.h"
+#include "RecentTransactionsModel.h"
+#include "WalletAdapter.h"
+
+#include "ui_overviewframe.h"
+
+namespace WalletGui {
+
+class RecentTransactionsDelegate : public QStyledItemDelegate {
+ Q_OBJECT
+
+public:
+ RecentTransactionsDelegate(QObject* _parent) : QStyledItemDelegate(_parent) {
+ }
+
+ ~RecentTransactionsDelegate() {
+ }
+
+ QWidget* createEditor(QWidget* _parent, const QStyleOptionViewItem& _option, const QModelIndex& _index) const Q_DECL_OVERRIDE {
+ if (!_index.isValid()) {
+ return nullptr;
+ }
+
+ return new TransactionFrame(_index, _parent);
+ }
+
+ QSize sizeHint(const QStyleOptionViewItem& _option, const QModelIndex& _index) const Q_DECL_OVERRIDE {
+ return QSize(346, 64);
+ }
+};
+
+OverviewFrame::OverviewFrame(QWidget* _parent) : QFrame(_parent), m_ui(new Ui::OverviewFrame), m_transactionModel(new RecentTransactionsModel) {
+ m_ui->setupUi(this);
+ connect(&WalletAdapter::instance(), &WalletAdapter::walletActualBalanceUpdatedSignal, this, &OverviewFrame::updateActualBalance,
+ Qt::QueuedConnection);
+ connect(&WalletAdapter::instance(), &WalletAdapter::walletPendingBalanceUpdatedSignal, this, &OverviewFrame::updatePendingBalance,
+ Qt::QueuedConnection);
+ connect(&WalletAdapter::instance(), &WalletAdapter::walletCloseCompletedSignal, this, &OverviewFrame::reset,
+ Qt::QueuedConnection);
+ connect(m_transactionModel.data(), &QAbstractItemModel::rowsInserted, this, &OverviewFrame::transactionsInserted);
+ connect(m_transactionModel.data(), &QAbstractItemModel::layoutChanged, this, &OverviewFrame::layoutChanged);
+
+ m_ui->m_tickerLabel1->setText(CurrencyAdapter::instance().getCurrencyTicker().toUpper());
+ m_ui->m_tickerLabel2->setText(CurrencyAdapter::instance().getCurrencyTicker().toUpper());
+ m_ui->m_tickerLabel3->setText(CurrencyAdapter::instance().getCurrencyTicker().toUpper());
+
+ m_ui->m_recentTransactionsView->setItemDelegate(new RecentTransactionsDelegate(this));
+ m_ui->m_recentTransactionsView->setModel(m_transactionModel.data());
+ reset();
+}
+
+OverviewFrame::~OverviewFrame() {
+}
+
+void OverviewFrame::transactionsInserted(const QModelIndex& _parent, int _first, int _last) {
+ for (quint32 i = _first; i <= _last; ++i) {
+ QModelIndex recentModelIndex = m_transactionModel->index(i, 0);
+ m_ui->m_recentTransactionsView->openPersistentEditor(recentModelIndex);
+ }
+}
+
+void OverviewFrame::layoutChanged() {
+ for (quint32 i = 0; i <= m_transactionModel->rowCount(); ++i) {
+ QModelIndex recent_index = m_transactionModel->index(i, 0);
+ m_ui->m_recentTransactionsView->openPersistentEditor(recent_index);
+ }
+}
+
+void OverviewFrame::updateActualBalance(quint64 _balance) {
+ m_ui->m_actualBalanceLabel->setText(CurrencyAdapter::instance().formatAmount(_balance));
+ quint64 pendingBalance = WalletAdapter::instance().getPendingBalance();
+ m_ui->m_totalBalanceLabel->setText(CurrencyAdapter::instance().formatAmount(_balance + pendingBalance));
+}
+
+void OverviewFrame::updatePendingBalance(quint64 _balance) {
+ m_ui->m_pendingBalanceLabel->setText(CurrencyAdapter::instance().formatAmount(_balance));
+ quint64 actualBalance = WalletAdapter::instance().getActualBalance();
+ m_ui->m_totalBalanceLabel->setText(CurrencyAdapter::instance().formatAmount(_balance + actualBalance));
+}
+
+void OverviewFrame::reset() {
+ updateActualBalance(0);
+ updatePendingBalance(0);
+}
+
+}
+
+#include "OverviewFrame.moc"
diff --git a/src/gui/OverviewFrame.h b/src/gui/OverviewFrame.h
new file mode 100644
index 0000000..e44456b
--- /dev/null
+++ b/src/gui/OverviewFrame.h
@@ -0,0 +1,39 @@
+// Copyright (c) 2011-2015 The Cryptonote developers
+// Distributed under the MIT/X11 software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+
+#pragma once
+
+#include
+#include
+
+namespace Ui {
+class OverviewFrame;
+}
+
+namespace WalletGui {
+
+class RecentTransactionsModel;
+
+class OverviewFrame : public QFrame {
+ Q_OBJECT
+ Q_DISABLE_COPY(OverviewFrame)
+
+public:
+ OverviewFrame(QWidget* _parent);
+ ~OverviewFrame();
+
+private:
+ QScopedPointer m_ui;
+ QSharedPointer m_transactionModel;
+
+ void transactionsInserted(const QModelIndex& _parent, int _first, int _last);
+ void transactionsRemoved(const QModelIndex& _parent, int _first, int _last);
+ void layoutChanged();
+ void updateActualBalance(quint64 _balance);
+ void updatePendingBalance(quint64 _balance);
+ void reset();
+};
+
+}
diff --git a/src/gui/PasswordDialog.cpp b/src/gui/PasswordDialog.cpp
new file mode 100644
index 0000000..bd1d982
--- /dev/null
+++ b/src/gui/PasswordDialog.cpp
@@ -0,0 +1,28 @@
+// Copyright (c) 2011-2015 The Cryptonote developers
+// Distributed under the MIT/X11 software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+
+#include "PasswordDialog.h"
+
+#include "ui_passworddialog.h"
+
+namespace WalletGui {
+
+PasswordDialog::PasswordDialog(bool _error, QWidget* _parent) : QDialog(_parent), m_ui(new Ui::PasswordDialog) {
+ m_ui->setupUi(this);
+ if (!_error) {
+ m_ui->m_errorLabel->hide();
+ }
+
+ adjustSize();
+}
+
+PasswordDialog::~PasswordDialog() {
+}
+
+QString PasswordDialog::getPassword() const {
+ return m_ui->m_passwordEdit->text();
+}
+
+}
diff --git a/src/gui/PasswordDialog.h b/src/gui/PasswordDialog.h
new file mode 100644
index 0000000..ee2c68c
--- /dev/null
+++ b/src/gui/PasswordDialog.h
@@ -0,0 +1,30 @@
+// Copyright (c) 2011-2015 The Cryptonote developers
+// Distributed under the MIT/X11 software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+
+#pragma once
+
+#include
+
+namespace Ui {
+class PasswordDialog;
+}
+
+namespace WalletGui {
+
+class PasswordDialog : public QDialog {
+ Q_OBJECT
+ Q_DISABLE_COPY(PasswordDialog)
+
+public:
+ PasswordDialog(bool _error, QWidget* _parent);
+ ~PasswordDialog();
+
+ QString getPassword() const;
+
+private:
+ QScopedPointer m_ui;
+};
+
+}
diff --git a/src/gui/ReceiveFrame.cpp b/src/gui/ReceiveFrame.cpp
new file mode 100644
index 0000000..b167cca
--- /dev/null
+++ b/src/gui/ReceiveFrame.cpp
@@ -0,0 +1,36 @@
+// Copyright (c) 2011-2015 The Cryptonote developers
+// Distributed under the MIT/X11 software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+
+#include
+
+#include "ReceiveFrame.h"
+#include "WalletAdapter.h"
+
+#include "ui_receiveframe.h"
+
+namespace WalletGui {
+
+ReceiveFrame::ReceiveFrame(QWidget* _parent) : QFrame(_parent), m_ui(new Ui::ReceiveFrame) {
+ m_ui->setupUi(this);
+ connect(&WalletAdapter::instance(), &WalletAdapter::updateWalletAddressSignal, this, &ReceiveFrame::updateWalletAddress);
+ connect(&WalletAdapter::instance(), &WalletAdapter::walletCloseCompletedSignal, this, &ReceiveFrame::reset);
+}
+
+ReceiveFrame::~ReceiveFrame() {
+}
+
+void ReceiveFrame::updateWalletAddress(const QString& _address) {
+ m_ui->m_addressEdit->setText(_address);
+}
+
+void ReceiveFrame::reset() {
+ m_ui->m_addressEdit->clear();
+}
+
+void ReceiveFrame::copyAddress() {
+ QApplication::clipboard()->setText(m_ui->m_addressEdit->text());
+}
+
+}
diff --git a/src/gui/ReceiveFrame.h b/src/gui/ReceiveFrame.h
new file mode 100644
index 0000000..c407acf
--- /dev/null
+++ b/src/gui/ReceiveFrame.h
@@ -0,0 +1,33 @@
+// Copyright (c) 2011-2015 The Cryptonote developers
+// Distributed under the MIT/X11 software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+
+#pragma once
+
+#include
+
+namespace Ui {
+class ReceiveFrame;
+}
+
+namespace WalletGui {
+
+class ReceiveFrame : public QFrame {
+ Q_OBJECT
+ Q_DISABLE_COPY(ReceiveFrame)
+
+public:
+ ReceiveFrame(QWidget* _parent);
+ ~ReceiveFrame();
+
+private:
+ QScopedPointer m_ui;
+
+ void updateWalletAddress(const QString& _address);
+ void reset();
+
+ Q_SLOT void copyAddress();
+};
+
+}
diff --git a/src/gui/RecentTransactionsModel.cpp b/src/gui/RecentTransactionsModel.cpp
new file mode 100644
index 0000000..1a0992c
--- /dev/null
+++ b/src/gui/RecentTransactionsModel.cpp
@@ -0,0 +1,37 @@
+// Copyright (c) 2011-2015 The Cryptonote developers
+// Distributed under the MIT/X11 software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+
+#include "RecentTransactionsModel.h"
+#include "SortedTransactionsModel.h"
+
+namespace WalletGui {
+
+RecentTransactionsModel::RecentTransactionsModel() : QSortFilterProxyModel() {
+ setSourceModel(&SortedTransactionsModel::instance());
+ setDynamicSortFilter(true);
+ connect(sourceModel(), &QAbstractItemModel::rowsInserted, this, &RecentTransactionsModel::invalidateFilter);
+}
+
+RecentTransactionsModel::~RecentTransactionsModel() {
+}
+
+int RecentTransactionsModel::columnCount(const QModelIndex& _parent) const {
+ return 1;
+}
+
+QVariant RecentTransactionsModel::data(const QModelIndex& _index, int _role) const {
+ if(_role == Qt::DecorationRole) {
+ return QVariant();
+ }
+
+ return QSortFilterProxyModel::data(_index, _role);
+}
+
+bool RecentTransactionsModel::filterAcceptsRow(int _sourceRow, const QModelIndex& _sourceParent) const {
+ return _sourceRow < 6;
+}
+
+
+}
diff --git a/src/gui/RecentTransactionsModel.h b/src/gui/RecentTransactionsModel.h
new file mode 100644
index 0000000..7a86cca
--- /dev/null
+++ b/src/gui/RecentTransactionsModel.h
@@ -0,0 +1,25 @@
+// Copyright (c) 2011-2015 The Cryptonote developers
+// Distributed under the MIT/X11 software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+
+#pragma once
+
+#include
+
+namespace WalletGui {
+
+class RecentTransactionsModel : public QSortFilterProxyModel {
+ Q_OBJECT
+ Q_DISABLE_COPY(RecentTransactionsModel)
+
+public:
+ RecentTransactionsModel();
+ ~RecentTransactionsModel();
+
+ int columnCount(const QModelIndex& _parent = QModelIndex()) const Q_DECL_OVERRIDE;
+ QVariant data(const QModelIndex& _index, int _role = Qt::DisplayRole) const Q_DECL_OVERRIDE;
+ bool filterAcceptsRow(int _source_row, const QModelIndex& _source_parent) const Q_DECL_OVERRIDE;
+};
+
+}
diff --git a/src/gui/SendFrame.cpp b/src/gui/SendFrame.cpp
new file mode 100644
index 0000000..9ac69ad
--- /dev/null
+++ b/src/gui/SendFrame.cpp
@@ -0,0 +1,114 @@
+// Copyright (c) 2011-2015 The Cryptonote developers
+// Distributed under the MIT/X11 software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+
+#include "AddressBookModel.h"
+#include "CurrencyAdapter.h"
+#include "MainWindow.h"
+#include "NodeAdapter.h"
+#include "SendFrame.h"
+#include "TransferFrame.h"
+#include "WalletAdapter.h"
+#include "WalletEvents.h"
+
+#include "ui_sendframe.h"
+
+namespace WalletGui {
+
+const quint64 MINIMAL_FEE = 1000000;
+
+SendFrame::SendFrame(QWidget* _parent) : QFrame(_parent), m_ui(new Ui::SendFrame) {
+ m_ui->setupUi(this);
+ clearAllClicked();
+ mixinValueChanged(m_ui->m_mixinSlider->value());
+
+ connect(&WalletAdapter::instance(), &WalletAdapter::walletSendTransactionCompletedSignal, this, &SendFrame::sendTransactionCompleted,
+ Qt::QueuedConnection);
+ connect(&WalletAdapter::instance(), &WalletAdapter::walletActualBalanceUpdatedSignal, this, &SendFrame::walletActualBalanceUpdated,
+ Qt::QueuedConnection);
+
+ m_ui->m_tickerLabel->setText(CurrencyAdapter::instance().getCurrencyTicker().toUpper());
+}
+
+SendFrame::~SendFrame() {
+}
+
+void SendFrame::addRecipientClicked() {
+ TransferFrame* newTransfer = new TransferFrame(m_ui->m_transfersScrollarea);
+ m_ui->m_send_frame_layout->insertWidget(m_transfers.size(), newTransfer);
+ m_transfers.append(newTransfer);
+ if (m_transfers.size() == 1) {
+ newTransfer->disableRemoveButton(true);
+ } else {
+ m_transfers[0]->disableRemoveButton(false);
+ }
+
+ connect(newTransfer, &TransferFrame::destroyed, [this](QObject* _obj) {
+ m_transfers.removeOne(static_cast(_obj));
+ if (m_transfers.size() == 1) {
+ m_transfers[0]->disableRemoveButton(true);
+ }
+ });
+}
+
+void SendFrame::clearAllClicked() {
+ Q_FOREACH (TransferFrame* transfer, m_transfers) {
+ transfer->close();
+ }
+
+ m_transfers.clear();
+ addRecipientClicked();
+ m_ui->m_paymentIdEdit->clear();
+ m_ui->m_mixinSlider->setValue(2);
+}
+
+void SendFrame::sendClicked() {
+ QVector walletTransfers;
+ Q_FOREACH (TransferFrame * transfer, m_transfers) {
+ QString address = transfer->getAddress();
+ if (!CurrencyAdapter::instance().validateAddress(address)) {
+ QCoreApplication::postEvent(
+ &MainWindow::instance(),
+ new ShowMessageEvent(tr("Invalid recipient address"), QtCriticalMsg));
+ return;
+ }
+
+ CryptoNote::Transfer walletTransfer;
+ walletTransfer.address = address.toStdString();
+ uint64_t amount = CurrencyAdapter::instance().parseAmount(transfer->getAmountString());
+ walletTransfer.amount = amount;
+ walletTransfers.push_back(walletTransfer);
+ QString label = transfer->getLabel();
+ if (!label.isEmpty()) {
+ AddressBookModel::instance().addAddress(label, address);
+ }
+ }
+
+ quint64 fee = MINIMAL_FEE;
+ if (fee < MINIMAL_FEE) {
+ QCoreApplication::postEvent(&MainWindow::instance(), new ShowMessageEvent(tr("Minimum allowed fee is 0.01"), QtCriticalMsg));
+ return;
+ }
+
+ if (WalletAdapter::instance().isOpen()) {
+ WalletAdapter::instance().sendTransaction(walletTransfers, fee, m_ui->m_paymentIdEdit->text(), m_ui->m_mixinSlider->value());
+ }
+}
+
+void SendFrame::mixinValueChanged(int _value) {
+ m_ui->m_mixinEdit->setText(QString::number(_value));
+}
+
+void SendFrame::sendTransactionCompleted(CryptoNote::TransactionId _id, bool _result, const QString& _errorText) {
+ Q_UNUSED(_id);
+ Q_UNUSED(_result);
+ Q_UNUSED(_errorText);
+ clearAllClicked();
+}
+
+void SendFrame::walletActualBalanceUpdated(quint64 _balance) {
+ m_ui->m_balanceLabel->setText(CurrencyAdapter::instance().formatAmount(_balance));
+}
+
+}
diff --git a/src/gui/SendFrame.h b/src/gui/SendFrame.h
new file mode 100644
index 0000000..eceac98
--- /dev/null
+++ b/src/gui/SendFrame.h
@@ -0,0 +1,41 @@
+// Copyright (c) 2011-2015 The Cryptonote developers
+// Distributed under the MIT/X11 software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+
+#pragma once
+
+#include
+
+#include
+
+namespace Ui {
+ class SendFrame;
+}
+
+namespace WalletGui {
+
+class TransferFrame;
+
+class SendFrame : public QFrame {
+ Q_OBJECT
+ Q_DISABLE_COPY(SendFrame)
+
+public:
+ SendFrame(QWidget* _parent);
+ ~SendFrame();
+
+private:
+ QScopedPointer m_ui;
+ QList m_transfers;
+
+ void sendTransactionCompleted(CryptoNote::TransactionId _id, bool _result, const QString& _error_text);
+ void walletActualBalanceUpdated(quint64 _balance);
+
+ Q_SLOT void addRecipientClicked();
+ Q_SLOT void clearAllClicked();
+ Q_SLOT void mixinValueChanged(int _value);
+ Q_SLOT void sendClicked();
+};
+
+}
diff --git a/src/gui/SortedTransactionsModel.cpp b/src/gui/SortedTransactionsModel.cpp
new file mode 100644
index 0000000..47fe856
--- /dev/null
+++ b/src/gui/SortedTransactionsModel.cpp
@@ -0,0 +1,45 @@
+// Copyright (c) 2011-2015 The Cryptonote developers
+// Distributed under the MIT/X11 software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+
+#include
+
+#include "SortedTransactionsModel.h"
+#include "TransactionsModel.h"
+
+namespace WalletGui {
+
+SortedTransactionsModel& SortedTransactionsModel::instance() {
+ static SortedTransactionsModel inst;
+ return inst;
+}
+
+SortedTransactionsModel::SortedTransactionsModel() : QSortFilterProxyModel() {
+ setSourceModel(&TransactionsModel::instance());
+ setDynamicSortFilter(true);
+ sort(TransactionsModel::COLUMN_DATE, Qt::DescendingOrder);
+}
+
+SortedTransactionsModel::~SortedTransactionsModel() {
+}
+
+bool SortedTransactionsModel::lessThan(const QModelIndex& _left, const QModelIndex& _right) const {
+ QDateTime leftDate = _left.data(TransactionsModel::ROLE_DATE).toDateTime();
+ QDateTime rightDate = _right.data(TransactionsModel::ROLE_DATE).toDateTime();
+ if((rightDate.isNull() || !rightDate.isValid()) && (leftDate.isNull() || !leftDate.isValid())) {
+ return _left.row() < _right.row();
+ }
+
+ if(leftDate.isNull() || !leftDate.isValid()) {
+ return false;
+ }
+
+ if(rightDate.isNull() || !rightDate.isValid()) {
+ return true;
+ }
+
+ return leftDate < rightDate;
+}
+
+}
diff --git a/src/gui/SortedTransactionsModel.h b/src/gui/SortedTransactionsModel.h
new file mode 100644
index 0000000..bd46883
--- /dev/null
+++ b/src/gui/SortedTransactionsModel.h
@@ -0,0 +1,29 @@
+// Copyright (c) 2011-2015 The Cryptonote developers
+// Distributed under the MIT/X11 software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+
+#pragma once
+
+#include
+
+namespace WalletGui {
+
+class SortedTransactionsModel : public QSortFilterProxyModel {
+ Q_OBJECT
+ Q_DISABLE_COPY(SortedTransactionsModel)
+
+public:
+ static SortedTransactionsModel& instance();
+
+protected:
+ bool lessThan(const QModelIndex& _left, const QModelIndex& _right) const Q_DECL_OVERRIDE;
+
+private:
+ SortedTransactionsModel();
+ ~SortedTransactionsModel();
+};
+
+
+
+}
diff --git a/src/gui/TransactionDetailsDialog.cpp b/src/gui/TransactionDetailsDialog.cpp
new file mode 100644
index 0000000..e3c3313
--- /dev/null
+++ b/src/gui/TransactionDetailsDialog.cpp
@@ -0,0 +1,46 @@
+// Copyright (c) 2011-2015 The Cryptonote developers
+// Distributed under the MIT/X11 software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+
+#include
+
+#include "CurrencyAdapter.h"
+#include "TransactionDetailsDialog.h"
+#include "TransactionsModel.h"
+
+#include "ui_transactiondetailsdialog.h"
+
+namespace WalletGui {
+
+TransactionDetailsDialog::TransactionDetailsDialog(const QModelIndex& _index, QWidget* _parent) : QDialog(_parent),
+ m_ui(new Ui::TransactionDetailsDialog), m_detailsTemplate(
+ "\n"
+ "Status: %1
\n"
+ "Date: %2
\n"
+ "To: %4
\n"
+ "Amount: %5
\n"
+ "Fee: %6
\n"
+ "Transaction hash: %8") {
+ m_ui->setupUi(this);
+
+ QModelIndex index = TransactionsModel::instance().index(_index.data(TransactionsModel::ROLE_ROW).toInt(),
+ _index.data(TransactionsModel::ROLE_ROW).toInt());
+
+ quint64 numberOfConfirmations = index.data(TransactionsModel::ROLE_NUMBER_OF_CONFIRMATIONS).value();
+ QString amountText = index.sibling(index.row(), TransactionsModel::COLUMN_AMOUNT).data().toString() + " " +
+ CurrencyAdapter::instance().getCurrencyTicker().toUpper();
+ QString feeText = CurrencyAdapter::instance().formatAmount(index.data(TransactionsModel::ROLE_FEE).value()) + " " +
+ CurrencyAdapter::instance().getCurrencyTicker().toUpper();
+
+ m_ui->m_detailsBrowser->setHtml(m_detailsTemplate.arg(QString("%1 confirmations").arg(numberOfConfirmations)).
+ arg(index.sibling(index.row(), TransactionsModel::COLUMN_DATE).data().toString()).arg(index.sibling(index.row(),
+ TransactionsModel::COLUMN_ADDRESS).data().toString()).arg(amountText).arg(feeText).
+ arg(index.sibling(index.row(), TransactionsModel::COLUMN_HASH).data().toString()));
+}
+
+TransactionDetailsDialog::~TransactionDetailsDialog() {
+}
+
+}
diff --git a/src/gui/TransactionDetailsDialog.h b/src/gui/TransactionDetailsDialog.h
new file mode 100644
index 0000000..0d116bb
--- /dev/null
+++ b/src/gui/TransactionDetailsDialog.h
@@ -0,0 +1,29 @@
+// Copyright (c) 2011-2015 The Cryptonote developers
+// Distributed under the MIT/X11 software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+
+#pragma once
+
+#include
+
+namespace Ui {
+class TransactionDetailsDialog;
+}
+
+namespace WalletGui {
+
+class TransactionDetailsDialog : public QDialog {
+ Q_OBJECT
+ Q_DISABLE_COPY(TransactionDetailsDialog)
+
+public:
+ TransactionDetailsDialog(const QModelIndex &_index, QWidget* _parent);
+ ~TransactionDetailsDialog();
+
+private:
+ QScopedPointer m_ui;
+ const QString m_detailsTemplate;
+};
+
+}
diff --git a/src/gui/TransactionFrame.cpp b/src/gui/TransactionFrame.cpp
new file mode 100644
index 0000000..3cf53ed
--- /dev/null
+++ b/src/gui/TransactionFrame.cpp
@@ -0,0 +1,68 @@
+// Copyright (c) 2011-2015 The Cryptonote developers
+// Distributed under the MIT/X11 software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+
+#include
+
+#include "MainWindow.h"
+#include "TransactionFrame.h"
+#include "TransactionsModel.h"
+
+#include "ui_transactionframe.h"
+
+namespace WalletGui {
+
+class RecentTransactionDelegate : public QStyledItemDelegate {
+ Q_OBJECT
+
+public:
+ RecentTransactionDelegate(QObject* _parent) : QStyledItemDelegate(_parent) {
+
+ }
+
+ ~RecentTransactionDelegate() {
+ }
+
+ void setEditorData(QWidget* _editor, const QModelIndex& _index) const Q_DECL_OVERRIDE {
+ switch(_index.column()) {
+ case TransactionsModel::COLUMN_AMOUNT:
+ case TransactionsModel::COLUMN_HASH:
+ case TransactionsModel::COLUMN_DATE:
+ static_cast(_editor)->setText(_index.data().toString());
+ return;
+ case TransactionsModel::COLUMN_TYPE:
+ static_cast(_editor)->setPixmap(_index.data(TransactionsModel::ROLE_ICON).value());
+ return;
+ default:
+ return;
+ }
+ }
+};
+
+TransactionFrame::TransactionFrame(const QModelIndex& _index, QWidget* _parent) : QFrame(_parent),
+ m_ui(new Ui::TransactionFrame), m_dataMapper(this), m_index(_index) {
+ m_ui->setupUi(this);
+ QFont font = QFontDatabase::systemFont(QFontDatabase::FixedFont);
+ font.setPixelSize(11);
+ m_ui->m_hashLabel->setFont(font);
+
+ m_dataMapper.setModel(const_cast(m_index.model()));
+ m_dataMapper.setItemDelegate(new RecentTransactionDelegate(this));
+ m_dataMapper.addMapping(m_ui->m_iconLabel, TransactionsModel::COLUMN_TYPE);
+ m_dataMapper.addMapping(m_ui->m_amountLabel, TransactionsModel::COLUMN_AMOUNT);
+ m_dataMapper.addMapping(m_ui->m_timeLabel, TransactionsModel::COLUMN_DATE);
+ m_dataMapper.addMapping(m_ui->m_hashLabel, TransactionsModel::COLUMN_HASH);
+ m_dataMapper.setCurrentModelIndex(m_index);
+}
+
+TransactionFrame::~TransactionFrame() {
+}
+
+void TransactionFrame::mousePressEvent(QMouseEvent* _event) {
+ MainWindow::instance().scrollToTransaction(TransactionsModel::instance().index(m_index.data(TransactionsModel::ROLE_ROW).toInt(), 0));
+}
+
+}
+
+#include "TransactionFrame.moc"
diff --git a/src/gui/TransactionFrame.h b/src/gui/TransactionFrame.h
new file mode 100644
index 0000000..ba5a971
--- /dev/null
+++ b/src/gui/TransactionFrame.h
@@ -0,0 +1,36 @@
+// Copyright (c) 2011-2015 The Cryptonote developers
+// Distributed under the MIT/X11 software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+
+#pragma once
+
+#include
+#include
+#include
+#include
+
+namespace Ui {
+class TransactionFrame;
+}
+
+namespace WalletGui {
+
+class TransactionFrame : public QFrame {
+ Q_OBJECT
+ Q_DISABLE_COPY(TransactionFrame)
+
+public:
+ TransactionFrame(const QModelIndex &_index, QWidget* _parent);
+ ~TransactionFrame();
+
+protected:
+ void mousePressEvent(QMouseEvent* _event) Q_DECL_OVERRIDE;
+
+private:
+ QScopedPointer m_ui;
+ QDataWidgetMapper m_dataMapper;
+ QPersistentModelIndex m_index;
+};
+
+}
diff --git a/src/gui/TransactionsFrame.cpp b/src/gui/TransactionsFrame.cpp
new file mode 100644
index 0000000..97a533d
--- /dev/null
+++ b/src/gui/TransactionsFrame.cpp
@@ -0,0 +1,62 @@
+// Copyright (c) 2011-2015 The Cryptonote developers
+// Distributed under the MIT/X11 software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+
+#include
+#include
+
+#include "MainWindow.h"
+#include "SortedTransactionsModel.h"
+#include "TransactionsFrame.h"
+#include "TransactionDetailsDialog.h"
+#include "TransactionsListModel.h"
+#include "TransactionsModel.h"
+
+#include "ui_transactionsframe.h"
+
+namespace WalletGui {
+
+TransactionsFrame::TransactionsFrame(QWidget* _parent) : QFrame(_parent), m_ui(new Ui::TransactionsFrame),
+ m_transactionsModel(new TransactionsListModel) {
+ m_ui->setupUi(this);
+ m_ui->m_transactionsView->setModel(m_transactionsModel.data());
+ m_ui->m_transactionsView->header()->setSectionResizeMode(TransactionsModel::COLUMN_STATE, QHeaderView::Fixed);
+ m_ui->m_transactionsView->header()->resizeSection(TransactionsModel::COLUMN_STATE, 25);
+ m_ui->m_transactionsView->header()->resizeSection(TransactionsModel::COLUMN_DATE, 140);
+ m_ui->m_transactionsView->header()->resizeSection(TransactionsModel::COLUMN_ADDRESS, 400);
+}
+
+TransactionsFrame::~TransactionsFrame() {
+}
+
+void TransactionsFrame::scrollToTransaction(const QModelIndex& _index) {
+ QModelIndex sortedModelIndex = SortedTransactionsModel::instance().mapFromSource(_index);
+ QModelIndex index = static_cast(m_ui->m_transactionsView->model())->mapFromSource(sortedModelIndex);
+ m_ui->m_transactionsView->scrollTo(index);
+ m_ui->m_transactionsView->setFocus();
+ m_ui->m_transactionsView->setCurrentIndex(index);
+}
+
+void TransactionsFrame::exportToCsv() {
+ QString file = QFileDialog::getSaveFileName(&MainWindow::instance(), tr("Select CSV file"), QDir::homePath(), "CSV (*.csv)");
+ if (!file.isEmpty()) {
+ QByteArray csv = TransactionsModel::instance().toCsv();
+ QFile f(file);
+ if (f.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
+ f.write(csv);
+ f.close();
+ }
+ }
+}
+
+void TransactionsFrame::showTransactionDetails(const QModelIndex& _index) {
+ if (!_index.isValid()) {
+ return;
+ }
+
+ TransactionDetailsDialog dlg(_index, &MainWindow::instance());
+ dlg.exec();
+}
+
+}
diff --git a/src/gui/TransactionsFrame.h b/src/gui/TransactionsFrame.h
new file mode 100644
index 0000000..7bbbddd
--- /dev/null
+++ b/src/gui/TransactionsFrame.h
@@ -0,0 +1,37 @@
+// Copyright (c) 2011-2015 The Cryptonote developers
+// Distributed under the MIT/X11 software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+
+#pragma once
+
+#include
+#include
+
+namespace Ui {
+class TransactionsFrame;
+}
+
+namespace WalletGui {
+
+class TransactionsListModel;
+
+class TransactionsFrame : public QFrame {
+ Q_OBJECT
+ Q_DISABLE_COPY(TransactionsFrame)
+
+public:
+ TransactionsFrame(QWidget* _parent);
+ ~TransactionsFrame();
+
+ void scrollToTransaction(const QModelIndex& _index);
+
+private:
+ QScopedPointer m_ui;
+ QScopedPointer m_transactionsModel;
+
+ Q_SLOT void exportToCsv();
+ Q_SLOT void showTransactionDetails(const QModelIndex& _index);
+};
+
+}
diff --git a/src/gui/TransactionsListModel.cpp b/src/gui/TransactionsListModel.cpp
new file mode 100644
index 0000000..1921a17
--- /dev/null
+++ b/src/gui/TransactionsListModel.cpp
@@ -0,0 +1,25 @@
+// Copyright (c) 2011-2015 The Cryptonote developers
+// Distributed under the MIT/X11 software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+
+#include "SortedTransactionsModel.h"
+#include "TransactionsListModel.h"
+#include "TransactionsModel.h"
+
+namespace WalletGui {
+
+TransactionsListModel::TransactionsListModel() : QSortFilterProxyModel() {
+ setSourceModel(&SortedTransactionsModel::instance());
+}
+
+TransactionsListModel::~TransactionsListModel() {
+}
+
+bool TransactionsListModel::filterAcceptsColumn(int _sourceColumn, const QModelIndex& _sourceParent) const {
+ quint32 column = sourceModel()->headerData(_sourceColumn, Qt::Horizontal, TransactionsModel::ROLE_COLUMN).toUInt();
+ return column != TransactionsModel::COLUMN_HASH && column != TransactionsModel::COLUMN_FEE && column != TransactionsModel::COLUMN_HEIGHT &&
+ column != TransactionsModel::COLUMN_TYPE;
+}
+
+}
diff --git a/src/gui/TransactionsListModel.h b/src/gui/TransactionsListModel.h
new file mode 100644
index 0000000..ead5f02
--- /dev/null
+++ b/src/gui/TransactionsListModel.h
@@ -0,0 +1,23 @@
+// Copyright (c) 2011-2015 The Cryptonote developers
+// Distributed under the MIT/X11 software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+
+#pragma once
+
+#include
+
+namespace WalletGui {
+
+class TransactionsListModel : public QSortFilterProxyModel {
+ Q_OBJECT
+
+public:
+ TransactionsListModel();
+ ~TransactionsListModel();
+
+protected:
+ bool filterAcceptsColumn(int _sourceColumn, const QModelIndex& _sourceParent) const Q_DECL_OVERRIDE;
+};
+
+}
diff --git a/src/gui/TransactionsModel.cpp b/src/gui/TransactionsModel.cpp
new file mode 100644
index 0000000..a0dc94e
--- /dev/null
+++ b/src/gui/TransactionsModel.cpp
@@ -0,0 +1,394 @@
+// Copyright (c) 2011-2015 The Cryptonote developers
+// Distributed under the MIT/X11 software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+
+#include
+#include
+#include
+#include
+
+#include "CurrencyAdapter.h"
+#include "NodeAdapter.h"
+#include "TransactionsModel.h"
+#include "WalletAdapter.h"
+
+namespace WalletGui {
+
+enum class TransactionType : quint8 {MINED, INPUT, OUTPUT, INOUT};
+
+const int TRANSACTIONS_MODEL_COLUMN_COUNT =
+ TransactionsModel::staticMetaObject.enumerator(TransactionsModel::staticMetaObject.indexOfEnumerator("Columns")).keyCount();
+
+namespace {
+
+QPixmap getTransactionIcon(TransactionType _transactionType) {
+ switch (_transactionType) {
+ case TransactionType::MINED:
+ return QPixmap(":icons/tx-mined");
+ case TransactionType::INPUT:
+ return QPixmap(":icons/tx-input");
+ case TransactionType::OUTPUT:
+ return QPixmap(":icons/tx-output");
+ case TransactionType::INOUT:
+ return QPixmap(":icons/tx-inout");
+ default:
+ break;
+ }
+
+ return QPixmap();
+}
+
+}
+
+TransactionsModel& TransactionsModel::instance() {
+ static TransactionsModel inst;
+ return inst;
+}
+
+TransactionsModel::TransactionsModel() : QAbstractItemModel() {
+ connect(&WalletAdapter::instance(), &WalletAdapter::reloadWalletTransactionsSignal, this, &TransactionsModel::reloadWalletTransactions,
+ Qt::QueuedConnection);
+ connect(&WalletAdapter::instance(), &WalletAdapter::walletTransactionCreatedSignal, this,
+ static_cast(&TransactionsModel::appendTransaction), Qt::QueuedConnection);
+ connect(&WalletAdapter::instance(), &WalletAdapter::walletTransactionUpdatedSignal, this, &TransactionsModel::updateWalletTransaction,
+ Qt::QueuedConnection);
+ connect(&NodeAdapter::instance(), &NodeAdapter::localBlockchainUpdatedSignal, this, &TransactionsModel::localBlockchainUpdated,
+ Qt::QueuedConnection);
+ connect(&WalletAdapter::instance(), &WalletAdapter::walletCloseCompletedSignal, this, &TransactionsModel::reset,
+ Qt::QueuedConnection);
+}
+
+TransactionsModel::~TransactionsModel() {
+}
+
+Qt::ItemFlags TransactionsModel::flags(const QModelIndex& _index) const {
+ Qt::ItemFlags flags = Qt::ItemIsEnabled | Qt::ItemNeverHasChildren | Qt::ItemIsSelectable;
+ if(_index.column() == COLUMN_HASH) {
+ flags |= Qt::ItemIsEditable;
+ }
+
+ return flags;
+}
+
+int TransactionsModel::columnCount(const QModelIndex& _parent) const {
+ return TRANSACTIONS_MODEL_COLUMN_COUNT;
+}
+
+int TransactionsModel::rowCount(const QModelIndex& _parent) const {
+ return m_transfers.size();
+}
+
+QVariant TransactionsModel::headerData(int _section, Qt::Orientation _orientation, int _role) const {
+ if(_orientation != Qt::Horizontal) {
+ return QVariant();
+ }
+
+ switch(_role) {
+ case Qt::DisplayRole:
+ switch(_section) {
+ case COLUMN_STATE:
+ return QVariant();
+ case COLUMN_DATE:
+ return tr("Date");
+ case COLUMN_TYPE:
+ return tr("Type");
+ case COLUMN_HASH:
+ return tr("Hash");
+ case COLUMN_ADDRESS:
+ return tr("Address");
+ case COLUMN_AMOUNT:
+ return tr("Amount");
+ case COLUMN_PAYMENT_ID:
+ return tr("PaymentID");
+ default:
+ break;
+ }
+
+ case Qt::TextAlignmentRole:
+ if (_section == COLUMN_AMOUNT) {
+ return static_cast(Qt::AlignRight | Qt::AlignVCenter);
+ }
+
+ return QVariant();
+
+ case ROLE_COLUMN:
+ return _section;
+ }
+
+ return QVariant();
+}
+
+QVariant TransactionsModel::data(const QModelIndex& _index, int _role) const {
+ if(!_index.isValid()) {
+ return QVariant();
+ }
+
+ CryptoNote::TransactionInfo transaction;
+ CryptoNote::Transfer transfer;
+ CryptoNote::TransactionId transactionId = m_transfers.value(_index.row()).first;
+ CryptoNote::TransferId transferId = m_transfers.value(_index.row()).second;
+
+ if(!WalletAdapter::instance().getTransaction(transactionId, transaction) ||
+ (m_transfers.value(_index.row()).second != CryptoNote::INVALID_TRANSFER_ID &&
+ !WalletAdapter::instance().getTransfer(transferId, transfer))) {
+ return QVariant();
+ }
+
+ switch(_role) {
+ case Qt::DisplayRole:
+ case Qt::EditRole:
+ return getDisplayRole(_index);
+
+ case Qt::DecorationRole:
+ return getDecorationRole(_index);
+
+ case Qt::TextAlignmentRole:
+ return getAlignmentRole(_index);
+
+ default:
+ return getUserRole(_index, _role, transactionId, transaction, transferId, transfer);
+ }
+
+ return QVariant();
+}
+
+QModelIndex TransactionsModel::index(int _row, int _column, const QModelIndex& _parent) const {
+ if(_parent.isValid()) {
+ return QModelIndex();
+ }
+
+ return createIndex(_row, _column, _row);
+}
+
+QModelIndex TransactionsModel::parent(const QModelIndex& _index) const {
+ return QModelIndex();
+}
+
+QByteArray TransactionsModel::toCsv() const {
+ QByteArray res;
+ res.append("\"State\",\"Date\",\"Amount\",\"Fee\",\"Hash\",\"Height\",\"Address\",\"Payment ID\"\n");
+ for (quint32 row = 0; row < rowCount(); ++row) {
+ QModelIndex ind = index(row, 0);
+ res.append("\"").append(ind.data().toString().toUtf8()).append("\",");
+ res.append("\"").append(ind.sibling(row, COLUMN_DATE).data().toString().toUtf8()).append("\",");
+ res.append("\"").append(ind.sibling(row, COLUMN_AMOUNT).data().toString().toUtf8()).append("\",");
+ res.append("\"").append(ind.sibling(row, COLUMN_FEE).data().toString().toUtf8()).append("\",");
+ res.append("\"").append(ind.sibling(row, COLUMN_HASH).data().toString().toUtf8()).append("\",");
+ res.append("\"").append(ind.sibling(row, COLUMN_HEIGHT).data().toString().toUtf8()).append("\",");
+ res.append("\"").append(ind.sibling(row, COLUMN_ADDRESS).data().toString().toUtf8()).append("\",");
+ res.append("\"").append(ind.sibling(row, COLUMN_PAYMENT_ID).data().toString().toUtf8()).append("\"\n");
+ }
+
+ return res;
+}
+
+QVariant TransactionsModel::getDisplayRole(const QModelIndex& _index) const {
+ switch(_index.column()) {
+ case COLUMN_DATE: {
+ QDateTime date = _index.data(ROLE_DATE).toDateTime();
+ return (date.isNull() || !date.isValid() ? "-" : date.toString("dd-MM-yy HH:mm"));
+ }
+
+ case COLUMN_HASH:
+ return _index.data(ROLE_HASH).toByteArray().toHex().toUpper();
+
+ case COLUMN_ADDRESS: {
+ TransactionType transactionType = static_cast(_index.data(ROLE_TYPE).value());
+ QString transactionAddress = _index.data(ROLE_ADDRESS).toString();
+ if (transactionType == TransactionType::INPUT || transactionType == TransactionType::MINED ||
+ transactionType == TransactionType::INOUT) {
+ return QString(tr("me (%1)").arg(WalletAdapter::instance().getAddress()));
+ } else if (transactionAddress.isEmpty()) {
+ return tr("(n/a)");
+ }
+
+ return transactionAddress;
+ }
+
+ case COLUMN_AMOUNT: {
+ qint64 amount = _index.data(ROLE_AMOUNT).value();
+ QString amountStr = CurrencyAdapter::instance().formatAmount(qAbs(amount));
+ return (amount < 0 ? "-" + amountStr : amountStr);
+ }
+
+ case COLUMN_PAYMENT_ID:
+ return _index.data(ROLE_PAYMENT_ID);
+
+ case COLUMN_FEE: {
+ qint64 fee = _index.data(ROLE_FEE).value();
+ return CurrencyAdapter::instance().formatAmount(fee);
+ }
+
+ case COLUMN_HEIGHT:
+ return QString::number(_index.data(ROLE_HEIGHT).value());
+
+ default:
+ break;
+ }
+
+ return QVariant();
+}
+
+QVariant TransactionsModel::getDecorationRole(const QModelIndex& _index) const {
+ if(_index.column() == COLUMN_STATE) {
+ quint64 numberOfConfirmations = _index.data(ROLE_NUMBER_OF_CONFIRMATIONS).value();
+ if(numberOfConfirmations == 0) {
+ return QPixmap(":icons/unconfirmed");
+ } else if(numberOfConfirmations < 2) {
+ return QPixmap(":icons/clock1");
+ } else if(numberOfConfirmations < 4) {
+ return QPixmap(":icons/clock2");
+ } else if(numberOfConfirmations < 6) {
+ return QPixmap(":icons/clock3");
+ } else if(numberOfConfirmations < 8) {
+ return QPixmap(":icons/clock4");
+ } else if(numberOfConfirmations < 10) {
+ return QPixmap(":icons/clock5");
+ } else {
+ return QPixmap(":icons/transaction");
+ }
+ } else if (_index.column() == COLUMN_ADDRESS) {
+ return _index.data(ROLE_ICON).value().scaled(20, 20, Qt::IgnoreAspectRatio, Qt::SmoothTransformation);
+ }
+
+ return QVariant();
+}
+
+QVariant TransactionsModel::getAlignmentRole(const QModelIndex& _index) const {
+ return headerData(_index.column(), Qt::Horizontal, Qt::TextAlignmentRole);
+}
+
+QVariant TransactionsModel::getUserRole(const QModelIndex& _index, int _role, CryptoNote::TransactionId _transactionId,
+ CryptoNote::TransactionInfo& _transaction, CryptoNote::TransferId _transferId, CryptoNote::Transfer& _transfer) const {
+ switch(_role) {
+ case ROLE_DATE:
+ return (_transaction.timestamp > 0 ? QDateTime::fromTime_t(_transaction.timestamp) : QDateTime());
+
+ case ROLE_TYPE: {
+ QString transactionAddress = _index.data(ROLE_ADDRESS).toString();
+ if(_transaction.isCoinbase) {
+ return static_cast(TransactionType::MINED);
+ } else if (!transactionAddress.compare(WalletAdapter::instance().getAddress())) {
+ return static_cast(TransactionType::INOUT);
+ } else if(_transaction.totalAmount < 0) {
+ return static_cast(TransactionType::OUTPUT);
+ }
+
+ return static_cast(TransactionType::INPUT);
+ }
+
+ case ROLE_HASH:
+ return QByteArray(reinterpret_cast(&_transaction.hash.front()), _transaction.hash.size());
+
+ case ROLE_ADDRESS:
+ return QString::fromStdString(_transfer.address);
+
+ case ROLE_AMOUNT:
+ return static_cast(_transferId == CryptoNote::INVALID_TRANSFER_ID ? _transaction.totalAmount : -_transfer.amount);
+
+ case ROLE_PAYMENT_ID:
+ return NodeAdapter::instance().extractPaymentId(_transaction.extra);
+
+ case ROLE_ICON: {
+ TransactionType transactionType = static_cast(_index.data(ROLE_TYPE).value());
+ return getTransactionIcon(transactionType);
+ }
+
+ case ROLE_TRANSACTION_ID:
+ return QVariant::fromValue(_transactionId);
+
+ case ROLE_HEIGHT:
+ return static_cast(_transaction.blockHeight);
+
+ case ROLE_FEE:
+ return static_cast(_transaction.fee);
+
+ case ROLE_NUMBER_OF_CONFIRMATIONS:
+ return (_transaction.blockHeight == CryptoNote::UNCONFIRMED_TRANSACTION_HEIGHT ? 0 :
+ NodeAdapter::instance().getLastKnownBlockHeight() - _transaction.blockHeight + 1);
+
+ case ROLE_COLUMN:
+ return headerData(_index.column(), Qt::Horizontal, ROLE_COLUMN);
+
+ case ROLE_ROW:
+ return _index.row();
+ }
+
+ return QVariant();
+}
+
+void TransactionsModel::reloadWalletTransactions() {
+ beginResetModel();
+ m_transfers.clear();
+ m_transactionRow.clear();
+ endResetModel();
+
+ quint32 row_count = 0;
+ for (CryptoNote::TransactionId transactionId = 0; transactionId < WalletAdapter::instance().getTransactionCount(); ++transactionId) {
+ appendTransaction(transactionId, row_count);
+ }
+
+ if (row_count > 0) {
+ beginInsertRows(QModelIndex(), 0, row_count - 1);
+ endInsertRows();
+ }
+}
+
+void TransactionsModel::appendTransaction(CryptoNote::TransactionId _transactionId, quint32& _insertedRowCount) {
+ CryptoNote::TransactionInfo transaction;
+ if (!WalletAdapter::instance().getTransaction(_transactionId, transaction)) {
+ return;
+ }
+
+ if (transaction.transferCount) {
+ m_transactionRow[_transactionId] = qMakePair(m_transfers.size(), transaction.transferCount);
+ for (CryptoNote::TransferId transfer_id = transaction.firstTransferId;
+ transfer_id < transaction.firstTransferId + transaction.transferCount; ++transfer_id) {
+ m_transfers.append(TransactionTransferId(_transactionId, transfer_id));
+ ++_insertedRowCount;
+ }
+ } else {
+ m_transfers.append(TransactionTransferId(_transactionId, CryptoNote::INVALID_TRANSFER_ID));
+ m_transactionRow[_transactionId] = qMakePair(m_transfers.size() - 1, 1);
+ ++_insertedRowCount;
+ }
+}
+
+void TransactionsModel::appendTransaction(CryptoNote::TransactionId _transactionId) {
+ if (m_transactionRow.contains(_transactionId)) {
+ return;
+ }
+
+ quint32 oldRowCount = rowCount();
+ quint32 insertedRowCount = 0;
+ for (quint64 transactionId = m_transactionRow.size(); transactionId <= _transactionId; ++transactionId) {
+ appendTransaction(transactionId, insertedRowCount);
+ }
+
+ if (insertedRowCount > 0) {
+ beginInsertRows(QModelIndex(), oldRowCount, oldRowCount + insertedRowCount - 1);
+ endInsertRows();
+ }
+}
+
+void TransactionsModel::updateWalletTransaction(CryptoNote::TransactionId _id) {
+ quint32 firstRow = m_transactionRow.value(_id).first;
+ quint32 lastRow = firstRow + m_transactionRow.value(_id).second - 1;
+ Q_EMIT dataChanged(index(firstRow, COLUMN_DATE), index(lastRow, COLUMN_DATE));
+}
+
+void TransactionsModel::localBlockchainUpdated(quint64 _height) {
+ if(rowCount() > 0) {
+ Q_EMIT dataChanged(index(0, COLUMN_STATE), index(rowCount() - 1, COLUMN_STATE));
+ }
+}
+
+void TransactionsModel::reset() {
+ beginResetModel();
+ m_transfers.clear();
+ m_transactionRow.clear();
+ endResetModel();
+}
+
+}
diff --git a/src/gui/TransactionsModel.h b/src/gui/TransactionsModel.h
new file mode 100644
index 0000000..883e9ea
--- /dev/null
+++ b/src/gui/TransactionsModel.h
@@ -0,0 +1,67 @@
+// Copyright (c) 2011-2015 The Cryptonote developers
+// Distributed under the MIT/X11 software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+
+#pragma once
+
+#include
+#include
+
+#include
+
+namespace WalletGui {
+
+typedef QPair TransactionTransferId;
+
+class TransactionsModel : public QAbstractItemModel {
+ Q_OBJECT
+ Q_ENUMS(Columns)
+ Q_ENUMS(Roles)
+
+public:
+ enum Columns{
+ COLUMN_STATE = 0, COLUMN_DATE, COLUMN_AMOUNT, COLUMN_ADDRESS, COLUMN_PAYMENT_ID, COLUMN_HASH, COLUMN_FEE,
+ COLUMN_HEIGHT, COLUMN_TYPE
+ };
+
+ enum Roles{
+ ROLE_DATE = Qt::UserRole, ROLE_TYPE, ROLE_HASH, ROLE_ADDRESS, ROLE_AMOUNT, ROLE_PAYMENT_ID, ROLE_ICON,
+ ROLE_TRANSACTION_ID, ROLE_HEIGHT, ROLE_FEE, ROLE_NUMBER_OF_CONFIRMATIONS, ROLE_COLUMN, ROLE_ROW
+ };
+
+ static TransactionsModel& instance();
+
+ Qt::ItemFlags flags(const QModelIndex& _index) const Q_DECL_OVERRIDE;
+ int columnCount(const QModelIndex& _parent = QModelIndex()) const Q_DECL_OVERRIDE;
+ int rowCount(const QModelIndex& _parent = QModelIndex()) const Q_DECL_OVERRIDE;
+
+ QVariant headerData(int _section, Qt::Orientation _orientation, int _role = Qt::DisplayRole) const Q_DECL_OVERRIDE;
+ QVariant data(const QModelIndex& _index, int _role = Qt::DisplayRole) const Q_DECL_OVERRIDE;
+ QModelIndex index(int _row, int _column, const QModelIndex& _parent = QModelIndex()) const Q_DECL_OVERRIDE;
+ QModelIndex parent(const QModelIndex& _index) const Q_DECL_OVERRIDE;
+
+ QByteArray toCsv() const;
+
+private:
+ QVector m_transfers;
+ QHash > m_transactionRow;
+
+ TransactionsModel();
+ ~TransactionsModel();
+
+ QVariant getDisplayRole(const QModelIndex& _index) const;
+ QVariant getDecorationRole(const QModelIndex& _index) const;
+ QVariant getAlignmentRole(const QModelIndex& _index) const;
+ QVariant getUserRole(const QModelIndex& _index, int _role, CryptoNote::TransactionId _transactionId, CryptoNote::TransactionInfo& _transaction,
+ CryptoNote::TransferId _transferId, CryptoNote::Transfer& _transfer) const;
+
+ void reloadWalletTransactions();
+ void appendTransaction(CryptoNote::TransactionId _id, quint32& _row_count);
+ void appendTransaction(CryptoNote::TransactionId _id);
+ void updateWalletTransaction(CryptoNote::TransactionId _id);
+ void localBlockchainUpdated(quint64 _height);
+ void reset();
+};
+
+}
diff --git a/src/gui/TransferFrame.cpp b/src/gui/TransferFrame.cpp
new file mode 100644
index 0000000..a19dc24
--- /dev/null
+++ b/src/gui/TransferFrame.cpp
@@ -0,0 +1,58 @@
+// Copyright (c) 2011-2015 The Cryptonote developers
+// Distributed under the MIT/X11 software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+
+#include
+#include
+
+#include "AddressBookDialog.h"
+#include "MainWindow.h"
+#include "CurrencyAdapter.h"
+#include "TransferFrame.h"
+
+#include "ui_transferframe.h"
+
+namespace WalletGui {
+
+TransferFrame::TransferFrame(QWidget* _parent) : QFrame(_parent), m_ui(new Ui::TransferFrame) {
+ m_ui->setupUi(this);
+ setAttribute(Qt::WA_DeleteOnClose);
+ m_ui->m_amountSpin->setSuffix(" " + CurrencyAdapter::instance().getCurrencyTicker().toUpper());
+}
+
+TransferFrame::~TransferFrame() {
+}
+
+QString TransferFrame::getAddress() const {
+ return m_ui->m_addressEdit->text().trimmed();
+}
+
+QString TransferFrame::getLabel() const {
+ return m_ui->m_labelEdit->text().trimmed();
+}
+
+qreal TransferFrame::getAmount() const {
+ return m_ui->m_amountSpin->value();
+}
+
+QString TransferFrame::getAmountString() const {
+ return m_ui->m_amountSpin->cleanText();
+}
+
+void TransferFrame::disableRemoveButton(bool _disable) {
+ m_ui->m_removeButton->setDisabled(_disable);
+}
+
+void TransferFrame::addressBookClicked() {
+ AddressBookDialog dlg(&MainWindow::instance());
+ if(dlg.exec() == QDialog::Accepted) {
+ m_ui->m_addressEdit->setText(dlg.getAddress());
+ }
+}
+
+void TransferFrame::pasteClicked() {
+ m_ui->m_addressEdit->setText(QApplication::clipboard()->text());
+}
+
+}
diff --git a/src/gui/TransferFrame.h b/src/gui/TransferFrame.h
new file mode 100644
index 0000000..e6bee51
--- /dev/null
+++ b/src/gui/TransferFrame.h
@@ -0,0 +1,38 @@
+// Copyright (c) 2011-2015 The Cryptonote developers
+// Distributed under the MIT/X11 software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+
+#pragma once
+
+#include
+
+namespace Ui {
+class TransferFrame;
+}
+
+namespace WalletGui {
+
+class TransferFrame : public QFrame {
+ Q_OBJECT
+ Q_DISABLE_COPY(TransferFrame)
+
+public:
+ TransferFrame(QWidget* _parent);
+ ~TransferFrame();
+
+ QString getAddress() const;
+ QString getLabel() const;
+ qreal getAmount() const;
+ QString getAmountString() const;
+
+ void disableRemoveButton(bool _disable);
+
+private:
+ QScopedPointer m_ui;
+
+ Q_SLOT void addressBookClicked();
+ Q_SLOT void pasteClicked();
+};
+
+}
diff --git a/src/gui/WalletEvents.h b/src/gui/WalletEvents.h
new file mode 100644
index 0000000..4e55a78
--- /dev/null
+++ b/src/gui/WalletEvents.h
@@ -0,0 +1,37 @@
+// Copyright (c) 2011-2015 The Cryptonote developers
+// Distributed under the MIT/X11 software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+
+#pragma once
+
+#include
+
+namespace WalletGui
+{
+ enum class WalletEventType : quint32 {
+ ShowMessage = QEvent::User
+ };
+
+ class ShowMessageEvent : public QEvent {
+ Q_DISABLE_COPY(ShowMessageEvent)
+
+ public:
+ ShowMessageEvent(const QString &_messageText, QtMsgType _messageType) : QEvent(static_cast(WalletEventType::ShowMessage)),
+ m_messageText(_messageText), m_messgaeType(_messageType) {
+ }
+
+ QString messageText() const {
+ return m_messageText;
+ }
+
+ QtMsgType messageType() const {
+ return m_messgaeType;
+ }
+
+ private:
+ QString m_messageText;
+ QtMsgType m_messgaeType;
+ };
+
+}
diff --git a/src/gui/ui/aboutdialog.ui b/src/gui/ui/aboutdialog.ui
new file mode 100644
index 0000000..ffc592f
--- /dev/null
+++ b/src/gui/ui/aboutdialog.ui
@@ -0,0 +1,102 @@
+
+
+ AboutDialog
+
+
+
+ 0
+ 0
+ 592
+ 240
+
+
+
+
+ 0
+ 0
+
+
+
+
+ 592
+ 240
+
+
+
+
+ 592
+ 240
+
+
+
+ About %1
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ <html><head/><body><p>CryptoNote coin version %1</p><p>CryptoNote coin is the next generation anonymous cryptocurrency based on CryptoNote.</p><p>Copyright (c) 2012-2015. CryptoNote Developers</p><p><a href="http://opensource.org/licenses/MIT"><span style=" text-decoration: underline; color:#0000ff;">http://opensource.org/licenses/MIT</span></a></p><p><a href="https://cryptonote.org/"><span style=" text-decoration: underline; color:#0000ff;">https://cryptonote.org/</span></a></p></body></html>
+
+
+ Qt::RichText
+
+
+ true
+
+
+ true
+
+
+
+ -
+
+
-
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+ Close
+
+
+
+
+
+
+
+
+
+
+ m_closeButton
+ clicked()
+ AboutDialog
+ accept()
+
+
+ 538
+ 285
+
+
+ 294
+ 156
+
+
+
+
+
diff --git a/src/gui/ui/addressbookdialog.ui b/src/gui/ui/addressbookdialog.ui
new file mode 100644
index 0000000..45f41ff
--- /dev/null
+++ b/src/gui/ui/addressbookdialog.ui
@@ -0,0 +1,81 @@
+
+
+ AddressBookDialog
+
+
+
+ 0
+ 0
+ 747
+ 525
+
+
+
+ Select address
+
+
+ -
+
+
+ -
+
+
-
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+ Choose
+
+
+
+
+
+
+
+
+
+
+ m_okButton
+ clicked()
+ AddressBookDialog
+ accept()
+
+
+ 694
+ 499
+
+
+ 373
+ 262
+
+
+
+
+ m_addressBookView
+ doubleClicked(QModelIndex)
+ AddressBookDialog
+ accept()
+
+
+ 373
+ 242
+
+
+ 373
+ 262
+
+
+
+
+
diff --git a/src/gui/ui/addressbookframe.ui b/src/gui/ui/addressbookframe.ui
new file mode 100644
index 0000000..43438f9
--- /dev/null
+++ b/src/gui/ui/addressbookframe.ui
@@ -0,0 +1,142 @@
+
+
+ AddressBookFrame
+
+
+
+ 0
+ 0
+ 874
+ 585
+
+
+
+ Frame
+
+
+ QFrame::StyledPanel
+
+
+ QFrame::Raised
+
+
+ -
+
+
+ -
+
+
-
+
+
+ New address
+
+
+
+ :/icons/add:/icons/add
+
+
+
+ -
+
+
+ false
+
+
+ Delete address
+
+
+
+ :/icons/remove:/icons/remove
+
+
+
+ -
+
+
+ false
+
+
+ Copy address
+
+
+
+ :/icons/copy:/icons/copy
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
+
+
+
+
+
+
+
+ m_addAddressButton
+ clicked()
+ AddressBookFrame
+ addClicked()
+
+
+ 53
+ 556
+
+
+ 436
+ 292
+
+
+
+
+ m_deleteAddressButton
+ clicked()
+ AddressBookFrame
+ deleteClicked()
+
+
+ 148
+ 556
+
+
+ 436
+ 292
+
+
+
+
+ m_copyAddressButton
+ clicked()
+ AddressBookFrame
+ copyClicked()
+
+
+ 243
+ 556
+
+
+ 436
+ 292
+
+
+
+
+
+ addClicked()
+ deleteClicked()
+ copyClicked()
+
+
diff --git a/src/gui/ui/changepassworddialog.ui b/src/gui/ui/changepassworddialog.ui
new file mode 100644
index 0000000..79526c7
--- /dev/null
+++ b/src/gui/ui/changepassworddialog.ui
@@ -0,0 +1,207 @@
+
+
+ ChangePasswordDialog
+
+
+
+ 0
+ 0
+ 338
+ 166
+
+
+
+
+ 0
+ 0
+
+
+
+
+ 338
+ 0
+
+
+
+
+ 338
+ 16777215
+
+
+
+ Change password
+
+
+ -
+
+
+ false
+
+
+ Ok
+
+
+ true
+
+
+
+ -
+
+
+ QLineEdit::Password
+
+
+
+ -
+
+
+ Old password:
+
+
+
+ -
+
+
+ QLineEdit::Password
+
+
+
+ -
+
+
+ Cancel
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ New password:
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ QLineEdit::Password
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Confirm:
+
+
+
+ -
+
+
+ color: #ff0000; font: 11px;
+
+
+ Password not confirmed
+
+
+ Qt::AlignCenter
+
+
+
+
+
+
+ m_oldPasswordEdit
+ m_newPasswordEdit
+ m_newPasswordConfirmationEdit
+ m_okButton
+ m_cancelButton
+
+
+
+
+ m_newPasswordEdit
+ textChanged(QString)
+ ChangePasswordDialog
+ checkPassword(QString)
+
+
+ 223
+ 64
+
+
+ 168
+ 82
+
+
+
+
+ m_newPasswordConfirmationEdit
+ textChanged(QString)
+ ChangePasswordDialog
+ checkPassword(QString)
+
+
+ 223
+ 103
+
+
+ 168
+ 82
+
+
+
+
+ m_okButton
+ clicked()
+ ChangePasswordDialog
+ accept()
+
+
+ 169
+ 141
+
+
+ 168
+ 82
+
+
+
+
+ m_cancelButton
+ clicked()
+ ChangePasswordDialog
+ reject()
+
+
+ 277
+ 141
+
+
+ 168
+ 82
+
+
+
+
+
+ checkPassword(QString)
+
+
diff --git a/src/gui/ui/exitwidget.ui b/src/gui/ui/exitwidget.ui
new file mode 100644
index 0000000..7aa24bc
--- /dev/null
+++ b/src/gui/ui/exitwidget.ui
@@ -0,0 +1,36 @@
+
+
+ ExitWidget
+
+
+
+ 0
+ 0
+ 385
+ 66
+
+
+
+ Saving data
+
+
+ -
+
+
+
+
+
+
+ -
+
+
+ %1 wallet is saving data.
+Please wait...
+
+
+
+
+
+
+
+
diff --git a/src/gui/ui/mainwindow.ui b/src/gui/ui/mainwindow.ui
new file mode 100644
index 0000000..6dd6806
--- /dev/null
+++ b/src/gui/ui/mainwindow.ui
@@ -0,0 +1,628 @@
+
+
+ MainWindow
+
+
+
+ 0
+ 0
+ 920
+ 600
+
+
+
+
+ 0
+ 0
+
+
+
+
+ 0
+ 0
+
+
+
+
+ 16777215
+ 16777215
+
+
+
+ MainWindow
+
+
+
+ :/images/cryptonote:/images/cryptonote
+
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+ -
+
+
+ QFrame::NoFrame
+
+
+ QFrame::Raised
+
+
+
+ -
+
+
+ QFrame::NoFrame
+
+
+ QFrame::Raised
+
+
+
+ -
+
+
+ QFrame::NoFrame
+
+
+ QFrame::Raised
+
+
+
+ -
+
+
+ QFrame::NoFrame
+
+
+ QFrame::Raised
+
+
+
+ -
+
+
+ QFrame::NoFrame
+
+
+ QFrame::Raised
+
+
+
+
+
+
+
+
+ toolBar
+
+
+ false
+
+
+ Qt::ToolButtonTextBesideIcon
+
+
+ TopToolBarArea
+
+
+ false
+
+
+
+
+
+
+
+
+
+
+ true
+
+
+ true
+
+
+
+ :/icons/overview:/icons/overview
+
+
+ Overview
+
+
+
+
+ true
+
+
+ true
+
+
+
+ :/icons/send:/icons/send
+
+
+ Send
+
+
+
+
+ true
+
+
+ true
+
+
+
+ :/icons/receive:/icons/receive
+
+
+ Receive
+
+
+
+
+ true
+
+
+ true
+
+
+
+ :/icons/transactions:/icons/transactions
+
+
+ Transactions
+
+
+
+
+ true
+
+
+ Exit
+
+
+ Ctrl+Q
+
+
+
+
+ true
+
+
+ true
+
+
+
+ :/icons/address-book:/icons/address-book
+
+
+ Address Book
+
+
+
+
+ true
+
+
+ Create wallet
+
+
+
+
+ true
+
+
+ Open wallet
+
+
+
+
+ true
+
+
+ Encrypt wallet
+
+
+
+
+ true
+
+
+ Change password
+
+
+ Change password
+
+
+
+
+ true
+
+
+ About
+
+
+
+
+ true
+
+
+ About Qt
+
+
+
+
+ true
+
+
+ Backup wallet
+
+
+
+
+ true
+
+
+ Start on system login
+
+
+
+
+ true
+
+
+ Minimize to tray
+
+
+
+
+ true
+
+
+ Close to tray
+
+
+
+
+
+ WalletGui::OverviewFrame
+ QFrame
+
+ 1
+
+
+ WalletGui::SendFrame
+ QFrame
+
+ 1
+
+
+ WalletGui::ReceiveFrame
+ QFrame
+
+ 1
+
+
+ WalletGui::TransactionsFrame
+ QFrame
+
+ 1
+
+
+ WalletGui::AddressBookFrame
+ QFrame
+
+ 1
+
+
+
+
+
+
+
+ m_overviewAction
+ toggled(bool)
+ m_overviewFrame
+ setVisible(bool)
+
+
+ -1
+ -1
+
+
+ 112
+ 333
+
+
+
+
+ m_sendAction
+ toggled(bool)
+ m_sendFrame
+ setVisible(bool)
+
+
+ -1
+ -1
+
+
+ 337
+ 333
+
+
+
+
+ m_receiveAction
+ toggled(bool)
+ m_receiveFrame
+ setVisible(bool)
+
+
+ -1
+ -1
+
+
+ 562
+ 333
+
+
+
+
+ m_transactionsAction
+ toggled(bool)
+ m_transactionsFrame
+ setVisible(bool)
+
+
+ -1
+ -1
+
+
+ 787
+ 333
+
+
+
+
+ m_addressBookAction
+ toggled(bool)
+ m_addressBookFrame
+ setVisible(bool)
+
+
+ -1
+ -1
+
+
+ 809
+ 320
+
+
+
+
+ m_createWalletAction
+ triggered()
+ MainWindow
+ createWallet()
+
+
+ -1
+ -1
+
+
+ 449
+ 299
+
+
+
+
+ m_openWalletAction
+ triggered()
+ MainWindow
+ openWallet()
+
+
+ -1
+ -1
+
+
+ 449
+ 299
+
+
+
+
+ m_encryptWalletAction
+ triggered()
+ MainWindow
+ encryptWallet()
+
+
+ -1
+ -1
+
+
+ 449
+ 299
+
+
+
+
+ m_changePasswordAction
+ triggered()
+ MainWindow
+ encryptWallet()
+
+
+ -1
+ -1
+
+
+ 449
+ 299
+
+
+
+
+ m_aboutQtAction
+ triggered()
+ MainWindow
+ aboutQt()
+
+
+ -1
+ -1
+
+
+ 449
+ 299
+
+
+
+
+ m_backupWalletAction
+ triggered()
+ MainWindow
+ backupWallet()
+
+
+ -1
+ -1
+
+
+ 449
+ 299
+
+
+
+
+ m_aboutCryptonoteAction
+ triggered()
+ MainWindow
+ about()
+
+
+ -1
+ -1
+
+
+ 459
+ 299
+
+
+
+
+ m_startOnLoginAction
+ triggered(bool)
+ MainWindow
+ setStartOnLogin(bool)
+
+
+ -1
+ -1
+
+
+ 459
+ 299
+
+
+
+
+ m_minimizeToTrayAction
+ triggered(bool)
+ MainWindow
+ setMinimizeToTray(bool)
+
+
+ -1
+ -1
+
+
+ 459
+ 299
+
+
+
+
+ m_closeToTrayAction
+ triggered(bool)
+ MainWindow
+ setCloseToTray(bool)
+
+
+ -1
+ -1
+
+
+ 459
+ 299
+
+
+
+
+
+ createWallet()
+ openWallet()
+ encryptWallet()
+ aboutQt()
+ backupWallet()
+ about()
+ setStartOnLogin(bool)
+ setMinimizeToTray(bool)
+ setCloseToTray(bool)
+
+
diff --git a/src/gui/ui/newaddressdialog.ui b/src/gui/ui/newaddressdialog.ui
new file mode 100644
index 0000000..758eef1
--- /dev/null
+++ b/src/gui/ui/newaddressdialog.ui
@@ -0,0 +1,133 @@
+
+
+ NewAddressDialog
+
+
+
+ 0
+ 0
+ 590
+ 127
+
+
+
+ New address
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Label:
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ QLineEdit::Normal
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Address:
+
+
+
+ -
+
+
+ QLineEdit::Normal
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 338
+ 20
+
+
+
+
+ -
+
+
+ true
+
+
+ Ok
+
+
+ true
+
+
+
+ -
+
+
+ Cancel
+
+
+
+
+
+
+
+
+ m_okButton
+ clicked()
+ NewAddressDialog
+ accept()
+
+
+ 410
+ 102
+
+
+ 294
+ 63
+
+
+
+
+ m_cancelButton
+ clicked()
+ NewAddressDialog
+ reject()
+
+
+ 526
+ 102
+
+
+ 294
+ 63
+
+
+
+
+
diff --git a/src/gui/ui/newpassworddialog.ui b/src/gui/ui/newpassworddialog.ui
new file mode 100644
index 0000000..14badb2
--- /dev/null
+++ b/src/gui/ui/newpassworddialog.ui
@@ -0,0 +1,180 @@
+
+
+ NewPasswordDialog
+
+
+
+ 0
+ 0
+ 338
+ 133
+
+
+
+
+ 0
+ 0
+
+
+
+ Enter password
+
+
+ -
+
+
+ false
+
+
+ Ok
+
+
+ true
+
+
+
+ -
+
+
+ Cancel
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Password:
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ QLineEdit::Password
+
+
+
+ -
+
+
+ QLineEdit::Password
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Confirm:
+
+
+
+ -
+
+
+ color: #ff0000; font: 11px;
+
+
+ Password not confirmed
+
+
+ Qt::AlignCenter
+
+
+
+
+
+
+ m_passwordEdit
+ m_passwordConfirmationEdit
+ m_okButton
+ m_cancelButton
+
+
+
+
+ m_okButton
+ clicked()
+ NewPasswordDialog
+ accept()
+
+
+ 147
+ 102
+
+
+ 147
+ 63
+
+
+
+
+ m_cancelButton
+ clicked()
+ NewPasswordDialog
+ reject()
+
+
+ 241
+ 102
+
+
+ 147
+ 63
+
+
+
+
+ m_passwordEdit
+ textChanged(QString)
+ NewPasswordDialog
+ checkPassword(QString)
+
+
+ 206
+ 25
+
+
+ 168
+ 64
+
+
+
+
+ m_passwordConfirmationEdit
+ textChanged(QString)
+ NewPasswordDialog
+ checkPassword(QString)
+
+
+ 206
+ 64
+
+
+ 168
+ 64
+
+
+
+
+
+ checkPassword(QString)
+
+
diff --git a/src/gui/ui/overviewframe.ui b/src/gui/ui/overviewframe.ui
new file mode 100644
index 0000000..7b5b99f
--- /dev/null
+++ b/src/gui/ui/overviewframe.ui
@@ -0,0 +1,290 @@
+
+
+ OverviewFrame
+
+
+
+ 0
+ 0
+ 866
+ 590
+
+
+
+ Frame
+
+
+ QFrame::StyledPanel
+
+
+ QFrame::Raised
+
+
+
+ QLayout::SetDefaultConstraint
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 5
+
+
+ 0
+
+ -
+
+
+
+ 0
+ 160
+
+
+
+
+ 16777215
+ 160
+
+
+
+ QFrame::NoFrame
+
+
+ QFrame::Raised
+
+
+
-
+
+
+
+ 75
+ true
+
+
+
+ 0.00
+
+
+
+ -
+
+
+
+ 75
+ true
+
+
+
+ 0.00
+
+
+
+ -
+
+
+ Total:
+
+
+
+ -
+
+
+
+
+
+
+ -
+
+
+
+ 75
+ true
+
+
+
+ Wallet
+
+
+
+ -
+
+
+ Unconfirmed:
+
+
+
+ -
+
+
+ Balance:
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ -
+
+
+
+ 75
+ true
+
+
+
+ 0.00
+
+
+
+ -
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+ QSizePolicy::Expanding
+
+
+
+ 20
+ 425
+
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 16777215
+ 16777215
+
+
+
+ QFrame::NoFrame
+
+
+ QFrame::Raised
+
+
+
-
+
+
-
+
+
+
+ 75
+ true
+
+
+
+ Recent transactions
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
+ -
+
+
+ Qt::NoFocus
+
+
+ background-color: transparent;
+
+
+ QFrame::NoFrame
+
+
+ QAbstractItemView::NoEditTriggers
+
+
+ QAbstractItemView::NoSelection
+
+
+ false
+
+
+ true
+
+
+ false
+
+
+ true
+
+
+ false
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/gui/ui/passworddialog.ui b/src/gui/ui/passworddialog.ui
new file mode 100644
index 0000000..4a65b8d
--- /dev/null
+++ b/src/gui/ui/passworddialog.ui
@@ -0,0 +1,131 @@
+
+
+ PasswordDialog
+
+
+
+ 0
+ 0
+ 338
+ 103
+
+
+
+
+ 338
+ 0
+
+
+
+
+ 338
+ 16777215
+
+
+
+ Enter password
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ QLineEdit::Password
+
+
+
+ -
+
+
+ Cancel
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+ Password:
+
+
+
+ -
+
+
+ true
+
+
+ Ok
+
+
+ true
+
+
+
+ -
+
+
+
+ 122
+ 0
+
+
+
+ color: #ff0000; font: 11px;
+
+
+ Wrong password
+
+
+ Qt::AlignCenter
+
+
+
+
+
+
+
+
+ m_okButton
+ clicked()
+ PasswordDialog
+ accept()
+
+
+ 142
+ 84
+
+
+ 158
+ 52
+
+
+
+
+ m_cancelButton
+ clicked()
+ PasswordDialog
+ reject()
+
+
+ 257
+ 84
+
+
+ 158
+ 52
+
+
+
+
+
diff --git a/src/gui/ui/receiveframe.ui b/src/gui/ui/receiveframe.ui
new file mode 100644
index 0000000..8071d82
--- /dev/null
+++ b/src/gui/ui/receiveframe.ui
@@ -0,0 +1,91 @@
+
+
+ ReceiveFrame
+
+
+
+ 0
+ 0
+ 846
+ 592
+
+
+
+ Frame
+
+
+ QFrame::StyledPanel
+
+
+ QFrame::Raised
+
+
+ -
+
+
-
+
+
+ Address:
+
+
+
+ -
+
+
+ true
+
+
+
+ -
+
+
+ ...
+
+
+
+ :/icons/copy:/icons/copy
+
+
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+
+
+
+
+
+
+
+ m_copyButton
+ clicked()
+ ReceiveFrame
+ copyAddress()
+
+
+ 818
+ 26
+
+
+ 422
+ 295
+
+
+
+
+
+ copyAddress()
+
+
diff --git a/src/gui/ui/sendframe.ui b/src/gui/ui/sendframe.ui
new file mode 100644
index 0000000..69040c6
--- /dev/null
+++ b/src/gui/ui/sendframe.ui
@@ -0,0 +1,320 @@
+
+
+ SendFrame
+
+
+
+ 0
+ 0
+ 866
+ 590
+
+
+
+ Frame
+
+
+ QFrame::StyledPanel
+
+
+ QFrame::Raised
+
+
+ -
+
+
+ QFrame::NoFrame
+
+
+ true
+
+
+
+
+ 0
+ 0
+ 840
+ 446
+
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
-
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 445
+
+
+
+
+
+
+
+
+ -
+
+
+ 5
+
+
-
+
+
+
+ 200
+ 16777215
+
+
+
+ 10
+
+
+ 1
+
+
+ 2
+
+
+ false
+
+
+ Qt::Horizontal
+
+
+ QSlider::TicksBothSides
+
+
+ 1
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::Expanding
+
+
+
+ 298
+ 20
+
+
+
+
+ -
+
+
+ PaymentID:
+
+
+
+ -
+
+
+
+ 30
+ 16777215
+
+
+
+
+
+
+ 5
+
+
+ Qt::AlignCenter
+
+
+ true
+
+
+
+ -
+
+
+ -
+
+
+ Anonimity level:
+
+
+
+
+
+ -
+
+
-
+
+
+ Send
+
+
+
+ :/icons/send:/icons/send
+
+
+
+ -
+
+
+ Clear All
+
+
+
+ :/icons/remove:/icons/remove
+
+
+
+ -
+
+
+ Add Recipient
+
+
+
+ :/icons/add:/icons/add
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+ Balance:
+
+
+
+ -
+
+
+ 0.00
+
+
+
+ -
+
+
+
+
+
+
+
+
+
+
+
+ m_paymentIdEdit
+ m_mixinSlider
+ m_sendButton
+ m_clearAllButton
+ m_addRecipientButton
+ m_transfersScrollarea
+ m_mixinEdit
+
+
+
+
+
+
+ m_addRecipientButton
+ clicked()
+ SendFrame
+ addRecipientClicked()
+
+
+ 282
+ 561
+
+
+ 432
+ 294
+
+
+
+
+ m_clearAllButton
+ clicked()
+ SendFrame
+ clearAllClicked()
+
+
+ 154
+ 561
+
+
+ 432
+ 294
+
+
+
+
+ m_sendButton
+ clicked()
+ SendFrame
+ sendClicked()
+
+
+ 53
+ 561
+
+
+ 432
+ 294
+
+
+
+
+ m_mixinSlider
+ valueChanged(int)
+ SendFrame
+ mixinValueChanged(int)
+
+
+ 221
+ 520
+
+
+ 432
+ 294
+
+
+
+
+
+ sendClicked()
+ clearAllClicked()
+ addRecipientClicked()
+ mixinValueChanged(int)
+
+
diff --git a/src/gui/ui/transactiondetailsdialog.ui b/src/gui/ui/transactiondetailsdialog.ui
new file mode 100644
index 0000000..4b324e1
--- /dev/null
+++ b/src/gui/ui/transactiondetailsdialog.ui
@@ -0,0 +1,65 @@
+
+
+ TransactionDetailsDialog
+
+
+
+ 0
+ 0
+ 736
+ 357
+
+
+
+ Transaction details
+
+
+ -
+
+
+ -
+
+
-
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+ Close
+
+
+
+
+
+
+
+
+
+
+ m_closeButton
+ clicked()
+ TransactionDetailsDialog
+ accept()
+
+
+ 619
+ 331
+
+
+ 335
+ 178
+
+
+
+
+
diff --git a/src/gui/ui/transactionframe.ui b/src/gui/ui/transactionframe.ui
new file mode 100644
index 0000000..f5d70a6
--- /dev/null
+++ b/src/gui/ui/transactionframe.ui
@@ -0,0 +1,156 @@
+
+
+ TransactionFrame
+
+
+
+ 0
+ 0
+ 346
+ 64
+
+
+
+
+ 0
+ 64
+
+
+
+
+ 540
+ 64
+
+
+
+ Frame
+
+
+ true
+
+
+ QFrame::NoFrame
+
+
+ QFrame::Raised
+
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+
+ 0
+
+ -
+
+
+
+
+
+
+ -
+
+
+ 0
+
+
-
+
+
+ Qt::Vertical
+
+
+ QSizePolicy::Fixed
+
+
+
+ 20
+ 10
+
+
+
+
+ -
+
+
+ 20
+
+
-
+
+
+ TextLabel
+
+
+
+ -
+
+
+ TextLabel
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+
+
+
+ -
+
+
+
+
+
+ TextLabel
+
+
+ Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter
+
+
+ -1
+
+
+ Qt::NoTextInteraction
+
+
+
+ -
+
+
+ Qt::Vertical
+
+
+ QSizePolicy::Fixed
+
+
+
+ 20
+ 10
+
+
+
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+
+
+
+
+
diff --git a/src/gui/ui/transactionsframe.ui b/src/gui/ui/transactionsframe.ui
new file mode 100644
index 0000000..e4868c4
--- /dev/null
+++ b/src/gui/ui/transactionsframe.ui
@@ -0,0 +1,125 @@
+
+
+ TransactionsFrame
+
+
+
+ 0
+ 0
+ 830
+ 614
+
+
+
+ Frame
+
+
+ QFrame::StyledPanel
+
+
+ QFrame::Raised
+
+
+ -
+
+
+ QAbstractScrollArea::AdjustIgnored
+
+
+ QAbstractItemView::AnyKeyPressed|QAbstractItemView::CurrentChanged|QAbstractItemView::EditKeyPressed|QAbstractItemView::SelectedClicked
+
+
+ true
+
+
+ false
+
+
+ true
+
+
+ false
+
+
+ false
+
+
+ true
+
+
+ false
+
+
+
+ -
+
+
-
+
+
+ Qt::Horizontal
+
+
+
+ 40
+ 20
+
+
+
+
+ -
+
+
+ Export
+
+
+
+ :/icons/export:/icons/export
+
+
+
+
+
+
+
+
+
+
+
+
+ m_csvButton
+ clicked()
+ TransactionsFrame
+ exportToCsv()
+
+
+ 761
+ 587
+
+
+ 414
+ 306
+
+
+
+
+ m_transactionsView
+ doubleClicked(QModelIndex)
+ TransactionsFrame
+ showTransactionDetails(QModelIndex)
+
+
+ 414
+ 287
+
+
+ 414
+ 306
+
+
+
+
+
+ exportToCsv()
+ showTransactionDetails(QModelIndex)
+
+
diff --git a/src/gui/ui/transferframe.ui b/src/gui/ui/transferframe.ui
new file mode 100644
index 0000000..c68ec8a
--- /dev/null
+++ b/src/gui/ui/transferframe.ui
@@ -0,0 +1,195 @@
+
+
+ TransferFrame
+
+
+
+ 0
+ 0
+ 885
+ 121
+
+
+
+ Frame
+
+
+ QFrame::Box
+
+
+ QFrame::Sunken
+
+
+
+ 5
+
+
+ 5
+
+
+ 5
+
+
+ 5
+
+ -
+
+
+ Pay To:
+
+
+
+ -
+
+
+ ...
+
+
+
+ :/icons/address-book:/icons/address-book
+
+
+
+ -
+
+
+ ...
+
+
+
+ :/icons/paste:/icons/paste
+
+
+
+ -
+
+
+ Enter a label for this address to add it to your address book
+
+
+
+ -
+
+
+ Amount:
+
+
+
+ -
+
+
+ ...
+
+
+
+ :/icons/remove:/icons/remove
+
+
+
+ -
+
+
+ Qt::Horizontal
+
+
+
+ 558
+ 17
+
+
+
+
+ -
+
+
+ Label:
+
+
+
+ -
+
+
+ Qt::LeftToRight
+
+
+
+
+
+ 8
+
+
+ 999999999.999999046325684
+
+
+
+ -
+
+
+
+
+
+ m_addressEdit
+ m_labelEdit
+ m_amountSpin
+ m_addressBookButton
+ m_pasteButton
+ m_removeButton
+
+
+
+
+
+
+ m_removeButton
+ clicked()
+ TransferFrame
+ close()
+
+
+ 862
+ 21
+
+
+ 442
+ 60
+
+
+
+
+ m_addressBookButton
+ clicked()
+ TransferFrame
+ addressBookClicked()
+
+
+ 786
+ 21
+
+
+ 442
+ 60
+
+
+
+
+ m_pasteButton
+ clicked()
+ TransferFrame
+ pasteClicked()
+
+
+ 824
+ 21
+
+
+ 442
+ 60
+
+
+
+
+
+ addressBookClicked()
+ pasteClicked()
+
+
diff --git a/src/icons/add.png b/src/icons/add.png
new file mode 100644
index 0000000000000000000000000000000000000000..ea53fc3493dde873101127e3e9f37edfdad968c7
GIT binary patch
literal 1112
zcmV-e1gHCnP)#Jvax;q&S5d;hob*B=^PIvC(Ha|eYUog1SRZMp3Dhi@|NfZS%*&36G
zfeb{%7(xaW<4nkC_w;;K-Fqw!WpNpr`GD!uoIa=S=6&va?(Ht&0Fi&5o<-zMQ4vva
z!QpU{kxEKNqKT}HN8bP!*k`2dGt$qJ?wONse6aA+!m-@t1eAcMvaCN+cX+Nn`2Lj(
zm(G0+{Bo!O2**x6acZU`9V#_RJSJ|>UaaTD{D}_m%%KA)5HzqIwj1$4McLL$8=O$l
z#$f^o;Da}Tir4{@w{chvVLAY)0Ynb?*7_L8tDhEtPy=Wg%AIF`Ac4}x;SExB8Y2i0
zf~WLlBpy|1s;>Z<27pj`PvZbV>Rzi@+^UUWY7Tg>rQbHIAa)B52GIE^f1zLW
zK7RG-*N!QwN+4hziW0Q`po9>T`C>jVeTBy)P1=N`F%10(=Jb7>yekR>06~Hk)D}T1
zS$F@)+cNuM=gW()Up({N(dX>|d9_{OHQ=q9L|Z4#zY#YFt~~yZYirlgL?NNBK%!PP
z{eMl-3c8nzuhLA@BPR#XQiZb4MyLU?d#|>JB|9MEUjeDOHTsF0t2Yq`ASj9e?YA^*
z2&4**L5i|-*EFJ$BMm@2@u-f&8dq1YusB|9b}l9#Ls&105J3Q`R;t^6+r)S7+WN+o
zb{vuT^48znczm73;UbkQA;APNi3t#`16DyHMb)lWW3vectG*SkS}=2j--dU&v2vYz
z<9j9=?^yZgd0?H0S(PqsehG<66
zLI9A8!u7v?;Pmm+RE_=XA&?2y#}E=jK#E$+>eea~;@}V>z%CFHtp!M>;%fLeyePT!
zbdUD|?B!2nSBL;ImJ+An1aXQwsT0HjiJ(q#BR8Uys{%0pPVZ$EeX3EKY&RU2IXUm<
zV7~s?J`qCjH;nb6(D!H?YM6cgVu@q*<47rlc_pjhpiT}xf@6X(aU@I
zO+Q_LPFmQk8xZEQIdh|iupH-W!U4i@fNv1*_soSFifI9?P_pH4
zB$AN2w9PCr7`nCi@^E<*SYn@XAiJJi=%00od#gwbSGyp2l1q_xT>UJatm6`w$elm$
eTmsIs&-f1xl@eeay)1+P0000r!I2aoic|aMG}NlzRW0Ov;r_i@@3
z9`}C5L}c0^+ip&>GU%AuS#pbhBycZj?VMNsl&$~^AD%2D^n`+~nZpG5#V8#l$K_vp
zfQ143&bO;a0ni()yq7C8S6I=6-E(Pv@xUizU4P45>`Q7WxKX0kdboXnpSmFFfl`P(xMeuS1h?EL>{
zxSQja(07kk8t$5*q02~3tC|M6nwCyA^7|n=MTjA_1&!X!uVT`e|C>)(74-=&-+St$
z0JuKfp4*3gXNJSt)f+bcUC}gTqO^0Ik{T5ijVh{IG&Cr*GNS+ssgMtiW3oc#l_o-?GVpd@)+sRY!ros1Z+?2V
z=kJaLytJ=j&j-ckGWk7plUe{K#-V@MNPD*$d5?~|Ru$#-qkP_G;(CXL6FIj?&THi3
zS}SL7=}4{CkyfjvyxoL∨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
literal 0
HcmV?d00001
diff --git a/src/icons/clock3.png b/src/icons/clock3.png
new file mode 100644
index 0000000000000000000000000000000000000000..7314d5302b7e31564d4ffbe95b85863b8ee2a697
GIT binary patch
literal 856
zcmV-e1E>6nP)N^kZBk2{(pnK}35_k_Q>A%}FDgNxG~&DZ=#vCQeT{_1mqwt1
zRw^JMLJNYG(6T0Lh-#xvZ|25IHkMUe?2Row9x!Z7?5h}Z{fzdb!YeLR;*uD*0If2t#!C!Oiykhe~)vdT(n
z!~F1D_}6;bT-b?Q1kC;d*5u^mg_DCFm!B1G%0-jPcHm1%pO?@sd^Ss4bmt!O{T)BP
zzL@%8W@hFKz&HTAoliWUp6Qd06Z>U0m7=G&mwW%*g#nwxeGdO^`t;hGw~ods6$PI+
z#>VD=Ik7u)F0H=Ko2-M1X-lWcWHN+7z~f4ppt^yrHhH;!3qin*dCka?hI61(Uuia*
z?ZWfC;TH~iLvgLf>iRmdQfO#tty&{$JO)(TDQ0gi2$j+K`DxD&5j@
zWt-`1OT7E>JfB@z0u!>`2>I~KfSm1vtUW>^Y@Ad=N4w&gTmn|ay;8_#
zEn;;W>P
z9>;M#ZZG`K(!&)Z;c$C7K#Q2-gOE?IHNcL!eA7!Fl&p(|LZL1IcFuV5=VJLiRr^C@
z8@dJ#;r1QC*A|z4tg}{?oIhijdOyRZpSqIo3hqac#$}
ziEydME^i+GifXmS(wgAgTi(k3lDW8-tN|>Z!szH|dp4ULQA!PEQu>UPk*N8}-0s4v
i)#{C6u~-8Pd-xx;e71%+z|EKd0000
literal 0
HcmV?d00001
diff --git a/src/icons/clock4.png b/src/icons/clock4.png
new file mode 100644
index 0000000000000000000000000000000000000000..07f5bfab5f2161ae1a65fe3ce3fc66f83271636b
GIT binary patch
literal 869
zcmV-r1DgDaP)C%v8`aWLO_%nD24_Sf#%nO{wj$1TTqE8
z!GM4Wst5vtq*k#0PRW`SVx!VLY`VdA^VrwjJA3Exc^9>WoZ)Z=_|7@Z2V-L&CMPHD
za5#M2^Sm_Y=A^IWx+?GMf)L^cfClz}?Y&P=Pj|PqtFMzs;{&0VI9h`-9PYe}a&-e6
zg-yD8P5ic5pb>xWCIA8e>wojq)YM2%PjD&SnHV@^S%`B9T`NEo4PyRD@C3`fWm4Ja
zYd6Nm#x4P14+5Ia$yDoS4|ChrP7%f8eR!gV+kY(K-sYb$`W}jZm%$qbubq*B!84VQ
zhKGlb`-I(mb6Z${)4}5b(lxZS1VLm&2p6TDEz}(ii-nNvb7A1(`i}-LW^4AseES!x
zE8=M@8jWUpo;Yw!*Gt&ATYyvw#sE?(R4NsSMhRe55Obi7i)cq3v9<=zy|UBGIX`PD
zr82q^VZ=DXkq)S)S!sAu!f{+k&w=sH#9Z)zi}VQ>PQ8JyEG@~~jJ<-x#&04
z04UfY1wezCht)L!V|gek3)QNbw*kz97oaGC_0<(T)mK95nH2yEMDkx{zWQti#N0J&VQ|Eq7uJ;%ICHFb_=W?rDHvx8IGsC6v{ld0`Saa$Yps_m<|y5FyXeDD`qimZ*~;oGe9nHM`&V}XKpI@e1>(BXZ160yUT3Sodce8!;=EcFkn$3I74Er!NG}Iajh0ZFa
v(t&`?C?$Dw+s%cQ%jKW)`FsTc;XnKXzVg5yGnYgS00000NkvXXu0mjfgBzRe
literal 0
HcmV?d00001
diff --git a/src/icons/clock5.png b/src/icons/clock5.png
new file mode 100644
index 0000000000000000000000000000000000000000..27e9630eb5a0ddd3484ef0f998ab292351db550a
GIT binary patch
literal 858
zcmV-g1Eu_lP)|
zP_3d+M2LcIC4xy4Vrm{XG&N03LNm^E9+|oKJnp?tAQ0(y_<#fd2j|Cec#5&HF(;8o
z3@D}gEhjoBrHrI!<{I~T-}h$#$sq_15!r0^bWb}c<^AABiM93=TLlHqrEHS^|!U_8}E|~
zdl&8kckTT*x20o~neNna*YR;&8^^W@qmcFWhq!GO5=Rxy=?VcJOFtWWUaeYdf4Hxe
zcD?IXDwP^c_vOzz$L_JYwTae-FaoU%l}d%0ETO43tRX`Wx3x~PRr2Cn#a`RCha6+f
zVDea9IH@XKgJ8L3f}J$s-hKuizLF>*2+#zM&+(UiDpg6>semAe_gj&zUx{}pyn2Y8
zl!VU~v7TQeMWT4RaIPH@29~;7?!5CwUMu;Drdq00I(O$)}B!U1iM3Dgu;ei1f
zBNW>Yd=v`PWbw_V?yD9sTUaLw?SSQ5g57@tGzu*=N~i-RvY|q9f7a)(nL69c7RH!)
zt57IRuik!UsjfhUhR`u6(U8L<8mEV12q`5!m^W8^#C^XZ#R8V**E>2HRS1o|XH(pq~-E?{W
zi@5RpkfQ@3ZD$O174YLkot-VksiBB>KW*l>Y<80D`9&9v+UzVzD7(OuumTpfTF^s^RQ@
kVC8c8r@6Vg3J`I?f2Tvd>e!g&_5c6?07*qoM6N<$f*i+_SpWb4
literal 0
HcmV?d00001
diff --git a/src/icons/connected.png b/src/icons/connected.png
new file mode 100644
index 0000000000000000000000000000000000000000..df42ee29a7c9bb98df378fdebb49cbe4913ca0d0
GIT binary patch
literal 762
zcmVm9(N{w3br+xqd8y3uF=hr=PIQmHjLIr(vFYU;tYBVcFC
z;^JaRr_<5RW)sH7#+Zg$tyY-HWd7(d!3Y~08`S>({*OwfLe9?4NW0x8*=&|z3g%iU
z6#BcnyHD}i(FE)3>$iD#5&*$T=u4@4$uaLKl!P?rI+-x>y^m@HiEEWUK4l5VgQ=kew07xOTNCCM|9+L0F56Nb;vC-)6
z{QP`76bk*qS)tWxLGC1XN;Rl)jG9DdkutI{5DyLd{k|JFzD?7#rrYfr!{PA7@$vCj
s+=o8r%3Yvn35hb|C2|UX)s27W-`poo-ZgGR)c^nh07*qoM6N<$f__O?!T!+~yOY1@YgTcxSV?SIRH2un8&CYBdENHx=7W1U)0s!d1?RL{F%buU?EgwB!
z4!`>L2Ug{|4phbAa5&X$Hla``NDtm6&)1yGG3LAXm~Pb!6mc*ZOcjeo5Cj2uo`+m6
z_Z|oYrd@MGH|+69`gC1mQ87?rr%I&~h@uGbcpU2WI<(C=j*}kmX1U#YGSRgJzu&);
z&*!0BE<+}h`AiUvMxk1*LVG+CiM*(tfpQCtM&l}xNYrq5qSNU}Hk&03!w?h`Ns*(k0wY9a~+~n@<@#^aC{QLdu>+jy(<+r!DM>YSXQ^z`jcz*Ehz#-{0Zn948Dyh;GVctl&gSk+`W7rVw3Jk_*7R^>imXspCR4#$*H3-1CSALjNLx$rzIj
zVAS`>{udfVV@x*SR)e(`yH^PP223`9Bj{-GGQ(s8^aXQ5gU35eE`XwRe_{7Mf%^wc
z#tOZRF-~<1h0PNt=K$*4JY#YWj@so`z258F`$v&%Kwh`T6zz^NYzAu#zo+$qm3t
mt^q6A0#I8WU^31EEo*-TJ~w5qT=Sp+0000`b~`h
literal 0
HcmV?d00001
diff --git a/src/icons/editpaste.png b/src/icons/editpaste.png
new file mode 100644
index 0000000000000000000000000000000000000000..e217e308ea43cbe2bd4ca3c41415f76968972625
GIT binary patch
literal 1135
zcmV-#1d#iQP)N~63&n+f`K9+L|Q13AO@%tEVWdcP%agi
z5r()5ihzKqN(#b>DGny2g1Xpk*KO~g=XrkJllt(1dAIXj#ho!&&ic^jr{Ze{P
z9k6?CWZ=vuIlZe@UVC=NGAohRL~Q1V%xZpO0iv%|aviPyVr0Z;czDDq<+4u{4q^A&
zG|j!A%+w|djoAf2Ax$8@xg&v)$tpyFC7;vu^7(C
zPkWAhK95DjMzAvaK7&$*N16?p-oDkxj(w49Z-0Ibplwnb7@L663Ol?|xQ8SnVu#;~
zMr*^IIV~%@x=wYiUAuBEFr1W#1?-I#6}by2g$Q-AA`2HjWM$e89XiSS^{c8RVgV!R
z4TuqlG%?DcO@KHUXf5RPITkN&u`+G@_n%f
ztOSb|&1PVr)V62Or)=8v^1l*EfvO{rQUEq7=#1MDOrAWEo}Qm<6O|ad($qA)4R{*}
zv$dXLGUP)!L0e%(1d$W-5fFhFjUad~Impqv_rqMJ>5o<}Bwm8jZREihDuD0KW9>brU>r3nIs
zG0EZFT2ln1SOz1wptIEt7$bzCszlr2@h9~>JJd@I2N6;l2t#7r8;A$TLu-8Eghcug
zjPX$%57)ZZP*NpQ8z>HGrc5%ppd)3g#*(brMWUvFHu6Ky>B&nUZz++_3H2yW2N9CD
z+gYOK5uCj`{8FF_2X72dYr}yV3maLK{N(NuwHx^M{IzS9tOK?H_s0L1!aGafTnFBo
zxB+L0^h>CKBH$-}#imjyOdAx5hdE26G!UbKvQ7O*Y@;uNWy})k0`8!c0R*TgVOQ^t
z24wsKS{r(M|K!r8f%vyzGFsc!)c7}{ukRYS`3I)+^&>B+!BGGJ002ovPDHLkV1l2;
B97X^D
literal 0
HcmV?d00001
diff --git a/src/icons/export.png b/src/icons/export.png
new file mode 100644
index 0000000000000000000000000000000000000000..5c1f51905428a5516438333f4ba59a42b6ba1696
GIT binary patch
literal 1931
zcmV;62Xy#}P)#wsP7;z-1&IPssO`2@)pl!JZRl3VmPyK5RI7B%E>T3Ywl#eLt7@R=mPK_}
zrDX!xQH3Q7Ei@5AO5`LbCr92Sk;uz=d(LxhhRmE!M#9c+KlL|r&HX%|uIu{W_dL(d
z&z>OeK$lluwDz9)?2L(9dmZ<@V(!b2a^ExZ*!HN7W{k9uy&$b+lwE49s?
ztgWss`m***Eh}2CK5stp-I?Cjxf&3(#vB;BT`xo)GoNm;$Lz}lvmaLeThcodc`ll9
z7dr4xRu5K|JQ%#?Z^IwG7?V6ipH{Bc^t>6GVV|z4xlJW|^vvu2JTpO**k>i#ojfxJIYO
zX-xK5J(K>N2A+LZPo_Sj$J3uu@Y*6
zJ-k)8a)zWV)l{Zv(wbByCgtnHoh5p6{bpI`g{kkZzG}a#yCRZ0Ds*F8vAmfW-%N8p
zUaz7`slqMoJmt}xWqYOa^{O1#)72AtaW@=oM}}+ZZzNBuu3i30BNnB}rvOWLuUEUo
z`xP=PT)_z;YW2ta)NgW(=B-+-8x@t3B)_Sf%v@zt>aowHJ(IMeEq~>1INlS(pAatP
ze=SvRSNw{-I-Kd%k14rUs&khzbpCRN^70(2z2TN5nUXwy$?cQ;l1^UnD`EalDgQ4j
zj_p*{2|9Bpc1Q8`^^r&^A4u*4O5U|uf8Kmmcfv3wuU~Q-lFKL6*2yPn(nmQuzHh1Y
zRh-^y$&x#M2Wnhwa5{m^L*NN!aBnx+x%t$1xh{c4O`SPSC$A
z6P|4QMijK8DZaReh`n8~nKN5RwyjdxMosx}yAGUpt0ATtXzmF#SGh|nu9ga`q%uQ#
zWnr!kZJi@+cuvz>@P3mMc(sb^l5+Nz<+>o}dk79^$o*yPKT}Kq5MTldv_^o>Pn`mj
zF}yG?wj1jX_kl;-GqHcmDJ>zg$xpG%%Ql-6$oe;ULWt>8MYhdBwHE-?&{!bAZ-V!x
zc|f&Z=-&&Pca7nnXIs;6bocw>LYUNK0Mo*$oC0iqM+TIa(>fYDggQ8$=?3bJ88{
zj~@&*I2R^(+%P7tGoK%S9P^PVCO+8w%`k{)G{7_uo}yBA73RC3FaxaNJm0gJ{U-|o
z6QF^)0ft#0u0aU54}Q@FBKy8Ta#{xljg0O(v>D@@{2RJ_Bww-tS5JZ`l$hQXWM8p!
z-3=B?V9$;E6Ynzvrh)c>cefix#kMCYbuh-mt$4kcH6a9=HyS{Ynw8B(pYsQ(t|l_t
zpkt_=V;Qvs0cQPv*EbI|{_%4l2yP$rXb
z&bqH=fzL;)Hg0-^<#6+(zRNHnjsw@`5F894&B0nHuLYcCa6HY;#-w6aEzYFS
z?t^aa7&@foz(?S>jot|bJ2Ot3K58<+j3nj(Ck3`_3d$iU=%#L(g|>j;I(Y#{DWsgK
zW&P$t-uobfx27Ip@r+~+tlk7^n~7_;jU9{fXccuYU0-a?%dM@6G{wRQ%kk=M9i^Q4
zQpQV)pSDErZz@&3TRM?r>hFiE^!8e(M$I~>L8A}p*Tc4GMC>P;HF>^1U+|XBZ2p7F
zQYJ_luS(gkOSx0()K^C?V4(@-wXhtVxx2&jEgzi;uD0MS
zhR{&J0!9rO<=`s7gQOz%0y)`lsQGQ!_Ujq4Vg4&)CWP(u)z6x;H!VLp=naPa;lWTU9qFe)q-RE&QjiS6#!|rtH
zQW0d9!sSZHsf7Zdh5%S;0xRr3wW)GJmjU7J!rPpqI^Roaj-R3{UYs_c+mYuj&MP+Z
zoYiJlA!J^=Wn`;2s^b4ae|Ta{5WC~=ZFYJDY<g?F=I$;2{e{@;~zP=RdG*GViZC
RnzjG{002ovPDHLkV1oU{ux$VU
literal 0
HcmV?d00001
diff --git a/src/icons/lock_closed.png b/src/icons/lock_closed.png
new file mode 100644
index 0000000000000000000000000000000000000000..6de207db7db063f910892e66845bf3f109c9be79
GIT binary patch
literal 1401
zcmV-<1%~>GP)Acx%bY@&hF0cvg)R2iiVMyiJ!GWqJc(cX`+&lsbuOx2$?3KC;=T-eo_W~VahfgfiE?%el*JLg>f
z>j?g0=FC|ViA2iZ8Zlz{tYO23JZGD(=cf@px(v(EhI#m^*jbwEFrC<06J#O5@+H2Qtm?7ue@856#
zQc86SgbW0n=bw0D$lCh)7e|gAJDT(7&!-O^IyN0R#Xrsb`O8H^!-ie^_aE0gcmAYT
ztoZo)qD8AF8uYbz@dqOt8aAJ4Y&@wiT=-3I*|P2U^5q*s2F3K71qUHZr^A;^yJ=36&rAt40)(q}Ed-j^%yZ5NKa^+9+OcDw-oAa0HlOz!
z6k`T#YivBBo0?km`t@IYV^EAkKdGpwnCLhGqR}W)$^-NhDOID{k_~2y+ErCmFWtG*
z<^UdrgGG@netWeeg!Qx#f>^98qp>a+6eBF_YAhCO11K*Kj}$`J8vGfDBFWkjA*Cu;
zN@3Q7B+yMi(M~w&nQK8HOdA!bDK?{Ufv;YzaV(^Lz2{7@)1IQ;j7KVKtKqSefl=ngEw!gJ3GfWrM>d&TRT^YB+&D(d{=+oyJ&wHE4B2i_J>bUjWx65VgOW-b
z^pRfxA^agR$QoWxjx@YDW;Fv(^5Lh~nX)!P{rVK|Y-}gda;^8;Z0r%;;3YwbJQjr_
z)472v0WuECkOnEp+~vgK+w3?7)xZGY$8)gnU=rdF{GtWOBpQ*YKmh^DFQEDimkIxK
z4_zIai2pDL_-niqa1#_R5CfS4O&*6rkr}^$XC#eu?`m1hm{i8BLrE^TfdHn~!s`+Pr@Y~#s1*L!?fWUB1_fyT2{?~D*u-6PK9A(0kWW6(2r7}vL(I?v;a#3
zY0y3l?E;2K!-FIjTXF>k0>4}aVgkYeC<{X95oH1bHfWtEe;Wc=^bQsoz$aW#DH0dr
zTyBACa&Fy(wu>5^fM243fK&Rr09*Kjz24!Z!O222PV!fmX-|RUfB-TQ(!Xoy0)~aa
z)AkdxFO~(`0U=A-1%PcsxD3idzMwDg
zHwD2daTf?7Y!SAh(lYGF#DWSN%M!LhANh|Ul}yGmoi%kK>lw7iV~MaeULry=ekI-S
zE@+Q-Y;4@#{7G4Pd8JlLQ7{5c=@CL$S@&l4?(O!k`M>24(#9V>1QEN`00000NkvXX
Hu0mjf+o_Kp
literal 0
HcmV?d00001
diff --git a/src/icons/lock_open.png b/src/icons/lock_open.png
new file mode 100644
index 0000000000000000000000000000000000000000..23ce3243aa2e0b81298e819114fed166cf8f9d57
GIT binary patch
literal 1359
zcmV-V1+e;wP)7ym$B4oV%;FQdCroNRgZ@mCBS-2g;m^=4^!qh6Uh%0nuMk6quo)5E5w!
z0<0`V0jf+iM`@-maj4+XrRt{BZTH^y-uK@3oYN}@nDgFtHyqIiJ{-<_xt#C&JkNPU
zjF5(g*Q>NvD-(&zMS;_W5KeFJy-RoQbRWs*z3+kRi~zw1Shj54vdNPteYAM-!rGde
z@x)?rLLrBIK1X-=Ee;<(eDU<@6Q2NkM?Jvu@>96EH60|)lM2W+J@IFyFQ#tpM(&z||+;>Gplot-^&baZgyL~FXO?T^;>
z_O{ExJ|RRX77oX$t*ymzoLAc0&z>|!T@GX@1tnO$dZRODO!*JZ&Ffyhd)K40vx8Ho
zj{llU^=&prT~ta3s0s0S`DgX@jVtTw>KHpV&Xz47pSyD9(gNTfkHX58Z#8b+y36d}
zf6VOI(PEmK-u`{Xisr`xdC(=7<&aslXwf@?45gq1Yu9et
zSyh#2Qi`1Cr9~>0Tm}3_5t7MUQ)bL~{!DZ8hDsqc+qZvu6j=C3L)g^x?i0RJ(r90U
zF~A*$Kq>d@jT<+wpE`9i`}XbeT3dg58u*+4iu(Gcn|ANsZ!TWEWwvem(kxi;^1?v=
z%@CuEGTi9z?*||nje&3~7=qCcpy|zPi4%@voCp~c_C^$7XJbvo&-t^3AO>iPgDJ9)
zkoPWlm*)6g?cDrpEZ+5Vyb$fEeKvY0<$5?W6gmqehaAFPYsP>6+)L9pS0th#tu!F8
z2&=GrGw8Wpg#alO+8DGl2qAE!z(|K|I!oKJ^Lv5yLssC)iPdk+ZI}}xRt+i#CLibz
zUbPQW0|rbE^nE?Zmw|S`*b8GbO8|&Zs@y!N5X$u1DUH%Idv
z314HkAfvOWs1TY{2jRquCMWI?y6iuL^03id*>~AFlU~Ip_4k()*0&ysbq&QN7
zc0j0M-U3HLH1g0ympz(gq2b*EA%r7h4pfY?afy_|3FH9_Taa2m|
zG5F~U+|-3^$r-ra)$_r@FV4IljmO6rtu;jv7>17!LgsT<^Ea>G-pT)se*mfny+@Xr
R#&07~M;lBZ;x|o|(
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
pM>wwIOV-qr?^OfqIL&E#$iGq+DUN!abJ-8
zG#_n~d+#|iW*8#Cv|u&C`0ZKx&3VNbscQaQOU2HVMF!@#-^&
zbEhURUdY7ITu6h*q88tKpF%7b*WT=Ad(S{~iLM8c>J_P#GU158BmeK!T1WlOhPi1vgG)snCey*8pJYoxKdMs0DK-St%xjI>m*
z9%6#LiM|E`5IY@!f2Pjb*{khP|Fp{H(lXs2J0w6#iX(y;7^SozK%n1DVi3wE00$}G
z7Z#bCo<_Bo*6+1xeBPk3@rBmL2Fd@I`ujohBM4W6u|6I6McWeKsw)0y;>2e@?RJf
zBo)Aq38V&zfS8d;AOR8tCqi~>Cerl6ya3#(p1CGwX91FdNdcGwwuZc!0p9N??mY0C
j!`TGhaO4>=V#J6)NhJ@SnNAkS00000NkvXXu0mjf%u*jv
literal 0
HcmV?d00001
diff --git a/src/icons/send.png b/src/icons/send.png
new file mode 100644
index 0000000000000000000000000000000000000000..43c3d7922c15e1e4962264322228746d54162413
GIT binary patch
literal 1345
zcmV-H1-|-;P)Nr3^29?oeI`6Gvz+
z8POr?ywEdVbFqLT@JUdiUM|VeO!b!b{G}4@g81KpBAq5QsAgeTzZ@&q0hK
z8w+)NeHvR7_l`>FUmUQa_C^L(7WbPoYx`@7s1o3XoCB!D44V7cIL2RI&Z&&XRSM-e
zC;+7Zh3i1*Dx%XdXor+i>^$OesMY16u?Zu};K@hF(yuH}-GATa+9;cZKy|J!VK(#}
zOcWDmVvIFc*3ky(sEz_-6d4VkUDH~lBcWFTG`D*^_o2&umm03A6sFuYoFo=+#`OAy
zQC1p&4Y>qB>jYN`^?OdR=A#z!(>{LS4DiB^Ab5U&HMjWWrF?>v
zLT3SiAOz)jtlZ<^Ifk1D3lB~o#l{UkRBYX{e+Dq~tO1Mt2~WEddFhSd5|zI;zpX
zn=1eiK>>bty>ze%lS7nVo0)?wnshH>Wu
zKwaHlK=10t4hRlHo@)plgKu#yzDn?vvN(8Xj8?=-Apio#5=C&w#SW84xZtH(zwt-b
z)P3EC7rc;L0D;za&CR1rxO`Z#HC8yDi{mJZgKM$1V`t`XKEdvTS&9^7w175D9_uru
z3c_@XZ99Hq>a$&L$>JdYfOG+Li%}b9#uxrEUJpUly-@uYg#09V%rM7(r(Zy$b>!
zpE{EK08U3Szn;u8a-f|XU&wVsQyu0TH|^)1d*0<}Q+o}0;bVVs-T(9%-9$#;fHujQ
z)#S`)l#odV^e#!?UBC8LD*A*>Ti!q_c}K}#G{M4I=wU-*1MK|l
zfb4HLEKLWGNOZbYc6{6@qiruhFmE>{)t6exfu0BW_|rx?*4!+|jvbe|wVz0@flFiZ
z7p$hVrtDvD$@?ll3KtxrwEA`n-KG1wV9C{`wkw|g%vLEIxgw_b
z!nKswR$Ax@itcLYvhxwv(W-mlsd+fgD?rS-s{i#HiY29}8D+p600000NkvXXu0mjf
DT@;2)
literal 0
HcmV?d00001
diff --git a/src/icons/sync_sprite.png b/src/icons/sync_sprite.png
new file mode 100644
index 0000000000000000000000000000000000000000..d9c9ce0e41b6913950953db53c89456115f1aa03
GIT binary patch
literal 43485
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