diff --git a/contrib/dash-qt.pro b/contrib/dash-qt.pro index 5ce552cedf..2c2c45b799 100644 --- a/contrib/dash-qt.pro +++ b/contrib/dash-qt.pro @@ -19,7 +19,8 @@ FORMS += \ ../src/qt/forms/sendcoinsdialog.ui \ ../src/qt/forms/sendcoinsentry.ui \ ../src/qt/forms/signverifymessagedialog.ui \ - ../src/qt/forms/transactiondescdialog.ui + ../src/qt/forms/transactiondescdialog.ui \ + ../src/qt/forms/createwalletdialog.ui RESOURCES += \ ../src/qt/dash.qrc diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include index ff39e7ea6b..c668fe5377 100644 --- a/src/Makefile.qt.include +++ b/src/Makefile.qt.include @@ -14,6 +14,7 @@ QT_FORMS_UI = \ qt/forms/appearancewidget.ui \ qt/forms/askpassphrasedialog.ui \ qt/forms/coincontroldialog.ui \ + qt/forms/createwalletdialog.ui \ qt/forms/editaddressdialog.ui \ qt/forms/governancelist.ui \ qt/forms/helpmessagedialog.ui \ @@ -37,6 +38,7 @@ QT_MOC_CPP = \ qt/moc_addresstablemodel.cpp \ qt/moc_appearancewidget.cpp \ qt/moc_askpassphrasedialog.cpp \ + qt/moc_createwalletdialog.cpp \ qt/moc_bantablemodel.cpp \ qt/moc_bitcoinaddressvalidator.cpp \ qt/moc_bitcoinamountfield.cpp \ @@ -125,6 +127,7 @@ BITCOIN_QT_H = \ qt/clientmodel.h \ qt/coincontroldialog.h \ qt/coincontroltreewidget.h \ + qt/createwalletdialog.h \ qt/csvmodelwriter.h \ qt/dash.h \ qt/editaddressdialog.h \ @@ -240,6 +243,7 @@ BITCOIN_QT_WALLET_CPP = \ qt/askpassphrasedialog.cpp \ qt/coincontroldialog.cpp \ qt/coincontroltreewidget.cpp \ + qt/createwalletdialog.cpp \ qt/editaddressdialog.cpp \ qt/governancelist.cpp \ qt/masternodelist.cpp \ diff --git a/src/dummywallet.cpp b/src/dummywallet.cpp index 757c3c45a3..0018e2258a 100644 --- a/src/dummywallet.cpp +++ b/src/dummywallet.cpp @@ -5,8 +5,10 @@ #include #include #include +#include class CWallet; +enum class WalletCreationStatus; namespace interfaces { class Chain; @@ -91,6 +93,11 @@ std::shared_ptr LoadWallet(interfaces::Chain& chain, const std::string& throw std::logic_error("Wallet function called in non-wallet build."); } +WalletCreationStatus CreateWallet(interfaces::Chain& chain, const SecureString& passphrase, uint64_t wallet_creation_flags, const std::string& name, std::string& error, std::string& warning, std::shared_ptr& result) +{ + throw std::logic_error("Wallet function called in non-wallet build."); +} + namespace interfaces { class Wallet; diff --git a/src/interfaces/node.cpp b/src/interfaces/node.cpp index dcf31d72dd..2ac2895ede 100644 --- a/src/interfaces/node.cpp +++ b/src/interfaces/node.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include @@ -49,6 +50,7 @@ fs::path GetWalletDir(); std::vector ListWalletDir(); std::vector> GetWallets(); std::shared_ptr LoadWallet(interfaces::Chain& chain, const std::string& name, std::string& error, std::string& warning); +WalletCreationStatus CreateWallet(interfaces::Chain& chain, const SecureString& passphrase, uint64_t wallet_creation_flags, const std::string& name, std::string& error, std::string& warning, std::shared_ptr& result); namespace interfaces { @@ -390,6 +392,13 @@ public: Masternode::Sync& masternodeSync() override { return m_masternodeSync; } CoinJoin::Options& coinJoinOptions() override { return m_coinjoin; } + WalletCreationStatus createWallet(const SecureString& passphrase, uint64_t wallet_creation_flags, const std::string& name, std::string& error, std::string& warning, std::unique_ptr& result) override + { + std::shared_ptr wallet; + WalletCreationStatus status = CreateWallet(*m_interfaces.chain, passphrase, wallet_creation_flags, name, error, warning, wallet); + result = MakeWallet(wallet); + return status; + } std::unique_ptr handleInitMessage(InitMessageFn fn) override { return MakeHandler(::uiInterface.InitMessage_connect(fn)); diff --git a/src/interfaces/node.h b/src/interfaces/node.h index 13adbcefb5..914365c738 100644 --- a/src/interfaces/node.h +++ b/src/interfaces/node.h @@ -9,6 +9,7 @@ #include // For CConnman::NumConnections #include // For banmap_t #include // For Network +#include // For SecureString #include #include @@ -29,6 +30,7 @@ class RPCTimerInterface; class UniValue; class proxyType; struct CNodeStateStats; +enum class WalletCreationStatus; namespace interfaces { class Handler; @@ -279,6 +281,9 @@ public: //! Return interface for accessing coinjoin related handler. virtual CoinJoin::Options& coinJoinOptions() = 0; + //! Create a wallet from file + virtual WalletCreationStatus createWallet(const SecureString& passphrase, uint64_t wallet_creation_flags, const std::string& name, std::string& error, std::string& warning, std::unique_ptr& result) = 0; + //! Register handler for init messages. using InitMessageFn = std::function; virtual std::unique_ptr handleInitMessage(InitMessageFn fn) = 0; diff --git a/src/qt/askpassphrasedialog.cpp b/src/qt/askpassphrasedialog.cpp index 7f93ebfab6..5550f43197 100644 --- a/src/qt/askpassphrasedialog.cpp +++ b/src/qt/askpassphrasedialog.cpp @@ -20,12 +20,13 @@ #include #include -AskPassphraseDialog::AskPassphraseDialog(Mode _mode, QWidget *parent) : +AskPassphraseDialog::AskPassphraseDialog(Mode _mode, QWidget *parent, SecureString* passphrase_out) : QDialog(parent), ui(new Ui::AskPassphraseDialog), mode(_mode), model(nullptr), - fCapsLock(false) + fCapsLock(false), + m_passphrase_out(passphrase_out) { ui->setupUi(this); @@ -106,7 +107,7 @@ void AskPassphraseDialog::setModel(WalletModel *_model) void AskPassphraseDialog::accept() { SecureString oldpass, newpass1, newpass2; - if(!model) + if (!model && mode != Encrypt) return; oldpass.reserve(MAX_PASSPHRASE_SIZE); newpass1.reserve(MAX_PASSPHRASE_SIZE); @@ -135,38 +136,45 @@ void AskPassphraseDialog::accept() { if(newpass1 == newpass2) { - if(model->setWalletEncrypted(true, newpass1)) - { - if (model->wallet().hdEnabled()) { - QMessageBox::warning(this, tr("Wallet encrypted"), + QString encryption_reminder = tr("Remember that encrypting your wallet cannot fully protect " + "your funds from being stolen by malware infecting your computer."); + if (m_passphrase_out) { + m_passphrase_out->assign(newpass1); + QMessageBox::warning(this, tr("Wallet to be encrypted"), "" + - tr("Your wallet is now encrypted. " - "Remember that encrypting your wallet cannot fully protect " - "your funds from being stolen by malware infecting your computer.") + - "

