From 8dcbd4913b5b7052ed14d89aad8b72a81cfa2099 Mon Sep 17 00:00:00 2001 From: JeremyRand Date: Sat, 27 Jun 2020 00:18:16 +0000 Subject: [PATCH] (WIP) Namecoin: Add Qt GUI for name_list Based on https://github.com/namecoin/namecoin-core/pull/187 by Brandon Roberts. TODO: Untested. --- src/Makefile.qt.include | 7 + src/qt/bitcoin.qrc | 1 + src/qt/bitcoingui.cpp | 23 ++ src/qt/bitcoingui.h | 4 + src/qt/forms/managenamespage.ui | 253 ++++++++++++++ src/qt/managenamespage.cpp | 151 +++++++++ src/qt/managenamespage.h | 56 +++ src/qt/nametablemodel.cpp | 319 ++++++++++++++++++ src/qt/nametablemodel.h | 98 ++++++ .../res/icons/bitcoin_transparent_letter.png | Bin 0 -> 53978 bytes src/qt/res/src/bitcoin_transparent_letter.svg | 95 ++++++ src/qt/walletframe.cpp | 7 + src/qt/walletframe.h | 2 + src/qt/walletmodel.cpp | 19 ++ src/qt/walletmodel.h | 4 + src/qt/walletview.cpp | 9 + src/qt/walletview.h | 4 + 17 files changed, 1052 insertions(+) create mode 100644 src/qt/forms/managenamespage.ui create mode 100644 src/qt/managenamespage.cpp create mode 100644 src/qt/managenamespage.h create mode 100644 src/qt/nametablemodel.cpp create mode 100644 src/qt/nametablemodel.h create mode 100644 src/qt/res/icons/bitcoin_transparent_letter.png create mode 100644 src/qt/res/src/bitcoin_transparent_letter.svg diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include index 780b5cdb78..12077e4130 100644 --- a/src/Makefile.qt.include +++ b/src/Makefile.qt.include @@ -31,6 +31,7 @@ QT_FORMS_UI = \ qt/forms/debugwindow.ui \ qt/forms/sendcoinsdialog.ui \ qt/forms/sendcoinsentry.ui \ + qt/forms/managenamespage.ui \ qt/forms/signverifymessagedialog.ui \ qt/forms/transactiondescdialog.ui @@ -55,6 +56,8 @@ QT_MOC_CPP = \ qt/moc_macdockiconhandler.cpp \ qt/moc_macnotificationhandler.cpp \ qt/moc_modaloverlay.cpp \ + qt/moc_managenamespage.cpp \ + qt/moc_nametablemodel.cpp \ qt/moc_notificator.cpp \ qt/moc_openuridialog.cpp \ qt/moc_optionsdialog.cpp \ @@ -125,8 +128,10 @@ BITCOIN_QT_H = \ qt/macnotificationhandler.h \ qt/macos_appnap.h \ qt/modaloverlay.h \ + qt/managenamespage.h \ qt/networkstyle.h \ qt/notificator.h \ + qt/nametablemodel.h \ qt/openuridialog.h \ qt/optionsdialog.h \ qt/optionsmodel.h \ @@ -245,6 +250,8 @@ BITCOIN_QT_WALLET_CPP = \ qt/coincontroltreewidget.cpp \ qt/createwalletdialog.cpp \ qt/editaddressdialog.cpp \ + qt/managenamespage.cpp \ + qt/nametablemodel.cpp \ qt/openuridialog.cpp \ qt/overviewpage.cpp \ qt/paymentserver.cpp \ diff --git a/src/qt/bitcoin.qrc b/src/qt/bitcoin.qrc index 7115459808..e61a544d03 100644 --- a/src/qt/bitcoin.qrc +++ b/src/qt/bitcoin.qrc @@ -1,6 +1,7 @@ res/icons/bitcoin.png + res/icons/bitcoin_transparent_letter.png res/icons/address-book.png res/icons/send.png res/icons/connect0.png diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index 339c4eaa18..e7ffadb6a1 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -277,6 +278,17 @@ void BitcoinGUI::createActions() historyAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_4)); tabGroup->addAction(historyAction); + manageNamesAction = new QAction(platformStyle->SingleColorIcon(":/icons/bitcoin_transparent_letter"), tr("&Manage Names"), this); + manageNamesAction->setStatusTip(tr("Manage names registered via Namecoin")); + manageNamesAction->setToolTip(manageNamesAction->statusTip()); + manageNamesAction->setCheckable(true); + manageNamesAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_5)); + tabGroup->addAction(manageNamesAction); + + manageNamesMenuAction = new QAction(manageNamesAction->text(), this); + manageNamesMenuAction->setStatusTip(manageNamesAction->statusTip()); + manageNamesMenuAction->setToolTip(manageNamesMenuAction->statusTip()); + #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. @@ -292,6 +304,8 @@ void BitcoinGUI::createActions() connect(receiveCoinsMenuAction, &QAction::triggered, this, &BitcoinGUI::gotoReceiveCoinsPage); connect(historyAction, &QAction::triggered, [this]{ showNormalIfMinimized(); }); connect(historyAction, &QAction::triggered, this, &BitcoinGUI::gotoHistoryPage); + connect(manageNamesAction, &QAction::triggered, [this]{ showNormalIfMinimized(); }); + connect(manageNamesAction, &QAction::triggered, this, &BitcoinGUI::gotoManageNamesPage); #endif // ENABLE_WALLET quitAction = new QAction(tr("E&xit"), this); @@ -550,6 +564,7 @@ void BitcoinGUI::createToolBars() toolbar->addAction(sendCoinsAction); toolbar->addAction(receiveCoinsAction); toolbar->addAction(historyAction); + toolbar->addAction(manageNamesAction); overviewAction->setChecked(true); #ifdef ENABLE_WALLET @@ -736,6 +751,7 @@ void BitcoinGUI::setWalletActionsEnabled(bool enabled) receiveCoinsAction->setEnabled(enabled); receiveCoinsMenuAction->setEnabled(enabled); historyAction->setEnabled(enabled); + manageNamesAction->setEnabled(enabled); encryptWalletAction->setEnabled(enabled); backupWalletAction->setEnabled(enabled); changePassphraseAction->setEnabled(enabled); @@ -786,6 +802,7 @@ void BitcoinGUI::createTrayIconMenu() if (enableWallet) { trayIconMenu->addAction(sendCoinsMenuAction); trayIconMenu->addAction(receiveCoinsMenuAction); + trayIconMenu->addAction(manageNamesMenuAction); trayIconMenu->addSeparator(); trayIconMenu->addAction(signMessageAction); trayIconMenu->addAction(verifyMessageAction); @@ -881,6 +898,12 @@ void BitcoinGUI::gotoSendCoinsPage(QString addr) if (walletFrame) walletFrame->gotoSendCoinsPage(addr); } +void BitcoinGUI::gotoManageNamesPage() +{ + manageNamesAction->setChecked(true); + if (walletFrame) walletFrame->gotoManageNamesPage(); +} + void BitcoinGUI::gotoSignMessageTab(QString addr) { if (walletFrame) walletFrame->gotoSignMessageTab(addr); diff --git a/src/qt/bitcoingui.h b/src/qt/bitcoingui.h index 912297a74e..6749be3e90 100644 --- a/src/qt/bitcoingui.h +++ b/src/qt/bitcoingui.h @@ -138,6 +138,8 @@ class BitcoinGUI : public QMainWindow QAction* sendCoinsMenuAction = nullptr; QAction* usedSendingAddressesAction = nullptr; QAction* usedReceivingAddressesAction = nullptr; + QAction* manageNamesAction = nullptr; + QAction* manageNamesMenuAction = nullptr; QAction* signMessageAction = nullptr; QAction* verifyMessageAction = nullptr; QAction* m_load_psbt_action = nullptr; @@ -276,6 +278,8 @@ public Q_SLOTS: void gotoReceiveCoinsPage(); /** Switch to send coins page */ void gotoSendCoinsPage(QString addr = ""); + /** Switch to manage names page */ + void gotoManageNamesPage(); /** Show Sign/Verify Message dialog and switch to sign message tab */ void gotoSignMessageTab(QString addr = ""); diff --git a/src/qt/forms/managenamespage.ui b/src/qt/forms/managenamespage.ui new file mode 100644 index 0000000000..4b15880084 --- /dev/null +++ b/src/qt/forms/managenamespage.ui @@ -0,0 +1,253 @@ + + + ManageNamesPage + + + + 0 + 0 + 776 + 364 + + + + + + + + 0 + 0 + + + + QFrame::StyledPanel + + + QFrame::Sunken + + + + + + + + 0 + 0 + + + + Your registered names (pending and unconfirmed names have blank expiration): + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + Qt::CustomContextMenu + + + Double-click name to configure + + + false + + + true + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + true + + + false + + + + + + + + + + + + + + + + + diff --git a/src/qt/managenamespage.cpp b/src/qt/managenamespage.cpp new file mode 100644 index 0000000000..d8335f3018 --- /dev/null +++ b/src/qt/managenamespage.cpp @@ -0,0 +1,151 @@ +// TODO: figure out which of these includes are actually still necessary for name_list +#include +#include + +#include +#include // cs_main +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +// TODO: figure out which of these members are actually still necessary for name_list +ManageNamesPage::ManageNamesPage(const PlatformStyle *platformStyle, QWidget *parent) : + QWidget(parent), + platformStyle(platformStyle), + ui(new Ui::ManageNamesPage), + model(nullptr), + walletModel(nullptr), + proxyModel(nullptr) +{ + ui->setupUi(this); + + // Context menu actions + QAction *copyNameAction = new QAction(tr("Copy &Name"), this); + QAction *copyValueAction = new QAction(tr("Copy &Value"), this); + + // Build context menu + contextMenu = new QMenu(); + contextMenu->addAction(copyNameAction); + contextMenu->addAction(copyValueAction); + + // Connect signals for context menu actions + connect(copyNameAction, &QAction::triggered, this, &ManageNamesPage::onCopyNameAction); + connect(copyValueAction, &QAction::triggered, this, &ManageNamesPage::onCopyValueAction); + + connect(ui->tableView, &QTableView::customContextMenuRequested, this, &ManageNamesPage::contextualMenu); + ui->tableView->setEditTriggers(QAbstractItemView::NoEditTriggers); + + ui->tableView->installEventFilter(this); +} + +ManageNamesPage::~ManageNamesPage() +{ + delete ui; +} + +void ManageNamesPage::setModel(WalletModel *walletModel) +{ + this->walletModel = walletModel; + model = walletModel->getNameTableModel(); + + proxyModel = new QSortFilterProxyModel(this); + proxyModel->setSourceModel(model); + proxyModel->setDynamicSortFilter(true); + proxyModel->setSortCaseSensitivity(Qt::CaseInsensitive); + proxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive); + + ui->tableView->setModel(proxyModel); + ui->tableView->sortByColumn(0, Qt::AscendingOrder); + + ui->tableView->horizontalHeader()->setHighlightSections(false); + + // Set column widths + ui->tableView->horizontalHeader()->resizeSection( + NameTableModel::Name, 320); + ui->tableView->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); + + connect(ui->tableView->selectionModel(), &QItemSelectionModel::selectionChanged, + this, &ManageNamesPage::selectionChanged); + + selectionChanged(); +} + +bool ManageNamesPage::eventFilter(QObject *object, QEvent *event) +{ + if (event->type() == QEvent::FocusIn) + { + if (object == ui->tableView) + { + } + } + return QWidget::eventFilter(object, event); +} + +void ManageNamesPage::selectionChanged() +{ + // Set button states based on selected tab and selection + QTableView *table = ui->tableView; + if (!table->selectionModel()) + return; + + const bool state = table->selectionModel()->hasSelection(); + //ui->configureNameButton->setEnabled(state); + //ui->renewNameButton->setEnabled(state); +} + +void ManageNamesPage::contextualMenu(const QPoint &point) +{ + QModelIndex index = ui->tableView->indexAt(point); + if (index.isValid()) + contextMenu->exec(QCursor::pos()); +} + +void ManageNamesPage::onCopyNameAction() +{ + GUIUtil::copyEntryData(ui->tableView, NameTableModel::Name); +} + +void ManageNamesPage::onCopyValueAction() +{ + GUIUtil::copyEntryData(ui->tableView, NameTableModel::Value); +} + +void ManageNamesPage::exportClicked() +{ + // CSV is currently the only supported format + QString suffixOut = ""; + QString filename = GUIUtil::getSaveFileName( + this, + tr("Export Registered Names Data"), + QString(), + tr("Comma separated file (*.csv)"), + &suffixOut); + + if (filename.isNull()) + return; + + CSVModelWriter writer(filename); + + // name, column, role + writer.setModel(proxyModel); + writer.addColumn("Name", NameTableModel::Name, Qt::EditRole); + writer.addColumn("Value", NameTableModel::Value, Qt::EditRole); + writer.addColumn("Expires In", NameTableModel::ExpiresIn, Qt::EditRole); + writer.addColumn("Name Status", NameTableModel::NameStatus, Qt::EditRole); + + if (!writer.write()) + { + QMessageBox::critical(this, tr("Error exporting"), tr("Could not write to file %1.").arg(filename), + QMessageBox::Abort, QMessageBox::Abort); + } +} diff --git a/src/qt/managenamespage.h b/src/qt/managenamespage.h new file mode 100644 index 0000000000..3079dbdb22 --- /dev/null +++ b/src/qt/managenamespage.h @@ -0,0 +1,56 @@ +#ifndef MANAGENAMESPAGE_H +#define MANAGENAMESPAGE_H + +#include + +#include + +class WalletModel; +class NameTableModel; + +namespace Ui { + class ManageNamesPage; +} + +QT_BEGIN_NAMESPACE +class QTableView; +class QItemSelection; +class QSortFilterProxyModel; +class QMenu; +class QModelIndex; +QT_END_NAMESPACE + +/** Page for managing names */ +class ManageNamesPage : public QWidget +{ + Q_OBJECT + +public: + explicit ManageNamesPage(const PlatformStyle *platformStyle, QWidget *parent = nullptr); + ~ManageNamesPage(); + + void setModel(WalletModel *walletModel); + +private: + const PlatformStyle *platformStyle; + Ui::ManageNamesPage *ui; + NameTableModel *model; + WalletModel *walletModel; + QSortFilterProxyModel *proxyModel; + QMenu *contextMenu; + +public Q_SLOTS: + void exportClicked(); + +private Q_SLOTS: + bool eventFilter(QObject *object, QEvent *event); + void selectionChanged(); + + /** Spawn contextual menu (right mouse menu) for name table entry */ + void contextualMenu(const QPoint &point); + + void onCopyNameAction(); + void onCopyValueAction(); +}; + +#endif // MANAGENAMESPAGE_H diff --git a/src/qt/nametablemodel.cpp b/src/qt/nametablemodel.cpp new file mode 100644 index 0000000000..e0e7d86025 --- /dev/null +++ b/src/qt/nametablemodel.cpp @@ -0,0 +1,319 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include // cs_main +#include + +#include +#include +#include + +// ExpiresIn column is right-aligned as it contains numbers +namespace { + int column_alignments[] = { + Qt::AlignLeft|Qt::AlignVCenter, // Name + Qt::AlignLeft|Qt::AlignVCenter, // Value + Qt::AlignRight|Qt::AlignVCenter, // Expires in + Qt::AlignRight|Qt::AlignVCenter, // Name Status + }; +} + +struct NameTableEntryLessThan +{ + bool operator()(const NameTableEntry &a, const NameTableEntry &b) const + { + return a.name < b.name; + } + bool operator()(const NameTableEntry &a, const QString &b) const + { + return a.name < b; + } + bool operator()(const QString &a, const NameTableEntry &b) const + { + return a < b.name; + } +}; + +// Returns true if new height is better +bool NameTableEntry::CompareHeight(int nOldHeight, int nNewHeight) +{ + if(nOldHeight == NAME_NON_EXISTING) + return true; + + // We use optimistic way, assuming that unconfirmed transaction will eventually become confirmed, + // so we update the name in the table immediately. Ideally we need a separate way of displaying + // unconfirmed names (e.g. grayed out) + if(nNewHeight == NAME_UNCONFIRMED) + return true; + + // Here we rely on the fact that dummy height values are always negative + return nNewHeight > nOldHeight; +} + +// Private implementation +class NameTablePriv +{ +public: + explicit NameTablePriv(NameTableModel *_parent) : + parent(_parent) + { + } + + NameTableModel *parent; + + /* Local cache of name table. + */ + QList cachedNameTable; + + /* Query entire name table anew from core. + */ + void refreshNameTable(interfaces::Wallet& wallet) + { + LOCK(parent->cs_model); + + qDebug() << "NameTablePriv::refreshNameTable"; + std::map< std::string, NameTableEntry > vNamesO; + + // confirmed names (name_list) + // TODO: Add unconfirmed names once support for this is added to + // name_list. + // TODO: Filter out expired=true and ismine=false once support for this + // is added to name_list. + // TODO: Set name and value encoding to hex, so that nonstandard + // encodings don't cause errors. + // TODO: Make sure we use the specified wallet. + util::Ref nameListContext; + JSONRPCRequest nameListRequest(nameListContext); + nameListRequest.URI = ("/wallet/" + parent->walletModel->getWalletName()).toStdString(); + nameListRequest.strMethod = "name_list"; + nameListRequest.params = NullUniValue; + nameListRequest.fHelp = false; + UniValue confirmedNames; + + try { + confirmedNames = tableRPC.execute(nameListRequest); + } catch (const UniValue& e) { + // although we shouldn't typically encounter error here, we + // should continue and try to add confirmed names and + // pending names. show error to user in case something + // actually went wrong so they can potentially recover + UniValue message = find_value( e, "message"); + LogPrintf ("name_list lookup error: %s\n", message.get_str()); + } + + // will be an object if name_list command isn't available/other error + if(confirmedNames.isArray()) + { + for (const auto& v : confirmedNames.getValues()) + { + std::string name = find_value ( v, "name").get_str(); + std::string data = find_value ( v, "value").get_str(); + int height = find_value ( v, "height").get_int(); + int expiresIn = find_value ( v, "expires_in").get_int(); + vNamesO[name] = NameTableEntry(name, data, height, expiresIn, "confirmed"); + } + } + + // TODO: use beginInsertRows/nop/beginRemoveRows instead + parent->beginResetModel(); + + // TODO: edit existing cached table instead of clearing it + cachedNameTable.clear(); + + // Add existing names + for (const auto& item : vNamesO) + cachedNameTable.append(item.second); + + // TODO: use endInsertRows/dataChanged/endRemoveRows instead + parent->endResetModel(); + } + + int size() + { + return cachedNameTable.size(); + } + + NameTableEntry *index(int idx) + { + if(idx >= 0 && idx < cachedNameTable.size()) + { + return &cachedNameTable[idx]; + } + else + { + return nullptr; + } + } +}; + +// TODO: figure out which of these members are actually still necessary for name_list +NameTableModel::NameTableModel(const PlatformStyle *platformStyle, WalletModel *parent): + QAbstractTableModel(parent), + walletModel(parent), + priv(new NameTablePriv(this)), + platformStyle(platformStyle) +{ + columns << tr("Name") << tr("Value") << tr("Expires In") << tr("Status"); + priv->refreshNameTable(walletModel->wallet()); + + connect(&walletModel->clientModel(), &ClientModel::numBlocksChanged, this, &NameTableModel::updateExpiration); + + connect(walletModel->getTransactionTableModel(), &TransactionTableModel::rowsInserted, this, &NameTableModel::processNewTransaction); + + subscribeToCoreSignals(); +} + +NameTableModel::~NameTableModel() +{ + unsubscribeFromCoreSignals(); +} + +void NameTableModel::updateExpiration(int count, const QDateTime& blockDate, double nVerificationProgress, bool header, SynchronizationState sync_state) +{ + // ClientModel already throttles this for us. + + priv->refreshNameTable(walletModel->wallet()); +} + +void NameTableModel::processNewTransaction(const QModelIndex& parent, int start, int /*end*/) +{ + // TransactionTableModel doesn't throttle this for us, so we have here a + // copy of the throttling code from WalletView::processNewTransaction. + + // Prevent balloon-spam when initial block download is in progress + if (!walletModel || walletModel->clientModel().node().isInitialBlockDownload()) + return; + + TransactionTableModel *ttm = walletModel->getTransactionTableModel(); + if (!ttm || ttm->processingQueuedTransactions()) + return; + + priv->refreshNameTable(walletModel->wallet()); +} + +int NameTableModel::rowCount(const QModelIndex &parent /*= QModelIndex()*/) const +{ + Q_UNUSED(parent); + return priv->size(); +} + +int NameTableModel::columnCount(const QModelIndex &parent /*= QModelIndex()*/) const +{ + Q_UNUSED(parent); + return columns.length(); +} + +QVariant NameTableModel::data(const QModelIndex &index, int role) const +{ + if(!index.isValid()) + return QVariant(); + + NameTableEntry *rec = static_cast(index.internalPointer()); + + // TODO: implement Qt::ForegroudRole for font color styling for states? + // TODO: implement Qt::ToolTipRole show name status on tooltip + if(role == Qt::DisplayRole || role == Qt::EditRole) + { + switch(index.column()) + { + case Name: + return rec->name; + case Value: + return rec->value; + case ExpiresIn: + return rec->expiresIn; + case NameStatus: + return rec->nameStatus; + } + } + + if(role == Qt::TextAlignmentRole) + return column_alignments[index.column()]; + + return QVariant(); +} + +QVariant NameTableModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if(orientation != Qt::Horizontal) + return QVariant(); + + if(role == Qt::DisplayRole) + return columns[section]; + + if(role == Qt::TextAlignmentRole) + return column_alignments[section]; + + if(role == Qt::ToolTipRole) + { + switch(section) + { + case Name: + return tr("Name registered using Namecoin."); + + case Value: + return tr("Data associated with the name."); + + case ExpiresIn: + return tr("Number of blocks, after which the name will expire. Update name to renew it.\nEmpty cell means pending(awaiting automatic name_firstupdate or awaiting network confirmation)."); + } + } + return QVariant(); +} + +Qt::ItemFlags NameTableModel::flags(const QModelIndex &index) const +{ + if(!index.isValid()) + return 0; + + return Qt::ItemIsSelectable | Qt::ItemIsEnabled; +} + +QModelIndex NameTableModel::index(int row, int column, const QModelIndex &parent /* = QModelIndex()*/) const +{ + Q_UNUSED(parent); + NameTableEntry *data = priv->index(row); + if(data) + { + return createIndex(row, column, priv->index(row)); + } + return QModelIndex(); +} + +void +NameTableModel::emitDataChanged(int idx) +{ + //emit + dataChanged(index(idx, 0), index(idx, columns.length()-1)); +} + +void +NameTableModel::subscribeToCoreSignals() +{ + // Connect signals to wallet + //m_handler_transaction_changed = walletModel->wallet().handleTransactionChanged(std::bind(NotifyTransactionChanged, this, std::placeholders::_1, std::placeholders::_2)); + //m_handler_show_progress = walletModel->wallet().handleShowProgress(std::bind(ShowProgress, this, std::placeholders::_1, std::placeholders::_2)); +} + +void +NameTableModel::unsubscribeFromCoreSignals() +{ + // Disconnect signals from wallet + //m_handler_transaction_changed->disconnect(); + //m_handler_show_progress->disconnect(); +} diff --git a/src/qt/nametablemodel.h b/src/qt/nametablemodel.h new file mode 100644 index 0000000000..bc9007d205 --- /dev/null +++ b/src/qt/nametablemodel.h @@ -0,0 +1,98 @@ +#ifndef NAMETABLEMODEL_H +#define NAMETABLEMODEL_H + +#include + +#include +#include +#include + +#include +#include + +namespace interfaces { +class Handler; +} + +class PlatformStyle; +class NameTablePriv; +class CWallet; +class WalletModel; + +enum class SynchronizationState; + +/** + Qt model for "Manage Names" page. + */ +class NameTableModel : public QAbstractTableModel +{ + Q_OBJECT + +public: + explicit NameTableModel(const PlatformStyle *platformStyle, WalletModel *parent=nullptr); + virtual ~NameTableModel(); + + enum ColumnIndex { + Name = 0, + Value = 1, + ExpiresIn = 2, + NameStatus = 3 + }; + + /** @name Methods overridden from QAbstractTableModel + @{*/ + int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; + QVariant data(const QModelIndex &index, int role) const; + QVariant headerData(int section, Qt::Orientation orientation, int role) const; + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const; + Qt::ItemFlags flags(const QModelIndex &index) const; + /*@}*/ + +private: + WalletModel *walletModel; + std::unique_ptr m_handler_transaction_changed; + //std::unique_ptr m_handler_show_progress; + QStringList columns; + std::unique_ptr priv; + const PlatformStyle *platformStyle; + int cachedNumBlocks; + RecursiveMutex cs_model; + + /** Notify listeners that data changed. */ + void emitDataChanged(int index); + + void subscribeToCoreSignals(); + void unsubscribeFromCoreSignals(); + +public Q_SLOTS: + void updateExpiration(int count, const QDateTime& blockDate, double nVerificationProgress, bool header, SynchronizationState sync_state); + void processNewTransaction(const QModelIndex& parent, int start, int /*end*/); + + friend class NameTablePriv; +}; + +struct NameTableEntry +{ + QString name; + QString value; + int nHeight; + int expiresIn; + QString nameStatus; + + static const int NAME_NEW = -1; // Dummy nHeight value for not-yet-created names + static const int NAME_NON_EXISTING = -2; // Dummy nHeight value for unitinialized entries + static const int NAME_UNCONFIRMED = -3; // Dummy nHeight value for unconfirmed name transactions + + // NOTE: making this const throws warning indicating it will not be const + bool HeightValid() { return nHeight >= 0; } + static bool CompareHeight(int nOldHeight, int nNewHeight); // Returns true if new height is better + + NameTableEntry() : nHeight(NAME_NON_EXISTING) {} + NameTableEntry(const QString &name, const QString &value, int nHeight, int expiresIn, const QString &nameStatus): + name(name), value(value), nHeight(nHeight), expiresIn(expiresIn), nameStatus(nameStatus) {} + NameTableEntry(const std::string &name, const std::string &value, int nHeight, int expiresIn, const std::string &nameStatus): + name(QString::fromStdString(name)), value(QString::fromStdString(value)), nHeight(nHeight), expiresIn(expiresIn), nameStatus(QString::fromStdString(nameStatus)) {} +}; + +#endif // NAMETABLEMODEL_H diff --git a/src/qt/res/icons/bitcoin_transparent_letter.png b/src/qt/res/icons/bitcoin_transparent_letter.png new file mode 100644 index 0000000000000000000000000000000000000000..4e468bcde1d30e9da57e8bf6c91f84821a3c7be1 GIT binary patch literal 53978 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 + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/qt/walletframe.cpp b/src/qt/walletframe.cpp index f16761d6b2..3c5ee0dbaa 100644 --- a/src/qt/walletframe.cpp +++ b/src/qt/walletframe.cpp @@ -172,6 +172,13 @@ void WalletFrame::gotoSendCoinsPage(QString addr) i.value()->gotoSendCoinsPage(addr); } +void WalletFrame::gotoManageNamesPage() +{ + QMap::const_iterator i; + for (i = mapWalletViews.constBegin(); i != mapWalletViews.constEnd(); ++i) + i.value()->gotoManageNamesPage(); +} + void WalletFrame::gotoSignMessageTab(QString addr) { WalletView *walletView = currentWalletView(); diff --git a/src/qt/walletframe.h b/src/qt/walletframe.h index 2b5f263468..b7a81c8b4c 100644 --- a/src/qt/walletframe.h +++ b/src/qt/walletframe.h @@ -72,6 +72,8 @@ public Q_SLOTS: void gotoReceiveCoinsPage(); /** Switch to send coins page */ void gotoSendCoinsPage(QString addr = ""); + /** Switch to manage names page */ + void gotoManageNamesPage(); /** Show Sign/Verify Message dialog and switch to sign message tab */ void gotoSignMessageTab(QString addr = ""); diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index 6a3f903206..b84cd9dc0d 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -28,6 +28,14 @@ #include #include // for CRecipient +// namecoin API-related includes +// TODO: figure out which of these includes are actually still necessary for name_list +#include +#include +#include +#include +#include + #include #include @@ -35,6 +43,10 @@ #include #include +// TODO: figure out which of these includes are actually still necessary for name_list +#include +#include +#include WalletModel::WalletModel(std::unique_ptr wallet, ClientModel& client_model, const PlatformStyle *platformStyle, QObject *parent) : QObject(parent), @@ -44,6 +56,7 @@ WalletModel::WalletModel(std::unique_ptr wallet, ClientModel optionsModel(client_model.getOptionsModel()), addressTableModel(nullptr), transactionTableModel(nullptr), + nameTableModel(nullptr), recentRequestsTableModel(nullptr), cachedEncryptionStatus(Unencrypted), timer(new QTimer(this)) @@ -51,6 +64,7 @@ WalletModel::WalletModel(std::unique_ptr wallet, ClientModel fHaveWatchOnly = m_wallet->haveWatchOnly(); addressTableModel = new AddressTableModel(this); transactionTableModel = new TransactionTableModel(platformStyle, this); + nameTableModel = new NameTableModel(platformStyle, this); recentRequestsTableModel = new RecentRequestsTableModel(this); subscribeToCoreSignals(); @@ -287,6 +301,11 @@ AddressTableModel *WalletModel::getAddressTableModel() return addressTableModel; } +NameTableModel *WalletModel::getNameTableModel() +{ + return nameTableModel; +} + TransactionTableModel *WalletModel::getTransactionTableModel() { return transactionTableModel; diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h index fd52db2da3..ad1105d4bd 100644 --- a/src/qt/walletmodel.h +++ b/src/qt/walletmodel.h @@ -12,6 +12,7 @@ #include #include