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 0000000000..4e468bcde1 Binary files /dev/null and b/src/qt/res/icons/bitcoin_transparent_letter.png differ diff --git a/src/qt/res/src/bitcoin_transparent_letter.svg b/src/qt/res/src/bitcoin_transparent_letter.svg new file mode 100644 index 0000000000..caf70542ad --- /dev/null +++ b/src/qt/res/src/bitcoin_transparent_letter.svg @@ -0,0 +1,95 @@ + + + + + + + + + + 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