" + - tr("IMPORTANT: Any previous backups you have made of your wallet file " - "should be replaced with the newly generated, encrypted wallet file. " - "Previous backups of the unencrypted wallet file contain the same HD seed and " - "still have full access to all your funds just like the new, encrypted wallet.") + - "
"); - } else { - QMessageBox::warning(this, tr("Wallet encrypted"), - "" + - tr("Your wallet is now encrypted. " - "Remember that encrypting your wallet cannot fully protect " - "your funds from being stolen by malware infecting your computer.") + - "

" + - tr("IMPORTANT: Any previous backups you have made of your wallet file " - "should be replaced with the newly generated, encrypted wallet file. " - "For security reasons, previous backups of the unencrypted wallet file " - "will become useless as soon as you start using the new, encrypted wallet.") + + tr("Your wallet is about to be encrypted. ") + encryption_reminder + "
"); + } else { + assert(model != nullptr); + if(model->setWalletEncrypted(true, newpass1)) + { + if (model->wallet().hdEnabled()) { + QMessageBox::warning(this, tr("Wallet encrypted"), + "" + + tr("Your wallet is now encrypted. ") + encryption_reminder + + "

" + + tr("IMPORTANT: Any previous backups you have made of your wallet file " + "should be replaced with the newly generated, encrypted wallet file. " + "Previous backups of the unencrypted wallet file contain the same HD seed and " + "still have full access to all your funds just like the new, encrypted wallet.") + + "
"); + } else { + QMessageBox::warning(this, tr("Wallet encrypted"), + "" + + tr("Your wallet is now encrypted. ") + encryption_reminder + + "

