diff --git a/src/Makefile.qttest.include b/src/Makefile.qttest.include index f28967128861..ffcc760d2f93 100644 --- a/src/Makefile.qttest.include +++ b/src/Makefile.qttest.include @@ -9,6 +9,7 @@ TESTS += qt/test/test_dash-qt TEST_QT_MOC_CPP = \ qt/test/moc_apptests.cpp \ qt/test/moc_compattests.cpp \ + qt/test/moc_proposaltests.cpp \ qt/test/moc_rpcnestedtests.cpp \ qt/test/moc_trafficgraphdatatests.cpp \ qt/test/moc_uritests.cpp @@ -31,6 +32,7 @@ TEST_QT_H = \ qt/test/uritests.h \ qt/test/util.h \ qt/test/paymentrequestdata.h \ + qt/test/proposaltests.h \ qt/test/paymentservertests.h \ qt/test/trafficgraphdatatests.h \ qt/test/wallettests.h @@ -46,6 +48,7 @@ qt_test_test_dash_qt_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) $(BITCOIN_QT_ qt_test_test_dash_qt_SOURCES = \ qt/test/apptests.cpp \ qt/test/compattests.cpp \ + qt/test/proposaltests.cpp \ qt/test/rpcnestedtests.cpp \ qt/test/test_main.cpp \ qt/test/trafficgraphdatatests.cpp \ diff --git a/src/qt/governancelist.cpp b/src/qt/governancelist.cpp index 11e36ec3f405..bbd7fcc871a6 100644 --- a/src/qt/governancelist.cpp +++ b/src/qt/governancelist.cpp @@ -26,6 +26,9 @@ Proposal::Proposal(const CGovernanceObject* p, QObject* parent) : QObject(parent), pGovObj(p) { + //current date is handled with attribute for making tests possible + m_currentDate = QDateTime::currentDateTime(); + UniValue prop_data; if (prop_data.read(pGovObj->GetDataAsPlainString())) { if (UniValue titleValue = find_value(prop_data, "name"); titleValue.isStr()) { @@ -58,6 +61,16 @@ QDateTime Proposal::startDate() const { return m_startDate; } QDateTime Proposal::endDate() const { return m_endDate; } +QDateTime Proposal::currentDate() const { return m_currentDate; } + +int Proposal::paymentRemaining() const +{ + long remainingInSecs = endDate().toSecsSinceEpoch() - QDateTime::currentSecsSinceEpoch(); + int remainingInDays = remainingInSecs / Params().GetConsensus().nPowTargetTimespan; + float remainingCycle = remainingInDays / CYCLE_IN_DAYS; + return round(remainingCycle); +} + float Proposal::paymentAmount() const { return m_paymentAmount; } QString Proposal::url() const { return m_url; } @@ -89,6 +102,21 @@ int Proposal::GetAbsoluteYesCount() const return pGovObj->GetAbsoluteYesCount(VOTE_SIGNAL_FUNDING); } +int Proposal::GetYesCount() const +{ + return pGovObj->GetYesCount(VOTE_SIGNAL_FUNDING); +} + +int Proposal::GetNoCount() const +{ + return pGovObj->GetNoCount(VOTE_SIGNAL_FUNDING); +} + +int Proposal::GetAbstainCount() const +{ + return pGovObj->GetAbstainCount(VOTE_SIGNAL_FUNDING); +} + void Proposal::openUrl() const { QDesktopServices::openUrl(QUrl(m_url)); @@ -117,26 +145,32 @@ int ProposalModel::columnCount(const QModelIndex& index) const QVariant ProposalModel::data(const QModelIndex& index, int role) const { - if (role != Qt::DisplayRole && role != Qt::EditRole) return {}; + if (role != Qt::DisplayRole && role != Qt::EditRole && role != Qt::TextAlignmentRole) return {}; const auto proposal = m_data[index.row()]; switch(role) { case Qt::DisplayRole: { switch (index.column()) { - case Column::HASH: - return proposal->hash(); case Column::TITLE: return proposal->title(); - case Column::START_DATE: - return proposal->startDate().date(); - case Column::END_DATE: - return proposal->endDate().date(); case Column::PAYMENT_AMOUNT: return proposal->paymentAmount(); + case Column::PAYMENTS_REMAINING: + return proposal->paymentRemaining(); + case Column::YES_COUNT: + return proposal->GetYesCount(); + case Column::NO_COUNT: + return proposal->GetNoCount(); + case Column::ABSOLUTE_YES: + return proposal->GetAbsoluteYesCount(); + case Column::ABSTEIN_COUNT: + return proposal->GetAbstainCount(); case Column::IS_ACTIVE: return proposal->isActive() ? "Y" : "N"; case Column::VOTING_STATUS: return proposal->votingStatus(nAbsVoteReq); + case Column::URL: + return proposal->url(); default: return {}; }; @@ -146,25 +180,61 @@ QVariant ProposalModel::data(const QModelIndex& index, int role) const { // Edit role is used for sorting, so return the raw values where possible switch (index.column()) { - case Column::HASH: - return proposal->hash(); case Column::TITLE: return proposal->title(); - case Column::START_DATE: - return proposal->startDate(); - case Column::END_DATE: - return proposal->endDate(); case Column::PAYMENT_AMOUNT: return proposal->paymentAmount(); + case Column::PAYMENTS_REMAINING: + return proposal->paymentRemaining(); + case Column::YES_COUNT: + return proposal->GetYesCount(); + case Column::NO_COUNT: + return proposal->GetNoCount(); + case Column::ABSOLUTE_YES: + return proposal->GetAbsoluteYesCount(); + case Column::ABSTEIN_COUNT: + return proposal->GetAbstainCount(); case Column::IS_ACTIVE: return proposal->isActive(); case Column::VOTING_STATUS: return proposal->GetAbsoluteYesCount(); + case Column::URL: + return proposal->url(); default: return {}; }; break; } + case Qt::TextAlignmentRole: + { + // Edit role is used for sorting, so return the raw values where possible + switch (index.column()) { + case Column::TITLE: + return Qt::AlignLeft; + case Column::PAYMENT_AMOUNT: + return Qt::AlignCenter; + case Column::PAYMENTS_REMAINING: + return Qt::AlignCenter; + case Column::YES_COUNT: + return Qt::AlignCenter; + case Column::NO_COUNT: + return Qt::AlignCenter; + case Column::ABSOLUTE_YES: + return Qt::AlignCenter; + case Column::ABSTEIN_COUNT: + return Qt::AlignCenter; + case Column::IS_ACTIVE: + return Qt::AlignCenter; + case Column::VOTING_STATUS: + return Qt::AlignLeft; + case Column::URL: + return Qt::AlignLeft; + default: + return Qt::AlignLeft; + }; + break; + } + }; return {}; } @@ -173,20 +243,26 @@ QVariant ProposalModel::headerData(int section, Qt::Orientation orientation, int { if (orientation != Qt::Horizontal || role != Qt::DisplayRole) return {}; switch (section) { - case Column::HASH: - return "Hash"; case Column::TITLE: return "Title"; - case Column::START_DATE: - return "Start"; - case Column::END_DATE: - return "End"; + case Column::PAYMENTS_REMAINING: + return "Payments Remaining"; case Column::PAYMENT_AMOUNT: return "Amount"; + case Column::YES_COUNT: + return "Yes"; + case Column::NO_COUNT: + return "No"; + case Column::ABSOLUTE_YES: + return "Absolute Yes"; + case Column::ABSTEIN_COUNT: + return "Abstein"; case Column::IS_ACTIVE: return "Active"; case Column::VOTING_STATUS: return "Status"; + case Column::URL: + return "URL"; default: return {}; } @@ -195,16 +271,19 @@ QVariant ProposalModel::headerData(int section, Qt::Orientation orientation, int int ProposalModel::columnWidth(int section) { switch (section) { - case Column::HASH: - return 80; case Column::TITLE: return 220; - case Column::START_DATE: - case Column::END_DATE: + case Column::ABSOLUTE_YES: + return 180; + case Column::YES_COUNT: + case Column::NO_COUNT: + case Column::ABSTEIN_COUNT: case Column::PAYMENT_AMOUNT: return 110; case Column::IS_ACTIVE: return 80; + case Column::URL: + case Column::PAYMENTS_REMAINING: case Column::VOTING_STATUS: return 220; default: @@ -298,7 +377,7 @@ GovernanceList::GovernanceList(QWidget* parent) : // Set up sorting. proposalModelProxy->setSortRole(Qt::EditRole); ui->govTableView->setSortingEnabled(true); - ui->govTableView->sortByColumn(ProposalModel::Column::START_DATE, Qt::DescendingOrder); + ui->govTableView->sortByColumn(ProposalModel::Column::PAYMENTS_REMAINING, Qt::AscendingOrder); // Set up filtering. proposalModelProxy->setFilterKeyColumn(ProposalModel::Column::TITLE); // filter by title column... @@ -376,7 +455,7 @@ void GovernanceList::showProposalContextMenu(const QPoint& pos) QAction* openProposalUrl = new QAction(proposal->url(), this); proposalContextMenu->clear(); proposalContextMenu->addAction(openProposalUrl); - connect(openProposalUrl, &QAction::triggered, proposal, &Proposal::openUrl); + connect(openProposalUrl, &QAction::triggered, this, &GovernanceList::openUrl); proposalContextMenu->exec(QCursor::pos()); } @@ -396,3 +475,25 @@ void GovernanceList::showAdditionalInfo(const QModelIndex& index) QMessageBox::information(this, windowTitle, json); } + +void GovernanceList::openUrl() +{ + QAction *openProposalUrl = qobject_cast(sender()); + QString urlString = openProposalUrl->text(); + QUrl url(urlString); + if (!url.isValid()) { + QMessageBox::critical(this, "Invalid URL", "URL is not valid: " + urlString); + return; + } + + QMessageBox urlWarning; + urlWarning.setWindowTitle("Warning"); + urlWarning.setText("Do you want to open the url? " + urlString); + urlWarning.setStandardButtons(QMessageBox::Yes); + urlWarning.addButton(QMessageBox::No); + if (urlWarning.exec() == QMessageBox::Yes) { + QDesktopServices::openUrl(QUrl(url)); + } else { + return; + } +} diff --git a/src/qt/governancelist.h b/src/qt/governancelist.h index 4f72946a60aa..be8f97086c7a 100644 --- a/src/qt/governancelist.h +++ b/src/qt/governancelist.h @@ -14,6 +14,7 @@ #include inline constexpr int GOVERNANCELIST_UPDATE_SECONDS = 10; +inline constexpr float CYCLE_IN_DAYS = 30.29; namespace Ui { class GovernanceList; @@ -26,11 +27,13 @@ class Proposal : public QObject { private: Q_OBJECT + friend class ProposalTests; const CGovernanceObject* pGovObj; QString m_title; QDateTime m_startDate; QDateTime m_endDate; + QDateTime m_currentDate; float m_paymentAmount; QString m_url; @@ -40,11 +43,16 @@ class Proposal : public QObject QString hash() const; QDateTime startDate() const; QDateTime endDate() const; + QDateTime currentDate() const; + int paymentRemaining() const; float paymentAmount() const; QString url() const; bool isActive() const; QString votingStatus(int nAbsVoteReq) const; int GetAbsoluteYesCount() const; + int GetYesCount() const; + int GetNoCount() const; + int GetAbstainCount() const; void openUrl() const; @@ -62,13 +70,16 @@ class ProposalModel : public QAbstractTableModel QAbstractTableModel(parent){}; enum Column : int { - HASH = 0, - TITLE, - START_DATE, - END_DATE, + TITLE = 0, + PAYMENTS_REMAINING, PAYMENT_AMOUNT, IS_ACTIVE, + YES_COUNT, + NO_COUNT, + ABSTEIN_COUNT, + ABSOLUTE_YES, VOTING_STATUS, + URL, _COUNT // for internal use only }; @@ -110,6 +121,7 @@ private Q_SLOTS: void updateProposalCount() const; void showProposalContextMenu(const QPoint& pos); void showAdditionalInfo(const QModelIndex& index); + void openUrl(); }; diff --git a/src/qt/test/proposaltests.cpp b/src/qt/test/proposaltests.cpp new file mode 100644 index 000000000000..11dbc15d5a30 --- /dev/null +++ b/src/qt/test/proposaltests.cpp @@ -0,0 +1,21 @@ +#include "proposaltests.h" +#include + +#include + +void ProposalTests::proposaltests() +{ + CGovernanceObject *pGovObj = new CGovernanceObject; + Proposal proposal(pGovObj); + proposal.m_currentDate = QDateTime(QDate(2022, 1, 2), QTime(12, 00)); + proposal.m_endDate = QDateTime(QDate(2022, 2, 9), QTime(12, 00)); + QVERIFY( proposal.paymentRemaining() == 1) ; + + proposal.m_currentDate = QDateTime(QDate(2022, 1, 2), QTime(12, 00)); + proposal.m_endDate = QDateTime(QDate(2022, 3, 5), QTime(12, 00)); + QVERIFY( proposal.paymentRemaining() == 2); + + proposal.m_currentDate = QDateTime(QDate(2022, 1, 2), QTime(12, 00)); + proposal.m_endDate = QDateTime(QDate(2022, 4, 10), QTime(12, 00)); + QVERIFY( proposal.paymentRemaining() == 3); +} diff --git a/src/qt/test/proposaltests.h b/src/qt/test/proposaltests.h new file mode 100644 index 000000000000..de45e99bb350 --- /dev/null +++ b/src/qt/test/proposaltests.h @@ -0,0 +1,15 @@ +#ifndef PROPOSALTESTS_H +#define PROPOSALTESTS_H + +#include +#include + +class ProposalTests : public QObject +{ + Q_OBJECT + +private Q_SLOTS: + void proposaltests(); +}; + +#endif // PROPOSALTESTS_H diff --git a/src/qt/test/test_main.cpp b/src/qt/test/test_main.cpp index c0482d6758a9..81bbf143947d 100644 --- a/src/qt/test/test_main.cpp +++ b/src/qt/test/test_main.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #ifdef ENABLE_WALLET @@ -107,5 +108,10 @@ int main(int argc, char *argv[]) TrafficGraphDataTests test7; if (QTest::qExec(&test7) != 0) fInvalid = true; + + ProposalTests test8; + if (QTest::qExec(&test8) != 0) + fInvalid = true; + return fInvalid; }