From 0628dfa08f09a04b7eda4bec43c515e5eecf6688 Mon Sep 17 00:00:00 2001 From: Just Wonder Date: Sat, 4 Apr 2020 15:43:25 -0700 Subject: [PATCH] Implemented GUI for Keva operations. --- contrib/bitcoin-qt.pro | 3 +- src/Makefile.qt.include | 34 ++ src/qt/bitcoin.qrc | 1 + src/qt/bitcoingui.cpp | 22 +- src/qt/bitcoingui.h | 5 +- src/qt/bitcoinstrings.cpp | 7 +- src/qt/forms/kevaaddkeydialog.ui | 71 ++++ src/qt/forms/kevabookmarksdialog.ui | 43 +++ src/qt/forms/kevadetaildialog.ui | 74 ++++ src/qt/forms/kevadialog.ui | 329 +++++++++++++++++ src/qt/forms/kevamynamespacesdialog.ui | 77 ++++ src/qt/forms/kevanewnamespacedialog.ui | 60 ++++ src/qt/kevaaddkeydialog.cpp | 60 ++++ src/qt/kevaaddkeydialog.h | 41 +++ src/qt/kevabookmarksdialog.cpp | 78 ++++ src/qt/kevabookmarksdialog.h | 51 +++ src/qt/kevabookmarksmodel.cpp | 242 +++++++++++++ src/qt/kevabookmarksmodel.h | 90 +++++ src/qt/kevadetaildialog.cpp | 56 +++ src/qt/kevadetaildialog.h | 40 +++ src/qt/kevadialog.cpp | 425 ++++++++++++++++++++++ src/qt/kevadialog.h | 88 +++++ src/qt/kevamynamespacesdialog.cpp | 80 +++++ src/qt/kevamynamespacesdialog.h | 51 +++ src/qt/kevanamespacemodel.cpp | 156 ++++++++ src/qt/kevanamespacemodel.h | 80 +++++ src/qt/kevanewnamespacedialog.cpp | 53 +++ src/qt/kevanewnamespacedialog.h | 36 ++ src/qt/kevatablemodel.cpp | 177 ++++++++++ src/qt/kevatablemodel.h | 89 +++++ src/qt/locale/bitcoin_en.ts | 469 +++++++++++++++++++------ src/qt/locale/bitcoin_zh_CN.ts | 223 ++++++++++++ src/qt/res/icons/about.png | Bin 7521 -> 7792 bytes src/qt/res/icons/keva.png | Bin 0 -> 6421 bytes src/qt/res/icons/star.png | Bin 0 -> 10323 bytes src/qt/res/icons/star_empty.png | Bin 0 -> 64514 bytes src/qt/test/wallettests.cpp | 1 + src/qt/walletframe.cpp | 7 + src/qt/walletframe.h | 2 + src/qt/walletmodel.cpp | 286 +++++++++++++++ src/qt/walletmodel.h | 29 +- src/qt/walletview.cpp | 11 +- src/qt/walletview.h | 4 + 43 files changed, 3538 insertions(+), 113 deletions(-) create mode 100644 src/qt/forms/kevaaddkeydialog.ui create mode 100644 src/qt/forms/kevabookmarksdialog.ui create mode 100644 src/qt/forms/kevadetaildialog.ui create mode 100644 src/qt/forms/kevadialog.ui create mode 100644 src/qt/forms/kevamynamespacesdialog.ui create mode 100644 src/qt/forms/kevanewnamespacedialog.ui create mode 100644 src/qt/kevaaddkeydialog.cpp create mode 100644 src/qt/kevaaddkeydialog.h create mode 100644 src/qt/kevabookmarksdialog.cpp create mode 100644 src/qt/kevabookmarksdialog.h create mode 100644 src/qt/kevabookmarksmodel.cpp create mode 100644 src/qt/kevabookmarksmodel.h create mode 100644 src/qt/kevadetaildialog.cpp create mode 100644 src/qt/kevadetaildialog.h create mode 100644 src/qt/kevadialog.cpp create mode 100644 src/qt/kevadialog.h create mode 100644 src/qt/kevamynamespacesdialog.cpp create mode 100644 src/qt/kevamynamespacesdialog.h create mode 100644 src/qt/kevanamespacemodel.cpp create mode 100644 src/qt/kevanamespacemodel.h create mode 100644 src/qt/kevanewnamespacedialog.cpp create mode 100644 src/qt/kevanewnamespacedialog.h create mode 100644 src/qt/kevatablemodel.cpp create mode 100644 src/qt/kevatablemodel.h create mode 100644 src/qt/res/icons/keva.png create mode 100644 src/qt/res/icons/star.png create mode 100644 src/qt/res/icons/star_empty.png diff --git a/contrib/bitcoin-qt.pro b/contrib/bitcoin-qt.pro index b8133bf78..2e13d7013 100644 --- a/contrib/bitcoin-qt.pro +++ b/contrib/bitcoin-qt.pro @@ -11,8 +11,9 @@ FORMS += \ ../src/qt/forms/overviewpage.ui \ ../src/qt/forms/receivecoinsdialog.ui \ ../src/qt/forms/receiverequestdialog.ui \ - ../src/qt/forms/debugwindow.ui \ ../src/qt/forms/sendcoinsdialog.ui \ + ../src/qt/forms/debugwindow.ui \ + ../src/qt/forms/kevadialog.ui \ ../src/qt/forms/sendcoinsentry.ui \ ../src/qt/forms/signverifymessagedialog.ui \ ../src/qt/forms/transactiondescdialog.ui \ diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include index 7f8bc1c6d..81181b98d 100644 --- a/src/Makefile.qt.include +++ b/src/Makefile.qt.include @@ -135,6 +135,12 @@ QT_FORMS_UI = \ qt/forms/overviewpage.ui \ qt/forms/receivecoinsdialog.ui \ qt/forms/receiverequestdialog.ui \ + qt/forms/kevadialog.ui \ + qt/forms/kevadetaildialog.ui \ + qt/forms/kevaaddkeydialog.ui \ + qt/forms/kevabookmarksdialog.ui \ + qt/forms/kevanewnamespacedialog.ui \ + qt/forms/kevamynamespacesdialog.ui \ qt/forms/debugwindow.ui \ qt/forms/sendcoinsdialog.ui \ qt/forms/sendcoinsentry.ui \ @@ -173,6 +179,14 @@ QT_MOC_CPP = \ qt/moc_receivecoinsdialog.cpp \ qt/moc_receiverequestdialog.cpp \ qt/moc_recentrequeststablemodel.cpp \ + qt/moc_kevatablemodel.cpp \ + qt/moc_kevanamespacemodel.cpp \ + qt/moc_kevabookmarksmodel.cpp \ + qt/moc_kevadetaildialog.cpp \ + qt/moc_kevaaddkeydialog.cpp \ + qt/moc_kevabookmarksdialog.cpp \ + qt/moc_kevanewnamespacedialog.cpp \ + qt/moc_kevamynamespacesdialog.cpp \ qt/moc_rpcconsole.cpp \ qt/moc_sendcoinsdialog.cpp \ qt/moc_sendcoinsentry.cpp \ @@ -184,6 +198,7 @@ QT_MOC_CPP = \ qt/moc_transactionfilterproxy.cpp \ qt/moc_transactiontablemodel.cpp \ qt/moc_transactionview.cpp \ + qt/moc_kevadialog.cpp \ qt/moc_utilitydialog.cpp \ qt/moc_walletframe.cpp \ qt/moc_walletmodel.cpp \ @@ -258,6 +273,15 @@ BITCOIN_QT_H = \ qt/transactionrecord.h \ qt/transactiontablemodel.h \ qt/transactionview.h \ + qt/kevadialog.h \ + qt/kevatablemodel.h \ + qt/kevanamespacemodel.h \ + qt/kevabookmarksmodel.h \ + qt/kevadetaildialog.h \ + qt/kevaaddkeydialog.h \ + qt/kevabookmarksdialog.h \ + qt/kevanewnamespacedialog.h \ + qt/kevamynamespacesdialog.h \ qt/utilitydialog.h \ qt/walletframe.h \ qt/walletmodel.h \ @@ -301,6 +325,7 @@ RES_ICONS = \ qt/res/icons/history.png \ qt/res/icons/info.png \ qt/res/icons/key.png \ + qt/res/icons/keva.png \ qt/res/icons/litecoin_splash.png \ qt/res/icons/lock_closed.png \ qt/res/icons/lock_open.png \ @@ -372,6 +397,15 @@ BITCOIN_QT_WALLET_CPP = \ qt/transactionrecord.cpp \ qt/transactiontablemodel.cpp \ qt/transactionview.cpp \ + qt/kevadialog.cpp \ + qt/kevatablemodel.cpp \ + qt/kevanamespacemodel.cpp \ + qt/kevabookmarksmodel.cpp \ + qt/kevadetaildialog.cpp \ + qt/kevaaddkeydialog.cpp \ + qt/kevabookmarksdialog.cpp \ + qt/kevanewnamespacedialog.cpp \ + qt/kevamynamespacesdialog.cpp \ qt/walletframe.cpp \ qt/walletmodel.cpp \ qt/walletmodeltransaction.cpp \ diff --git a/src/qt/bitcoin.qrc b/src/qt/bitcoin.qrc index 5d4cee07c..6aae59014 100644 --- a/src/qt/bitcoin.qrc +++ b/src/qt/bitcoin.qrc @@ -39,6 +39,7 @@ res/icons/lock_closed.png res/icons/lock_open.png res/icons/key.png + res/icons/keva.png res/icons/filesave.png res/icons/debugwindow.png res/icons/open.png diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index 422c90725..53b34cea7 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -313,6 +313,13 @@ void BitcoinGUI::createActions() historyAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_4)); tabGroup->addAction(historyAction); + kevaAction = new QAction(platformStyle->SingleColorIcon(":/icons/keva"), tr("&Keva"), this); + kevaAction->setStatusTip(tr("Keva related operations")); + kevaAction->setToolTip(kevaAction->statusTip()); + kevaAction->setCheckable(true); + kevaAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_5)); + tabGroup->addAction(kevaAction); + #ifdef ENABLE_WALLET // These showNormalIfMinimized are needed because Send Coins and Receive Coins // can be triggered from the tray menu, and need to show the GUI to be useful. @@ -328,6 +335,8 @@ void BitcoinGUI::createActions() connect(receiveCoinsMenuAction, SIGNAL(triggered()), this, SLOT(gotoReceiveCoinsPage())); connect(historyAction, SIGNAL(triggered()), this, SLOT(showNormalIfMinimized())); connect(historyAction, SIGNAL(triggered()), this, SLOT(gotoHistoryPage())); + connect(kevaAction, SIGNAL(triggered()), this, SLOT(showNormalIfMinimized())); + connect(kevaAction, SIGNAL(triggered()), this, SLOT(gotoKevaPage())); #endif // ENABLE_WALLET quitAction = new QAction(platformStyle->TextColorIcon(":/icons/quit"), tr("E&xit"), this); @@ -462,6 +471,7 @@ void BitcoinGUI::createToolBars() toolbar->addAction(sendCoinsAction); toolbar->addAction(receiveCoinsAction); toolbar->addAction(historyAction); + toolbar->addAction(kevaAction); overviewAction->setChecked(true); } } @@ -498,13 +508,13 @@ void BitcoinGUI::setClientModel(ClientModel *_clientModel) } #endif // ENABLE_WALLET unitDisplayControl->setOptionsModel(_clientModel->getOptionsModel()); - + OptionsModel* optionsModel = _clientModel->getOptionsModel(); if(optionsModel) { // be aware of the tray icon disable state change reported by the OptionsModel object. connect(optionsModel,SIGNAL(hideTrayIconChanged(bool)),this,SLOT(setTrayIconVisible(bool))); - + // initialize the disable state of the tray icon with the current value in the model. setTrayIconVisible(optionsModel->getHideTrayIcon()); } @@ -691,6 +701,12 @@ void BitcoinGUI::gotoHistoryPage() if (walletFrame) walletFrame->gotoHistoryPage(); } +void BitcoinGUI::gotoKevaPage() +{ + kevaAction->setChecked(true); + if (walletFrame) walletFrame->gotoKevaPage(); +} + void BitcoinGUI::gotoReceiveCoinsPage() { receiveCoinsAction->setChecked(true); @@ -1052,7 +1068,7 @@ void BitcoinGUI::setHDStatus(int hdEnabled) labelWalletHDStatusIcon->setPixmap(platformStyle->SingleColorIcon(hdEnabled ? ":/icons/hd_enabled" : ":/icons/hd_disabled").pixmap(STATUSBAR_ICONSIZE,STATUSBAR_ICONSIZE)); labelWalletHDStatusIcon->setToolTip(hdEnabled ? tr("HD key generation is enabled") : tr("HD key generation is disabled")); - // eventually disable the QLabel to set its opacity to 50% + // eventually disable the QLabel to set its opacity to 50% labelWalletHDStatusIcon->setEnabled(hdEnabled); } diff --git a/src/qt/bitcoingui.h b/src/qt/bitcoingui.h index ddb7ecb76..feaa27a8a 100644 --- a/src/qt/bitcoingui.h +++ b/src/qt/bitcoingui.h @@ -91,6 +91,7 @@ private: QMenuBar *appMenuBar; QAction *overviewAction; + QAction *kevaAction; QAction *historyAction; QAction *quitAction; QAction *sendCoinsAction; @@ -195,6 +196,8 @@ private Q_SLOTS: void gotoOverviewPage(); /** Switch to history (transactions) page */ void gotoHistoryPage(); + /** Switch to Keva page */ + void gotoKevaPage(); /** Switch to receive coins page */ void gotoReceiveCoinsPage(); /** Switch to send coins page */ @@ -233,7 +236,7 @@ private Q_SLOTS: /** Show progress dialog e.g. for verifychain */ void showProgress(const QString &title, int nProgress); - + /** When hideTrayIcon setting is changed in OptionsModel hide or show the icon accordingly. */ void setTrayIconVisible(bool); diff --git a/src/qt/bitcoinstrings.cpp b/src/qt/bitcoinstrings.cpp index d6f4e7100..37169f73d 100644 --- a/src/qt/bitcoinstrings.cpp +++ b/src/qt/bitcoinstrings.cpp @@ -9,7 +9,7 @@ #define UNUSED #endif static const char UNUSED *bitcoin_strings[] = { -QT_TRANSLATE_NOOP("bitcoin-core", "Bitcoin Core"), +QT_TRANSLATE_NOOP("bitcoin-core", "Kevacoin Core"), QT_TRANSLATE_NOOP("bitcoin-core", "The %s developers"), QT_TRANSLATE_NOOP("bitcoin-core", "" "(1 = keep tx meta data e.g. account owner and payment request information, 2 " @@ -156,7 +156,7 @@ QT_TRANSLATE_NOOP("bitcoin-core", "" "and enables automatic pruning of old blocks if a target size in MiB is " "provided. This mode is incompatible with -txindex and -rescan. Warning: " "Reverting this setting requires re-downloading the entire blockchain. " -"(default: 0 = disable pruning blocks, 1 = allow manual pruning via RPC, >%u " +"(default: 0 = disable pruning blocks, 1 = allow manual pruning via RPC, >=%u " "= automatically prune block files to stay under the specified target size in " "MiB)"), QT_TRANSLATE_NOOP("bitcoin-core", "" @@ -320,6 +320,9 @@ QT_TRANSLATE_NOOP("bitcoin-core", "Include IP addresses in debug output (default QT_TRANSLATE_NOOP("bitcoin-core", "Incorrect or no genesis block found. Wrong datadir for network?"), QT_TRANSLATE_NOOP("bitcoin-core", "Information"), QT_TRANSLATE_NOOP("bitcoin-core", "Initialization sanity check failed. %s is shutting down."), +QT_TRANSLATE_NOOP("bitcoin-core", "Input tx is not a keva operation"), +QT_TRANSLATE_NOOP("bitcoin-core", "Input tx is not mine"), +QT_TRANSLATE_NOOP("bitcoin-core", "Input tx not found in wallet"), QT_TRANSLATE_NOOP("bitcoin-core", "Insufficient funds"), QT_TRANSLATE_NOOP("bitcoin-core", "Invalid -onion address or hostname: '%s'"), QT_TRANSLATE_NOOP("bitcoin-core", "Invalid -proxy address or hostname: '%s'"), diff --git a/src/qt/forms/kevaaddkeydialog.ui b/src/qt/forms/kevaaddkeydialog.ui new file mode 100644 index 000000000..8f15e1aa0 --- /dev/null +++ b/src/qt/forms/kevaaddkeydialog.ui @@ -0,0 +1,71 @@ + + + KevaAddKeyDialog + + + + 0 + 0 + 800 + 400 + + + + Add New Key-Value Pair + + + + + + Key + + + + + + + New value + + + + + + + Qt::Horizontal + + + + 40 + 15 + + + + + + + + Value + + + + + + + New value + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Save + + + + + + + diff --git a/src/qt/forms/kevabookmarksdialog.ui b/src/qt/forms/kevabookmarksdialog.ui new file mode 100644 index 000000000..f1b1124a4 --- /dev/null +++ b/src/qt/forms/kevabookmarksdialog.ui @@ -0,0 +1,43 @@ + + + KevaBookmarksDialog + + + + 0 + 0 + 800 + 400 + + + + Namespace Bookmarks + + + + + + Qt::CustomContextMenu + + + false + + + true + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Apply|QDialogButtonBox::Cancel + + + + + + + diff --git a/src/qt/forms/kevadetaildialog.ui b/src/qt/forms/kevadetaildialog.ui new file mode 100644 index 000000000..6bb86bbd8 --- /dev/null +++ b/src/qt/forms/kevadetaildialog.ui @@ -0,0 +1,74 @@ + + + KevaDetailDialog + + + + 0 + 0 + 800 + 400 + + + + Value + + + + + + This pane shows the value associated with a give key + + + false + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Close|QDialogButtonBox::Save + + + + + + + + + buttonBox + accepted() + KevaDetailDialog + accept() + + + 20 + 20 + + + 20 + 20 + + + + + buttonBox + rejected() + KevaDetailDialog + reject() + + + 20 + 20 + + + 20 + 20 + + + + + diff --git a/src/qt/forms/kevadialog.ui b/src/qt/forms/kevadialog.ui new file mode 100644 index 000000000..393ad632c --- /dev/null +++ b/src/qt/forms/kevadialog.ui @@ -0,0 +1,329 @@ + + + KevaDialog + + + + 0 + 0 + 776 + 396 + + + + + + + + 0 + 0 + + + + QFrame::StyledPanel + + + QFrame::Sunken + + + + + + + + The namespace ID with a prefix "N". + + + + + + + Use this form to perform Keva database operations. + + + + + + + The namespace ID with a prefix "N". + + + Namespace: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + namespace + + + + + + + + + + 0 + 0 + + + + Show content of the namespace. + + + Show + + + + :/icons/eye:/icons/eye + + + false + + + + + + + + 0 + 0 + + + + Create a new namespace + + + &Create namespace + + + + :/icons/add:/icons/add + + + + + + + + 0 + 0 + + + + List my namespaces + + + &My Namespaces + + + + :/icons/editpaste:/icons/editpaste + + + + + + + + 0 + 0 + + + + Show bookmarks + + + &Bookmarks + + + + :/icons/address-book:/icons/address-book + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + + + + + + + + Qt::Vertical + + + + 20 + 10 + + + + + + + + + 0 + 0 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + + 75 + true + + + + Content of namespace + + + + + + + Qt::CustomContextMenu + + + false + + + true + + + + + + + + + false + + + Show the selected request (does the same as double clicking an entry) + + + Show + + + + :/icons/edit:/icons/edit + + + false + + + + + + + false + + + Remove the selected entries from the list + + + Remove + + + + :/icons/remove:/icons/remove + + + false + + + + + + + false + + + Add new key-value pair + + + Add key-value + + + + :/icons/add:/icons/add + + + false + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + BitcoinAmountField + QLineEdit +
qt/bitcoinamountfield.h
+ 1 +
+
+ + reqMessage + receiveButton + clearButton + recentRequestsView + showValueButton + removeButton + + + + + +
diff --git a/src/qt/forms/kevamynamespacesdialog.ui b/src/qt/forms/kevamynamespacesdialog.ui new file mode 100644 index 000000000..841154035 --- /dev/null +++ b/src/qt/forms/kevamynamespacesdialog.ui @@ -0,0 +1,77 @@ + + + KevaMyNamespacesDialog + + + + 0 + 0 + 800 + 400 + + + + My Namespaces + + + + + + Qt::CustomContextMenu + + + false + + + true + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Apply|QDialogButtonBox::Cancel + + + + + + + + + buttonBox + accepted() + KevaMyNamespacesDialog + accept() + + + 20 + 20 + + + 20 + 20 + + + + + buttonBox + rejected() + KevaMyNamespacesDialog + reject() + + + 20 + 20 + + + 20 + 20 + + + + + diff --git a/src/qt/forms/kevanewnamespacedialog.ui b/src/qt/forms/kevanewnamespacedialog.ui new file mode 100644 index 000000000..25b9738d4 --- /dev/null +++ b/src/qt/forms/kevanewnamespacedialog.ui @@ -0,0 +1,60 @@ + + + KevaNewNamespaceDialog + + + + 0 + 0 + 400 + 100 + + + + Create New Namespace + + + + + + + + The name of the namespace. + + + Name: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + namespaceText + + + + + + + This pane allows the creation of a new Keva namespace + + + false + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Save + + + + + + + diff --git a/src/qt/kevaaddkeydialog.cpp b/src/qt/kevaaddkeydialog.cpp new file mode 100644 index 000000000..12a71ecbf --- /dev/null +++ b/src/qt/kevaaddkeydialog.cpp @@ -0,0 +1,60 @@ +// Copyright (c) 2011-2017 The Bitcoin Core developers +// 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 + +KevaAddKeyDialog::KevaAddKeyDialog(QWidget *parent, QString &nameSpace) : + QDialog(parent), + ui(new Ui::KevaAddKeyDialog) +{ + ui->setupUi(this); + this->nameSpace = nameSpace; + connect(ui->buttonBox->button(QDialogButtonBox::Cancel), SIGNAL(clicked()), this, SLOT(cancel())); + connect(ui->buttonBox->button(QDialogButtonBox::Save), SIGNAL(clicked()), this, SLOT(create())); + connect(ui->keyText, SIGNAL(textChanged(const QString &)), this, SLOT(onKeyChanged(const QString &))); + connect(ui->valueText, SIGNAL(textChanged()), this, SLOT(onValueChanged())); + ui->buttonBox->button(QDialogButtonBox::Save)->setEnabled(false); +} + +KevaAddKeyDialog::~KevaAddKeyDialog() +{ + delete ui; +} + +void KevaAddKeyDialog::create() +{ + KevaDialog* dialog = (KevaDialog*)this->parentWidget(); + std::string keyText = ui->keyText->text().toStdString(); + std::string valueText = ui->valueText->toPlainText().toStdString(); + std::string ns = nameSpace.toStdString(); + if (!dialog->addKeyValue(ns, keyText, valueText)) { + QDialog::close(); + return; + } + dialog->showNamespace(nameSpace); + QDialog::close(); +} + +void KevaAddKeyDialog::cancel() +{ + QDialog::close(); +} + +void KevaAddKeyDialog::onKeyChanged(const QString& key) +{ + bool enabled = key.length() > 0 && ui->valueText->toPlainText().length() > 0; + ui->buttonBox->button(QDialogButtonBox::Save)->setEnabled(enabled); +} + +void KevaAddKeyDialog::onValueChanged() +{ + bool enabled = ui->valueText->toPlainText().length() > 0 && ui->keyText->text().length() > 0; + ui->buttonBox->button(QDialogButtonBox::Save)->setEnabled(enabled); +} diff --git a/src/qt/kevaaddkeydialog.h b/src/qt/kevaaddkeydialog.h new file mode 100644 index 000000000..d65db8b86 --- /dev/null +++ b/src/qt/kevaaddkeydialog.h @@ -0,0 +1,41 @@ +// Copyright (c) 2011-2014 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_KEVAADDKEYDIALOG_H +#define BITCOIN_QT_KEVAADDKEYDIALOG_H + +#include +#include + +#include + +namespace Ui { + class KevaAddKeyDialog; +} + +QT_BEGIN_NAMESPACE +class QModelIndex; +QT_END_NAMESPACE + +/** Dialog add new key-value pair. */ +class KevaAddKeyDialog : public QDialog +{ + Q_OBJECT + +public: + explicit KevaAddKeyDialog(QWidget *parent, QString &nameSpace); + ~KevaAddKeyDialog(); + +private: + Ui::KevaAddKeyDialog *ui; + QString nameSpace; + +public Q_SLOTS: + void create(); + void cancel(); + void onKeyChanged(const QString& key); + void onValueChanged(); +}; + +#endif // BITCOIN_QT_KEVAADDKEYDIALOG_H diff --git a/src/qt/kevabookmarksdialog.cpp b/src/qt/kevabookmarksdialog.cpp new file mode 100644 index 000000000..0cc46892a --- /dev/null +++ b/src/qt/kevabookmarksdialog.cpp @@ -0,0 +1,78 @@ +// Copyright (c) 2011-2017 The Bitcoin Core developers +// 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 + +KevaBookmarksDialog::KevaBookmarksDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::KevaBookmarksDialog) +{ + ui->setupUi(this); + ui->buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false); + ui->buttonBox->button(QDialogButtonBox::Apply)->setText(tr("Show")); + connect(ui->buttonBox->button(QDialogButtonBox::Cancel), SIGNAL(clicked()), this, SLOT(reject())); + connect(ui->buttonBox->button(QDialogButtonBox::Apply), SIGNAL(clicked()), this, SLOT(apply())); +} + +void KevaBookmarksDialog::setModel(WalletModel *_model) +{ + this->model = _model; + + if(_model && _model->getOptionsModel()) + { + _model->getKevaBookmarksModel()->sort(KevaBookmarksModel::Name, Qt::DescendingOrder); + QTableView* tableView = ui->namespaceView; + + tableView->verticalHeader()->hide(); + tableView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + tableView->setModel(_model->getKevaBookmarksModel()); + tableView->setAlternatingRowColors(true); + tableView->setSelectionBehavior(QAbstractItemView::SelectRows); + tableView->setSelectionMode(QAbstractItemView::ContiguousSelection); + tableView->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); + + connect(tableView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), + this, SLOT(namespaceView_selectionChanged())); + } +} + + +void KevaBookmarksDialog::namespaceView_selectionChanged() +{ + bool enable = !ui->namespaceView->selectionModel()->selectedRows().isEmpty(); + ui->buttonBox->button(QDialogButtonBox::Apply)->setEnabled(enable); + + if (enable) { + selectedIndex = ui->namespaceView->selectionModel()->currentIndex(); + } else { + QModelIndex empty; + selectedIndex = empty; + } +} + +void KevaBookmarksDialog::apply() +{ + QModelIndex idIdx = selectedIndex.sibling(selectedIndex.row(), KevaBookmarksModel::Id); + QString idStr = idIdx.data(Qt::DisplayRole).toString(); + KevaDialog* dialog = (KevaDialog*)this->parentWidget(); + dialog->showNamespace(idStr); + QDialog::close(); +} + +void KevaBookmarksDialog::reject() +{ + QDialog::reject(); +} + +KevaBookmarksDialog::~KevaBookmarksDialog() +{ + delete ui; +} \ No newline at end of file diff --git a/src/qt/kevabookmarksdialog.h b/src/qt/kevabookmarksdialog.h new file mode 100644 index 000000000..cd96d71f1 --- /dev/null +++ b/src/qt/kevabookmarksdialog.h @@ -0,0 +1,51 @@ +// Copyright (c) 2011-2014 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_KEVABOOKMARKSDIALOG_H +#define BITCOIN_QT_KEVABOOKMARKSDIALOG_H + +#include +#include + +#include +#include +#include + +class WalletModel; + +namespace Ui { + class KevaBookmarksDialog; +} + + +/** Dialog showing namepsace creation. */ +class KevaBookmarksDialog : public QDialog +{ + Q_OBJECT + + enum ColumnWidths { + ID_COLUMN_WIDTH = 260, + NAME_COLUMN_WIDTH = 260, + MINIMUM_COLUMN_WIDTH = 260 + }; + +public: + explicit KevaBookmarksDialog(QWidget *parent = 0); + ~KevaBookmarksDialog(); + void setModel(WalletModel *_model); + +public Q_SLOTS: + void apply(); + void reject(); + +private Q_SLOTS: + void namespaceView_selectionChanged(); + +private: + Ui::KevaBookmarksDialog *ui; + WalletModel *model; + QModelIndex selectedIndex; +}; + +#endif // BITCOIN_QT_KEVABOOKMARKSDIALOG_H diff --git a/src/qt/kevabookmarksmodel.cpp b/src/qt/kevabookmarksmodel.cpp new file mode 100644 index 000000000..9e823f806 --- /dev/null +++ b/src/qt/kevabookmarksmodel.cpp @@ -0,0 +1,242 @@ +// Copyright (c) 2011-2017 The Bitcoin Core developers +// 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 +#include +#include +#include + + +KevaBookmarksModel::KevaBookmarksModel(CWallet *wallet, WalletModel *parent) : + QAbstractTableModel(parent), walletModel(parent) +{ + Q_UNUSED(wallet) + + /* These columns must match the indices in the ColumnIndex enumeration */ + columns << tr("Id") << tr("Name"); +} + +KevaBookmarksModel::~KevaBookmarksModel() +{ + /* Intentionally left empty */ +} + +int KevaBookmarksModel::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent); + + return list.length(); +} + +int KevaBookmarksModel::columnCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent); + + return columns.length(); +} + +QVariant KevaBookmarksModel::data(const QModelIndex &index, int role) const +{ + if(!index.isValid() || index.row() >= list.length()) + return QVariant(); + + if (role == Qt::TextColorRole) + { + return QVariant(); + } + else if(role == Qt::DisplayRole || role == Qt::EditRole) + { + const BookmarkEntry *rec = &list[index.row()]; + switch(index.column()) + { + case Id: + return QString::fromStdString(rec->id); + case Name: + return QString::fromStdString(rec->name); + } + } + else if (role == Qt::TextAlignmentRole) + { + return (int)(Qt::AlignLeft|Qt::AlignVCenter); + } + return QVariant(); +} + +bool KevaBookmarksModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + return true; +} + +QVariant KevaBookmarksModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if(orientation == Qt::Horizontal) + { + if(role == Qt::DisplayRole && section < columns.size()) + { + return columns[section]; + } + } + return QVariant(); +} + + +QModelIndex KevaBookmarksModel::index(int row, int column, const QModelIndex &parent) const +{ + Q_UNUSED(parent); + + return createIndex(row, column); +} + +bool KevaBookmarksModel::removeRows(int row, int count, const QModelIndex &parent) +{ + Q_UNUSED(parent); + + if(count > 0 && row >= 0 && (row+count) <= list.size()) + { + beginRemoveRows(parent, row, row + count - 1); + list.erase(list.begin() + row, list.begin() + row + count); + endRemoveRows(); + return true; + } else { + return false; + } +} + +Qt::ItemFlags KevaBookmarksModel::flags(const QModelIndex &index) const +{ + return Qt::ItemIsSelectable | Qt::ItemIsEnabled; +} + +// actually add to table in GUI +void KevaBookmarksModel::setBookmarks(std::vector vBookmarEntries) +{ + // Remove the old ones. + removeRows(0, list.size()); + list.clear(); + + for (auto it = vBookmarEntries.begin(); it != vBookmarEntries.end(); it++) { + beginInsertRows(QModelIndex(), 0, 0); + list.prepend(*it); + endInsertRows(); + } +} + +void KevaBookmarksModel::setBookmarks(QJsonArray &array) +{ + std::vector vBookmarEntries; + for (int i = 0; i < array.size(); ++i) { + QJsonObject obj = array[i].toObject(); + BookmarkEntry entry; + entry.id = obj["id"].toString().toStdString(); + entry.name = obj["name"].toString().toStdString(); + vBookmarEntries.push_back(entry); + } + setBookmarks(std::move(vBookmarEntries)); +} + +void KevaBookmarksModel::sort(int column, Qt::SortOrder order) +{ + qSort(list.begin(), list.end(), BookmarkEntryLessThan(column, order)); + Q_EMIT dataChanged(index(0, 0, QModelIndex()), index(list.size() - 1, NUMBER_OF_COLUMNS - 1, QModelIndex())); +} + + +QString KevaBookmarksModel::getBookmarkFile() +{ + QString dataDir = GUIUtil::boostPathToQString(GetDataDir()); + return dataDir + QDir::separator() + QStringLiteral(BOOKMARK_FILE); +} + +int KevaBookmarksModel::loadBookmarks() +{ + QJsonArray json; + if (loadBookmarks(json)) { + setBookmarks(json); + return 1; + } + + // No bookmark file. Save and load the default ones. + QJsonObject entry0; + entry0["id"] = "NgKBKkBAJMtzsuit85TpTpo5Xj6UQUg1wr"; + entry0["name"] = "Kevacoin Official Blog"; + + QJsonObject entry1; + entry1["id"] = "NV9GkLpCLMh4Nd6nZRkch8iNbuV8w9khTm"; + entry1["name"] = "Kevacoin官方博客"; + + QJsonObject entry2; + entry2["id"] = "NfFPgVqzk3H9afHjX8FDoyhkwtwGNanjyG"; + entry2["name"] = "Официальный блог Kevacoin"; + + QJsonArray array; + array.append(entry0); + array.append(entry1); + array.append(entry2); + + if (!saveBookmarks(array)) { + return 0; + } + + // Load the bookmarks again. + if (loadBookmarks(json)) { + setBookmarks(json); + return 1; + } + return 0; +} + + +int KevaBookmarksModel::loadBookmarks(QJsonArray &json) +{ + QFile loadFile(getBookmarkFile()); + if (!loadFile.open(QIODevice::ReadOnly)) { + return 0; + } + QByteArray saveData = loadFile.readAll(); + QJsonDocument loadDoc(QJsonDocument::fromBinaryData(saveData)); + json = loadDoc.array(); + return 1; +} + +int KevaBookmarksModel::saveBookmarks(QJsonArray &json) +{ + QFile saveFile(getBookmarkFile()); + if (!saveFile.open(QIODevice::WriteOnly)) { + return 0; + } + + QJsonDocument saveDoc(json); + saveFile.write(saveDoc.toBinaryData()); + return 1; +} + + +bool BookmarkEntryLessThan::operator()(BookmarkEntry &left, BookmarkEntry &right) const +{ + BookmarkEntry *pLeft = &left; + BookmarkEntry *pRight = &right; + if (order == Qt::DescendingOrder) + std::swap(pLeft, pRight); + + switch(column) + { + case KevaBookmarksModel::Id: + return pLeft->id < pRight->id; + case KevaBookmarksModel::Name: + return pLeft->name < pRight->name; + default: + return pLeft->id < pRight->id; + } +} diff --git a/src/qt/kevabookmarksmodel.h b/src/qt/kevabookmarksmodel.h new file mode 100644 index 000000000..b2821b3a1 --- /dev/null +++ b/src/qt/kevabookmarksmodel.h @@ -0,0 +1,90 @@ +// Copyright (c) 2011-2017 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_KEVABOOKMARKSMODEL_H +#define BITCOIN_QT_KEVABOOKMARKSMODEL_H + +#include + +#include +#include +#include +#include +#include + +#define BOOKMARK_FILE "bookmarks.dat" + +class CWallet; + +class BookmarkEntry +{ +public: + BookmarkEntry() { } + + std::string id; + std::string name; +}; + +class BookmarkEntryLessThan +{ +public: + BookmarkEntryLessThan(int nColumn, Qt::SortOrder fOrder): + column(nColumn), order(fOrder) {} + bool operator()(BookmarkEntry &left, BookmarkEntry &right) const; + +private: + int column; + Qt::SortOrder order; +}; + +/** Model for list of recently generated payment requests / bitcoin: URIs. + * Part of wallet model. + */ +class KevaBookmarksModel: public QAbstractTableModel +{ + Q_OBJECT + +public: + explicit KevaBookmarksModel(CWallet *wallet, WalletModel *parent); + ~KevaBookmarksModel(); + + enum ColumnIndex { + Id = 0, + Name = 1, + NUMBER_OF_COLUMNS + }; + + /** @name Methods overridden from QAbstractTableModel + @{*/ + int rowCount(const QModelIndex &parent) const; + int columnCount(const QModelIndex &parent) const; + QVariant data(const QModelIndex &index, int role) const; + bool setData(const QModelIndex &index, const QVariant &value, int role); + QVariant headerData(int section, Qt::Orientation orientation, int role) const; + QModelIndex index(int row, int column, const QModelIndex &parent) const; + bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()); + Qt::ItemFlags flags(const QModelIndex &index) const; + /*@}*/ + + const BookmarkEntry &entry(int row) const { return list[row]; } + void setBookmarks(std::vector vBookmarkEntries); + void setBookmarks(QJsonArray &json); + + int loadBookmarks(); + int loadBookmarks(QJsonArray &json); + int saveBookmarks(QJsonArray &json); + +public Q_SLOTS: + void sort(int column, Qt::SortOrder order = Qt::AscendingOrder); + +private: + WalletModel *walletModel; + QStringList columns; + QList list; + int64_t nReceiveRequestsMaxId; + + QString getBookmarkFile(); +}; + +#endif // BITCOIN_QT_KEVABOOKMARKSMODEL_H diff --git a/src/qt/kevadetaildialog.cpp b/src/qt/kevadetaildialog.cpp new file mode 100644 index 000000000..8e9376dd4 --- /dev/null +++ b/src/qt/kevadetaildialog.cpp @@ -0,0 +1,56 @@ +// Copyright (c) 2011-2017 The Bitcoin Core developers +// 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 + + +KevaDetailDialog::KevaDetailDialog(const QModelIndex &idx, QWidget *parent, const QString &nameSpace) : + QDialog(parent), + ui(new Ui::KevaDetailDialog) +{ + ui->setupUi(this); + QModelIndex keyIdx = idx.sibling(idx.row(), KevaTableModel::Key); + QModelIndex valueIdx = idx.sibling(idx.row(), KevaTableModel::Value); + this->nameSpace = nameSpace; + key = keyIdx.data(Qt::DisplayRole).toString(); + setWindowTitle(tr("Value for %1").arg(key)); + QString desc = valueIdx.data(Qt::DisplayRole).toString(); + connect(ui->detailText, SIGNAL(textChanged()), this, SLOT(onValueChanged())); + //ui->detailText->setHtml(desc); + ui->detailText->setPlainText(desc); + ui->buttonBox->button(QDialogButtonBox::Save)->setEnabled(false); + connect(ui->buttonBox->button(QDialogButtonBox::Save), SIGNAL(clicked()), this, SLOT(onSave())); +} + +KevaDetailDialog::~KevaDetailDialog() +{ + delete ui; +} + +void KevaDetailDialog::onValueChanged() +{ + bool enabled = ui->detailText->toPlainText().length() > 0; + ui->buttonBox->button(QDialogButtonBox::Save)->setEnabled(enabled); +} + +void KevaDetailDialog::onSave() +{ + KevaDialog* dialog = (KevaDialog*)this->parentWidget(); + std::string keyText = key.toStdString(); + std::string valueText = ui->detailText->toPlainText().toStdString(); + std::string ns = nameSpace.toStdString(); + if (!dialog->addKeyValue(ns, keyText, valueText)) { + QDialog::close(); + return; + } + dialog->showNamespace(nameSpace); + QDialog::close(); +} diff --git a/src/qt/kevadetaildialog.h b/src/qt/kevadetaildialog.h new file mode 100644 index 000000000..597eb1bc4 --- /dev/null +++ b/src/qt/kevadetaildialog.h @@ -0,0 +1,40 @@ +// Copyright (c) 2011-2014 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_KEVADETAILDIALOG_H +#define BITCOIN_QT_KEVADETAILDIALOG_H + +#include +#include + +#include + +namespace Ui { + class KevaDetailDialog; +} + +QT_BEGIN_NAMESPACE +class QModelIndex; +QT_END_NAMESPACE + +/** Dialog showing transaction details. */ +class KevaDetailDialog : public QDialog +{ + Q_OBJECT + +public: + explicit KevaDetailDialog(const QModelIndex &idx, QWidget *parent, const QString &nameSpace); + ~KevaDetailDialog(); + +public Q_SLOTS: + void onValueChanged(); + void onSave(); + +private: + Ui::KevaDetailDialog *ui; + QString nameSpace; + QString key; +}; + +#endif // BITCOIN_QT_KEVADETAILDIALOG_H diff --git a/src/qt/kevadialog.cpp b/src/qt/kevadialog.cpp new file mode 100644 index 000000000..c9e5230ca --- /dev/null +++ b/src/qt/kevadialog.cpp @@ -0,0 +1,425 @@ +// Copyright (c) 2011-2017 The Bitcoin Core developers +// 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +KevaDialog::KevaDialog(const PlatformStyle *_platformStyle, QWidget *parent) : + QDialog(parent), + ui(new Ui::KevaDialog), + columnResizingFixer(0), + model(0), + platformStyle(_platformStyle) +{ + ui->setupUi(this); + + if (!_platformStyle->getImagesOnButtons()) { + ui->bookmarksButton->setIcon(QIcon()); + ui->showValueButton->setIcon(QIcon()); + ui->removeButton->setIcon(QIcon()); + } else { + ui->bookmarksButton->setIcon(_platformStyle->SingleColorIcon(":/icons/address-book")); + ui->showValueButton->setIcon(_platformStyle->SingleColorIcon(":/icons/edit")); + ui->removeButton->setIcon(_platformStyle->SingleColorIcon(":/icons/remove")); + ui->addKVButton->setIcon(_platformStyle->SingleColorIcon(":/icons/add")); + } + + // context menu actions + QAction *copyURIAction = new QAction(tr("Copy URI"), this); + QAction *copyLabelAction = new QAction(tr("Copy label"), this); + QAction *copyMessageAction = new QAction(tr("Copy message"), this); + QAction *copyAmountAction = new QAction(tr("Copy amount"), this); + + // context menu + contextMenu = new QMenu(this); + contextMenu->addAction(copyURIAction); + contextMenu->addAction(copyLabelAction); + contextMenu->addAction(copyMessageAction); + contextMenu->addAction(copyAmountAction); + + // context menu signals + connect(ui->kevaView, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(showMenu(QPoint))); + connect(copyURIAction, SIGNAL(triggered()), this, SLOT(copyURI())); + connect(copyLabelAction, SIGNAL(triggered()), this, SLOT(copyLabel())); + connect(copyMessageAction, SIGNAL(triggered()), this, SLOT(copyMessage())); + connect(copyAmountAction, SIGNAL(triggered()), this, SLOT(copyAmount())); + + connect(ui->nameSpace, SIGNAL(textChanged(const QString &)), this, SLOT(onNamespaceChanged(const QString &))); +} + +void KevaDialog::setModel(WalletModel *_model) +{ + this->model = _model; + + if(_model && _model->getOptionsModel()) + { + _model->getKevaTableModel()->sort(KevaTableModel::Block, Qt::DescendingOrder); + QTableView* tableView = ui->kevaView; + + tableView->verticalHeader()->hide(); + tableView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + tableView->setModel(_model->getKevaTableModel()); + tableView->setAlternatingRowColors(true); + tableView->setSelectionBehavior(QAbstractItemView::SelectRows); + tableView->setSelectionMode(QAbstractItemView::ContiguousSelection); + tableView->setColumnWidth(KevaTableModel::Date, DATE_COLUMN_WIDTH); + tableView->setColumnWidth(KevaTableModel::Key, KEY_COLUMN_WIDTH); + tableView->setColumnWidth(KevaTableModel::Block, BLOCK_MINIMUM_COLUMN_WIDTH); + + connect(ui->kevaView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), + this, SLOT(kevaView_selectionChanged())); + + // Last 2 columns are set by the columnResizingFixer, when the table geometry is ready. + columnResizingFixer = new GUIUtil::TableViewLastColumnResizingFixer(tableView, BLOCK_MINIMUM_COLUMN_WIDTH, DATE_COLUMN_WIDTH, this); + } +} + +void KevaDialog::showNamespace(QString ns) +{ + ui->nameSpace->setText(ns); + on_showContent_clicked(); +} + +KevaDialog::~KevaDialog() +{ + delete ui; +} + +void KevaDialog::clear() +{ + ui->nameSpace->setText(""); + updateDisplayUnit(); +} + +void KevaDialog::reject() +{ + clear(); +} + +void KevaDialog::accept() +{ + clear(); +} + +void KevaDialog::updateDisplayUnit() +{ + if(model && model->getOptionsModel()) + { + } +} + +void KevaDialog::on_createNamespace_clicked() +{ + if(!model || !model->getKevaTableModel()) + return; + + KevaNewNamespaceDialog *dialog = new KevaNewNamespaceDialog(this); + dialog->setAttribute(Qt::WA_DeleteOnClose); + dialog->show(); +} + +void KevaDialog::onNamespaceChanged(const QString& nameSpace) +{ + std::string namespaceStr = nameSpace.toStdString(); + valtype nameSpaceVal; + if (DecodeKevaNamespace(namespaceStr, Params(), nameSpaceVal)) { + ui->addKVButton->setEnabled(true); + } else { + ui->addKVButton->setEnabled(false); + } +} + + +void KevaDialog::on_listNamespaces_clicked() +{ + if(!model || !model->getKevaTableModel()) + return; + + KevaMyNamespacesDialog *dialog = new KevaMyNamespacesDialog(this); + + std::vector vNamespaceEntries; + model->getNamespaceEntries(vNamespaceEntries); + model->getKevaNamespaceModel()->setNamespace(std::move(vNamespaceEntries)); + model->getKevaNamespaceModel()->sort(KevaNamespaceModel::Name, Qt::DescendingOrder); + + dialog->setModel(model); + dialog->setAttribute(Qt::WA_DeleteOnClose); + dialog->show(); +} + +void KevaDialog::on_bookmarksButton_clicked() +{ + if(!model || !model->getKevaTableModel()) + return; + + KevaBookmarksDialog *dialog = new KevaBookmarksDialog(this); + + model->getKevaBookmarksModel()->loadBookmarks(); + model->getKevaBookmarksModel()->sort(KevaBookmarksModel::Name, Qt::DescendingOrder); + + dialog->setModel(model); + dialog->setAttribute(Qt::WA_DeleteOnClose); + dialog->show(); +} + +void KevaDialog::on_showContent_clicked() +{ + if(!model || !model->getKevaTableModel()) + return; + + valtype namespaceVal; + QString nameSpace = ui->nameSpace->text(); + if (!DecodeKevaNamespace(nameSpace.toStdString(), Params(), namespaceVal)) { + // TODO: show error dialog + return; + } + + std::vector vKevaEntries; + model->getKevaEntries(vKevaEntries, ValtypeToString(namespaceVal)); + model->getKevaTableModel()->setKeva(std::move(vKevaEntries)); + model->getKevaTableModel()->sort(KevaTableModel::Date, Qt::DescendingOrder); +} + +void KevaDialog::on_kevaView_doubleClicked(const QModelIndex &index) +{ + QString nameSpace = ui->nameSpace->text(); + KevaDetailDialog *dialog = new KevaDetailDialog(index, this, nameSpace); + dialog->setAttribute(Qt::WA_DeleteOnClose); + dialog->show(); +} + +void KevaDialog::kevaView_selectionChanged() +{ + // Enable Show/Remove buttons only if anything is selected. + bool enable = !ui->kevaView->selectionModel()->selectedRows().isEmpty(); + ui->showValueButton->setEnabled(enable); + ui->removeButton->setEnabled(enable); + ui->addKVButton->setEnabled(enable); +} + +void KevaDialog::on_showValueButton_clicked() +{ + if(!model || !model->getKevaTableModel() || !ui->kevaView->selectionModel()) + return; + QModelIndexList selection = ui->kevaView->selectionModel()->selectedRows(); + + for (const QModelIndex& index : selection) { + on_kevaView_doubleClicked(index); + } +} + +void KevaDialog::on_removeButton_clicked() +{ + if(!model || !model->getKevaTableModel() || !ui->kevaView->selectionModel()) + return; + QModelIndexList selection = ui->kevaView->selectionModel()->selectedRows(); + if(selection.empty()) + return; + + QMessageBox::StandardButton reply; + QModelIndex index = selection.at(0); + QModelIndex keyIdx = index.sibling(index.row(), KevaTableModel::Key); + QString keyStr = keyIdx.data(Qt::DisplayRole).toString(); + reply = QMessageBox::warning(this, tr("Warning"), tr("Delete the key \"%1\"?").arg(keyStr), + QMessageBox::Cancel|QMessageBox::Ok); + + if (reply == QMessageBox::Cancel) { + return; + } + + std::string nameSpace = ui->nameSpace->text().toStdString(); + std::string key = keyStr.toStdString(); + + int ret = this->model->deleteKevaEntry(nameSpace, key); + if (ret > 0) { + QString msg; + switch (ret) { + case WalletModel::InvalidNamespace: + msg = tr("Invalid namespace \"%1\"").arg(ui->nameSpace->text()); + break; + case WalletModel::KeyNotFound: + msg = tr("Key not found: \"%1\".").arg(keyStr); + break; + default: + msg = tr("Unknown error."); + } + QMessageBox::critical(this, tr("Error"), msg, QMessageBox::Ok); + return; + } + + // correct for selection mode ContiguousSelection + QModelIndex firstIndex = selection.at(0); + model->getKevaTableModel()->removeRows(firstIndex.row(), selection.length(), firstIndex.parent()); +} + +void KevaDialog::on_addKVButton_clicked() +{ + QString ns = ui->nameSpace->text(); + KevaAddKeyDialog *dialog = new KevaAddKeyDialog(this, ns); + dialog->setAttribute(Qt::WA_DeleteOnClose); + dialog->show(); +} + +// We override the virtual resizeEvent of the QWidget to adjust tables column +// sizes as the tables width is proportional to the dialogs width. +void KevaDialog::resizeEvent(QResizeEvent *event) +{ + QWidget::resizeEvent(event); + columnResizingFixer->stretchColumnWidth(KevaTableModel::Block); +} + +void KevaDialog::keyPressEvent(QKeyEvent *event) +{ + if (event->key() == Qt::Key_Return) + { + // press return -> submit form + if (ui->nameSpace->hasFocus()) + { + event->ignore(); + on_showContent_clicked(); + return; + } + } + + this->QDialog::keyPressEvent(event); +} + +QModelIndex KevaDialog::selectedRow() +{ + if(!model || !model->getKevaTableModel() || !ui->kevaView->selectionModel()) + return QModelIndex(); + QModelIndexList selection = ui->kevaView->selectionModel()->selectedRows(); + if(selection.empty()) + return QModelIndex(); + // correct for selection mode ContiguousSelection + QModelIndex firstIndex = selection.at(0); + return firstIndex; +} + +// copy column of selected row to clipboard +void KevaDialog::copyColumnToClipboard(int column) +{ + QModelIndex firstIndex = selectedRow(); + if (!firstIndex.isValid()) { + return; + } + GUIUtil::setClipboard(model->getKevaTableModel()->data(firstIndex.child(firstIndex.row(), column), Qt::EditRole).toString()); +} + +// context menu +void KevaDialog::showMenu(const QPoint &point) +{ + if (!selectedRow().isValid()) { + return; + } + contextMenu->exec(QCursor::pos()); +} + +// context menu action: copy URI +void KevaDialog::copyURI() +{ +#if 0 + QModelIndex sel = selectedRow(); + if (!sel.isValid()) { + return; + } + + const KevaTableModel * const submodel = model->getKevaTableModel(); + const QString uri = GUIUtil::formatBitcoinURI(submodel->entry(sel.row()).recipient); + GUIUtil::setClipboard(uri); +#endif +} + +// context menu action: copy label +void KevaDialog::copyLabel() +{ + copyColumnToClipboard(KevaTableModel::Key); +} + +// context menu action: copy message +void KevaDialog::copyMessage() +{ + copyColumnToClipboard(KevaTableModel::Value); +} + +// context menu action: copy amount +void KevaDialog::copyAmount() +{ + copyColumnToClipboard(KevaTableModel::Block); +} + + +int KevaDialog::createNamespace(std::string displayName, std::string& namespaceId) +{ + if (!this->model) { + return 0; + } + + int ret = this->model->createNamespace(displayName, namespaceId); + if (ret > 0) { + QString msg; + switch (ret) { + case WalletModel::NamespaceTooLong: + msg = tr("Namespace too long \"%1\"").arg(QString::fromStdString(displayName)); + break; + default: + msg = tr("Unknown error."); + } + QMessageBox::critical(this, tr("Error"), msg, QMessageBox::Ok); + return 0; + } + return 1; +} + +int KevaDialog::addKeyValue(std::string& namespaceId, std::string& key, std::string& value) +{ + if (!this->model) { + return 0; + } + + int ret = this->model->addKeyValue(namespaceId, key, value); + if (ret > 0) { + QString msg; + switch (ret) { + case WalletModel::CannotUpdate: + msg = tr("Cannot add key-value. Make sure you own this namespace."); + break; + case WalletModel::KeyTooLong: + msg = tr("Key too long."); + break; + case WalletModel::ValueTooLong: + msg = tr("Value too long."); + break; + default: + msg = tr("Unknown error."); + } + QMessageBox::critical(this, tr("Error"), msg, QMessageBox::Ok); + return 0; + } + return 1; +} diff --git a/src/qt/kevadialog.h b/src/qt/kevadialog.h new file mode 100644 index 000000000..66b9958c2 --- /dev/null +++ b/src/qt/kevadialog.h @@ -0,0 +1,88 @@ +// Copyright (c) 2011-2017 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_KEVADIALOG_H +#define BITCOIN_QT_KEVADIALOG_H + +#include + +#include +#include +#include +#include +#include +#include +#include + +class PlatformStyle; +class WalletModel; + +namespace Ui { + class KevaDialog; +} + +QT_BEGIN_NAMESPACE +class QModelIndex; +QT_END_NAMESPACE + +/** Dialog for requesting payment of bitcoins */ +class KevaDialog : public QDialog +{ + Q_OBJECT + +public: + enum ColumnWidths { + DATE_COLUMN_WIDTH = 130, + KEY_COLUMN_WIDTH = 120, + BLOCK_MINIMUM_COLUMN_WIDTH = 100, + MINIMUM_COLUMN_WIDTH = 100 + }; + + explicit KevaDialog(const PlatformStyle *platformStyle, QWidget *parent = 0); + ~KevaDialog(); + + void setModel(WalletModel *model); + void showNamespace(QString ns); + int createNamespace(std::string displayName, std::string& namespaceId); + int addKeyValue(std::string& namespaceId, std::string& key, std::string& Value); + +public Q_SLOTS: + void clear(); + void reject(); + void accept(); + void onNamespaceChanged(const QString& nameSpace); + +protected: + virtual void keyPressEvent(QKeyEvent *event); + +private: + Ui::KevaDialog *ui; + GUIUtil::TableViewLastColumnResizingFixer *columnResizingFixer; + WalletModel *model; + QMenu *contextMenu; + const PlatformStyle *platformStyle; + + QModelIndex selectedRow(); + void copyColumnToClipboard(int column); + virtual void resizeEvent(QResizeEvent *event); + +private Q_SLOTS: + void on_showContent_clicked(); + void on_createNamespace_clicked(); + void on_listNamespaces_clicked(); + void on_showValueButton_clicked(); + void on_removeButton_clicked(); + void on_addKVButton_clicked(); + void on_kevaView_doubleClicked(const QModelIndex &index); + void kevaView_selectionChanged(); + void on_bookmarksButton_clicked(); + void updateDisplayUnit(); + void showMenu(const QPoint &point); + void copyURI(); + void copyLabel(); + void copyMessage(); + void copyAmount(); +}; + +#endif // BITCOIN_QT_KEVADIALOG_H diff --git a/src/qt/kevamynamespacesdialog.cpp b/src/qt/kevamynamespacesdialog.cpp new file mode 100644 index 000000000..191f08612 --- /dev/null +++ b/src/qt/kevamynamespacesdialog.cpp @@ -0,0 +1,80 @@ +// Copyright (c) 2011-2017 The Bitcoin Core developers +// 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 + +KevaMyNamespacesDialog::KevaMyNamespacesDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::KevaMyNamespacesDialog) +{ + ui->setupUi(this); + ui->buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false); + ui->buttonBox->button(QDialogButtonBox::Apply)->setText(tr("Show")); + connect(ui->buttonBox->button(QDialogButtonBox::Cancel), SIGNAL(clicked()), this, SLOT(reject())); + connect(ui->buttonBox->button(QDialogButtonBox::Apply), SIGNAL(clicked()), this, SLOT(apply())); +} + +void KevaMyNamespacesDialog::setModel(WalletModel *_model) +{ + this->model = _model; + + if(_model && _model->getOptionsModel()) + { + _model->getKevaNamespaceModel()->sort(KevaNamespaceModel::Name, Qt::DescendingOrder); + QTableView* tableView = ui->namespaceView; + + tableView->verticalHeader()->hide(); + tableView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + tableView->setModel(_model->getKevaNamespaceModel()); + tableView->setAlternatingRowColors(true); + tableView->setSelectionBehavior(QAbstractItemView::SelectRows); + tableView->setSelectionMode(QAbstractItemView::ContiguousSelection); + //tableView->setColumnWidth(KevaNamespaceModel::Id, ID_COLUMN_WIDTH); + //tableView->setColumnWidth(KevaNamespaceModel::Name, NAME_COLUMN_WIDTH); + tableView->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); + + connect(tableView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), + this, SLOT(namespaceView_selectionChanged())); + } +} + + +void KevaMyNamespacesDialog::namespaceView_selectionChanged() +{ + bool enable = !ui->namespaceView->selectionModel()->selectedRows().isEmpty(); + ui->buttonBox->button(QDialogButtonBox::Apply)->setEnabled(enable); + + if (enable) { + selectedIndex = ui->namespaceView->selectionModel()->currentIndex(); + } else { + QModelIndex empty; + selectedIndex = empty; + } +} + +void KevaMyNamespacesDialog::apply() +{ + QModelIndex idIdx = selectedIndex.sibling(selectedIndex.row(), KevaNamespaceModel::Id); + QString idStr = idIdx.data(Qt::DisplayRole).toString(); + KevaDialog* dialog = (KevaDialog*)this->parentWidget(); + dialog->showNamespace(idStr); + QDialog::close(); +} + +void KevaMyNamespacesDialog::reject() +{ + QDialog::reject(); +} + +KevaMyNamespacesDialog::~KevaMyNamespacesDialog() +{ + delete ui; +} \ No newline at end of file diff --git a/src/qt/kevamynamespacesdialog.h b/src/qt/kevamynamespacesdialog.h new file mode 100644 index 000000000..b8ed56cad --- /dev/null +++ b/src/qt/kevamynamespacesdialog.h @@ -0,0 +1,51 @@ +// Copyright (c) 2011-2014 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_KEVAMYNMAESPACESDIALOG_H +#define BITCOIN_QT_KEVAMYNMAESPACESDIALOG_H + +#include +#include + +#include +#include +#include + +class WalletModel; + +namespace Ui { + class KevaMyNamespacesDialog; +} + + +/** Dialog showing namepsace creation. */ +class KevaMyNamespacesDialog : public QDialog +{ + Q_OBJECT + + enum ColumnWidths { + ID_COLUMN_WIDTH = 260, + NAME_COLUMN_WIDTH = 260, + MINIMUM_COLUMN_WIDTH = 260 + }; + +public: + explicit KevaMyNamespacesDialog(QWidget *parent = 0); + ~KevaMyNamespacesDialog(); + void setModel(WalletModel *_model); + +public Q_SLOTS: + void apply(); + void reject(); + +private Q_SLOTS: + void namespaceView_selectionChanged(); + +private: + Ui::KevaMyNamespacesDialog *ui; + WalletModel *model; + QModelIndex selectedIndex; +}; + +#endif // BITCOIN_QT_KEVAMYNMAESPACESDIALOG_H diff --git a/src/qt/kevanamespacemodel.cpp b/src/qt/kevanamespacemodel.cpp new file mode 100644 index 000000000..ae88e1b4a --- /dev/null +++ b/src/qt/kevanamespacemodel.cpp @@ -0,0 +1,156 @@ +// Copyright (c) 2011-2017 The Bitcoin Core developers +// 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 + + +KevaNamespaceModel::KevaNamespaceModel(CWallet *wallet, WalletModel *parent) : + QAbstractTableModel(parent), walletModel(parent) +{ + Q_UNUSED(wallet) + + /* These columns must match the indices in the ColumnIndex enumeration */ + columns << tr("Id") << tr("Name"); +} + +KevaNamespaceModel::~KevaNamespaceModel() +{ + /* Intentionally left empty */ +} + +int KevaNamespaceModel::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent); + + return list.length(); +} + +int KevaNamespaceModel::columnCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent); + + return columns.length(); +} + +QVariant KevaNamespaceModel::data(const QModelIndex &index, int role) const +{ + if(!index.isValid() || index.row() >= list.length()) + return QVariant(); + + if (role == Qt::TextColorRole) + { + const NamespaceEntry *rec = &list[index.row()]; + if (!rec->confirmed) { + return QVariant(QBrush (QColor(Qt::gray))); + } + return QVariant(); + } + else if(role == Qt::DisplayRole || role == Qt::EditRole) + { + const NamespaceEntry *rec = &list[index.row()]; + switch(index.column()) + { + case Id: + return QString::fromStdString(rec->id); + case Name: + return QString::fromStdString(rec->name); + } + } + else if (role == Qt::TextAlignmentRole) + { + return (int)(Qt::AlignLeft|Qt::AlignVCenter); + } + return QVariant(); +} + +bool KevaNamespaceModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + return true; +} + +QVariant KevaNamespaceModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if(orientation == Qt::Horizontal) + { + if(role == Qt::DisplayRole && section < columns.size()) + { + return columns[section]; + } + } + return QVariant(); +} + + +QModelIndex KevaNamespaceModel::index(int row, int column, const QModelIndex &parent) const +{ + Q_UNUSED(parent); + + return createIndex(row, column); +} + +bool KevaNamespaceModel::removeRows(int row, int count, const QModelIndex &parent) +{ + Q_UNUSED(parent); + + if(count > 0 && row >= 0 && (row+count) <= list.size()) + { + beginRemoveRows(parent, row, row + count - 1); + list.erase(list.begin() + row, list.begin() + row + count); + endRemoveRows(); + return true; + } else { + return false; + } +} + +Qt::ItemFlags KevaNamespaceModel::flags(const QModelIndex &index) const +{ + return Qt::ItemIsSelectable | Qt::ItemIsEnabled; +} + +// actually add to table in GUI +void KevaNamespaceModel::setNamespace(std::vector vNamespaceEntries) +{ + // Remove the old ones. + removeRows(0, list.size()); + list.clear(); + + for (auto it = vNamespaceEntries.begin(); it != vNamespaceEntries.end(); it++) { + beginInsertRows(QModelIndex(), 0, 0); + list.prepend(*it); + endInsertRows(); + } +} + +void KevaNamespaceModel::sort(int column, Qt::SortOrder order) +{ + qSort(list.begin(), list.end(), NamespaceEntryLessThan(column, order)); + Q_EMIT dataChanged(index(0, 0, QModelIndex()), index(list.size() - 1, NUMBER_OF_COLUMNS - 1, QModelIndex())); +} + + +bool NamespaceEntryLessThan::operator()(NamespaceEntry &left, NamespaceEntry &right) const +{ + NamespaceEntry *pLeft = &left; + NamespaceEntry *pRight = &right; + if (order == Qt::DescendingOrder) + std::swap(pLeft, pRight); + + switch(column) + { + case KevaNamespaceModel::Id: + return pLeft->id < pRight->id; + case KevaNamespaceModel::Name: + return pLeft->name < pRight->name; + default: + return pLeft->id < pRight->id; + } +} diff --git a/src/qt/kevanamespacemodel.h b/src/qt/kevanamespacemodel.h new file mode 100644 index 000000000..56b99bdd5 --- /dev/null +++ b/src/qt/kevanamespacemodel.h @@ -0,0 +1,80 @@ +// Copyright (c) 2011-2017 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_KEVANAMESPACEMODEL_H +#define BITCOIN_QT_KEVANAMESPACEMODEL_H + +#include + +#include +#include +#include + +class CWallet; + +class NamespaceEntry +{ +public: + NamespaceEntry():confirmed(true) { } + + std::string id; + std::string name; + bool confirmed; +}; + +class NamespaceEntryLessThan +{ +public: + NamespaceEntryLessThan(int nColumn, Qt::SortOrder fOrder): + column(nColumn), order(fOrder) {} + bool operator()(NamespaceEntry &left, NamespaceEntry &right) const; + +private: + int column; + Qt::SortOrder order; +}; + +/** Model for list of recently generated payment requests / bitcoin: URIs. + * Part of wallet model. + */ +class KevaNamespaceModel: public QAbstractTableModel +{ + Q_OBJECT + +public: + explicit KevaNamespaceModel(CWallet *wallet, WalletModel *parent); + ~KevaNamespaceModel(); + + enum ColumnIndex { + Id = 0, + Name = 1, + NUMBER_OF_COLUMNS + }; + + /** @name Methods overridden from QAbstractTableModel + @{*/ + int rowCount(const QModelIndex &parent) const; + int columnCount(const QModelIndex &parent) const; + QVariant data(const QModelIndex &index, int role) const; + bool setData(const QModelIndex &index, const QVariant &value, int role); + QVariant headerData(int section, Qt::Orientation orientation, int role) const; + QModelIndex index(int row, int column, const QModelIndex &parent) const; + bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()); + Qt::ItemFlags flags(const QModelIndex &index) const; + /*@}*/ + + const NamespaceEntry &entry(int row) const { return list[row]; } + void setNamespace(std::vector vNamespaceEntries); + +public Q_SLOTS: + void sort(int column, Qt::SortOrder order = Qt::AscendingOrder); + +private: + WalletModel *walletModel; + QStringList columns; + QList list; + int64_t nReceiveRequestsMaxId; +}; + +#endif // BITCOIN_QT_KEVANAMESPACEMODEL_H diff --git a/src/qt/kevanewnamespacedialog.cpp b/src/qt/kevanewnamespacedialog.cpp new file mode 100644 index 000000000..7a54df968 --- /dev/null +++ b/src/qt/kevanewnamespacedialog.cpp @@ -0,0 +1,53 @@ +// Copyright (c) 2011-2017 The Bitcoin Core developers +// 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 + +KevaNewNamespaceDialog::KevaNewNamespaceDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::KevaNewNamespaceDialog) +{ + ui->setupUi(this); + connect(ui->buttonBox->button(QDialogButtonBox::Cancel), SIGNAL(clicked()), this, SLOT(close())); + connect(ui->buttonBox->button(QDialogButtonBox::Save), SIGNAL(clicked()), this, SLOT(create())); + connect(ui->namespaceText, SIGNAL(textChanged(const QString &)), this, SLOT(onNamespaceChanged(const QString &))); + ui->buttonBox->button(QDialogButtonBox::Save)->setEnabled(false); +} + +void KevaNewNamespaceDialog::onNamespaceChanged(const QString & ns) +{ + int length = ns.length(); + bool enabled = length > 0; + ui->buttonBox->button(QDialogButtonBox::Save)->setEnabled(enabled); +} + +void KevaNewNamespaceDialog::create() +{ + KevaDialog* dialog = (KevaDialog*)this->parentWidget(); + QString nsText = ui->namespaceText->text(); + std::string namespaceId; + if (!dialog->createNamespace(nsText.toStdString(), namespaceId)) { + QDialog::close(); + return; + } + dialog->showNamespace(QString::fromStdString(namespaceId)); + QDialog::close(); +} + +void KevaNewNamespaceDialog::close() +{ + QDialog::close(); +} + +KevaNewNamespaceDialog::~KevaNewNamespaceDialog() +{ + delete ui; +} \ No newline at end of file diff --git a/src/qt/kevanewnamespacedialog.h b/src/qt/kevanewnamespacedialog.h new file mode 100644 index 000000000..00407ef91 --- /dev/null +++ b/src/qt/kevanewnamespacedialog.h @@ -0,0 +1,36 @@ +// Copyright (c) 2011-2014 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_KEVANEWNMAESPACEDIALOG_H +#define BITCOIN_QT_KEVANEWNMAESPACEDIALOG_H + +#include +#include + +#include + +namespace Ui { + class KevaNewNamespaceDialog; +} + + +/** Dialog showing namepsace creation. */ +class KevaNewNamespaceDialog : public QDialog +{ + Q_OBJECT + +public: + explicit KevaNewNamespaceDialog(QWidget *parent = 0); + ~KevaNewNamespaceDialog(); + +public Q_SLOTS: + void create(); + void close(); + void onNamespaceChanged(const QString & ns); + +private: + Ui::KevaNewNamespaceDialog *ui; +}; + +#endif // BITCOIN_QT_KEVANEWNMAESPACEDIALOG_H diff --git a/src/qt/kevatablemodel.cpp b/src/qt/kevatablemodel.cpp new file mode 100644 index 000000000..08327be60 --- /dev/null +++ b/src/qt/kevatablemodel.cpp @@ -0,0 +1,177 @@ +// Copyright (c) 2011-2017 The Bitcoin Core developers +// 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 + + +KevaTableModel::KevaTableModel(CWallet *wallet, WalletModel *parent) : + QAbstractTableModel(parent), walletModel(parent) +{ + Q_UNUSED(wallet) + + /* These columns must match the indices in the ColumnIndex enumeration */ + columns << tr("Date") << tr("Key") << tr("Value") << tr("Block"); + + // TODO: display new keva entry when it arrives. + // connect(walletModel->getOptionsModel(), SIGNAL(displayUnitChanged(int)), this, SLOT(updateDisplayUnit())); +} + +KevaTableModel::~KevaTableModel() +{ + /* Intentionally left empty */ +} + +int KevaTableModel::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent); + + return list.length(); +} + +int KevaTableModel::columnCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent); + + return columns.length(); +} + +QVariant KevaTableModel::data(const QModelIndex &index, int role) const +{ + if(!index.isValid() || index.row() >= list.length()) + return QVariant(); + + if (role == Qt::TextColorRole) + { + const KevaEntry *rec = &list[index.row()]; + if (rec->block < 0) { + return QVariant(QBrush (QColor(Qt::gray))); + } + return QVariant(); + } + else if(role == Qt::DisplayRole || role == Qt::EditRole) + { + const KevaEntry *rec = &list[index.row()]; + switch(index.column()) + { + case Date: + return GUIUtil::dateTimeStr(rec->date); + case Key: + return QString::fromStdString(rec->key); + case Value: + return QString::fromStdString(rec->value); + case Block: + return QString::number(rec->block); + } + } + else if (role == Qt::TextAlignmentRole) + { + if (index.column() == Block) + return (int)(Qt::AlignRight|Qt::AlignVCenter); + } + return QVariant(); +} + +bool KevaTableModel::setData(const QModelIndex &index, const QVariant &value, int role) +{ + return true; +} + +QVariant KevaTableModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if(orientation == Qt::Horizontal) + { + if(role == Qt::DisplayRole && section < columns.size()) + { + return columns[section]; + } + } + return QVariant(); +} + +/** Gets title for amount column including current display unit if optionsModel reference available. */ +QString KevaTableModel::getAmountTitle() +{ + return (this->walletModel->getOptionsModel() != nullptr) ? tr("Requested") + " ("+BitcoinUnits::shortName(this->walletModel->getOptionsModel()->getDisplayUnit()) + ")" : ""; +} + +QModelIndex KevaTableModel::index(int row, int column, const QModelIndex &parent) const +{ + Q_UNUSED(parent); + + return createIndex(row, column); +} + +bool KevaTableModel::removeRows(int row, int count, const QModelIndex &parent) +{ + Q_UNUSED(parent); + + if(count > 0 && row >= 0 && (row+count) <= list.size()) + { + beginRemoveRows(parent, row, row + count - 1); + list.erase(list.begin() + row, list.begin() + row + count); + endRemoveRows(); + return true; + } else { + return false; + } +} + +Qt::ItemFlags KevaTableModel::flags(const QModelIndex &index) const +{ + return Qt::ItemIsSelectable | Qt::ItemIsEnabled; +} + + +// actually add to table in GUI +void KevaTableModel::setKeva(std::vector vKevaEntries) +{ + // Remove the old ones. + removeRows(0, list.size()); + list.clear(); + + for (auto it = vKevaEntries.begin(); it != vKevaEntries.end(); it++) { + beginInsertRows(QModelIndex(), 0, 0); + list.prepend(*it); + endInsertRows(); + } +} + +void KevaTableModel::sort(int column, Qt::SortOrder order) +{ + qSort(list.begin(), list.end(), KevaEntryLessThan(column, order)); + Q_EMIT dataChanged(index(0, 0, QModelIndex()), index(list.size() - 1, NUMBER_OF_COLUMNS - 1, QModelIndex())); +} + +void KevaTableModel::updateDisplayUnit() +{ +} + +bool KevaEntryLessThan::operator()(KevaEntry &left, KevaEntry &right) const +{ + KevaEntry *pLeft = &left; + KevaEntry *pRight = &right; + if (order == Qt::DescendingOrder) + std::swap(pLeft, pRight); + + switch(column) + { + case KevaTableModel::Date: + return pLeft->date.toTime_t() < pRight->date.toTime_t(); + case KevaTableModel::Block: + return pLeft->block < pRight->block; + case KevaTableModel::Key: + return pLeft->key < pRight->key; + case KevaTableModel::Value: + return pLeft->value < pRight->value; + default: + return pLeft->block < pRight->block; + } +} diff --git a/src/qt/kevatablemodel.h b/src/qt/kevatablemodel.h new file mode 100644 index 000000000..83600bc0b --- /dev/null +++ b/src/qt/kevatablemodel.h @@ -0,0 +1,89 @@ +// Copyright (c) 2011-2017 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_KEVATABLEMODEL_H +#define BITCOIN_QT_KEVATABLEMODEL_H + +#include + +#include +#include +#include + +class CWallet; + +class KevaEntry +{ +public: + KevaEntry() { } + + std::string key; + std::string value; + int64_t block; + QDateTime date; +}; + +class KevaEntryLessThan +{ +public: + KevaEntryLessThan(int nColumn, Qt::SortOrder fOrder): + column(nColumn), order(fOrder) {} + bool operator()(KevaEntry &left, KevaEntry &right) const; + +private: + int column; + Qt::SortOrder order; +}; + +/** Model for list of recently generated payment requests / bitcoin: URIs. + * Part of wallet model. + */ +class KevaTableModel: public QAbstractTableModel +{ + Q_OBJECT + +public: + explicit KevaTableModel(CWallet *wallet, WalletModel *parent); + ~KevaTableModel(); + + enum ColumnIndex { + Date = 0, + Key = 1, + Value = 2, + Block = 3, + NUMBER_OF_COLUMNS + }; + + /** @name Methods overridden from QAbstractTableModel + @{*/ + int rowCount(const QModelIndex &parent) const; + int columnCount(const QModelIndex &parent) const; + QVariant data(const QModelIndex &index, int role) const; + bool setData(const QModelIndex &index, const QVariant &value, int role); + QVariant headerData(int section, Qt::Orientation orientation, int role) const; + QModelIndex index(int row, int column, const QModelIndex &parent) const; + bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex()); + Qt::ItemFlags flags(const QModelIndex &index) const; + /*@}*/ + + const KevaEntry &entry(int row) const { return list[row]; } + void setKeva(std::vector vKevaEntries); + +public Q_SLOTS: + void sort(int column, Qt::SortOrder order = Qt::AscendingOrder); + void updateDisplayUnit(); + +private: + WalletModel *walletModel; + QStringList columns; + QList list; + int64_t nReceiveRequestsMaxId; + + /** Updates the column title to "Amount (DisplayUnit)" and emits headerDataChanged() signal for table headers to react. */ + void updateAmountColumnTitle(); + /** Gets title for amount column including current display unit if optionsModel reference available. */ + QString getAmountTitle(); +}; + +#endif // BITCOIN_QT_KEVATABLEMODEL_H diff --git a/src/qt/locale/bitcoin_en.ts b/src/qt/locale/bitcoin_en.ts index 647e8f566..384cb904d 100644 --- a/src/qt/locale/bitcoin_en.ts +++ b/src/qt/locale/bitcoin_en.ts @@ -85,7 +85,7 @@ - These are your Kevacoin addresses for receiving payments. It is recommended to use a new receiving address for each transaction. + These are your Kevaoin addresses for receiving payments. It is recommended to use a new receiving address for each transaction. @@ -304,17 +304,17 @@ BitcoinGUI - + Sign &message... Sign &message... - + Synchronizing with network... Synchronizing with network... - + &Overview &Overview @@ -339,7 +339,17 @@ Browse transaction history - + + &Keva + + + + + Keva related operations + + + + E&xit E&xit @@ -409,7 +419,7 @@ - + Click to disable network activity. @@ -434,12 +444,12 @@ Reindexing blocks on disk... - + Send coins to a Kevacoin address Send coins to a Kevacoin address - + Backup wallet to another location Backup wallet to another location @@ -464,12 +474,12 @@ &Verify message... - + Kevacoin Kevacoin - + Wallet Wallet @@ -484,7 +494,7 @@ &Receive - + &Show / Hide &Show / Hide @@ -529,12 +539,12 @@ Tabs toolbar - + Request payments (generates QR codes and kevacoin: URIs) - + Show the list of used sending addresses and labels @@ -554,7 +564,7 @@ - + %n active connection(s) to Kevacoin network %n active connection to Kevacoin network @@ -615,17 +625,17 @@ Up to date - + Show the %1 help message to get a list with possible Kevacoin command-line options - + %1 client - + Connecting to peers... @@ -635,7 +645,7 @@ Catching up... - + Date: %1 @@ -1149,6 +1159,290 @@ + + KevaAddKeyDialog + + + Key + + + + + + New value + + + + + Value + + + + + KevaBookmarksDialog + + + Show + + + + + KevaBookmarksModel + + + Id + + + + + Name + + + + + KevaDetailDialog + + + This pane shows the value associated with a give key + + + + + Value for %1 + + + + + KevaDialog + + + Show the selected request (does the same as double clicking an entry) + + + + + + Show + + + + + + The namespace ID with a prefix "N". + + + + + Use this form to perform Keva database operations. + + + + + Namespace: + + + + + Show content of the namespace. + + + + + Create a new namespace + + + + + &Create namespace + + + + + List my namespaces + + + + + &My Namespaces + + + + + Show bookmarks + + + + + &Bookmarks + + + + + Content of namespace + + + + + Remove the selected entries from the list + + + + + Remove + + + + + Add new key-value pair + + + + + Add key-value + + + + + Copy URI + + + + + Copy label + + + + + Copy message + + + + + Copy amount + + + + + Warning + Warning + + + + Delete the key "%1"? + + + + + Invalid namespace "%1" + + + + + Key not found: "%1". + + + + + + + Unknown error. + + + + + + + Error + Error + + + + Namespace too long "%1" + + + + + Cannot add key-value. Make sure you own this namespace. + + + + + Key too long. + + + + + Value too long. + + + + + KevaMyNamespacesDialog + + + Show + + + + + KevaNamespaceModel + + + Id + + + + + Name + + + + + KevaNewNamespaceDialog + + + The name of the namespace. + + + + + Name: + + + + + This pane allows the creation of a new Keva namespace + + + + + KevaTableModel + + + Date + Date + + + + Key + + + + + Value + + + + + Block + + + + + Requested + + + ModalOverlay @@ -1671,7 +1965,7 @@ PaymentServer - + @@ -1950,7 +2244,7 @@ - + %1 didn't yet exit safely... @@ -2623,7 +2917,7 @@ SendCoinsDialog - + Send Coins Send Coins @@ -2754,7 +3048,7 @@ - + Send to multiple recipients at once Send to multiple recipients at once @@ -2769,7 +3063,7 @@ - + Dust: @@ -2779,17 +3073,7 @@ - - Enable Replace-By-Fee - - - - - With Replace-By-Fee (BIP-125) you can increase a transaction's fee after it is sent. Without this, a higher fee may be recommended to compensate for increased transaction delay risk. - - - - + Clear &All Clear &All @@ -2809,7 +3093,7 @@ S&end - + Copy quantity @@ -2849,7 +3133,7 @@ - + @@ -2877,22 +3161,12 @@ - - You can increase the fee later (signals Replace-By-Fee, BIP-125). - - - - - Not signalling Replace-By-Fee, BIP-125. - - - - + Confirm send coins - + The recipient address is not valid. Please recheck. @@ -2942,7 +3216,7 @@ - + Estimated to begin confirmation within %n block(s). Estimated to begin confirmation within %n block. @@ -3329,11 +3603,6 @@ conflicted with a transaction with %1 confirmations - - - %1/offline - - 0/unconfirmed, %1 @@ -3370,20 +3639,7 @@ - - , has not been successfully broadcast yet - - - - - , broadcast through %n node(s) - - , broadcast through %n node - , broadcast through %n nodes - - - - + Date Date @@ -3593,11 +3849,6 @@ Open until %1 - - - Offline - - Unconfirmed @@ -3628,11 +3879,6 @@ Immature (%1 confirmations, will be available after %2) - - - This block was not received by any other nodes and will probably not be accepted! - - Generated but not accepted @@ -3674,7 +3920,7 @@ - + (no label) @@ -3927,12 +4173,12 @@ WalletModel - + Send Coins Send Coins - + Fee bump error @@ -3982,7 +4228,7 @@ WalletView - + &Export &Export @@ -3992,7 +4238,7 @@ Export the data in the current tab to a file - + Backup Wallet @@ -4025,7 +4271,7 @@ bitcoin-core - + Options: Options: @@ -4035,17 +4281,17 @@ Specify data directory - + Connect to a node to retrieve peer addresses, and disconnect Connect to a node to retrieve peer addresses, and disconnect - + Specify your own public address Specify your own public address - + Accept command line and JSON-RPC commands Accept command line and JSON-RPC commands @@ -4085,7 +4331,7 @@ - + Pruning blockstore... @@ -4100,7 +4346,7 @@ - + Kevacoin Core Kevacoin Core @@ -4200,12 +4446,7 @@ - - Reduce storage requirements by enabling pruning (deleting) of old blocks. This allows the pruneblockchain RPC to be called to delete specific blocks, and enables automatic pruning of old blocks if a target size in MiB is provided. This mode is incompatible with -txindex and -rescan. Warning: Reverting this setting requires re-downloading the entire blockchain. (default: 0 = disable pruning blocks, 1 = allow manual pruning via RPC, >%u = automatically prune block files to stay under the specified target size in MiB) - - - - + Set lowest fee rate (in %s/kB) for transactions to be included in block creation. (default: %s) @@ -4449,6 +4690,21 @@ Initialization sanity check failed. %s is shutting down. + + + Input tx is not a keva operation + + + + + Input tx is not mine + + + + + Input tx not found in wallet + + Invalid amount for -%s=<amount>: '%s' @@ -4625,7 +4881,7 @@ - + Allow JSON-RPC connections from specified source. Valid for <ip> are a single IP (e.g. 1.2.3.4), a network/netmask (e.g. 1.2.3.4/255.255.255.0) or a network/CIDR (e.g. 1.2.3.4/24). This option can be specified multiple times @@ -4740,7 +4996,7 @@ Information - + Invalid -onion address or hostname: '%s' @@ -4925,7 +5181,7 @@ Password for JSON-RPC connections - + Execute command when the best block changes (%s in cmd is replaced by block hash) Execute command when the best block changes (%s in cmd is replaced by block hash) @@ -5000,7 +5256,12 @@ - + + Reduce storage requirements by enabling pruning (deleting) of old blocks. This allows the pruneblockchain RPC to be called to delete specific blocks, and enables automatic pruning of old blocks if a target size in MiB is provided. This mode is incompatible with -txindex and -rescan. Warning: Reverting this setting requires re-downloading the entire blockchain. (default: 0 = disable pruning blocks, 1 = allow manual pruning via RPC, >=%u = automatically prune block files to stay under the specified target size in MiB) + + + + Sets the serialization of raw transaction or block hex returned in non-verbose mode, non-segwit(0) or segwit(1) (default: %d) @@ -5115,7 +5376,7 @@ - + Keypool ran out, please call keypoolrefill first @@ -5260,17 +5521,17 @@ Loading wallet... - + Cannot downgrade wallet Cannot downgrade wallet - + Rescanning... Rescanning... - + Done loading Done loading @@ -5280,4 +5541,4 @@ Error - \ No newline at end of file + diff --git a/src/qt/locale/bitcoin_zh_CN.ts b/src/qt/locale/bitcoin_zh_CN.ts index 88e3c2079..8c5b70ce1 100644 --- a/src/qt/locale/bitcoin_zh_CN.ts +++ b/src/qt/locale/bitcoin_zh_CN.ts @@ -907,6 +907,229 @@ (需要%n GB空间) + + KevaAddKeyDialog + + Key + + + + New value + 新值 + + + Value + + + + + KevaBookmarksDialog + + Show + 显示 + + + + KevaBookmarksModel + + Id + ID + + + Name + 名称 + + + + KevaDetailDialog + + This pane shows the value associated with a give key + 此窗格显示与给定键关联的值 + + + Value for %1 + %1 相应值 + + + + KevaDialog + + Show + 显示 + + + The namespace ID with a prefix "N". + + + + Use this form to perform Keva database operations. + 使用此表单执行Keva数据库操作。 + + + Namespace: + 命名空间: + + + Show content of the namespace. + 显示名称空间的内容。 + + + Create a new namespace + 创建一个新的名称空间 + + + &Create namespace + 创建名称空间 + + + List my namespaces + 列出我的名称空间 + + + &My Namespaces + 我的名称空间 + + + Show bookmarks + 显示书签 + + + &Bookmarks + 书签 + + + Content of namespace + 命名空间的内容 + + + Remove the selected entries from the list + 从列表中删除所选条目 + + + Remove + 删除 + + + Add new key-value pair + 添加新的键值对 + + + Add key-value + 添加键值对 + + + Copy URI + + + + Copy label + + + + Copy message + + + + Copy amount + + + + Warning + 警告 + + + Delete the key "%1"? + 删除键 "%1"? + + + Invalid namespace "%1" + 无效的名称空间 "%1" + + + Key not found: "%1". + 找不到键: "%1" + + + Unknown error. + 未知错误。 + + + Error + 错误 + + + Namespace too long "%1" + 命名空间太长: "%1" + + + Cannot add key-value. Make sure you own this namespace. + 无法添加键值。确保您拥有此名称空间。 + + + Key too long. + 键太长 + + + Value too long. + 值太长。 + + + + KevaMyNamespacesDialog + + Show + 显示 + + + + KevaNamespaceModel + + Id + Id + + + Name + 名称 + + + + KevaNewNamespaceDialog + + The name of the namespace. + 命名空间的名称。 + + + Name: + 名称: + + + This pane allows the creation of a new Keva namespace + 此窗格允许创建新的Keva命名空间 + + + + KevaTableModel + + Date + 日期 + + + Key + + + + Value + + + + Block + 区块 + + + Requested + 已要求 + + ModalOverlay diff --git a/src/qt/res/icons/about.png b/src/qt/res/icons/about.png index 473725b76a2d762b033288d446727ddb271f1a42..d034f477c2425a63e77d8b2c4b31e870b81f8478 100644 GIT binary patch literal 7792 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 Zm?>iq>i#f+@7DmZwme`_v4?c}zW~5t|K|Vz literal 0 HcmV?d00001 diff --git a/src/qt/res/icons/star_empty.png b/src/qt/res/icons/star_empty.png new file mode 100644 index 0000000000000000000000000000000000000000..87a5472b86c632b9ab702e2602757cf7e2083ba1 GIT binary patch literal 64514 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

