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 473725b76..d034f477c 100644 Binary files a/src/qt/res/icons/about.png and b/src/qt/res/icons/about.png differ diff --git a/src/qt/res/icons/keva.png b/src/qt/res/icons/keva.png new file mode 100644 index 000000000..aa342e03c Binary files /dev/null and b/src/qt/res/icons/keva.png differ diff --git a/src/qt/res/icons/star.png b/src/qt/res/icons/star.png new file mode 100644 index 000000000..79868e2cb Binary files /dev/null and b/src/qt/res/icons/star.png differ diff --git a/src/qt/res/icons/star_empty.png b/src/qt/res/icons/star_empty.png new file mode 100644 index 000000000..87a5472b8 Binary files /dev/null and b/src/qt/res/icons/star_empty.png differ diff --git a/src/qt/test/wallettests.cpp b/src/qt/test/wallettests.cpp index 7a4ffea03..5d2abe771 100644 --- a/src/qt/test/wallettests.cpp +++ b/src/qt/test/wallettests.cpp @@ -9,6 +9,7 @@ #include #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 */