" + + tr("IMPORTANT: Any previous backups you have made of your wallet file " + "should be replaced with the newly generated, encrypted wallet file. " + "For security reasons, previous backups of the unencrypted wallet file " + "will become useless as soon as you start using the new, encrypted wallet.") + + "
"); + } + } + else + { + QMessageBox::critical(this, tr("Wallet encryption failed"), + tr("Wallet encryption failed due to an internal error. Your wallet was not encrypted.")); } - } - else - { - QMessageBox::critical(this, tr("Wallet encryption failed"), - tr("Wallet encryption failed due to an internal error. Your wallet was not encrypted.")); } QDialog::accept(); // Success } diff --git a/src/qt/askpassphrasedialog.h b/src/qt/askpassphrasedialog.h index 02a1900010..c19720a2d0 100644 --- a/src/qt/askpassphrasedialog.h +++ b/src/qt/askpassphrasedialog.h @@ -7,6 +7,8 @@ #include +#include + class WalletModel; namespace Ui { @@ -28,7 +30,7 @@ public: Decrypt /**< Ask passphrase and decrypt wallet */ }; - explicit AskPassphraseDialog(Mode mode, QWidget *parent); + explicit AskPassphraseDialog(Mode mode, QWidget *parent, SecureString* passphrase_out = nullptr); ~AskPassphraseDialog(); void accept() override; @@ -40,6 +42,7 @@ private: Mode mode; WalletModel *model; bool fCapsLock; + SecureString* m_passphrase_out; private Q_SLOTS: void textChanged(); diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index b8c7089651..4523a79c1d 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -436,6 +437,9 @@ void BitcoinGUI::createActions() m_close_wallet_action = new QAction(tr("Close Wallet..."), this); m_close_wallet_action->setStatusTip(tr("Close wallet")); + m_create_wallet_action = new QAction(tr("Create Wallet..."), this); + m_create_wallet_action->setStatusTip(tr("Create a new wallet")); + showHelpMessageAction = new QAction(tr("&Command-line options"), this); showHelpMessageAction->setMenuRole(QAction::NoRole); showHelpMessageAction->setStatusTip(tr("Show the %1 help message to get a list with possible Dash command-line options").arg(PACKAGE_NAME)); @@ -499,31 +503,11 @@ void BitcoinGUI::createActions() continue; } - connect(action, &QAction::triggered, [this, name, path] { - OpenWalletActivity* activity = m_wallet_controller->openWallet(path); - - QProgressDialog* dialog = new QProgressDialog(this); - dialog->setLabelText(tr("Opening Wallet %1...").arg(name.toHtmlEscaped())); - dialog->setRange(0, 0); - dialog->setCancelButton(nullptr); - dialog->setWindowModality(Qt::ApplicationModal); - dialog->show(); - - connect(activity, &OpenWalletActivity::message, this, [this] (QMessageBox::Icon icon, QString text) { - QMessageBox box; - box.setIcon(icon); - box.setText(tr("Open Wallet Failed")); - box.setInformativeText(text); - box.setStandardButtons(QMessageBox::Ok); - box.setDefaultButton(QMessageBox::Ok); - connect(this, &QObject::destroyed, &box, &QDialog::accept); - box.exec(); - }); + connect(action, &QAction::triggered, [this, path] { + auto activity = new OpenWalletActivity(m_wallet_controller, this); connect(activity, &OpenWalletActivity::opened, this, &BitcoinGUI::setCurrentWallet); connect(activity, &OpenWalletActivity::finished, activity, &QObject::deleteLater); - connect(activity, &OpenWalletActivity::finished, dialog, &QObject::deleteLater); - bool invoked = QMetaObject::invokeMethod(activity, "open"); - assert(invoked); + activity->open(path); }); } if (m_open_wallet_menu->isEmpty()) { @@ -534,6 +518,12 @@ void BitcoinGUI::createActions() connect(m_close_wallet_action, &QAction::triggered, [this] { m_wallet_controller->closeWallet(walletFrame->currentWalletModel(), this); }); + connect(m_create_wallet_action, &QAction::triggered, [this] { + auto activity = new CreateWalletActivity(m_wallet_controller, this); + connect(activity, &CreateWalletActivity::created, this, &BitcoinGUI::setCurrentWallet); + connect(activity, &CreateWalletActivity::finished, activity, &QObject::deleteLater); + activity->create(); + }); } #endif // ENABLE_WALLET @@ -558,6 +548,7 @@ void BitcoinGUI::createMenuBar() QMenu *file = appMenuBar->addMenu(tr("&File")); if(walletFrame) { + file->addAction(m_create_wallet_action); file->addAction(m_open_wallet_action); file->addAction(m_close_wallet_action); file->addSeparator(); diff --git a/src/qt/bitcoingui.h b/src/qt/bitcoingui.h index ca9247b439..84252271da 100644 --- a/src/qt/bitcoingui.h +++ b/src/qt/bitcoingui.h @@ -160,6 +160,7 @@ private: QAction* showBackupsAction = nullptr; QAction* openAction = nullptr; QAction* showHelpMessageAction = nullptr; + QAction* m_create_wallet_action{nullptr}; QAction* m_open_wallet_action{nullptr}; QMenu* m_open_wallet_menu{nullptr}; QAction* m_close_wallet_action{nullptr}; diff --git a/src/qt/createwalletdialog.cpp b/src/qt/createwalletdialog.cpp new file mode 100644 index 0000000000..0edfcba195 --- /dev/null +++ b/src/qt/createwalletdialog.cpp @@ -0,0 +1,61 @@ +// Copyright (c) 2019 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#if defined(HAVE_CONFIG_H) +#include +#endif + +#include +#include + +#include + +CreateWalletDialog::CreateWalletDialog(QWidget* parent) : + QDialog(parent), + ui(new Ui::CreateWalletDialog) +{ + ui->setupUi(this); + ui->buttonBox->button(QDialogButtonBox::Ok)->setText(tr("Create")); + ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false); + ui->wallet_name_line_edit->setFocus(Qt::ActiveWindowFocusReason); + + connect(ui->wallet_name_line_edit, &QLineEdit::textEdited, [this](const QString& text) { + ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(!text.isEmpty()); + }); + + connect(ui->encrypt_wallet_checkbox, &QCheckBox::toggled, [this](bool checked) { + // Disable disable_privkeys_checkbox when encrypt is set to true, enable it when encrypt is false + ui->disable_privkeys_checkbox->setEnabled(!checked); + + // When the disable_privkeys_checkbox is disabled, uncheck it. + if (!ui->disable_privkeys_checkbox->isEnabled()) { + ui->disable_privkeys_checkbox->setChecked(false); + } + }); +} + +CreateWalletDialog::~CreateWalletDialog() +{ + delete ui; +} + +QString CreateWalletDialog::walletName() const +{ + return ui->wallet_name_line_edit->text(); +} + +bool CreateWalletDialog::encrypt() const +{ + return ui->encrypt_wallet_checkbox->isChecked(); +} + +bool CreateWalletDialog::disablePrivateKeys() const +{ + return ui->disable_privkeys_checkbox->isChecked(); +} + +bool CreateWalletDialog::blank() const +{ + return ui->blank_wallet_checkbox->isChecked(); +} diff --git a/src/qt/createwalletdialog.h b/src/qt/createwalletdialog.h new file mode 100644 index 0000000000..a1365b5969 --- /dev/null +++ b/src/qt/createwalletdialog.h @@ -0,0 +1,35 @@ +// Copyright (c) 2019 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_QT_CREATEWALLETDIALOG_H +#define BITCOIN_QT_CREATEWALLETDIALOG_H + +#include + +class WalletModel; + +namespace Ui { + class CreateWalletDialog; +} + +/** Dialog for creating wallets + */ +class CreateWalletDialog : public QDialog +{ + Q_OBJECT + +public: + explicit CreateWalletDialog(QWidget* parent); + virtual ~CreateWalletDialog(); + + QString walletName() const; + bool encrypt() const; + bool disablePrivateKeys() const; + bool blank() const; + +private: + Ui::CreateWalletDialog *ui; +}; + +#endif // BITCOIN_QT_CREATEWALLETDIALOG_H diff --git a/src/qt/forms/createwalletdialog.ui b/src/qt/forms/createwalletdialog.ui new file mode 100644 index 0000000000..f6b8f751df --- /dev/null +++ b/src/qt/forms/createwalletdialog.ui @@ -0,0 +1,151 @@ + + + CreateWalletDialog + + + + 0 + 0 + 364 + 195 + + + + Create Wallet + + + + + 10 + 140 + 341 + 40 + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + 120 + 20 + 231 + 24 + + + + + + + 20 + 20 + 101 + 21 + + + + Wallet Name + + + + + + 20 + 60 + 171 + 22 + + + + Encrypt the wallet. The wallet will be encrypted with a password of your choice. + + + Encrypt Wallet + + + true + + + + + false + + + + 20 + 90 + 171 + 22 + + + + Disable private keys for this wallet. Wallets with private keys disabled will have no private keys and cannot have an HD seed or imported private keys. This is ideal for watch-only wallets. + + + Disable Private Keys + + + + + + 20 + 120 + 171 + 22 + + + + Make a blank wallet. Blank wallets do not initially have private keys or scripts. Private keys and addresses can be imported, or an HD seed can be set, at a later time. + + + Make Blank Wallet + + + + + wallet_name_line_edit + encrypt_wallet_checkbox + disable_privkeys_checkbox + blank_wallet_checkbox + + + + + buttonBox + accepted() + CreateWalletDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + CreateWalletDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/src/qt/guiconstants.h b/src/qt/guiconstants.h index 03f314937b..d7c7f921bb 100644 --- a/src/qt/guiconstants.h +++ b/src/qt/guiconstants.h @@ -6,6 +6,8 @@ #ifndef BITCOIN_QT_GUICONSTANTS_H #define BITCOIN_QT_GUICONSTANTS_H +#include + /* Milliseconds between model updates */ static const int MODEL_UPDATE_DELAY = 250; diff --git a/src/qt/walletcontroller.cpp b/src/qt/walletcontroller.cpp index d82bfe1651..2ece1b4f4d 100644 --- a/src/qt/walletcontroller.cpp +++ b/src/qt/walletcontroller.cpp @@ -2,9 +2,14 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include +#include +#include #include #include +#include + #include #include @@ -14,10 +19,13 @@ #include #include #include +#include #include WalletController::WalletController(interfaces::Node& node, OptionsModel* options_model, QObject* parent) : QObject(parent) + , m_activity_thread(new QThread(this)) + , m_activity_worker(new QObject) , m_node(node) , m_options_model(options_model) { @@ -29,15 +37,17 @@ WalletController::WalletController(interfaces::Node& node, OptionsModel* options getOrCreateWallet(std::move(wallet)); } - m_activity_thread.start(); + m_activity_worker->moveToThread(m_activity_thread); + m_activity_thread->start(); } // Not using the default destructor because not all member types definitions are // available in the header, just forward declared. WalletController::~WalletController() { - m_activity_thread.quit(); - m_activity_thread.wait(); + m_activity_thread->quit(); + m_activity_thread->wait(); + delete m_activity_worker; } std::vector WalletController::getOpenWallets() const @@ -60,13 +70,6 @@ std::map WalletController::listWalletDir() const return wallets; } -OpenWalletActivity* WalletController::openWallet(const std::string& name, QWidget* parent) -{ - OpenWalletActivity* activity = new OpenWalletActivity(this, name); - activity->moveToThread(&m_activity_thread); - return activity; -} - void WalletController::closeWallet(WalletModel* wallet_model, QWidget* parent) { QMessageBox box(parent); @@ -146,23 +149,147 @@ void WalletController::removeAndDeleteWallet(WalletModel* wallet_model) delete wallet_model; } - -OpenWalletActivity::OpenWalletActivity(WalletController* wallet_controller, const std::string& name) - : m_wallet_controller(wallet_controller) - , m_name(name) -{} - -void OpenWalletActivity::open() +WalletControllerActivity::WalletControllerActivity(WalletController* wallet_controller, QWidget* parent_widget) + : QObject(wallet_controller) + , m_wallet_controller(wallet_controller) + , m_parent_widget(parent_widget) { - std::string error, warning; - std::unique_ptr wallet = m_wallet_controller->m_node.loadWallet(m_name, error, warning); - if (!warning.empty()) { - Q_EMIT message(QMessageBox::Warning, QString::fromStdString(warning)); +} + +WalletControllerActivity::~WalletControllerActivity() +{ + delete m_progress_dialog; +} + +void WalletControllerActivity::showProgressDialog(const QString& label_text) +{ + m_progress_dialog = new QProgressDialog(m_parent_widget); + + m_progress_dialog->setLabelText(label_text); + m_progress_dialog->setRange(0, 0); + m_progress_dialog->setCancelButton(nullptr); + m_progress_dialog->setWindowModality(Qt::ApplicationModal); + GUIUtil::PolishProgressDialog(m_progress_dialog); +} + +CreateWalletActivity::CreateWalletActivity(WalletController* wallet_controller, QWidget* parent_widget) + : WalletControllerActivity(wallet_controller, parent_widget) +{ + m_passphrase.reserve(MAX_PASSPHRASE_SIZE); +} + +CreateWalletActivity::~CreateWalletActivity() +{ + delete m_create_wallet_dialog; + delete m_passphrase_dialog; +} + +void CreateWalletActivity::askPasshprase() +{ + m_passphrase_dialog = new AskPassphraseDialog(AskPassphraseDialog::Encrypt, m_parent_widget, &m_passphrase); + m_passphrase_dialog->show(); + + connect(m_passphrase_dialog, &QObject::destroyed, [this] { + m_passphrase_dialog = nullptr; + }); + connect(m_passphrase_dialog, &QDialog::accepted, [this] { + createWallet(); + }); + connect(m_passphrase_dialog, &QDialog::rejected, [this] { + Q_EMIT finished(); + }); +} + +void CreateWalletActivity::createWallet() +{ + showProgressDialog(tr("Creating Wallet %1...").arg(m_create_wallet_dialog->walletName().toHtmlEscaped())); + + std::string name = m_create_wallet_dialog->walletName().toStdString(); + uint64_t flags = 0; + if (m_create_wallet_dialog->disablePrivateKeys()) { + flags |= WALLET_FLAG_DISABLE_PRIVATE_KEYS; } - if (wallet) { - Q_EMIT opened(m_wallet_controller->getOrCreateWallet(std::move(wallet))); - } else { - Q_EMIT message(QMessageBox::Critical, QString::fromStdString(error)); + if (m_create_wallet_dialog->blank()) { + flags |= WALLET_FLAG_BLANK_WALLET; } + + QTimer::singleShot(500, worker(), [this, name, flags] { + std::unique_ptr wallet; + WalletCreationStatus status = node().createWallet(m_passphrase, flags, name, m_error_message, m_warning_message, wallet); + + if (status == WalletCreationStatus::SUCCESS) m_wallet_model = m_wallet_controller->getOrCreateWallet(std::move(wallet)); + + QTimer::singleShot(500, this, &CreateWalletActivity::finish); + }); +} + +void CreateWalletActivity::finish() +{ + m_progress_dialog->hide(); + + if (!m_error_message.empty()) { + QMessageBox::critical(m_parent_widget, tr("Create wallet failed"), QString::fromStdString(m_error_message)); + } else if (!m_warning_message.empty()) { + QMessageBox::warning(m_parent_widget, tr("Create wallet warning"), QString::fromStdString(m_warning_message)); + } + + if (m_wallet_model) Q_EMIT created(m_wallet_model); + Q_EMIT finished(); } + +void CreateWalletActivity::create() +{ + m_create_wallet_dialog = new CreateWalletDialog(m_parent_widget); + m_create_wallet_dialog->setWindowModality(Qt::ApplicationModal); + m_create_wallet_dialog->show(); + + connect(m_create_wallet_dialog, &QObject::destroyed, [this] { + m_create_wallet_dialog = nullptr; + }); + connect(m_create_wallet_dialog, &QDialog::rejected, [this] { + Q_EMIT finished(); + }); + connect(m_create_wallet_dialog, &QDialog::accepted, [this] { + if (m_create_wallet_dialog->encrypt()) { + askPasshprase(); + } else { + createWallet(); + } + }); +} + +OpenWalletActivity::OpenWalletActivity(WalletController* wallet_controller, QWidget* parent_widget) + : WalletControllerActivity(wallet_controller, parent_widget) +{ +} + +void OpenWalletActivity::finish() +{ + m_progress_dialog->hide(); + + if (!m_error_message.empty()) { + QMessageBox::critical(m_parent_widget, tr("Open wallet failed"), QString::fromStdString(m_error_message)); + } else if (!m_warning_message.empty()) { + QMessageBox::warning(m_parent_widget, tr("Open wallet warning"), QString::fromStdString(m_warning_message)); + } + + if (m_wallet_model) Q_EMIT opened(m_wallet_model); + + Q_EMIT finished(); +} + +void OpenWalletActivity::open(const std::string& path) +{ + QString name = path.empty() ? QString("["+tr("default wallet")+"]") : QString::fromStdString(path); + + showProgressDialog(tr("Opening Wallet %1...").arg(name.toHtmlEscaped())); + + QTimer::singleShot(0, worker(), [this, path] { + std::unique_ptr wallet = node().loadWallet(path, m_error_message, m_warning_message); + + if (wallet) m_wallet_model = m_wallet_controller->getOrCreateWallet(std::move(wallet)); + + QTimer::singleShot(0, this, &OpenWalletActivity::finish); + }); +} diff --git a/src/qt/walletcontroller.h b/src/qt/walletcontroller.h index 3961f35b26..a06b9dca77 100644 --- a/src/qt/walletcontroller.h +++ b/src/qt/walletcontroller.h @@ -6,15 +6,20 @@ #define BITCOIN_QT_WALLETCONTROLLER_H #include +#include #include #include #include +#include #include #include #include +#include #include +#include +#include class OptionsModel; class PlatformStyle; @@ -24,7 +29,11 @@ class Handler; class Node; } // namespace interfaces +class AskPassphraseDialog; +class CreateWalletActivity; +class CreateWalletDialog; class OpenWalletActivity; +class WalletControllerActivity; /** * Controller between interfaces::Node, WalletModel instances and the GUI. @@ -33,7 +42,6 @@ class WalletController : public QObject { Q_OBJECT - WalletModel* getOrCreateWallet(std::unique_ptr wallet); void removeAndDeleteWallet(WalletModel* wallet_model); public: @@ -43,11 +51,12 @@ public: //! Returns wallet models currently open. std::vector getOpenWallets() const; + WalletModel* getOrCreateWallet(std::unique_ptr wallet); + //! Returns all wallet names in the wallet dir mapped to whether the wallet //! is loaded. std::map listWalletDir() const; - OpenWalletActivity* openWallet(const std::string& name, QWidget* parent = nullptr); void closeWallet(WalletModel* wallet_model, QWidget* parent = nullptr); Q_SIGNALS: @@ -57,34 +66,80 @@ Q_SIGNALS: void coinsSent(WalletModel* wallet_model, SendCoinsRecipient recipient, QByteArray transaction); private: - QThread m_activity_thread; + QThread* const m_activity_thread; + QObject* const m_activity_worker; interfaces::Node& m_node; OptionsModel* const m_options_model; mutable QMutex m_mutex; std::vector m_wallets; std::unique_ptr m_handler_load_wallet; - friend class OpenWalletActivity; + friend class WalletControllerActivity; }; -class OpenWalletActivity : public QObject +class WalletControllerActivity : public QObject { Q_OBJECT public: - OpenWalletActivity(WalletController* wallet_controller, const std::string& name); - -public Q_SLOTS: - void open(); + WalletControllerActivity(WalletController* wallet_controller, QWidget* parent_widget); + virtual ~WalletControllerActivity(); Q_SIGNALS: - void message(QMessageBox::Icon icon, const QString text); void finished(); + +protected: + interfaces::Node& node() const { return m_wallet_controller->m_node; } + QObject* worker() const { return m_wallet_controller->m_activity_worker; } + + void showProgressDialog(const QString& label_text); + + WalletController* const m_wallet_controller; + QWidget* const m_parent_widget; + QProgressDialog* m_progress_dialog{nullptr}; + WalletModel* m_wallet_model{nullptr}; + std::string m_error_message; + std::string m_warning_message; +}; + + +class CreateWalletActivity : public WalletControllerActivity +{ + Q_OBJECT + +public: + CreateWalletActivity(WalletController* wallet_controller, QWidget* parent_widget); + virtual ~CreateWalletActivity(); + + void create(); + +Q_SIGNALS: + void created(WalletModel* wallet_model); + +private: + void askPasshprase(); + void createWallet(); + void finish(); + + SecureString m_passphrase; + CreateWalletDialog* m_create_wallet_dialog{nullptr}; + AskPassphraseDialog* m_passphrase_dialog{nullptr}; +}; + +class OpenWalletActivity : public WalletControllerActivity +{ + Q_OBJECT + +public: + OpenWalletActivity(WalletController* wallet_controller, QWidget* parent_widget); + + void open(const std::string& path); + +Q_SIGNALS: void opened(WalletModel* wallet_model); private: - WalletController* const m_wallet_controller; - std::string const m_name; + void finish(); }; #endif // BITCOIN_QT_WALLETCONTROLLER_H