OJ5upFUZc^ySNzkm|KV0|L~qD8JWo#^fTpfTkUH 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 #include #include +#include #include #include #include diff --git a/src/qt/walletframe.cpp b/src/qt/walletframe.cpp index c0b9d0426..ad84801fb 100644 --- a/src/qt/walletframe.cpp +++ b/src/qt/walletframe.cpp @@ -124,6 +124,13 @@ void WalletFrame::gotoHistoryPage() i.value()->gotoHistoryPage(); } +void WalletFrame::gotoKevaPage() +{ + QMap::const_iterator i; + for (i = mapWalletViews.constBegin(); i != mapWalletViews.constEnd(); ++i) + i.value()->gotoKevaPage(); +} + void WalletFrame::gotoReceiveCoinsPage() { QMap::const_iterator i; diff --git a/src/qt/walletframe.h b/src/qt/walletframe.h index 42ce69fea..fbc648b2f 100644 --- a/src/qt/walletframe.h +++ b/src/qt/walletframe.h @@ -66,6 +66,8 @@ public Q_SLOTS: void gotoOverviewPage(); /** Switch to history (transactions) page */ void gotoHistoryPage(); + /** Switch to Keva page */ + void gotoKevaPage(); /** Switch to receive coins page */ void gotoReceiveCoinsPage(); /** Switch to send coins page */ diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index dbafd1613..ebfcc7b69 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -11,12 +11,17 @@ #include #include #include +#include +#include +#include #include #include #include #include #include +#include +#include #include #include // for g_connman #include @@ -36,11 +41,16 @@ #include #include +const int NAMESPACE_LENGTH = 21; +const std::string DUMMY_NAMESPACE = "___DUMMY_NAMESPACE___"; WalletModel::WalletModel(const PlatformStyle *platformStyle, CWallet *_wallet, OptionsModel *_optionsModel, QObject *parent) : QObject(parent), wallet(_wallet), optionsModel(_optionsModel), addressTableModel(0), transactionTableModel(0), recentRequestsTableModel(0), + kevaTableModel(0), + kevaNamespaceModel(0), + kevaBookmarksModel(0), cachedBalance(0), cachedUnconfirmedBalance(0), cachedImmatureBalance(0), cachedEncryptionStatus(Unencrypted), cachedNumBlocks(0) @@ -51,6 +61,9 @@ WalletModel::WalletModel(const PlatformStyle *platformStyle, CWallet *_wallet, O addressTableModel = new AddressTableModel(wallet, this); transactionTableModel = new TransactionTableModel(platformStyle, wallet, this); recentRequestsTableModel = new RecentRequestsTableModel(wallet, this); + kevaTableModel = new KevaTableModel(wallet, this); + kevaNamespaceModel = new KevaNamespaceModel(wallet, this); + kevaBookmarksModel = new KevaBookmarksModel(wallet, this); // This timer will be fired repeatedly to update the balance pollTimer = new QTimer(this); @@ -393,6 +406,21 @@ RecentRequestsTableModel *WalletModel::getRecentRequestsTableModel() return recentRequestsTableModel; } +KevaTableModel *WalletModel::getKevaTableModel() +{ + return kevaTableModel; +} + +KevaNamespaceModel *WalletModel::getKevaNamespaceModel() +{ + return kevaNamespaceModel; +} + +KevaBookmarksModel *WalletModel::getKevaBookmarksModel() +{ + return kevaBookmarksModel; +} + WalletModel::EncryptionStatus WalletModel::getEncryptionStatus() const { if(!wallet->IsCrypted()) @@ -743,3 +771,261 @@ int WalletModel::getDefaultConfirmTarget() const { return nTxConfirmTarget; } + +void WalletModel::getKevaEntries(std::vector& vKevaEntries, std::string nameSpace) +{ + valtype nameSpaceVal = ValtypeFromString(nameSpace); + + { + // Get the unconfirmed namespaces and list them at the beginning. + LOCK (mempool.cs); + + std::vector> unconfirmedKeyValueList; + mempool.getUnconfirmedKeyValueList(unconfirmedKeyValueList, nameSpaceVal); + + for (auto e : unconfirmedKeyValueList) { + KevaEntry entry; + entry.key = ValtypeToString(std::get<1>(e)); + entry.value = ValtypeToString(std::get<2>(e)); + entry.block = -1; // Unconfirmed. + entry.date = QDateTime::currentDateTime(); + vKevaEntries.push_back(std::move(entry)); + } + } + + LOCK(cs_main); + + valtype key; + CKevaData data; + std::unique_ptr iter(pcoinsTip->IterateKeys(nameSpaceVal)); + while (iter->next(key, data)) { + KevaEntry entry; + entry.key = ValtypeToString(key); + entry.value = ValtypeToString(data.getValue()); + entry.block = data.getHeight(); + CBlockIndex* pblockindex = chainActive[entry.block]; + if (pblockindex) { + entry.date.setTime_t(pblockindex->nTime); + } + vKevaEntries.push_back(std::move(entry)); + } + +} + +void WalletModel::getNamespaceEntries(std::vector& vNamespaceEntries) +{ + LOCK2(cs_main, wallet->cs_wallet); + std::map mapObjects; + for (const auto& item : wallet->mapWallet) { + const CWalletTx& tx = item.second; + if (!tx.tx->IsKevacoin()) { + continue; + } + + CKevaScript kevaOp; + int nOut = -1; + for (unsigned i = 0; i < tx.tx->vout.size(); ++i) { + const CKevaScript cur(tx.tx->vout[i].scriptPubKey); + if (cur.isKevaOp()) { + if (nOut != -1) { + LogPrintf("ERROR: wallet contains tx with multiple name outputs"); + } else { + kevaOp = cur; + nOut = i; + } + } + } + + if (nOut == -1) { + continue; + } + + if (!kevaOp.isNamespaceRegistration() && !kevaOp.isAnyUpdate()) { + continue; + } + + const valtype nameSpace = kevaOp.getOpNamespace(); + const std::string nameSpaceStr = EncodeBase58Check(nameSpace); + const CBlockIndex* pindex; + const int depth = tx.GetDepthInMainChain(pindex); + if (depth <= 0) { + continue; + } + + const bool mine = IsMine(*wallet, kevaOp.getAddress()); + CKevaData data; + if (mine && pcoinsTip->GetNamespace(nameSpace, data)) { + std::string displayName = ValtypeToString(data.getValue()); + mapObjects[nameSpaceStr] = displayName; + } + } + + { + // Also get the unconfirmed namespaces and list them at the beginning. + LOCK (mempool.cs); + + std::vector> unconfirmedNamespaces; + mempool.getUnconfirmedNamespaceList(unconfirmedNamespaces); + + for (auto ns: unconfirmedNamespaces) { + NamespaceEntry entry; + entry.id = EncodeBase58Check(std::get<0>(ns)); + entry.name = ValtypeToString(std::get<1>(ns)); + entry.confirmed = false; + vNamespaceEntries.push_back(std::move(entry)); + } + } + + // The confirmed namespaces. + std::map::iterator it = mapObjects.begin(); + while (it != mapObjects.end()) { + NamespaceEntry entry; + entry.id = it->first; + entry.name = it->second; + vNamespaceEntries.push_back(std::move(entry)); + it++; + } +} + +int WalletModel::createNamespace(std::string displayNameStr, std::string& namespaceId) +{ + const valtype displayName = ValtypeFromString (displayNameStr); + if (displayName.size() > MAX_NAMESPACE_LENGTH) { + return NamespaceTooLong; + } + + CReserveKey keyName(wallet); + CPubKey pubKey; + const bool ok = keyName.GetReservedKey(pubKey, true); + assert(ok); + + CKeyID keyId = pubKey.GetID(); + + // The namespace name is: Hash160("first txin") + // For now we don't know the first txin, so use dummy name here. + // It will be replaced later in CreateTransaction. + valtype namespaceDummy = ToByteVector(std::string(DUMMY_NAMESPACE)); + assert(namespaceDummy.size() == NAMESPACE_LENGTH); + + CScript redeemScript = GetScriptForDestination(WitnessV0KeyHash(keyId)); + CScriptID scriptHash = CScriptID(redeemScript); + CScript addrName = GetScriptForDestination(scriptHash); + const CScript newScript = CKevaScript::buildKevaNamespace(addrName, namespaceDummy, displayName); + + CCoinControl coinControl; + CWalletTx wtx; + valtype kevaNamespace; + SendMoneyToScript(wallet, newScript, nullptr, kevaNamespace, + KEVA_LOCKED_AMOUNT, false, wtx, coinControl); + keyName.KeepKey(); + + namespaceId = EncodeBase58Check(kevaNamespace); + return 0; +} + + +int WalletModel::deleteKevaEntry(std::string namespaceStr, std::string keyStr) +{ + valtype nameSpace; + if (!DecodeKevaNamespace(namespaceStr, Params(), nameSpace)) { + return InvalidNamespace; + } + + const valtype key = ValtypeFromString(keyStr); + if (key.size() > MAX_KEY_LENGTH) { + return KeyTooLong; + } + + bool hasKey = false; + CKevaData data; + { + LOCK2(cs_main, mempool.cs); + std::vector> unconfirmedKeyValueList; + valtype val; + if (mempool.getUnconfirmedKeyValue(nameSpace, key, val)) { + if (val.size() > 0) { + hasKey = true; + } + } else if (pcoinsTip->GetName(nameSpace, key, data)) { + hasKey = true; + } + } + + if (!hasKey) { + return KeyNotFound; + } + + COutput output; + std::string kevaNamespce = namespaceStr; + if (!wallet->FindKevaCoin(output, kevaNamespce)) { + // TODO: This namespace can not be updated + return 0; + } + const COutPoint outp(output.tx->GetHash(), output.i); + const CTxIn txIn(outp); + + CReserveKey keyName(wallet); + CPubKey pubKeyReserve; + const bool ok = keyName.GetReservedKey(pubKeyReserve, true); + assert(ok); + + CScript redeemScript = GetScriptForDestination(WitnessV0KeyHash(pubKeyReserve.GetID())); + CScriptID scriptHash = CScriptID(redeemScript); + CScript addrName = GetScriptForDestination(scriptHash); + + const CScript kevaScript = CKevaScript::buildKevaDelete(addrName, nameSpace, key); + + CCoinControl coinControl; + CWalletTx wtx; + valtype empty; + SendMoneyToScript(wallet, kevaScript, &txIn, empty, + KEVA_LOCKED_AMOUNT, false, wtx, coinControl); + + keyName.KeepKey(); + return 0; +} + +int WalletModel::addKeyValue(std::string& namespaceStr, std::string& keyStr, std::string& valueStr) +{ + valtype nameSpace; + if (!DecodeKevaNamespace(namespaceStr, Params(), nameSpace)) { + return InvalidNamespace; + } + + const valtype key = ValtypeFromString(keyStr); + if (keyStr.size() > MAX_KEY_LENGTH) { + return KeyTooLong; + } + + const valtype value = ValtypeFromString(valueStr); + if (value.size() > MAX_VALUE_LENGTH) { + return ValueTooLong; + } + + COutput output; + if (!wallet->FindKevaCoin(output, namespaceStr)) { + return CannotUpdate; + } + const COutPoint outp(output.tx->GetHash(), output.i); + const CTxIn txIn(outp); + + CReserveKey keyName(wallet); + CPubKey pubKeyReserve; + const bool ok = keyName.GetReservedKey(pubKeyReserve, true); + assert(ok); + CKeyID keyId = pubKeyReserve.GetID(); + CScript redeemScript = GetScriptForDestination(WitnessV0KeyHash(keyId)); + CScriptID scriptHash = CScriptID(redeemScript); + CScript addrName = GetScriptForDestination(scriptHash); + + const CScript kevaScript = CKevaScript::buildKevaPut(addrName, nameSpace, key, value); + + CCoinControl coinControl; + CWalletTx wtx; + valtype empty; + SendMoneyToScript(wallet, kevaScript, &txIn, empty, + KEVA_LOCKED_AMOUNT, false, wtx, coinControl); + + keyName.KeepKey(); + return 0; +} diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h index 9e13de79b..4140cb56d 100644 --- a/src/qt/walletmodel.h +++ b/src/qt/walletmodel.h @@ -21,6 +21,12 @@ class AddressTableModel; class OptionsModel; class PlatformStyle; class RecentRequestsTableModel; +class KevaTableModel; +class KevaNamespaceModel; +class KevaBookmarksModel; +class KevaEntry; +class NamespaceEntry; +class BookmarkEntry; class TransactionTableModel; class WalletModelTransaction; @@ -116,7 +122,15 @@ public: TransactionCreationFailed, // Error returned when wallet is still locked TransactionCommitFailed, AbsurdFee, - PaymentRequestExpired + PaymentRequestExpired, + + // Keva status + InvalidNamespace, + KeyTooLong, + NamespaceTooLong, + KeyNotFound, + ValueTooLong, + CannotUpdate, }; enum EncryptionStatus @@ -130,6 +144,9 @@ public: AddressTableModel *getAddressTableModel(); TransactionTableModel *getTransactionTableModel(); RecentRequestsTableModel *getRecentRequestsTableModel(); + KevaTableModel *getKevaTableModel(); + KevaNamespaceModel *getKevaNamespaceModel(); + KevaBookmarksModel *getKevaBookmarksModel(); CAmount getBalance(const CCoinControl *coinControl = nullptr) const; CAmount getUnconfirmedBalance() const; @@ -220,6 +237,13 @@ public: int getDefaultConfirmTarget() const; + // Keva + void getKevaEntries(std::vector& vKevaEntries, std::string nameSpace); + void getNamespaceEntries(std::vector& vNamespaceEntries); + int createNamespace(std::string displayName, std::string& namespaceId); + int deleteKevaEntry(std::string nameSpace, std::string key); + int addKeyValue(std::string& namespaceStr, std::string& keyStr, std::string& valueStr); + private: CWallet *wallet; bool fHaveWatchOnly; @@ -232,6 +256,9 @@ private: AddressTableModel *addressTableModel; TransactionTableModel *transactionTableModel; RecentRequestsTableModel *recentRequestsTableModel; + KevaTableModel *kevaTableModel; + KevaNamespaceModel *kevaNamespaceModel; + KevaBookmarksModel *kevaBookmarksModel; // Cache some values to be able to detect changes CAmount cachedBalance; diff --git a/src/qt/walletview.cpp b/src/qt/walletview.cpp index 7eced9289..b0bb15935 100644 --- a/src/qt/walletview.cpp +++ b/src/qt/walletview.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -53,6 +54,7 @@ WalletView::WalletView(const PlatformStyle *_platformStyle, QWidget *parent): vbox->addLayout(hbox_buttons); transactionsPage->setLayout(vbox); + kevaPage = new KevaDialog(platformStyle); receiveCoinsPage = new ReceiveCoinsDialog(platformStyle); sendCoinsPage = new SendCoinsDialog(platformStyle); @@ -61,6 +63,7 @@ WalletView::WalletView(const PlatformStyle *_platformStyle, QWidget *parent): addWidget(overviewPage); addWidget(transactionsPage); + addWidget(kevaPage); addWidget(receiveCoinsPage); addWidget(sendCoinsPage); @@ -100,7 +103,7 @@ void WalletView::setBitcoinGUI(BitcoinGUI *gui) // Pass through transaction notifications connect(this, SIGNAL(incomingTransaction(QString,int,CAmount,QString,QString,QString)), gui, SLOT(incomingTransaction(QString,int,CAmount,QString,QString,QString))); - // Connect HD enabled state signal + // Connect HD enabled state signal connect(this, SIGNAL(hdEnabledStatusChanged(int)), gui, SLOT(setHDStatus(int))); } } @@ -119,6 +122,7 @@ void WalletView::setWalletModel(WalletModel *_walletModel) // Put transaction list in tabs transactionView->setModel(_walletModel); + kevaPage->setModel(_walletModel); overviewPage->setWalletModel(_walletModel); receiveCoinsPage->setModel(_walletModel); sendCoinsPage->setModel(_walletModel); @@ -179,6 +183,11 @@ void WalletView::gotoHistoryPage() setCurrentWidget(transactionsPage); } +void WalletView::gotoKevaPage() +{ + setCurrentWidget(kevaPage); +} + void WalletView::gotoReceiveCoinsPage() { setCurrentWidget(receiveCoinsPage); diff --git a/src/qt/walletview.h b/src/qt/walletview.h index 30d68e4ef..d810a110a 100644 --- a/src/qt/walletview.h +++ b/src/qt/walletview.h @@ -15,6 +15,7 @@ class OverviewPage; class PlatformStyle; class ReceiveCoinsDialog; class SendCoinsDialog; +class KevaDialog; class SendCoinsRecipient; class TransactionView; class WalletModel; @@ -62,6 +63,7 @@ private: QWidget *transactionsPage; ReceiveCoinsDialog *receiveCoinsPage; SendCoinsDialog *sendCoinsPage; + KevaDialog *kevaPage; AddressBookPage *usedSendingAddressesPage; AddressBookPage *usedReceivingAddressesPage; @@ -75,6 +77,8 @@ public Q_SLOTS: void gotoOverviewPage(); /** Switch to history (transactions) page */ void gotoHistoryPage(); + /** Switch to Keva page */ + void gotoKevaPage(); /** Switch to receive coins page */ void gotoReceiveCoinsPage(); /** Switch to send coins page */