diff --git a/contrib/dash-qt.pro b/contrib/dash-qt.pro index 708978d1d574..5ce552cedf51 100644 --- a/contrib/dash-qt.pro +++ b/contrib/dash-qt.pro @@ -6,6 +6,7 @@ FORMS += \ ../src/qt/forms/coincontroldialog.ui \ ../src/qt/forms/debugwindow.ui \ ../src/qt/forms/editaddressdialog.ui \ + ../src/qt/forms/governancelist.ui \ ../src/qt/forms/helpmessagedialog.ui \ ../src/qt/forms/intro.ui \ ../src/qt/forms/masternodelist.ui \ diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include index 967f601b39bf..3c0006100635 100644 --- a/src/Makefile.qt.include +++ b/src/Makefile.qt.include @@ -37,6 +37,7 @@ QT_FORMS_UI = \ qt/forms/coincontroldialog.ui \ qt/forms/editaddressdialog.ui \ qt/forms/helpmessagedialog.ui \ + qt/forms/governancelist.ui \ qt/forms/intro.ui \ qt/forms/modaloverlay.ui \ qt/forms/masternodelist.ui \ @@ -69,6 +70,7 @@ QT_MOC_CPP = \ qt/moc_csvmodelwriter.cpp \ qt/moc_editaddressdialog.cpp \ qt/moc_guiutil.cpp \ + qt/moc_governancelist.cpp \ qt/moc_intro.cpp \ qt/moc_macdockiconhandler.cpp \ qt/moc_macnotificationhandler.cpp \ @@ -147,6 +149,7 @@ BITCOIN_QT_H = \ qt/coincontroltreewidget.h \ qt/csvmodelwriter.h \ qt/editaddressdialog.h \ + qt/governancelist.h \ qt/guiconstants.h \ qt/guiutil.h \ qt/intro.h \ @@ -256,6 +259,7 @@ BITCOIN_QT_WALLET_CPP = \ qt/coincontroldialog.cpp \ qt/coincontroltreewidget.cpp \ qt/editaddressdialog.cpp \ + qt/governancelist.cpp \ qt/masternodelist.cpp \ qt/openuridialog.cpp \ qt/overviewpage.cpp \ diff --git a/src/governance/governance-object.cpp b/src/governance/governance-object.cpp index 7d96dd49557a..34fd5411e3ff 100644 --- a/src/governance/governance-object.cpp +++ b/src/governance/governance-object.cpp @@ -14,9 +14,10 @@ #include #include #include - #include +static constexpr int64_t SECONDS_IN_ONE_MONTH = 60 * 60 * 24 * 30; + CGovernanceObject::CGovernanceObject() : cs(), nObjectType(GOVERNANCE_OBJECT_UNKNOWN), @@ -624,6 +625,66 @@ int CGovernanceObject::CountMatchingVotes(vote_signal_enum_t eVoteSignalIn, vote * Get specific vote counts for each outcome (funding, validity, etc) */ +int64_t CGovernanceObject::GetPaymentStartTime() +{ + UniValue data; + data.read(this->GetDataAsPlainString()); + UniValue payment_start_data = find_value(data, "start_epoch"); + + if (payment_start_data.isNum()) return payment_start_data.get_int(); + + return -1; +} + +int64_t CGovernanceObject::GetPaymentEndTime() +{ + UniValue data; + data.read(this->GetDataAsPlainString()); + UniValue payment_end_data = find_value(data, "end_epoch"); + if (payment_end_data.isNum()) return payment_end_data.get_int(); + + return -1; +} + +int64_t CGovernanceObject::GetCycles() +{ + int diffInSeconds = GetPaymentEndTime() - GetPaymentStartTime(); + int cycle = std::round((float)((float)diffInSeconds / (float)SECONDS_IN_ONE_MONTH)); + + return cycle; +} + + +int64_t CGovernanceObject::GetCurrentCycle() +{ + int diffInSeconds = GetTime() - GetPaymentStartTime(); + int cycle = std::ceil((float)((float)diffInSeconds / (float)SECONDS_IN_ONE_MONTH)); + + return cycle; +} + + +std::string CGovernanceObject::GetURL() +{ + UniValue data; + data.read(this->GetDataAsPlainString()); + UniValue url_data = find_value(data, "url"); + + if (url_data.isStr()) return url_data.get_str(); + + return "not a valid url"; +} + +int64_t CGovernanceObject::GetPaymentAmount() +{ + UniValue data; + data.read(this->GetDataAsPlainString()); + UniValue payment_amount = find_value(data, "payment_amount"); + if (payment_amount.isNum()) return payment_amount.get_int(); + + return -1; +} + int CGovernanceObject::GetAbsoluteYesCount(vote_signal_enum_t eVoteSignalIn) const { return GetYesCount(eVoteSignalIn) - GetNoCount(eVoteSignalIn); diff --git a/src/governance/governance-object.h b/src/governance/governance-object.h index 3fbb4f70fc01..de7836408275 100644 --- a/src/governance/governance-object.h +++ b/src/governance/governance-object.h @@ -162,6 +162,18 @@ class CGovernanceObject // Public Getter methods + int64_t GetPaymentStartTime(); + + int64_t GetPaymentEndTime(); + + int64_t GetPaymentAmount(); + + int64_t GetCycles(); + + int64_t GetCurrentCycle(); + + std::string GetURL(); + int64_t GetCreationTime() const { return nTime; diff --git a/src/interfaces/node.cpp b/src/interfaces/node.cpp index b1ffa68abe34..90db3527fbe1 100644 --- a/src/interfaces/node.cpp +++ b/src/interfaces/node.cpp @@ -443,6 +443,14 @@ class NodeImpl : public Node fn(newList); })); } + + std::unique_ptr handleNotifyGovernanceListChanged(NotifyGovernanceListChangedFn fn) override + { + return MakeHandler( + ::uiInterface.NotifyGovernanceListChanged_connect([fn](std::vector newList) { + fn(newList); + })); + } std::unique_ptr handleNotifyAdditionalDataSyncProgressChanged(NotifyAdditionalDataSyncProgressChangedFn fn) override { return MakeHandler( diff --git a/src/interfaces/node.h b/src/interfaces/node.h index 40f68e30521c..e8449703c20d 100644 --- a/src/interfaces/node.h +++ b/src/interfaces/node.h @@ -28,6 +28,7 @@ class RPCTimerInterface; class UniValue; class proxyType; struct CNodeStateStats; +struct CGovernanceObject; namespace interfaces { class Handler; @@ -325,6 +326,12 @@ class Node std::function; virtual std::unique_ptr handleNotifyMasternodeListChanged(NotifyMasternodeListChangedFn fn) = 0; + //! Register handler for governance list update messages. + using NotifyGovernanceListChangedFn = + std::function newList)>; + virtual std::unique_ptr handleNotifyGovernanceListChanged(NotifyGovernanceListChangedFn fn) = 0; + + //! Register handler for additional data sync progress update messages. using NotifyAdditionalDataSyncProgressChangedFn = std::function; diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index 84c0504f940c..8b37dc235c03 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #include #include @@ -583,6 +584,14 @@ void BitcoinGUI::createToolBars() masternodeButton->setEnabled(true); } + if (settings.value("fShowGovernanceTab").toBool()) { + governanceButton = new QToolButton(this); + governanceButton->setText(tr("&Governance")); + governanceButton->setStatusTip(tr("Browse governance")); + tabGroup->addButton(governanceButton); + connect(governanceButton, &QToolButton::clicked, this, &BitcoinGUI::gotoGovernancePage); + } + connect(overviewButton, &QToolButton::clicked, this, &BitcoinGUI::gotoOverviewPage); connect(sendCoinsButton, &QToolButton::clicked, [this]{ gotoSendCoinsPage(); }); connect(coinJoinCoinsButton, &QToolButton::clicked, [this]{ gotoCoinJoinCoinsPage(); }); @@ -1006,6 +1015,15 @@ void BitcoinGUI::gotoMasternodePage() } } +void BitcoinGUI::gotoGovernancePage() +{ + QSettings settings; + if (settings.value("fShowGovernanceTab").toBool() && governanceButton) { + governanceButton->setChecked(true); + if (walletFrame) walletFrame->gotoGovernancePage(); + } +} + void BitcoinGUI::gotoReceiveCoinsPage() { receiveCoinsButton->setChecked(true); diff --git a/src/qt/bitcoingui.h b/src/qt/bitcoingui.h index 7fd749ad7f74..f157630d5536 100644 --- a/src/qt/bitcoingui.h +++ b/src/qt/bitcoingui.h @@ -124,6 +124,7 @@ class BitcoinGUI : public QMainWindow QToolButton* receiveCoinsButton = nullptr; QToolButton* historyButton = nullptr; QToolButton* masternodeButton = nullptr; + QToolButton* governanceButton = nullptr; QAction* appToolBarLogoAction = nullptr; QAction* quitAction = nullptr; QAction* sendCoinsMenuAction = nullptr; @@ -293,6 +294,8 @@ public Q_SLOTS: void gotoHistoryPage(); /** Switch to masternode page */ void gotoMasternodePage(); + /** Switch to governance page */ + void gotoGovernancePage(); /** Switch to receive coins page */ void gotoReceiveCoinsPage(); /** Switch to send coins page */ diff --git a/src/qt/clientmodel.cpp b/src/qt/clientmodel.cpp index 6fb1e42e77a6..4362459e800e 100644 --- a/src/qt/clientmodel.cpp +++ b/src/qt/clientmodel.cpp @@ -62,7 +62,6 @@ ClientModel::~ClientModel() int ClientModel::getNumConnections(unsigned int flags) const { CConnman::NumConnections connections = CConnman::CONNECTIONS_NONE; - if(flags == CONNECTIONS_IN) connections = CConnman::CONNECTIONS_IN; else if (flags == CONNECTIONS_OUT) @@ -83,6 +82,7 @@ void ClientModel::setMasternodeList(const CDeterministicMNList& mnList) Q_EMIT masternodeListChanged(); } + CDeterministicMNList ClientModel::getMasternodeList() const { LOCK(cs_mnlinst); @@ -95,6 +95,23 @@ void ClientModel::refreshMasternodeList() setMasternodeList(m_node.evo().getListAtChainTip()); } + +void ClientModel::setGovernanceList(std::vector list) +{ + govListCached = list; + Q_EMIT governanceListChanged(); +} + + +std::vector ClientModel::getGovernanceList() +{ + // right now gets all the list. This may change + govListCached = governance.GetAllNewerThan(0); + + return govListCached; +} + + int ClientModel::getHeaderTipHeight() const { if (cachedBestHeaderHeight == -1) { @@ -289,7 +306,12 @@ static void NotifyMasternodeListChanged(ClientModel *clientmodel, const CDetermi clientmodel->setMasternodeList(newList); } -static void NotifyAdditionalDataSyncProgressChanged(ClientModel *clientmodel, double nSyncProgress) +static void NotifyGovernanceListChanged(ClientModel *clientmodel, std::vector newList) +{ + clientmodel->setGovernanceList(newList); +} + +static void NotifyAdditionalDataSyncProgressChanged(ClientModel* clientmodel, double nSyncProgress) { QMetaObject::invokeMethod(clientmodel, "additionalDataSyncProgressChanged", Qt::QueuedConnection, Q_ARG(double, nSyncProgress)); @@ -298,6 +320,7 @@ static void NotifyAdditionalDataSyncProgressChanged(ClientModel *clientmodel, do void ClientModel::subscribeToCoreSignals() { // Connect signals to client + m_handler_show_progress = m_node.handleShowProgress(std::bind(ShowProgress, this, std::placeholders::_1, std::placeholders::_2)); m_handler_notify_num_connections_changed = m_node.handleNotifyNumConnectionsChanged(std::bind(NotifyNumConnectionsChanged, this, std::placeholders::_1)); m_handler_notify_network_active_changed = m_node.handleNotifyNetworkActiveChanged(std::bind(NotifyNetworkActiveChanged, this, std::placeholders::_1)); @@ -307,6 +330,7 @@ void ClientModel::subscribeToCoreSignals() m_handler_notify_chainlock = m_node.handleNotifyChainLock(std::bind(NotifyChainLock, this, std::placeholders::_1, std::placeholders::_2)); m_handler_notify_header_tip = m_node.handleNotifyHeaderTip(std::bind(BlockTipChanged, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5, true)); m_handler_notify_masternodelist_changed = m_node.handleNotifyMasternodeListChanged(std::bind(NotifyMasternodeListChanged, this, std::placeholders::_1)); + m_handler_notify_masternodelist_changed = m_node.handleNotifyGovernanceListChanged(std::bind(NotifyGovernanceListChanged, this, std::placeholders::_1)); m_handler_notify_additional_data_sync_progess_changed = m_node.handleNotifyAdditionalDataSyncProgressChanged(std::bind(NotifyAdditionalDataSyncProgressChanged, this, std::placeholders::_1)); } diff --git a/src/qt/clientmodel.h b/src/qt/clientmodel.h index ae5efa409893..5fdfdc5d03d7 100644 --- a/src/qt/clientmodel.h +++ b/src/qt/clientmodel.h @@ -6,6 +6,7 @@ #ifndef BITCOIN_QT_CLIENTMODEL_H #define BITCOIN_QT_CLIENTMODEL_H +#include #include #include @@ -67,6 +68,9 @@ class ClientModel : public QObject CDeterministicMNList getMasternodeList() const; void refreshMasternodeList(); + void setGovernanceList(std::vector list); + std::vector getGovernanceList(); + //! Returns enum BlockSource of the current importing/syncing state enum BlockSource getBlockSource() const; //! Return warnings to be displayed in status bar @@ -96,6 +100,7 @@ class ClientModel : public QObject std::unique_ptr m_handler_notify_chainlock; std::unique_ptr m_handler_notify_header_tip; std::unique_ptr m_handler_notify_masternodelist_changed; + std::unique_ptr m_handler_notify_governancelist_changed; std::unique_ptr m_handler_notify_additional_data_sync_progess_changed; OptionsModel *optionsModel; PeerTableModel *peerTableModel; @@ -108,6 +113,7 @@ class ClientModel : public QObject // representation of the list in UI during initial sync/reindex, so we cache it here too. mutable CCriticalSection cs_mnlinst; // protects mnListCached CDeterministicMNListPtr mnListCached; + std::vector govListCached; void subscribeToCoreSignals(); void unsubscribeFromCoreSignals(); @@ -116,6 +122,7 @@ class ClientModel : public QObject void numConnectionsChanged(int count); void masternodeListChanged() const; void chainLockChanged(const QString& bestChainLockHash, int bestChainLockHeight); + void governanceListChanged() const; void numBlocksChanged(int count, const QDateTime& blockDate, const QString& blockHash, double nVerificationProgress, bool header); void additionalDataSyncProgressChanged(double nSyncProgress); void mempoolSizeChanged(long count, size_t mempoolSizeInBytes); diff --git a/src/qt/forms/governancelist.ui b/src/qt/forms/governancelist.ui new file mode 100644 index 000000000000..fb31d99f77c3 --- /dev/null +++ b/src/qt/forms/governancelist.ui @@ -0,0 +1,196 @@ + + + GovernanceList + + + + 0 + 0 + 1318 + 639 + + + + Form + + + + 20 + + + 0 + + + 20 + + + 0 + + + + + 0 + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + true + + + + + 0 + 0 + 1272 + 574 + + + + + + + QAbstractItemView::NoEditTriggers + + + true + + + QAbstractItemView::SingleSelection + + + QAbstractItemView::SelectRows + + + Qt::ElideMiddle + + + true + + + true + + + true + + + true + + + + Active + + + + + Amount + + + + + Cycles + + + + + Current Cycle + + + + + Absolute Yes Count + + + + + Yes Count + + + + + No Count + + + + + Abstain Count + + + + + Payment Start + + + + + Payment End + + + + + Creation Time + + + + + + + + + + + + 0 + + + + + Filter List: + + + + + + + Filter masternode list + + + + + + + Qt::Horizontal + + + + 10 + 20 + + + + + + + + + + + + + + + diff --git a/src/qt/forms/optionsdialog.ui b/src/qt/forms/optionsdialog.ui index c797bdf4bf5d..18603b3a48ad 100644 --- a/src/qt/forms/optionsdialog.ui +++ b/src/qt/forms/optionsdialog.ui @@ -6,8 +6,8 @@ 0 0 - 583 - 420 + 595 + 461 @@ -102,7 +102,7 @@ - 5 + 1 @@ -307,48 +307,55 @@ - - - - - Whether to show coin control features or not. - - - Enable coin &control features - - - - - - - Show additional tab listing all your masternodes in first sub-tab<br/>and all masternodes on the network in second sub-tab. - - - Show Masternodes Tab - - - - - - - If you disable the spending of unconfirmed change, the change from a transaction<br/>cannot be used until that transaction has at least one confirmation.<br/>This also affects how your balance is computed. - - - &Spend unconfirmed change - - - - - - - Show mixing interface on Overview screen and reveal an additional screen which allows to spend fully mixed coins only.<br/>A new tab with more settings will also appear in this dialog, please make sure to check them before mixing your coins. - - - Enable CoinJoin features - - - - + + + + + Whether to show coin control features or not. + + + Enable coin &control features + + + + + + + Show additional tab listing all your masternodes in first sub-tab<br/>and all masternodes on the network in second sub-tab. + + + Show Masternodes Tab + + + + + + + Show Governance Tab + + + + + + + If you disable the spending of unconfirmed change, the change from a transaction<br/>cannot be used until that transaction has at least one confirmation.<br/>This also affects how your balance is computed. + + + &Spend unconfirmed change + + + + + + + Show mixing interface on Overview screen and reveal an additional screen which allows to spend fully mixed coins only.<br/>A new tab with more settings will also appear in this dialog, please make sure to check them before mixing your coins. + + + Enable CoinJoin features + + + + diff --git a/src/qt/governancelist.cpp b/src/qt/governancelist.cpp new file mode 100644 index 000000000000..390c0bad8457 --- /dev/null +++ b/src/qt/governancelist.cpp @@ -0,0 +1,362 @@ +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include + + +template +class CGovernanceListWidgetItem : public QTableWidgetItem +{ + T itemData; + +public: + explicit CGovernanceListWidgetItem(const QString& text, const T& data, int type = Type) : + QTableWidgetItem(text, type), + itemData(data) {} + + bool operator<(const QTableWidgetItem& other) const + { + return itemData < ((CGovernanceListWidgetItem*)&other)->itemData; + } +}; + +GovernanceList::GovernanceList(QWidget* parent) : + QWidget(parent), + ui(new Ui::GovernanceList), + clientModel(0), + walletModel(0), + fFilterUpdatedDIP3(true), + nTimeFilterUpdatedDIP3(0), + nTimeUpdatedDIP3(0), + mnListChanged(true) +{ + ui->setupUi(this); + GUIUtil::setFont({ui->label_filter_2}, GUIUtil::FontWeight::Normal, 15); + + //hiding for now, will be use in future + ui->label_filter_2->hide(); + ui->filterLineEditDIP3->hide(); + + int columnStatusWidth = 80; + int columnAmountWidth = 80; + int columnCyclesWidth = 130; + int columnCurrentCycleWidth = 100; + int columnAbsYesCountWidth = 130; + int columnNoCountWidth = 80; + int columnAbsteinCountWidth = 130; + int columnPaymentStartWidth = 160; + int columnPaymentEndWidth = 160; + int columnCreationTime = 130; + + + ui->tableWidgetProposalsDIP3->setColumnWidth(COLUMN_STATUS, columnStatusWidth); + ui->tableWidgetProposalsDIP3->setColumnWidth(COLUMN_AMOUNT, columnAmountWidth); + ui->tableWidgetProposalsDIP3->setColumnWidth(COLUMN_CYCLES, columnCyclesWidth); + ui->tableWidgetProposalsDIP3->setColumnWidth(COLUMN_CURRENT_CYCLE, columnCurrentCycleWidth); + ui->tableWidgetProposalsDIP3->setColumnWidth(COLUMN_ABS_YES_COUNT, columnAbsYesCountWidth); + ui->tableWidgetProposalsDIP3->setColumnWidth(COLUMN_YES_COUNT, columnAbsYesCountWidth); + ui->tableWidgetProposalsDIP3->setColumnWidth(COLUMN_NO_COUNT, columnNoCountWidth); + ui->tableWidgetProposalsDIP3->setColumnWidth(COLUMN_ABSTAIN_COUNT, columnAbsteinCountWidth); + ui->tableWidgetProposalsDIP3->setColumnWidth(COLUMN_PAYMENT_START, columnPaymentStartWidth); + ui->tableWidgetProposalsDIP3->setColumnWidth(COLUMN_PAYMENT_END, columnPaymentEndWidth); + ui->tableWidgetProposalsDIP3->setColumnWidth(COLUMN_CREATION_TIME, columnCreationTime); + + + ui->tableWidgetProposalsDIP3->setContextMenuPolicy(Qt::CustomContextMenu); + + ui->filterLineEditDIP3->setPlaceholderText(tr("Filter by any property (e.g. address or protx hash)")); + + QAction* openProLinkAction = new QAction(tr("Open Proposal Link"), this); + contextMenuDIP3 = new QMenu(this); + contextMenuDIP3->addAction(openProLinkAction); + connect(ui->tableWidgetProposalsDIP3, &QTableWidget::customContextMenuRequested, this, &GovernanceList::showContextMenuDIP3); + connect(ui->tableWidgetProposalsDIP3, &QTableWidget::doubleClicked, this, &GovernanceList::extraInfoDIP3_clicked); + connect(ui->tableWidgetProposalsDIP3, &QTableWidget::clicked, this, &GovernanceList::tableWidgetRow_clicked); + connect(openProLinkAction, &QAction::triggered, this, &GovernanceList::openProLink_clicked); + + timer = new QTimer(this); + connect(timer, &QTimer::timeout, this, &GovernanceList::updateDIP3ListScheduled); + timer->start(10000); + + GUIUtil::updateFonts(); +} + +GovernanceList::~GovernanceList() +{ + delete ui; +} + +void GovernanceList::setClientModel(ClientModel* model) +{ + this->clientModel = model; + if (model) { + // try to update list when masternode count changes + connect(clientModel, &ClientModel::governanceListChanged, this, &GovernanceList::handleGovernanceListChanged); + } +} + +void GovernanceList::setWalletModel(WalletModel* model) +{ + this->walletModel = model; +} + +void GovernanceList::showContextMenuDIP3(const QPoint& point) +{ + QTableWidgetItem* item = ui->tableWidgetProposalsDIP3->itemAt(point); + if (item) contextMenuDIP3->exec(QCursor::pos()); +} + +void GovernanceList::handleGovernanceListChanged() +{ + LOCK(cs_dip3list); + mnListChanged = true; +} + + +//update schedule is same as masternodes' update schedule. May be change if needed any governanceList specified change +void GovernanceList::updateDIP3ListScheduled() +{ + TRY_LOCK(cs_dip3list, fLockAcquired); + if (!fLockAcquired) return; + + if (!clientModel || clientModel->node().shutdownRequested()) { + return; + } + + // To prevent high cpu usage update only once in GOVERNANCELIST_FILTER_COOLDOWN_SECONDS seconds + // after filter was last changed unless we want to force the update. + if (fFilterUpdatedDIP3) { + int64_t nSecondsToWait = nTimeFilterUpdatedDIP3 - GetTime() + GOVERNANCELIST_FILTER_COOLDOWN_SECONDS; + + if (nSecondsToWait <= 0) { + updateDIP3List(); + fFilterUpdatedDIP3 = false; + } + } else if (mnListChanged) { + int64_t nMnListUpdateSecods = clientModel->masternodeSync().isBlockchainSynced() ? GOVERNANCELIST_UPDATE_SECONDS : GOVERNANCELIST_UPDATE_SECONDS * 10; + int64_t nSecondsToWait = nTimeUpdatedDIP3 - GetTime() + nMnListUpdateSecods; + + if (nSecondsToWait <= 0) { + updateDIP3List(); + mnListChanged = false; + } + } +} + + +void GovernanceList::updateDIP3List() +{ + if (!clientModel || clientModel->node().shutdownRequested()) { + return; + } + + std::vector governanceList = clientModel->getGovernanceList(); + + LOCK(cs_dip3list); + + QString strToFilter; + ui->tableWidgetProposalsDIP3->setSortingEnabled(false); + + //deletes prev. items in the table + for (int i = 0; i < ui->tableWidgetProposalsDIP3->rowCount(); i++) { + QTableWidgetItem* status_item = ui->tableWidgetProposalsDIP3->item(i, COLUMN_STATUS); + if (status_item != nullptr) delete status_item; + + QTableWidgetItem* amount_item = ui->tableWidgetProposalsDIP3->item(i, COLUMN_AMOUNT); + if (status_item != nullptr) delete amount_item; + + QTableWidgetItem* cycles_item = ui->tableWidgetProposalsDIP3->item(i, COLUMN_CYCLES); + if (status_item != nullptr) delete cycles_item; + + QTableWidgetItem* curr_cycles_item = ui->tableWidgetProposalsDIP3->item(i, COLUMN_CURRENT_CYCLE); + if (status_item != nullptr) delete curr_cycles_item; + + QTableWidgetItem* abs_yes_item = ui->tableWidgetProposalsDIP3->item(i, COLUMN_ABS_YES_COUNT); + if (status_item != nullptr) delete abs_yes_item; + + QTableWidgetItem* yes_item = ui->tableWidgetProposalsDIP3->item(i, COLUMN_YES_COUNT); + if (status_item != nullptr) delete yes_item; + + QTableWidgetItem* no_item = ui->tableWidgetProposalsDIP3->item(i, COLUMN_NO_COUNT); + if (status_item != nullptr) delete no_item; + + QTableWidgetItem* abstain_yes_item = ui->tableWidgetProposalsDIP3->item(i, COLUMN_ABSTAIN_COUNT); + if (status_item != nullptr) delete abstain_yes_item; + + QTableWidgetItem* payment_start_item = ui->tableWidgetProposalsDIP3->item(i, COLUMN_PAYMENT_START); + if (status_item != nullptr) delete payment_start_item; + + QTableWidgetItem* payment_end_item = ui->tableWidgetProposalsDIP3->item(i, COLUMN_PAYMENT_END); + if (status_item != nullptr) delete payment_end_item; + + QTableWidgetItem* creation_item = ui->tableWidgetProposalsDIP3->item(i, COLUMN_CREATION_TIME); + if (status_item != nullptr) delete creation_item; + } + + ui->tableWidgetProposalsDIP3->clearContents(); + ui->tableWidgetProposalsDIP3->setRowCount(0); + + nTimeUpdatedDIP3 = GetTime(); + + for (int i = 0; i < governanceList.size(); i++) { + CGovernanceObject* obj = (CGovernanceObject*)governanceList.at(i); + + QString isActiveStr = ""; + std::string err = ""; + + if(obj->IsValidLocally(err, false)) + isActiveStr = "Yes"; + else + isActiveStr = "No"; + + QDateTime creationTime = QDateTime::fromSecsSinceEpoch(obj->GetCreationTime()); + QDateTime paymentStartTime = QDateTime::fromSecsSinceEpoch(obj->GetPaymentStartTime()); + QDateTime paymentEndTime = QDateTime::fromSecsSinceEpoch(obj->GetPaymentEndTime()); + int yesCount = obj->GetYesCount(VOTE_SIGNAL_FUNDING); + int noCount = obj->GetNoCount(VOTE_SIGNAL_FUNDING); + int abstainCount = obj->GetAbstainCount(VOTE_SIGNAL_FUNDING); + int absYesCount = obj->GetAbsoluteYesCount(VOTE_SIGNAL_FUNDING); + int paymentAmount = obj->GetPaymentAmount(); + int cycles = obj->GetCycles(); + int currentCycle = obj->GetCurrentCycle(); + + QString paymentAmountStr = "-"; + QString cyclesStr = "-"; + QString currentCycleStr = "-"; + QString paymentStartStr = "-"; + QString paymentEndStr = "-"; + QString creationTimeStr = "-"; + + if (obj->GetObjectType() == GOVERNANCE_OBJECT_PROPOSAL) { + paymentAmountStr = QString::number(paymentAmount); + cyclesStr = QString::number(cycles); + currentCycleStr = QString::number(currentCycle); + paymentStartStr = paymentStartTime.toString("dd.MM.yyyy HH.mm"); + paymentEndStr = paymentEndTime.toString("dd.MM.yyyy HH.mm"); + creationTimeStr = creationTime.toString("dd.MM.yyyy HH.mm"); + } + QTableWidgetItem* statusItem = new QTableWidgetItem(isActiveStr); + QTableWidgetItem* amountItem = new QTableWidgetItem(paymentAmountStr); + QTableWidgetItem* cyclesItem = new QTableWidgetItem(cyclesStr); + QTableWidgetItem* currentCycleItem = new QTableWidgetItem(currentCycleStr); + QTableWidgetItem* absuluteYesCountItem = new QTableWidgetItem(QString::number(absYesCount)); + QTableWidgetItem* yesCountItem = new QTableWidgetItem(QString::number(yesCount)); + QTableWidgetItem* noCountItem = new QTableWidgetItem(QString::number(noCount)); + QTableWidgetItem* abstainCountItem = new QTableWidgetItem(QString::number(abstainCount)); + QTableWidgetItem* paymentStartItem = new QTableWidgetItem(paymentStartStr); + QTableWidgetItem* paymentEndItem = new QTableWidgetItem(paymentEndStr); + QTableWidgetItem* creationTimeItem = new QTableWidgetItem(creationTimeStr); + + //hash is embedding to COLUMN_STATUS/UserRole to use on row selection + statusItem->setData(Qt::UserRole, QString::fromStdString(governanceList[i]->GetHash().ToString())); + + ui->tableWidgetProposalsDIP3->insertRow(0); + + ui->tableWidgetProposalsDIP3->setItem(0, COLUMN_STATUS, statusItem); + ui->tableWidgetProposalsDIP3->setItem(0, COLUMN_AMOUNT, amountItem); + ui->tableWidgetProposalsDIP3->setItem(0, COLUMN_CYCLES, cyclesItem); + ui->tableWidgetProposalsDIP3->setItem(0, COLUMN_CURRENT_CYCLE, currentCycleItem); + ui->tableWidgetProposalsDIP3->setItem(0, COLUMN_ABS_YES_COUNT, absuluteYesCountItem); + ui->tableWidgetProposalsDIP3->setItem(0, COLUMN_YES_COUNT, yesCountItem); + ui->tableWidgetProposalsDIP3->setItem(0, COLUMN_NO_COUNT, noCountItem); + ui->tableWidgetProposalsDIP3->setItem(0, COLUMN_ABSTAIN_COUNT, abstainCountItem); + ui->tableWidgetProposalsDIP3->setItem(0, COLUMN_PAYMENT_START, paymentStartItem); + ui->tableWidgetProposalsDIP3->setItem(0, COLUMN_PAYMENT_END, paymentEndItem); + ui->tableWidgetProposalsDIP3->setItem(0, COLUMN_CREATION_TIME, creationTimeItem); + } + + ui->tableWidgetProposalsDIP3->setSortingEnabled(true); +} + +void GovernanceList::on_filterLineEditDIP3_textChanged(const QString& strFilterIn) +{ + strCurrentFilterDIP3 = strFilterIn; + nTimeFilterUpdatedDIP3 = GetTime(); + fFilterUpdatedDIP3 = true; +} + +CGovernanceObject* GovernanceList::GetSelectedDIP3GOV() +{ + if (!clientModel) { + return nullptr; + } + + std::string strProTxHash; + { + LOCK(cs_dip3list); + + QItemSelectionModel* selectionModel = ui->tableWidgetProposalsDIP3->selectionModel(); + QModelIndexList selected = selectionModel->selectedRows(); + + if (selected.count() == 0) return nullptr; + + QModelIndex index = selected.at(0); + int nSelectedRow = index.row(); + strProTxHash = ui->tableWidgetProposalsDIP3->item(nSelectedRow, COLUMN_STATUS)->data(Qt::UserRole).toString().toStdString(); + } + + uint256 proTxHash; + proTxHash.SetHex(strProTxHash); + + std::vector list = clientModel->getGovernanceList(); + for (int i = 0; i < list.size(); i++) { + const CGovernanceObject* obj = list[i]; + + if (proTxHash == obj->GetHash()) { + return (CGovernanceObject*)obj; + } + } + return nullptr; +} + +void GovernanceList::extraInfoDIP3_clicked() +{ + CGovernanceObject* gov = GetSelectedDIP3GOV(); + if (gov == nullptr) { + return; + } + + UniValue json(UniValue::VOBJ); + json = gov->ToJson(); + + // Title of popup window + QString strWindowtitle = tr("Additional information for Proposal %1").arg(QString::fromStdString(gov->GetHash().ToString())); + QString strText = QString::fromStdString(json.write(2)); + + QMessageBox::information(this, strWindowtitle, strText); +} + +void GovernanceList::openProLink_clicked() +{ + CGovernanceObject* gov = GetSelectedDIP3GOV(); + if (gov == nullptr) { + return; + } + + std::string url = gov->GetURL(); + QDesktopServices::openUrl(QUrl(QString::fromStdString(url))); +} + + +//For future use. Maybe to show details in a some area on UI +void GovernanceList::tableWidgetRow_clicked() +{ + /* + CGovernanceObject *gov = GetSelectedDIP3GOV(); + if (gov == nullptr) { + return; + } + */ + +} diff --git a/src/qt/governancelist.h b/src/qt/governancelist.h new file mode 100644 index 000000000000..c67ae0169b76 --- /dev/null +++ b/src/qt/governancelist.h @@ -0,0 +1,95 @@ +#ifndef BITCOIN_QT_GOVERNANCELIST_H +#define BITCOIN_QT_GOVERNANCELIST_H + +#include +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +#define GOVERNANCELIST_UPDATE_SECONDS 3 +#define GOVERNANCELIST_FILTER_COOLDOWN_SECONDS 3 + +namespace Ui { +class GovernanceList; +} + +class ClientModel; +class WalletModel; +class CGovernanceObject; +class CSuperblock; + +QT_BEGIN_NAMESPACE +class QModelIndex; +QT_END_NAMESPACE + +/** Masternode Manager page widget */ +class GovernanceList : public QWidget +{ + Q_OBJECT + +public: + explicit GovernanceList(QWidget* parent = 0); + ~GovernanceList(); + + enum { + COLUMN_STATUS, + COLUMN_AMOUNT, + COLUMN_CYCLES, + COLUMN_CURRENT_CYCLE, + COLUMN_ABS_YES_COUNT, + COLUMN_YES_COUNT, + COLUMN_NO_COUNT, + COLUMN_ABSTAIN_COUNT, + COLUMN_PAYMENT_START, + COLUMN_PAYMENT_END, + COLUMN_CREATION_TIME, + }; + + void setClientModel(ClientModel* clientModel); + void setWalletModel(WalletModel* walletModel); + +private: + QMenu* contextMenuDIP3; + int64_t nTimeFilterUpdatedDIP3; + int64_t nTimeUpdatedDIP3; + bool fFilterUpdatedDIP3; + + QTimer* timer; + Ui::GovernanceList* ui; + ClientModel* clientModel; + WalletModel* walletModel; + + // Protects tableWidgetProposalsDIP3 + CCriticalSection cs_dip3list; + + QString strCurrentFilterDIP3; + + bool mnListChanged; + + CGovernanceObject* GetSelectedDIP3GOV(); + + void updateDIP3List(); + +Q_SIGNALS: + void doubleClicked(const QModelIndex&); + +private Q_SLOTS: + void showContextMenuDIP3(const QPoint&); + void on_filterLineEditDIP3_textChanged(const QString& strFilterIn); + + void extraInfoDIP3_clicked(); + void openProLink_clicked(); + void tableWidgetRow_clicked(); + + void handleGovernanceListChanged(); + void updateDIP3ListScheduled(); +}; +#endif // BITCOIN_QT_MASTERNODELIST_H diff --git a/src/qt/optionsdialog.cpp b/src/qt/optionsdialog.cpp index 8fe5920779a0..e7573a3c31b5 100644 --- a/src/qt/optionsdialog.cpp +++ b/src/qt/optionsdialog.cpp @@ -246,6 +246,7 @@ void OptionsDialog::setModel(OptionsModel *_model) connect(ui->databaseCache, static_cast(&QSpinBox::valueChanged), this, &OptionsDialog::showRestartWarning); connect(ui->threadsScriptVerif, static_cast(&QSpinBox::valueChanged), this, &OptionsDialog::showRestartWarning); /* Wallet */ + connect(ui->showMasternodesTab, &QCheckBox::clicked, this, &OptionsDialog::showRestartWarning); connect(ui->spendZeroConfChange, &QCheckBox::clicked, this, &OptionsDialog::showRestartWarning); connect(ui->spendZeroConfChange, &QCheckBox::clicked, this, &OptionsDialog::showRestartWarning); @@ -302,6 +303,7 @@ void OptionsDialog::setMapper() /* Wallet */ mapper->addMapping(ui->coinControlFeatures, OptionsModel::CoinControlFeatures); mapper->addMapping(ui->showMasternodesTab, OptionsModel::ShowMasternodesTab); + mapper->addMapping(ui->showGovernanceTab, OptionsModel::ShowGovernanceTab); mapper->addMapping(ui->showAdvancedCJUI, OptionsModel::ShowAdvancedCJUI); mapper->addMapping(ui->showCoinJoinPopups, OptionsModel::ShowCoinJoinPopups); mapper->addMapping(ui->lowKeysWarning, OptionsModel::LowKeysWarning); diff --git a/src/qt/optionsmodel.cpp b/src/qt/optionsmodel.cpp index 88c65cdf5a0c..f5a8775271f7 100644 --- a/src/qt/optionsmodel.cpp +++ b/src/qt/optionsmodel.cpp @@ -395,6 +395,8 @@ QVariant OptionsModel::data(const QModelIndex & index, int role) const return settings.value("bSpendZeroConfChange"); case ShowMasternodesTab: return settings.value("fShowMasternodesTab"); + case ShowGovernanceTab: + return settings.value("fShowGovernanceTab"); case CoinJoinEnabled: return settings.value("fCoinJoinEnabled"); case ShowAdvancedCJUI: @@ -556,6 +558,12 @@ bool OptionsModel::setData(const QModelIndex & index, const QVariant & value, in setRestartRequired(true); } break; + case ShowGovernanceTab: + if (settings.value("fShowGovernanceTab") != value) { + settings.setValue("fShowGovernanceTab", value); + setRestartRequired(true); + } + break; case CoinJoinEnabled: if (settings.value("fCoinJoinEnabled") != value) { settings.setValue("fCoinJoinEnabled", value.toBool()); diff --git a/src/qt/optionsmodel.h b/src/qt/optionsmodel.h index 541d6385f8e0..12e5f33982fc 100644 --- a/src/qt/optionsmodel.h +++ b/src/qt/optionsmodel.h @@ -34,41 +34,42 @@ class OptionsModel : public QAbstractListModel explicit OptionsModel(interfaces::Node& node, QObject *parent = nullptr, bool resetSettings = false); enum OptionID { - StartAtStartup, // bool - HideTrayIcon, // bool - MinimizeToTray, // bool - MapPortUPnP, // bool - MinimizeOnClose, // bool - ProxyUse, // bool - ProxyIP, // QString - ProxyPort, // int - ProxyUseTor, // bool - ProxyIPTor, // QString - ProxyPortTor, // int - DisplayUnit, // BitcoinUnits::Unit - ThirdPartyTxUrls, // QString - Digits, // QString - Theme, // QString - FontFamily, // int - FontScale, // int - FontWeightNormal, // int - FontWeightBold, // int - Language, // QString - CoinControlFeatures, // bool - ThreadsScriptVerif, // int - Prune, // bool - PruneSize, // int - DatabaseCache, // int - SpendZeroConfChange, // bool - ShowMasternodesTab, // bool - CoinJoinEnabled, // bool - ShowAdvancedCJUI, // bool - ShowCoinJoinPopups, // bool - LowKeysWarning, // bool - CoinJoinRounds, // int - CoinJoinAmount, // int - CoinJoinMultiSession,// bool - Listen, // bool + StartAtStartup, // bool + HideTrayIcon, // bool + MinimizeToTray, // bool + MapPortUPnP, // bool + MinimizeOnClose, // bool + ProxyUse, // bool + ProxyIP, // QString + ProxyPort, // int + ProxyUseTor, // bool + ProxyIPTor, // QString + ProxyPortTor, // int + DisplayUnit, // BitcoinUnits::Unit + ThirdPartyTxUrls, // QString + Digits, // QString + Theme, // QString + FontFamily, // int + FontScale, // int + FontWeightNormal, // int + FontWeightBold, // int + Language, // QString + CoinControlFeatures, // bool + ThreadsScriptVerif, // int + Prune, // bool + PruneSize, // int + DatabaseCache, // int + SpendZeroConfChange, // bool + ShowMasternodesTab, // bool + ShowGovernanceTab, // bool + CoinJoinEnabled, // bool + ShowAdvancedCJUI, // bool + ShowCoinJoinPopups, // bool + LowKeysWarning, // bool + CoinJoinRounds, // int + CoinJoinAmount, // int + CoinJoinMultiSession, // bool + Listen, // bool OptionIDRowCount, }; diff --git a/src/qt/walletframe.cpp b/src/qt/walletframe.cpp index ba35538b6178..63524d5566c7 100644 --- a/src/qt/walletframe.cpp +++ b/src/qt/walletframe.cpp @@ -162,6 +162,14 @@ void WalletFrame::gotoMasternodePage() i.value()->gotoMasternodePage(); } + +void WalletFrame::gotoGovernancePage() +{ + QMap::const_iterator i; + for (i = mapWalletViews.constBegin(); i != mapWalletViews.constEnd(); ++i) + i.value()->gotoGovernancePage(); +} + void WalletFrame::gotoReceiveCoinsPage() { QMap::const_iterator i; diff --git a/src/qt/walletframe.h b/src/qt/walletframe.h index 70b9e6af671d..dfa44c6a3e90 100644 --- a/src/qt/walletframe.h +++ b/src/qt/walletframe.h @@ -69,6 +69,8 @@ public Q_SLOTS: void gotoHistoryPage(); /** Switch to masternode page */ void gotoMasternodePage(); + /** Switch to governance page */ + void gotoGovernancePage(); /** Switch to receive coins page */ void gotoReceiveCoinsPage(); /** Switch to send coins page */ diff --git a/src/qt/walletview.cpp b/src/qt/walletview.cpp index 83d81405f189..a0d48cf78de1 100644 --- a/src/qt/walletview.cpp +++ b/src/qt/walletview.cpp @@ -90,6 +90,11 @@ WalletView::WalletView(QWidget* parent) : addWidget(masternodeListPage); } + if (settings.value("fShowGovernanceTab").toBool()) { + governanceListPage = new GovernanceList(); + addWidget(governanceListPage); + } + // Clicking on a transaction on the overview pre-selects the transaction on the transaction history page connect(overviewPage, &OverviewPage::transactionClicked, transactionView, static_cast(&TransactionView::focusTransaction)); connect(overviewPage, &OverviewPage::outOfSyncWarningClicked, this, &WalletView::requestedSyncWarningInfo); @@ -156,6 +161,9 @@ void WalletView::setClientModel(ClientModel *_clientModel) if (settings.value("fShowMasternodesTab").toBool()) { masternodeListPage->setClientModel(_clientModel); } + if (settings.value("fShowGovernanceTab").toBool()) { + governanceListPage->setClientModel(_clientModel); + } } void WalletView::setWalletModel(WalletModel *_walletModel) @@ -169,6 +177,9 @@ void WalletView::setWalletModel(WalletModel *_walletModel) if (settings.value("fShowMasternodesTab").toBool()) { masternodeListPage->setWalletModel(_walletModel); } + if (settings.value("fShowGovernanceTab").toBool()) { + governanceListPage->setWalletModel(_walletModel); + } receiveCoinsPage->setModel(_walletModel); sendCoinsPage->setModel(_walletModel); coinJoinCoinsPage->setModel(_walletModel); @@ -245,6 +256,15 @@ void WalletView::gotoMasternodePage() } } + +void WalletView::gotoGovernancePage() +{ + QSettings settings; + if (settings.value("fShowGovernanceTab").toBool()) { + setCurrentWidget(governanceListPage); + } +} + void WalletView::gotoReceiveCoinsPage() { setCurrentWidget(receiveCoinsPage); diff --git a/src/qt/walletview.h b/src/qt/walletview.h index a6ba03d7440a..50200fb12345 100644 --- a/src/qt/walletview.h +++ b/src/qt/walletview.h @@ -6,6 +6,7 @@ #define BITCOIN_QT_WALLETVIEW_H #include +#include #include #include @@ -68,6 +69,7 @@ class WalletView : public QStackedWidget AddressBookPage *usedSendingAddressesPage; AddressBookPage *usedReceivingAddressesPage; MasternodeList *masternodeListPage; + GovernanceList* governanceListPage; TransactionView *transactionView; @@ -81,6 +83,8 @@ public Q_SLOTS: void gotoHistoryPage(); /** Switch to masternode page */ void gotoMasternodePage(); + /** Switch to governance page */ + void gotoGovernancePage(); /** Switch to receive coins page */ void gotoReceiveCoinsPage(); /** Switch to send coins page */ diff --git a/src/ui_interface.cpp b/src/ui_interface.cpp index 2352581cf021..d7641fd7b0fc 100644 --- a/src/ui_interface.cpp +++ b/src/ui_interface.cpp @@ -23,6 +23,7 @@ struct UISignals { boost::signals2::signal NotifyChainLock; boost::signals2::signal NotifyHeaderTip; boost::signals2::signal NotifyMasternodeListChanged; + boost::signals2::signal NotifyGovernanceListChanged; boost::signals2::signal NotifyAdditionalDataSyncProgressChanged; boost::signals2::signal BannedListChanged; } g_ui_signals; @@ -49,6 +50,7 @@ ADD_SIGNALS_IMPL_WRAPPER(NotifyBlockTip); ADD_SIGNALS_IMPL_WRAPPER(NotifyChainLock); ADD_SIGNALS_IMPL_WRAPPER(NotifyHeaderTip); ADD_SIGNALS_IMPL_WRAPPER(NotifyMasternodeListChanged); +ADD_SIGNALS_IMPL_WRAPPER(NotifyGovernanceListChanged); ADD_SIGNALS_IMPL_WRAPPER(NotifyAdditionalDataSyncProgressChanged); ADD_SIGNALS_IMPL_WRAPPER(BannedListChanged); @@ -64,6 +66,7 @@ void CClientUIInterface::NotifyBlockTip(bool b, const CBlockIndex* i) { return g void CClientUIInterface::NotifyChainLock(const std::string& bestChainLockHash, int bestChainLockHeight) { return g_ui_signals.NotifyChainLock(bestChainLockHash, bestChainLockHeight); } void CClientUIInterface::NotifyHeaderTip(bool b, const CBlockIndex* i) { return g_ui_signals.NotifyHeaderTip(b, i); } void CClientUIInterface::NotifyMasternodeListChanged(const CDeterministicMNList& list) { return g_ui_signals.NotifyMasternodeListChanged(list); } +void CClientUIInterface::NotifyGovernanceListChanged(std::vector list) { return g_ui_signals.NotifyGovernanceListChanged(list); } void CClientUIInterface::NotifyAdditionalDataSyncProgressChanged(double nSyncProgress) { return g_ui_signals.NotifyAdditionalDataSyncProgressChanged(nSyncProgress); } void CClientUIInterface::BannedListChanged() { return g_ui_signals.BannedListChanged(); } diff --git a/src/ui_interface.h b/src/ui_interface.h index 603c2eedd8e4..708e974f2399 100644 --- a/src/ui_interface.h +++ b/src/ui_interface.h @@ -10,10 +10,12 @@ #include #include #include +#include class CWallet; class CBlockIndex; class CDeterministicMNList; +class CGovernanceObject; namespace boost { namespace signals2 { class connection; @@ -123,6 +125,9 @@ class CClientUIInterface /** Masternode list has changed */ ADD_SIGNALS_DECL_WRAPPER(NotifyMasternodeListChanged, void, const CDeterministicMNList&); + /** Governance list has changed */ + ADD_SIGNALS_DECL_WRAPPER(NotifyGovernanceListChanged, void, std::vector); + /** Additional data sync progress changed */ ADD_SIGNALS_DECL_WRAPPER(NotifyAdditionalDataSyncProgressChanged, void, double nSyncProgress);