From 833b8d159deffa76bed8df4411b2bdabe8dae45a Mon Sep 17 00:00:00 2001 From: furszy Date: Wed, 12 May 2021 11:17:14 -0300 Subject: [PATCH 01/59] governancemodel introduction --- src/budget/budgetmanager.h | 3 ++ src/qt/CMakeLists.txt | 1 + src/qt/pivx/governancemodel.cpp | 37 ++++++++++++++++++++ src/qt/pivx/governancemodel.h | 61 +++++++++++++++++++++++++++++++++ 4 files changed, 102 insertions(+) create mode 100644 src/qt/pivx/governancemodel.cpp create mode 100644 src/qt/pivx/governancemodel.h diff --git a/src/budget/budgetmanager.h b/src/budget/budgetmanager.h index e36683554861..e3c836d85b8b 100644 --- a/src/budget/budgetmanager.h +++ b/src/budget/budgetmanager.h @@ -119,6 +119,9 @@ class CBudgetManager : public CValidationInterface // finds the proposal with the given name, with highest net yes count. const CBudgetProposal* FindProposalByName(const std::string& strProposalName) const; + // Returns true if there is at least one proposal stored. + bool HasAnyProposal() const { return WITH_LOCK(cs_proposals, return !mapProposals.empty()); } + static CAmount GetTotalBudget(int nHeight); std::vector GetBudget(); // Get all the budget proposals sorted by votes (highest to lowest) diff --git a/src/qt/CMakeLists.txt b/src/qt/CMakeLists.txt index ff44c92b3c83..bebea3b4772d 100644 --- a/src/qt/CMakeLists.txt +++ b/src/qt/CMakeLists.txt @@ -159,6 +159,7 @@ SET(QT_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/pivx/settings/settingswidget.cpp ${CMAKE_CURRENT_SOURCE_DIR}/pivx/welcomecontentwidget.cpp ${CMAKE_CURRENT_SOURCE_DIR}/pivx/splash.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/pivx/governancemodel.cpp ) execute_process( diff --git a/src/qt/pivx/governancemodel.cpp b/src/qt/pivx/governancemodel.cpp new file mode 100644 index 000000000000..96c74b3a026e --- /dev/null +++ b/src/qt/pivx/governancemodel.cpp @@ -0,0 +1,37 @@ +#include "qt/pivx/governancemodel.h" + +#include "budget/budgetmanager.h" +#include "destination_io.h" +#include "script/standard.h" + +GovernanceModel::GovernanceModel(ClientModel* _clientModel) : clientModel(_clientModel) +{ +} + +std::list GovernanceModel::getProposals() +{ + if (!clientModel) return {}; + std::list ret; + for (const auto& prop : g_budgetman.GetAllProposals()) { + CTxDestination recipient; + ExtractDestination(prop->GetPayee(), recipient); + ret.emplace_back( + prop->GetHash(), + prop->GetName(), + prop->GetURL(), + prop->GetYeas(), + prop->GetNays(), + Standard::EncodeDestination(recipient), + prop->GetAmount(), + prop->GetTotalPaymentCount(), + prop->GetRemainingPaymentCount(clientModel->getLastBlockProcessedHeight()) + ); + } + return ret; +} + +bool GovernanceModel::hasProposals() +{ + return g_budgetman.HasAnyProposal(); +} + diff --git a/src/qt/pivx/governancemodel.h b/src/qt/pivx/governancemodel.h new file mode 100644 index 000000000000..277654555922 --- /dev/null +++ b/src/qt/pivx/governancemodel.h @@ -0,0 +1,61 @@ +#ifndef GOVERNANCEMODEL_H +#define GOVERNANCEMODEL_H + +#include "uint256.h" +#include "clientmodel.h" + +#include +#include +#include +#include + +struct ProposalInfo { +public: + /** Proposal hash */ + uint256 id; + std::string name; + std::string url; + int votesYes; + int votesNo; + /** Payment script destination */ + std::string recipientAdd; + /** Amount of PIV paid per month */ + CAmount amount; + /** Amount of times that the proposal will be paid */ + int totalPayments; + /** Amount of times that the proposal was paid already */ + int paidPayments; + + ProposalInfo() {} + explicit ProposalInfo(const uint256& _id, std::string _name, std::string _url, + int _votesYes, int _votesNo, std::string _recipientAdd, + CAmount _amount, int _totalPayments, int _paidPayments) : + id(_id), name(std::move(_name)), url(std::move(_url)), votesYes(_votesYes), votesNo(_votesNo), + recipientAdd(std::move(_recipientAdd)), amount(_amount), totalPayments(_totalPayments), + paidPayments(_paidPayments) {} + + bool operator==(const ProposalInfo& prop2) const + { + return id == prop2.id; + } +}; + +class GovernanceModel +{ + +public: + explicit GovernanceModel(ClientModel* _clientModel); + + // Return proposals ordered by net votes + std::list getProposals(); + // Returns true if there is at least one proposal cached + bool hasProposals(); + // Whether a visual refresh is needed + bool isRefreshNeeded() { return refreshNeeded; } + +private: + ClientModel* clientModel{nullptr}; + std::atomic refreshNeeded{false}; +}; + +#endif // GOVERNANCEMODEL_H From 6a2ffa99082b3715b7cc44919c9881b2e050d43e Mon Sep 17 00:00:00 2001 From: furszy Date: Sat, 15 May 2021 12:16:03 -0300 Subject: [PATCH 02/59] governancemodel: add proposal status --- src/budget/budgetproposal.h | 5 +++++ src/qt/pivx/governancemodel.cpp | 27 ++++++++++++++++++++++++--- src/qt/pivx/governancemodel.h | 18 ++++++++++++++++-- 3 files changed, 45 insertions(+), 5 deletions(-) diff --git a/src/budget/budgetproposal.h b/src/budget/budgetproposal.h index bb1591bda300..eeef33a9b2b3 100644 --- a/src/budget/budgetproposal.h +++ b/src/budget/budgetproposal.h @@ -128,6 +128,11 @@ class CBudgetProposal CDataStream GetBroadcast() const; void Relay(); + inline bool operator==(const CBudgetProposal& other) const + { + return GetHash() == other.GetHash(); + } + // compare proposals by proposal hash inline bool operator>(const CBudgetProposal& other) const { diff --git a/src/qt/pivx/governancemodel.cpp b/src/qt/pivx/governancemodel.cpp index 96c74b3a026e..67653f086b50 100644 --- a/src/qt/pivx/governancemodel.cpp +++ b/src/qt/pivx/governancemodel.cpp @@ -1,9 +1,15 @@ +// Copyright (c) 2021 The PIVX developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or https://www.opensource.org/licenses/mit-license.php. + #include "qt/pivx/governancemodel.h" #include "budget/budgetmanager.h" #include "destination_io.h" #include "script/standard.h" +#include + GovernanceModel::GovernanceModel(ClientModel* _clientModel) : clientModel(_clientModel) { } @@ -12,19 +18,34 @@ std::list GovernanceModel::getProposals() { if (!clientModel) return {}; std::list ret; + std::vector budget = g_budgetman.GetBudget(); for (const auto& prop : g_budgetman.GetAllProposals()) { CTxDestination recipient; ExtractDestination(prop->GetPayee(), recipient); + + // Calculate status + int votesYes = prop->GetYeas(); + int votesNo = prop->GetNays(); + ProposalInfo::Status status; + if (std::find(budget.begin(), budget.end(), *prop) != budget.end()) { + status = ProposalInfo::PASSING; + } else if (votesYes > votesNo){ + status = ProposalInfo::PASSING_NOT_FUNDED; + } else { + status = ProposalInfo::NOT_PASSING; + } + ret.emplace_back( prop->GetHash(), prop->GetName(), prop->GetURL(), - prop->GetYeas(), - prop->GetNays(), + votesYes, + votesNo, Standard::EncodeDestination(recipient), prop->GetAmount(), prop->GetTotalPaymentCount(), - prop->GetRemainingPaymentCount(clientModel->getLastBlockProcessedHeight()) + prop->GetRemainingPaymentCount(clientModel->getLastBlockProcessedHeight()), + status ); } return ret; diff --git a/src/qt/pivx/governancemodel.h b/src/qt/pivx/governancemodel.h index 277654555922..a0a543bcc51d 100644 --- a/src/qt/pivx/governancemodel.h +++ b/src/qt/pivx/governancemodel.h @@ -1,3 +1,7 @@ +// Copyright (c) 2021 The PIVX developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or https://www.opensource.org/licenses/mit-license.php. + #ifndef GOVERNANCEMODEL_H #define GOVERNANCEMODEL_H @@ -11,6 +15,13 @@ struct ProposalInfo { public: + enum Status { + WAITING_FOR_APPROVAL, + PASSING, + PASSING_NOT_FUNDED, + NOT_PASSING + }; + /** Proposal hash */ uint256 id; std::string name; @@ -25,14 +36,17 @@ struct ProposalInfo { int totalPayments; /** Amount of times that the proposal was paid already */ int paidPayments; + /** Proposal state */ + Status status; ProposalInfo() {} explicit ProposalInfo(const uint256& _id, std::string _name, std::string _url, int _votesYes, int _votesNo, std::string _recipientAdd, - CAmount _amount, int _totalPayments, int _paidPayments) : + CAmount _amount, int _totalPayments, int _paidPayments, + Status _status) : id(_id), name(std::move(_name)), url(std::move(_url)), votesYes(_votesYes), votesNo(_votesNo), recipientAdd(std::move(_recipientAdd)), amount(_amount), totalPayments(_totalPayments), - paidPayments(_paidPayments) {} + paidPayments(_paidPayments), status(_status) {} bool operator==(const ProposalInfo& prop2) const { From ce00bcdf4dff4a693ec5081c83e49ce1c7c12660 Mon Sep 17 00:00:00 2001 From: furszy Date: Thu, 20 May 2021 10:58:23 -0300 Subject: [PATCH 03/59] GUI: Main governance screen introduced and navigation connected :). --- src/Makefile.qt.include | 21 +- src/qt/CMakeLists.txt | 2 + src/qt/pivx.qrc | 10 + src/qt/pivx/forms/governancewidget.ui | 661 ++++++++++++++++++ src/qt/pivx/forms/navmenuwidget.ui | 25 + src/qt/pivx/forms/proposalcard.ui | 369 ++++++++++ src/qt/pivx/governancemodel.h | 6 +- src/qt/pivx/governancewidget.cpp | 213 ++++++ src/qt/pivx/governancewidget.h | 75 ++ src/qt/pivx/navmenuwidget.cpp | 14 +- src/qt/pivx/navmenuwidget.h | 1 + src/qt/pivx/pivxgui.cpp | 10 + src/qt/pivx/pivxgui.h | 3 + src/qt/pivx/proposalcard.cpp | 74 ++ src/qt/pivx/proposalcard.h | 45 ++ src/qt/pivx/res/css/style_light.css | 226 ++++++ src/qt/pivx/res/img/ic-check-vote-active.svg | 5 + src/qt/pivx/res/img/ic-check-vote.svg | 4 + src/qt/pivx/res/img/ic-filter.svg | 5 + src/qt/pivx/res/img/ic-link-hover.svg | 4 + src/qt/pivx/res/img/ic-link.svg | 4 + .../pivx/res/img/ic-nav-governance-active.svg | 10 + .../pivx/res/img/ic-nav-governance-hover.svg | 10 + src/qt/pivx/res/img/ic-nav-governance.svg | 10 + src/qt/pivx/res/img/ic-time.svg | 4 + src/qt/pivx/res/img/img-empty-governance.svg | 14 + 26 files changed, 1819 insertions(+), 6 deletions(-) create mode 100644 src/qt/pivx/forms/governancewidget.ui create mode 100644 src/qt/pivx/forms/proposalcard.ui create mode 100644 src/qt/pivx/governancewidget.cpp create mode 100644 src/qt/pivx/governancewidget.h create mode 100644 src/qt/pivx/proposalcard.cpp create mode 100644 src/qt/pivx/proposalcard.h create mode 100644 src/qt/pivx/res/img/ic-check-vote-active.svg create mode 100644 src/qt/pivx/res/img/ic-check-vote.svg create mode 100644 src/qt/pivx/res/img/ic-filter.svg create mode 100644 src/qt/pivx/res/img/ic-link-hover.svg create mode 100644 src/qt/pivx/res/img/ic-link.svg create mode 100644 src/qt/pivx/res/img/ic-nav-governance-active.svg create mode 100644 src/qt/pivx/res/img/ic-nav-governance-hover.svg create mode 100644 src/qt/pivx/res/img/ic-nav-governance.svg create mode 100644 src/qt/pivx/res/img/ic-time.svg create mode 100644 src/qt/pivx/res/img/img-empty-governance.svg diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include index 1128eec933e6..9d0f1660e86c 100644 --- a/src/Makefile.qt.include +++ b/src/Makefile.qt.include @@ -50,6 +50,8 @@ QT_FORMS_UI = \ qt/pivx/forms/addresseswidget.ui \ qt/pivx/forms/defaultdialog.ui \ qt/pivx/forms/coldstakingwidget.ui \ + qt/pivx/forms/proposalcard.ui \ + qt/pivx/forms/governancewidget.ui \ qt/pivx/settings/forms/settingsbackupwallet.ui \ qt/pivx/settings/forms/settingsexportcsv.ui \ qt/pivx/settings/forms/settingsbittoolwidget.ui \ @@ -136,6 +138,8 @@ QT_MOC_CPP = \ qt/pivx/moc_addresseswidget.cpp \ qt/pivx/moc_defaultdialog.cpp \ qt/pivx/moc_coldstakingwidget.cpp \ + qt/pivx/moc_proposalcard.cpp \ + qt/pivx/moc_governancewidget.cpp \ qt/pivx/settings/moc_settingsbackupwallet.cpp \ qt/pivx/settings/moc_settingsexportcsv.cpp \ qt/pivx/settings/moc_settingsbittoolwidget.cpp \ @@ -249,6 +253,9 @@ BITCOIN_QT_H = \ qt/pivx/addresseswidget.h \ qt/pivx/defaultdialog.h \ qt/pivx/coldstakingwidget.h \ + qt/pivx/governancemodel.h \ + qt/pivx/proposalcard.h \ + qt/pivx/governancewidget.h \ qt/pivx/settings/settingsbackupwallet.h \ qt/pivx/settings/settingsexportcsv.h \ qt/pivx/settings/settingsbittoolwidget.h \ @@ -485,7 +492,16 @@ RES_ICONS = \ qt/pivx/res/img/ic-information.svg \ qt/pivx/res/img/ic-information-hover.svg \ qt/pivx/res/img/ani-loading-dark.gif \ - qt/pivx/res/img/ani-loading.gif + qt/pivx/res/img/ani-loading.gif \ + qt/pivx/res/img/ic-check-vote.svg \ + qt/pivx/res/img/ic-check-vote-active.svg \ + qt/pivx/res/img/ic-filter.svg \ + qt/pivx/res/img/ic-link.svg \ + qt/pivx/res/img/ic-nav-governance.svg \ + qt/pivx/res/img/ic-nav-governance-active.svg \ + qt/pivx/res/img/ic-nav-governance-hover.svg \ + qt/pivx/res/img/ic-time.svg \ + qt/pivx/res/img/img-empty-governance.svg BITCOIN_QT_BASE_CPP = \ qt/bantablemodel.cpp \ @@ -569,6 +585,9 @@ BITCOIN_QT_WALLET_CPP = \ qt/pivx/addresseswidget.cpp \ qt/pivx/defaultdialog.cpp \ qt/pivx/coldstakingwidget.cpp \ + qt/pivx/governancemodel.cpp \ + qt/pivx/proposalcard.cpp \ + qt/pivx/governancewidget.cpp \ qt/pivx/settings/settingsbackupwallet.cpp \ qt/pivx/settings/settingsexportcsv.cpp \ qt/pivx/settings/settingsbittoolwidget.cpp \ diff --git a/src/qt/CMakeLists.txt b/src/qt/CMakeLists.txt index bebea3b4772d..ca4b2e4d8f0f 100644 --- a/src/qt/CMakeLists.txt +++ b/src/qt/CMakeLists.txt @@ -145,6 +145,8 @@ SET(QT_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/pivx/addresseswidget.cpp ${CMAKE_CURRENT_SOURCE_DIR}/pivx/defaultdialog.cpp ${CMAKE_CURRENT_SOURCE_DIR}/pivx/coldstakingwidget.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/pivx/proposalcard.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/pivx/governancewidget.cpp ${CMAKE_CURRENT_SOURCE_DIR}/pivx/settings/settingsbackupwallet.cpp ${CMAKE_CURRENT_SOURCE_DIR}/pivx/settings/settingsbittoolwidget.cpp ${CMAKE_CURRENT_SOURCE_DIR}/pivx/settings/settingsconsolewidget.cpp diff --git a/src/qt/pivx.qrc b/src/qt/pivx.qrc index 7d6ccdfef7d3..d18fbd19cf4e 100644 --- a/src/qt/pivx.qrc +++ b/src/qt/pivx.qrc @@ -223,5 +223,15 @@ pivx/res/img/ic-check-cold-staking-enabled.svg pivx/res/img/ic-information.svg pivx/res/img/ic-information-hover.svg + pivx/res/img/ic-check-vote.svg + pivx/res/img/ic-check-vote-active.svg + pivx/res/img/ic-filter.svg + pivx/res/img/ic-link.svg + pivx/res/img/ic-link-hover.svg + pivx/res/img/ic-nav-governance.svg + pivx/res/img/ic-nav-governance-active.svg + pivx/res/img/ic-nav-governance-hover.svg + pivx/res/img/ic-time.svg + pivx/res/img/img-empty-governance.svg diff --git a/src/qt/pivx/forms/governancewidget.ui b/src/qt/pivx/forms/governancewidget.ui new file mode 100644 index 000000000000..c377698df016 --- /dev/null +++ b/src/qt/pivx/forms/governancewidget.ui @@ -0,0 +1,661 @@ + + + governancewidget + + + + 0 + 0 + 629 + 406 + + + + + 0 + 150 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 0 + + + 20 + + + + + 5 + + + + + TextLabel + + + + + + + TextLabel + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + 0 + + + 0 + + + + + + 150 + 0 + + + + + + + + + + + + 0 + 0 + + + + + 0 + + + 0 + + + + + Filter + + + + + + + + + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + Qt::ScrollBarAlwaysOff + + + true + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + 0 + 0 + 417 + 175 + + + + + + + + + + + + + 0 + 0 + + + + + 0 + 100 + + + + margin-bottom:12px; + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 12 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 100 + 100 + + + + + 100 + 100 + + + + + + + + 100 + 100 + + + + + + + + + + + No active proposals yet + + + Qt::AlignCenter + + + + + + + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 6 + + + + + + 0 + 60 + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 6 + + + + + + + + + 0 + 200 + + + + + 0 + + + 0 + + + 0 + + + + + Budget Distribution + + + + + + + Funds accessible for the proposals' implementation + + + true + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 6 + + + + + + + + + 3 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Available + + + + + + + 6,000 PIV + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 6 + + + + + + + + + 0 + 1 + + + + + 16777215 + 1 + + + + background-color:#bababa; + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 20 + + + + + + + + + 3 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Allocated + + + + + + + 37,000 PIV + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 6 + + + + + + + + + 0 + 1 + + + + + 16777215 + 1 + + + + background-color:#bababa; + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + 4 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 26 + 26 + + + + + 26 + 26 + + + + + + + + 26 + 26 + + + + + + + + Next superblock in 7,544 blocks + + + true + + + + + + + + + + + + + + + + + + + OptionButton + QWidget +
qt/pivx/optionbutton.h
+ 1 +
+
+ + +
diff --git a/src/qt/pivx/forms/navmenuwidget.ui b/src/qt/pivx/forms/navmenuwidget.ui index df91ea4b53a8..5b00d05c164d 100644 --- a/src/qt/pivx/forms/navmenuwidget.ui +++ b/src/qt/pivx/forms/navmenuwidget.ui @@ -259,6 +259,31 @@ background-color:transparent; + + + + + 100 + 80 + + + + DAO + + + + 32 + 32 + + + + true + + + true + + + diff --git a/src/qt/pivx/forms/proposalcard.ui b/src/qt/pivx/forms/proposalcard.ui new file mode 100644 index 000000000000..6d5bef8c45df --- /dev/null +++ b/src/qt/pivx/forms/proposalcard.ui @@ -0,0 +1,369 @@ + + + ProposalCard + + + + 0 + 0 + 250 + 200 + + + + + 0 + 0 + + + + + 250 + 200 + + + + + 320 + 200 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 0 + 200 + + + + + + + + 12 + + + 12 + + + 12 + + + 12 + + + + + + + Proposal Name + + + + + + + + 0 + 0 + + + + PointingHandCursor + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 5 + + + QLayout::SetMinimumSize + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 5,000 PIV + + + + + + + 2 months passed of 4 + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + margin-top:16; + + + Passing + + + + + + + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 0 + 25 + + + + + NoAntialias + + + + 24 + + + false + + + + + + + + 0 + 0 + + + + + 0 + + + 0 + + + 0 + + + + + color:#4D4D4D; + + + 24% No + + + + + + + Qt::Horizontal + + + + 93 + 20 + + + + + + + + color:#5C4B7D; + + + 76% Yes + + + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 80 + 0 + + + + Qt::NoFocus + + + + + + Vote + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + diff --git a/src/qt/pivx/governancemodel.h b/src/qt/pivx/governancemodel.h index a0a543bcc51d..ff06e1246bc3 100644 --- a/src/qt/pivx/governancemodel.h +++ b/src/qt/pivx/governancemodel.h @@ -35,18 +35,18 @@ struct ProposalInfo { /** Amount of times that the proposal will be paid */ int totalPayments; /** Amount of times that the proposal was paid already */ - int paidPayments; + int remainingPayments; /** Proposal state */ Status status; ProposalInfo() {} explicit ProposalInfo(const uint256& _id, std::string _name, std::string _url, int _votesYes, int _votesNo, std::string _recipientAdd, - CAmount _amount, int _totalPayments, int _paidPayments, + CAmount _amount, int _totalPayments, int _remainingPayments, Status _status) : id(_id), name(std::move(_name)), url(std::move(_url)), votesYes(_votesYes), votesNo(_votesNo), recipientAdd(std::move(_recipientAdd)), amount(_amount), totalPayments(_totalPayments), - paidPayments(_paidPayments), status(_status) {} + remainingPayments(_remainingPayments), status(_status) {} bool operator==(const ProposalInfo& prop2) const { diff --git a/src/qt/pivx/governancewidget.cpp b/src/qt/pivx/governancewidget.cpp new file mode 100644 index 000000000000..e20847f38991 --- /dev/null +++ b/src/qt/pivx/governancewidget.cpp @@ -0,0 +1,213 @@ +#include "qt/pivx/governancewidget.h" +#include "qt/pivx/forms/ui_governancewidget.h" + +#include "qt/pivx/governancemodel.h" +#include "qt/pivx/qtutils.h" + +#include +#include + +GovernanceWidget::GovernanceWidget(PIVXGUI* parent) : + PWidget(parent), + ui(new Ui::governancewidget) +{ + ui->setupUi(this); + this->setStyleSheet(parent->styleSheet()); + + setCssProperty(ui->left, "container"); + ui->left->setContentsMargins(0,20,0,0); + setCssProperty(ui->right, "container-right"); + ui->right->setContentsMargins(20,10,20,20); + setCssProperty(ui->scrollArea, "container"); + + /* Title */ + ui->labelTitle->setText("Governance"); + setCssProperty(ui->labelTitle, "text-title-screen"); + ui->labelSubtitle1->setText("View, follow, vote and submit network budget proposals.\nBe part of the DAO."); + setCssProperty(ui->labelSubtitle1, "text-subtitle"); + setCssProperty(ui->pushImgEmpty, "img-empty-governance"); + setCssProperty(ui->labelEmpty, "text-empty"); + + // Combo box sort + setCssProperty(ui->comboBoxSort, "btn-combo"); + ui->comboBoxSort->setEditable(true); + SortEdit* lineEdit = new SortEdit(ui->comboBoxSort); + lineEdit->setReadOnly(true); + lineEdit->setAlignment(Qt::AlignRight); + QFont font; + font.setPointSize(14); + lineEdit->setFont(font); + ui->comboBoxSort->setLineEdit(lineEdit); + + QStandardItemModel *model = new QStandardItemModel(this); + Delegate *delegate = new Delegate(this); + QList values; + values.append("Date"); + values.append("Value"); + values.append("Name"); + for (int n = 0; n < values.size(); n++) { + model->appendRow(new QStandardItem(tr("Sort by: %1").arg(values.at(n)))); + } + delegate->setValues(values); + ui->comboBoxSort->setModel(model); + ui->comboBoxSort->setItemDelegate(delegate); + + // Filter + + ui->btnFilter->setText("Filter"); + ui->btnFilter->setProperty("cssClass", "btn-secundary-filter"); + + // Budget + ui->labelBudget->setText("Budget Distribution"); + setCssProperty(ui->labelBudget, "btn-title-grey"); + setCssProperty(ui->labelBudgetSubTitle, "text-subtitle"); + setCssProperty(ui->labelAvailableTitle, "label-budget-text"); + setCssProperty(ui->labelAllocatedTitle, "label-budget-text"); + setCssProperty(ui->labelAvailableAmount, "label-budget-amount"); + setCssProperty(ui->labelAllocatedAmount, "label-budget-amount-allocated"); + setCssProperty(ui->iconClock , "ic-time"); + setCssProperty(ui->labelNextSuperblock, "label-budget-text"); + ui->labelNextSuperblock->setText("Next superblock in ~4 days.\n7,544 blocks to go."); // Update superblock data + + // Create proposal + ui->btnCreateProposal->setTitleClassAndText("btn-title-grey", "Create Proposal"); + ui->btnCreateProposal->setSubTitleClassAndText("text-subtitle", "Prepare and submit a new proposal."); + connect(ui->btnCreateProposal, SIGNAL(clicked()), this, SLOT(onCreatePropClicked())); + ui->emptyContainer->setVisible(false); + + // Move to update process. + ui->labelAllocatedAmount->setText("37,394.912 PIV"); + ui->labelAvailableAmount->setText("5,394.912 PIV"); +} + +GovernanceWidget::~GovernanceWidget() +{ + delete ui; + delete governanceModel; +} + +void GovernanceWidget::loadClientModel() { + governanceModel = new GovernanceModel(clientModel); +} + +void GovernanceWidget::showEvent(QShowEvent *event) +{ + tryGridRefresh(); +} + +void GovernanceWidget::resizeEvent(QResizeEvent *event) +{ + if (!isVisible()) return; + tryGridRefresh(); +} + +void GovernanceWidget::tryGridRefresh() +{ + int _propsPerRow = calculateColumnsPerRow(); + if (_propsPerRow != propsPerRow) { + propsPerRow = _propsPerRow; + refreshCardsGrid(true); + } +} + +static void setCardShadow(QWidget* edit) +{ + QGraphicsDropShadowEffect* shadowEffect = new QGraphicsDropShadowEffect(); + shadowEffect->setColor(QColor(77, 77, 77, 30)); + shadowEffect->setXOffset(0); + shadowEffect->setYOffset(4); + shadowEffect->setBlurRadius(6); + edit->setGraphicsEffect(shadowEffect); +} + +ProposalCard* GovernanceWidget::newCard() +{ + ProposalCard* propCard = new ProposalCard(ui->scrollAreaWidgetContents); + setCardShadow(propCard); + return propCard; +} + +void GovernanceWidget::showEmptyScreen(bool show) +{ + if (ui->emptyContainer->isVisible() != show) { + ui->emptyContainer->setVisible(show); + ui->mainContainer->setVisible(!show); + } +} + +void GovernanceWidget::refreshCardsGrid(bool forceRefresh) +{ + if (!governanceModel) return; + if (!governanceModel->hasProposals()) { + showEmptyScreen(true); + return; + } + + showEmptyScreen(false); + if (!gridLayout) { + gridLayout = new QGridLayout(); + gridLayout->setAlignment(Qt::AlignTop); + gridLayout->setHorizontalSpacing(16); + gridLayout->setVerticalSpacing(16); + ui->scrollArea->setWidgetResizable(true); + ui->scrollAreaWidgetContents->setLayout(gridLayout); + } + + // Refresh grid only if needed + if (!(forceRefresh || governanceModel->isRefreshNeeded())) return; + + std::list props = governanceModel->getProposals(); + + // Start marking all the cards + for (ProposalCard* card : cards) { + card->setNeedsUpdate(true); + } + + // Refresh the card if exists or create a new one. + int column = 0; + int row = 0; + for (const auto& prop : props) { + QLayoutItem* item = gridLayout->itemAtPosition(row, column); + ProposalCard* card{nullptr}; + if (item) { + card = dynamic_cast(item->widget()); + card->setNeedsUpdate(false); + } else { + card = newCard(); + cards.emplace_back(card); + gridLayout->addWidget(card, row, column, 1, 1); + } + card->setProposal(prop); + column++; + if (column == propsPerRow) { + column = 0; + row++; + } + } + + // Now delete the not longer needed cards + auto it = cards.begin(); + while (it != cards.end()) { + ProposalCard* card = (*it); + if (!card->isUpdateNeeded()) { + it++; + continue; + } + gridLayout->takeAt(gridLayout->indexOf(card)); + it = cards.erase(it); + delete card; + } +} + +int GovernanceWidget::calculateColumnsPerRow() +{ + int widgetWidth = ui->left->width(); + if (widgetWidth < 785) { + return 2; + } else if (widgetWidth < 1100){ + return 3; + } else { + return 4; // max amount of cards + } +} + diff --git a/src/qt/pivx/governancewidget.h b/src/qt/pivx/governancewidget.h new file mode 100644 index 000000000000..6d2959716752 --- /dev/null +++ b/src/qt/pivx/governancewidget.h @@ -0,0 +1,75 @@ +// Copyright (c) 2021 The PIVX developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or https://www.opensource.org/licenses/mit-license.php. + +#ifndef GOVERNANCEWIDGET_H +#define GOVERNANCEWIDGET_H + +#include "qt/pivx/pwidget.h" +#include "qt/pivx/proposalcard.h" + +#include +#include +#include +#include +#include + +namespace Ui { +class governancewidget; +} + +class PIVXGUI; +class GovernanceModel; + +class Delegate : public QStyledItemDelegate +{ + Q_OBJECT +public: + explicit Delegate(QObject *parent = nullptr) : + QStyledItemDelegate(parent) {} + + void setValues(QList _values) { + values = _values; + } + + void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override { + if (!index.isValid()) + return; + + QStyleOptionViewItem opt = option; + QStyle *style = option.widget ? option.widget->style() : QApplication::style(); + opt.text = values.value(index.row()); + style->drawControl(QStyle::CE_ItemViewItem, &opt, painter, option.widget); + } + +private: + QList values; +}; + +class GovernanceWidget : public PWidget +{ + Q_OBJECT + +public: + explicit GovernanceWidget(PIVXGUI* parent); + ~GovernanceWidget() override; + + void showEvent(QShowEvent *event) override; + void resizeEvent(QResizeEvent *event) override; + void loadClientModel() override; + +private: + Ui::governancewidget *ui; + GovernanceModel* governanceModel{nullptr}; + QGridLayout* gridLayout{nullptr}; // cards + std::vector cards; + int propsPerRow = 0; + + void showEmptyScreen(bool show); + void tryGridRefresh(); + ProposalCard* newCard(); + void refreshCardsGrid(bool forceRefresh); + int calculateColumnsPerRow(); +}; + +#endif // GOVERNANCEWIDGET_H diff --git a/src/qt/pivx/navmenuwidget.cpp b/src/qt/pivx/navmenuwidget.cpp index d25c0422fa2a..2a7ab8e68e91 100644 --- a/src/qt/pivx/navmenuwidget.cpp +++ b/src/qt/pivx/navmenuwidget.cpp @@ -38,7 +38,9 @@ NavMenuWidget::NavMenuWidget(PIVXGUI *mainWindow, QWidget *parent) : ui->btnColdStaking->setToolButtonStyle(Qt::ToolButtonTextUnderIcon); ui->btnSettings->setProperty("name", "settings"); ui->btnSettings->setToolButtonStyle(Qt::ToolButtonTextUnderIcon); - btns = {ui->btnDashboard, ui->btnSend, ui->btnReceive, ui->btnAddress, ui->btnMaster, ui->btnColdStaking, ui->btnSettings, ui->btnColdStaking}; + ui->btnGovernance->setProperty("name", "governance"); + ui->btnGovernance->setToolButtonStyle(Qt::ToolButtonTextUnderIcon); + btns = {ui->btnDashboard, ui->btnSend, ui->btnReceive, ui->btnAddress, ui->btnMaster, ui->btnColdStaking, ui->btnSettings, ui->btnGovernance}; onNavSelected(ui->btnDashboard, true); ui->scrollAreaNav->setWidgetResizable(true); @@ -71,6 +73,7 @@ void NavMenuWidget::connectActions() { connect(ui->btnSettings, &QPushButton::clicked, this, &NavMenuWidget::onSettingsClicked); connect(ui->btnReceive, &QPushButton::clicked, this, &NavMenuWidget::onReceiveClicked); connect(ui->btnColdStaking, &QPushButton::clicked, this, &NavMenuWidget::onColdStakingClicked); + connect(ui->btnGovernance, &QPushButton::clicked, this, &NavMenuWidget::onGovClicked); ui->btnDashboard->setShortcut(QKeySequence(SHORT_KEY + Qt::Key_1)); ui->btnSend->setShortcut(QKeySequence(SHORT_KEY + Qt::Key_2)); @@ -106,6 +109,12 @@ void NavMenuWidget::onColdStakingClicked() { onNavSelected(ui->btnColdStaking); } +void NavMenuWidget::onGovClicked() +{ + window->goToGovernance(); + onNavSelected(ui->btnGovernance); +} + void NavMenuWidget::onSettingsClicked(){ window->goToSettings(); onNavSelected(ui->btnSettings); @@ -153,7 +162,8 @@ void NavMenuWidget::updateButtonStyles(){ ui->btnMaster, ui->btnSettings, ui->btnReceive, - ui->btnColdStaking + ui->btnColdStaking, + ui->btnGovernance }); } diff --git a/src/qt/pivx/navmenuwidget.h b/src/qt/pivx/navmenuwidget.h index 6021dd8d0fea..ff0763c4cac8 100644 --- a/src/qt/pivx/navmenuwidget.h +++ b/src/qt/pivx/navmenuwidget.h @@ -35,6 +35,7 @@ private Q_SLOTS: void onAddressClicked(); void onMasterNodesClicked(); void onColdStakingClicked(); + void onGovClicked(); void onSettingsClicked(); void onReceiveClicked(); void updateButtonStyles(); diff --git a/src/qt/pivx/pivxgui.cpp b/src/qt/pivx/pivxgui.cpp index dfb703026702..346a9fc92a92 100644 --- a/src/qt/pivx/pivxgui.cpp +++ b/src/qt/pivx/pivxgui.cpp @@ -126,6 +126,7 @@ PIVXGUI::PIVXGUI(const NetworkStyle* networkStyle, QWidget* parent) : addressesWidget = new AddressesWidget(this); masterNodesWidget = new MasterNodesWidget(this); coldStakingWidget = new ColdStakingWidget(this); + governancewidget = new GovernanceWidget(this); settingsWidget = new SettingsWidget(this); // Add to parent @@ -135,6 +136,7 @@ PIVXGUI::PIVXGUI(const NetworkStyle* networkStyle, QWidget* parent) : stackedContainer->addWidget(addressesWidget); stackedContainer->addWidget(masterNodesWidget); stackedContainer->addWidget(coldStakingWidget); + stackedContainer->addWidget(governancewidget); stackedContainer->addWidget(settingsWidget); stackedContainer->setCurrentWidget(dashboard); @@ -202,6 +204,8 @@ void PIVXGUI::connectActions() connect(masterNodesWidget, &MasterNodesWidget::execDialog, this, &PIVXGUI::execDialog); connect(coldStakingWidget, &ColdStakingWidget::showHide, this, &PIVXGUI::showHide); connect(coldStakingWidget, &ColdStakingWidget::execDialog, this, &PIVXGUI::execDialog); + connect(governancewidget, &GovernanceWidget::showHide, this, &PIVXGUI::showHide); + connect(governancewidget, &GovernanceWidget::execDialog, this, &PIVXGUI::execDialog); connect(settingsWidget, &SettingsWidget::execDialog, this, &PIVXGUI::execDialog); } @@ -253,6 +257,7 @@ void PIVXGUI::setClientModel(ClientModel* _clientModel) sendWidget->setClientModel(clientModel); masterNodesWidget->setClientModel(clientModel); settingsWidget->setClientModel(clientModel); + governancewidget->setClientModel(clientModel); // Receive and report messages from client model connect(clientModel, &ClientModel::message, this, &PIVXGUI::message); @@ -508,6 +513,11 @@ void PIVXGUI::goToColdStaking() showTop(coldStakingWidget); } +void PIVXGUI::goToGovernance() +{ + showTop(governancewidget); +} + void PIVXGUI::goToSettings(){ showTop(settingsWidget); } diff --git a/src/qt/pivx/pivxgui.h b/src/qt/pivx/pivxgui.h index e343e9030c6a..75e75236a7ba 100644 --- a/src/qt/pivx/pivxgui.h +++ b/src/qt/pivx/pivxgui.h @@ -21,6 +21,7 @@ #include "qt/pivx/receivewidget.h" #include "qt/pivx/addresseswidget.h" #include "qt/pivx/coldstakingwidget.h" +#include "qt/pivx/governancewidget.h" #include "qt/pivx/masternodeswidget.h" #include "qt/pivx/snackbar.h" #include "qt/pivx/settings/settingswidget.h" @@ -70,6 +71,7 @@ public Q_SLOTS: void goToReceive(); void goToAddresses(); void goToMasterNodes(); + void goToGovernance(); void goToColdStaking(); void goToSettings(); void goToSettingsInfo(); @@ -138,6 +140,7 @@ public Q_SLOTS: AddressesWidget *addressesWidget = nullptr; MasterNodesWidget *masterNodesWidget = nullptr; ColdStakingWidget *coldStakingWidget = nullptr; + GovernanceWidget* governancewidget{nullptr}; SettingsWidget* settingsWidget = nullptr; SnackBar *snackBar = nullptr; diff --git a/src/qt/pivx/proposalcard.cpp b/src/qt/pivx/proposalcard.cpp new file mode 100644 index 000000000000..13a003b31dc0 --- /dev/null +++ b/src/qt/pivx/proposalcard.cpp @@ -0,0 +1,74 @@ +// Copyright (c) 2021 The PIVX developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or https://www.opensource.org/licenses/mit-license.php. + +#include "qt/pivx/proposalcard.h" +#include "qt/pivx/forms/ui_proposalcard.h" + +#include "qt/pivx/qtutils.h" + +ProposalCard::ProposalCard(QWidget *parent) : + QWidget(parent), + ui(new Ui::ProposalCard) +{ + ui->setupUi(this); + setStyleSheet(parent->styleSheet()); + setCssProperty(ui->btnVote, "btn-primary"); + setCssProperty(ui->card, "card-governance"); + setCssProperty(ui->labelPropName, "card-title"); + setCssProperty(ui->labelPropAmount, "card-amount"); + setCssProperty(ui->labelPropMonths, "card-time"); + setCssProperty(ui->labelStatus, "card-status-passing"); + setCssProperty(ui->btnVote, "card-btn-vote"); + setCssProperty(ui->btnLink, "btn-link"); + setCssProperty(ui->containerVotes, "card-progress-box"); + ui->containerVotes->setContentsMargins(1,1,1,1); + ui->containerVotes->layout()->setMargin(0); + ui->votesBar->setMaximum(100); + ui->votesBar->setMinimum(0); + ui->votesBar->setTextVisible(false); + setCssProperty(ui->votesBar, "vote-progress"); + ui->votesBar->setContentsMargins(0,0,0,0); + + connect(ui->btnVote, &QPushButton::clicked, [this](){ Q_EMIT voteClicked(); }); + connect(ui->btnLink, &QPushButton::clicked, this, &ProposalCard::onCopyUrlClicked); +} + + +void ProposalCard::setProposal(const ProposalInfo& _proposalInfo) +{ + proposalInfo = _proposalInfo; + ui->labelPropName->setText(QString::fromStdString(proposalInfo.name)); + ui->labelPropAmount->setText(GUIUtil::formatBalance(proposalInfo.amount)); + ui->labelPropMonths->setText(tr("%1 months passed of %2") + .arg(proposalInfo.totalPayments - proposalInfo.remainingPayments).arg(proposalInfo.totalPayments)); + double totalVotes = _proposalInfo.votesYes + _proposalInfo.votesNo; + double percentageNo = (totalVotes == 0) ? 0 : (_proposalInfo.votesNo / totalVotes) * 100; + double percentageYes = (totalVotes == 0) ? 0 : (_proposalInfo.votesYes / totalVotes) * 100; + ui->votesBar->setValue((int)percentageNo); + ui->labelNo->setText(QString::fromStdString(std::to_string((int)percentageNo) + "% No")); + ui->labelYes->setText(QString::fromStdString("Yes "+ std::to_string((int)percentageYes) + "%")); + + QString cssClassStatus; + if (percentageYes < percentageNo) { + cssClassStatus = "card-status-not-passing"; + ui->labelStatus->setText(tr("Not Passing")); + } else if (percentageYes > percentageNo) { + cssClassStatus = "card-status-passing"; + ui->labelStatus->setText(tr("Passing")); + } else { + cssClassStatus = "card-status-no-votes"; + ui->labelStatus->setText(tr("No Votes")); + } + setCssProperty(ui->labelStatus, cssClassStatus, true); +} + +void ProposalCard::onCopyUrlClicked() +{ + // todo: add copy +} + +ProposalCard::~ProposalCard() +{ + delete ui; +} diff --git a/src/qt/pivx/proposalcard.h b/src/qt/pivx/proposalcard.h new file mode 100644 index 000000000000..3202cdebb41d --- /dev/null +++ b/src/qt/pivx/proposalcard.h @@ -0,0 +1,45 @@ +// Copyright (c) 2021 The PIVX developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or https://www.opensource.org/licenses/mit-license.php. + +#ifndef PROPOSALCARD_H +#define PROPOSALCARD_H + +#include "qt/pivx/governancemodel.h" + +#include +#include +#include + +namespace Ui { +class ProposalCard; +} + +class ProposalCard : public QWidget +{ + Q_OBJECT + +public: + explicit ProposalCard(QWidget *parent = nullptr); + ~ProposalCard(); + + void setProposal(const ProposalInfo& _proposalInfo); + ProposalInfo getProposal() { return proposalInfo; } + + // Update-only functions + void setNeedsUpdate(bool _update) { needsUpdate = _update; } + bool isUpdateNeeded() { return needsUpdate; } + +public Q_SLOTS: + void onCopyUrlClicked(); + +Q_SIGNALS: + void voteClicked(); + +private: + Ui::ProposalCard *ui; + ProposalInfo proposalInfo; + bool needsUpdate{false}; +}; + +#endif // PROPOSALCARD_H diff --git a/src/qt/pivx/res/css/style_light.css b/src/qt/pivx/res/css/style_light.css index 6dbd2cbe5917..cf683365f89f 100644 --- a/src/qt/pivx/res/css/style_light.css +++ b/src/qt/pivx/res/css/style_light.css @@ -353,6 +353,42 @@ QPushButton[cssClass="img-nav-logo"] { color: #B088FF; } + +*[cssClass="btn-nav-governance"] { + qproperty-icon: url("://ic-nav-governance") off, + url("://ic-nav-governance-active") on ; + qproperty-iconSize: 32px 32px; + background-color:transparent; + font-size:14px; + color: #938da5; +} + +*[cssClass="btn-nav-governance"]:checked { + background-color: qlineargradient(x1:0, x2: 1, stop: 0 #3c2559, stop: 1 #1f162b); + font-size:14px; + color: #B088FF; +} + +*[cssClass="btn-nav-governance"]:checked:hover { + background-color: qlineargradient(x1:0, x2: 1, stop: 0 #3c2559, stop: 1 #1f162b); + color: #B088FF; +} + +*[cssClass="btn-nav-governance"]:hover { + qproperty-icon: url("://ic-nav-governance-hover"); + qproperty-iconSize: 32px 32px; + background-color: transparent; + color: #FFFFFF; +} + +*[cssClass="btn-nav-governance-active"] { + qproperty-icon:url("://ic-nav-governance-active") ; + qproperty-iconSize: 32px 32px; + background-color: qlineargradient(x1:0, x2: 1, stop: 0 #3c2559, stop: 1 #1f162b); + font-size:14px; + color: #B088FF; +} + /*HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH HH TOP BAR HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH*/ @@ -2146,6 +2182,190 @@ HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH*/ padding:10px 10px; } +/*HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH +HH PROPOSAL +HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH*/ + +*[cssClass="dialog-proposal-message"] { + color:#707070; + font-size:16px; +} + +/*HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH +HH GOVERNANCE +HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH*/ + +*[cssClass="label-budget-title"] { + color:#707070; + font-size:22px; +} + + +*[cssClass="label-budget-text"] { + color:#707070; + font-size:16px; +} + + +*[cssClass="label-budget-amount"] { + color:#707070; + font-size:17px; +} + + +*[cssClass="label-budget-amount-allocated"] { + color:#bababa; + font-size:17px; +} + +*[cssClass="ic-time"] { + qproperty-icon: url("://ic-time"); + qproperty-iconSize: 26px 26px; + background-color: transparent; +} + + +QPushButton[cssClass="btn-secundary-filter"] { + background-image: url(://ic-filter); + background-position:right center; + background-repeat:no-repeat; + border: 1px solid #335c4b7d; + background-color:#FFFFFF; + font-size:18px; + padding-top:4px; + padding-left:6px; + padding-bottom:4px; + padding-right:30px; + color: #5c4b7d; + border-radius: 2px; +} + + +QPushButton[cssClass="btn-secundary-filter"]:hover { + border: 1px solid #5c4b7d; + background-color:#FFFFFF; +} + +QPushButton[cssClass="btn-secundary-filter"]:pressed { + border: 1px solid #5c4b7d; + background-color:#1A5c4b7d; +} + +/*HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH +HH GOVERNANCE CARD +HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH*/ + + +*[cssClass="card-governance"] { + background-color:#ffffff; + border: 1px solid #E0E0E0; + border-radius: 6px; +} + + +*[cssClass="card-title"] { + color:#4D4D4D; + font-size:20px; +} + +*[cssClass="card-amount"] { + color:#4D4D4D; + font-size:18px; +} + +*[cssClass="card-time"] { + color:#707070; + font-size:14px; +} + + +*[cssClass="card-status-passing"] { + color:#4D4D4D; + font-size:12px; + border-radius:12px; + background-color:rgba(176, 136, 255, 0.2); + padding:6px 12px; +} + +*[cssClass="card-status-not-passing"] { + color:#4D4D4D; + font-size:12px; + border-radius:12px; + background-color:rgba(186, 186, 186, 0.2); + padding:6px 12px; +} + +*[cssClass="card-status-no-votes"] { + border: 1px solid #BABABA; + color:#4D4D4D; + font-size:12px; + border-radius:12px; + background-color:transparent; + padding:6px 12px; +} + +*[cssClass="card-progress-box"] { + border: 1px solid #bababa; + border-radius: 2px; +} + + +*[cssClass="card-progress-no"] { + color:#4D4D4D; + font-size:14px; + padding-left:4px; + background-color:transparent; +} + + +*[cssClass="card-progress-no"]::chunk:horizontal { + background: #cbcbcb; + margin-right: 2px; +} + + +*[cssClass="card-progress-yes"] { + color:#4D4D4D; + font-size:14px; + padding-right:4px; + background-color:transparent; +} + + +*[cssClass="card-progress-yes"]::chunk:horizontal { + background: transparent; + margin-right: 2px; +} + +*[cssClass="btn-link"] { + qproperty-icon: url(" "); + qproperty-iconSize: 22px 22px; + background-image: url("://ic-link"); + background-repeat: no-repeat; + background-color: transparent; + background-position: center center; +} + + +*[cssClass="btn-link"]:hover { + background-color: transparent; + background-image: url("://ic-link-hover"); + background-repeat: no-repeat; + background-position: center center; +} + +*[cssClass="card-btn-vote"] { + border: 1px solid #5c4b7d; + background-color:#FFFFFF; + font-size:18px; + padding:4px 10px; + color: #5c4b7d; + border-radius: 2px; +} + +*[cssClass="card-btn-vote"]:hover { + background-color:rgba(92, 75, 125, 0.2);; + } /*HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH HH SPIN BOX @@ -3299,6 +3519,12 @@ QHeaderView::section { color: #707070; } +*[cssClass="img-empty-governance"] { + qproperty-icon: url(://img-empty-governance); + qproperty-iconSize: 100px 100px; + background-color: transparent; +} + /*HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH HH SEND MULTI HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH*/ diff --git a/src/qt/pivx/res/img/ic-check-vote-active.svg b/src/qt/pivx/res/img/ic-check-vote-active.svg new file mode 100644 index 000000000000..aa84dee383a0 --- /dev/null +++ b/src/qt/pivx/res/img/ic-check-vote-active.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/qt/pivx/res/img/ic-check-vote.svg b/src/qt/pivx/res/img/ic-check-vote.svg new file mode 100644 index 000000000000..e4a6a8df3902 --- /dev/null +++ b/src/qt/pivx/res/img/ic-check-vote.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/qt/pivx/res/img/ic-filter.svg b/src/qt/pivx/res/img/ic-filter.svg new file mode 100644 index 000000000000..f19c1258a3c8 --- /dev/null +++ b/src/qt/pivx/res/img/ic-filter.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/qt/pivx/res/img/ic-link-hover.svg b/src/qt/pivx/res/img/ic-link-hover.svg new file mode 100644 index 000000000000..4832a75eb610 --- /dev/null +++ b/src/qt/pivx/res/img/ic-link-hover.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/qt/pivx/res/img/ic-link.svg b/src/qt/pivx/res/img/ic-link.svg new file mode 100644 index 000000000000..4f9ec42ef987 --- /dev/null +++ b/src/qt/pivx/res/img/ic-link.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/qt/pivx/res/img/ic-nav-governance-active.svg b/src/qt/pivx/res/img/ic-nav-governance-active.svg new file mode 100644 index 000000000000..178cedc7f065 --- /dev/null +++ b/src/qt/pivx/res/img/ic-nav-governance-active.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/qt/pivx/res/img/ic-nav-governance-hover.svg b/src/qt/pivx/res/img/ic-nav-governance-hover.svg new file mode 100644 index 000000000000..a90c024bfe8d --- /dev/null +++ b/src/qt/pivx/res/img/ic-nav-governance-hover.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/qt/pivx/res/img/ic-nav-governance.svg b/src/qt/pivx/res/img/ic-nav-governance.svg new file mode 100644 index 000000000000..ac4bee6cbc0d --- /dev/null +++ b/src/qt/pivx/res/img/ic-nav-governance.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/src/qt/pivx/res/img/ic-time.svg b/src/qt/pivx/res/img/ic-time.svg new file mode 100644 index 000000000000..7435f4fef7e6 --- /dev/null +++ b/src/qt/pivx/res/img/ic-time.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/qt/pivx/res/img/img-empty-governance.svg b/src/qt/pivx/res/img/img-empty-governance.svg new file mode 100644 index 000000000000..3d96e410f035 --- /dev/null +++ b/src/qt/pivx/res/img/img-empty-governance.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + From 96c63cfc7b7563b4f8ee705c76b9a602d5cc5984 Mon Sep 17 00:00:00 2001 From: Gregory Solarte Date: Thu, 20 May 2021 11:23:19 -0300 Subject: [PATCH 04/59] GUI: add styles for governance screens. --- src/qt/pivx/res/css/style_light.css | 115 ++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) diff --git a/src/qt/pivx/res/css/style_light.css b/src/qt/pivx/res/css/style_light.css index cf683365f89f..c24b5ed7fe75 100644 --- a/src/qt/pivx/res/css/style_light.css +++ b/src/qt/pivx/res/css/style_light.css @@ -2367,6 +2367,121 @@ HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH*/ background-color:rgba(92, 75, 125, 0.2);; } +/*HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH +HH VOTE DIALOG +HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH*/ + + + +*[cssClass="vote-title"] { + color:#4D4D4D; + font-size:22px; +} + +*[cssClass="vote-amount"] { + color:#4D4D4D; + font-size:20px; +} + +*[cssClass="vote-time"] { + color:#707070; + font-size:18px; +} + +*[cssClass="btn-vote-select"] { + background: transparent; + border: 0px; + color:#5C4B7D; + font-size:16px; +} + +*[cssClass="btn-vote-select"]:hover { + text-decoration: underline; +} + + +*[cssClass="vote-message"] { + color:#707070; + font-size:14px; +} + +*[cssClass="vote-grid"] { + border: 1px solid #BABABA; + background-color:transparent; +} + + +*[cssClass="vote-progress"] { + color: #4d4d4d; + font-size:14px; + background-color: transparent; + border-radius: 2px; +} + + +*[cssClass="vote-progress"]::chunk:horizontal { + background: rgba(238, 238, 238, 0.85); + border-right:1px solid #bababa; +} + + + +*[cssClass="vote-progress-no"] { + height: 36px; + color: #4d4d4d; + font-size:14px; + background-color: transparent; + border-radius: 2px; +} + + +*[cssClass="vote-progress-no"]::chunk:horizontal { + background: rgba(238, 238, 238, 0.85); + height: 36px; +} + + +*[cssClass="vote-progress-yes"] { + min-height: 36px; + color: #4d4d4d; + font-size:14px; + background-color: transparent; + border-radius: 2px; +} + + +*[cssClass="vote-progress-yes"]::chunk:horizontal { + background: rgba(92, 75, 125, 0.2); +} + + + +QCheckBox[cssClass="check-vote"] { + spacing: 5px; + padding:5px; + font-size:18px; + color:#707070; +} + +QCheckBox:checked[cssClass="check-vote"] { + spacing: 5px; + font-size:18px; + color:#5c4b7d; +} + +QCheckBox::indicator[cssClass="check-vote"] { + width: 24px; + height: 24px; +} + +QCheckBox::indicator:unchecked[cssClass="check-vote"] { + image: url("://ic-check-vote"); +} + +QCheckBox::indicator:checked[cssClass="check-vote"] { + image: url("://ic-check-vote-active"); +} + /*HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH HH SPIN BOX HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH*/ From d67a12146a6546fe6a13dc4309c62730d711b8cd Mon Sep 17 00:00:00 2001 From: furszy Date: Thu, 20 May 2021 15:07:20 -0300 Subject: [PATCH 05/59] GUI: governance vote for proposal dialog introduced. --- src/Makefile.qt.include | 4 + src/qt/CMakeLists.txt | 1 + src/qt/pivx/forms/votedialog.ui | 559 +++++++++++++++++++++++++++++++ src/qt/pivx/governancewidget.cpp | 9 + src/qt/pivx/governancewidget.h | 3 + src/qt/pivx/votedialog.cpp | 97 ++++++ src/qt/pivx/votedialog.h | 41 +++ 7 files changed, 714 insertions(+) create mode 100644 src/qt/pivx/forms/votedialog.ui create mode 100644 src/qt/pivx/votedialog.cpp create mode 100644 src/qt/pivx/votedialog.h diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include index 9d0f1660e86c..4482e02e54e1 100644 --- a/src/Makefile.qt.include +++ b/src/Makefile.qt.include @@ -51,6 +51,7 @@ QT_FORMS_UI = \ qt/pivx/forms/defaultdialog.ui \ qt/pivx/forms/coldstakingwidget.ui \ qt/pivx/forms/proposalcard.ui \ + qt/pivx/forms/votedialog.ui \ qt/pivx/forms/governancewidget.ui \ qt/pivx/settings/forms/settingsbackupwallet.ui \ qt/pivx/settings/forms/settingsexportcsv.ui \ @@ -139,6 +140,7 @@ QT_MOC_CPP = \ qt/pivx/moc_defaultdialog.cpp \ qt/pivx/moc_coldstakingwidget.cpp \ qt/pivx/moc_proposalcard.cpp \ + qt/pivx/moc_votedialog.cpp \ qt/pivx/moc_governancewidget.cpp \ qt/pivx/settings/moc_settingsbackupwallet.cpp \ qt/pivx/settings/moc_settingsexportcsv.cpp \ @@ -255,6 +257,7 @@ BITCOIN_QT_H = \ qt/pivx/coldstakingwidget.h \ qt/pivx/governancemodel.h \ qt/pivx/proposalcard.h \ + qt/pivx/votedialog.h \ qt/pivx/governancewidget.h \ qt/pivx/settings/settingsbackupwallet.h \ qt/pivx/settings/settingsexportcsv.h \ @@ -587,6 +590,7 @@ BITCOIN_QT_WALLET_CPP = \ qt/pivx/coldstakingwidget.cpp \ qt/pivx/governancemodel.cpp \ qt/pivx/proposalcard.cpp \ + qt/pivx/votedialog.cpp \ qt/pivx/governancewidget.cpp \ qt/pivx/settings/settingsbackupwallet.cpp \ qt/pivx/settings/settingsexportcsv.cpp \ diff --git a/src/qt/CMakeLists.txt b/src/qt/CMakeLists.txt index ca4b2e4d8f0f..7e165c1ba8d3 100644 --- a/src/qt/CMakeLists.txt +++ b/src/qt/CMakeLists.txt @@ -146,6 +146,7 @@ SET(QT_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/pivx/defaultdialog.cpp ${CMAKE_CURRENT_SOURCE_DIR}/pivx/coldstakingwidget.cpp ${CMAKE_CURRENT_SOURCE_DIR}/pivx/proposalcard.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/pivx/votedialog.cpp ${CMAKE_CURRENT_SOURCE_DIR}/pivx/governancewidget.cpp ${CMAKE_CURRENT_SOURCE_DIR}/pivx/settings/settingsbackupwallet.cpp ${CMAKE_CURRENT_SOURCE_DIR}/pivx/settings/settingsbittoolwidget.cpp diff --git a/src/qt/pivx/forms/votedialog.ui b/src/qt/pivx/forms/votedialog.ui new file mode 100644 index 000000000000..3ccbb916588a --- /dev/null +++ b/src/qt/pivx/forms/votedialog.ui @@ -0,0 +1,559 @@ + + + VoteDialog + + + + 0 + 0 + 550 + 450 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 6 + + + 20 + + + 20 + + + 20 + + + 20 + + + + + 0 + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 40 + + + + + 16777215 + 40 + + + + padding-left:24px; + + + Vote For Proposal + + + Qt::AlignCenter + + + 7 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 24 + 24 + + + + + 24 + 24 + + + + Qt::NoFocus + + + + + + + + + + + + Select vote direction and the masternodes that will vote for it + + + + + + + Qt::Vertical + + + QSizePolicy::Expanding + + + + 20 + 40 + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 30 + + + + PIVX-MBD-Jan-2021 + + + Qt::AlignCenter + + + true + + + + + + + + 0 + 30 + + + + + 16777215 + 30 + + + + PointingHandCursor + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + 0 + 50 + + + + + 6 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + 5,000 PIV + + + + + + + 2 months of 4 + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 10 + + + + + + + + + 0 + 36 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + 0 + 36 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 16 + 20 + + + + + + + + + 0 + 36 + + + + Qt::LeftToRight + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 10 + + + + + + + + PointingHandCursor + + + Select Voting Masternodes + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 20 + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 50 + + + + Qt::NoFocus + + + CANCEL + + + + + + + + 0 + 50 + + + + Qt::NoFocus + + + VOTE + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Expanding + + + + 20 + 10 + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + You can change your vote later + + + + + + + Qt::Vertical + + + QSizePolicy::Expanding + + + + 20 + 40 + + + + + + + + + + + + diff --git a/src/qt/pivx/governancewidget.cpp b/src/qt/pivx/governancewidget.cpp index e20847f38991..97260d82016e 100644 --- a/src/qt/pivx/governancewidget.cpp +++ b/src/qt/pivx/governancewidget.cpp @@ -3,6 +3,7 @@ #include "qt/pivx/governancemodel.h" #include "qt/pivx/qtutils.h" +#include "qt/pivx/votedialog.h" #include #include @@ -86,6 +87,14 @@ GovernanceWidget::~GovernanceWidget() delete governanceModel; } +void GovernanceWidget::onVoteForPropClicked() +{ + window->showHide(true); + VoteDialog* dialog = new VoteDialog(window); + openDialogWithOpaqueBackgroundY(dialog, window, 4.5, 5); + dialog->deleteLater(); +} + void GovernanceWidget::loadClientModel() { governanceModel = new GovernanceModel(clientModel); } diff --git a/src/qt/pivx/governancewidget.h b/src/qt/pivx/governancewidget.h index 6d2959716752..b5d7fa9f10e5 100644 --- a/src/qt/pivx/governancewidget.h +++ b/src/qt/pivx/governancewidget.h @@ -58,6 +58,9 @@ class GovernanceWidget : public PWidget void resizeEvent(QResizeEvent *event) override; void loadClientModel() override; +public Q_SLOTS: + void onVoteForPropClicked(); + private: Ui::governancewidget *ui; GovernanceModel* governanceModel{nullptr}; diff --git a/src/qt/pivx/votedialog.cpp b/src/qt/pivx/votedialog.cpp new file mode 100644 index 000000000000..024ce3e3c54a --- /dev/null +++ b/src/qt/pivx/votedialog.cpp @@ -0,0 +1,97 @@ +// Copyright (c) 2021 The PIVX developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or https://www.opensource.org/licenses/mit-license.php. + +#include "qt/pivx/votedialog.h" +#include "qt/pivx/forms/ui_votedialog.h" + +#include "qt/pivx/qtutils.h" + +VoteDialog::VoteDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::VoteDialog) +{ + ui->setupUi(this); + this->setStyleSheet(parent->styleSheet()); + setCssProperty(ui->frame, "container-dialog"); + setCssProperty(ui->labelTitle, "text-title-dialog"); + setCssProperty(ui->labelSubtitle, "text-subtitle"); + + // Vote Info + setCssProperty(ui->labelTitleVote, "vote-title"); + setCssProperty(ui->labelAmount, "vote-amount"); + ui->labelAmount->setAlignment(Qt::AlignCenter); + setCssProperty(ui->labelTime, "vote-time"); + ui->labelTime->setAlignment(Qt::AlignCenter); + setCssProperty(ui->labelMessage, "vote-message"); + ui->labelMessage->setAlignment(Qt::AlignCenter); + + setCssProperty(ui->btnEsc, "ic-close"); + setCssProperty(ui->btnCancel, "btn-dialog-cancel"); + setCssProperty(ui->btnSave, "btn-primary"); + setCssProperty(ui->btnLink, "btn-link"); + setCssProperty(ui->btnSelectMasternodes, "btn-vote-select"); + setCssProperty(ui->containerNo, "card-progress-box"); + setCssProperty(ui->containerYes, "card-progress-box"); + + progressBarNo = new QProgressBar(ui->containerNo); + checkBoxNo = new QCheckBox(ui->containerNo); + initVoteCheck(ui->containerNo, checkBoxNo, progressBarNo, "No", Qt::LayoutDirection::RightToLeft, false); + + progressBarYes = new QProgressBar(ui->containerYes); + checkBoxYes = new QCheckBox(ui->containerYes); + initVoteCheck(ui->containerYes, checkBoxYes, progressBarYes, "Yes", Qt::LayoutDirection::LeftToRight, true); + + connect(ui->btnEsc, &QPushButton::clicked, this, &VoteDialog::close); + connect(ui->btnCancel, &QPushButton::clicked, this, &VoteDialog::close); + connect(ui->btnSave, &QPushButton::clicked, this, &VoteDialog::onAcceptClicked); +} + +void VoteDialog::onAcceptClicked() +{ + close(); +} + +void VoteDialog::showEvent(QShowEvent *event) +{ + // Qt hack to solve the macOS-only extra margin issue. + progressBarYes->setFixedWidth(progressBarYes->width() + 5); + progressBarNo->setFixedWidth(progressBarNo->width() + 5); +} + +void VoteDialog::onCheckBoxClicked(QCheckBox* checkBox, QProgressBar* progressBar, bool isVoteYes) +{ + // todo: set value. +} + +void VoteDialog::initVoteCheck(QWidget* container, QCheckBox* checkBox, QProgressBar* progressBar, QString text, Qt::LayoutDirection direction, bool isVoteYes) +{ + QGridLayout* gridLayout = dynamic_cast(container->layout()); + progressBar->setMaximum(100); + progressBar->setMinimum(0); + progressBar->setLayoutDirection(direction); + progressBar->setTextVisible(false); + progressBar->setAlignment(Qt::AlignCenter); + progressBar->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); + progressBar->setOrientation(Qt::Horizontal); + progressBar->setContentsMargins(0,0,0,0); + setCssProperty(progressBar, "vote-progress-yes"); + gridLayout->addWidget(progressBar, 0, 0, 1, 1); + progressBar->setAttribute(Qt::WA_LayoutUsesWidgetRect); + + checkBox->setText(text); + checkBox->setLayoutDirection(direction); + setCssProperty(checkBox, "check-vote"); + gridLayout->addWidget(checkBox, 0, 0, 1, 1); + setCssProperty(container, "vote-grid"); + gridLayout->setMargin(0); + container->setContentsMargins(0,0,0,0); + connect(checkBox, &QCheckBox::clicked, [this, checkBox, progressBar, isVoteYes](){ onCheckBoxClicked(checkBox, progressBar, isVoteYes); }); + checkBox->setAttribute(Qt::WA_LayoutUsesWidgetRect); + checkBox->show(); +} + +VoteDialog::~VoteDialog() +{ + delete ui; +} diff --git a/src/qt/pivx/votedialog.h b/src/qt/pivx/votedialog.h new file mode 100644 index 000000000000..04e3d51b3140 --- /dev/null +++ b/src/qt/pivx/votedialog.h @@ -0,0 +1,41 @@ +// Copyright (c) 2021 The PIVX developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or https://www.opensource.org/licenses/mit-license.php. + +#ifndef VOTEDIALOG_H +#define VOTEDIALOG_H + +#include +#include +#include + +namespace Ui { +class VoteDialog; +} + +class VoteDialog : public QDialog +{ + Q_OBJECT + +public: + explicit VoteDialog(QWidget *parent = nullptr); + ~VoteDialog(); + + void showEvent(QShowEvent *event) override; + +public Q_SLOTS: + void onAcceptClicked(); + void onCheckBoxClicked(QCheckBox* checkBox, QProgressBar* progressBar, bool isVoteYes); + +private: + Ui::VoteDialog *ui; + QCheckBox* checkBoxNo{nullptr}; + QCheckBox* checkBoxYes{nullptr}; + QProgressBar* progressBarNo{nullptr}; + QProgressBar* progressBarYes{nullptr}; + + void initVoteCheck(QWidget* container, QCheckBox* checkBox, QProgressBar* progressBar, + QString text, Qt::LayoutDirection direction, bool isVoteYes); +}; + +#endif // VOTEDIALOG_H From feab44402d2e193935fd7e3987e2273d80eed868 Mon Sep 17 00:00:00 2001 From: furszy Date: Thu, 20 May 2021 15:25:23 -0300 Subject: [PATCH 06/59] GUI: governance vote for proposal dialog: masternode/s selection dialog introduced. --- src/Makefile.qt.include | 4 + src/qt/CMakeLists.txt | 1 + src/qt/pivx/forms/mnselectiondialog.ui | 401 +++++++++++++++++++++++++ src/qt/pivx/mnselectiondialog.cpp | 101 +++++++ src/qt/pivx/mnselectiondialog.h | 41 +++ src/qt/pivx/qtutils.cpp | 4 +- src/qt/pivx/qtutils.h | 2 +- src/qt/pivx/votedialog.cpp | 12 + src/qt/pivx/votedialog.h | 1 + 9 files changed, 564 insertions(+), 3 deletions(-) create mode 100644 src/qt/pivx/forms/mnselectiondialog.ui create mode 100644 src/qt/pivx/mnselectiondialog.cpp create mode 100644 src/qt/pivx/mnselectiondialog.h diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include index 4482e02e54e1..fc8bdc6439ff 100644 --- a/src/Makefile.qt.include +++ b/src/Makefile.qt.include @@ -51,6 +51,7 @@ QT_FORMS_UI = \ qt/pivx/forms/defaultdialog.ui \ qt/pivx/forms/coldstakingwidget.ui \ qt/pivx/forms/proposalcard.ui \ + qt/pivx/forms/mnselectiondialog.ui \ qt/pivx/forms/votedialog.ui \ qt/pivx/forms/governancewidget.ui \ qt/pivx/settings/forms/settingsbackupwallet.ui \ @@ -140,6 +141,7 @@ QT_MOC_CPP = \ qt/pivx/moc_defaultdialog.cpp \ qt/pivx/moc_coldstakingwidget.cpp \ qt/pivx/moc_proposalcard.cpp \ + qt/pivx/moc_mnselectiondialog.cpp \ qt/pivx/moc_votedialog.cpp \ qt/pivx/moc_governancewidget.cpp \ qt/pivx/settings/moc_settingsbackupwallet.cpp \ @@ -257,6 +259,7 @@ BITCOIN_QT_H = \ qt/pivx/coldstakingwidget.h \ qt/pivx/governancemodel.h \ qt/pivx/proposalcard.h \ + qt/pivx/mnselectiondialog.h \ qt/pivx/votedialog.h \ qt/pivx/governancewidget.h \ qt/pivx/settings/settingsbackupwallet.h \ @@ -590,6 +593,7 @@ BITCOIN_QT_WALLET_CPP = \ qt/pivx/coldstakingwidget.cpp \ qt/pivx/governancemodel.cpp \ qt/pivx/proposalcard.cpp \ + qt/pivx/mnselectiondialog.cpp \ qt/pivx/votedialog.cpp \ qt/pivx/governancewidget.cpp \ qt/pivx/settings/settingsbackupwallet.cpp \ diff --git a/src/qt/CMakeLists.txt b/src/qt/CMakeLists.txt index 7e165c1ba8d3..e4f559598425 100644 --- a/src/qt/CMakeLists.txt +++ b/src/qt/CMakeLists.txt @@ -145,6 +145,7 @@ SET(QT_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/pivx/addresseswidget.cpp ${CMAKE_CURRENT_SOURCE_DIR}/pivx/defaultdialog.cpp ${CMAKE_CURRENT_SOURCE_DIR}/pivx/coldstakingwidget.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/pivx/mnselectiondialog.cpp ${CMAKE_CURRENT_SOURCE_DIR}/pivx/proposalcard.cpp ${CMAKE_CURRENT_SOURCE_DIR}/pivx/votedialog.cpp ${CMAKE_CURRENT_SOURCE_DIR}/pivx/governancewidget.cpp diff --git a/src/qt/pivx/forms/mnselectiondialog.ui b/src/qt/pivx/forms/mnselectiondialog.ui new file mode 100644 index 000000000000..a4c5be9b2fd5 --- /dev/null +++ b/src/qt/pivx/forms/mnselectiondialog.ui @@ -0,0 +1,401 @@ + + + MnSelectionDialog + + + + 0 + 0 + 500 + 400 + + + + Form + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 6 + + + 30 + + + 20 + + + 30 + + + 20 + + + + + 0 + + + + + + 24 + 24 + + + + + 24 + 24 + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 40 + + + + + 16777215 + 40 + + + + padding-right:24px; + + + Select Masternodes + + + Qt::AlignCenter + + + 7 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Each Masternode refers to a single vote, you can select which one will vote for the proposal + + + Qt::AlignCenter + + + true + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 30 + + + + + + + + + 0 + 55 + + + + + 0 + + + 60 + + + 0 + + + 60 + + + 0 + + + + + + 3 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 180 + 36 + + + + + 16777215 + 36 + + + + + 4 + + + 4 + + + 4 + + + 4 + + + 4 + + + + + + 140 + 0 + + + + + 16777215 + 16777215 + + + + Amount of votes: + + + + + + + 3 + + + + + + + + + + Qt::Horizontal + + + + 211 + 20 + + + + + + + + + 90 + 36 + + + + + 16777215 + 36 + + + + Select All + + + + + + + + + + + + + 60 + + + 60 + + + + + Qt::NoFocus + + + outline: 0; + + + + + + + + + Name + + + + + Status + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 12 + + + + + + + + 60 + + + 60 + + + + + + 0 + 50 + + + + CANCEL + + + + + + + + 0 + 50 + + + + SELECT VOTES + + + + + + + + + + + + + diff --git a/src/qt/pivx/mnselectiondialog.cpp b/src/qt/pivx/mnselectiondialog.cpp new file mode 100644 index 000000000000..44ddfac46534 --- /dev/null +++ b/src/qt/pivx/mnselectiondialog.cpp @@ -0,0 +1,101 @@ +// Copyright (c) 2021 The PIVX developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or https://www.opensource.org/licenses/mit-license.php. + +#include "qt/pivx/mnselectiondialog.h" +#include "qt/pivx/forms/ui_mnselectiondialog.h" +#include "qt/pivx/qtutils.h" + +MnSelectionDialog::MnSelectionDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::MnSelectionDialog) +{ + ui->setupUi(this); + this->setStyleSheet(parent->styleSheet()); + setCssProperty(ui->frame, "container-dialog"); + setCssProperty(ui->labelTitle, "text-title-dialog"); + setCssProperty(ui->labelMessage, "text-main-grey"); + setCssProperty(ui->btnEsc, "ic-chevron-left"); + setCssProperty(ui->btnCancel, "btn-dialog-cancel"); + setCssProperty(ui->btnSave, "btn-primary"); + setCssProperty(ui->containerAmountOfVotes, "container-border-purple"); + setCssProperty(ui->labelAmountOfVotesText, "text-purple"); + setCssProperty(ui->labelAmountOfVotes, "text-purple"); + setCssProperty(ui->btnSelectAll, "btn-dialog-secundary"); + + ui->treeWidget->setColumnWidth(COLUMN_CHECKBOX, colCheckBoxWidth_treeMode); + ui->treeWidget->setColumnWidth(COLUMN_NAME, 110); + ui->treeWidget->setColumnWidth(COLUMN_STATUS, 60); + ui->treeWidget->header()->setStretchLastSection(true); + ui->treeWidget->header()->setSectionResizeMode(1, QHeaderView::Stretch); + ui->treeWidget->setRootIsDecorated(false); + ui->treeWidget->setFocusPolicy(Qt::NoFocus); + + connect(ui->btnEsc, SIGNAL(clicked()), this, SLOT(close())); + connect(ui->btnCancel, SIGNAL(clicked()), this, SLOT(close())); + connect(ui->btnSave, SIGNAL(clicked()), this, SLOT(close())); +} + +void MnSelectionDialog::setModel() +{ + updateView(); +} + +class MnInfo { +public: + explicit MnInfo() {} + ~MnInfo() {} +}; + +void MnSelectionDialog::updateView() +{ + ui->treeWidget->clear(); + ui->treeWidget->setEnabled(false); // performance, otherwise updateLabels would be called for every checked checkbox + QFlags flgCheckbox = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable; + QFlags flgTristate = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable | Qt::ItemIsTristate; + + // todo: implement MnInfo + std::list masternodesList; //= getMns(); + appendItem(flgCheckbox, flgTristate, "Masternode1", "Enabled"); + appendItem(flgCheckbox, flgTristate, "Masternode2", "Enabled"); + appendItem(flgCheckbox, flgTristate, "Masternode3", "Disabled"); + for (const MnInfo& mnInfo : masternodesList) { + appendItem(flgCheckbox, flgTristate, "Masternode1", "Enabled"); + } + + // save COLUMN_CHECKBOX width for tree-mode + colCheckBoxWidth_treeMode = std::max(110, ui->treeWidget->columnWidth(COLUMN_CHECKBOX)); + // minimize COLUMN_CHECKBOX width in list-mode (need to display only the check box) + ui->treeWidget->setColumnWidth(COLUMN_CHECKBOX, 40); + + ui->treeWidget->setEnabled(true); +} + +void MnSelectionDialog::appendItem(QFlags flgCheckbox, + QFlags flgTristate, + const QString& mnName, + const QString& mnStatus) +{ + QTreeWidgetItem* itemOutput = new QTreeWidgetItem(ui->treeWidget); + itemOutput->setFlags(flgCheckbox); + itemOutput->setCheckState(COLUMN_CHECKBOX, Qt::Unchecked); + itemOutput->setText(COLUMN_NAME, mnName); + itemOutput->setToolTip(COLUMN_NAME, "Masternode name"); + itemOutput->setText(COLUMN_STATUS, mnStatus); + itemOutput->setToolTip(COLUMN_STATUS, "Masternode status"); + + // todo: disable inactive masternodes + /*if (isInactive) { + itemOutput->setDisabled(true); + itemOutput->setIcon(COLUMN_CHECKBOX, QIcon(":/icons/check_disbled")); + }*/ + + // todo: set checkbox value + // if (coinControl->IsSelected(COutPoint(txhash, outIndex))) + // itemOutput->setCheckState(COLUMN_CHECKBOX, Qt::Checked); +} + +MnSelectionDialog::~MnSelectionDialog() +{ + delete ui; +} diff --git a/src/qt/pivx/mnselectiondialog.h b/src/qt/pivx/mnselectiondialog.h new file mode 100644 index 000000000000..782f58b1e087 --- /dev/null +++ b/src/qt/pivx/mnselectiondialog.h @@ -0,0 +1,41 @@ +// Copyright (c) 2021 The PIVX developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or https://www.opensource.org/licenses/mit-license.php. + +#ifndef MN_SELECTION_DEFAULTDIALOG_H +#define MN_SELECTION_DEFAULTDIALOG_H + +#include + +namespace Ui { + class MnSelectionDialog; +} + +class MnSelectionDialog : public QDialog +{ +Q_OBJECT + +public: + explicit MnSelectionDialog(QWidget *parent = nullptr); + ~MnSelectionDialog(); + + void setModel(); + void updateView(); + +private: + Ui::MnSelectionDialog *ui; + int colCheckBoxWidth_treeMode{50}; + + enum { + COLUMN_CHECKBOX, + COLUMN_NAME, + COLUMN_STATUS + }; + + void appendItem(QFlags flgCheckbox, + QFlags flgTristate, + const QString& mnName, + const QString& mnStats); +}; + +#endif // MN_SELECTION_DEFAULTDIALOG_H diff --git a/src/qt/pivx/qtutils.cpp b/src/qt/pivx/qtutils.cpp index 611c1a45070a..72767029cacf 100644 --- a/src/qt/pivx/qtutils.cpp +++ b/src/qt/pivx/qtutils.cpp @@ -56,7 +56,7 @@ void openDialogFullScreen(QWidget* parent, QWidget* dialog) dialog->resize(parent->width(), parent->height()); } -bool openDialogWithOpaqueBackgroundY(QDialog* widget, PIVXGUI* gui, double posX, int posY) +bool openDialogWithOpaqueBackgroundY(QDialog* widget, PIVXGUI* gui, double posX, int posY, bool hideOpaqueBackground) { widget->setWindowFlags(Qt::CustomizeWindowHint); widget->setAttribute(Qt::WA_TranslucentBackground, true); @@ -69,7 +69,7 @@ bool openDialogWithOpaqueBackgroundY(QDialog* widget, PIVXGUI* gui, double posX, animation->start(QAbstractAnimation::DeleteWhenStopped); widget->activateWindow(); bool res = widget->exec(); - gui->showHide(false); + if (hideOpaqueBackground) gui->showHide(false); return res; } diff --git a/src/qt/pivx/qtutils.h b/src/qt/pivx/qtutils.h index d63a08bd6500..fdf7b48742f3 100644 --- a/src/qt/pivx/qtutils.h +++ b/src/qt/pivx/qtutils.h @@ -35,7 +35,7 @@ extern Qt::Modifier SHORT_KEY; bool openDialog(QDialog* widget, QWidget* gui); void closeDialog(QDialog* widget, PIVXGUI* gui); void openDialogFullScreen(QWidget* parent, QWidget* dialog); -bool openDialogWithOpaqueBackgroundY(QDialog* widget, PIVXGUI* gui, double posX = 3, int posY = 5); +bool openDialogWithOpaqueBackgroundY(QDialog* widget, PIVXGUI* gui, double posX = 3, int posY = 5, bool hideOpaqueBackground = true); bool openDialogWithOpaqueBackground(QDialog* widget, PIVXGUI* gui, double posX = 3); bool openDialogWithOpaqueBackgroundFullScreen(QDialog* widget, PIVXGUI* gui); diff --git a/src/qt/pivx/votedialog.cpp b/src/qt/pivx/votedialog.cpp index 024ce3e3c54a..063880ce7890 100644 --- a/src/qt/pivx/votedialog.cpp +++ b/src/qt/pivx/votedialog.cpp @@ -5,6 +5,7 @@ #include "qt/pivx/votedialog.h" #include "qt/pivx/forms/ui_votedialog.h" +#include "qt/pivx/mnselectiondialog.h" #include "qt/pivx/qtutils.h" VoteDialog::VoteDialog(QWidget *parent) : @@ -42,6 +43,7 @@ VoteDialog::VoteDialog(QWidget *parent) : checkBoxYes = new QCheckBox(ui->containerYes); initVoteCheck(ui->containerYes, checkBoxYes, progressBarYes, "Yes", Qt::LayoutDirection::LeftToRight, true); + connect(ui->btnSelectMasternodes, &QPushButton::clicked, this, &VoteDialog::onMnSelectionClicked); connect(ui->btnEsc, &QPushButton::clicked, this, &VoteDialog::close); connect(ui->btnCancel, &QPushButton::clicked, this, &VoteDialog::close); connect(ui->btnSave, &QPushButton::clicked, this, &VoteDialog::onAcceptClicked); @@ -59,6 +61,16 @@ void VoteDialog::showEvent(QShowEvent *event) progressBarNo->setFixedWidth(progressBarNo->width() + 5); } +void VoteDialog::onMnSelectionClicked() +{ + PIVXGUI* window = dynamic_cast(parent()); + MnSelectionDialog* dialog = new MnSelectionDialog(window); + dialog->resize(size()); + dialog->setModel(); // todo: set mnmodel. + openDialogWithOpaqueBackgroundY(dialog, window, 4.5, 5, false); + dialog->deleteLater(); +} + void VoteDialog::onCheckBoxClicked(QCheckBox* checkBox, QProgressBar* progressBar, bool isVoteYes) { // todo: set value. diff --git a/src/qt/pivx/votedialog.h b/src/qt/pivx/votedialog.h index 04e3d51b3140..394f065b63c7 100644 --- a/src/qt/pivx/votedialog.h +++ b/src/qt/pivx/votedialog.h @@ -26,6 +26,7 @@ class VoteDialog : public QDialog public Q_SLOTS: void onAcceptClicked(); void onCheckBoxClicked(QCheckBox* checkBox, QProgressBar* progressBar, bool isVoteYes); + void onMnSelectionClicked(); private: Ui::VoteDialog *ui; From 61821eb0aede4b7f9aa82ae9b269941c86a3af70 Mon Sep 17 00:00:00 2001 From: Gregory Solarte Date: Thu, 20 May 2021 15:32:50 -0300 Subject: [PATCH 07/59] GUI: add mnselectiondialog styles. --- src/qt/pivx/forms/mnselectiondialog.ui | 2 +- src/qt/pivx/mnselectiondialog.cpp | 2 +- src/qt/pivx/res/css/style_light.css | 115 ++++++++++++++++++++++--- 3 files changed, 107 insertions(+), 12 deletions(-) diff --git a/src/qt/pivx/forms/mnselectiondialog.ui b/src/qt/pivx/forms/mnselectiondialog.ui index a4c5be9b2fd5..ac8a0e51dfdf 100644 --- a/src/qt/pivx/forms/mnselectiondialog.ui +++ b/src/qt/pivx/forms/mnselectiondialog.ui @@ -160,7 +160,7 @@ 20 - 30 + 20 diff --git a/src/qt/pivx/mnselectiondialog.cpp b/src/qt/pivx/mnselectiondialog.cpp index 44ddfac46534..252297babb6e 100644 --- a/src/qt/pivx/mnselectiondialog.cpp +++ b/src/qt/pivx/mnselectiondialog.cpp @@ -21,7 +21,7 @@ MnSelectionDialog::MnSelectionDialog(QWidget *parent) : setCssProperty(ui->containerAmountOfVotes, "container-border-purple"); setCssProperty(ui->labelAmountOfVotesText, "text-purple"); setCssProperty(ui->labelAmountOfVotes, "text-purple"); - setCssProperty(ui->btnSelectAll, "btn-dialog-secundary"); + setCssProperty(ui->btnSelectAll, "btn-dialog-secondary"); ui->treeWidget->setColumnWidth(COLUMN_CHECKBOX, colCheckBoxWidth_treeMode); ui->treeWidget->setColumnWidth(COLUMN_NAME, 110); diff --git a/src/qt/pivx/res/css/style_light.css b/src/qt/pivx/res/css/style_light.css index c24b5ed7fe75..fe63afb4a848 100644 --- a/src/qt/pivx/res/css/style_light.css +++ b/src/qt/pivx/res/css/style_light.css @@ -2399,7 +2399,6 @@ HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH*/ text-decoration: underline; } - *[cssClass="vote-message"] { color:#707070; font-size:14px; @@ -2410,7 +2409,6 @@ HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH*/ background-color:transparent; } - *[cssClass="vote-progress"] { color: #4d4d4d; font-size:14px; @@ -2418,14 +2416,11 @@ HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH*/ border-radius: 2px; } - *[cssClass="vote-progress"]::chunk:horizontal { background: rgba(238, 238, 238, 0.85); border-right:1px solid #bababa; } - - *[cssClass="vote-progress-no"] { height: 36px; color: #4d4d4d; @@ -2434,13 +2429,11 @@ HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH*/ border-radius: 2px; } - *[cssClass="vote-progress-no"]::chunk:horizontal { background: rgba(238, 238, 238, 0.85); height: 36px; } - *[cssClass="vote-progress-yes"] { min-height: 36px; color: #4d4d4d; @@ -2449,13 +2442,10 @@ HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH*/ border-radius: 2px; } - *[cssClass="vote-progress-yes"]::chunk:horizontal { background: rgba(92, 75, 125, 0.2); } - - QCheckBox[cssClass="check-vote"] { spacing: 5px; padding:5px; @@ -2482,6 +2472,103 @@ QCheckBox::indicator:checked[cssClass="check-vote"] { image: url("://ic-check-vote-active"); } +/*HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH +HH SELECT MASTER NODE +HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH*/ + + +*[cssClass="text-title-purple"] { + color: #5C4B7D; + font-size: 14px; +} + +*[cssClass="text-purple"] { + color: #5C4B7D; + font-size: 14px; + padding-left: 8px; + padding-right: 8px; +} + +*[cssClass="btn-dialog-secondary"] { + background: #ffffff; + border:1px solid #bababa; + border-radius: 2px; + color: #4d4d4d; + font-size: 14px; + padding-left: 8px; + padding-right: 8px; +} + + +*[cssClass="btn-dialog-secondary"]:hover { + border:1px solid #5C4B7D; + color: #5C4B7D; +} + + +QHeaderView::section { + background-color: #5C4B7D; + color: white; + font-size: 14px; + padding-top: 8px; + padding-bottom: 8px; + border: 1px solid #5C4B7D; +} + + +QTreeView { + show-decoration-selected: 1; + background: white; + border: 1px solid #5C4B7D; + font-size: 14px; + color: #4d4d4d; + padding-left: 0px; +} + +QTreeView::item { + border:0px; + padding-top: 8px; + padding-bottom: 8px; + padding-left: 0px; + font-size: 14px; + color: #4d4d4d; +} + +QTreeView::item:hover { + background:rgba(176, 136, 255, 0.2); + border:0px; +} + +QTreeView::item:selected { + color: #5C4B7D; + border:0px; +} + +QTreeView::item:selected:active{ + color: #5C4B7D; + border:0px; +} + +QTreeView::item:selected:!active { + color:#5C4B7D; + border:0px; +} + + +QTreeView::indicator { + width: 24px; + height: 24px; +} + + +QTreeView::indicator:unchecked { + image: url(://ic-check-box); +} + +QTreeView::indicator:checked { + image: url(://ic-check-active); +} + /*HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH HH SPIN BOX HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH*/ @@ -3102,6 +3189,14 @@ QPushButton[cssClass="ic-arrow"]:checked{ border:none; } +*[cssClass="ic-chevron-left"] { + qproperty-icon: url("://ic-chevron-left") off, + url("://ic-chevron-left") on ; + qproperty-iconSize: 24px 24px; + background-color: transparent; + border:none; +} + *[cssClass="layout-arrow"] { background: url("://ic-arrow-right"); background-repeat:no-repeat; From 64dbc869eafcee01c3a408d2c37f46535b61ec21 Mon Sep 17 00:00:00 2001 From: furszy Date: Thu, 20 May 2021 16:15:28 -0300 Subject: [PATCH 08/59] GUI: governance, introducing create proposal dialog + navigation connected. --- src/Makefile.qt.include | 4 + src/qt/CMakeLists.txt | 1 + src/qt/pivx/createproposaldialog.cpp | 174 +++ src/qt/pivx/createproposaldialog.h | 38 + src/qt/pivx/forms/createproposaldialog.ui | 1305 +++++++++++++++++++++ src/qt/pivx/governancewidget.cpp | 9 + src/qt/pivx/governancewidget.h | 1 + 7 files changed, 1532 insertions(+) create mode 100644 src/qt/pivx/createproposaldialog.cpp create mode 100644 src/qt/pivx/createproposaldialog.h create mode 100644 src/qt/pivx/forms/createproposaldialog.ui diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include index fc8bdc6439ff..e728a14f1e06 100644 --- a/src/Makefile.qt.include +++ b/src/Makefile.qt.include @@ -53,6 +53,7 @@ QT_FORMS_UI = \ qt/pivx/forms/proposalcard.ui \ qt/pivx/forms/mnselectiondialog.ui \ qt/pivx/forms/votedialog.ui \ + qt/pivx/forms/createproposaldialog.ui \ qt/pivx/forms/governancewidget.ui \ qt/pivx/settings/forms/settingsbackupwallet.ui \ qt/pivx/settings/forms/settingsexportcsv.ui \ @@ -143,6 +144,7 @@ QT_MOC_CPP = \ qt/pivx/moc_proposalcard.cpp \ qt/pivx/moc_mnselectiondialog.cpp \ qt/pivx/moc_votedialog.cpp \ + qt/pivx/moc_createproposaldialog.cpp \ qt/pivx/moc_governancewidget.cpp \ qt/pivx/settings/moc_settingsbackupwallet.cpp \ qt/pivx/settings/moc_settingsexportcsv.cpp \ @@ -261,6 +263,7 @@ BITCOIN_QT_H = \ qt/pivx/proposalcard.h \ qt/pivx/mnselectiondialog.h \ qt/pivx/votedialog.h \ + qt/pivx/createproposaldialog.h \ qt/pivx/governancewidget.h \ qt/pivx/settings/settingsbackupwallet.h \ qt/pivx/settings/settingsexportcsv.h \ @@ -595,6 +598,7 @@ BITCOIN_QT_WALLET_CPP = \ qt/pivx/proposalcard.cpp \ qt/pivx/mnselectiondialog.cpp \ qt/pivx/votedialog.cpp \ + qt/pivx/createproposaldialog.cpp \ qt/pivx/governancewidget.cpp \ qt/pivx/settings/settingsbackupwallet.cpp \ qt/pivx/settings/settingsexportcsv.cpp \ diff --git a/src/qt/CMakeLists.txt b/src/qt/CMakeLists.txt index e4f559598425..1fbf344f330c 100644 --- a/src/qt/CMakeLists.txt +++ b/src/qt/CMakeLists.txt @@ -148,6 +148,7 @@ SET(QT_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/pivx/mnselectiondialog.cpp ${CMAKE_CURRENT_SOURCE_DIR}/pivx/proposalcard.cpp ${CMAKE_CURRENT_SOURCE_DIR}/pivx/votedialog.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/pivx/createproposaldialog.cpp ${CMAKE_CURRENT_SOURCE_DIR}/pivx/governancewidget.cpp ${CMAKE_CURRENT_SOURCE_DIR}/pivx/settings/settingsbackupwallet.cpp ${CMAKE_CURRENT_SOURCE_DIR}/pivx/settings/settingsbittoolwidget.cpp diff --git a/src/qt/pivx/createproposaldialog.cpp b/src/qt/pivx/createproposaldialog.cpp new file mode 100644 index 000000000000..90a068a80610 --- /dev/null +++ b/src/qt/pivx/createproposaldialog.cpp @@ -0,0 +1,174 @@ +// Copyright (c) 2021 The PIVX developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or https://www.opensource.org/licenses/mit-license.php. + +#include "qt/pivx/createproposaldialog.h" +#include "qt/pivx/forms/ui_createproposaldialog.h" + +#include "qt/pivx/qtutils.h" + +void initPageIndexBtn(QPushButton* btn) +{ + QSize BUTTON_SIZE = QSize(22, 22); + setCssProperty(btn, "ic-step-confirm"); + btn->setMinimumSize(BUTTON_SIZE); + btn->setMaximumSize(BUTTON_SIZE); + btn->move(0, 0); + btn->show(); + btn->raise(); + btn->setVisible(false); +} + +CreateProposalDialog::CreateProposalDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::CreateProposalDialog), + icConfirm1(new QPushButton()), + icConfirm2(new QPushButton()), + icConfirm3(new QPushButton()) +{ + ui->setupUi(this); + this->setStyleSheet(parent->styleSheet()); + + setCssProperty(ui->frame, "container-dialog"); + ui->frame->setContentsMargins(10,10,10,10); + setCssProperty({ui->labelLine1, ui->labelLine2}, "line-purple"); + setCssProperty({ui->groupBoxName, ui->groupContainer}, "container-border"); + setCssProperty({ui->pushNumber1, ui->pushNumber2, ui->pushNumber3}, "btn-number-check"); + setCssProperty({ui->pushName1, ui->pushName2, ui->pushName3}, "btn-name-check"); + + // Pages setup + setupPageOne(); + setupPageTwo(); + setupPageThree(); + + // Confirm icons + ui->stackedIcon1->addWidget(icConfirm1); + ui->stackedIcon2->addWidget(icConfirm2); + ui->stackedIcon3->addWidget(icConfirm3); + initPageIndexBtn(icConfirm1); + initPageIndexBtn(icConfirm2); + initPageIndexBtn(icConfirm3); + + // Connect btns + setCssProperty(ui->btnNext, "btn-primary"); + ui->btnNext->setText(tr("NEXT")); + setCssProperty(ui->btnBack, "btn-dialog-cancel"); + ui->btnBack->setVisible(false); + ui->btnBack->setText(tr("BACK")); + setCssProperty(ui->pushButtonSkip, "ic-close"); + + connect(ui->pushButtonSkip, &QPushButton::clicked, this, &CreateProposalDialog::close); + connect(ui->btnNext, &QPushButton::clicked, this, &CreateProposalDialog::onNextClicked); + connect(ui->btnBack, &QPushButton::clicked, this, &CreateProposalDialog::onBackClicked); +} + +void setEditBoxStyle(QLabel* label, QLineEdit* lineEdit, const QString& placeholderText) +{ + setCssProperty(label, "text-title"); + lineEdit->setPlaceholderText(placeholderText); + setCssProperty(lineEdit, "edit-primary"); + lineEdit->setAttribute(Qt::WA_MacShowFocusRect, false); + setShadow(lineEdit); +} + +void CreateProposalDialog::setupPageOne() +{ + setCssProperty(ui->labelTitle1, "text-title-dialog"); + setCssProperty(ui->labelMessage1b, "dialog-proposal-message"); + setEditBoxStyle(ui->labelName, ui->lineEditPropName, "e.g Best proposal ever!"); + setEditBoxStyle(ui->labelURL, ui->lineEditURL, "e.g https://forum.pivx/proposals/best_proposal_ever"); +} + +void CreateProposalDialog::setupPageTwo() +{ + setCssProperty(ui->labelTitleDest, "text-title-dialog"); + setCssProperty(ui->labelMessageDest, "dialog-proposal-message"); + setEditBoxStyle(ui->labelAmount, ui->lineEditAmount, "e.g 500 PIV"); + setEditBoxStyle(ui->labelMonths, ui->lineEditMonths, "e.g 2"); + setEditBoxStyle(ui->labelAddress, ui->lineEditAddress, "e.g D...something.."); +} + +void CreateProposalDialog::setupPageThree() +{ + setCssProperty(ui->labelTitle3, "text-title-dialog"); + ui->stackedWidget->setCurrentIndex(pos); + setCssProperty({ui->labelResultNameTitle, + ui->labelResultAmountTitle, + ui->labelResultAddressTitle, + ui->labelResultMonthsTitle, + ui->labelResultUrlTitle}, + "text-title"); + setCssProperty({ui->labelResultName, + ui->labelResultName, + ui->labelResultAmount, + ui->labelResultAddress, + ui->labelResultMonths, + ui->labelResultUrl}, "text-body1-dialog"); +} + +void CreateProposalDialog::onNextClicked() +{ + int nextPos = pos + 1; + switch (pos){ + case 0:{ + ui->stackedWidget->setCurrentIndex(nextPos); + ui->pushNumber2->setChecked(true); + ui->pushName3->setChecked(false); + ui->pushName2->setChecked(true); + ui->pushName1->setChecked(true); + icConfirm1->setVisible(true); + ui->btnBack->setVisible(true); + break; + } + case 1:{ + ui->stackedWidget->setCurrentIndex(nextPos); + ui->pushNumber3->setChecked(true); + ui->pushName3->setChecked(true); + ui->pushName2->setChecked(true); + ui->pushName1->setChecked(true); + icConfirm2->setVisible(true); + ui->btnNext->setText("Send"); + break; + } + case 2:{ + accept(); + } + } + pos = nextPos; +} + +void CreateProposalDialog::onBackClicked() +{ + if (pos == 0) return; + pos--; + switch(pos){ + case 0:{ + ui->stackedWidget->setCurrentIndex(pos); + ui->pushNumber1->setChecked(true); + ui->pushNumber3->setChecked(false); + ui->pushNumber2->setChecked(false); + ui->pushName3->setChecked(false); + ui->pushName2->setChecked(false); + ui->pushName1->setChecked(true); + icConfirm1->setVisible(false); + ui->btnBack->setVisible(false); + break; + } + case 1:{ + ui->stackedWidget->setCurrentIndex(pos); + ui->pushNumber2->setChecked(true); + ui->pushNumber3->setChecked(false); + ui->pushName3->setChecked(false); + ui->pushName2->setChecked(true); + ui->pushName1->setChecked(true); + icConfirm2->setVisible(false); + ui->btnNext->setText("Next"); + break; + } + } +} + +CreateProposalDialog::~CreateProposalDialog() +{ + delete ui; +} diff --git a/src/qt/pivx/createproposaldialog.h b/src/qt/pivx/createproposaldialog.h new file mode 100644 index 000000000000..bf0037adc475 --- /dev/null +++ b/src/qt/pivx/createproposaldialog.h @@ -0,0 +1,38 @@ +// Copyright (c) 2021 The PIVX developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or https://www.opensource.org/licenses/mit-license.php. + +#ifndef CREATEPROPOSALDIALOG_H +#define CREATEPROPOSALDIALOG_H + +#include + +namespace Ui { +class CreateProposalDialog; +class QPushButton; +} + +class CreateProposalDialog : public QDialog +{ + Q_OBJECT + +public: + explicit CreateProposalDialog(QWidget *parent = nullptr); + ~CreateProposalDialog() override; + +public Q_SLOTS: + void onNextClicked(); + void onBackClicked(); +private: + Ui::CreateProposalDialog *ui; + QPushButton *icConfirm1; + QPushButton *icConfirm2; + QPushButton *icConfirm3; + int pos = 0; + + void setupPageOne(); + void setupPageTwo(); + void setupPageThree(); +}; + +#endif // CREATEPROPOSALDIALOG_H diff --git a/src/qt/pivx/forms/createproposaldialog.ui b/src/qt/pivx/forms/createproposaldialog.ui new file mode 100644 index 000000000000..d79457183118 --- /dev/null +++ b/src/qt/pivx/forms/createproposaldialog.ui @@ -0,0 +1,1305 @@ + + + CreateProposalDialog + + + + 0 + 0 + 715 + 602 + + + + Dialog + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 20 + + + 20 + + + + + 0 + + + 20 + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 20 + 20 + + + + + 20 + 20 + + + + + + + + + + + + + + 0 + 24 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 100 + 20 + + + + + + + + + 22 + 22 + + + + + 22 + 22 + + + + 0 + + + + + 22 + 22 + + + + + 22 + 22 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + false + + + + 22 + 22 + + + + + 22 + 22 + + + + 1 + + + true + + + true + + + true + + + + + + + + + + + + + 0 + 1 + + + + + 16777215 + 1 + + + + + + + + + + + + + + + 22 + 22 + + + + + 22 + 22 + + + + 0 + + + + + 22 + 22 + + + + + 22 + 22 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + false + + + + 22 + 22 + + + + + 22 + 22 + + + + 2 + + + true + + + false + + + true + + + + + + + + + + + + + 0 + 1 + + + + + 16777215 + 1 + + + + + + + + + + + + + + + 22 + 22 + + + + + 22 + 22 + + + + 0 + + + + + 22 + 22 + + + + + 22 + 22 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + false + + + + 22 + 22 + + + + + 22 + 22 + + + + 3 + + + true + + + false + + + true + + + + + + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 100 + 20 + + + + + + + + + + + true + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + true + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 70 + 20 + + + + + + + + false + + + + 100 + 0 + + + + + 80 + 16777215 + + + + Name + + + true + + + true + + + false + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + false + + + + 100 + 0 + + + + + 80 + 16777215 + + + + Receiver + + + true + + + false + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + false + + + + 100 + 0 + + + + + 80 + 16777215 + + + + Summary + + + true + + + false + + + + + + + Qt::Horizontal + + + QSizePolicy::Fixed + + + + 70 + 20 + + + + + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Expanding + + + + 20 + 40 + + + + + + + + + 0 + 0 + + + + + 0 + 350 + + + + + 16777215 + 350 + + + + + + 6 + + + 40 + + + 0 + + + 40 + + + 12 + + + + + + 0 + 0 + + + + + 16777215 + 75 + + + + + 0 + + + + + + 0 + 0 + + + + + 0 + 40 + + + + + 16777215 + 40 + + + + Create New Proposal + + + + + + + + 0 + 0 + + + + Make sure you have 50 PIV for the proposal fee + + + Qt::AlignCenter + + + + + + + + + + + 0 + 150 + + + + + 0 + + + + + 10 + + + + + Proposal Name + + + + + + + + + + + + 10 + + + + + Proposal Forum URL + + + + + + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 60 + + + + + + + + + + 40 + + + 0 + + + 40 + + + 12 + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + + + + + + 0 + 0 + + + + + 0 + 40 + + + + + 16777215 + 40 + + + + Send Proposal + + + + + + + + 0 + 0 + + + + Add the amount and time + + + Qt::AlignCenter + + + + + + + + + + + 0 + 150 + + + + + 0 + + + + + + + 10 + + + + + Amount + + + + + + + + + + + + 10 + + + + + Months + + + + + + + + + + + + + + 10 + + + + + Address + + + + + + + + + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 40 + + + + + + + + + + 6 + + + 40 + + + 0 + + + 40 + + + 12 + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + + + + + + 0 + 0 + + + + + 0 + 40 + + + + + 16777215 + 40 + + + + Summary + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + 0 + 150 + + + + + 0 + + + + + + + 10 + + + + + Name + + + + + + + PIVX-MBD-JanJun2021 + + + + + + + + + 10 + + + + + Qt::RightToLeft + + + Amount + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + 5500 PIV + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + + + 10 + + + + + Address + + + + + + + r7VFR83SQbiezrW72hjcWJtcfip5krte2Z + + + + + + + + + 10 + + + + + Qt::RightToLeft + + + Months + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + 2 Months + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + 10 + + + + + Proposal URL + + + + + + + Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do + + + + + + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 40 + + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 50 + + + + + 180 + 16777215 + + + + CANCEL + + + + + + + + 0 + 50 + + + + + 180 + 16777215 + + + + OK + + + + + + + + + + + + + diff --git a/src/qt/pivx/governancewidget.cpp b/src/qt/pivx/governancewidget.cpp index 97260d82016e..34b9b2a73b81 100644 --- a/src/qt/pivx/governancewidget.cpp +++ b/src/qt/pivx/governancewidget.cpp @@ -1,6 +1,7 @@ #include "qt/pivx/governancewidget.h" #include "qt/pivx/forms/ui_governancewidget.h" +#include "qt/pivx/createproposaldialog.h" #include "qt/pivx/governancemodel.h" #include "qt/pivx/qtutils.h" #include "qt/pivx/votedialog.h" @@ -95,6 +96,14 @@ void GovernanceWidget::onVoteForPropClicked() dialog->deleteLater(); } +void GovernanceWidget::onCreatePropClicked() +{ + window->showHide(true); + CreateProposalDialog* dialog = new CreateProposalDialog(window); + openDialogWithOpaqueBackgroundY(dialog, window, 4.5, 5); + dialog->deleteLater(); +} + void GovernanceWidget::loadClientModel() { governanceModel = new GovernanceModel(clientModel); } diff --git a/src/qt/pivx/governancewidget.h b/src/qt/pivx/governancewidget.h index b5d7fa9f10e5..3ba6d095d939 100644 --- a/src/qt/pivx/governancewidget.h +++ b/src/qt/pivx/governancewidget.h @@ -60,6 +60,7 @@ class GovernanceWidget : public PWidget public Q_SLOTS: void onVoteForPropClicked(); + void onCreatePropClicked(); private: Ui::governancewidget *ui; From 112378959cf989cd796fd680d41f43553cf202a0 Mon Sep 17 00:00:00 2001 From: furszy Date: Fri, 21 May 2021 14:36:23 -0300 Subject: [PATCH 09/59] GUI: governance create proposal, connect field validations and error notifications. --- src/operationresult.h | 1 + src/qt/pivx/createproposaldialog.cpp | 100 ++++++++++++++++++++++++++- src/qt/pivx/createproposaldialog.h | 26 +++++-- src/qt/pivx/governancemodel.cpp | 35 ++++++++++ src/qt/pivx/governancemodel.h | 12 +++- src/qt/pivx/governancewidget.cpp | 4 +- src/qt/pivx/pivxgui.cpp | 2 + src/qt/pivx/qtutils.cpp | 1 + 8 files changed, 173 insertions(+), 8 deletions(-) diff --git a/src/operationresult.h b/src/operationresult.h index 087ed5e0a0e4..40dc502d8d0f 100644 --- a/src/operationresult.h +++ b/src/operationresult.h @@ -19,6 +19,7 @@ class OperationResult OperationResult(bool _res) : m_res(_res) { } std::string getError() const { return (m_error ? *m_error : ""); } + bool getRes() const { return m_res; } explicit operator bool() const { return m_res; } }; diff --git a/src/qt/pivx/createproposaldialog.cpp b/src/qt/pivx/createproposaldialog.cpp index 90a068a80610..1e6d545fafba 100644 --- a/src/qt/pivx/createproposaldialog.cpp +++ b/src/qt/pivx/createproposaldialog.cpp @@ -5,7 +5,9 @@ #include "qt/pivx/createproposaldialog.h" #include "qt/pivx/forms/ui_createproposaldialog.h" +#include "qt/pivx/governancemodel.h" #include "qt/pivx/qtutils.h" +#include "qt/pivx/snackbar.h" void initPageIndexBtn(QPushButton* btn) { @@ -19,9 +21,11 @@ void initPageIndexBtn(QPushButton* btn) btn->setVisible(false); } -CreateProposalDialog::CreateProposalDialog(QWidget *parent) : +CreateProposalDialog::CreateProposalDialog(QWidget *parent, GovernanceModel* _govModel, WalletModel* _walletModel) : QDialog(parent), ui(new Ui::CreateProposalDialog), + govModel(_govModel), + walletModel(_walletModel), icConfirm1(new QPushButton()), icConfirm2(new QPushButton()), icConfirm3(new QPushButton()) @@ -77,6 +81,9 @@ void CreateProposalDialog::setupPageOne() setCssProperty(ui->labelMessage1b, "dialog-proposal-message"); setEditBoxStyle(ui->labelName, ui->lineEditPropName, "e.g Best proposal ever!"); setEditBoxStyle(ui->labelURL, ui->lineEditURL, "e.g https://forum.pivx/proposals/best_proposal_ever"); + + connect(ui->lineEditPropName, &QLineEdit::textChanged, this, &CreateProposalDialog::propNameChanged); + connect(ui->lineEditURL, &QLineEdit::textChanged, this, &CreateProposalDialog::propUrlChanged); } void CreateProposalDialog::setupPageTwo() @@ -86,6 +93,13 @@ void CreateProposalDialog::setupPageTwo() setEditBoxStyle(ui->labelAmount, ui->lineEditAmount, "e.g 500 PIV"); setEditBoxStyle(ui->labelMonths, ui->lineEditMonths, "e.g 2"); setEditBoxStyle(ui->labelAddress, ui->lineEditAddress, "e.g D...something.."); + + ui->lineEditAmount->setValidator(new QIntValidator(1,43200, this)); + ui->lineEditMonths->setValidator(new QIntValidator(1, govModel->getPropMaxPaymentsCount(), this)); + + connect(ui->lineEditAmount, &QLineEdit::textChanged, this, &CreateProposalDialog::propAmountChanged); + connect(ui->lineEditMonths, &QLineEdit::textChanged, this, &CreateProposalDialog::propMonthsChanged); + connect(ui->lineEditAddress, &QLineEdit::textChanged, this, &CreateProposalDialog::propaddressChanged); } void CreateProposalDialog::setupPageThree() @@ -106,11 +120,86 @@ void CreateProposalDialog::setupPageThree() ui->labelResultUrl}, "text-body1-dialog"); } +void CreateProposalDialog::propNameChanged(const QString& newText) +{ + setCssEditLine(ui->lineEditPropName, !newText.isEmpty(), true); +} + +void CreateProposalDialog::propUrlChanged(const QString& newText) +{ + setCssEditLine(ui->lineEditURL, govModel->validatePropURL(newText).getRes(), true); +} + +void CreateProposalDialog::propAmountChanged(const QString& newText) +{ + setCssEditLine(ui->lineEditAmount, govModel->validatePropAmount(newText.toInt()).getRes(), true); +} + +void CreateProposalDialog::propMonthsChanged(const QString& newText) +{ + setCssEditLine(ui->lineEditMonths, govModel->validatePropPaymentCount(newText.toInt()).getRes(), true); +} + +bool CreateProposalDialog::propaddressChanged(const QString& str) +{ + if (!str.isEmpty()) { + QString trimmedStr = str.trimmed(); + bool isShielded = false; + const bool valid = walletModel->validateAddress(trimmedStr, false, isShielded) && !isShielded; + setCssEditLine(ui->lineEditAddress, valid, true); + return valid; + } + setCssEditLine(ui->lineEditAddress, true, true); + return false; +} + +bool CreateProposalDialog::validatePageOne() +{ + if (ui->lineEditPropName->text().isEmpty()) { + inform(tr("Proposal name field cannot be empty")); + return false; + } + auto res = govModel->validatePropURL(ui->lineEditURL->text()); + if (!res) inform(QString::fromStdString(res.getError())); + return res.getRes(); +} + +bool CreateProposalDialog::validatePageTwo() +{ + QString sPaymentCount = ui->lineEditAmount->text(); + if (sPaymentCount.isEmpty()) { + inform(tr("Proposal amount field cannot be empty")); + return false; + } + + // Amount validation + auto opRes = govModel->validatePropAmount(ui->lineEditAmount->text().toInt()); + if (!opRes) { + inform(QString::fromStdString(opRes.getError())); + return false; + } + + // Payments count validation + opRes = govModel->validatePropPaymentCount(sPaymentCount.toInt()); + if (!opRes) { + inform(QString::fromStdString(opRes.getError())); + return false; + } + + if (!propaddressChanged(ui->lineEditAddress->text())) { + inform(tr("Invalid payment address")); + return false; + } + + return true; +} + void CreateProposalDialog::onNextClicked() { int nextPos = pos + 1; switch (pos){ case 0:{ + if (!validatePageOne()) return; ui->stackedWidget->setCurrentIndex(nextPos); ui->pushNumber2->setChecked(true); ui->pushName3->setChecked(false); @@ -121,6 +210,7 @@ void CreateProposalDialog::onNextClicked() break; } case 1:{ + if (!validatePageTwo()) return; ui->stackedWidget->setCurrentIndex(nextPos); ui->pushNumber3->setChecked(true); ui->pushName3->setChecked(true); @@ -168,6 +258,14 @@ void CreateProposalDialog::onBackClicked() } } +void CreateProposalDialog::inform(const QString& text) +{ + if (!snackBar) snackBar = new SnackBar(nullptr, this); + snackBar->setText(text); + snackBar->resize(this->width(), snackBar->height()); + openDialog(snackBar, this); +} + CreateProposalDialog::~CreateProposalDialog() { delete ui; diff --git a/src/qt/pivx/createproposaldialog.h b/src/qt/pivx/createproposaldialog.h index bf0037adc475..a338093f5ac2 100644 --- a/src/qt/pivx/createproposaldialog.h +++ b/src/qt/pivx/createproposaldialog.h @@ -12,27 +12,45 @@ class CreateProposalDialog; class QPushButton; } +class GovernanceModel; +class SnackBar; +class WalletModel; + class CreateProposalDialog : public QDialog { Q_OBJECT public: - explicit CreateProposalDialog(QWidget *parent = nullptr); + explicit CreateProposalDialog(QWidget *parent, GovernanceModel* _govModel, WalletModel* _walletModel); ~CreateProposalDialog() override; public Q_SLOTS: void onNextClicked(); void onBackClicked(); + void propNameChanged(const QString& newText); + void propUrlChanged(const QString& newText); + void propAmountChanged(const QString& newText); + void propMonthsChanged(const QString& newText); + bool propaddressChanged(const QString& newText); + private: Ui::CreateProposalDialog *ui; - QPushButton *icConfirm1; - QPushButton *icConfirm2; - QPushButton *icConfirm3; + GovernanceModel* govModel{nullptr}; + WalletModel* walletModel{nullptr}; + SnackBar* snackBar{nullptr}; + QPushButton* icConfirm1{nullptr}; + QPushButton* icConfirm2{nullptr}; + QPushButton* icConfirm3{nullptr}; int pos = 0; void setupPageOne(); void setupPageTwo(); void setupPageThree(); + + bool validatePageOne(); + bool validatePageTwo(); + + void inform(const QString& text); }; #endif // CREATEPROPOSALDIALOG_H diff --git a/src/qt/pivx/governancemodel.cpp b/src/qt/pivx/governancemodel.cpp index 67653f086b50..0dbb9af5cff6 100644 --- a/src/qt/pivx/governancemodel.cpp +++ b/src/qt/pivx/governancemodel.cpp @@ -7,6 +7,8 @@ #include "budget/budgetmanager.h" #include "destination_io.h" #include "script/standard.h" +#include "utilmoneystr.h" +#include "utilstrencodings.h" #include @@ -56,3 +58,36 @@ bool GovernanceModel::hasProposals() return g_budgetman.HasAnyProposal(); } +CAmount GovernanceModel::getMaxAvailableBudgetAmount() const +{ + return Params().GetConsensus().nBudgetCycleBlocks; +} + +int GovernanceModel::getPropMaxPaymentsCount() const +{ + return Params().GetConsensus().nMaxProposalPayments; +} + +OperationResult GovernanceModel::validatePropURL(const QString& url) const +{ + std::string strError; + return {validateURL(url.toStdString(), strError, PROP_URL_MAX_SIZE), strError}; +} + +OperationResult GovernanceModel::validatePropAmount(CAmount amount) const +{ + if (amount > getMaxAvailableBudgetAmount()) { + return {false, strprintf("Amount exceeding the maximum available budget amount of %s PIV", FormatMoney(amount))}; + } + return {true}; +} + +OperationResult GovernanceModel::validatePropPaymentCount(int paymentCount) const +{ + if (paymentCount < 1) return { false, "Invalid payment count, must be greater than zero."}; + int nMaxPayments = getPropMaxPaymentsCount(); + if (paymentCount > nMaxPayments) { + return { false, strprintf("Invalid payment count, cannot be greater than %d", nMaxPayments)}; + } + return {true}; +} diff --git a/src/qt/pivx/governancemodel.h b/src/qt/pivx/governancemodel.h index ff06e1246bc3..be3af3769923 100644 --- a/src/qt/pivx/governancemodel.h +++ b/src/qt/pivx/governancemodel.h @@ -5,8 +5,9 @@ #ifndef GOVERNANCEMODEL_H #define GOVERNANCEMODEL_H -#include "uint256.h" #include "clientmodel.h" +#include "operationresult.h" +#include "uint256.h" #include #include @@ -56,6 +57,7 @@ struct ProposalInfo { class GovernanceModel { + static const int PROP_URL_MAX_SIZE = 100; public: explicit GovernanceModel(ClientModel* _clientModel); @@ -66,6 +68,14 @@ class GovernanceModel bool hasProposals(); // Whether a visual refresh is needed bool isRefreshNeeded() { return refreshNeeded; } + // Return the budget maximum available amount for the running chain + CAmount getMaxAvailableBudgetAmount() const; + // Return the proposal maximum payments count for the running chain + int getPropMaxPaymentsCount() const; + // Check if the URL is valid. + OperationResult validatePropURL(const QString& url) const; + OperationResult validatePropAmount(CAmount amount) const; + OperationResult validatePropPaymentCount(int paymentCount) const; private: ClientModel* clientModel{nullptr}; diff --git a/src/qt/pivx/governancewidget.cpp b/src/qt/pivx/governancewidget.cpp index 34b9b2a73b81..83869872f555 100644 --- a/src/qt/pivx/governancewidget.cpp +++ b/src/qt/pivx/governancewidget.cpp @@ -99,8 +99,8 @@ void GovernanceWidget::onVoteForPropClicked() void GovernanceWidget::onCreatePropClicked() { window->showHide(true); - CreateProposalDialog* dialog = new CreateProposalDialog(window); - openDialogWithOpaqueBackgroundY(dialog, window, 4.5, 5); + CreateProposalDialog* dialog = new CreateProposalDialog(window, governanceModel, walletModel); + openDialogWithOpaqueBackgroundY(dialog, window, 4.5, ui->left->height() < 700 ? 12 : 5); dialog->deleteLater(); } diff --git a/src/qt/pivx/pivxgui.cpp b/src/qt/pivx/pivxgui.cpp index 346a9fc92a92..17755b29b011 100644 --- a/src/qt/pivx/pivxgui.cpp +++ b/src/qt/pivx/pivxgui.cpp @@ -634,6 +634,7 @@ bool PIVXGUI::addWallet(const QString& name, WalletModel* walletModel) addressesWidget->setWalletModel(walletModel); masterNodesWidget->setWalletModel(walletModel); coldStakingWidget->setWalletModel(walletModel); + governancewidget->setWalletModel(walletModel); settingsWidget->setWalletModel(walletModel); // Connect actions.. @@ -644,6 +645,7 @@ bool PIVXGUI::addWallet(const QString& name, WalletModel* walletModel) connect(sendWidget, &SendWidget::message,this, &PIVXGUI::message); connect(receiveWidget, &ReceiveWidget::message,this, &PIVXGUI::message); connect(addressesWidget, &AddressesWidget::message,this, &PIVXGUI::message); + connect(governancewidget, &GovernanceWidget::message,this, &PIVXGUI::message); connect(settingsWidget, &SettingsWidget::message, this, &PIVXGUI::message); // Pass through transaction notifications diff --git a/src/qt/pivx/qtutils.cpp b/src/qt/pivx/qtutils.cpp index 72767029cacf..e68d86adf187 100644 --- a/src/qt/pivx/qtutils.cpp +++ b/src/qt/pivx/qtutils.cpp @@ -337,6 +337,7 @@ void setCssProperty(std::initializer_list args, const QString& value) void setCssProperty(QWidget* wid, const QString& value, bool forceUpdate) { + if (wid->property("cssClass") == value) return; wid->setProperty("cssClass", value); forceUpdateStyle(wid, forceUpdate); } From 0801c3c600dbe75a9b828494a72c419b6d758cca Mon Sep 17 00:00:00 2001 From: furszy Date: Sat, 22 May 2021 15:09:25 -0300 Subject: [PATCH 10/59] Governance model: createAndSendProposalFeeTx method created. --- src/qt/pivx/governancemodel.cpp | 45 +++++++++++++++++++++++++++++--- src/qt/pivx/governancemodel.h | 16 ++++++++++++ src/qt/pivx/governancewidget.cpp | 8 +++++- src/qt/pivx/governancewidget.h | 1 + src/qt/walletmodel.cpp | 39 +++++++++++++++++++++++++++ src/qt/walletmodel.h | 3 +++ 6 files changed, 108 insertions(+), 4 deletions(-) diff --git a/src/qt/pivx/governancemodel.cpp b/src/qt/pivx/governancemodel.cpp index 0dbb9af5cff6..ad809c14fa5d 100644 --- a/src/qt/pivx/governancemodel.cpp +++ b/src/qt/pivx/governancemodel.cpp @@ -6,15 +6,16 @@ #include "budget/budgetmanager.h" #include "destination_io.h" +#include "masternode-sync.h" #include "script/standard.h" #include "utilmoneystr.h" #include "utilstrencodings.h" +#include "walletmodel.h" #include -GovernanceModel::GovernanceModel(ClientModel* _clientModel) : clientModel(_clientModel) -{ -} +GovernanceModel::GovernanceModel(ClientModel* _clientModel) : clientModel(_clientModel) {} +void GovernanceModel::setWalletModel(WalletModel* _walletModel) { walletModel = _walletModel; } std::list GovernanceModel::getProposals() { @@ -59,6 +60,11 @@ bool GovernanceModel::hasProposals() } CAmount GovernanceModel::getMaxAvailableBudgetAmount() const +{ + return Params().GetConsensus().nBudgetCycleBlocks * COIN; +} + +int GovernanceModel::getNumBlocksPerBudgetCycle() const { return Params().GetConsensus().nBudgetCycleBlocks; } @@ -91,3 +97,36 @@ OperationResult GovernanceModel::validatePropPaymentCount(int paymentCount) cons } return {true}; } + +bool GovernanceModel::isTierTwoSync() +{ + return masternodeSync.IsBlockchainSynced(); +} + +OperationResult GovernanceModel::createProposal(const std::string& strProposalName, + const std::string& strURL, + int nPaymentCount, + CAmount nAmount, + const std::string& strPaymentAddr) +{ + // First get the next superblock height + const int nBlocksPerCycle = getNumBlocksPerBudgetCycle(); + const int chainHeight = clientModel->getNumBlocks(); + int nBlockStart = chainHeight - chainHeight % nBlocksPerCycle + nBlocksPerCycle; + + + // Parse address + const CTxDestination* dest = Standard::GetTransparentDestination(Standard::DecodeDestination(strPaymentAddr)); + if (!dest) return {false, "invalid recipient address for the proposal"}; + CScript scriptPubKey = GetScriptForDestination(*dest); + + // Validate proposal + CBudgetProposal proposal(strProposalName, strURL, nPaymentCount, scriptPubKey, nAmount, nBlockStart, UINT256_ZERO); + if (!proposal.IsWellFormed(g_budgetman.GetTotalBudget(proposal.GetBlockStart()))) { + return {false, strprintf("Proposal is not valid %s", proposal.IsInvalidReason())}; + } + + // Craft and send transaction. + auto opRes = walletModel->createAndSendProposalFeeTx(proposal); + if (!opRes) return opRes; +} \ No newline at end of file diff --git a/src/qt/pivx/governancemodel.h b/src/qt/pivx/governancemodel.h index be3af3769923..6b7b4a413b7d 100644 --- a/src/qt/pivx/governancemodel.h +++ b/src/qt/pivx/governancemodel.h @@ -55,12 +55,15 @@ struct ProposalInfo { } }; +class WalletModel; + class GovernanceModel { static const int PROP_URL_MAX_SIZE = 100; public: explicit GovernanceModel(ClientModel* _clientModel); + void setWalletModel(WalletModel* _walletModel); // Return proposals ordered by net votes std::list getProposals(); @@ -68,6 +71,8 @@ class GovernanceModel bool hasProposals(); // Whether a visual refresh is needed bool isRefreshNeeded() { return refreshNeeded; } + // Return the number of blocks per budget cycle + int getNumBlocksPerBudgetCycle() const; // Return the budget maximum available amount for the running chain CAmount getMaxAvailableBudgetAmount() const; // Return the proposal maximum payments count for the running chain @@ -76,9 +81,20 @@ class GovernanceModel OperationResult validatePropURL(const QString& url) const; OperationResult validatePropAmount(CAmount amount) const; OperationResult validatePropPaymentCount(int paymentCount) const; + // Whether the tier two network synchronization has finished or not + bool isTierTwoSync(); + // Creates a proposal, crafting and broadcasting the fee transaction, + // storing it locally to be broadcasted when the fee tx proposal depth + // fulfills the minimum depth requirements + OperationResult createProposal(const std::string& strProposalName, + const std::string& strURL, + int nPaymentCount, + CAmount nAmount, + const std::string& strPaymentAddr); private: ClientModel* clientModel{nullptr}; + WalletModel* walletModel{nullptr}; std::atomic refreshNeeded{false}; }; diff --git a/src/qt/pivx/governancewidget.cpp b/src/qt/pivx/governancewidget.cpp index 83869872f555..40acaa90c159 100644 --- a/src/qt/pivx/governancewidget.cpp +++ b/src/qt/pivx/governancewidget.cpp @@ -104,10 +104,16 @@ void GovernanceWidget::onCreatePropClicked() dialog->deleteLater(); } -void GovernanceWidget::loadClientModel() { +void GovernanceWidget::loadClientModel() +{ governanceModel = new GovernanceModel(clientModel); } +void GovernanceWidget::loadWalletModel() +{ + governanceModel->setWalletModel(walletModel); +} + void GovernanceWidget::showEvent(QShowEvent *event) { tryGridRefresh(); diff --git a/src/qt/pivx/governancewidget.h b/src/qt/pivx/governancewidget.h index 3ba6d095d939..fb53ddfcc7ed 100644 --- a/src/qt/pivx/governancewidget.h +++ b/src/qt/pivx/governancewidget.h @@ -57,6 +57,7 @@ class GovernanceWidget : public PWidget void showEvent(QShowEvent *event) override; void resizeEvent(QResizeEvent *event) override; void loadClientModel() override; + void loadWalletModel() override; public Q_SLOTS: void onVoteForPropClicked(); diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index 9f8ccb58dc1f..e9db97fc2a0f 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -6,6 +6,7 @@ #include "walletmodel.h" +#include "budget/budgetproposal.h" #include "init.h" // for ShutdownRequested() #include "interfaces/handler.h" #include "sapling/key_io_sapling.h" @@ -29,6 +30,14 @@ #include #include +// Util function +template +static std::string toHexStr(const T& obj) +{ + CDataStream ss(SER_DISK, CLIENT_VERSION); + ss << obj; + return HexStr(ss); +} WalletModel::WalletModel(CWallet* wallet, OptionsModel* optionsModel, QObject* parent) : QObject(parent), wallet(wallet), walletWrapper(*wallet), optionsModel(optionsModel), @@ -645,6 +654,36 @@ OperationResult WalletModel::PrepareShieldedTransaction(WalletModelTransaction* return operationResult; } +OperationResult WalletModel::createAndSendProposalFeeTx(CBudgetProposal& proposal) +{ + CTransactionRef wtx; + const uint256& nHash = proposal.GetHash(); + CReserveKey keyChange(wallet); + if (!wallet->CreateBudgetFeeTX(wtx, nHash, keyChange, false)) { // 50 PIV collateral for proposal + return {false ,"Error making fee transaction for proposal. Please check your wallet balance."}; + } + + //send the tx to the network + const CWallet::CommitResult& res = wallet->CommitTransaction(wtx, keyChange, g_connman.get()); + if (res.status != CWallet::CommitStatus::OK) { + return {false, strprintf("Cannot commit proposal fee transaction: %s", res.ToString())}; + } + proposal.SetFeeTxHash(wtx->GetHash()); + + { + // todo: encapsulate inside wallet module + LOCK(wallet->cs_wallet); + // Store own proposal data attached to the transaction that originated it. + // The proposal will be automatically broadcasted when it gets up to the minimum required confirmations. + assert(wallet->mapWallet.count(wtx->GetHash())); + auto& inWtx = wallet->mapWallet.at(wtx->GetHash()); // Internal tx + inWtx.SetComment("Proposal: " + proposal.GetName()); + inWtx.mapValue.emplace("proposal", toHexStr(proposal)); + } + + return {true}; +} + const CWalletTx* WalletModel::getTx(uint256 id) { return wallet->GetWalletTx(id); diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h index 162112184f5c..efff7b6faf75 100644 --- a/src/qt/walletmodel.h +++ b/src/qt/walletmodel.h @@ -25,6 +25,7 @@ #include class AddressTableModel; +class CBudgetProposal; class ClientModel; class OptionsModel; class RecentRequestsTableModel; @@ -223,6 +224,8 @@ class WalletModel : public QObject bool fromTransparent, const CCoinControl* coinControl = nullptr); + OperationResult createAndSendProposalFeeTx(CBudgetProposal& prop); + // Wallet encryption bool setWalletEncrypted(bool encrypted, const SecureString& passphrase); // Passphrase only needed when unlocking From f8d112bebf99f291d59e802a4ce131396d79ddb8 Mon Sep 17 00:00:00 2001 From: furszy Date: Sat, 22 May 2021 18:12:46 -0300 Subject: [PATCH 11/59] GUI: create proposal: summary page connected, proposal creation and automated broadcast flow added. --- src/budget/budgetproposal.h | 3 +- src/qt/pivx/createproposaldialog.cpp | 32 ++++++- src/qt/pivx/createproposaldialog.h | 3 + src/qt/pivx/governancemodel.cpp | 122 +++++++++++++++++++++------ src/qt/pivx/governancemodel.h | 26 +++++- src/qt/pivx/governancewidget.cpp | 23 ++++- src/qt/pivx/governancewidget.h | 8 +- src/qt/pivx/proposalcard.cpp | 4 +- 8 files changed, 184 insertions(+), 37 deletions(-) diff --git a/src/budget/budgetproposal.h b/src/budget/budgetproposal.h index eeef33a9b2b3..138fdb1fe655 100644 --- a/src/budget/budgetproposal.h +++ b/src/budget/budgetproposal.h @@ -92,10 +92,11 @@ class CBudgetProposal std::map GetVotes() const { return mapVotes; } int GetYeas() const { return GetVoteCount(CBudgetVote::VOTE_YES); } int GetNays() const { return GetVoteCount(CBudgetVote::VOTE_NO); } - int GetAbstains() const { return GetVoteCount(CBudgetVote::VOTE_ABSTAIN); }; + int GetAbstains() const { return GetVoteCount(CBudgetVote::VOTE_ABSTAIN); } CAmount GetAmount() const { return nAmount; } void SetAllotted(CAmount nAllottedIn) { nAllotted = nAllottedIn; } CAmount GetAllotted() const { return nAllotted; } + void SetFeeTxHash(const uint256& txid) { nFeeTXHash = txid; } uint256 GetHash() const { diff --git a/src/qt/pivx/createproposaldialog.cpp b/src/qt/pivx/createproposaldialog.cpp index 1e6d545fafba..8ecd6b346d72 100644 --- a/src/qt/pivx/createproposaldialog.cpp +++ b/src/qt/pivx/createproposaldialog.cpp @@ -166,7 +166,7 @@ bool CreateProposalDialog::validatePageOne() bool CreateProposalDialog::validatePageTwo() { - QString sPaymentCount = ui->lineEditAmount->text(); + QString sPaymentCount = ui->lineEditMonths->text(); if (sPaymentCount.isEmpty()) { inform(tr("Proposal amount field cannot be empty")); return false; @@ -194,6 +194,33 @@ bool CreateProposalDialog::validatePageTwo() return true; } +void CreateProposalDialog::loadSummary() +{ + ui->labelResultName->setText(ui->lineEditPropName->text()); + ui->labelResultUrl->setText(ui->lineEditURL->text()); + ui->labelResultAmount->setText(GUIUtil::formatBalance(ui->lineEditAmount->text().toInt() * COIN)); + ui->labelResultMonths->setText(ui->lineEditMonths->text()); + ui->labelResultAddress->setText(ui->lineEditAddress->text()); + ui->labelResultUrl->setText(ui->lineEditURL->text()); +} + +void CreateProposalDialog::sendProposal() +{ + CAmount amount = ui->lineEditAmount->text().toInt() * COIN; + auto opRes = govModel->createProposal( + ui->lineEditPropName->text().toStdString(), + ui->lineEditURL->text().toStdString(), + ui->lineEditMonths->text().toInt(), + amount, + ui->lineEditAddress->text().toStdString() + ); + if (!opRes) { + inform(QString::fromStdString(opRes.getError())); + return; + } + accept(); +} + void CreateProposalDialog::onNextClicked() { int nextPos = pos + 1; @@ -211,6 +238,7 @@ void CreateProposalDialog::onNextClicked() } case 1:{ if (!validatePageTwo()) return; + loadSummary(); ui->stackedWidget->setCurrentIndex(nextPos); ui->pushNumber3->setChecked(true); ui->pushName3->setChecked(true); @@ -221,7 +249,7 @@ void CreateProposalDialog::onNextClicked() break; } case 2:{ - accept(); + sendProposal(); } } pos = nextPos; diff --git a/src/qt/pivx/createproposaldialog.h b/src/qt/pivx/createproposaldialog.h index a338093f5ac2..3daa6222e866 100644 --- a/src/qt/pivx/createproposaldialog.h +++ b/src/qt/pivx/createproposaldialog.h @@ -43,6 +43,9 @@ public Q_SLOTS: QPushButton* icConfirm3{nullptr}; int pos = 0; + void loadSummary(); + void sendProposal(); + void setupPageOne(); void setupPageTwo(); void setupPageThree(); diff --git a/src/qt/pivx/governancemodel.cpp b/src/qt/pivx/governancemodel.cpp index ad809c14fa5d..015f9324b3b4 100644 --- a/src/qt/pivx/governancemodel.cpp +++ b/src/qt/pivx/governancemodel.cpp @@ -6,6 +6,7 @@ #include "budget/budgetmanager.h" #include "destination_io.h" +#include "guiconstants.h" #include "masternode-sync.h" #include "script/standard.h" #include "utilmoneystr.h" @@ -13,44 +14,64 @@ #include "walletmodel.h" #include +#include GovernanceModel::GovernanceModel(ClientModel* _clientModel) : clientModel(_clientModel) {} +GovernanceModel::~GovernanceModel() {} void GovernanceModel::setWalletModel(WalletModel* _walletModel) { walletModel = _walletModel; } -std::list GovernanceModel::getProposals() +ProposalInfo GovernanceModel::buidProposalInfo(const CBudgetProposal* prop, + const std::vector& currentBudget, + bool isPending) { - if (!clientModel) return {}; - std::list ret; - std::vector budget = g_budgetman.GetBudget(); - for (const auto& prop : g_budgetman.GetAllProposals()) { - CTxDestination recipient; - ExtractDestination(prop->GetPayee(), recipient); - - // Calculate status - int votesYes = prop->GetYeas(); - int votesNo = prop->GetNays(); - ProposalInfo::Status status; - if (std::find(budget.begin(), budget.end(), *prop) != budget.end()) { + CTxDestination recipient; + ExtractDestination(prop->GetPayee(), recipient); + + // Calculate status + int votesYes = prop->GetYeas(); + int votesNo = prop->GetNays(); + ProposalInfo::Status status; + + if (isPending) { + // Proposal waiting for confirmation to be broadcasted. + status = ProposalInfo::WAITING_FOR_APPROVAL; + } else { + if (std::find(currentBudget.begin(), currentBudget.end(), *prop) != currentBudget.end()) { status = ProposalInfo::PASSING; - } else if (votesYes > votesNo){ + } else if (votesYes > votesNo) { status = ProposalInfo::PASSING_NOT_FUNDED; } else { status = ProposalInfo::NOT_PASSING; } + } + + + return ProposalInfo(prop->GetHash(), + prop->GetName(), + prop->GetURL(), + votesYes, + votesNo, + Standard::EncodeDestination(recipient), + prop->GetAmount(), + prop->GetTotalPaymentCount(), + prop->GetRemainingPaymentCount(clientModel->getLastBlockProcessedHeight()), + status); +} + +std::list GovernanceModel::getProposals() +{ + if (!clientModel) return {}; + std::list ret; + std::vector budget = g_budgetman.GetBudget(); + for (const auto& prop : g_budgetman.GetAllProposalsOrdered()) { + ret.emplace_back(buidProposalInfo(prop, budget, false)); + } - ret.emplace_back( - prop->GetHash(), - prop->GetName(), - prop->GetURL(), - votesYes, - votesNo, - Standard::EncodeDestination(recipient), - prop->GetAmount(), - prop->GetTotalPaymentCount(), - prop->GetRemainingPaymentCount(clientModel->getLastBlockProcessedHeight()), - status - ); + // Add pending proposals + for (const auto& prop : waitingPropsForConfirmations) { + ret.emplace_back(buidProposalInfo(&prop, budget, true)); } + return ret; } @@ -129,4 +150,51 @@ OperationResult GovernanceModel::createProposal(const std::string& strProposalNa // Craft and send transaction. auto opRes = walletModel->createAndSendProposalFeeTx(proposal); if (!opRes) return opRes; -} \ No newline at end of file + scheduleBroadcast(proposal); + + return {true}; +} + +void GovernanceModel::scheduleBroadcast(const CBudgetProposal& proposal) +{ + // Cache the proposal to be sent as soon as it gets the minimum required confirmations + // without requiring user interaction + waitingPropsForConfirmations.emplace_back(proposal); + + // Launch timer if it's not already running + if (!pollTimer) pollTimer = new QTimer(this); + if (!pollTimer->isActive()) { + connect(pollTimer, &QTimer::timeout, this, &GovernanceModel::pollGovernanceChanged); + pollTimer->start(MODEL_UPDATE_DELAY * 60 * 3.5); // Every 3.5 minutes + } +} + +void GovernanceModel::pollGovernanceChanged() +{ + if (!isTierTwoSync()) return; + + // Try to broadcast any pending for confirmations proposal + auto it = waitingPropsForConfirmations.begin(); + while (it != waitingPropsForConfirmations.end()) { + if (!g_budgetman.AddProposal(*it)) { + LogPrint(BCLog::QT, "Cannot broadcast budget proposal - %s", it->IsInvalidReason()); + it++; + continue; + } + it->Relay(); + it = waitingPropsForConfirmations.erase(it); + } + + // If there are no more waiting proposals, turn the timer off. + if (waitingPropsForConfirmations.empty()) { + pollTimer->stop(); + } +} + +void GovernanceModel::stopPolling() +{ + if (pollTimer && pollTimer->isActive()) { + pollTimer->stop(); + } +} + diff --git a/src/qt/pivx/governancemodel.h b/src/qt/pivx/governancemodel.h index 6b7b4a413b7d..020a3ca8db85 100644 --- a/src/qt/pivx/governancemodel.h +++ b/src/qt/pivx/governancemodel.h @@ -14,6 +14,8 @@ #include #include +#include + struct ProposalInfo { public: enum Status { @@ -55,14 +57,20 @@ struct ProposalInfo { } }; +class CBudgetProposal; class WalletModel; -class GovernanceModel +QT_BEGIN_NAMESPACE +class QTimer; +QT_END_NAMESPACE + +class GovernanceModel : public QObject { static const int PROP_URL_MAX_SIZE = 100; public: explicit GovernanceModel(ClientModel* _clientModel); + ~GovernanceModel(); void setWalletModel(WalletModel* _walletModel); // Return proposals ordered by net votes @@ -92,10 +100,26 @@ class GovernanceModel int nPaymentCount, CAmount nAmount, const std::string& strPaymentAddr); +public Q_SLOTS: + void pollGovernanceChanged(); + private: ClientModel* clientModel{nullptr}; WalletModel* walletModel{nullptr}; std::atomic refreshNeeded{false}; + + QTimer* pollTimer{nullptr}; + // Cached proposals waiting for the minimum required confirmations + // to be broadcasted to the network. + std::vector waitingPropsForConfirmations; + + void scheduleBroadcast(const CBudgetProposal& proposal); + void stopPolling(); + + // Util function to create a ProposalInfo object + ProposalInfo buidProposalInfo(const CBudgetProposal* prop, + const std::vector& currentBudget, + bool isPending); }; #endif // GOVERNANCEMODEL_H diff --git a/src/qt/pivx/governancewidget.cpp b/src/qt/pivx/governancewidget.cpp index 40acaa90c159..4a1ca17ef1e1 100644 --- a/src/qt/pivx/governancewidget.cpp +++ b/src/qt/pivx/governancewidget.cpp @@ -8,6 +8,7 @@ #include #include +#include GovernanceWidget::GovernanceWidget(PIVXGUI* parent) : PWidget(parent), @@ -100,7 +101,11 @@ void GovernanceWidget::onCreatePropClicked() { window->showHide(true); CreateProposalDialog* dialog = new CreateProposalDialog(window, governanceModel, walletModel); - openDialogWithOpaqueBackgroundY(dialog, window, 4.5, ui->left->height() < 700 ? 12 : 5); + if (openDialogWithOpaqueBackgroundY(dialog, window, 4.5, ui->left->height() < 700 ? 12 : 5)) { + // future: make this refresh atomic, no need to refresh the entire grid. + tryGridRefresh(true); + inform(tr("Proposal transaction fee broadcasted!")); + } dialog->deleteLater(); } @@ -116,7 +121,17 @@ void GovernanceWidget::loadWalletModel() void GovernanceWidget::showEvent(QShowEvent *event) { - tryGridRefresh(); + tryGridRefresh(true); // future: move to background worker + if (!refreshTimer) refreshTimer = new QTimer(this); + if (!refreshTimer->isActive()) { + connect(refreshTimer, &QTimer::timeout, [this]() { tryGridRefresh(true); }); + refreshTimer->start(1000 * 60 * 3.5); // Try to refresh screen 3.5 minutes + } +} + +void GovernanceWidget::hideEvent(QHideEvent *event) +{ + refreshTimer->stop(); } void GovernanceWidget::resizeEvent(QResizeEvent *event) @@ -125,10 +140,10 @@ void GovernanceWidget::resizeEvent(QResizeEvent *event) tryGridRefresh(); } -void GovernanceWidget::tryGridRefresh() +void GovernanceWidget::tryGridRefresh(bool force) { int _propsPerRow = calculateColumnsPerRow(); - if (_propsPerRow != propsPerRow) { + if (_propsPerRow != propsPerRow || force) { propsPerRow = _propsPerRow; refreshCardsGrid(true); } diff --git a/src/qt/pivx/governancewidget.h b/src/qt/pivx/governancewidget.h index fb53ddfcc7ed..947a598e6682 100644 --- a/src/qt/pivx/governancewidget.h +++ b/src/qt/pivx/governancewidget.h @@ -18,6 +18,10 @@ namespace Ui { class governancewidget; } +QT_BEGIN_NAMESPACE +class QTimer; +QT_END_NAMESPACE + class PIVXGUI; class GovernanceModel; @@ -55,6 +59,7 @@ class GovernanceWidget : public PWidget ~GovernanceWidget() override; void showEvent(QShowEvent *event) override; + void hideEvent(QHideEvent *event) override; void resizeEvent(QResizeEvent *event) override; void loadClientModel() override; void loadWalletModel() override; @@ -69,9 +74,10 @@ public Q_SLOTS: QGridLayout* gridLayout{nullptr}; // cards std::vector cards; int propsPerRow = 0; + QTimer* refreshTimer{nullptr}; void showEmptyScreen(bool show); - void tryGridRefresh(); + void tryGridRefresh(bool force=false); ProposalCard* newCard(); void refreshCardsGrid(bool forceRefresh); int calculateColumnsPerRow(); diff --git a/src/qt/pivx/proposalcard.cpp b/src/qt/pivx/proposalcard.cpp index 13a003b31dc0..ffcce1a9c8f5 100644 --- a/src/qt/pivx/proposalcard.cpp +++ b/src/qt/pivx/proposalcard.cpp @@ -58,8 +58,10 @@ void ProposalCard::setProposal(const ProposalInfo& _proposalInfo) ui->labelStatus->setText(tr("Passing")); } else { cssClassStatus = "card-status-no-votes"; - ui->labelStatus->setText(tr("No Votes")); + ui->labelStatus->setText(proposalInfo.status == ProposalInfo::Status::WAITING_FOR_APPROVAL ? + tr("Waiting") : tr("No Votes")); } + setCssProperty(ui->labelStatus, cssClassStatus, true); } From a50276f7d096688ea323f96b8cdf327d429ec187 Mon Sep 17 00:00:00 2001 From: furszy Date: Mon, 24 May 2021 12:23:40 -0300 Subject: [PATCH 12/59] GUI: governancemodel load and schedule waiting for approval proposals at startup. --- src/qt/pivx.cpp | 8 +++++-- src/qt/pivx/governancemodel.cpp | 38 ++++++++++++++++++++++++++++++-- src/qt/pivx/governancemodel.h | 4 +++- src/qt/pivx/governancewidget.cpp | 4 ++-- src/qt/pivx/governancewidget.h | 2 +- src/qt/pivx/pivxgui.cpp | 6 +++++ src/qt/pivx/pivxgui.h | 1 + src/qt/transactionrecord.h | 8 +++---- src/qt/transactiontablemodel.cpp | 26 +++++++++++++++++----- src/qt/transactiontablemodel.h | 4 ++++ src/qt/walletmodel.cpp | 4 ++++ src/qt/walletmodel.h | 1 + 12 files changed, 89 insertions(+), 17 deletions(-) diff --git a/src/qt/pivx.cpp b/src/qt/pivx.cpp index c15a221f0e48..eecabcb90700 100644 --- a/src/qt/pivx.cpp +++ b/src/qt/pivx.cpp @@ -23,6 +23,7 @@ #include "winshutdownmonitor.h" #ifdef ENABLE_WALLET +#include "qt/pivx/governancemodel.h" #include "paymentserver.h" #include "walletmodel.h" #include "interfaces/wallet.h" @@ -41,8 +42,6 @@ #include "wallet/wallet.h" #endif -#include - #include #include #include @@ -236,6 +235,7 @@ public Q_SLOTS: #ifdef ENABLE_WALLET PaymentServer* paymentServer{nullptr}; WalletModel* walletModel{nullptr}; + GovernanceModel* govModel{nullptr}; #endif int returnValue{0}; QTranslator qtTranslatorBase, qtTranslator, translatorBase, translator; @@ -488,11 +488,15 @@ void BitcoinApplication::initializeResult(int retval) window->setClientModel(clientModel); #ifdef ENABLE_WALLET + govModel = new GovernanceModel(clientModel); // TODO: Expose secondary wallets if (!vpwallets.empty()) { walletModel = new WalletModel(vpwallets[0], optionsModel); walletModel->setClientModel(clientModel); + govModel->setWalletModel(walletModel); + walletModel->init(); + window->setGovModel(govModel); window->addWallet(PIVXGUI::DEFAULT_WALLET, walletModel); window->setCurrentWallet(PIVXGUI::DEFAULT_WALLET); } diff --git a/src/qt/pivx/governancemodel.cpp b/src/qt/pivx/governancemodel.cpp index 015f9324b3b4..5e3b6e1887cb 100644 --- a/src/qt/pivx/governancemodel.cpp +++ b/src/qt/pivx/governancemodel.cpp @@ -9,6 +9,8 @@ #include "guiconstants.h" #include "masternode-sync.h" #include "script/standard.h" +#include "qt/transactiontablemodel.h" +#include "qt/transactionrecord.h" #include "utilmoneystr.h" #include "utilstrencodings.h" #include "walletmodel.h" @@ -18,7 +20,12 @@ GovernanceModel::GovernanceModel(ClientModel* _clientModel) : clientModel(_clientModel) {} GovernanceModel::~GovernanceModel() {} -void GovernanceModel::setWalletModel(WalletModel* _walletModel) { walletModel = _walletModel; } + +void GovernanceModel::setWalletModel(WalletModel* _walletModel) +{ + walletModel = _walletModel; + connect(walletModel->getTransactionTableModel(), &TransactionTableModel::txLoaded, this, &GovernanceModel::txLoaded); +} ProposalInfo GovernanceModel::buidProposalInfo(const CBudgetProposal* prop, const std::vector& currentBudget, @@ -173,12 +180,19 @@ void GovernanceModel::pollGovernanceChanged() { if (!isTierTwoSync()) return; + int chainHeight = clientModel->getNumBlocks(); // Try to broadcast any pending for confirmations proposal auto it = waitingPropsForConfirmations.begin(); while (it != waitingPropsForConfirmations.end()) { if (!g_budgetman.AddProposal(*it)) { LogPrint(BCLog::QT, "Cannot broadcast budget proposal - %s", it->IsInvalidReason()); - it++; + if (it->GetBlockStart() >= chainHeight) { + // Edge case, the proposal was never broadcasted before the next superblock, can be removed. + // future: notify the user about it. + it = waitingPropsForConfirmations.erase(it); + } else { + it++; + } continue; } it->Relay(); @@ -198,3 +212,23 @@ void GovernanceModel::stopPolling() } } +void GovernanceModel::txLoaded(const QString& id, const int txType, const int txStatus) +{ + if (txType == TransactionRecord::SendToNobody) { + // If this is a proposal fee, parse it. + const auto& wtx = walletModel->getTx(uint256S(id.toStdString())); + assert(wtx); + const auto& it = wtx->mapValue.find("proposal"); + if (it != wtx->mapValue.end()) { + const std::vector vec = ParseHex(it->second); + if (vec.empty()) return; + CDataStream ss(vec, SER_DISK, CLIENT_VERSION); + CBudgetProposal proposal; + ss >> proposal; + if (!g_budgetman.HaveProposal(proposal.GetHash()) && + proposal.GetBlockStart() < clientModel->getNumBlocks()) { + scheduleBroadcast(proposal); + } + } + } +} \ No newline at end of file diff --git a/src/qt/pivx/governancemodel.h b/src/qt/pivx/governancemodel.h index 020a3ca8db85..c54547692bec 100644 --- a/src/qt/pivx/governancemodel.h +++ b/src/qt/pivx/governancemodel.h @@ -58,6 +58,7 @@ struct ProposalInfo { }; class CBudgetProposal; +class TransactionRecord; class WalletModel; QT_BEGIN_NAMESPACE @@ -70,7 +71,7 @@ class GovernanceModel : public QObject public: explicit GovernanceModel(ClientModel* _clientModel); - ~GovernanceModel(); + ~GovernanceModel() override; void setWalletModel(WalletModel* _walletModel); // Return proposals ordered by net votes @@ -102,6 +103,7 @@ class GovernanceModel : public QObject const std::string& strPaymentAddr); public Q_SLOTS: void pollGovernanceChanged(); + void txLoaded(const QString& hash, const int txType, const int txStatus); private: ClientModel* clientModel{nullptr}; diff --git a/src/qt/pivx/governancewidget.cpp b/src/qt/pivx/governancewidget.cpp index 4a1ca17ef1e1..5a7188a73dfa 100644 --- a/src/qt/pivx/governancewidget.cpp +++ b/src/qt/pivx/governancewidget.cpp @@ -109,9 +109,9 @@ void GovernanceWidget::onCreatePropClicked() dialog->deleteLater(); } -void GovernanceWidget::loadClientModel() +void GovernanceWidget::setGovModel(GovernanceModel* _model) { - governanceModel = new GovernanceModel(clientModel); + governanceModel = _model; } void GovernanceWidget::loadWalletModel() diff --git a/src/qt/pivx/governancewidget.h b/src/qt/pivx/governancewidget.h index 947a598e6682..bcecf201165c 100644 --- a/src/qt/pivx/governancewidget.h +++ b/src/qt/pivx/governancewidget.h @@ -61,8 +61,8 @@ class GovernanceWidget : public PWidget void showEvent(QShowEvent *event) override; void hideEvent(QHideEvent *event) override; void resizeEvent(QResizeEvent *event) override; - void loadClientModel() override; void loadWalletModel() override; + void setGovModel(GovernanceModel* _model); public Q_SLOTS: void onVoteForPropClicked(); diff --git a/src/qt/pivx/pivxgui.cpp b/src/qt/pivx/pivxgui.cpp index 17755b29b011..0bcd7fb3f671 100644 --- a/src/qt/pivx/pivxgui.cpp +++ b/src/qt/pivx/pivxgui.cpp @@ -619,6 +619,12 @@ void PIVXGUI::openFAQ(SettingsFaqWidget::Section section) #ifdef ENABLE_WALLET +void PIVXGUI::setGovModel(GovernanceModel* govModel) +{ + if (!stackedContainer || !clientModel) return; + governancewidget->setGovModel(govModel); +} + bool PIVXGUI::addWallet(const QString& name, WalletModel* walletModel) { // Single wallet supported for now.. diff --git a/src/qt/pivx/pivxgui.h b/src/qt/pivx/pivxgui.h index 75e75236a7ba..0498fd2d3756 100644 --- a/src/qt/pivx/pivxgui.h +++ b/src/qt/pivx/pivxgui.h @@ -98,6 +98,7 @@ public Q_SLOTS: /** Show incoming transaction notification for new transactions. */ void incomingTransaction(const QString& date, int unit, const CAmount& amount, const QString& type, const QString& address); #ifdef ENABLE_WALLET + void setGovModel(GovernanceModel* govModel); /** Set the wallet model. The wallet model represents a bitcoin wallet, and offers access to the list of transactions, address book and sending functionality. diff --git a/src/qt/transactionrecord.h b/src/qt/transactionrecord.h index 6bf51cd2cc12..9d3f6a1c3ce6 100644 --- a/src/qt/transactionrecord.h +++ b/src/qt/transactionrecord.h @@ -28,8 +28,8 @@ class TransactionStatus { } - enum Status { - Confirmed, /**< Have 6 or more confirmations (normal tx) or fully mature (mined tx) **/ + enum Status : uint16_t{ + Confirmed = 0, /**< Have 6 or more confirmations (normal tx) or fully mature (mined tx) **/ /// Normal (sent/received) transactions OpenUntilDate, /**< Transaction not yet final, waiting for date */ OpenUntilBlock, /**< Transaction not yet final, waiting for block */ @@ -72,8 +72,8 @@ class TransactionStatus class TransactionRecord { public: - enum Type { - Other, + enum Type : uint16_t { + Other = 0, Generated, StakeMint, StakeZPIV, diff --git a/src/qt/transactiontablemodel.cpp b/src/qt/transactiontablemodel.cpp index 3789f52a507d..70395c9adbce 100644 --- a/src/qt/transactiontablemodel.cpp +++ b/src/qt/transactiontablemodel.cpp @@ -128,6 +128,7 @@ class TransactionTablePriv tasks.append( QtConcurrent::run( convertTxToRecords, + this, wallet, std::vector(walletTxes.begin() + totalSumSize, walletTxes.begin() + totalSumSize + subsetSize) ) @@ -137,8 +138,8 @@ class TransactionTablePriv // Now take the remaining ones and do the work here std::size_t const remainingSize = txesSize - totalSumSize; - auto res = convertTxToRecords(wallet, - std::vector(walletTxes.end() - remainingSize, walletTxes.end()) + auto res = convertTxToRecords(this, wallet, + std::vector(walletTxes.end() - remainingSize, walletTxes.end()) ); cachedWallet.append(res.records); nFirstLoadedTxTime = res.nFirstLoadedTxTime; @@ -157,13 +158,22 @@ class TransactionTablePriv } else { // Single thread flow - ConvertTxToVectorResult convertRes = convertTxToRecords(wallet, walletTxes); + ConvertTxToVectorResult convertRes = convertTxToRecords(this, wallet, walletTxes); cachedWallet.append(convertRes.records); nFirstLoadedTxTime = convertRes.nFirstLoadedTxTime; } } - static ConvertTxToVectorResult convertTxToRecords(const CWallet* wallet, const std::vector& walletTxes) { + void emitTxLoaded(const TransactionRecord& rec) + { + Q_EMIT parent->txLoaded(QString::fromStdString(rec.hash.GetHex()), + rec.type, rec.status.status); + } + + static ConvertTxToVectorResult convertTxToRecords(TransactionTablePriv* tablePriv, + const CWallet* wallet, + const std::vector& walletTxes) + { ConvertTxToVectorResult res; for (const auto& tx : walletTxes) { QList records = TransactionRecord::decomposeTransaction(wallet, tx); @@ -172,6 +182,9 @@ class TransactionTablePriv if (res.nFirstLoadedTxTime == 0 || res.nFirstLoadedTxTime > time) { res.nFirstLoadedTxTime = time; } + for (const auto& rec : records) { + tablePriv->emitTxLoaded(rec); + } res.records.append(records); } return res; @@ -296,10 +309,13 @@ TransactionTableModel::TransactionTableModel(CWallet* wallet, WalletModel* paren fProcessingQueuedTransactions(false) { columns << QString() << QString() << tr("Date") << tr("Type") << tr("Address") << BitcoinUnits::getAmountColumnTitle(walletModel->getOptionsModel()->getDisplayUnit()); - priv->refreshWallet(); connect(walletModel->getOptionsModel(), &OptionsModel::displayUnitChanged, this, &TransactionTableModel::updateDisplayUnit); +} +void TransactionTableModel::init() +{ + priv->refreshWallet(); subscribeToCoreSignals(); } diff --git a/src/qt/transactiontablemodel.h b/src/qt/transactiontablemodel.h index 47c8a1a75e9c..52d2a975b8b6 100644 --- a/src/qt/transactiontablemodel.h +++ b/src/qt/transactiontablemodel.h @@ -32,6 +32,7 @@ class TransactionTableModel : public QAbstractTableModel public: explicit TransactionTableModel(CWallet* wallet, WalletModel* parent = nullptr); ~TransactionTableModel() override; + void init(); enum ColumnIndex { Status = 0, @@ -83,6 +84,9 @@ class TransactionTableModel : public QAbstractTableModel bool processingQueuedTransactions() const { return fProcessingQueuedTransactions; } Q_SIGNALS: + // Emitted only during startup when records gets parsed + void txLoaded(const QString& hash, const int txType, const int txStatus); + // Emitted when a transaction that belongs to this wallet gets connected to the chain and/or committed locally. void txArrived(const QString& hash, const bool isCoinStake, const bool isCSAnyType); private: diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index e9db97fc2a0f..dc7d17161bf4 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -50,7 +50,11 @@ WalletModel::WalletModel(CWallet* wallet, OptionsModel* optionsModel, QObject* p addressTableModel = new AddressTableModel(wallet, this); transactionTableModel = new TransactionTableModel(wallet, this); recentRequestsTableModel = new RecentRequestsTableModel(wallet, this); +} +void WalletModel::init() +{ + transactionTableModel->init(); // This timer will be fired repeatedly to update the balance pollTimer = new QTimer(this); connect(pollTimer, &QTimer::timeout, this, &WalletModel::pollBalanceChanged); diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h index efff7b6faf75..c0e1044926bd 100644 --- a/src/qt/walletmodel.h +++ b/src/qt/walletmodel.h @@ -113,6 +113,7 @@ class WalletModel : public QObject public: explicit WalletModel(CWallet* wallet, OptionsModel* optionsModel, QObject* parent = 0); ~WalletModel(); + void init(); enum StatusCode // Returned by sendCoins { From 18ae037fc9b38254173b6e37ba24d53a57b3d378 Mon Sep 17 00:00:00 2001 From: furszy Date: Mon, 24 May 2021 12:56:08 -0300 Subject: [PATCH 13/59] GUI governance: calculate, cache and connect budget available and allocated amounts. --- src/qt/pivx/governancemodel.cpp | 25 ++++++++++++++----------- src/qt/pivx/governancemodel.h | 11 ++++++++--- src/qt/pivx/governancewidget.cpp | 27 ++++++++++++++++++++------- src/qt/pivx/governancewidget.h | 2 ++ 4 files changed, 44 insertions(+), 21 deletions(-) diff --git a/src/qt/pivx/governancemodel.cpp b/src/qt/pivx/governancemodel.cpp index 5e3b6e1887cb..6877ed94b7ce 100644 --- a/src/qt/pivx/governancemodel.cpp +++ b/src/qt/pivx/governancemodel.cpp @@ -27,9 +27,7 @@ void GovernanceModel::setWalletModel(WalletModel* _walletModel) connect(walletModel->getTransactionTableModel(), &TransactionTableModel::txLoaded, this, &GovernanceModel::txLoaded); } -ProposalInfo GovernanceModel::buidProposalInfo(const CBudgetProposal* prop, - const std::vector& currentBudget, - bool isPending) +ProposalInfo GovernanceModel::buidProposalInfo(const CBudgetProposal* prop, bool isPassing, bool isPending) { CTxDestination recipient; ExtractDestination(prop->GetPayee(), recipient); @@ -43,7 +41,7 @@ ProposalInfo GovernanceModel::buidProposalInfo(const CBudgetProposal* prop, // Proposal waiting for confirmation to be broadcasted. status = ProposalInfo::WAITING_FOR_APPROVAL; } else { - if (std::find(currentBudget.begin(), currentBudget.end(), *prop) != currentBudget.end()) { + if (isPassing) { status = ProposalInfo::PASSING; } else if (votesYes > votesNo) { status = ProposalInfo::PASSING_NOT_FUNDED; @@ -71,14 +69,15 @@ std::list GovernanceModel::getProposals() std::list ret; std::vector budget = g_budgetman.GetBudget(); for (const auto& prop : g_budgetman.GetAllProposalsOrdered()) { - ret.emplace_back(buidProposalInfo(prop, budget, false)); + bool isPassing = std::find(budget.begin(), budget.end(), *prop) != budget.end(); + ret.emplace_back(buidProposalInfo(prop, isPassing, false)); + if (isPassing) allocatedAmount += prop->GetAmount(); } // Add pending proposals for (const auto& prop : waitingPropsForConfirmations) { - ret.emplace_back(buidProposalInfo(&prop, budget, true)); + ret.emplace_back(buidProposalInfo(&prop, false, true)); } - return ret; } @@ -102,6 +101,13 @@ int GovernanceModel::getPropMaxPaymentsCount() const return Params().GetConsensus().nMaxProposalPayments; } +int GovernanceModel::getNextSuperblockHeight() const +{ + const int nBlocksPerCycle = getNumBlocksPerBudgetCycle(); + const int chainHeight = clientModel->getNumBlocks(); + return chainHeight - chainHeight % nBlocksPerCycle + nBlocksPerCycle; +} + OperationResult GovernanceModel::validatePropURL(const QString& url) const { std::string strError; @@ -138,10 +144,7 @@ OperationResult GovernanceModel::createProposal(const std::string& strProposalNa const std::string& strPaymentAddr) { // First get the next superblock height - const int nBlocksPerCycle = getNumBlocksPerBudgetCycle(); - const int chainHeight = clientModel->getNumBlocks(); - int nBlockStart = chainHeight - chainHeight % nBlocksPerCycle + nBlocksPerCycle; - + int nBlockStart = getNextSuperblockHeight(); // Parse address const CTxDestination* dest = Standard::GetTransparentDestination(Standard::DecodeDestination(strPaymentAddr)); diff --git a/src/qt/pivx/governancemodel.h b/src/qt/pivx/governancemodel.h index c54547692bec..89e2a15fee28 100644 --- a/src/qt/pivx/governancemodel.h +++ b/src/qt/pivx/governancemodel.h @@ -86,6 +86,10 @@ class GovernanceModel : public QObject CAmount getMaxAvailableBudgetAmount() const; // Return the proposal maximum payments count for the running chain int getPropMaxPaymentsCount() const; + int getNextSuperblockHeight() const; + // Returns the sum of all of the passing proposals + CAmount getBudgetAllocatedAmount() const { return allocatedAmount; }; + CAmount getBudgetAvailableAmount() const { return getMaxAvailableBudgetAmount() - allocatedAmount; }; // Check if the URL is valid. OperationResult validatePropURL(const QString& url) const; OperationResult validatePropAmount(CAmount amount) const; @@ -110,6 +114,9 @@ public Q_SLOTS: WalletModel* walletModel{nullptr}; std::atomic refreshNeeded{false}; + // Cached amount + CAmount allocatedAmount{0}; + QTimer* pollTimer{nullptr}; // Cached proposals waiting for the minimum required confirmations // to be broadcasted to the network. @@ -119,9 +126,7 @@ public Q_SLOTS: void stopPolling(); // Util function to create a ProposalInfo object - ProposalInfo buidProposalInfo(const CBudgetProposal* prop, - const std::vector& currentBudget, - bool isPending); + ProposalInfo buidProposalInfo(const CBudgetProposal* prop, bool isPassing, bool isPending); }; #endif // GOVERNANCEMODEL_H diff --git a/src/qt/pivx/governancewidget.cpp b/src/qt/pivx/governancewidget.cpp index 5a7188a73dfa..9df3fc329a8f 100644 --- a/src/qt/pivx/governancewidget.cpp +++ b/src/qt/pivx/governancewidget.cpp @@ -54,9 +54,7 @@ GovernanceWidget::GovernanceWidget(PIVXGUI* parent) : delegate->setValues(values); ui->comboBoxSort->setModel(model); ui->comboBoxSort->setItemDelegate(delegate); - // Filter - ui->btnFilter->setText("Filter"); ui->btnFilter->setProperty("cssClass", "btn-secundary-filter"); @@ -70,17 +68,12 @@ GovernanceWidget::GovernanceWidget(PIVXGUI* parent) : setCssProperty(ui->labelAllocatedAmount, "label-budget-amount-allocated"); setCssProperty(ui->iconClock , "ic-time"); setCssProperty(ui->labelNextSuperblock, "label-budget-text"); - ui->labelNextSuperblock->setText("Next superblock in ~4 days.\n7,544 blocks to go."); // Update superblock data // Create proposal ui->btnCreateProposal->setTitleClassAndText("btn-title-grey", "Create Proposal"); ui->btnCreateProposal->setSubTitleClassAndText("text-subtitle", "Prepare and submit a new proposal."); connect(ui->btnCreateProposal, SIGNAL(clicked()), this, SLOT(onCreatePropClicked())); ui->emptyContainer->setVisible(false); - - // Move to update process. - ui->labelAllocatedAmount->setText("37,394.912 PIV"); - ui->labelAvailableAmount->setText("5,394.912 PIV"); } GovernanceWidget::~GovernanceWidget() @@ -109,6 +102,21 @@ void GovernanceWidget::onCreatePropClicked() dialog->deleteLater(); } +void GovernanceWidget::loadClientModel() +{ + connect(clientModel, &ClientModel::numBlocksChanged, this, &GovernanceWidget::chainHeightChanged); +} + +void GovernanceWidget::chainHeightChanged(int height) +{ + if (!isVisible()) return; + int remainingBlocks = governanceModel->getNextSuperblockHeight() - height; + int remainingDays = remainingBlocks / 1440; + QString text = remainingDays == 0 ? tr("Next superblock today!\n%2 blocks to go.").arg(remainingBlocks) : + tr("Next superblock in %1 days.\n%2 blocks to go.").arg(remainingDays).arg(remainingBlocks); + ui->labelNextSuperblock->setText(text); +} + void GovernanceWidget::setGovModel(GovernanceModel* _model) { governanceModel = _model; @@ -146,6 +154,11 @@ void GovernanceWidget::tryGridRefresh(bool force) if (_propsPerRow != propsPerRow || force) { propsPerRow = _propsPerRow; refreshCardsGrid(true); + + // refresh budget distribution values + chainHeightChanged(clientModel->getNumBlocks()); + ui->labelAllocatedAmount->setText(GUIUtil::formatBalance(governanceModel->getBudgetAllocatedAmount())); + ui->labelAvailableAmount->setText(GUIUtil::formatBalance(governanceModel->getBudgetAvailableAmount())); } } diff --git a/src/qt/pivx/governancewidget.h b/src/qt/pivx/governancewidget.h index bcecf201165c..abf25e4ef65e 100644 --- a/src/qt/pivx/governancewidget.h +++ b/src/qt/pivx/governancewidget.h @@ -61,10 +61,12 @@ class GovernanceWidget : public PWidget void showEvent(QShowEvent *event) override; void hideEvent(QHideEvent *event) override; void resizeEvent(QResizeEvent *event) override; + void loadClientModel() override; void loadWalletModel() override; void setGovModel(GovernanceModel* _model); public Q_SLOTS: + void chainHeightChanged(int height); void onVoteForPropClicked(); void onCreatePropClicked(); From 8e41aada5484321ab99ee9cc16ad684e560a051a Mon Sep 17 00:00:00 2001 From: furszy Date: Tue, 25 May 2021 12:22:18 -0300 Subject: [PATCH 14/59] GUI vote dialog: connect MN selection dialog to the model. --- src/qt/pivx.cpp | 4 ++ src/qt/pivx/forms/mnselectiondialog.ui | 2 +- src/qt/pivx/governancewidget.cpp | 9 +++- src/qt/pivx/governancewidget.h | 3 ++ src/qt/pivx/masternodeswidget.cpp | 45 +++++++++---------- src/qt/pivx/masternodeswidget.h | 6 +-- src/qt/pivx/mnselectiondialog.cpp | 34 +++++++------- src/qt/pivx/mnselectiondialog.h | 9 +++- src/qt/pivx/pivxgui.cpp | 7 +++ src/qt/pivx/pivxgui.h | 1 + .../settings/settingssignmessagewidgets.cpp | 6 +-- src/qt/pivx/votedialog.cpp | 18 ++++++-- src/qt/pivx/votedialog.h | 8 +++- 13 files changed, 95 insertions(+), 57 deletions(-) diff --git a/src/qt/pivx.cpp b/src/qt/pivx.cpp index eecabcb90700..903e2b7e07f4 100644 --- a/src/qt/pivx.cpp +++ b/src/qt/pivx.cpp @@ -24,6 +24,7 @@ #ifdef ENABLE_WALLET #include "qt/pivx/governancemodel.h" +#include "qt/pivx/mnmodel.h" #include "paymentserver.h" #include "walletmodel.h" #include "interfaces/wallet.h" @@ -236,6 +237,7 @@ public Q_SLOTS: PaymentServer* paymentServer{nullptr}; WalletModel* walletModel{nullptr}; GovernanceModel* govModel{nullptr}; + MNModel* mnModel{nullptr}; #endif int returnValue{0}; QTranslator qtTranslatorBase, qtTranslator, translatorBase, translator; @@ -500,6 +502,8 @@ void BitcoinApplication::initializeResult(int retval) window->addWallet(PIVXGUI::DEFAULT_WALLET, walletModel); window->setCurrentWallet(PIVXGUI::DEFAULT_WALLET); } + mnModel = new MNModel(this, walletModel); + window->setMNModel(mnModel); #endif // If -min option passed, start window minimized. diff --git a/src/qt/pivx/forms/mnselectiondialog.ui b/src/qt/pivx/forms/mnselectiondialog.ui index ac8a0e51dfdf..aaea7f9499ca 100644 --- a/src/qt/pivx/forms/mnselectiondialog.ui +++ b/src/qt/pivx/forms/mnselectiondialog.ui @@ -259,7 +259,7 @@ - 3 + 0 diff --git a/src/qt/pivx/governancewidget.cpp b/src/qt/pivx/governancewidget.cpp index 9df3fc329a8f..4ca080545213 100644 --- a/src/qt/pivx/governancewidget.cpp +++ b/src/qt/pivx/governancewidget.cpp @@ -3,6 +3,7 @@ #include "qt/pivx/createproposaldialog.h" #include "qt/pivx/governancemodel.h" +#include "qt/pivx/mnmodel.h" #include "qt/pivx/qtutils.h" #include "qt/pivx/votedialog.h" @@ -79,13 +80,12 @@ GovernanceWidget::GovernanceWidget(PIVXGUI* parent) : GovernanceWidget::~GovernanceWidget() { delete ui; - delete governanceModel; } void GovernanceWidget::onVoteForPropClicked() { window->showHide(true); - VoteDialog* dialog = new VoteDialog(window); + VoteDialog* dialog = new VoteDialog(window, governanceModel, mnModel); openDialogWithOpaqueBackgroundY(dialog, window, 4.5, 5); dialog->deleteLater(); } @@ -122,6 +122,11 @@ void GovernanceWidget::setGovModel(GovernanceModel* _model) governanceModel = _model; } +void GovernanceWidget::setMNModel(MNModel* _mnModel) +{ + mnModel = _mnModel; +} + void GovernanceWidget::loadWalletModel() { governanceModel->setWalletModel(walletModel); diff --git a/src/qt/pivx/governancewidget.h b/src/qt/pivx/governancewidget.h index abf25e4ef65e..27277b436bb8 100644 --- a/src/qt/pivx/governancewidget.h +++ b/src/qt/pivx/governancewidget.h @@ -22,6 +22,7 @@ QT_BEGIN_NAMESPACE class QTimer; QT_END_NAMESPACE +class MNModel; class PIVXGUI; class GovernanceModel; @@ -64,6 +65,7 @@ class GovernanceWidget : public PWidget void loadClientModel() override; void loadWalletModel() override; void setGovModel(GovernanceModel* _model); + void setMNModel(MNModel* _mnModel); public Q_SLOTS: void chainHeightChanged(int height); @@ -73,6 +75,7 @@ public Q_SLOTS: private: Ui::governancewidget *ui; GovernanceModel* governanceModel{nullptr}; + MNModel* mnModel{nullptr}; QGridLayout* gridLayout{nullptr}; // cards std::vector cards; int propsPerRow = 0; diff --git a/src/qt/pivx/masternodeswidget.cpp b/src/qt/pivx/masternodeswidget.cpp index 89b50b18d45e..db0d1bf95bf0 100644 --- a/src/qt/pivx/masternodeswidget.cpp +++ b/src/qt/pivx/masternodeswidget.cpp @@ -19,6 +19,7 @@ #include "masternodeconfig.h" #include "masternodeman.h" #include "util/system.h" +#include "qt/pivx/mnmodel.h" #include "qt/pivx/optionbutton.h" #include @@ -138,14 +139,12 @@ void MasterNodesWidget::hideEvent(QHideEvent *event) if (timer) timer->stop(); } -void MasterNodesWidget::loadWalletModel() +void MasterNodesWidget::setMNModel(MNModel* _mnModel) { - if (walletModel) { - mnModel = new MNModel(this, walletModel); - ui->listMn->setModel(mnModel); - ui->listMn->setModelColumn(AddressTableModel::Label); - updateListState(); - } + mnModel = _mnModel; + ui->listMn->setModel(mnModel); + ui->listMn->setModelColumn(AddressTableModel::Label); + updateListState(); } void MasterNodesWidget::updateListState() @@ -216,6 +215,20 @@ void MasterNodesWidget::onEditMNClicked() } } +static bool startMN(const CMasternodeConfig::CMasternodeEntry& mne, int chainHeight, std::string& strError) +{ + CMasternodeBroadcast mnb; + if (!CMasternodeBroadcast::Create(mne.getIp(), mne.getPrivKey(), mne.getTxHash(), mne.getOutputIndex(), strError, mnb, false, chainHeight)) + return false; + + mnodeman.UpdateMasternodeList(mnb); + if (activeMasternode.pubKeyMasternode == mnb.GetPubKey()) { + activeMasternode.EnableHotColdMasterNode(mnb.vin, mnb.addr); + } + mnb.Relay(); + return true; +} + void MasterNodesWidget::startAlias(const QString& strAlias) { QString strStatusHtml; @@ -224,7 +237,7 @@ void MasterNodesWidget::startAlias(const QString& strAlias) for (const auto& mne : masternodeConfig.getEntries()) { if (mne.getAlias() == strAlias.toStdString()) { std::string strError; - strStatusHtml += (!startMN(mne, strError)) ? ("failed to start.\nError: " + QString::fromStdString(strError)) : "successfully started."; + strStatusHtml += (!startMN(mne, walletModel->getLastBlockProcessedNum(), strError)) ? ("failed to start.\nError: " + QString::fromStdString(strError)) : "successfully started."; break; } } @@ -238,20 +251,6 @@ void MasterNodesWidget::updateModelAndInform(const QString& informText) inform(informText); } -bool MasterNodesWidget::startMN(const CMasternodeConfig::CMasternodeEntry& mne, std::string& strError) -{ - CMasternodeBroadcast mnb; - if (!CMasternodeBroadcast::Create(mne.getIp(), mne.getPrivKey(), mne.getTxHash(), mne.getOutputIndex(), strError, mnb, false, walletModel->getLastBlockProcessedNum())) - return false; - - mnodeman.UpdateMasternodeList(mnb); - if (activeMasternode.pubKeyMasternode == mnb.GetPubKey()) { - activeMasternode.EnableHotColdMasterNode(mnb.vin, mnb.addr); - } - mnb.Relay(); - return true; -} - void MasterNodesWidget::onStartAllClicked(int type) { if (!Params().IsRegTestNet() && !checkMNsNetwork()) return; // skip on RegNet: so we can test even if tier two not synced @@ -291,7 +290,7 @@ bool MasterNodesWidget::startAll(QString& failText, bool onlyMissing) } std::string strError; - if (!startMN(mne, strError)) { + if (!startMN(mne, walletModel->getLastBlockProcessedNum(), strError)) { amountOfMnFailed++; } else { amountOfMnStarted++; diff --git a/src/qt/pivx/masternodeswidget.h b/src/qt/pivx/masternodeswidget.h index f115ec2570d7..33255535bec4 100644 --- a/src/qt/pivx/masternodeswidget.h +++ b/src/qt/pivx/masternodeswidget.h @@ -7,7 +7,6 @@ #include "qt/pivx/pwidget.h" #include "qt/pivx/furabstractlistitemdelegate.h" -#include "qt/pivx/mnmodel.h" #include "qt/pivx/tooltipmenu.h" #include "walletmodel.h" @@ -17,6 +16,7 @@ #include class PIVXGUI; +class MNModel; namespace Ui { class MasterNodesWidget; @@ -34,8 +34,7 @@ class MasterNodesWidget : public PWidget explicit MasterNodesWidget(PIVXGUI *parent = nullptr); ~MasterNodesWidget(); - - void loadWalletModel() override; + void setMNModel(MNModel* _mnModel); void run(int type) override; void onError(QString error, int type) override; @@ -67,7 +66,6 @@ private Q_SLOTS: bool checkMNsNetwork(); void startAlias(const QString& strAlias); bool startAll(QString& failedMN, bool onlyMissing); - bool startMN(const CMasternodeConfig::CMasternodeEntry& mne, std::string& strError); }; #endif // MASTERNODESWIDGET_H diff --git a/src/qt/pivx/mnselectiondialog.cpp b/src/qt/pivx/mnselectiondialog.cpp index 252297babb6e..a5521851fefe 100644 --- a/src/qt/pivx/mnselectiondialog.cpp +++ b/src/qt/pivx/mnselectiondialog.cpp @@ -4,6 +4,7 @@ #include "qt/pivx/mnselectiondialog.h" #include "qt/pivx/forms/ui_mnselectiondialog.h" +#include "qt/pivx/mnmodel.h" #include "qt/pivx/qtutils.h" MnSelectionDialog::MnSelectionDialog(QWidget *parent) : @@ -36,15 +37,20 @@ MnSelectionDialog::MnSelectionDialog(QWidget *parent) : connect(ui->btnSave, SIGNAL(clicked()), this, SLOT(close())); } -void MnSelectionDialog::setModel() +void MnSelectionDialog::setModel(MNModel* _mnModel) { + mnModel = _mnModel; updateView(); } class MnInfo { public: - explicit MnInfo() {} + explicit MnInfo(const QString& _alias, + const QString& _status) : alias(_alias), status(_status) {} ~MnInfo() {} + + QString alias; + QString status; }; void MnSelectionDialog::updateView() @@ -54,13 +60,11 @@ void MnSelectionDialog::updateView() QFlags flgCheckbox = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable; QFlags flgTristate = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable | Qt::ItemIsTristate; - // todo: implement MnInfo - std::list masternodesList; //= getMns(); - appendItem(flgCheckbox, flgTristate, "Masternode1", "Enabled"); - appendItem(flgCheckbox, flgTristate, "Masternode2", "Enabled"); - appendItem(flgCheckbox, flgTristate, "Masternode3", "Disabled"); - for (const MnInfo& mnInfo : masternodesList) { - appendItem(flgCheckbox, flgTristate, "Masternode1", "Enabled"); + for (int i = 0; i < mnModel->rowCount(); ++i) { + QString alias = mnModel->index(i, MNModel::ALIAS, QModelIndex()).data().toString(); + QString status = mnModel->index(i, MNModel::STATUS, QModelIndex()).data().toString(); + masternodesList.emplace_back(alias, status); + appendItem(flgCheckbox, flgTristate, alias, status); } // save COLUMN_CHECKBOX width for tree-mode @@ -84,15 +88,11 @@ void MnSelectionDialog::appendItem(QFlags flgCheckbox, itemOutput->setText(COLUMN_STATUS, mnStatus); itemOutput->setToolTip(COLUMN_STATUS, "Masternode status"); - // todo: disable inactive masternodes - /*if (isInactive) { + if (mnStatus != "ENABLED") { itemOutput->setDisabled(true); - itemOutput->setIcon(COLUMN_CHECKBOX, QIcon(":/icons/check_disbled")); - }*/ - - // todo: set checkbox value - // if (coinControl->IsSelected(COutPoint(txhash, outIndex))) - // itemOutput->setCheckState(COLUMN_CHECKBOX, Qt::Checked); + // TODO: add disabled visual representation. + //itemOutput->setIcon(COLUMN_CHECKBOX, QIcon(":/icons/check_disbled")); + } } MnSelectionDialog::~MnSelectionDialog() diff --git a/src/qt/pivx/mnselectiondialog.h b/src/qt/pivx/mnselectiondialog.h index 782f58b1e087..8ccbb06ce4c9 100644 --- a/src/qt/pivx/mnselectiondialog.h +++ b/src/qt/pivx/mnselectiondialog.h @@ -11,20 +11,25 @@ namespace Ui { class MnSelectionDialog; } +class MNModel; +class MnInfo; + class MnSelectionDialog : public QDialog { Q_OBJECT public: - explicit MnSelectionDialog(QWidget *parent = nullptr); + explicit MnSelectionDialog(QWidget *parent); ~MnSelectionDialog(); - void setModel(); + void setModel(MNModel* _mnModel); void updateView(); private: Ui::MnSelectionDialog *ui; + MNModel* mnModel{nullptr}; int colCheckBoxWidth_treeMode{50}; + std::list masternodesList; enum { COLUMN_CHECKBOX, diff --git a/src/qt/pivx/pivxgui.cpp b/src/qt/pivx/pivxgui.cpp index 0bcd7fb3f671..ca6de7dace51 100644 --- a/src/qt/pivx/pivxgui.cpp +++ b/src/qt/pivx/pivxgui.cpp @@ -625,6 +625,13 @@ void PIVXGUI::setGovModel(GovernanceModel* govModel) governancewidget->setGovModel(govModel); } +void PIVXGUI::setMNModel(MNModel* mnModel) +{ + if (!stackedContainer || !clientModel) return; + governancewidget->setMNModel(mnModel); + masterNodesWidget->setMNModel(mnModel); +} + bool PIVXGUI::addWallet(const QString& name, WalletModel* walletModel) { // Single wallet supported for now.. diff --git a/src/qt/pivx/pivxgui.h b/src/qt/pivx/pivxgui.h index 0498fd2d3756..de43bf57450f 100644 --- a/src/qt/pivx/pivxgui.h +++ b/src/qt/pivx/pivxgui.h @@ -99,6 +99,7 @@ public Q_SLOTS: void incomingTransaction(const QString& date, int unit, const CAmount& amount, const QString& type, const QString& address); #ifdef ENABLE_WALLET void setGovModel(GovernanceModel* govModel); + void setMNModel(MNModel* mnModel); /** Set the wallet model. The wallet model represents a bitcoin wallet, and offers access to the list of transactions, address book and sending functionality. diff --git a/src/qt/pivx/settings/settingssignmessagewidgets.cpp b/src/qt/pivx/settings/settingssignmessagewidgets.cpp index 3b35842a1adf..b09d8fbc3bc0 100644 --- a/src/qt/pivx/settings/settingssignmessagewidgets.cpp +++ b/src/qt/pivx/settings/settingssignmessagewidgets.cpp @@ -4,16 +4,16 @@ #include "qt/pivx/settings/settingssignmessagewidgets.h" -#include "init.h" #include "key_io.h" #include "messagesigner.h" #include "qt/askpassphrasedialog.h" #include "qt/addressbookpage.h" -#include "qt/guiutil.h" #include "qt/pivx/settings/forms/ui_settingssignmessagewidgets.h" #include "qt/pivx/qtutils.h" #include "qt/walletmodel.h" -#include "wallet/wallet.h" +#include "util/validation.h" + +#include "messagesigner.h" #include #include diff --git a/src/qt/pivx/votedialog.cpp b/src/qt/pivx/votedialog.cpp index 063880ce7890..f00fd6839ab0 100644 --- a/src/qt/pivx/votedialog.cpp +++ b/src/qt/pivx/votedialog.cpp @@ -5,12 +5,15 @@ #include "qt/pivx/votedialog.h" #include "qt/pivx/forms/ui_votedialog.h" +#include "qt/pivx/mnmodel.h" #include "qt/pivx/mnselectiondialog.h" #include "qt/pivx/qtutils.h" -VoteDialog::VoteDialog(QWidget *parent) : +VoteDialog::VoteDialog(QWidget *parent, GovernanceModel* _govModel, MNModel* _mnModel) : QDialog(parent), - ui(new Ui::VoteDialog) + ui(new Ui::VoteDialog), + govModel(_govModel), + mnModel(_mnModel) { ui->setupUi(this); this->setStyleSheet(parent->styleSheet()); @@ -66,14 +69,18 @@ void VoteDialog::onMnSelectionClicked() PIVXGUI* window = dynamic_cast(parent()); MnSelectionDialog* dialog = new MnSelectionDialog(window); dialog->resize(size()); - dialog->setModel(); // todo: set mnmodel. + dialog->setModel(mnModel); openDialogWithOpaqueBackgroundY(dialog, window, 4.5, 5, false); dialog->deleteLater(); } void VoteDialog::onCheckBoxClicked(QCheckBox* checkBox, QProgressBar* progressBar, bool isVoteYes) { - // todo: set value. + if (isVoteYes) { + checkBoxNo->setCheckState(Qt::Unchecked); + } else { + checkBoxYes->setCheckState(Qt::Unchecked); + } } void VoteDialog::initVoteCheck(QWidget* container, QCheckBox* checkBox, QProgressBar* progressBar, QString text, Qt::LayoutDirection direction, bool isVoteYes) @@ -101,6 +108,9 @@ void VoteDialog::initVoteCheck(QWidget* container, QCheckBox* checkBox, QProgres connect(checkBox, &QCheckBox::clicked, [this, checkBox, progressBar, isVoteYes](){ onCheckBoxClicked(checkBox, progressBar, isVoteYes); }); checkBox->setAttribute(Qt::WA_LayoutUsesWidgetRect); checkBox->show(); + + // Progress bar + progressBar->setValue(35); } VoteDialog::~VoteDialog() diff --git a/src/qt/pivx/votedialog.h b/src/qt/pivx/votedialog.h index 394f065b63c7..46bded0920df 100644 --- a/src/qt/pivx/votedialog.h +++ b/src/qt/pivx/votedialog.h @@ -13,12 +13,15 @@ namespace Ui { class VoteDialog; } +class MNModel; +class GovernanceModel; + class VoteDialog : public QDialog { Q_OBJECT public: - explicit VoteDialog(QWidget *parent = nullptr); + explicit VoteDialog(QWidget *parent, GovernanceModel* _govModel, MNModel* _mnModel); ~VoteDialog(); void showEvent(QShowEvent *event) override; @@ -30,6 +33,9 @@ public Q_SLOTS: private: Ui::VoteDialog *ui; + GovernanceModel* govModel{nullptr}; + MNModel* mnModel{nullptr}; + QCheckBox* checkBoxNo{nullptr}; QCheckBox* checkBoxYes{nullptr}; QProgressBar* progressBarNo{nullptr}; From b5765b01ffe612f0098c14c6d6b3a744cb1df794 Mon Sep 17 00:00:00 2001 From: furszy Date: Tue, 25 May 2021 12:38:48 -0300 Subject: [PATCH 15/59] GUI vote dialog, present proposal info. --- src/qt/pivx/governancewidget.cpp | 11 ++++++----- src/qt/pivx/governancewidget.h | 2 +- src/qt/pivx/proposalcard.cpp | 2 +- src/qt/pivx/proposalcard.h | 2 +- src/qt/pivx/votedialog.cpp | 15 ++++++++++++++- src/qt/pivx/votedialog.h | 4 +++- 6 files changed, 26 insertions(+), 10 deletions(-) diff --git a/src/qt/pivx/governancewidget.cpp b/src/qt/pivx/governancewidget.cpp index 4ca080545213..8fe439583a9b 100644 --- a/src/qt/pivx/governancewidget.cpp +++ b/src/qt/pivx/governancewidget.cpp @@ -49,8 +49,8 @@ GovernanceWidget::GovernanceWidget(PIVXGUI* parent) : values.append("Date"); values.append("Value"); values.append("Name"); - for (int n = 0; n < values.size(); n++) { - model->appendRow(new QStandardItem(tr("Sort by: %1").arg(values.at(n)))); + for (const auto& value : values) { + model->appendRow(new QStandardItem(tr("Sort by: %1").arg(value))); } delegate->setValues(values); ui->comboBoxSort->setModel(model); @@ -73,7 +73,7 @@ GovernanceWidget::GovernanceWidget(PIVXGUI* parent) : // Create proposal ui->btnCreateProposal->setTitleClassAndText("btn-title-grey", "Create Proposal"); ui->btnCreateProposal->setSubTitleClassAndText("text-subtitle", "Prepare and submit a new proposal."); - connect(ui->btnCreateProposal, SIGNAL(clicked()), this, SLOT(onCreatePropClicked())); + connect(ui->btnCreateProposal, &OptionButton::clicked, this, &GovernanceWidget::onCreatePropClicked); ui->emptyContainer->setVisible(false); } @@ -82,10 +82,11 @@ GovernanceWidget::~GovernanceWidget() delete ui; } -void GovernanceWidget::onVoteForPropClicked() +void GovernanceWidget::onVoteForPropClicked(const ProposalInfo& proposalInfo) { window->showHide(true); VoteDialog* dialog = new VoteDialog(window, governanceModel, mnModel); + dialog->setProposal(proposalInfo); openDialogWithOpaqueBackgroundY(dialog, window, 4.5, 5); dialog->deleteLater(); } @@ -180,6 +181,7 @@ static void setCardShadow(QWidget* edit) ProposalCard* GovernanceWidget::newCard() { ProposalCard* propCard = new ProposalCard(ui->scrollAreaWidgetContents); + connect(propCard, &ProposalCard::voteClicked, this, &GovernanceWidget::onVoteForPropClicked); setCardShadow(propCard); return propCard; } @@ -267,4 +269,3 @@ int GovernanceWidget::calculateColumnsPerRow() return 4; // max amount of cards } } - diff --git a/src/qt/pivx/governancewidget.h b/src/qt/pivx/governancewidget.h index 27277b436bb8..e20eb22f6283 100644 --- a/src/qt/pivx/governancewidget.h +++ b/src/qt/pivx/governancewidget.h @@ -69,7 +69,7 @@ class GovernanceWidget : public PWidget public Q_SLOTS: void chainHeightChanged(int height); - void onVoteForPropClicked(); + void onVoteForPropClicked(const ProposalInfo& proposalInfo); void onCreatePropClicked(); private: diff --git a/src/qt/pivx/proposalcard.cpp b/src/qt/pivx/proposalcard.cpp index ffcce1a9c8f5..a9ec45fd4d76 100644 --- a/src/qt/pivx/proposalcard.cpp +++ b/src/qt/pivx/proposalcard.cpp @@ -30,7 +30,7 @@ ProposalCard::ProposalCard(QWidget *parent) : setCssProperty(ui->votesBar, "vote-progress"); ui->votesBar->setContentsMargins(0,0,0,0); - connect(ui->btnVote, &QPushButton::clicked, [this](){ Q_EMIT voteClicked(); }); + connect(ui->btnVote, &QPushButton::clicked, [this](){ Q_EMIT voteClicked(proposalInfo); }); connect(ui->btnLink, &QPushButton::clicked, this, &ProposalCard::onCopyUrlClicked); } diff --git a/src/qt/pivx/proposalcard.h b/src/qt/pivx/proposalcard.h index 3202cdebb41d..0d0ca8a35094 100644 --- a/src/qt/pivx/proposalcard.h +++ b/src/qt/pivx/proposalcard.h @@ -34,7 +34,7 @@ public Q_SLOTS: void onCopyUrlClicked(); Q_SIGNALS: - void voteClicked(); + void voteClicked(const ProposalInfo& proposalInfo); private: Ui::ProposalCard *ui; diff --git a/src/qt/pivx/votedialog.cpp b/src/qt/pivx/votedialog.cpp index f00fd6839ab0..459a4b00f2cd 100644 --- a/src/qt/pivx/votedialog.cpp +++ b/src/qt/pivx/votedialog.cpp @@ -52,6 +52,19 @@ VoteDialog::VoteDialog(QWidget *parent, GovernanceModel* _govModel, MNModel* _mn connect(ui->btnSave, &QPushButton::clicked, this, &VoteDialog::onAcceptClicked); } +void VoteDialog::setProposal(const ProposalInfo& prop) +{ + ui->labelTitleVote->setText(QString::fromStdString(prop.name)); + ui->labelAmount->setText(GUIUtil::formatBalance(prop.amount)); + ui->labelTime->setText(tr("%1 months paid of %2").arg(prop.totalPayments - prop.remainingPayments).arg(prop.totalPayments)); + double totalVotes = prop.votesYes + prop.votesNo; + double percentageNo = (totalVotes == 0) ? 0 : (prop.votesNo / totalVotes) * 100; + double percentageYes = (totalVotes == 0) ? 0 : (prop.votesYes / totalVotes) * 100; + progressBarNo->setValue((int)percentageNo); + progressBarYes->setValue((int)percentageYes); + // todo: add votes amount text +} + void VoteDialog::onAcceptClicked() { close(); @@ -83,7 +96,7 @@ void VoteDialog::onCheckBoxClicked(QCheckBox* checkBox, QProgressBar* progressBa } } -void VoteDialog::initVoteCheck(QWidget* container, QCheckBox* checkBox, QProgressBar* progressBar, QString text, Qt::LayoutDirection direction, bool isVoteYes) +void VoteDialog::initVoteCheck(QWidget* container, QCheckBox* checkBox, QProgressBar* progressBar, const QString& text, Qt::LayoutDirection direction, bool isVoteYes) { QGridLayout* gridLayout = dynamic_cast(container->layout()); progressBar->setMaximum(100); diff --git a/src/qt/pivx/votedialog.h b/src/qt/pivx/votedialog.h index 46bded0920df..f9998838e77b 100644 --- a/src/qt/pivx/votedialog.h +++ b/src/qt/pivx/votedialog.h @@ -13,6 +13,7 @@ namespace Ui { class VoteDialog; } +class ProposalInfo; class MNModel; class GovernanceModel; @@ -25,6 +26,7 @@ class VoteDialog : public QDialog ~VoteDialog(); void showEvent(QShowEvent *event) override; + void setProposal(const ProposalInfo& prop); public Q_SLOTS: void onAcceptClicked(); @@ -42,7 +44,7 @@ public Q_SLOTS: QProgressBar* progressBarYes{nullptr}; void initVoteCheck(QWidget* container, QCheckBox* checkBox, QProgressBar* progressBar, - QString text, Qt::LayoutDirection direction, bool isVoteYes); + const QString& text, Qt::LayoutDirection direction, bool isVoteYes); }; #endif // VOTEDIALOG_H From c8bdeb7b97806428178a3448ddffabe672b6858e Mon Sep 17 00:00:00 2001 From: furszy Date: Tue, 25 May 2021 20:05:50 -0300 Subject: [PATCH 16/59] GUI governance: emit vote for proposal connected. --- src/qt/pivx/governancemodel.cpp | 28 +++++++++++++++++++- src/qt/pivx/governancemodel.h | 4 +++ src/qt/pivx/governancewidget.cpp | 4 ++- src/qt/pivx/mnselectiondialog.cpp | 35 +++++++++++++++++++++--- src/qt/pivx/mnselectiondialog.h | 8 +++++- src/qt/pivx/votedialog.cpp | 44 +++++++++++++++++++++++++------ src/qt/pivx/votedialog.h | 9 +++++++ 7 files changed, 117 insertions(+), 15 deletions(-) diff --git a/src/qt/pivx/governancemodel.cpp b/src/qt/pivx/governancemodel.cpp index 6877ed94b7ce..700f94d4253c 100644 --- a/src/qt/pivx/governancemodel.cpp +++ b/src/qt/pivx/governancemodel.cpp @@ -5,12 +5,14 @@ #include "qt/pivx/governancemodel.h" #include "budget/budgetmanager.h" +#include "budget/budgetutil.h" #include "destination_io.h" #include "guiconstants.h" #include "masternode-sync.h" #include "script/standard.h" #include "qt/transactiontablemodel.h" #include "qt/transactionrecord.h" +#include "qt/pivx/mnmodel.h" #include "utilmoneystr.h" #include "utilstrencodings.h" #include "walletmodel.h" @@ -165,6 +167,30 @@ OperationResult GovernanceModel::createProposal(const std::string& strProposalNa return {true}; } +OperationResult GovernanceModel::voteForProposal(const ProposalInfo& prop, + bool isVotePositive, + const std::vector& mnVotingAlias) +{ + UniValue ret; // future: don't use UniValue here. + for (const auto& mnAlias : mnVotingAlias) { + bool fLegacyMN = true; // For now, only legacy MNs + ret = mnBudgetVoteInner(nullptr, + fLegacyMN, + prop.id, + false, + isVotePositive ? CBudgetVote::VoteDirection::VOTE_YES : CBudgetVote::VoteDirection::VOTE_NO, + mnAlias); + if (ret.exists("detail") && ret["detail"].isArray()) { + const UniValue& obj = ret["detail"].get_array()[0]; + if (obj["result"].getValStr() != "success") { + return {false, obj["error"].getValStr()}; + } + } + } + // add more information with ret["overall"] + return {true}; +} + void GovernanceModel::scheduleBroadcast(const CBudgetProposal& proposal) { // Cache the proposal to be sent as soon as it gets the minimum required confirmations @@ -234,4 +260,4 @@ void GovernanceModel::txLoaded(const QString& id, const int txType, const int tx } } } -} \ No newline at end of file +} diff --git a/src/qt/pivx/governancemodel.h b/src/qt/pivx/governancemodel.h index 89e2a15fee28..d1d0c7bf5625 100644 --- a/src/qt/pivx/governancemodel.h +++ b/src/qt/pivx/governancemodel.h @@ -105,6 +105,10 @@ class GovernanceModel : public QObject int nPaymentCount, CAmount nAmount, const std::string& strPaymentAddr); + + OperationResult voteForProposal(const ProposalInfo& prop, + bool isVotePositive, + const std::vector& mnVotingAlias); public Q_SLOTS: void pollGovernanceChanged(); void txLoaded(const QString& hash, const int txType, const int txStatus); diff --git a/src/qt/pivx/governancewidget.cpp b/src/qt/pivx/governancewidget.cpp index 8fe439583a9b..f374aff24735 100644 --- a/src/qt/pivx/governancewidget.cpp +++ b/src/qt/pivx/governancewidget.cpp @@ -87,7 +87,9 @@ void GovernanceWidget::onVoteForPropClicked(const ProposalInfo& proposalInfo) window->showHide(true); VoteDialog* dialog = new VoteDialog(window, governanceModel, mnModel); dialog->setProposal(proposalInfo); - openDialogWithOpaqueBackgroundY(dialog, window, 4.5, 5); + if (openDialogWithOpaqueBackgroundY(dialog, window, 4.5, 5)) { + inform(tr("Vote emitted successfully!")); + } dialog->deleteLater(); } diff --git a/src/qt/pivx/mnselectiondialog.cpp b/src/qt/pivx/mnselectiondialog.cpp index a5521851fefe..936c0bf4e5ac 100644 --- a/src/qt/pivx/mnselectiondialog.cpp +++ b/src/qt/pivx/mnselectiondialog.cpp @@ -34,7 +34,8 @@ MnSelectionDialog::MnSelectionDialog(QWidget *parent) : connect(ui->btnEsc, SIGNAL(clicked()), this, SLOT(close())); connect(ui->btnCancel, SIGNAL(clicked()), this, SLOT(close())); - connect(ui->btnSave, SIGNAL(clicked()), this, SLOT(close())); + connect(ui->btnSave, SIGNAL(clicked()), this, SLOT(accept())); + connect(ui->treeWidget, &QTreeWidget::itemChanged, this, &MnSelectionDialog::viewItemChanged); } void MnSelectionDialog::setModel(MNModel* _mnModel) @@ -53,6 +54,28 @@ class MnInfo { QString status; }; +void MnSelectionDialog::viewItemChanged(QTreeWidgetItem* item, int column) +{ + if (column == COLUMN_CHECKBOX) { + MnInfo mnInfo(item->text(COLUMN_NAME), item->text(COLUMN_STATUS)); + if (mnInfo.alias.isEmpty()) return; + auto it = std::find(selectedMnList.begin(), selectedMnList.end(), mnInfo.alias.toStdString()); + if (item->checkState(COLUMN_CHECKBOX) == Qt::Unchecked) { + if (it != selectedMnList.end()) { + selectedMnList.erase(it); + ui->labelAmountOfVotes->setText(QString::number((int)selectedMnList.size())); + } + } else if (item->isDisabled()) { + item->setCheckState(COLUMN_CHECKBOX, Qt::Unchecked); + } else { + if (it == selectedMnList.end()) { + selectedMnList.emplace_back(mnInfo.alias.toStdString()); + ui->labelAmountOfVotes->setText(QString::number((int)selectedMnList.size())); + } + } + } +} + void MnSelectionDialog::updateView() { ui->treeWidget->clear(); @@ -63,7 +86,6 @@ void MnSelectionDialog::updateView() for (int i = 0; i < mnModel->rowCount(); ++i) { QString alias = mnModel->index(i, MNModel::ALIAS, QModelIndex()).data().toString(); QString status = mnModel->index(i, MNModel::STATUS, QModelIndex()).data().toString(); - masternodesList.emplace_back(alias, status); appendItem(flgCheckbox, flgTristate, alias, status); } @@ -90,11 +112,16 @@ void MnSelectionDialog::appendItem(QFlags flgCheckbox, if (mnStatus != "ENABLED") { itemOutput->setDisabled(true); - // TODO: add disabled visual representation. - //itemOutput->setIcon(COLUMN_CHECKBOX, QIcon(":/icons/check_disbled")); + // TODO: add disable icon. + //itemOutput->setIcon(COLUMN_CHECKBOX, QIcon(":/icons/check_disabled")); } } +std::vector MnSelectionDialog::getSelectedMnAlias() +{ + return selectedMnList; +} + MnSelectionDialog::~MnSelectionDialog() { delete ui; diff --git a/src/qt/pivx/mnselectiondialog.h b/src/qt/pivx/mnselectiondialog.h index 8ccbb06ce4c9..37d2dcd821af 100644 --- a/src/qt/pivx/mnselectiondialog.h +++ b/src/qt/pivx/mnselectiondialog.h @@ -12,6 +12,7 @@ namespace Ui { } class MNModel; +class QTreeWidgetItem; class MnInfo; class MnSelectionDialog : public QDialog @@ -24,12 +25,17 @@ Q_OBJECT void setModel(MNModel* _mnModel); void updateView(); + std::vector getSelectedMnAlias(); + +public Q_SLOTS: + void viewItemChanged(QTreeWidgetItem*, int); private: Ui::MnSelectionDialog *ui; MNModel* mnModel{nullptr}; int colCheckBoxWidth_treeMode{50}; - std::list masternodesList; + // selected MNs alias + std::vector selectedMnList; enum { COLUMN_CHECKBOX, diff --git a/src/qt/pivx/votedialog.cpp b/src/qt/pivx/votedialog.cpp index 459a4b00f2cd..189788c4fdd2 100644 --- a/src/qt/pivx/votedialog.cpp +++ b/src/qt/pivx/votedialog.cpp @@ -54,6 +54,7 @@ VoteDialog::VoteDialog(QWidget *parent, GovernanceModel* _govModel, MNModel* _mn void VoteDialog::setProposal(const ProposalInfo& prop) { + proposal = std::make_unique(prop); ui->labelTitleVote->setText(QString::fromStdString(prop.name)); ui->labelAmount->setText(GUIUtil::formatBalance(prop.amount)); ui->labelTime->setText(tr("%1 months paid of %2").arg(prop.totalPayments - prop.remainingPayments).arg(prop.totalPayments)); @@ -67,7 +68,26 @@ void VoteDialog::setProposal(const ProposalInfo& prop) void VoteDialog::onAcceptClicked() { - close(); + bool isPositive = checkBoxYes->isChecked(); + bool isNegative = checkBoxNo->isChecked(); + + if (!isPositive && !isNegative) { + inform(tr("Select a vote direction")); + return; + } + + if (vecSelectedMn.empty()) { + inform(tr("Missing voting masternodes selection")); + return; + } + + // Craft and broadcast vote + auto res = govModel->voteForProposal(*proposal, isPositive, vecSelectedMn); + if (!res) { + inform(QString::fromStdString(res.getError())); + return; + } + accept(); } void VoteDialog::showEvent(QShowEvent *event) @@ -80,11 +100,14 @@ void VoteDialog::showEvent(QShowEvent *event) void VoteDialog::onMnSelectionClicked() { PIVXGUI* window = dynamic_cast(parent()); - MnSelectionDialog* dialog = new MnSelectionDialog(window); - dialog->resize(size()); - dialog->setModel(mnModel); - openDialogWithOpaqueBackgroundY(dialog, window, 4.5, 5, false); - dialog->deleteLater(); + if (!mnSelectionDialog) { + mnSelectionDialog = new MnSelectionDialog(window); + mnSelectionDialog->setModel(mnModel); + } + mnSelectionDialog->resize(size()); + if (openDialogWithOpaqueBackgroundY(mnSelectionDialog, window, 4.5, 5, false)) { + vecSelectedMn = mnSelectionDialog->getSelectedMnAlias(); + } } void VoteDialog::onCheckBoxClicked(QCheckBox* checkBox, QProgressBar* progressBar, bool isVoteYes) @@ -121,9 +144,14 @@ void VoteDialog::initVoteCheck(QWidget* container, QCheckBox* checkBox, QProgres connect(checkBox, &QCheckBox::clicked, [this, checkBox, progressBar, isVoteYes](){ onCheckBoxClicked(checkBox, progressBar, isVoteYes); }); checkBox->setAttribute(Qt::WA_LayoutUsesWidgetRect); checkBox->show(); +} - // Progress bar - progressBar->setValue(35); +void VoteDialog::inform(const QString& text) +{ + if (!snackBar) snackBar = new SnackBar(nullptr, this); + snackBar->setText(text); + snackBar->resize(this->width(), snackBar->height()); + openDialog(snackBar, this); } VoteDialog::~VoteDialog() diff --git a/src/qt/pivx/votedialog.h b/src/qt/pivx/votedialog.h index f9998838e77b..12282c72cbc8 100644 --- a/src/qt/pivx/votedialog.h +++ b/src/qt/pivx/votedialog.h @@ -15,7 +15,9 @@ class VoteDialog; class ProposalInfo; class MNModel; +class MnSelectionDialog; class GovernanceModel; +class SnackBar; class VoteDialog : public QDialog { @@ -37,14 +39,21 @@ public Q_SLOTS: Ui::VoteDialog *ui; GovernanceModel* govModel{nullptr}; MNModel* mnModel{nullptr}; + SnackBar* snackBar{nullptr}; QCheckBox* checkBoxNo{nullptr}; QCheckBox* checkBoxYes{nullptr}; QProgressBar* progressBarNo{nullptr}; QProgressBar* progressBarYes{nullptr}; + std::unique_ptr proposal; + MnSelectionDialog* mnSelectionDialog{nullptr}; + std::vector vecSelectedMn; + void initVoteCheck(QWidget* container, QCheckBox* checkBox, QProgressBar* progressBar, const QString& text, Qt::LayoutDirection direction, bool isVoteYes); + + void inform(const QString& text); }; #endif // VOTEDIALOG_H From aa197169769800bba8a17cfc292532ee0ddfa437 Mon Sep 17 00:00:00 2001 From: furszy Date: Fri, 28 May 2021 00:40:12 -0300 Subject: [PATCH 17/59] GovernanceModel: implement getLocalMNsVotesForProposal to retrieve all of the wallet's MN voters for a certain proposal --- src/qt/pivx.cpp | 8 +++++--- src/qt/pivx/governancemodel.cpp | 28 +++++++++++++++++++++++++++- src/qt/pivx/governancemodel.h | 18 +++++++++++++++++- src/qt/pivx/mnmodel.cpp | 7 +++---- src/qt/pivx/mnmodel.h | 4 +++- 5 files changed, 55 insertions(+), 10 deletions(-) diff --git a/src/qt/pivx.cpp b/src/qt/pivx.cpp index 903e2b7e07f4..f3f422e207b2 100644 --- a/src/qt/pivx.cpp +++ b/src/qt/pivx.cpp @@ -490,20 +490,22 @@ void BitcoinApplication::initializeResult(int retval) window->setClientModel(clientModel); #ifdef ENABLE_WALLET - govModel = new GovernanceModel(clientModel); + mnModel = new MNModel(this); + govModel = new GovernanceModel(clientModel, mnModel); // TODO: Expose secondary wallets if (!vpwallets.empty()) { walletModel = new WalletModel(vpwallets[0], optionsModel); walletModel->setClientModel(clientModel); + mnModel->setWalletModel(walletModel); govModel->setWalletModel(walletModel); walletModel->init(); + mnModel->init(); window->setGovModel(govModel); window->addWallet(PIVXGUI::DEFAULT_WALLET, walletModel); window->setCurrentWallet(PIVXGUI::DEFAULT_WALLET); + window->setMNModel(mnModel); } - mnModel = new MNModel(this, walletModel); - window->setMNModel(mnModel); #endif // If -min option passed, start window minimized. diff --git a/src/qt/pivx/governancemodel.cpp b/src/qt/pivx/governancemodel.cpp index 700f94d4253c..8d875eadf032 100644 --- a/src/qt/pivx/governancemodel.cpp +++ b/src/qt/pivx/governancemodel.cpp @@ -20,7 +20,7 @@ #include #include -GovernanceModel::GovernanceModel(ClientModel* _clientModel) : clientModel(_clientModel) {} +GovernanceModel::GovernanceModel(ClientModel* _clientModel, MNModel* _mnModel) : clientModel(_clientModel), mnModel(_mnModel) {} GovernanceModel::~GovernanceModel() {} void GovernanceModel::setWalletModel(WalletModel* _walletModel) @@ -110,6 +110,32 @@ int GovernanceModel::getNextSuperblockHeight() const return chainHeight - chainHeight % nBlocksPerCycle + nBlocksPerCycle; } +std::vector GovernanceModel::getLocalMNsVotesForProposal(const ProposalInfo& propInfo) +{ + // First, get the local masternodes + std::vector vecLocalMn; + for (int i = 0; i < mnModel->rowCount(); ++i) { + vecLocalMn.emplace_back( + uint256S(mnModel->index(i, MNModel::COLLATERAL_ID, QModelIndex()).data().toString().toStdString()), + mnModel->index(i, MNModel::COLLATERAL_OUT_INDEX, QModelIndex()).data().toInt() + ); + } + + std::vector localVotes; + // Get the budget proposal, get the votes, then loop over it and return the ones that correspond to the local masternodes here. + CBudgetProposal* prop = g_budgetman.FindProposal(propInfo.id); + const auto& mapVotes = prop->GetVotes(); + for (const auto& it : mapVotes) { + for (const auto& mn : vecLocalMn) { + if (it.first == mn && it.second.IsValid()) { + localVotes.emplace_back(mn, (VoteInfo::VoteDirection) it.second.GetDirection()); + break; + } + } + } + return localVotes; +} + OperationResult GovernanceModel::validatePropURL(const QString& url) const { std::string strError; diff --git a/src/qt/pivx/governancemodel.h b/src/qt/pivx/governancemodel.h index d1d0c7bf5625..d6e0be338cd0 100644 --- a/src/qt/pivx/governancemodel.h +++ b/src/qt/pivx/governancemodel.h @@ -57,8 +57,21 @@ struct ProposalInfo { } }; +struct VoteInfo { + enum VoteDirection { + ABSTAIN=0, + YES=1, + NO=2 + }; + + explicit VoteInfo(const COutPoint _mnId, VoteDirection _vote) : mnVoter(_mnId), vote(_vote) {} + COutPoint mnVoter; + VoteDirection vote; +}; + class CBudgetProposal; class TransactionRecord; +class MNModel; class WalletModel; QT_BEGIN_NAMESPACE @@ -70,7 +83,7 @@ class GovernanceModel : public QObject static const int PROP_URL_MAX_SIZE = 100; public: - explicit GovernanceModel(ClientModel* _clientModel); + explicit GovernanceModel(ClientModel* _clientModel, MNModel* _mnModel); ~GovernanceModel() override; void setWalletModel(WalletModel* _walletModel); @@ -90,6 +103,8 @@ class GovernanceModel : public QObject // Returns the sum of all of the passing proposals CAmount getBudgetAllocatedAmount() const { return allocatedAmount; }; CAmount getBudgetAvailableAmount() const { return getMaxAvailableBudgetAmount() - allocatedAmount; }; + // Return the votes that the local masternodes did for the inputted proposal + std::vector getLocalMNsVotesForProposal(const ProposalInfo& propInfo); // Check if the URL is valid. OperationResult validatePropURL(const QString& url) const; OperationResult validatePropAmount(CAmount amount) const; @@ -116,6 +131,7 @@ public Q_SLOTS: private: ClientModel* clientModel{nullptr}; WalletModel* walletModel{nullptr}; + MNModel* mnModel{nullptr}; std::atomic refreshNeeded{false}; // Cached amount diff --git a/src/qt/pivx/mnmodel.cpp b/src/qt/pivx/mnmodel.cpp index c4dfcab0a287..1c0def4f8ea1 100644 --- a/src/qt/pivx/mnmodel.cpp +++ b/src/qt/pivx/mnmodel.cpp @@ -8,13 +8,12 @@ #include "masternode-sync.h" #include "masternodeman.h" #include "net.h" // for validateMasternodeIP -#include "sync.h" #include "uint256.h" #include "wallet/wallet.h" -MNModel::MNModel(QObject *parent, WalletModel* _model) : - QAbstractTableModel(parent), - walletModel(_model) +MNModel::MNModel(QObject *parent) : QAbstractTableModel(parent) {} + +void MNModel::init() { updateMNList(); } diff --git a/src/qt/pivx/mnmodel.h b/src/qt/pivx/mnmodel.h index e425e5ea92c1..d6c9bad59172 100644 --- a/src/qt/pivx/mnmodel.h +++ b/src/qt/pivx/mnmodel.h @@ -15,11 +15,13 @@ class MNModel : public QAbstractTableModel Q_OBJECT public: - explicit MNModel(QObject *parent, WalletModel* _model); + explicit MNModel(QObject *parent); ~MNModel() override { nodes.clear(); collateralTxAccepted.clear(); } + void init(); + void setWalletModel(WalletModel* _model) { walletModel = _model; }; enum ColumnIndex { ALIAS = 0, /**< User specified MN alias */ From da39a1388c0aac764bb930574cc5d1772678b5d4 Mon Sep 17 00:00:00 2001 From: furszy Date: Fri, 28 May 2021 00:41:43 -0300 Subject: [PATCH 18/59] wallet: encapsulate wtx extra values set. --- src/qt/walletmodel.cpp | 19 +++++-------------- src/wallet/wallet.cpp | 3 ++- src/wallet/wallet.h | 2 +- 3 files changed, 8 insertions(+), 16 deletions(-) diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index dc7d17161bf4..7afd2e024ccc 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -667,24 +667,15 @@ OperationResult WalletModel::createAndSendProposalFeeTx(CBudgetProposal& proposa return {false ,"Error making fee transaction for proposal. Please check your wallet balance."}; } - //send the tx to the network - const CWallet::CommitResult& res = wallet->CommitTransaction(wtx, keyChange, g_connman.get()); + // send the tx to the network + mapValue_t extraValues; + extraValues.emplace("proposal", toHexStr(proposal)); + const CWallet::CommitResult& res = wallet->CommitTransaction(wtx, &keyChange, g_connman.get(), &extraValues); if (res.status != CWallet::CommitStatus::OK) { return {false, strprintf("Cannot commit proposal fee transaction: %s", res.ToString())}; } + // Everything went fine, set the fee tx hash proposal.SetFeeTxHash(wtx->GetHash()); - - { - // todo: encapsulate inside wallet module - LOCK(wallet->cs_wallet); - // Store own proposal data attached to the transaction that originated it. - // The proposal will be automatically broadcasted when it gets up to the minimum required confirmations. - assert(wallet->mapWallet.count(wtx->GetHash())); - auto& inWtx = wallet->mapWallet.at(wtx->GetHash()); // Internal tx - inWtx.SetComment("Proposal: " + proposal.GetName()); - inWtx.mapValue.emplace("proposal", toHexStr(proposal)); - } - return {true}; } diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index b8d8cd9d4613..468813baf7e5 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -3398,7 +3398,7 @@ CWallet::CommitResult CWallet::CommitTransaction(CTransactionRef tx, CReserveKey /** * Call after CreateTransaction unless you want to abort */ -CWallet::CommitResult CWallet::CommitTransaction(CTransactionRef tx, CReserveKey* opReservekey, CConnman* connman) +CWallet::CommitResult CWallet::CommitTransaction(CTransactionRef tx, CReserveKey* opReservekey, CConnman* connman, mapValue_t* extras) { CommitResult res; @@ -3407,6 +3407,7 @@ CWallet::CommitResult CWallet::CommitTransaction(CTransactionRef tx, CReserveKey wtxNew.BindWallet(this); wtxNew.fFromMe = true; wtxNew.fStakeDelegationVoided = wtxNew.tx->HasP2CSOutputs(); + if (extras) wtxNew.mapValue.insert(extras->begin(), extras->end()); { LOCK2(cs_main, cs_wallet); diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 8d63124a3fdf..1bd2958f7026 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -1076,7 +1076,7 @@ class CWallet : public CCryptoKeyStore, public CValidationInterface std::string ToString() const; }; CWallet::CommitResult CommitTransaction(CTransactionRef tx, CReserveKey& opReservekey, CConnman* connman); - CWallet::CommitResult CommitTransaction(CTransactionRef tx, CReserveKey* reservekey, CConnman* connman); + CWallet::CommitResult CommitTransaction(CTransactionRef tx, CReserveKey* reservekey, CConnman* connman, mapValue_t* extraValues=nullptr); bool CreateCoinStake(const CBlockIndex* pindexPrev, unsigned int nBits, CMutableTransaction& txNew, From 8cd50feaac4d9119535ec98e07941aaf4a2d9c92 Mon Sep 17 00:00:00 2001 From: furszy Date: Fri, 28 May 2021 00:42:32 -0300 Subject: [PATCH 19/59] gui: do not open vote dialog if the proposal wasn't accepted by the network yet. --- src/qt/pivx/governancewidget.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/qt/pivx/governancewidget.cpp b/src/qt/pivx/governancewidget.cpp index f374aff24735..3afb04278ea4 100644 --- a/src/qt/pivx/governancewidget.cpp +++ b/src/qt/pivx/governancewidget.cpp @@ -84,6 +84,10 @@ GovernanceWidget::~GovernanceWidget() void GovernanceWidget::onVoteForPropClicked(const ProposalInfo& proposalInfo) { + if (proposalInfo.status == ProposalInfo::Status::WAITING_FOR_APPROVAL) { + inform(tr("Cannot vote for the proposal yet, wait until it's confirmed by the network")); + return; + } window->showHide(true); VoteDialog* dialog = new VoteDialog(window, governanceModel, mnModel); dialog->setProposal(proposalInfo); From 6c1ff96670a8fa6c9d69b50cd63ab4e6821f80b9 Mon Sep 17 00:00:00 2001 From: furszy Date: Fri, 28 May 2021 11:26:30 -0300 Subject: [PATCH 20/59] GUI: vote dialog, connect already broadcasted votes. --- src/Makefile.qt.include | 1 + src/qt/pivx/votedialog.cpp | 19 +++++++++++++++++-- src/qt/pivx/votedialog.h | 8 +++++++- 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include index e728a14f1e06..7cbc31b45895 100644 --- a/src/Makefile.qt.include +++ b/src/Makefile.qt.include @@ -510,6 +510,7 @@ RES_ICONS = \ qt/pivx/res/img/ic-nav-governance-active.svg \ qt/pivx/res/img/ic-nav-governance-hover.svg \ qt/pivx/res/img/ic-time.svg \ + qt/pivx/res/img/ic-link-hover.svg \ qt/pivx/res/img/img-empty-governance.svg BITCOIN_QT_BASE_CPP = \ diff --git a/src/qt/pivx/votedialog.cpp b/src/qt/pivx/votedialog.cpp index 189788c4fdd2..ab18d288ffff 100644 --- a/src/qt/pivx/votedialog.cpp +++ b/src/qt/pivx/votedialog.cpp @@ -57,13 +57,16 @@ void VoteDialog::setProposal(const ProposalInfo& prop) proposal = std::make_unique(prop); ui->labelTitleVote->setText(QString::fromStdString(prop.name)); ui->labelAmount->setText(GUIUtil::formatBalance(prop.amount)); - ui->labelTime->setText(tr("%1 months paid of %2").arg(prop.totalPayments - prop.remainingPayments).arg(prop.totalPayments)); + ui->labelTime->setText(tr("%1 months passed of %2").arg(prop.totalPayments - prop.remainingPayments).arg(prop.totalPayments)); double totalVotes = prop.votesYes + prop.votesNo; double percentageNo = (totalVotes == 0) ? 0 : (prop.votesNo / totalVotes) * 100; double percentageYes = (totalVotes == 0) ? 0 : (prop.votesYes / totalVotes) * 100; progressBarNo->setValue((int)percentageNo); progressBarYes->setValue((int)percentageYes); - // todo: add votes amount text + checkBoxNo->setText(tr("%1 / %2% No").arg(prop.votesNo).arg(percentageNo)); + checkBoxYes->setText(tr("Yes %1 / %2%").arg(prop.votesYes).arg(percentageYes)); + votes = govModel->getLocalMNsVotesForProposal(prop); + updateMnSelectionNum(); } void VoteDialog::onAcceptClicked() @@ -146,6 +149,18 @@ void VoteDialog::initVoteCheck(QWidget* container, QCheckBox* checkBox, QProgres checkBox->show(); } +void VoteDialog::updateMnSelectionNum() +{ + QString text; + if (vecSelectedMn.empty()) { + text = !votes.empty() ? tr("You have voted with %1 Masternodes for this proposal\nChange votes").arg(votes.size()) : + tr("Select Voting Masternodes"); + } else { + text = tr("%1 Masternodes selected to vote").arg(vecSelectedMn.size()); + } + ui->btnSelectMasternodes->setText(text); +} + void VoteDialog::inform(const QString& text) { if (!snackBar) snackBar = new SnackBar(nullptr, this); diff --git a/src/qt/pivx/votedialog.h b/src/qt/pivx/votedialog.h index 12282c72cbc8..63ec77e371a0 100644 --- a/src/qt/pivx/votedialog.h +++ b/src/qt/pivx/votedialog.h @@ -9,11 +9,15 @@ #include #include +#include "qt/pivx/governancemodel.h" +#include + namespace Ui { class VoteDialog; } -class ProposalInfo; +struct ProposalInfo; +struct VoteInfo; class MNModel; class MnSelectionDialog; class GovernanceModel; @@ -47,6 +51,7 @@ public Q_SLOTS: QProgressBar* progressBarYes{nullptr}; std::unique_ptr proposal; + std::vector votes; MnSelectionDialog* mnSelectionDialog{nullptr}; std::vector vecSelectedMn; @@ -54,6 +59,7 @@ public Q_SLOTS: const QString& text, Qt::LayoutDirection direction, bool isVoteYes); void inform(const QString& text); + void updateMnSelectionNum(); }; #endif // VOTEDIALOG_H From c95219694d1456521553dc5f794590d77ebfcb01 Mon Sep 17 00:00:00 2001 From: furszy Date: Fri, 28 May 2021 18:19:57 -0300 Subject: [PATCH 21/59] Model governance: do not try to broadcast proposals that expired or that due a reorg lost the fee transaction. --- src/qt/pivx/governancemodel.cpp | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/qt/pivx/governancemodel.cpp b/src/qt/pivx/governancemodel.cpp index 8d875eadf032..891fd5c6a73f 100644 --- a/src/qt/pivx/governancemodel.cpp +++ b/src/qt/pivx/governancemodel.cpp @@ -227,7 +227,7 @@ void GovernanceModel::scheduleBroadcast(const CBudgetProposal& proposal) if (!pollTimer) pollTimer = new QTimer(this); if (!pollTimer->isActive()) { connect(pollTimer, &QTimer::timeout, this, &GovernanceModel::pollGovernanceChanged); - pollTimer->start(MODEL_UPDATE_DELAY * 60 * 3.5); // Every 3.5 minutes + pollTimer->start(MODEL_UPDATE_DELAY * 60 * (walletModel->isTestNetwork() ? 0.5 : 3.5)); // Every 3.5 minutes } } @@ -239,8 +239,22 @@ void GovernanceModel::pollGovernanceChanged() // Try to broadcast any pending for confirmations proposal auto it = waitingPropsForConfirmations.begin(); while (it != waitingPropsForConfirmations.end()) { + // Remove expired proposals + if (it->IsExpired(clientModel->getNumBlocks())) { + it = waitingPropsForConfirmations.erase(it); + continue; + } + + // Try to add it if (!g_budgetman.AddProposal(*it)) { LogPrint(BCLog::QT, "Cannot broadcast budget proposal - %s", it->IsInvalidReason()); + // Remove proposals who due a reorg lost their fee tx + if (it->IsInvalidReason().find("Can't find collateral tx") != std::string::npos) { + // future: notify the user about it. + it = waitingPropsForConfirmations.erase(it); + continue; + } + // Check if the proposal didn't exceed the superblock start height if (it->GetBlockStart() >= chainHeight) { // Edge case, the proposal was never broadcasted before the next superblock, can be removed. // future: notify the user about it. @@ -281,6 +295,7 @@ void GovernanceModel::txLoaded(const QString& id, const int txType, const int tx CBudgetProposal proposal; ss >> proposal; if (!g_budgetman.HaveProposal(proposal.GetHash()) && + !proposal.IsExpired(clientModel->getNumBlocks()) && proposal.GetBlockStart() < clientModel->getNumBlocks()) { scheduleBroadcast(proposal); } From de13d6f4b83d6b0cb1a116bcbb886b7f812cf0cc Mon Sep 17 00:00:00 2001 From: furszy Date: Fri, 28 May 2021 18:21:22 -0300 Subject: [PATCH 22/59] GUI governance: load the existent MNs votes in the mn selection dialog. --- src/qt/pivx/forms/mnselectiondialog.ui | 6 +++++ src/qt/pivx/governancemodel.cpp | 15 ++++++----- src/qt/pivx/governancemodel.h | 3 ++- src/qt/pivx/governancewidget.cpp | 5 ++++ src/qt/pivx/mnselectiondialog.cpp | 37 +++++++++++++++++++++++--- src/qt/pivx/mnselectiondialog.h | 5 ++++ src/qt/pivx/votedialog.cpp | 2 ++ 7 files changed, 61 insertions(+), 12 deletions(-) diff --git a/src/qt/pivx/forms/mnselectiondialog.ui b/src/qt/pivx/forms/mnselectiondialog.ui index aaea7f9499ca..f909849caf2b 100644 --- a/src/qt/pivx/forms/mnselectiondialog.ui +++ b/src/qt/pivx/forms/mnselectiondialog.ui @@ -296,6 +296,12 @@ Select All + + true + + + false + diff --git a/src/qt/pivx/governancemodel.cpp b/src/qt/pivx/governancemodel.cpp index 891fd5c6a73f..2d76563745f3 100644 --- a/src/qt/pivx/governancemodel.cpp +++ b/src/qt/pivx/governancemodel.cpp @@ -90,7 +90,7 @@ bool GovernanceModel::hasProposals() CAmount GovernanceModel::getMaxAvailableBudgetAmount() const { - return Params().GetConsensus().nBudgetCycleBlocks * COIN; + return g_budgetman.GetTotalBudget(clientModel->getNumBlocks()); } int GovernanceModel::getNumBlocksPerBudgetCycle() const @@ -113,11 +113,12 @@ int GovernanceModel::getNextSuperblockHeight() const std::vector GovernanceModel::getLocalMNsVotesForProposal(const ProposalInfo& propInfo) { // First, get the local masternodes - std::vector vecLocalMn; + std::vector> vecLocalMn; for (int i = 0; i < mnModel->rowCount(); ++i) { - vecLocalMn.emplace_back( - uint256S(mnModel->index(i, MNModel::COLLATERAL_ID, QModelIndex()).data().toString().toStdString()), - mnModel->index(i, MNModel::COLLATERAL_OUT_INDEX, QModelIndex()).data().toInt() + vecLocalMn.emplace_back(std::make_pair( + COutPoint(uint256S(mnModel->index(i, MNModel::COLLATERAL_ID, QModelIndex()).data().toString().toStdString()), + mnModel->index(i, MNModel::COLLATERAL_OUT_INDEX, QModelIndex()).data().toInt()), + mnModel->index(i, MNModel::ALIAS, QModelIndex()).data().toString().toStdString()) ); } @@ -127,8 +128,8 @@ std::vector GovernanceModel::getLocalMNsVotesForProposal(const Proposa const auto& mapVotes = prop->GetVotes(); for (const auto& it : mapVotes) { for (const auto& mn : vecLocalMn) { - if (it.first == mn && it.second.IsValid()) { - localVotes.emplace_back(mn, (VoteInfo::VoteDirection) it.second.GetDirection()); + if (it.first == mn.first && it.second.IsValid()) { + localVotes.emplace_back(mn.first, (VoteInfo::VoteDirection) it.second.GetDirection(), mn.second); break; } } diff --git a/src/qt/pivx/governancemodel.h b/src/qt/pivx/governancemodel.h index d6e0be338cd0..248b57c41c3a 100644 --- a/src/qt/pivx/governancemodel.h +++ b/src/qt/pivx/governancemodel.h @@ -64,9 +64,10 @@ struct VoteInfo { NO=2 }; - explicit VoteInfo(const COutPoint _mnId, VoteDirection _vote) : mnVoter(_mnId), vote(_vote) {} + explicit VoteInfo(const COutPoint _mnId, VoteDirection _vote, std::string _mnAlias) : mnVoter(_mnId), vote(_vote), mnAlias(_mnAlias) {} COutPoint mnVoter; VoteDirection vote; + std::string mnAlias; }; class CBudgetProposal; diff --git a/src/qt/pivx/governancewidget.cpp b/src/qt/pivx/governancewidget.cpp index 3afb04278ea4..aec03978bd6e 100644 --- a/src/qt/pivx/governancewidget.cpp +++ b/src/qt/pivx/governancewidget.cpp @@ -84,6 +84,11 @@ GovernanceWidget::~GovernanceWidget() void GovernanceWidget::onVoteForPropClicked(const ProposalInfo& proposalInfo) { + if (!governanceModel->isTierTwoSync()) { + inform(tr("Please wait until the node is fully synced")); + return; + } + if (proposalInfo.status == ProposalInfo::Status::WAITING_FOR_APPROVAL) { inform(tr("Cannot vote for the proposal yet, wait until it's confirmed by the network")); return; diff --git a/src/qt/pivx/mnselectiondialog.cpp b/src/qt/pivx/mnselectiondialog.cpp index 936c0bf4e5ac..0785e792d20e 100644 --- a/src/qt/pivx/mnselectiondialog.cpp +++ b/src/qt/pivx/mnselectiondialog.cpp @@ -32,16 +32,23 @@ MnSelectionDialog::MnSelectionDialog(QWidget *parent) : ui->treeWidget->setRootIsDecorated(false); ui->treeWidget->setFocusPolicy(Qt::NoFocus); - connect(ui->btnEsc, SIGNAL(clicked()), this, SLOT(close())); - connect(ui->btnCancel, SIGNAL(clicked()), this, SLOT(close())); - connect(ui->btnSave, SIGNAL(clicked()), this, SLOT(accept())); + connect(ui->btnEsc, &QPushButton::clicked, this, &MnSelectionDialog::close); + connect(ui->btnCancel, &QPushButton::clicked, this, &MnSelectionDialog::close); + connect(ui->btnSave, &QPushButton::clicked, this, &MnSelectionDialog::accept); + connect(ui->btnSelectAll, &QPushButton::clicked, this, &MnSelectionDialog::selectAll); connect(ui->treeWidget, &QTreeWidget::itemChanged, this, &MnSelectionDialog::viewItemChanged); } void MnSelectionDialog::setModel(MNModel* _mnModel) { mnModel = _mnModel; - updateView(); +} + +void MnSelectionDialog::setMnVoters(const std::vector& votes) +{ + for (const auto& voter : votes) { + selectedMnList.emplace_back(voter.mnAlias); + } } class MnInfo { @@ -76,6 +83,22 @@ void MnSelectionDialog::viewItemChanged(QTreeWidgetItem* item, int column) } } +void MnSelectionDialog::selectAll() +{ + const bool fSelectAll = ui->btnSelectAll->isChecked(); + Qt::CheckState wantedState = fSelectAll ? Qt::Checked : Qt::Unchecked; + ui->treeWidget->setEnabled(false); + for (int i = 0; i < ui->treeWidget->topLevelItemCount(); i++) + if (ui->treeWidget->topLevelItem(i)->checkState(COLUMN_CHECKBOX) != wantedState) + ui->treeWidget->topLevelItem(i)->setCheckState(COLUMN_CHECKBOX, wantedState); + ui->treeWidget->setEnabled(true); + if (!fSelectAll) { + selectedMnList.clear(); + } + updateView(); + ui->btnSelectAll->setText(fSelectAll ? tr("Unselect All") : tr("Select All")); +} + void MnSelectionDialog::updateView() { ui->treeWidget->clear(); @@ -110,6 +133,12 @@ void MnSelectionDialog::appendItem(QFlags flgCheckbox, itemOutput->setText(COLUMN_STATUS, mnStatus); itemOutput->setToolTip(COLUMN_STATUS, "Masternode status"); + if (std::find(selectedMnList.begin(), selectedMnList.end(), mnName.toStdString()) != selectedMnList.end()) { + itemOutput->setCheckState(COLUMN_CHECKBOX, Qt::Checked); + } else { + itemOutput->setCheckState(COLUMN_CHECKBOX, Qt::Unchecked); + } + if (mnStatus != "ENABLED") { itemOutput->setDisabled(true); // TODO: add disable icon. diff --git a/src/qt/pivx/mnselectiondialog.h b/src/qt/pivx/mnselectiondialog.h index 37d2dcd821af..9dd1689ffc6c 100644 --- a/src/qt/pivx/mnselectiondialog.h +++ b/src/qt/pivx/mnselectiondialog.h @@ -14,6 +14,7 @@ namespace Ui { class MNModel; class QTreeWidgetItem; class MnInfo; +struct VoteInfo; class MnSelectionDialog : public QDialog { @@ -25,10 +26,14 @@ Q_OBJECT void setModel(MNModel* _mnModel); void updateView(); + // Sets the MNs who already voted for this proposal + void setMnVoters(const std::vector& votes); + // Return the MNs who are going to vote for this proposal std::vector getSelectedMnAlias(); public Q_SLOTS: void viewItemChanged(QTreeWidgetItem*, int); + void selectAll(); private: Ui::MnSelectionDialog *ui; diff --git a/src/qt/pivx/votedialog.cpp b/src/qt/pivx/votedialog.cpp index ab18d288ffff..3f5d0e94157b 100644 --- a/src/qt/pivx/votedialog.cpp +++ b/src/qt/pivx/votedialog.cpp @@ -107,6 +107,8 @@ void VoteDialog::onMnSelectionClicked() mnSelectionDialog = new MnSelectionDialog(window); mnSelectionDialog->setModel(mnModel); } + mnSelectionDialog->setMnVoters(votes); + mnSelectionDialog->updateView(); mnSelectionDialog->resize(size()); if (openDialogWithOpaqueBackgroundY(mnSelectionDialog, window, 4.5, 5, false)) { vecSelectedMn = mnSelectionDialog->getSelectedMnAlias(); From 0b3b3ec6a1a8a27aec1a62ff62615f892f68ed15 Mon Sep 17 00:00:00 2001 From: Gregory Solarte Date: Fri, 28 May 2021 18:42:52 -0300 Subject: [PATCH 23/59] GUI: governance, proposals filter combobox. --- src/qt/pivx/forms/governancewidget.ui | 9 ++++-- src/qt/pivx/governancewidget.cpp | 38 +++++++++++++++------- src/qt/pivx/qtutils.cpp | 4 +-- src/qt/pivx/qtutils.h | 2 +- src/qt/pivx/res/css/style_dark.css | 18 +++++++++++ src/qt/pivx/res/css/style_light.css | 45 +++++++++++---------------- 6 files changed, 71 insertions(+), 45 deletions(-) diff --git a/src/qt/pivx/forms/governancewidget.ui b/src/qt/pivx/forms/governancewidget.ui index c377698df016..5cc8c7cf39da 100644 --- a/src/qt/pivx/forms/governancewidget.ui +++ b/src/qt/pivx/forms/governancewidget.ui @@ -136,9 +136,12 @@ 0 - - - Filter + + + + 110 + 0 + diff --git a/src/qt/pivx/governancewidget.cpp b/src/qt/pivx/governancewidget.cpp index aec03978bd6e..8c0ef0468a2a 100644 --- a/src/qt/pivx/governancewidget.cpp +++ b/src/qt/pivx/governancewidget.cpp @@ -32,20 +32,18 @@ GovernanceWidget::GovernanceWidget(PIVXGUI* parent) : setCssProperty(ui->pushImgEmpty, "img-empty-governance"); setCssProperty(ui->labelEmpty, "text-empty"); - // Combo box sort - setCssProperty(ui->comboBoxSort, "btn-combo"); - ui->comboBoxSort->setEditable(true); - SortEdit* lineEdit = new SortEdit(ui->comboBoxSort); - lineEdit->setReadOnly(true); - lineEdit->setAlignment(Qt::AlignRight); + // Font QFont font; font.setPointSize(14); + + // Combo box sort + SortEdit* lineEdit = new SortEdit(ui->comboBoxSort); lineEdit->setFont(font); - ui->comboBoxSort->setLineEdit(lineEdit); + initComboBox(ui->comboBoxSort, lineEdit, "btn-combo", false); - QStandardItemModel *model = new QStandardItemModel(this); - Delegate *delegate = new Delegate(this); - QList values; + QStandardItemModel* model = new QStandardItemModel(this); + Delegate* delegate = new Delegate(this); + QList values; // todo: Add sort actions values.append("Date"); values.append("Value"); values.append("Name"); @@ -55,9 +53,25 @@ GovernanceWidget::GovernanceWidget(PIVXGUI* parent) : delegate->setValues(values); ui->comboBoxSort->setModel(model); ui->comboBoxSort->setItemDelegate(delegate); + // Filter - ui->btnFilter->setText("Filter"); - ui->btnFilter->setProperty("cssClass", "btn-secundary-filter"); + SortEdit* lineEditFilter = new SortEdit(ui->comboBoxFilter); + lineEditFilter->setFont(font); + initComboBox(ui->comboBoxFilter, lineEditFilter, "btn-filter", false); + + QStandardItemModel* modelFilter = new QStandardItemModel(this); + Delegate* delegateFilter = new Delegate(this); + QList valuesFilter; // todo: Add filter actions + valuesFilter.append("All"); + valuesFilter.append("Passing"); + valuesFilter.append("Not Passing"); + valuesFilter.append("No Votes"); + for (int n = 0; n < values.size(); n++) { + modelFilter->appendRow(new QStandardItem(tr("Filter: %1").arg(valuesFilter.at(n)))); + } + delegateFilter->setValues(valuesFilter); + ui->comboBoxFilter->setModel(modelFilter); + ui->comboBoxFilter->setItemDelegate(delegateFilter); // Budget ui->labelBudget->setText("Budget Distribution"); diff --git a/src/qt/pivx/qtutils.cpp b/src/qt/pivx/qtutils.cpp index e68d86adf187..1086ee60a363 100644 --- a/src/qt/pivx/qtutils.cpp +++ b/src/qt/pivx/qtutils.cpp @@ -238,7 +238,7 @@ QColor getRowColor(bool isLightTheme, bool isHovered, bool isSelected) } } -void initComboBox(QComboBox* combo, QLineEdit* lineEdit, QString cssClass) +void initComboBox(QComboBox* combo, QLineEdit* lineEdit, QString cssClass, bool setView) { setCssProperty(combo, std::move(cssClass)); combo->setEditable(true); @@ -248,7 +248,7 @@ void initComboBox(QComboBox* combo, QLineEdit* lineEdit, QString cssClass) combo->setLineEdit(lineEdit); } combo->setStyleSheet("selection-background-color:transparent;"); - combo->setView(new QListView()); + if (setView) combo->setView(new QListView()); } void fillAddressSortControls(SortEdit* seType, SortEdit* seOrder, QComboBox* boxType, QComboBox* boxOrder) diff --git a/src/qt/pivx/qtutils.h b/src/qt/pivx/qtutils.h index fdf7b48742f3..d1bf4c839d30 100644 --- a/src/qt/pivx/qtutils.h +++ b/src/qt/pivx/qtutils.h @@ -58,7 +58,7 @@ void setupSettings(QSettings* settings); bool isLightTheme(); void setTheme(bool isLight); -void initComboBox(QComboBox* combo, QLineEdit* lineEdit = nullptr, QString cssClass = "btn-combo"); +void initComboBox(QComboBox* combo, QLineEdit* lineEdit = nullptr, QString cssClass = "btn-combo", bool setView = true); void fillAddressSortControls(SortEdit* seType, SortEdit* seOrder, QComboBox* boxType, QComboBox* boxOrder); void initCssEditLine(QLineEdit* edit, bool isDialog = false); void setCssEditLine(QLineEdit* edit, bool isValid, bool forceUpdate = false); diff --git a/src/qt/pivx/res/css/style_dark.css b/src/qt/pivx/res/css/style_dark.css index c4e3ebfd7f77..2f951f1a41cf 100644 --- a/src/qt/pivx/res/css/style_dark.css +++ b/src/qt/pivx/res/css/style_dark.css @@ -2221,6 +2221,24 @@ QComboBox[cssClass="btn-combo-small"]::down-arrow { height: 15px; } +QComboBox[cssClass="btn-filter"] { + background-image: url("://ic-filter"); + background-position:right center; + background-repeat:no-repeat; + background-color:#0f0b16; + padding:10px 10px 10px 10px; + border:1px solid #0f0b16; + font-size:16px; + color: #B3FFFFFF; + text-align: right; +} + +QComboBox[cssClass="btn-filter"]::down-arrow { + image: url(noimg); + width: 24px; + height: 24px; +} + QComboBox[cssClass="btn-combo-edit"] { background-color:#0f0b16; padding:10px 20px 10px 10px; diff --git a/src/qt/pivx/res/css/style_light.css b/src/qt/pivx/res/css/style_light.css index fe63afb4a848..a5d214a6195d 100644 --- a/src/qt/pivx/res/css/style_light.css +++ b/src/qt/pivx/res/css/style_light.css @@ -2224,33 +2224,6 @@ HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH*/ background-color: transparent; } - -QPushButton[cssClass="btn-secundary-filter"] { - background-image: url(://ic-filter); - background-position:right center; - background-repeat:no-repeat; - border: 1px solid #335c4b7d; - background-color:#FFFFFF; - font-size:18px; - padding-top:4px; - padding-left:6px; - padding-bottom:4px; - padding-right:30px; - color: #5c4b7d; - border-radius: 2px; -} - - -QPushButton[cssClass="btn-secundary-filter"]:hover { - border: 1px solid #5c4b7d; - background-color:#FFFFFF; -} - -QPushButton[cssClass="btn-secundary-filter"]:pressed { - border: 1px solid #5c4b7d; - background-color:#1A5c4b7d; -} - /*HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH HH GOVERNANCE CARD HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH*/ @@ -2639,6 +2612,24 @@ QComboBox[cssClass="btn-combo-small"]::down-arrow { height: 15px; } +QComboBox[cssClass="btn-filter"] { + background-image: url("://ic-filter"); + background-position:right center; + background-repeat:no-repeat; + background-color:#ffffff; + padding:10px 10px 10px 10px; + font-size:16px; + border:1px solid #ffffff; + color: #5c4b7d; + text-align: right; +} + +QComboBox[cssClass="btn-filter"]::down-arrow { + image: url(noimg); + width: 24px; + height: 24px; +} + QComboBox[cssClass="btn-combo-edit"] { background-color:#ffffff; padding:10px 20px 10px 10px; From 77bdd2ea15b98607d33de315877ab957b38945de Mon Sep 17 00:00:00 2001 From: Gregory Solarte Date: Fri, 28 May 2021 21:43:04 -0300 Subject: [PATCH 24/59] GUI: governance, dark theme style added --- src/Makefile.qt.include | 3 + src/qt/pivx.qrc | 3 + src/qt/pivx/res/css/style_dark.css | 410 +++++++++++++++++- src/qt/pivx/res/css/style_light.css | 7 + src/qt/pivx/res/img/ic-check-block.svg | 4 + .../res/img/ic-check-vote-active-dark.svg | 5 + src/qt/pivx/res/img/ic-check-vote-dark.svg | 4 + 7 files changed, 435 insertions(+), 1 deletion(-) create mode 100644 src/qt/pivx/res/img/ic-check-block.svg create mode 100644 src/qt/pivx/res/img/ic-check-vote-active-dark.svg create mode 100644 src/qt/pivx/res/img/ic-check-vote-dark.svg diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include index 7cbc31b45895..790e3769f3be 100644 --- a/src/Makefile.qt.include +++ b/src/Makefile.qt.include @@ -504,6 +504,9 @@ RES_ICONS = \ qt/pivx/res/img/ani-loading.gif \ qt/pivx/res/img/ic-check-vote.svg \ qt/pivx/res/img/ic-check-vote-active.svg \ + qt/pivx/res/img/ic-check-vote-dark.svg \ + qt/pivx/res/img/ic-check-vote-active-dark.svg \ + qt/pivx/res/img/ic-check-block.svg \ qt/pivx/res/img/ic-filter.svg \ qt/pivx/res/img/ic-link.svg \ qt/pivx/res/img/ic-nav-governance.svg \ diff --git a/src/qt/pivx.qrc b/src/qt/pivx.qrc index d18fbd19cf4e..43422dd61bf6 100644 --- a/src/qt/pivx.qrc +++ b/src/qt/pivx.qrc @@ -225,6 +225,8 @@ pivx/res/img/ic-information-hover.svg pivx/res/img/ic-check-vote.svg pivx/res/img/ic-check-vote-active.svg + pivx/res/img/ic-check-vote-dark.svg + pivx/res/img/ic-check-vote-active-dark.svg pivx/res/img/ic-filter.svg pivx/res/img/ic-link.svg pivx/res/img/ic-link-hover.svg @@ -233,5 +235,6 @@ pivx/res/img/ic-nav-governance-hover.svg pivx/res/img/ic-time.svg pivx/res/img/img-empty-governance.svg + pivx/res/img/ic-check-block.svg diff --git a/src/qt/pivx/res/css/style_dark.css b/src/qt/pivx/res/css/style_dark.css index 2f951f1a41cf..894440b2cad6 100644 --- a/src/qt/pivx/res/css/style_dark.css +++ b/src/qt/pivx/res/css/style_dark.css @@ -394,6 +394,41 @@ QPushButton[cssClass="btn-check-cold-staking"]:checked { color: #B088FF; } +*[cssClass="btn-nav-governance"] { + qproperty-icon: url("://ic-nav-governance") off, + url("://ic-nav-governance-active") on ; + qproperty-iconSize: 32px 32px; + background-color:transparent; + font-size:14px; + color: #938da5; +} + +*[cssClass="btn-nav-governance"]:checked { + background-color: qlineargradient(x1:0, x2: 1, stop: 0 #3c2559, stop: 1 #1f162b); + font-size:14px; + color: #B088FF; +} + +*[cssClass="btn-nav-governance"]:checked:hover { + background-color: qlineargradient(x1:0, x2: 1, stop: 0 #3c2559, stop: 1 #1f162b); + color: #B088FF; +} + +*[cssClass="btn-nav-governance"]:hover { + qproperty-icon: url("://ic-nav-governance-hover"); + qproperty-iconSize: 32px 32px; + background-color:#0f0b16; + color: #FFFFFF; +} + +*[cssClass="btn-nav-governance-active"] { + qproperty-icon:url("://ic-nav-governance-active") ; + qproperty-iconSize: 32px 32px; + background-color: #0f0b16; + font-size:14px; + color: #B088FF; +} + /*HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH HH TOP BAR HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH*/ @@ -2137,7 +2172,6 @@ QComboBox[cssClass="btn-combo-coins"]::down-arrow { width: 16px; } - /*HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH HH RECEIVE HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH*/ @@ -2149,6 +2183,372 @@ HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH*/ padding:10px 10px; } +/*HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH +HH PPROPOSAL +HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH*/ + +*[cssClass="dialog-proposal-message"] { + color:#707070; + font-size:16px; +} + +/*HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH +HH GOVERNANCE +HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH*/ + +*[cssClass="label-budget-title"] { + color:#B3FFFFFF; + font-size:22px; +} + +*[cssClass="label-budget-text"] { + color:#B3FFFFFF; + font-size:16px; +} + + +*[cssClass="label-budget-amount"] { + color:#ffffff; + font-size:17px; +} + +*[cssClass="label-budget-amount-allocated"] { + color:#4d4d4d; + font-size:17px; +} + +*[cssClass="ic-time"] { + qproperty-icon: url("://ic-time"); + qproperty-iconSize: 26px 26px; + background-color: transparent; +} + +/*HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH +HH GOVERNANCE CARD +HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH*/ + +*[cssClass="card-governance"] { + background-color:#0f0b16; + border: 1px solid #5c4b7d; + border-radius: 6px; +} + +*[cssClass="card-title"] { + color:#b088ff; + font-size:20px; +} + +*[cssClass="card-amount"] { + color:#ffffff; + font-size:18px; +} + +*[cssClass="card-time"] { + color:#B3FFFFFF; + font-size:14px; +} + +*[cssClass="card-status-passing"] { + color:#B3FFFFFF; + font-size:12px; + border-radius:12px; + background-color:rgba(176, 136, 255, 0.2); + padding:6px 12px; +} + +*[cssClass="card-status-not-passing"] { + color:#B3FFFFFF; + font-size:12px; + border-radius:12px; + background-color:rgba(186, 186, 186, 0.2); + padding:6px 12px; +} + +*[cssClass="card-status-no-votes"] { + border: 1px solid #BABABA; + color:#B3FFFFFF; + font-size:12px; + border-radius:12px; + background-color:transparent; + padding:6px 12px; +} + +*[cssClass="card-progress-box"] { + border: 1px solid #5c4b7d; + border-radius: 2px; +} + +*[cssClass="card-progress-no"] { + color:#B3FFFFFF; + font-size:14px; + padding-left:4px; + background-color:transparent; +} + +*[cssClass="card-progress-no"]::chunk:horizontal { + background: #B3000000; + margin-right: 2px; +} + +*[cssClass="label-progress-no"] { + color:#E6FFFFFF; +} + +*[cssClass="label-progress-yes"] { + color:#b088ff; +} + +*[cssClass="card-progress-yes"] { + color:#b088ff; + font-size:14px; + padding-right:4px; + background-color:transparent; +} + +*[cssClass="card-progress-yes"]::chunk:horizontal { + background: transparent; + margin-right: 2px; +} + +*[cssClass="btn-link"] { + qproperty-icon: url(" "); + qproperty-iconSize: 22px 22px; + background-image: url("://ic-link"); + background-repeat: no-repeat; + background-color: transparent; + background-position: center center; +} + +*[cssClass="btn-link"]:hover { + background-color: transparent; + background-image: url("://ic-link-hover"); + background-repeat: no-repeat; + background-position: center center; +} + +*[cssClass="card-btn-vote"] { + background-position:right center; + background-repeat:no-repeat; + border: 1px solid #5c4b7d; + background-color:#0f0b16; + font-size:18px; + color: #b088ff; + border-radius: 2px; + padding:4px 10px; +} + +QPushButton[cssClass="card-btn-vote"]:hover { + border: 1px solid #b088ff; + background-color:#0f0b16; +} + +QPushButton[cssClass="card-btn-vote"]:pressed { + border: 1px solid #b088ff; + background-color:#4Db088ff; +} + + +/*HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH +HH VOTE DIALOG +HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH*/ + +*[cssClass="vote-title"] { + color:#b088ff; + font-size:22px; +} + +*[cssClass="vote-amount"] { + color:#FFFFFF; + font-size:20px; +} + +*[cssClass="vote-time"] { + color:#b0ffffff; + font-size:18px; +} + +*[cssClass="btn-vote-select"] { + background: transparent; + border: 0px; + color:#5C4B7D; + font-size:16px; +} + +*[cssClass="btn-vote-select"]:hover { + text-decoration: underline; +} + + +*[cssClass="vote-message"] { + color:#707070; + font-size:14px; +} + +*[cssClass="vote-grid"] { + border: 1px solid #5c4b7d; + background-color:transparent; +} + + +*[cssClass="vote-progress"] { + color: #5c4b7d; + font-size:14px; + background-color: transparent; + border-radius: 2px; +} + + +*[cssClass="vote-progress"]::chunk:horizontal { + background: rgba(92, 75, 125, 0.2); + border-right:1px solid #5c4b7d; +} + + + +*[cssClass="vote-progress-no"] { + height: 36px; + color: #4d4d4d; + font-size:14px; + background-color: transparent; + border-radius: 2px; +} + + +*[cssClass="vote-progress-no"]::chunk:horizontal { + background: rgba(238, 238, 238, 0.85); + height: 36px; +} + + +*[cssClass="vote-progress-yes"] { + min-height: 36px; + color: #4d4d4d; + font-size:14px; + background-color: transparent; + border-radius: 2px; +} + + +*[cssClass="vote-progress-yes"]::chunk:horizontal { + background: rgba(92, 75, 125, 0.4); +} + +QCheckBox[cssClass="check-vote"] { + spacing: 5px; + padding:5px; + font-size:18px; + color:#E6ffffff; +} + +QCheckBox:checked[cssClass="check-vote"] { + spacing: 5px; + font-size:18px; + color:#B088FF; +} + +QCheckBox::indicator[cssClass="check-vote"] { + width: 24px; + height: 24px; +} + +QCheckBox::indicator:unchecked[cssClass="check-vote"] { + image: url(://ic-check-vote-dark); +} + +QCheckBox::indicator:checked[cssClass="check-vote"] { + image: url(://ic-check-vote-active-dark); +} + +/*HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH +HH SELECT MASTER NODE +HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH*/ + +*[cssClass="text-title-purple"] { + color: #5C4B7D; + font-size: 14px; +} + +*[cssClass="text-purple"] { + color: #5C4B7D; + font-size: 14px; + padding-left: 8px; + padding-right: 8px; +} + +*[cssClass="btn-dialog-secundary"] { + border: 1px solid #5c4b7d; + background-color:#0f0b16; + border-radius: 2px; + color: #b088ff; + font-size: 14px; + padding-left: 8px; + padding-right: 8px; +} + +*[cssClass="btn-dialog-secundary"]:hover { + border: 1px solid #b088ff; + background-color:#0f0b16; +} + +QHeaderView::section { + background-color: #5C4B7D; + color: white; + font-size: 14px; + padding-top: 8px; + padding-bottom: 8px; + border: 1px solid #5C4B7D; +} + +QTreeView { + show-decoration-selected: 1; + background: white; + border: 1px solid #5C4B7D; + font-size: 14px; + color: #4d4d4d; + padding-left: 0px; +} + +QTreeView::item { + border:0px; + padding-top: 8px; + padding-bottom: 8px; + padding-left: 0px; + font-size: 14px; + color: #4d4d4d; +} + +QTreeView::item:hover { + background:rgba(176, 136, 255, 0.2); + border:0px; +} + +QTreeView::item:selected { + color: #5C4B7D; + border:0px; +} + +QTreeView::item:selected:active{ + color: #5C4B7D; + border:0px; +} + +QTreeView::item:selected:!active { + color:#5C4B7D; + border:0px; +} + +QTreeView::indicator { + width: 24px; + height: 24px; +} + +QTreeView::indicator:unchecked { + image: url(://ic-check-box); +} + +QTreeView::indicator:checked { + image: url(://ic-check-active); +} /*HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH HH SPIN BOX @@ -2790,6 +3190,14 @@ HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH*/ border:none; } +*[cssClass="ic-chevron-left"] { + qproperty-icon: url("://ic-chevron-left") off, + url("://ic-chevron-left") on ; + qproperty-iconSize: 24px 24px; + background-color: transparent; + border:none; +} + *[cssClass="layout-arrow"] { background: url("://ic-arrow-right-white"); background-repeat:no-repeat; diff --git a/src/qt/pivx/res/css/style_light.css b/src/qt/pivx/res/css/style_light.css index a5d214a6195d..59935e2fab77 100644 --- a/src/qt/pivx/res/css/style_light.css +++ b/src/qt/pivx/res/css/style_light.css @@ -2296,6 +2296,13 @@ HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH*/ margin-right: 2px; } +*[cssClass="label-progress-no"] { + color:#4D4D4D; +} + +*[cssClass="label-progress-yes"] { + color:#5C4B7D; +} *[cssClass="card-progress-yes"] { color:#4D4D4D; diff --git a/src/qt/pivx/res/img/ic-check-block.svg b/src/qt/pivx/res/img/ic-check-block.svg new file mode 100644 index 000000000000..9f9cc3b98070 --- /dev/null +++ b/src/qt/pivx/res/img/ic-check-block.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/qt/pivx/res/img/ic-check-vote-active-dark.svg b/src/qt/pivx/res/img/ic-check-vote-active-dark.svg new file mode 100644 index 000000000000..3517228c50ee --- /dev/null +++ b/src/qt/pivx/res/img/ic-check-vote-active-dark.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/qt/pivx/res/img/ic-check-vote-dark.svg b/src/qt/pivx/res/img/ic-check-vote-dark.svg new file mode 100644 index 000000000000..23910e5da246 --- /dev/null +++ b/src/qt/pivx/res/img/ic-check-vote-dark.svg @@ -0,0 +1,4 @@ + + + + From bb08e442f592fea93f751bc162872604a7ea550c Mon Sep 17 00:00:00 2001 From: furszy Date: Fri, 28 May 2021 19:34:48 -0300 Subject: [PATCH 25/59] active masternode do not stall if ACTIVE_MASTERNODE_NOT_CAPABLE was set. --- src/activemasternode.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/activemasternode.cpp b/src/activemasternode.cpp index 89823f5b503b..8002f2885778 100644 --- a/src/activemasternode.cpp +++ b/src/activemasternode.cpp @@ -316,7 +316,7 @@ void CActiveMasternode::ManageStatus() if (status == ACTIVE_MASTERNODE_SYNC_IN_PROCESS) status = ACTIVE_MASTERNODE_INITIAL; - if (status == ACTIVE_MASTERNODE_INITIAL) { + if (status == ACTIVE_MASTERNODE_INITIAL || (pmn && status == ACTIVE_MASTERNODE_NOT_CAPABLE)) { if (pmn) { if (pmn->protocolVersion != PROTOCOL_VERSION) { LogPrintf("%s: ERROR Trying to start a masternode running an old protocol version, " From 34a982c6ed9a681f30c44dee126e479ec36b2802 Mon Sep 17 00:00:00 2001 From: furszy Date: Fri, 28 May 2021 19:36:17 -0300 Subject: [PATCH 26/59] GUI governance, refresh grid after vote. --- src/qt/pivx/governancewidget.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/qt/pivx/governancewidget.cpp b/src/qt/pivx/governancewidget.cpp index 8c0ef0468a2a..ba8847b36c29 100644 --- a/src/qt/pivx/governancewidget.cpp +++ b/src/qt/pivx/governancewidget.cpp @@ -111,6 +111,8 @@ void GovernanceWidget::onVoteForPropClicked(const ProposalInfo& proposalInfo) VoteDialog* dialog = new VoteDialog(window, governanceModel, mnModel); dialog->setProposal(proposalInfo); if (openDialogWithOpaqueBackgroundY(dialog, window, 4.5, 5)) { + // future: make this refresh atomic, no need to refresh the entire grid. + tryGridRefresh(true); inform(tr("Vote emitted successfully!")); } dialog->deleteLater(); From 6603c10cf1773a085cb6be61495e42c4ac6a1ada Mon Sep 17 00:00:00 2001 From: furszy Date: Sat, 29 May 2021 01:26:17 -0300 Subject: [PATCH 27/59] GovernanceModel: Do not try to broadcast proposal whose fee tx is conflicted or not accepted. --- src/qt/pivx/governancemodel.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/qt/pivx/governancemodel.cpp b/src/qt/pivx/governancemodel.cpp index 2d76563745f3..963be6725768 100644 --- a/src/qt/pivx/governancemodel.cpp +++ b/src/qt/pivx/governancemodel.cpp @@ -285,6 +285,11 @@ void GovernanceModel::stopPolling() void GovernanceModel::txLoaded(const QString& id, const int txType, const int txStatus) { if (txType == TransactionRecord::SendToNobody) { + // If the tx is not longer available in the mainchain, drop it. + if (txStatus == TransactionStatus::Conflicted || + txStatus == TransactionStatus::NotAccepted) { + return; + } // If this is a proposal fee, parse it. const auto& wtx = walletModel->getTx(uint256S(id.toStdString())); assert(wtx); From d18d265101a8900f3c57606ee859aed782713803 Mon Sep 17 00:00:00 2001 From: furszy Date: Sat, 29 May 2021 08:58:02 -0300 Subject: [PATCH 28/59] GUI governance: add proposals filter --- src/qt/pivx/forms/governancewidget.ui | 2 +- src/qt/pivx/governancemodel.cpp | 12 ++++++-- src/qt/pivx/governancemodel.h | 2 +- src/qt/pivx/governancewidget.cpp | 44 ++++++++++++++++++++++----- src/qt/pivx/governancewidget.h | 4 +++ 5 files changed, 51 insertions(+), 13 deletions(-) diff --git a/src/qt/pivx/forms/governancewidget.ui b/src/qt/pivx/forms/governancewidget.ui index 5cc8c7cf39da..6183a5817fc1 100644 --- a/src/qt/pivx/forms/governancewidget.ui +++ b/src/qt/pivx/forms/governancewidget.ui @@ -139,7 +139,7 @@ - 110 + 115 0 diff --git a/src/qt/pivx/governancemodel.cpp b/src/qt/pivx/governancemodel.cpp index 963be6725768..315438080d66 100644 --- a/src/qt/pivx/governancemodel.cpp +++ b/src/qt/pivx/governancemodel.cpp @@ -65,20 +65,26 @@ ProposalInfo GovernanceModel::buidProposalInfo(const CBudgetProposal* prop, bool status); } -std::list GovernanceModel::getProposals() +std::list GovernanceModel::getProposals(const ProposalInfo::Status* filterByStatus) { if (!clientModel) return {}; std::list ret; std::vector budget = g_budgetman.GetBudget(); for (const auto& prop : g_budgetman.GetAllProposalsOrdered()) { bool isPassing = std::find(budget.begin(), budget.end(), *prop) != budget.end(); - ret.emplace_back(buidProposalInfo(prop, isPassing, false)); + ProposalInfo propInfo = buidProposalInfo(prop, isPassing, false); + if (!filterByStatus || propInfo.status == *filterByStatus) { + ret.emplace_back(propInfo); + } if (isPassing) allocatedAmount += prop->GetAmount(); } // Add pending proposals for (const auto& prop : waitingPropsForConfirmations) { - ret.emplace_back(buidProposalInfo(&prop, false, true)); + ProposalInfo propInfo = buidProposalInfo(&prop, false, true); + if (!filterByStatus || propInfo.status == *filterByStatus) { + ret.emplace_back(propInfo); + } } return ret; } diff --git a/src/qt/pivx/governancemodel.h b/src/qt/pivx/governancemodel.h index 248b57c41c3a..797f67515222 100644 --- a/src/qt/pivx/governancemodel.h +++ b/src/qt/pivx/governancemodel.h @@ -89,7 +89,7 @@ class GovernanceModel : public QObject void setWalletModel(WalletModel* _walletModel); // Return proposals ordered by net votes - std::list getProposals(); + std::list getProposals(const ProposalInfo::Status* filterByStatus = nullptr); // Returns true if there is at least one proposal cached bool hasProposals(); // Whether a visual refresh is needed diff --git a/src/qt/pivx/governancewidget.cpp b/src/qt/pivx/governancewidget.cpp index ba8847b36c29..6b66e4beeb53 100644 --- a/src/qt/pivx/governancewidget.cpp +++ b/src/qt/pivx/governancewidget.cpp @@ -11,6 +11,9 @@ #include #include +// Proposals filter +const std::vector propFilter = {"All", "Passing", "Not Passing", "Waiting"}; + GovernanceWidget::GovernanceWidget(PIVXGUI* parent) : PWidget(parent), ui(new Ui::governancewidget) @@ -39,6 +42,7 @@ GovernanceWidget::GovernanceWidget(PIVXGUI* parent) : // Combo box sort SortEdit* lineEdit = new SortEdit(ui->comboBoxSort); lineEdit->setFont(font); + lineEdit->setAlignment(Qt::AlignRight); initComboBox(ui->comboBoxSort, lineEdit, "btn-combo", false); QStandardItemModel* model = new QStandardItemModel(this); @@ -53,6 +57,7 @@ GovernanceWidget::GovernanceWidget(PIVXGUI* parent) : delegate->setValues(values); ui->comboBoxSort->setModel(model); ui->comboBoxSort->setItemDelegate(delegate); + ui->comboBoxSort->setVisible(false); // Filter SortEdit* lineEditFilter = new SortEdit(ui->comboBoxFilter); @@ -61,17 +66,20 @@ GovernanceWidget::GovernanceWidget(PIVXGUI* parent) : QStandardItemModel* modelFilter = new QStandardItemModel(this); Delegate* delegateFilter = new Delegate(this); - QList valuesFilter; // todo: Add filter actions - valuesFilter.append("All"); - valuesFilter.append("Passing"); - valuesFilter.append("Not Passing"); - valuesFilter.append("No Votes"); - for (int n = 0; n < values.size(); n++) { - modelFilter->appendRow(new QStandardItem(tr("Filter: %1").arg(valuesFilter.at(n)))); + QList valuesFilter; + for (int i = 0; i < propFilter.size(); ++i) { + QString str = QString::fromStdString(propFilter[i]); + valuesFilter.append(str); + auto item = new QStandardItem(tr("Filter: %1").arg(str)); + item->setData(i); + modelFilter->appendRow(item); } delegateFilter->setValues(valuesFilter); ui->comboBoxFilter->setModel(modelFilter); ui->comboBoxFilter->setItemDelegate(delegateFilter); + ui->comboBoxFilter->setCurrentIndex(0); + connect(ui->comboBoxFilter, static_cast(&QComboBox::currentTextChanged), + this, &GovernanceWidget::onFilterChanged); // Budget ui->labelBudget->setText("Budget Distribution"); @@ -96,6 +104,26 @@ GovernanceWidget::~GovernanceWidget() delete ui; } +void GovernanceWidget::onFilterChanged(const QString& value) +{ + int filterByType = ui->comboBoxFilter->currentIndex(); + switch (filterByType) { + case 1: + statusFilter = ProposalInfo::Status::PASSING; + break; + case 2: + statusFilter = ProposalInfo::Status::NOT_PASSING; + break; + case 3: + statusFilter = ProposalInfo::Status::WAITING_FOR_APPROVAL; + break; + default: + statusFilter = nullopt; + break; + } + refreshCardsGrid(true); +} + void GovernanceWidget::onVoteForPropClicked(const ProposalInfo& proposalInfo) { if (!governanceModel->isTierTwoSync()) { @@ -242,7 +270,7 @@ void GovernanceWidget::refreshCardsGrid(bool forceRefresh) // Refresh grid only if needed if (!(forceRefresh || governanceModel->isRefreshNeeded())) return; - std::list props = governanceModel->getProposals(); + std::list props = governanceModel->getProposals(statusFilter.get_ptr()); // Start marking all the cards for (ProposalCard* card : cards) { diff --git a/src/qt/pivx/governancewidget.h b/src/qt/pivx/governancewidget.h index e20eb22f6283..23b83194ba33 100644 --- a/src/qt/pivx/governancewidget.h +++ b/src/qt/pivx/governancewidget.h @@ -68,6 +68,7 @@ class GovernanceWidget : public PWidget void setMNModel(MNModel* _mnModel); public Q_SLOTS: + void onFilterChanged(const QString& value); void chainHeightChanged(int height); void onVoteForPropClicked(const ProposalInfo& proposalInfo); void onCreatePropClicked(); @@ -81,6 +82,9 @@ public Q_SLOTS: int propsPerRow = 0; QTimer* refreshTimer{nullptr}; + // Proposals filter + Optional statusFilter{nullopt}; + void showEmptyScreen(bool show); void tryGridRefresh(bool force=false); ProposalCard* newCard(); From ebb9c1fa51c059d5ac9baf942b35a1e69d189f12 Mon Sep 17 00:00:00 2001 From: furszy Date: Sat, 29 May 2021 12:39:23 -0300 Subject: [PATCH 29/59] GUI: proposal url copy to clipboard connected. --- src/qt/pivx/governancewidget.cpp | 1 + src/qt/pivx/proposalcard.cpp | 3 ++- src/qt/pivx/proposalcard.h | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/qt/pivx/governancewidget.cpp b/src/qt/pivx/governancewidget.cpp index 6b66e4beeb53..3671a3d74b5f 100644 --- a/src/qt/pivx/governancewidget.cpp +++ b/src/qt/pivx/governancewidget.cpp @@ -237,6 +237,7 @@ ProposalCard* GovernanceWidget::newCard() { ProposalCard* propCard = new ProposalCard(ui->scrollAreaWidgetContents); connect(propCard, &ProposalCard::voteClicked, this, &GovernanceWidget::onVoteForPropClicked); + connect(propCard, &ProposalCard::inform, this, &GovernanceWidget::inform); setCardShadow(propCard); return propCard; } diff --git a/src/qt/pivx/proposalcard.cpp b/src/qt/pivx/proposalcard.cpp index a9ec45fd4d76..e99bd1d4a665 100644 --- a/src/qt/pivx/proposalcard.cpp +++ b/src/qt/pivx/proposalcard.cpp @@ -67,7 +67,8 @@ void ProposalCard::setProposal(const ProposalInfo& _proposalInfo) void ProposalCard::onCopyUrlClicked() { - // todo: add copy + GUIUtil::setClipboard(QString::fromStdString(proposalInfo.url)); + Q_EMIT inform(tr("Proposal URL copied to clipboard")); } ProposalCard::~ProposalCard() diff --git a/src/qt/pivx/proposalcard.h b/src/qt/pivx/proposalcard.h index 0d0ca8a35094..f8c92f3e7d2b 100644 --- a/src/qt/pivx/proposalcard.h +++ b/src/qt/pivx/proposalcard.h @@ -35,6 +35,7 @@ public Q_SLOTS: Q_SIGNALS: void voteClicked(const ProposalInfo& proposalInfo); + void inform(const QString& text); private: Ui::ProposalCard *ui; From 8800a6303fa98b1d9fafcde8844ea6eecb345888 Mon Sep 17 00:00:00 2001 From: furszy Date: Sat, 29 May 2021 16:40:42 -0300 Subject: [PATCH 30/59] GUI: add checkbox disabled style. --- src/qt/pivx/mnselectiondialog.cpp | 2 -- src/qt/pivx/res/css/style_dark.css | 7 +++++++ src/qt/pivx/res/css/style_light.css | 8 ++++++++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/qt/pivx/mnselectiondialog.cpp b/src/qt/pivx/mnselectiondialog.cpp index 0785e792d20e..27aa59ad748d 100644 --- a/src/qt/pivx/mnselectiondialog.cpp +++ b/src/qt/pivx/mnselectiondialog.cpp @@ -141,8 +141,6 @@ void MnSelectionDialog::appendItem(QFlags flgCheckbox, if (mnStatus != "ENABLED") { itemOutput->setDisabled(true); - // TODO: add disable icon. - //itemOutput->setIcon(COLUMN_CHECKBOX, QIcon(":/icons/check_disabled")); } } diff --git a/src/qt/pivx/res/css/style_dark.css b/src/qt/pivx/res/css/style_dark.css index 894440b2cad6..53903cbaf5a0 100644 --- a/src/qt/pivx/res/css/style_dark.css +++ b/src/qt/pivx/res/css/style_dark.css @@ -2103,6 +2103,9 @@ QCheckBox::indicator:checked { image: url("://ic-check-liliac-on"); } +QTreeView::indicator:indeterminate:disabled{ + image: url("://ic-check-block") +} QCheckBox[cssClass="btn-watch-password"] { spacing: 5px; @@ -3634,6 +3637,10 @@ HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH*/ image: url("://ic-check-box-dark-active"); } +#treeWidget::indicator:unchecked:disabled { + image: url("://ic-check-block"); +} + #treeWidget::item { border:none; border-bottom: 1px solid #40ffffff; diff --git a/src/qt/pivx/res/css/style_light.css b/src/qt/pivx/res/css/style_light.css index 59935e2fab77..d6f3a0709327 100644 --- a/src/qt/pivx/res/css/style_light.css +++ b/src/qt/pivx/res/css/style_light.css @@ -2104,6 +2104,10 @@ QCheckBox::indicator:checked { image: url("://ic-check-active"); } +QTreeView::indicator:indeterminate:disabled{ + image: url("://ic-check-block") +} + QCheckBox[cssClass="btn-watch-password"] { spacing: 5px; @@ -3628,6 +3632,10 @@ HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH*/ image: url("://ic-check-active"); } +#treeWidget::indicator:unchecked:disabled { + image: url("://ic-check-block"); +} + #treeWidget::item { border:none; border-bottom: 1px solid #bababa; From caa5b0ab76bbc97f8d5387e6664850e22f306a41 Mon Sep 17 00:00:00 2001 From: furszy Date: Sat, 29 May 2021 17:22:04 -0300 Subject: [PATCH 31/59] GUI: improve create proposal dialog subtitles. --- src/qt/pivx/forms/createproposaldialog.ui | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/qt/pivx/forms/createproposaldialog.ui b/src/qt/pivx/forms/createproposaldialog.ui index d79457183118..f8f48fb8c768 100644 --- a/src/qt/pivx/forms/createproposaldialog.ui +++ b/src/qt/pivx/forms/createproposaldialog.ui @@ -875,7 +875,7 @@ - Send Proposal + Proposal Recipient @@ -888,7 +888,10 @@ - Add the amount and time + Add the amount per month, the months that will remain active and the recipient address + + + true Qt::AlignCenter @@ -1051,7 +1054,7 @@ - Summary + Proposal Summary From ec99bae40c6276ec5ed564b613102f988c996b9c Mon Sep 17 00:00:00 2001 From: furszy Date: Sat, 29 May 2021 17:27:00 -0300 Subject: [PATCH 32/59] GUI: improve proposalcard status --- src/qt/pivx/proposalcard.cpp | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/qt/pivx/proposalcard.cpp b/src/qt/pivx/proposalcard.cpp index e99bd1d4a665..febea4701b0f 100644 --- a/src/qt/pivx/proposalcard.cpp +++ b/src/qt/pivx/proposalcard.cpp @@ -50,18 +50,22 @@ void ProposalCard::setProposal(const ProposalInfo& _proposalInfo) ui->labelYes->setText(QString::fromStdString("Yes "+ std::to_string((int)percentageYes) + "%")); QString cssClassStatus; - if (percentageYes < percentageNo) { + if (proposalInfo.status == ProposalInfo::WAITING_FOR_APPROVAL){ + cssClassStatus = "card-status-no-votes"; + ui->labelStatus->setText(tr("Waiting")); + ui->votesBar->setValue(50); + } else if (totalVotes == 0) { + cssClassStatus = "card-status-no-votes"; + ui->labelStatus->setText(tr("No Votes")); + ui->votesBar->setValue(50); + } else if (proposalInfo.status == ProposalInfo::NOT_PASSING || + proposalInfo.status == ProposalInfo::PASSING_NOT_FUNDED) { cssClassStatus = "card-status-not-passing"; ui->labelStatus->setText(tr("Not Passing")); - } else if (percentageYes > percentageNo) { + } else if (proposalInfo.status == ProposalInfo::PASSING) { cssClassStatus = "card-status-passing"; ui->labelStatus->setText(tr("Passing")); - } else { - cssClassStatus = "card-status-no-votes"; - ui->labelStatus->setText(proposalInfo.status == ProposalInfo::Status::WAITING_FOR_APPROVAL ? - tr("Waiting") : tr("No Votes")); } - setCssProperty(ui->labelStatus, cssClassStatus, true); } From 2d15cd6a87218658faf7d3836b898307d7c52192 Mon Sep 17 00:00:00 2001 From: Gregory Solarte Date: Tue, 1 Jun 2021 15:01:09 -0300 Subject: [PATCH 33/59] [GUI] dark theme mnselectiondialog. --- src/qt/pivx/forms/mnselectiondialog.ui | 16 ++++++++-- src/qt/pivx/mnselectiondialog.cpp | 6 ++-- src/qt/pivx/res/css/style_dark.css | 41 ++++++++++++++++---------- src/qt/pivx/res/css/style_light.css | 8 +++-- 4 files changed, 49 insertions(+), 22 deletions(-) diff --git a/src/qt/pivx/forms/mnselectiondialog.ui b/src/qt/pivx/forms/mnselectiondialog.ui index f909849caf2b..30cc77a83571 100644 --- a/src/qt/pivx/forms/mnselectiondialog.ui +++ b/src/qt/pivx/forms/mnselectiondialog.ui @@ -75,6 +75,9 @@ 24 + + Qt::NoFocus + @@ -226,13 +229,13 @@ 4 - 4 + 12 4 - 4 + 12 4 @@ -293,6 +296,9 @@ 36 + + Qt::NoFocus + Select All @@ -377,6 +383,9 @@ 50 + + Qt::NoFocus + CANCEL @@ -390,6 +399,9 @@ 50 + + Qt::NoFocus + SELECT VOTES diff --git a/src/qt/pivx/mnselectiondialog.cpp b/src/qt/pivx/mnselectiondialog.cpp index 27aa59ad748d..2cd74a243666 100644 --- a/src/qt/pivx/mnselectiondialog.cpp +++ b/src/qt/pivx/mnselectiondialog.cpp @@ -19,9 +19,9 @@ MnSelectionDialog::MnSelectionDialog(QWidget *parent) : setCssProperty(ui->btnEsc, "ic-chevron-left"); setCssProperty(ui->btnCancel, "btn-dialog-cancel"); setCssProperty(ui->btnSave, "btn-primary"); - setCssProperty(ui->containerAmountOfVotes, "container-border-purple"); - setCssProperty(ui->labelAmountOfVotesText, "text-purple"); - setCssProperty(ui->labelAmountOfVotes, "text-purple"); + setCssProperty(ui->containerAmountOfVotes, "container-border-light"); + setCssProperty(ui->labelAmountOfVotesText, "text-body-dialog"); + setCssProperty(ui->labelAmountOfVotes, "text-body-dialog"); setCssProperty(ui->btnSelectAll, "btn-dialog-secondary"); ui->treeWidget->setColumnWidth(COLUMN_CHECKBOX, colCheckBoxWidth_treeMode); diff --git a/src/qt/pivx/res/css/style_dark.css b/src/qt/pivx/res/css/style_dark.css index 53903cbaf5a0..73dd07e88c4f 100644 --- a/src/qt/pivx/res/css/style_dark.css +++ b/src/qt/pivx/res/css/style_dark.css @@ -2255,24 +2255,23 @@ HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH*/ color:#B3FFFFFF; font-size:12px; border-radius:12px; - background-color:rgba(176, 136, 255, 0.2); + background-color:rgba(176, 136, 255, 0.4); padding:6px 12px; } *[cssClass="card-status-not-passing"] { - color:#B3FFFFFF; + color:#FFFFFF; font-size:12px; border-radius:12px; - background-color:rgba(186, 186, 186, 0.2); + background-color:rgba(112, 112, 112, 0.3); padding:6px 12px; } *[cssClass="card-status-no-votes"] { - border: 1px solid #BABABA; - color:#B3FFFFFF; + color:rgba(255, 255, 255, 0.5); font-size:12px; border-radius:12px; - background-color:transparent; + background-color:rgba(186, 186, 186, 0.1); padding:6px 12px; } @@ -2478,17 +2477,17 @@ HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH*/ padding-right: 8px; } -*[cssClass="btn-dialog-secundary"] { +*[cssClass="btn-dialog-secondary"] { border: 1px solid #5c4b7d; background-color:#0f0b16; border-radius: 2px; color: #b088ff; font-size: 14px; - padding-left: 8px; - padding-right: 8px; + padding-left: 6px; + padding-right: 6px; } -*[cssClass="btn-dialog-secundary"]:hover { +*[cssClass="btn-dialog-secondary"]:hover { border: 1px solid #b088ff; background-color:#0f0b16; } @@ -2504,7 +2503,7 @@ QHeaderView::section { QTreeView { show-decoration-selected: 1; - background: white; + background: #0f0b16; border: 1px solid #5C4B7D; font-size: 14px; color: #4d4d4d; @@ -2517,7 +2516,7 @@ QTreeView::item { padding-bottom: 8px; padding-left: 0px; font-size: 14px; - color: #4d4d4d; + color: #D9ffffff; } QTreeView::item:hover { @@ -2526,17 +2525,17 @@ QTreeView::item:hover { } QTreeView::item:selected { - color: #5C4B7D; + color: #b088ff; border:0px; } QTreeView::item:selected:active{ - color: #5C4B7D; + color: #b088ff; border:0px; } QTreeView::item:selected:!active { - color:#5C4B7D; + color:#b088ff; border:0px; } @@ -2553,6 +2552,14 @@ QTreeView::indicator:checked { image: url(://ic-check-active); } +QTreeView:disabled::indicator:unchecked { + image: url(://ic-check-block); +} + +QTreeView:disabled::indicator:checked { + image: url(://ic-check-block); +} + /*HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH HH SPIN BOX HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH*/ @@ -2958,6 +2965,10 @@ HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH*/ font-size:22px; } +*[cssClass="text-body-dialog"] { + color:#B3FFFFFF; + font-size:16px; +} *[cssClass="text-body1-dialog"] { color:#B3FFFFFF; diff --git a/src/qt/pivx/res/css/style_light.css b/src/qt/pivx/res/css/style_light.css index d6f3a0709327..a390e7a53659 100644 --- a/src/qt/pivx/res/css/style_light.css +++ b/src/qt/pivx/res/css/style_light.css @@ -2479,8 +2479,8 @@ HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH*/ border-radius: 2px; color: #4d4d4d; font-size: 14px; - padding-left: 8px; - padding-right: 8px; + padding-left: 6px; + padding-right: 6px; } @@ -2951,6 +2951,10 @@ HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH*/ font-size:22px; } +*[cssClass="text-body-dialog"] { + color:#5C4B7D; + font-size:16px; +} *[cssClass="text-body1-dialog"] { color:#707070; From e18972837d1c9d758dd8f18f8f5bc2e009e53284 Mon Sep 17 00:00:00 2001 From: furszy Date: Tue, 8 Jun 2021 13:06:42 -0300 Subject: [PATCH 34/59] GUI: Governance model, add waiting proposal to the hasProposals() return. --- src/qt/pivx/governancemodel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qt/pivx/governancemodel.cpp b/src/qt/pivx/governancemodel.cpp index 315438080d66..6d4f7e9e55d9 100644 --- a/src/qt/pivx/governancemodel.cpp +++ b/src/qt/pivx/governancemodel.cpp @@ -91,7 +91,7 @@ std::list GovernanceModel::getProposals(const ProposalInfo::Status bool GovernanceModel::hasProposals() { - return g_budgetman.HasAnyProposal(); + return g_budgetman.HasAnyProposal() || !waitingPropsForConfirmations.empty(); } CAmount GovernanceModel::getMaxAvailableBudgetAmount() const From b46621a13171aa257e4c7b35b6c4faac2df71d28 Mon Sep 17 00:00:00 2001 From: furszy Date: Tue, 8 Jun 2021 13:14:29 -0300 Subject: [PATCH 35/59] GUI: transactionrecord parse new wtx proposal fee comment format. --- src/qt/transactionrecord.cpp | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/qt/transactionrecord.cpp b/src/qt/transactionrecord.cpp index 60ea2b62f362..77ba7c9456f3 100644 --- a/src/qt/transactionrecord.cpp +++ b/src/qt/transactionrecord.cpp @@ -7,6 +7,7 @@ #include "transactionrecord.h" #include "key_io.h" +#include "budget/budgetproposal.h" #include "sapling/key_io_sapling.h" #include "wallet/wallet.h" @@ -330,11 +331,22 @@ bool TransactionRecord::decomposeDebitTransaction(const CWallet* wallet, const C sub.address = getValueOrReturnEmpty(wtx.mapValue, "to"); if (sub.address.empty() && txout.scriptPubKey.StartsWithOpcode(OP_RETURN)) { sub.type = TransactionRecord::SendToNobody; - // Burned PIVs, op_return could be for a proposal/budget fee or another sort of data stored there. + // Burned PIVs, op_return could be for a kind of data stored there. For now, support UTF8 comments. std::string comment = wtx.GetComment(); - if (IsValidUTF8(comment)) { + if (!comment.empty() && IsValidUTF8(comment)) { sub.address = comment; } + // Check if this is a budget proposal fee (future: encapsulate functionality inside wallet/governanceModel) + std::string prop = getValueOrReturnEmpty(wtx.mapValue, "proposal"); + if (!prop.empty()) { + const std::vector vec = ParseHex(prop); + if (!vec.empty()) { + CDataStream ss(vec, SER_DISK, CLIENT_VERSION); + CBudgetProposal proposal; + ss >> proposal; + sub.address = "Proposal: " + proposal.GetName(); + } + } // future: could expand this to support base64 or hex encoded messages } } From debbed9b15d0ddbd6e381f0a419397d23f119392 Mon Sep 17 00:00:00 2001 From: furszy Date: Tue, 8 Jun 2021 13:20:11 -0300 Subject: [PATCH 36/59] GUI: governancemodel fix zombie waiting proposals check. --- src/qt/pivx/governancemodel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qt/pivx/governancemodel.cpp b/src/qt/pivx/governancemodel.cpp index 6d4f7e9e55d9..624f0d48fb9a 100644 --- a/src/qt/pivx/governancemodel.cpp +++ b/src/qt/pivx/governancemodel.cpp @@ -262,7 +262,7 @@ void GovernanceModel::pollGovernanceChanged() continue; } // Check if the proposal didn't exceed the superblock start height - if (it->GetBlockStart() >= chainHeight) { + if (chainHeight > it->GetBlockStart()) { // Edge case, the proposal was never broadcasted before the next superblock, can be removed. // future: notify the user about it. it = waitingPropsForConfirmations.erase(it); From 9b45f72dbb3de096b64efc20c46edf2da1f6dff3 Mon Sep 17 00:00:00 2001 From: Gregory Solarte Date: Mon, 14 Jun 2021 14:44:52 -0300 Subject: [PATCH 37/59] GUI: Add governance empty screen image for dark theme. --- src/Makefile.qt.include | 3 ++- src/qt/pivx.qrc | 1 + src/qt/pivx/res/css/style_dark.css | 10 +++++--- src/qt/pivx/res/css/style_light.css | 14 +++++------ .../res/img/img-empty-dark-governance.svg | 15 +++++++++++ src/qt/pivx/res/img/img-empty-governance.svg | 25 ++++++++++--------- 6 files changed, 45 insertions(+), 23 deletions(-) create mode 100644 src/qt/pivx/res/img/img-empty-dark-governance.svg diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include index 790e3769f3be..3b6332fdd8af 100644 --- a/src/Makefile.qt.include +++ b/src/Makefile.qt.include @@ -514,7 +514,8 @@ RES_ICONS = \ qt/pivx/res/img/ic-nav-governance-hover.svg \ qt/pivx/res/img/ic-time.svg \ qt/pivx/res/img/ic-link-hover.svg \ - qt/pivx/res/img/img-empty-governance.svg + qt/pivx/res/img/img-empty-governance.svg \ + qt/pivx/res/img/img-empty-dark-governance.svg BITCOIN_QT_BASE_CPP = \ qt/bantablemodel.cpp \ diff --git a/src/qt/pivx.qrc b/src/qt/pivx.qrc index 43422dd61bf6..0bd0a70a2c4d 100644 --- a/src/qt/pivx.qrc +++ b/src/qt/pivx.qrc @@ -235,6 +235,7 @@ pivx/res/img/ic-nav-governance-hover.svg pivx/res/img/ic-time.svg pivx/res/img/img-empty-governance.svg + pivx/res/img/img-empty-dark-governance.svg pivx/res/img/ic-check-block.svg diff --git a/src/qt/pivx/res/css/style_dark.css b/src/qt/pivx/res/css/style_dark.css index 73dd07e88c4f..9e2f75ee886e 100644 --- a/src/qt/pivx/res/css/style_dark.css +++ b/src/qt/pivx/res/css/style_dark.css @@ -418,7 +418,7 @@ QPushButton[cssClass="btn-check-cold-staking"]:checked { qproperty-icon: url("://ic-nav-governance-hover"); qproperty-iconSize: 32px 32px; background-color:#0f0b16; - color: #FFFFFF; + color: #938da5; } *[cssClass="btn-nav-governance-active"] { @@ -3594,20 +3594,24 @@ HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH*/ background-color: transparent; } - *[cssClass="img-empty-staking-off"] { qproperty-icon: url("://img-empty-dark-staking-off"); qproperty-iconSize: 100px 100px; background-color: transparent; } - *[cssClass="img-empty-privacy"] { qproperty-icon: url("://img-empty-dark-privacy"); qproperty-iconSize: 100px 100px; background-color: transparent; } +*[cssClass="img-empty-governance"] { + qproperty-icon: url("://img-empty-dark-governance"); + qproperty-iconSize: 100px 100px; + background-color: transparent; +} + /*HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH HH TREE WIDGET HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH*/ diff --git a/src/qt/pivx/res/css/style_light.css b/src/qt/pivx/res/css/style_light.css index a390e7a53659..06cc67e59080 100644 --- a/src/qt/pivx/res/css/style_light.css +++ b/src/qt/pivx/res/css/style_light.css @@ -378,7 +378,7 @@ QPushButton[cssClass="img-nav-logo"] { qproperty-icon: url("://ic-nav-governance-hover"); qproperty-iconSize: 32px 32px; background-color: transparent; - color: #FFFFFF; + color: #938da5; } *[cssClass="btn-nav-governance-active"] { @@ -3596,6 +3596,12 @@ HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH*/ background-color: transparent; } +*[cssClass="img-empty-governance"] { + qproperty-icon: url("://img-empty-governance"); + qproperty-iconSize: 100px 100px; + background-color: transparent; +} + /*HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH HH TREE WIDGET HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH*/ @@ -3739,12 +3745,6 @@ QHeaderView::section { color: #707070; } -*[cssClass="img-empty-governance"] { - qproperty-icon: url(://img-empty-governance); - qproperty-iconSize: 100px 100px; - background-color: transparent; -} - /*HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH HH SEND MULTI HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH*/ diff --git a/src/qt/pivx/res/img/img-empty-dark-governance.svg b/src/qt/pivx/res/img/img-empty-dark-governance.svg new file mode 100644 index 000000000000..26a8a1b039f5 --- /dev/null +++ b/src/qt/pivx/res/img/img-empty-dark-governance.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/src/qt/pivx/res/img/img-empty-governance.svg b/src/qt/pivx/res/img/img-empty-governance.svg index 3d96e410f035..1d511fefce61 100644 --- a/src/qt/pivx/res/img/img-empty-governance.svg +++ b/src/qt/pivx/res/img/img-empty-governance.svg @@ -1,14 +1,15 @@ - - - - - - - - - - - - + + + + + + + + + + + + + From dfaf0847b6b3ebf0e71d81d7780fc871ff16b919 Mon Sep 17 00:00:00 2001 From: furszy Date: Wed, 7 Jul 2021 12:55:27 -0300 Subject: [PATCH 38/59] GUI: add addresses selector to the proposal creation wizard. --- src/qt/pivx/contactsdropdown.cpp | 14 ++++-- src/qt/pivx/contactsdropdown.h | 5 ++- src/qt/pivx/createproposaldialog.cpp | 54 ++++++++++++++++++++++- src/qt/pivx/createproposaldialog.h | 7 ++- src/qt/pivx/forms/createproposaldialog.ui | 42 +++++++++--------- 5 files changed, 95 insertions(+), 27 deletions(-) diff --git a/src/qt/pivx/contactsdropdown.cpp b/src/qt/pivx/contactsdropdown.cpp index 3abf55d15b76..639543db82ab 100644 --- a/src/qt/pivx/contactsdropdown.cpp +++ b/src/qt/pivx/contactsdropdown.cpp @@ -14,7 +14,7 @@ #include "addresstablemodel.h" #define DECORATION_SIZE 70 -#define NUM_ITEMS 3 +#define NUM_ITEMS 2 class ContViewHolder : public FurListRow { @@ -48,12 +48,20 @@ class ContViewHolder : public FurListRow ContactDropdownRow* row = nullptr; }; -ContactsDropdown::ContactsDropdown(int minWidth, int minHeight, PWidget *parent) : - PWidget(parent) +ContactsDropdown::ContactsDropdown(int minWidth, int minHeight, PIVXGUI* _window, QWidget* _parent) : PWidget(_window, _parent) { + this->setStyleSheet(_window->styleSheet()); + init(minWidth, minHeight); +} +ContactsDropdown::ContactsDropdown(int minWidth, int minHeight, PWidget* parent) : PWidget(parent) +{ this->setStyleSheet(parent->styleSheet()); + init(minWidth, minHeight); +} +void ContactsDropdown::init(int minWidth, int minHeight) +{ delegate = new FurAbstractListItemDelegate( DECORATION_SIZE, new ContViewHolder(isLightTheme()), diff --git a/src/qt/pivx/contactsdropdown.h b/src/qt/pivx/contactsdropdown.h index 0985e249e5ec..e435e870ad98 100644 --- a/src/qt/pivx/contactsdropdown.h +++ b/src/qt/pivx/contactsdropdown.h @@ -28,7 +28,9 @@ class ContactsDropdown : public PWidget { Q_OBJECT public: - explicit ContactsDropdown(int minWidth, int minHeight, PWidget *parent = nullptr); + explicit ContactsDropdown(int minWidth, int minHeight, PWidget* parent = nullptr); + ContactsDropdown(int minWidth, int minHeight, PIVXGUI* _window = nullptr, + QWidget* parent = nullptr); void resizeList(int minWidth, int mintHeight); void setWalletModel(WalletModel* _model, const QStringList& type); @@ -42,6 +44,7 @@ class ContactsDropdown : public PWidget AddressFilterProxyModel *filter = nullptr; QListView *list; QFrame *frameList; + void init(int minWidth, int minHeight); private Q_SLOTS: void handleClick(const QModelIndex &index); }; diff --git a/src/qt/pivx/createproposaldialog.cpp b/src/qt/pivx/createproposaldialog.cpp index 8ecd6b346d72..06ed6a7a6805 100644 --- a/src/qt/pivx/createproposaldialog.cpp +++ b/src/qt/pivx/createproposaldialog.cpp @@ -5,7 +5,9 @@ #include "qt/pivx/createproposaldialog.h" #include "qt/pivx/forms/ui_createproposaldialog.h" +#include "qt/pivx/contactsdropdown.h" #include "qt/pivx/governancemodel.h" +#include "qt/pivx/pwidget.h" #include "qt/pivx/qtutils.h" #include "qt/pivx/snackbar.h" @@ -21,7 +23,7 @@ void initPageIndexBtn(QPushButton* btn) btn->setVisible(false); } -CreateProposalDialog::CreateProposalDialog(QWidget *parent, GovernanceModel* _govModel, WalletModel* _walletModel) : +CreateProposalDialog::CreateProposalDialog(PIVXGUI* parent, GovernanceModel* _govModel, WalletModel* _walletModel) : QDialog(parent), ui(new Ui::CreateProposalDialog), govModel(_govModel), @@ -92,7 +94,10 @@ void CreateProposalDialog::setupPageTwo() setCssProperty(ui->labelMessageDest, "dialog-proposal-message"); setEditBoxStyle(ui->labelAmount, ui->lineEditAmount, "e.g 500 PIV"); setEditBoxStyle(ui->labelMonths, ui->lineEditMonths, "e.g 2"); + setEditBoxStyle(ui->labelAddress, ui->lineEditAddress, "e.g D...something.."); + setCssProperty(ui->lineEditAddress, "edit-primary-multi-book"); + actAddrList = ui->lineEditAddress->addAction(QIcon("://ic-contact-arrow-down"), QLineEdit::TrailingPosition); ui->lineEditAmount->setValidator(new QIntValidator(1,43200, this)); ui->lineEditMonths->setValidator(new QIntValidator(1, govModel->getPropMaxPaymentsCount(), this)); @@ -100,6 +105,7 @@ void CreateProposalDialog::setupPageTwo() connect(ui->lineEditAmount, &QLineEdit::textChanged, this, &CreateProposalDialog::propAmountChanged); connect(ui->lineEditMonths, &QLineEdit::textChanged, this, &CreateProposalDialog::propMonthsChanged); connect(ui->lineEditAddress, &QLineEdit::textChanged, this, &CreateProposalDialog::propaddressChanged); + connect(actAddrList, &QAction::triggered, this, &CreateProposalDialog::onAddrListClicked); } void CreateProposalDialog::setupPageThree() @@ -286,6 +292,52 @@ void CreateProposalDialog::onBackClicked() } } +void CreateProposalDialog::onAddrListClicked() +{ + int addrSize = walletModel->getAddressTableModel()->sizeSend() + + walletModel->getAddressTableModel()->sizeRecv(); + if (addrSize == 0) { + inform(tr("No contacts available, you can go to the contacts screen and add some there!")); + return; + } + + int rowHeight = ui->lineEditAddress->height(); + int height = 70 * 2 + 1; // 2 rows (70 each row). + int width = ui->lineEditAddress->width(); + + if (!menuContacts) { + // TODO: add different row icon for contacts and own addresses. + // TODO: add filter/search option. + // TODO: fix bug that the last presented address isn't being showed. + menuContacts = new ContactsDropdown( + width, + height, + static_cast(parent()), + this + ); + menuContacts->setWalletModel(walletModel, {AddressTableModel::Send, AddressTableModel::Receive}); + connect(menuContacts, &ContactsDropdown::contactSelected, [this](QString address, QString label) { + ui->lineEditAddress->setText(address); + }); + + } + + if (menuContacts->isVisible()) { + menuContacts->hide(); + return; + } + + menuContacts->resizeList(width, height); + menuContacts->setStyleSheet(this->styleSheet()); + menuContacts->adjustSize(); + + QPoint pos = ui->containerPage2->rect().bottomLeft(); + pos.setY(pos.y() + rowHeight * 2 - 20); + pos.setX(pos.x() + 74); // Add widget's fixed padding manually + menuContacts->move(pos); + menuContacts->show(); +} + void CreateProposalDialog::inform(const QString& text) { if (!snackBar) snackBar = new SnackBar(nullptr, this); diff --git a/src/qt/pivx/createproposaldialog.h b/src/qt/pivx/createproposaldialog.h index 3daa6222e866..b22b49f69ba7 100644 --- a/src/qt/pivx/createproposaldialog.h +++ b/src/qt/pivx/createproposaldialog.h @@ -12,7 +12,9 @@ class CreateProposalDialog; class QPushButton; } +class ContactsDropdown; class GovernanceModel; +class PIVXGUI; class SnackBar; class WalletModel; @@ -21,7 +23,7 @@ class CreateProposalDialog : public QDialog Q_OBJECT public: - explicit CreateProposalDialog(QWidget *parent, GovernanceModel* _govModel, WalletModel* _walletModel); + explicit CreateProposalDialog(PIVXGUI* parent, GovernanceModel* _govModel, WalletModel* _walletModel); ~CreateProposalDialog() override; public Q_SLOTS: @@ -32,6 +34,7 @@ public Q_SLOTS: void propAmountChanged(const QString& newText); void propMonthsChanged(const QString& newText); bool propaddressChanged(const QString& newText); + void onAddrListClicked(); private: Ui::CreateProposalDialog *ui; @@ -41,6 +44,8 @@ public Q_SLOTS: QPushButton* icConfirm1{nullptr}; QPushButton* icConfirm2{nullptr}; QPushButton* icConfirm3{nullptr}; + ContactsDropdown* menuContacts{nullptr}; + QAction* actAddrList{nullptr}; int pos = 0; void loadSummary(); diff --git a/src/qt/pivx/forms/createproposaldialog.ui b/src/qt/pivx/forms/createproposaldialog.ui index f8f48fb8c768..9fa43458d79f 100644 --- a/src/qt/pivx/forms/createproposaldialog.ui +++ b/src/qt/pivx/forms/createproposaldialog.ui @@ -831,7 +831,7 @@ 12 - + 0 @@ -890,12 +890,12 @@ Add the amount per month, the months that will remain active and the recipient address - - true - Qt::AlignCenter + + true + @@ -913,6 +913,23 @@ 0 + + + + 10 + + + + + Address + + + + + + + + @@ -951,23 +968,6 @@ - - - - 10 - - - - - Address - - - - - - - - From 33b1b87cb45190e37f582073a2a7899af28d3f20 Mon Sep 17 00:00:00 2001 From: furszy Date: Sat, 24 Jul 2021 19:44:15 -0300 Subject: [PATCH 39/59] GUI: Create proposal dialog, implement months input field spinbox. Plus fixed several warnings for QT duplicate widget container names. --- src/qt/pivx.qrc | 2 +- src/qt/pivx/createproposaldialog.cpp | 41 ++++++------- src/qt/pivx/createproposaldialog.h | 2 +- src/qt/pivx/forms/createproposaldialog.ui | 70 +++++++++++++++++------ 4 files changed, 75 insertions(+), 40 deletions(-) diff --git a/src/qt/pivx.qrc b/src/qt/pivx.qrc index 0bd0a70a2c4d..2fce11e60dd5 100644 --- a/src/qt/pivx.qrc +++ b/src/qt/pivx.qrc @@ -235,7 +235,7 @@ pivx/res/img/ic-nav-governance-hover.svg pivx/res/img/ic-time.svg pivx/res/img/img-empty-governance.svg - pivx/res/img/img-empty-dark-governance.svg + pivx/res/img/img-empty-dark-governance.svg pivx/res/img/ic-check-block.svg diff --git a/src/qt/pivx/createproposaldialog.cpp b/src/qt/pivx/createproposaldialog.cpp index 06ed6a7a6805..85d57caec4a2 100644 --- a/src/qt/pivx/createproposaldialog.cpp +++ b/src/qt/pivx/createproposaldialog.cpp @@ -93,21 +93,30 @@ void CreateProposalDialog::setupPageTwo() setCssProperty(ui->labelTitleDest, "text-title-dialog"); setCssProperty(ui->labelMessageDest, "dialog-proposal-message"); setEditBoxStyle(ui->labelAmount, ui->lineEditAmount, "e.g 500 PIV"); - setEditBoxStyle(ui->labelMonths, ui->lineEditMonths, "e.g 2"); setEditBoxStyle(ui->labelAddress, ui->lineEditAddress, "e.g D...something.."); setCssProperty(ui->lineEditAddress, "edit-primary-multi-book"); actAddrList = ui->lineEditAddress->addAction(QIcon("://ic-contact-arrow-down"), QLineEdit::TrailingPosition); - - ui->lineEditAmount->setValidator(new QIntValidator(1,43200, this)); - ui->lineEditMonths->setValidator(new QIntValidator(1, govModel->getPropMaxPaymentsCount(), this)); + ui->lineEditAmount->setValidator(new QIntValidator(1, govModel->getMaxAvailableBudgetAmount() / 100000000, this)); + setCssProperty(ui->lineEditMonths, "btn-spin-box"); + setShadow(ui->lineEditMonths); + ui->lineEditMonths->setAttribute(Qt::WA_MacShowFocusRect, false); + connect(ui->lineEditMonths, static_cast(&QSpinBox::valueChanged), this, + &CreateProposalDialog::monthsEditDeselect, Qt::QueuedConnection); + connect(ui->lineEditMonths->findChild(), &QLineEdit::cursorPositionChanged, + this, &CreateProposalDialog::monthsEditDeselect, Qt::QueuedConnection); connect(ui->lineEditAmount, &QLineEdit::textChanged, this, &CreateProposalDialog::propAmountChanged); - connect(ui->lineEditMonths, &QLineEdit::textChanged, this, &CreateProposalDialog::propMonthsChanged); connect(ui->lineEditAddress, &QLineEdit::textChanged, this, &CreateProposalDialog::propaddressChanged); connect(actAddrList, &QAction::triggered, this, &CreateProposalDialog::onAddrListClicked); } +void CreateProposalDialog::monthsEditDeselect(int i) +{ + ui->lineEditMonths->findChild()->deselect(); + ui->lineEditMonths->clearFocus(); +} + void CreateProposalDialog::setupPageThree() { setCssProperty(ui->labelTitle3, "text-title-dialog"); @@ -138,12 +147,8 @@ void CreateProposalDialog::propUrlChanged(const QString& newText) void CreateProposalDialog::propAmountChanged(const QString& newText) { - setCssEditLine(ui->lineEditAmount, govModel->validatePropAmount(newText.toInt()).getRes(), true); -} - -void CreateProposalDialog::propMonthsChanged(const QString& newText) -{ - setCssEditLine(ui->lineEditMonths, govModel->validatePropPaymentCount(newText.toInt()).getRes(), true); + CAmount amt = newText.toDouble() * COIN; + setCssEditLine(ui->lineEditAmount, govModel->validatePropAmount(amt).getRes(), true); } bool CreateProposalDialog::propaddressChanged(const QString& str) @@ -162,6 +167,7 @@ bool CreateProposalDialog::propaddressChanged(const QString& str) bool CreateProposalDialog::validatePageOne() { if (ui->lineEditPropName->text().isEmpty()) { + setCssEditLine(ui->lineEditPropName, false, true); inform(tr("Proposal name field cannot be empty")); return false; } @@ -172,12 +178,6 @@ bool CreateProposalDialog::validatePageOne() bool CreateProposalDialog::validatePageTwo() { - QString sPaymentCount = ui->lineEditMonths->text(); - if (sPaymentCount.isEmpty()) { - inform(tr("Proposal amount field cannot be empty")); - return false; - } - // Amount validation auto opRes = govModel->validatePropAmount(ui->lineEditAmount->text().toInt()); if (!opRes) { @@ -186,7 +186,7 @@ bool CreateProposalDialog::validatePageTwo() } // Payments count validation - opRes = govModel->validatePropPaymentCount(sPaymentCount.toInt()); + opRes = govModel->validatePropPaymentCount(ui->lineEditMonths->value()); if (!opRes) { inform(QString::fromStdString(opRes.getError())); return false; @@ -205,18 +205,19 @@ void CreateProposalDialog::loadSummary() ui->labelResultName->setText(ui->lineEditPropName->text()); ui->labelResultUrl->setText(ui->lineEditURL->text()); ui->labelResultAmount->setText(GUIUtil::formatBalance(ui->lineEditAmount->text().toInt() * COIN)); - ui->labelResultMonths->setText(ui->lineEditMonths->text()); + ui->labelResultMonths->setText(QString::number(ui->lineEditMonths->value())); ui->labelResultAddress->setText(ui->lineEditAddress->text()); ui->labelResultUrl->setText(ui->lineEditURL->text()); } void CreateProposalDialog::sendProposal() { + int months = ui->lineEditMonths->value(); CAmount amount = ui->lineEditAmount->text().toInt() * COIN; auto opRes = govModel->createProposal( ui->lineEditPropName->text().toStdString(), ui->lineEditURL->text().toStdString(), - ui->lineEditMonths->text().toInt(), + months, amount, ui->lineEditAddress->text().toStdString() ); diff --git a/src/qt/pivx/createproposaldialog.h b/src/qt/pivx/createproposaldialog.h index b22b49f69ba7..085d9bde2c04 100644 --- a/src/qt/pivx/createproposaldialog.h +++ b/src/qt/pivx/createproposaldialog.h @@ -32,9 +32,9 @@ public Q_SLOTS: void propNameChanged(const QString& newText); void propUrlChanged(const QString& newText); void propAmountChanged(const QString& newText); - void propMonthsChanged(const QString& newText); bool propaddressChanged(const QString& newText); void onAddrListClicked(); + void monthsEditDeselect(int i); private: Ui::CreateProposalDialog *ui; diff --git a/src/qt/pivx/forms/createproposaldialog.ui b/src/qt/pivx/forms/createproposaldialog.ui index 9fa43458d79f..6dabd32a56d8 100644 --- a/src/qt/pivx/forms/createproposaldialog.ui +++ b/src/qt/pivx/forms/createproposaldialog.ui @@ -56,7 +56,7 @@ 20 - + Qt::Horizontal @@ -757,12 +757,12 @@ 150 - + 0 - + 10 @@ -774,12 +774,16 @@ - + + + 20 + + - + 10 @@ -791,7 +795,11 @@ - + + + 64 + + @@ -832,7 +840,7 @@ - + 0 @@ -849,8 +857,8 @@ 0 - - + + 0 @@ -909,7 +917,7 @@ 150 - + 0 @@ -933,7 +941,7 @@ - + 10 @@ -945,12 +953,16 @@ - + + + 12 + + - + 10 @@ -962,7 +974,29 @@ - + + + + 100 + 0 + + + + Qt::NoFocus + + + false + + + false + + + 1 + + + 6 + + @@ -1089,7 +1123,7 @@ - + 10 @@ -1110,7 +1144,7 @@ - + 10 @@ -1197,7 +1231,7 @@ - + 10 @@ -1244,7 +1278,7 @@ - + From 37f711806ce5f70f3bb6fd07f60a9b5b440b1595 Mon Sep 17 00:00:00 2001 From: furszy Date: Mon, 2 Aug 2021 17:01:29 -0300 Subject: [PATCH 40/59] GUI: Add cs_proposals mutex lock in governancemodel FindProposal function call. * Connect governanceModel stop(). * And not update next superblock height visually until the wallet passed IBD. --- src/qt/pivx.cpp | 1 + src/qt/pivx/forms/createproposaldialog.ui | 4 ++-- src/qt/pivx/forms/governancewidget.ui | 3 ++- src/qt/pivx/governancemodel.cpp | 27 +++++++++++++---------- src/qt/pivx/governancemodel.h | 7 ++++-- src/qt/pivx/governancewidget.cpp | 2 +- src/qt/walletmodel.cpp | 2 +- 7 files changed, 27 insertions(+), 19 deletions(-) diff --git a/src/qt/pivx.cpp b/src/qt/pivx.cpp index f3f422e207b2..9a0d02555a06 100644 --- a/src/qt/pivx.cpp +++ b/src/qt/pivx.cpp @@ -455,6 +455,7 @@ void BitcoinApplication::requestShutdown() qDebug() << __func__ << ": Requesting shutdown"; startThread(); window->hide(); + if (govModel) govModel->stop(); if (walletModel) walletModel->stop(); window->setClientModel(nullptr); pollShutdownTimer->stop(); diff --git a/src/qt/pivx/forms/createproposaldialog.ui b/src/qt/pivx/forms/createproposaldialog.ui index 6dabd32a56d8..c11c379cfedf 100644 --- a/src/qt/pivx/forms/createproposaldialog.ui +++ b/src/qt/pivx/forms/createproposaldialog.ui @@ -883,7 +883,7 @@ - Proposal Recipient + Proposal Payee @@ -896,7 +896,7 @@ - Add the amount per month, the months that will remain active and the recipient address + Add the requested number of payments, the amount per payment, and the payee address. Qt::AlignCenter diff --git a/src/qt/pivx/forms/governancewidget.ui b/src/qt/pivx/forms/governancewidget.ui index 6183a5817fc1..f464758b43a0 100644 --- a/src/qt/pivx/forms/governancewidget.ui +++ b/src/qt/pivx/forms/governancewidget.ui @@ -631,7 +631,8 @@ - Next superblock in 7,544 blocks + Next superblock unknown. + Wait until the node is fully sync. true diff --git a/src/qt/pivx/governancemodel.cpp b/src/qt/pivx/governancemodel.cpp index 624f0d48fb9a..c984b813ac1f 100644 --- a/src/qt/pivx/governancemodel.cpp +++ b/src/qt/pivx/governancemodel.cpp @@ -29,7 +29,7 @@ void GovernanceModel::setWalletModel(WalletModel* _walletModel) connect(walletModel->getTransactionTableModel(), &TransactionTableModel::txLoaded, this, &GovernanceModel::txLoaded); } -ProposalInfo GovernanceModel::buidProposalInfo(const CBudgetProposal* prop, bool isPassing, bool isPending) +ProposalInfo GovernanceModel::buildProposalInfo(const CBudgetProposal* prop, bool isPassing, bool isPending) { CTxDestination recipient; ExtractDestination(prop->GetPayee(), recipient); @@ -72,7 +72,7 @@ std::list GovernanceModel::getProposals(const ProposalInfo::Status std::vector budget = g_budgetman.GetBudget(); for (const auto& prop : g_budgetman.GetAllProposalsOrdered()) { bool isPassing = std::find(budget.begin(), budget.end(), *prop) != budget.end(); - ProposalInfo propInfo = buidProposalInfo(prop, isPassing, false); + ProposalInfo propInfo = buildProposalInfo(prop, isPassing, false); if (!filterByStatus || propInfo.status == *filterByStatus) { ret.emplace_back(propInfo); } @@ -81,7 +81,7 @@ std::list GovernanceModel::getProposals(const ProposalInfo::Status // Add pending proposals for (const auto& prop : waitingPropsForConfirmations) { - ProposalInfo propInfo = buidProposalInfo(&prop, false, true); + ProposalInfo propInfo = buildProposalInfo(&prop, false, true); if (!filterByStatus || propInfo.status == *filterByStatus) { ret.emplace_back(propInfo); } @@ -129,14 +129,17 @@ std::vector GovernanceModel::getLocalMNsVotesForProposal(const Proposa } std::vector localVotes; - // Get the budget proposal, get the votes, then loop over it and return the ones that correspond to the local masternodes here. - CBudgetProposal* prop = g_budgetman.FindProposal(propInfo.id); - const auto& mapVotes = prop->GetVotes(); - for (const auto& it : mapVotes) { - for (const auto& mn : vecLocalMn) { - if (it.first == mn.first && it.second.IsValid()) { - localVotes.emplace_back(mn.first, (VoteInfo::VoteDirection) it.second.GetDirection(), mn.second); - break; + { + LOCK(g_budgetman.cs_proposals); // future: encapsulate this mutex lock. + // Get the budget proposal, get the votes, then loop over it and return the ones that correspond to the local masternodes here. + CBudgetProposal* prop = g_budgetman.FindProposal(propInfo.id); + const auto& mapVotes = prop->GetVotes(); + for (const auto& it : mapVotes) { + for (const auto& mn : vecLocalMn) { + if (it.first == mn.first && it.second.IsValid()) { + localVotes.emplace_back(mn.first, (VoteInfo::VoteDirection) it.second.GetDirection(), mn.second); + break; + } } } } @@ -281,7 +284,7 @@ void GovernanceModel::pollGovernanceChanged() } } -void GovernanceModel::stopPolling() +void GovernanceModel::stop() { if (pollTimer && pollTimer->isActive()) { pollTimer->stop(); diff --git a/src/qt/pivx/governancemodel.h b/src/qt/pivx/governancemodel.h index 797f67515222..d93202974c09 100644 --- a/src/qt/pivx/governancemodel.h +++ b/src/qt/pivx/governancemodel.h @@ -125,6 +125,10 @@ class GovernanceModel : public QObject OperationResult voteForProposal(const ProposalInfo& prop, bool isVotePositive, const std::vector& mnVotingAlias); + + // Stop internal timers + void stop(); + public Q_SLOTS: void pollGovernanceChanged(); void txLoaded(const QString& hash, const int txType, const int txStatus); @@ -144,10 +148,9 @@ public Q_SLOTS: std::vector waitingPropsForConfirmations; void scheduleBroadcast(const CBudgetProposal& proposal); - void stopPolling(); // Util function to create a ProposalInfo object - ProposalInfo buidProposalInfo(const CBudgetProposal* prop, bool isPassing, bool isPending); + ProposalInfo buildProposalInfo(const CBudgetProposal* prop, bool isPassing, bool isPending); }; #endif // GOVERNANCEMODEL_H diff --git a/src/qt/pivx/governancewidget.cpp b/src/qt/pivx/governancewidget.cpp index 3671a3d74b5f..8daef83f1ec7 100644 --- a/src/qt/pivx/governancewidget.cpp +++ b/src/qt/pivx/governancewidget.cpp @@ -165,7 +165,7 @@ void GovernanceWidget::loadClientModel() void GovernanceWidget::chainHeightChanged(int height) { - if (!isVisible()) return; + if (!isVisible() && clientModel->inInitialBlockDownload()) return; int remainingBlocks = governanceModel->getNextSuperblockHeight() - height; int remainingDays = remainingBlocks / 1440; QString text = remainingDays == 0 ? tr("Next superblock today!\n%2 blocks to go.").arg(remainingBlocks) : diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index 7afd2e024ccc..c4dc0f8ee6f2 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -321,7 +321,7 @@ void WalletModel::pollFinished() void WalletModel::stop() { - if(pollFuture.isRunning()) { + if (pollFuture.isRunning()) { pollFuture.cancel(); pollFuture.setPaused(true); } From bf99df97be5ba4428eeb21e21d60cbc2f2a22ed7 Mon Sep 17 00:00:00 2001 From: furszy Date: Sat, 7 Aug 2021 10:05:39 -0300 Subject: [PATCH 41/59] GUI: Reset governance total allocated amount --- src/qt/pivx/governancemodel.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/qt/pivx/governancemodel.cpp b/src/qt/pivx/governancemodel.cpp index c984b813ac1f..c9a572f02b44 100644 --- a/src/qt/pivx/governancemodel.cpp +++ b/src/qt/pivx/governancemodel.cpp @@ -70,6 +70,7 @@ std::list GovernanceModel::getProposals(const ProposalInfo::Status if (!clientModel) return {}; std::list ret; std::vector budget = g_budgetman.GetBudget(); + allocatedAmount = 0; for (const auto& prop : g_budgetman.GetAllProposalsOrdered()) { bool isPassing = std::find(budget.begin(), budget.end(), *prop) != budget.end(); ProposalInfo propInfo = buildProposalInfo(prop, isPassing, false); From cb73091e2ed0aef22c4636af5368ebec494658fd Mon Sep 17 00:00:00 2001 From: furszy Date: Sat, 7 Aug 2021 11:33:48 -0300 Subject: [PATCH 42/59] GUI: Add sync warning for governance screen. --- src/qt/pivx/forms/governancewidget.ui | 304 ++++++++++++++++++-------- src/qt/pivx/governancewidget.cpp | 18 +- src/qt/pivx/governancewidget.h | 2 + src/qt/pivx/pivxgui.cpp | 1 + src/qt/pivx/topbar.cpp | 3 +- src/qt/pivx/topbar.h | 1 + 6 files changed, 237 insertions(+), 92 deletions(-) diff --git a/src/qt/pivx/forms/governancewidget.ui b/src/qt/pivx/forms/governancewidget.ui index f464758b43a0..a29cccfc909b 100644 --- a/src/qt/pivx/forms/governancewidget.ui +++ b/src/qt/pivx/forms/governancewidget.ui @@ -37,7 +37,7 @@ - + 0 @@ -51,104 +51,228 @@ 0 - - - 0 - - - 20 + + + + 16777215 + 60 + - - - - 5 - - - - - TextLabel - - - - - - - TextLabel - - - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 0 - 0 - - - - - 0 - - - 0 + + + 0 + + + 20 + + + 0 + + + 0 + + + 0 + + + + + 5 - - - - 150 - 0 - + + + TextLabel - - - - - - - - 0 - 0 - - - - - 0 - - - 0 - - - - - - 115 - 0 - + + + + TextLabel - - - + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 0 + + + + + 0 + + + 0 + + + + + + 150 + 0 + + + + + + + + + + + + 0 + 0 + + + + + 0 + + + 0 + + + + + + 115 + 0 + + + + + + + + + + + + + + + 16777215 + 40 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 6 + + + 18 + + + 6 + + + 20 + + + 0 + + + + + border:none; + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 3 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + border:none; + + + + + + + 24 + 24 + + + + + + + + + + + N/A + + + Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft + + + + + + + + + + + @@ -194,7 +318,7 @@ 0 0 417 - 175 + 157 diff --git a/src/qt/pivx/governancewidget.cpp b/src/qt/pivx/governancewidget.cpp index 8daef83f1ec7..a77cdbab4d0d 100644 --- a/src/qt/pivx/governancewidget.cpp +++ b/src/qt/pivx/governancewidget.cpp @@ -92,6 +92,12 @@ GovernanceWidget::GovernanceWidget(PIVXGUI* parent) : setCssProperty(ui->iconClock , "ic-time"); setCssProperty(ui->labelNextSuperblock, "label-budget-text"); + // Sync Warning + ui->layoutWarning->setVisible(true); + ui->lblWarning->setText(tr("Please wait until the node is fully synced to see the correct information")); + setCssProperty(ui->lblWarning, "text-warning"); + setCssProperty(ui->imgWarning, "ic-warning"); + // Create proposal ui->btnCreateProposal->setTitleClassAndText("btn-title-grey", "Create Proposal"); ui->btnCreateProposal->setSubTitleClassAndText("text-subtitle", "Prepare and submit a new proposal."); @@ -165,7 +171,7 @@ void GovernanceWidget::loadClientModel() void GovernanceWidget::chainHeightChanged(int height) { - if (!isVisible() && clientModel->inInitialBlockDownload()) return; + if (!isVisible() || clientModel->inInitialBlockDownload()) return; int remainingBlocks = governanceModel->getNextSuperblockHeight() - height; int remainingDays = remainingBlocks / 1440; QString text = remainingDays == 0 ? tr("Next superblock today!\n%2 blocks to go.").arg(remainingBlocks) : @@ -325,3 +331,13 @@ int GovernanceWidget::calculateColumnsPerRow() return 4; // max amount of cards } } + +void GovernanceWidget::tierTwoSynced(bool sync) +{ + if (isSync != sync) { + isSync = sync; + ui->layoutWarning->setVisible(!isSync); + if (!isVisible()) return; + tryGridRefresh(); + } +} \ No newline at end of file diff --git a/src/qt/pivx/governancewidget.h b/src/qt/pivx/governancewidget.h index 23b83194ba33..72fa8648acb2 100644 --- a/src/qt/pivx/governancewidget.h +++ b/src/qt/pivx/governancewidget.h @@ -68,6 +68,7 @@ class GovernanceWidget : public PWidget void setMNModel(MNModel* _mnModel); public Q_SLOTS: + void tierTwoSynced(bool IsSync); void onFilterChanged(const QString& value); void chainHeightChanged(int height); void onVoteForPropClicked(const ProposalInfo& proposalInfo); @@ -77,6 +78,7 @@ public Q_SLOTS: Ui::governancewidget *ui; GovernanceModel* governanceModel{nullptr}; MNModel* mnModel{nullptr}; + bool isSync{false}; QGridLayout* gridLayout{nullptr}; // cards std::vector cards; int propsPerRow = 0; diff --git a/src/qt/pivx/pivxgui.cpp b/src/qt/pivx/pivxgui.cpp index ca6de7dace51..1d56b335804e 100644 --- a/src/qt/pivx/pivxgui.cpp +++ b/src/qt/pivx/pivxgui.cpp @@ -266,6 +266,7 @@ void PIVXGUI::setClientModel(ClientModel* _clientModel) }); connect(topBar, &TopBar::walletSynced, dashboard, &DashboardWidget::walletSynced); connect(topBar, &TopBar::walletSynced, coldStakingWidget, &ColdStakingWidget::walletSynced); + connect(topBar, &TopBar::tierTwoSynced, governancewidget, &GovernanceWidget::tierTwoSynced); // Get restart command-line parameters and handle restart connect(settingsWidget, &SettingsWidget::handleRestart, [this](QStringList arg){handleRestart(arg);}); diff --git a/src/qt/pivx/topbar.cpp b/src/qt/pivx/topbar.cpp index 5fb7744d3897..043c709877e6 100644 --- a/src/qt/pivx/topbar.cpp +++ b/src/qt/pivx/topbar.cpp @@ -480,8 +480,9 @@ void TopBar::setNumBlocks(int count) if (masternodeSync.IsSynced()) { // Node synced ui->pushButtonSync->setButtonText(tr("Synchronized - Block: %1").arg(QString::number(count))); - progressBar->setRange(0,100); + progressBar->setRange(0, 100); progressBar->setValue(100); + Q_EMIT tierTwoSynced(true); return; } else { diff --git a/src/qt/pivx/topbar.h b/src/qt/pivx/topbar.h index 8a68ce11cf2f..8cc44a4698e7 100644 --- a/src/qt/pivx/topbar.h +++ b/src/qt/pivx/topbar.h @@ -57,6 +57,7 @@ public Q_SLOTS: Q_SIGNALS: void themeChanged(bool isLight); void walletSynced(bool isSync); + void tierTwoSynced(bool isSync); void onShowHideColdStakingChanged(bool show); protected: From 87beffea2c124e09cc767c5d319bd6d31e71a564 Mon Sep 17 00:00:00 2001 From: furszy Date: Sat, 7 Aug 2021 12:27:16 -0300 Subject: [PATCH 43/59] GUI: accept only UTF-8 valid proposal names. --- src/qt/pivx/createproposaldialog.cpp | 17 ++++++++++++++--- src/qt/pivx/forms/createproposaldialog.ui | 6 ++++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/qt/pivx/createproposaldialog.cpp b/src/qt/pivx/createproposaldialog.cpp index 85d57caec4a2..cb44b97c17f7 100644 --- a/src/qt/pivx/createproposaldialog.cpp +++ b/src/qt/pivx/createproposaldialog.cpp @@ -137,7 +137,8 @@ void CreateProposalDialog::setupPageThree() void CreateProposalDialog::propNameChanged(const QString& newText) { - setCssEditLine(ui->lineEditPropName, !newText.isEmpty(), true); + bool isValid = !newText.isEmpty() && IsValidUTF8(newText.toStdString()); + setCssEditLine(ui->lineEditPropName, isValid, true); } void CreateProposalDialog::propUrlChanged(const QString& newText) @@ -166,11 +167,21 @@ bool CreateProposalDialog::propaddressChanged(const QString& str) bool CreateProposalDialog::validatePageOne() { - if (ui->lineEditPropName->text().isEmpty()) { + QString propName = ui->lineEditPropName->text(); + if (propName.isEmpty()) { setCssEditLine(ui->lineEditPropName, false, true); - inform(tr("Proposal name field cannot be empty")); + inform(tr("Proposal name cannot be empty")); return false; } + + // For now, only accept UTF8 valid strings. + if (!IsValidUTF8(propName.toStdString())) { + setCssEditLine(ui->lineEditPropName, false, true); + inform(tr("Proposal name cannot contain non UTF-8 characters")); + return false; + } + + auto res = govModel->validatePropURL(ui->lineEditURL->text()); if (!res) inform(QString::fromStdString(res.getError())); return res.getRes(); diff --git a/src/qt/pivx/forms/createproposaldialog.ui b/src/qt/pivx/forms/createproposaldialog.ui index c11c379cfedf..bac265efff0c 100644 --- a/src/qt/pivx/forms/createproposaldialog.ui +++ b/src/qt/pivx/forms/createproposaldialog.ui @@ -1306,6 +1306,9 @@ 16777215 + + Qt::NoFocus + CANCEL @@ -1325,6 +1328,9 @@ 16777215 + + Qt::NoFocus + OK From 427913ebf652c6cc0ad3b2018b5c71f6138f9ce9 Mon Sep 17 00:00:00 2001 From: furszy Date: Sat, 4 Sep 2021 08:00:57 -0300 Subject: [PATCH 44/59] GUI: Stop translating test strings in the .ui forms. Plus whitespace styling corrections. --- src/qt/forms/coincontroldialog.ui | 6 +++--- src/qt/pivx/createproposaldialog.cpp | 18 +++++++++--------- src/qt/pivx/forms/createproposaldialog.ui | 20 ++++++++++---------- src/qt/pivx/forms/governancewidget.ui | 13 ++++++------- src/qt/pivx/forms/mnselectiondialog.ui | 4 ++-- src/qt/pivx/forms/proposalcard.ui | 12 ++++++------ src/qt/pivx/forms/votedialog.ui | 6 +++--- src/qt/pivx/governancewidget.cpp | 6 +++--- src/qt/pivx/masternodewizarddialog.cpp | 6 +++--- src/qt/pivx/proposalcard.cpp | 4 ++-- src/qt/pivx/votedialog.cpp | 4 ++-- src/qt/pivx/welcomecontentwidget.cpp | 12 ++++++------ src/qt/walletmodel.cpp | 3 +-- 13 files changed, 56 insertions(+), 58 deletions(-) diff --git a/src/qt/forms/coincontroldialog.ui b/src/qt/forms/coincontroldialog.ui index 0f7a76fb647d..f330b9fd5412 100644 --- a/src/qt/forms/coincontroldialog.ui +++ b/src/qt/forms/coincontroldialog.ui @@ -206,7 +206,7 @@ padding-right:4px;
- 0 + 0 @@ -591,7 +591,7 @@ padding-right:4px; - 0 + 0 @@ -683,7 +683,7 @@ padding-right:4px; - 0 + 0 diff --git a/src/qt/pivx/createproposaldialog.cpp b/src/qt/pivx/createproposaldialog.cpp index cb44b97c17f7..3050dff8e1f1 100644 --- a/src/qt/pivx/createproposaldialog.cpp +++ b/src/qt/pivx/createproposaldialog.cpp @@ -242,8 +242,8 @@ void CreateProposalDialog::sendProposal() void CreateProposalDialog::onNextClicked() { int nextPos = pos + 1; - switch (pos){ - case 0:{ + switch (pos) { + case 0: { if (!validatePageOne()) return; ui->stackedWidget->setCurrentIndex(nextPos); ui->pushNumber2->setChecked(true); @@ -254,7 +254,7 @@ void CreateProposalDialog::onNextClicked() ui->btnBack->setVisible(true); break; } - case 1:{ + case 1: { if (!validatePageTwo()) return; loadSummary(); ui->stackedWidget->setCurrentIndex(nextPos); @@ -263,10 +263,10 @@ void CreateProposalDialog::onNextClicked() ui->pushName2->setChecked(true); ui->pushName1->setChecked(true); icConfirm2->setVisible(true); - ui->btnNext->setText("Send"); + ui->btnNext->setText(tr("Send")); break; } - case 2:{ + case 2: { sendProposal(); } } @@ -277,8 +277,8 @@ void CreateProposalDialog::onBackClicked() { if (pos == 0) return; pos--; - switch(pos){ - case 0:{ + switch(pos) { + case 0: { ui->stackedWidget->setCurrentIndex(pos); ui->pushNumber1->setChecked(true); ui->pushNumber3->setChecked(false); @@ -290,7 +290,7 @@ void CreateProposalDialog::onBackClicked() ui->btnBack->setVisible(false); break; } - case 1:{ + case 1: { ui->stackedWidget->setCurrentIndex(pos); ui->pushNumber2->setChecked(true); ui->pushNumber3->setChecked(false); @@ -298,7 +298,7 @@ void CreateProposalDialog::onBackClicked() ui->pushName2->setChecked(true); ui->pushName1->setChecked(true); icConfirm2->setVisible(false); - ui->btnNext->setText("Next"); + ui->btnNext->setText(tr("Next")); break; } } diff --git a/src/qt/pivx/forms/createproposaldialog.ui b/src/qt/pivx/forms/createproposaldialog.ui index bac265efff0c..20b61b4dfe9d 100644 --- a/src/qt/pivx/forms/createproposaldialog.ui +++ b/src/qt/pivx/forms/createproposaldialog.ui @@ -11,7 +11,7 @@ - Dialog + Dialog @@ -193,7 +193,7 @@ - 1 + 1 true @@ -297,7 +297,7 @@ - 2 + 2 true @@ -401,7 +401,7 @@ - 3 + 3 true @@ -790,7 +790,7 @@ - Proposal Forum URL + Proposal URL @@ -1137,7 +1137,7 @@ - PIVX-MBD-JanJun2021 + PIVX-MBD-JanJun2021 @@ -1164,7 +1164,7 @@ - 5500 PIV + 5500 PIV Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter @@ -1192,7 +1192,7 @@ - r7VFR83SQbiezrW72hjcWJtcfip5krte2Z + N/A @@ -1219,7 +1219,7 @@ - 2 Months + 2 Months Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter @@ -1245,7 +1245,7 @@ - Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do + N/A diff --git a/src/qt/pivx/forms/governancewidget.ui b/src/qt/pivx/forms/governancewidget.ui index a29cccfc909b..b7dbaf56698f 100644 --- a/src/qt/pivx/forms/governancewidget.ui +++ b/src/qt/pivx/forms/governancewidget.ui @@ -17,7 +17,7 @@ - Form + Form @@ -82,14 +82,14 @@ - TextLabel + TextLabel - TextLabel + TextLabel @@ -568,7 +568,7 @@ - 6,000 PIV + 6,000 PIV @@ -654,7 +654,7 @@ - 37,000 PIV + 37,000 PIV @@ -755,8 +755,7 @@ - Next superblock unknown. - Wait until the node is fully sync. + Next superblock unknown. Wait until the node is fully sync. true diff --git a/src/qt/pivx/forms/mnselectiondialog.ui b/src/qt/pivx/forms/mnselectiondialog.ui index 30cc77a83571..f2a2ccd1036c 100644 --- a/src/qt/pivx/forms/mnselectiondialog.ui +++ b/src/qt/pivx/forms/mnselectiondialog.ui @@ -11,7 +11,7 @@ - Form + Form @@ -262,7 +262,7 @@ - 0 + 0 diff --git a/src/qt/pivx/forms/proposalcard.ui b/src/qt/pivx/forms/proposalcard.ui index 6d5bef8c45df..9900a21c1c85 100644 --- a/src/qt/pivx/forms/proposalcard.ui +++ b/src/qt/pivx/forms/proposalcard.ui @@ -79,7 +79,7 @@ - Proposal Name + Proposal Name @@ -162,14 +162,14 @@ - 5,000 PIV + 5,000 PIV - 2 months passed of 4 + 2 months passed of 4 @@ -195,7 +195,7 @@ margin-top:16; - Passing + Passing @@ -277,7 +277,7 @@ color:#4D4D4D; - 24% No + 24% No @@ -300,7 +300,7 @@ color:#5C4B7D; - 76% Yes + 76% Yes diff --git a/src/qt/pivx/forms/votedialog.ui b/src/qt/pivx/forms/votedialog.ui index 3ccbb916588a..3a1e1c6af6d5 100644 --- a/src/qt/pivx/forms/votedialog.ui +++ b/src/qt/pivx/forms/votedialog.ui @@ -183,7 +183,7 @@ - PIVX-MBD-Jan-2021 + PIVX-MBD-Jan-2021 Qt::AlignCenter @@ -257,14 +257,14 @@ - 5,000 PIV + 5,000 PIV - 2 months of 4 + 2 months of 4 diff --git a/src/qt/pivx/governancewidget.cpp b/src/qt/pivx/governancewidget.cpp index a77cdbab4d0d..aa36cbf67840 100644 --- a/src/qt/pivx/governancewidget.cpp +++ b/src/qt/pivx/governancewidget.cpp @@ -28,9 +28,9 @@ GovernanceWidget::GovernanceWidget(PIVXGUI* parent) : setCssProperty(ui->scrollArea, "container"); /* Title */ - ui->labelTitle->setText("Governance"); + ui->labelTitle->setText(tr("Governance")); setCssProperty(ui->labelTitle, "text-title-screen"); - ui->labelSubtitle1->setText("View, follow, vote and submit network budget proposals.\nBe part of the DAO."); + ui->labelSubtitle1->setText(tr("View, follow, vote and submit network budget proposals.\nBe part of the DAO.")); setCssProperty(ui->labelSubtitle1, "text-subtitle"); setCssProperty(ui->pushImgEmpty, "img-empty-governance"); setCssProperty(ui->labelEmpty, "text-empty"); @@ -100,7 +100,7 @@ GovernanceWidget::GovernanceWidget(PIVXGUI* parent) : // Create proposal ui->btnCreateProposal->setTitleClassAndText("btn-title-grey", "Create Proposal"); - ui->btnCreateProposal->setSubTitleClassAndText("text-subtitle", "Prepare and submit a new proposal."); + ui->btnCreateProposal->setSubTitleClassAndText("text-subtitle", tr("Prepare and submit a new proposal.")); connect(ui->btnCreateProposal, &OptionButton::clicked, this, &GovernanceWidget::onCreatePropClicked); ui->emptyContainer->setVisible(false); } diff --git a/src/qt/pivx/masternodewizarddialog.cpp b/src/qt/pivx/masternodewizarddialog.cpp index 09d2eacc4964..db40ce781532 100644 --- a/src/qt/pivx/masternodewizarddialog.cpp +++ b/src/qt/pivx/masternodewizarddialog.cpp @@ -133,7 +133,7 @@ void MasterNodeWizardDialog::accept() ui->lineEditName->setFocus(); break; } - case 1:{ + case 1: { // No empty names accepted. if (ui->lineEditName->text().isEmpty()) { @@ -152,7 +152,7 @@ void MasterNodeWizardDialog::accept() ui->lineEditIpAddress->setFocus(); break; } - case 2:{ + case 2: { // No empty address accepted if (ui->lineEditIpAddress->text().isEmpty()) { @@ -404,7 +404,7 @@ void MasterNodeWizardDialog::onBackClicked() ui->btnBack->setVisible(false); break; } - case 1:{ + case 1: { ui->stackedWidget->setCurrentIndex(1); ui->lineEditName->setFocus(); ui->pushNumber4->setChecked(false); diff --git a/src/qt/pivx/proposalcard.cpp b/src/qt/pivx/proposalcard.cpp index febea4701b0f..658166e78cfb 100644 --- a/src/qt/pivx/proposalcard.cpp +++ b/src/qt/pivx/proposalcard.cpp @@ -46,8 +46,8 @@ void ProposalCard::setProposal(const ProposalInfo& _proposalInfo) double percentageNo = (totalVotes == 0) ? 0 : (_proposalInfo.votesNo / totalVotes) * 100; double percentageYes = (totalVotes == 0) ? 0 : (_proposalInfo.votesYes / totalVotes) * 100; ui->votesBar->setValue((int)percentageNo); - ui->labelNo->setText(QString::fromStdString(std::to_string((int)percentageNo) + "% No")); - ui->labelYes->setText(QString::fromStdString("Yes "+ std::to_string((int)percentageYes) + "%")); + ui->labelNo->setText(QString::number(percentageNo) + "% " + tr("No")); + ui->labelYes->setText(tr("Yes") + " " + QString::number(percentageYes) + "%"); QString cssClassStatus; if (proposalInfo.status == ProposalInfo::WAITING_FOR_APPROVAL){ diff --git a/src/qt/pivx/votedialog.cpp b/src/qt/pivx/votedialog.cpp index 3f5d0e94157b..53f8e50a1157 100644 --- a/src/qt/pivx/votedialog.cpp +++ b/src/qt/pivx/votedialog.cpp @@ -63,8 +63,8 @@ void VoteDialog::setProposal(const ProposalInfo& prop) double percentageYes = (totalVotes == 0) ? 0 : (prop.votesYes / totalVotes) * 100; progressBarNo->setValue((int)percentageNo); progressBarYes->setValue((int)percentageYes); - checkBoxNo->setText(tr("%1 / %2% No").arg(prop.votesNo).arg(percentageNo)); - checkBoxYes->setText(tr("Yes %1 / %2%").arg(prop.votesYes).arg(percentageYes)); + checkBoxNo->setText(QString::number(prop.votesNo) + " / " + QString::number(percentageNo) + "% " + tr("No")); + checkBoxYes->setText(tr("Yes") + " " + QString::number(prop.votesYes) + " / " + QString::number(percentageYes) + "%"); votes = govModel->getLocalMNsVotesForProposal(prop); updateMnSelectionNum(); } diff --git a/src/qt/pivx/welcomecontentwidget.cpp b/src/qt/pivx/welcomecontentwidget.cpp index 70fcf911c1dd..c927e6822701 100644 --- a/src/qt/pivx/welcomecontentwidget.cpp +++ b/src/qt/pivx/welcomecontentwidget.cpp @@ -214,12 +214,12 @@ void WelcomeContentWidget::checkLanguage() void WelcomeContentWidget::onNextClicked() { - switch(pos){ + switch(pos) { case 0:{ ui->stackedWidget->setCurrentIndex(1); break; } - case 1:{ + case 1: { backButton->setVisible(true); ui->stackedWidget->setCurrentIndex(2); ui->pushNumber2->setChecked(true); @@ -230,7 +230,7 @@ void WelcomeContentWidget::onNextClicked() icConfirm1->setVisible(true); break; } - case 2:{ + case 2: { ui->stackedWidget->setCurrentIndex(3); ui->pushNumber3->setChecked(true); ui->pushName4->setChecked(false); @@ -264,12 +264,12 @@ void WelcomeContentWidget::onBackClicked() { if (pos == 0) return; pos--; - switch(pos){ + switch(pos) { case 0:{ ui->stackedWidget->setCurrentIndex(0); break; } - case 1:{ + case 1: { ui->stackedWidget->setCurrentIndex(1); ui->pushNumber1->setChecked(true); ui->pushNumber4->setChecked(false); @@ -284,7 +284,7 @@ void WelcomeContentWidget::onBackClicked() break; } - case 2:{ + case 2: { ui->stackedWidget->setCurrentIndex(2); ui->pushNumber2->setChecked(true); ui->pushNumber4->setChecked(false); diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index c4dc0f8ee6f2..28552670deea 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -580,7 +580,6 @@ WalletModel::SendCoinsReturn WalletModel::sendCoins(WalletModelTransaction& tran { LOCK2(cs_main, wallet->cs_wallet); - QList recipients = transaction.getRecipients(); CReserveKey* keyChange = transaction.getPossibleKeyChange(); const CWallet::CommitResult& res = wallet->CommitTransaction(newTx, keyChange, g_connman.get()); @@ -664,7 +663,7 @@ OperationResult WalletModel::createAndSendProposalFeeTx(CBudgetProposal& proposa const uint256& nHash = proposal.GetHash(); CReserveKey keyChange(wallet); if (!wallet->CreateBudgetFeeTX(wtx, nHash, keyChange, false)) { // 50 PIV collateral for proposal - return {false ,"Error making fee transaction for proposal. Please check your wallet balance."}; + return {false , "Error making fee transaction for proposal. Please check your wallet balance."}; } // send the tx to the network From 2b11018b3d32c33b10a68677955560b629f28379 Mon Sep 17 00:00:00 2001 From: furszy Date: Wed, 8 Sep 2021 20:01:14 -0300 Subject: [PATCH 45/59] GUI: refactor GUIUtil::parseValue() to be used equally as `SendMultiRow::getAmountValue()`. --- src/qt/guiutil.cpp | 12 +++++++++++- src/qt/guiutil.h | 5 +++-- src/qt/pivx/requestdialog.cpp | 17 ++++++----------- src/qt/pivx/sendcustomfeedialog.cpp | 12 ++++++++++-- src/qt/pivx/sendmultirow.cpp | 21 +++++---------------- src/qt/pivx/sendmultirow.h | 1 - 6 files changed, 35 insertions(+), 33 deletions(-) diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp index baf74b5ffe03..b2d7b596ba80 100644 --- a/src/qt/guiutil.cpp +++ b/src/qt/guiutil.cpp @@ -94,7 +94,7 @@ QFont bitcoinAddressFont() * return validity. * @note Must return 0 if !valid. */ -CAmount parseValue(const QString& text, int displayUnit, bool* valid_out) +static CAmount parseValue(const QString& text, int displayUnit, bool* valid_out) { CAmount val = 0; bool valid = BitcoinUnits::parse(displayUnit, text, &val); @@ -107,6 +107,16 @@ CAmount parseValue(const QString& text, int displayUnit, bool* valid_out) return valid ? val : 0; } +/** + * Returns 0 if the value is invalid + */ +CAmount parseValue(const QString& amount, int displayUnit) +{ + bool isValid = false; + CAmount value = GUIUtil::parseValue(amount, displayUnit, &isValid); + return isValid ? value : 0; +} + QString formatBalance(CAmount amount, int nDisplayUnit, bool isZpiv) { return (amount == 0) ? ("0.00 " + BitcoinUnits::name(nDisplayUnit, isZpiv)) : BitcoinUnits::floorHtmlWithUnit(nDisplayUnit, amount, false, BitcoinUnits::separatorAlways, true, isZpiv); diff --git a/src/qt/guiutil.h b/src/qt/guiutil.h index ae3247a9adaf..744bd8230e5b 100644 --- a/src/qt/guiutil.h +++ b/src/qt/guiutil.h @@ -54,8 +54,9 @@ QString dateTimeStr(qint64 nTime); // Render PIVX addresses in monospace font QFont bitcoinAddressFont(); -// Parse string into a CAmount value -CAmount parseValue(const QString& text, int displayUnit, bool* valid_out = 0); +// Parse string into a CAmount value. +// Return 0 if the value is invalid +CAmount parseValue(const QString& amount, int displayUnit = 0); // Format an amount QString formatBalance(CAmount amount, int nDisplayUnit = 0, bool isZpiv = false); diff --git a/src/qt/pivx/requestdialog.cpp b/src/qt/pivx/requestdialog.cpp index 1805d673ade3..740845177a99 100644 --- a/src/qt/pivx/requestdialog.cpp +++ b/src/qt/pivx/requestdialog.cpp @@ -8,7 +8,6 @@ #include "qt/pivx/qtutils.h" #include "guiutil.h" -#include "amount.h" #include "optionsmodel.h" RequestDialog::RequestDialog(QWidget *parent) : @@ -83,14 +82,6 @@ void RequestDialog::accept() if (walletModel) { QString labelStr = ui->lineEditLabel->text(); - //Amount - int displayUnit = walletModel->getOptionsModel()->getDisplayUnit(); - bool isValueValid = true; - CAmount value = (ui->lineEditAmount->text().isEmpty() ? - 0 : - GUIUtil::parseValue(ui->lineEditAmount->text(), displayUnit, &isValueValid) - ); - if (!this->isPaymentRequest) { // Add specific checks for cold staking address creation if (labelStr.isEmpty()) { @@ -99,7 +90,11 @@ void RequestDialog::accept() } } - if (value < 0 || !isValueValid) { + int displayUnit = walletModel->getOptionsModel()->getDisplayUnit(); + auto value = ui->lineEditAmount->text().isEmpty() ? -1 : + GUIUtil::parseValue(ui->lineEditAmount->text(), displayUnit); + + if (value <= 0) { inform(tr("Invalid amount")); return; } @@ -116,7 +111,7 @@ void RequestDialog::accept() CallResult r; if (this->isPaymentRequest) { r = walletModel->getNewAddress(label); - title = tr("Request for ") + BitcoinUnits::format(displayUnit, value, false, BitcoinUnits::separatorAlways) + " " + QString(CURRENCY_UNIT.c_str()); + title = tr("Request for ") + BitcoinUnits::format(displayUnit, info->amount, false, BitcoinUnits::separatorAlways) + " " + QString(CURRENCY_UNIT.c_str()); } else { r = walletModel->getNewStakingAddress(label); title = tr("Cold Staking Address Generated"); diff --git a/src/qt/pivx/sendcustomfeedialog.cpp b/src/qt/pivx/sendcustomfeedialog.cpp index f7a139333c5e..46562c2ec849 100644 --- a/src/qt/pivx/sendcustomfeedialog.cpp +++ b/src/qt/pivx/sendcustomfeedialog.cpp @@ -150,8 +150,16 @@ void SendCustomFeeDialog::clear() CFeeRate SendCustomFeeDialog::getFeeRate() { - return ui->checkBoxRecommended->isChecked() ? - feeRate : CFeeRate(GUIUtil::parseValue(ui->lineEditCustomFee->text(), walletModel->getOptionsModel()->getDisplayUnit())); + if (ui->checkBoxRecommended->isChecked()) { + return feeRate; + } + + // Parse custom value + auto value = GUIUtil::parseValue(ui->lineEditCustomFee->text(), walletModel->getOptionsModel()->getDisplayUnit()); + if (value <= 0) { + inform(tr("Invalid custom fee amount")); + } + return CFeeRate(value); } bool SendCustomFeeDialog::isCustomFeeChecked() diff --git a/src/qt/pivx/sendmultirow.cpp b/src/qt/pivx/sendmultirow.cpp index 3a80e657f947..ab86eece3900 100644 --- a/src/qt/pivx/sendmultirow.cpp +++ b/src/qt/pivx/sendmultirow.cpp @@ -65,11 +65,10 @@ SendMultiRow::SendMultiRow(PIVXGUI* _window, PWidget *parent) : connect(ui->btnAddMemo, &QPushButton::clicked, this, &SendMultiRow::onMemoClicked); } -void SendMultiRow::amountChanged(const QString& amount) +void SendMultiRow::amountChanged(const QString& amountStr) { - if (!amount.isEmpty()) { - QString amountStr = amount; - CAmount value = getAmountValue(amountStr); + if (!amountStr.isEmpty()) { + auto value = GUIUtil::parseValue(amountStr, displayUnit); if (value > 0) { GUIUtil::updateWidgetTextAndCursorPosition(ui->lineEditAmount, amountStr); setCssEditLine(ui->lineEditAmount, true, true); @@ -106,16 +105,6 @@ bool SendMultiRow::launchMemoDialog() return ret; } -/** - * Returns -1 if the value is invalid - */ -CAmount SendMultiRow::getAmountValue(QString amount) -{ - bool isValid = false; - CAmount value = GUIUtil::parseValue(amount, displayUnit, &isValid); - return isValid ? value : -1; -} - bool SendMultiRow::addressChanged(const QString& str, bool fOnlyValidate) { if (!str.isEmpty()) { @@ -203,7 +192,7 @@ bool SendMultiRow::validate() } else retval = addressChanged(address, true); - CAmount value = getAmountValue(ui->lineEditAmount->text()); + CAmount value = getAmountValue(); // Sending a zero amount is invalid if (value <= 0) { @@ -238,7 +227,7 @@ QString SendMultiRow::getAddress() CAmount SendMultiRow::getAmountValue() { - return getAmountValue(ui->lineEditAmount->text()); + return GUIUtil::parseValue(ui->lineEditAmount->text(), displayUnit); } QString SendMultiRow::getMemo() diff --git a/src/qt/pivx/sendmultirow.h b/src/qt/pivx/sendmultirow.h index 9bb34c5d3fbf..6d760167680d 100644 --- a/src/qt/pivx/sendmultirow.h +++ b/src/qt/pivx/sendmultirow.h @@ -43,7 +43,6 @@ class SendMultiRow : public PWidget /** Return whether the entry is still empty and unedited */ bool isClear(); void setOnlyStakingAddressAccepted(bool onlyStakingAddress); - CAmount getAmountValue(QString str); void setAddress(const QString& address); void setLabel(const QString& label); From b8f0ac626c5360318772ed2e5d3ca4dd8e075f38 Mon Sep 17 00:00:00 2001 From: furszy Date: Wed, 8 Sep 2021 20:19:28 -0300 Subject: [PATCH 46/59] Connect PROPOSAL_MIN_AMOUNT constant to gov model validatePropAmount() function. --- src/qt/pivx/governancemodel.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/qt/pivx/governancemodel.cpp b/src/qt/pivx/governancemodel.cpp index c9a572f02b44..eb81052c7e60 100644 --- a/src/qt/pivx/governancemodel.cpp +++ b/src/qt/pivx/governancemodel.cpp @@ -155,8 +155,12 @@ OperationResult GovernanceModel::validatePropURL(const QString& url) const OperationResult GovernanceModel::validatePropAmount(CAmount amount) const { + if (amount < PROPOSAL_MIN_AMOUNT) { // Future: move constant to a budget interface. + return {false, strprintf("Amount below the minimum of %s PIV", FormatMoney(PROPOSAL_MIN_AMOUNT))}; + } + if (amount > getMaxAvailableBudgetAmount()) { - return {false, strprintf("Amount exceeding the maximum available budget amount of %s PIV", FormatMoney(amount))}; + return {false, strprintf("Amount exceeding the maximum available of %s PIV", FormatMoney(getMaxAvailableBudgetAmount()))}; } return {true}; } From 51d799bb143a24e4fbe5b7c7507072e3fbdc6fe6 Mon Sep 17 00:00:00 2001 From: furszy Date: Wed, 8 Sep 2021 20:23:12 -0300 Subject: [PATCH 47/59] GUI create proposal: unify amount parsing using GUIUtil::parseValue(). This fixes a conversion issue for the QString::toInt() previous usage. --- src/qt/pivx/createproposaldialog.cpp | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/qt/pivx/createproposaldialog.cpp b/src/qt/pivx/createproposaldialog.cpp index 3050dff8e1f1..7cce6285ec76 100644 --- a/src/qt/pivx/createproposaldialog.cpp +++ b/src/qt/pivx/createproposaldialog.cpp @@ -148,8 +148,12 @@ void CreateProposalDialog::propUrlChanged(const QString& newText) void CreateProposalDialog::propAmountChanged(const QString& newText) { - CAmount amt = newText.toDouble() * COIN; - setCssEditLine(ui->lineEditAmount, govModel->validatePropAmount(amt).getRes(), true); + if (newText.isEmpty()) { + setCssEditLine(ui->lineEditAmount, true, true); + return; + } + auto amount = GUIUtil::parseValue(newText); + setCssEditLine(ui->lineEditAmount, govModel->validatePropAmount(amount).getRes(), true); } bool CreateProposalDialog::propaddressChanged(const QString& str) @@ -190,7 +194,12 @@ bool CreateProposalDialog::validatePageOne() bool CreateProposalDialog::validatePageTwo() { // Amount validation - auto opRes = govModel->validatePropAmount(ui->lineEditAmount->text().toInt()); + auto amount = GUIUtil::parseValue(ui->lineEditAmount->text()); + if (amount <= 0) { + inform(tr("Invalid amount")); + return false; + } + auto opRes = govModel->validatePropAmount(amount); if (!opRes) { inform(QString::fromStdString(opRes.getError())); return false; @@ -215,7 +224,7 @@ void CreateProposalDialog::loadSummary() { ui->labelResultName->setText(ui->lineEditPropName->text()); ui->labelResultUrl->setText(ui->lineEditURL->text()); - ui->labelResultAmount->setText(GUIUtil::formatBalance(ui->lineEditAmount->text().toInt() * COIN)); + ui->labelResultAmount->setText(GUIUtil::formatBalance(GUIUtil::parseValue(ui->lineEditAmount->text()))); ui->labelResultMonths->setText(QString::number(ui->lineEditMonths->value())); ui->labelResultAddress->setText(ui->lineEditAddress->text()); ui->labelResultUrl->setText(ui->lineEditURL->text()); @@ -224,7 +233,7 @@ void CreateProposalDialog::loadSummary() void CreateProposalDialog::sendProposal() { int months = ui->lineEditMonths->value(); - CAmount amount = ui->lineEditAmount->text().toInt() * COIN; + CAmount amount = GUIUtil::parseValue(ui->lineEditAmount->text()); auto opRes = govModel->createProposal( ui->lineEditPropName->text().toStdString(), ui->lineEditURL->text().toStdString(), From e264153beb9dd569f5e308cdf20e763fe4020f82 Mon Sep 17 00:00:00 2001 From: furszy Date: Wed, 8 Sep 2021 21:42:26 -0300 Subject: [PATCH 48/59] GUI: request unlock wallet before try to create a proposal And don't try to create a proposal if the wallet has not enough balance to pay for the proposal fee. --- src/qt/pivx/governancemodel.cpp | 5 +++++ src/qt/pivx/governancemodel.h | 2 ++ src/qt/pivx/governancewidget.cpp | 15 +++++++++++++++ src/qt/pivx/sendcustomfeedialog.cpp | 8 ++++---- src/qt/walletmodel.cpp | 5 +++++ src/qt/walletmodel.h | 1 + 6 files changed, 32 insertions(+), 4 deletions(-) diff --git a/src/qt/pivx/governancemodel.cpp b/src/qt/pivx/governancemodel.cpp index eb81052c7e60..cf27aaf1150e 100644 --- a/src/qt/pivx/governancemodel.cpp +++ b/src/qt/pivx/governancemodel.cpp @@ -110,6 +110,11 @@ int GovernanceModel::getPropMaxPaymentsCount() const return Params().GetConsensus().nMaxProposalPayments; } +CAmount GovernanceModel::getProposalFeeAmount() const +{ + return PROPOSAL_FEE_TX; +} + int GovernanceModel::getNextSuperblockHeight() const { const int nBlocksPerCycle = getNumBlocksPerBudgetCycle(); diff --git a/src/qt/pivx/governancemodel.h b/src/qt/pivx/governancemodel.h index d93202974c09..d7384ae33ecf 100644 --- a/src/qt/pivx/governancemodel.h +++ b/src/qt/pivx/governancemodel.h @@ -100,6 +100,8 @@ class GovernanceModel : public QObject CAmount getMaxAvailableBudgetAmount() const; // Return the proposal maximum payments count for the running chain int getPropMaxPaymentsCount() const; + // Return the required fee for proposals + CAmount getProposalFeeAmount() const; int getNextSuperblockHeight() const; // Returns the sum of all of the passing proposals CAmount getBudgetAllocatedAmount() const { return allocatedAmount; }; diff --git a/src/qt/pivx/governancewidget.cpp b/src/qt/pivx/governancewidget.cpp index aa36cbf67840..c98e6363a95d 100644 --- a/src/qt/pivx/governancewidget.cpp +++ b/src/qt/pivx/governancewidget.cpp @@ -154,6 +154,21 @@ void GovernanceWidget::onVoteForPropClicked(const ProposalInfo& proposalInfo) void GovernanceWidget::onCreatePropClicked() { + if (!walletModel || !governanceModel) return; + + auto ptrUnlockedContext = std::make_unique(walletModel->requestUnlock()); + if (!ptrUnlockedContext->isValid()) { + inform(tr("Cannot create proposal, wallet locked")); + return; + } + + auto balance = walletModel->GetWalletBalances(); + if (balance.balance <= governanceModel->getProposalFeeAmount()) { + inform(tr("Cannot create proposal, need to have at least %1 to pay for the proposal fee").arg( + GUIUtil::formatBalance(governanceModel->getProposalFeeAmount() + walletModel->getNetMinFee()).toStdString().c_str())); + return; + } + window->showHide(true); CreateProposalDialog* dialog = new CreateProposalDialog(window, governanceModel, walletModel); if (openDialogWithOpaqueBackgroundY(dialog, window, 4.5, ui->left->height() < 700 ? 12 : 5)) { diff --git a/src/qt/pivx/sendcustomfeedialog.cpp b/src/qt/pivx/sendcustomfeedialog.cpp index 46562c2ec849..320d7dc18c24 100644 --- a/src/qt/pivx/sendcustomfeedialog.cpp +++ b/src/qt/pivx/sendcustomfeedialog.cpp @@ -124,19 +124,19 @@ void SendCustomFeeDialog::accept() // Check insane fee const CAmount insaneFee = ::minRelayTxFee.GetFeePerK() * 10000; if (customFee >= insaneFee) { - ui->lineEditCustomFee->setText(BitcoinUnits::format(walletModel->getOptionsModel()->getDisplayUnit(), insaneFee - GetRequiredFee(1000))); + ui->lineEditCustomFee->setText(BitcoinUnits::format(walletModel->getOptionsModel()->getDisplayUnit(), insaneFee - walletModel->getNetMinFee())); inform(tr("Fee too high. Must be below: %1").arg( BitcoinUnits::formatWithUnit(walletModel->getOptionsModel()->getDisplayUnit(), insaneFee))); - } else if (customFee < GetRequiredFee(1000)) { + } else if (customFee < walletModel->getNetMinFee()) { CAmount nFee = 0; if (walletModel->hasWalletCustomFee()) { walletModel->getWalletCustomFee(nFee); } else { - nFee = GetRequiredFee(1000); + nFee = walletModel->getNetMinFee(); } ui->lineEditCustomFee->setText(BitcoinUnits::format(walletModel->getOptionsModel()->getDisplayUnit(), nFee)); inform(tr("Fee too low. Must be at least: %1").arg( - BitcoinUnits::formatWithUnit(walletModel->getOptionsModel()->getDisplayUnit(), GetRequiredFee(1000)))); + BitcoinUnits::formatWithUnit(walletModel->getOptionsModel()->getDisplayUnit(), walletModel->getNetMinFee()))); } else { walletModel->setWalletCustomFee(fUseCustomFee, customFee); QDialog::accept(); diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index 28552670deea..97378a533e73 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -13,6 +13,7 @@ #include "sapling/sapling_operation.h" #include "sapling/transaction_builder.h" #include "spork.h" +#include "wallet/fees.h" #include "qt/addresstablemodel.h" #include "qt/clientmodel.h" @@ -1204,3 +1205,7 @@ int WalletModel::getLastBlockProcessedNum() const return m_client_model ? m_client_model->getLastBlockProcessedHeight() : 0; } +CAmount WalletModel::getNetMinFee() +{ // future: unify minimum required fee. + return GetRequiredFee(1000); +} diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h index c0e1044926bd..22246458daa0 100644 --- a/src/qt/walletmodel.h +++ b/src/qt/walletmodel.h @@ -206,6 +206,7 @@ class WalletModel : public QObject bool hasWalletCustomFee(); bool getWalletCustomFee(CAmount& nFeeRet); void setWalletCustomFee(bool fUseCustomFee, const CAmount nFee = DEFAULT_TRANSACTION_FEE); + CAmount getNetMinFee(); void setWalletStakeSplitThreshold(const CAmount nStakeSplitThreshold); CAmount getWalletStakeSplitThreshold() const; From 889a720cb699175e1f9a375babc4b079f26a1149 Mon Sep 17 00:00:00 2001 From: furszy Date: Thu, 9 Sep 2021 19:45:35 -0300 Subject: [PATCH 49/59] GUI: Adjust proposal card months label text and the status label padding to not overlap. --- src/qt/pivx/forms/proposalcard.ui | 8 ++++---- src/qt/pivx/proposalcard.cpp | 4 ++-- src/qt/pivx/res/css/style_dark.css | 6 +++--- src/qt/pivx/res/css/style_light.css | 6 +++--- src/qt/pivx/votedialog.cpp | 2 +- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/qt/pivx/forms/proposalcard.ui b/src/qt/pivx/forms/proposalcard.ui index 9900a21c1c85..c32bf1530ce3 100644 --- a/src/qt/pivx/forms/proposalcard.ui +++ b/src/qt/pivx/forms/proposalcard.ui @@ -6,7 +6,7 @@ 0 0 - 250 + 260 200 @@ -18,13 +18,13 @@ - 250 + 260 200 - 320 + 330 200 @@ -169,7 +169,7 @@ - 2 months passed of 4 + 2 months remaining diff --git a/src/qt/pivx/proposalcard.cpp b/src/qt/pivx/proposalcard.cpp index 658166e78cfb..362608a67ad6 100644 --- a/src/qt/pivx/proposalcard.cpp +++ b/src/qt/pivx/proposalcard.cpp @@ -40,8 +40,8 @@ void ProposalCard::setProposal(const ProposalInfo& _proposalInfo) proposalInfo = _proposalInfo; ui->labelPropName->setText(QString::fromStdString(proposalInfo.name)); ui->labelPropAmount->setText(GUIUtil::formatBalance(proposalInfo.amount)); - ui->labelPropMonths->setText(tr("%1 months passed of %2") - .arg(proposalInfo.totalPayments - proposalInfo.remainingPayments).arg(proposalInfo.totalPayments)); + ui->labelPropMonths->setText((proposalInfo.remainingPayments == 0) ? tr("Last month in course") : + tr("%1 of %2 months left").arg(proposalInfo.remainingPayments).arg(proposalInfo.totalPayments)); double totalVotes = _proposalInfo.votesYes + _proposalInfo.votesNo; double percentageNo = (totalVotes == 0) ? 0 : (_proposalInfo.votesNo / totalVotes) * 100; double percentageYes = (totalVotes == 0) ? 0 : (_proposalInfo.votesYes / totalVotes) * 100; diff --git a/src/qt/pivx/res/css/style_dark.css b/src/qt/pivx/res/css/style_dark.css index 9e2f75ee886e..c53c300081fc 100644 --- a/src/qt/pivx/res/css/style_dark.css +++ b/src/qt/pivx/res/css/style_dark.css @@ -2256,7 +2256,7 @@ HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH*/ font-size:12px; border-radius:12px; background-color:rgba(176, 136, 255, 0.4); - padding:6px 12px; + padding:6px 6px; } *[cssClass="card-status-not-passing"] { @@ -2264,7 +2264,7 @@ HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH*/ font-size:12px; border-radius:12px; background-color:rgba(112, 112, 112, 0.3); - padding:6px 12px; + padding:6px 6px; } *[cssClass="card-status-no-votes"] { @@ -2272,7 +2272,7 @@ HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH*/ font-size:12px; border-radius:12px; background-color:rgba(186, 186, 186, 0.1); - padding:6px 12px; + padding:6px 6px; } *[cssClass="card-progress-box"] { diff --git a/src/qt/pivx/res/css/style_light.css b/src/qt/pivx/res/css/style_light.css index 06cc67e59080..541a9f58cc41 100644 --- a/src/qt/pivx/res/css/style_light.css +++ b/src/qt/pivx/res/css/style_light.css @@ -2261,7 +2261,7 @@ HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH*/ font-size:12px; border-radius:12px; background-color:rgba(176, 136, 255, 0.2); - padding:6px 12px; + padding:6px 6px; } *[cssClass="card-status-not-passing"] { @@ -2269,7 +2269,7 @@ HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH*/ font-size:12px; border-radius:12px; background-color:rgba(186, 186, 186, 0.2); - padding:6px 12px; + padding:6px 6px; } *[cssClass="card-status-no-votes"] { @@ -2278,7 +2278,7 @@ HHHHHHHHHHHHHHHHHHHHHHHHHHHHHHHH*/ font-size:12px; border-radius:12px; background-color:transparent; - padding:6px 12px; + padding:6px 6px; } *[cssClass="card-progress-box"] { diff --git a/src/qt/pivx/votedialog.cpp b/src/qt/pivx/votedialog.cpp index 53f8e50a1157..b02dc4466e0a 100644 --- a/src/qt/pivx/votedialog.cpp +++ b/src/qt/pivx/votedialog.cpp @@ -57,7 +57,7 @@ void VoteDialog::setProposal(const ProposalInfo& prop) proposal = std::make_unique(prop); ui->labelTitleVote->setText(QString::fromStdString(prop.name)); ui->labelAmount->setText(GUIUtil::formatBalance(prop.amount)); - ui->labelTime->setText(tr("%1 months passed of %2").arg(prop.totalPayments - prop.remainingPayments).arg(prop.totalPayments)); + ui->labelTime->setText(tr("%1 months remaining").arg(prop.remainingPayments)); double totalVotes = prop.votesYes + prop.votesNo; double percentageNo = (totalVotes == 0) ? 0 : (prop.votesNo / totalVotes) * 100; double percentageYes = (totalVotes == 0) ? 0 : (prop.votesYes / totalVotes) * 100; From 559a0953d6f7715ef9825a7b194ddc1c83ecfbe7 Mon Sep 17 00:00:00 2001 From: furszy Date: Thu, 9 Sep 2021 20:46:33 -0300 Subject: [PATCH 50/59] GUI: governance widget, Add translations and generalize sort/filter combo-boxes view initialization. --- src/qt/pivx/forms/governancewidget.ui | 6 ++++ src/qt/pivx/governancewidget.cpp | 51 ++++++++++----------------- src/qt/pivx/governancewidget.h | 2 +- 3 files changed, 26 insertions(+), 33 deletions(-) diff --git a/src/qt/pivx/forms/governancewidget.ui b/src/qt/pivx/forms/governancewidget.ui index b7dbaf56698f..e9fca02b18de 100644 --- a/src/qt/pivx/forms/governancewidget.ui +++ b/src/qt/pivx/forms/governancewidget.ui @@ -159,6 +159,12 @@ 0 + + QComboBox::AdjustToMinimumContentsLength + + + 15 + diff --git a/src/qt/pivx/governancewidget.cpp b/src/qt/pivx/governancewidget.cpp index c98e6363a95d..81febc2561d2 100644 --- a/src/qt/pivx/governancewidget.cpp +++ b/src/qt/pivx/governancewidget.cpp @@ -11,8 +11,20 @@ #include #include -// Proposals filter -const std::vector propFilter = {"All", "Passing", "Not Passing", "Waiting"}; +void initComboView(PWidget* parent, QComboBox* comboBox, const QString& filterHint, const QList& values) +{ + auto* modelFilter = new QStandardItemModel(parent); + Delegate* delegateFilter = new Delegate(parent); + for (int i = 0; i < values.size(); ++i) { + auto item = new QStandardItem(QString(filterHint+": %1").arg(values.value(i))); + item->setData(i); + modelFilter->appendRow(item); + } + delegateFilter->setValues(values); + comboBox->setModel(modelFilter); + comboBox->setItemDelegate(delegateFilter); + comboBox->setCurrentIndex(0); +} GovernanceWidget::GovernanceWidget(PIVXGUI* parent) : PWidget(parent), @@ -44,45 +56,20 @@ GovernanceWidget::GovernanceWidget(PIVXGUI* parent) : lineEdit->setFont(font); lineEdit->setAlignment(Qt::AlignRight); initComboBox(ui->comboBoxSort, lineEdit, "btn-combo", false); - - QStandardItemModel* model = new QStandardItemModel(this); - Delegate* delegate = new Delegate(this); - QList values; // todo: Add sort actions - values.append("Date"); - values.append("Value"); - values.append("Name"); - for (const auto& value : values) { - model->appendRow(new QStandardItem(tr("Sort by: %1").arg(value))); - } - delegate->setValues(values); - ui->comboBoxSort->setModel(model); - ui->comboBoxSort->setItemDelegate(delegate); - ui->comboBoxSort->setVisible(false); + QList values{tr("Date"), tr("Amount"), tr("Name")}; + initComboView(this, ui->comboBoxSort, tr("Sort by"), values); + ui->comboBoxSort->setVisible(false); // Future: add sort actions // Filter SortEdit* lineEditFilter = new SortEdit(ui->comboBoxFilter); lineEditFilter->setFont(font); initComboBox(ui->comboBoxFilter, lineEditFilter, "btn-filter", false); - - QStandardItemModel* modelFilter = new QStandardItemModel(this); - Delegate* delegateFilter = new Delegate(this); - QList valuesFilter; - for (int i = 0; i < propFilter.size(); ++i) { - QString str = QString::fromStdString(propFilter[i]); - valuesFilter.append(str); - auto item = new QStandardItem(tr("Filter: %1").arg(str)); - item->setData(i); - modelFilter->appendRow(item); - } - delegateFilter->setValues(valuesFilter); - ui->comboBoxFilter->setModel(modelFilter); - ui->comboBoxFilter->setItemDelegate(delegateFilter); - ui->comboBoxFilter->setCurrentIndex(0); + QList valuesFilter{tr("All"), tr("Passing"), tr("Not Passing"), tr("Waiting")}; + initComboView(this, ui->comboBoxFilter, tr("Filter"), valuesFilter); connect(ui->comboBoxFilter, static_cast(&QComboBox::currentTextChanged), this, &GovernanceWidget::onFilterChanged); // Budget - ui->labelBudget->setText("Budget Distribution"); setCssProperty(ui->labelBudget, "btn-title-grey"); setCssProperty(ui->labelBudgetSubTitle, "text-subtitle"); setCssProperty(ui->labelAvailableTitle, "label-budget-text"); diff --git a/src/qt/pivx/governancewidget.h b/src/qt/pivx/governancewidget.h index 72fa8648acb2..2e1c2fa55381 100644 --- a/src/qt/pivx/governancewidget.h +++ b/src/qt/pivx/governancewidget.h @@ -34,7 +34,7 @@ class Delegate : public QStyledItemDelegate QStyledItemDelegate(parent) {} void setValues(QList _values) { - values = _values; + values = std::move(_values); } void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const override { From 702db9f9ffcb744c78fc1b2bda94e1cc751ae8f7 Mon Sep 17 00:00:00 2001 From: furszy Date: Mon, 13 Sep 2021 11:36:19 -0300 Subject: [PATCH 51/59] GUI governance: by default, do not show finished proposals that haven't been cleared from the backend yet. --- src/budget/budgetproposal.cpp | 2 +- src/qt/pivx/governancemodel.cpp | 12 ++++++++---- src/qt/pivx/governancemodel.h | 14 +++++++------- src/qt/pivx/proposalcard.cpp | 24 +++++++++++++++--------- src/qt/pivx/proposalcard.h | 2 ++ 5 files changed, 33 insertions(+), 21 deletions(-) diff --git a/src/budget/budgetproposal.cpp b/src/budget/budgetproposal.cpp index ba383511d161..8a189359c6bc 100644 --- a/src/budget/budgetproposal.cpp +++ b/src/budget/budgetproposal.cpp @@ -317,7 +317,7 @@ int CBudgetProposal::GetTotalPaymentCount() const int CBudgetProposal::GetRemainingPaymentCount(int nCurrentHeight) const { - // If this budget starts in the future, this value will be wrong + // If the proposal is already finished (passed the end block cycle), the payments value will be negative int nPayments = (GetBlockEndCycle() - GetBlockCycle(nCurrentHeight)) / Params().GetConsensus().nBudgetCycleBlocks - 1; // Take the lowest value return std::min(nPayments, GetTotalPaymentCount()); diff --git a/src/qt/pivx/governancemodel.cpp b/src/qt/pivx/governancemodel.cpp index cf27aaf1150e..61ffa9a6958a 100644 --- a/src/qt/pivx/governancemodel.cpp +++ b/src/qt/pivx/governancemodel.cpp @@ -37,13 +37,16 @@ ProposalInfo GovernanceModel::buildProposalInfo(const CBudgetProposal* prop, boo // Calculate status int votesYes = prop->GetYeas(); int votesNo = prop->GetNays(); + int remainingPayments = prop->GetRemainingPaymentCount(clientModel->getLastBlockProcessedHeight()); ProposalInfo::Status status; if (isPending) { // Proposal waiting for confirmation to be broadcasted. status = ProposalInfo::WAITING_FOR_APPROVAL; } else { - if (isPassing) { + if (remainingPayments <= 0) { + status = ProposalInfo::FINISHED; + } if (isPassing) { status = ProposalInfo::PASSING; } else if (votesYes > votesNo) { status = ProposalInfo::PASSING_NOT_FUNDED; @@ -52,7 +55,6 @@ ProposalInfo GovernanceModel::buildProposalInfo(const CBudgetProposal* prop, boo } } - return ProposalInfo(prop->GetHash(), prop->GetName(), prop->GetURL(), @@ -61,11 +63,11 @@ ProposalInfo GovernanceModel::buildProposalInfo(const CBudgetProposal* prop, boo Standard::EncodeDestination(recipient), prop->GetAmount(), prop->GetTotalPaymentCount(), - prop->GetRemainingPaymentCount(clientModel->getLastBlockProcessedHeight()), + remainingPayments, status); } -std::list GovernanceModel::getProposals(const ProposalInfo::Status* filterByStatus) +std::list GovernanceModel::getProposals(const ProposalInfo::Status* filterByStatus, bool filterFinished) { if (!clientModel) return {}; std::list ret; @@ -74,6 +76,8 @@ std::list GovernanceModel::getProposals(const ProposalInfo::Status for (const auto& prop : g_budgetman.GetAllProposalsOrdered()) { bool isPassing = std::find(budget.begin(), budget.end(), *prop) != budget.end(); ProposalInfo propInfo = buildProposalInfo(prop, isPassing, false); + + if (filterFinished && propInfo.isFinished()) continue; if (!filterByStatus || propInfo.status == *filterByStatus) { ret.emplace_back(propInfo); } diff --git a/src/qt/pivx/governancemodel.h b/src/qt/pivx/governancemodel.h index d7384ae33ecf..3473edf35ce1 100644 --- a/src/qt/pivx/governancemodel.h +++ b/src/qt/pivx/governancemodel.h @@ -22,7 +22,8 @@ struct ProposalInfo { WAITING_FOR_APPROVAL, PASSING, PASSING_NOT_FUNDED, - NOT_PASSING + NOT_PASSING, + FINISHED }; /** Proposal hash */ @@ -51,10 +52,8 @@ struct ProposalInfo { recipientAdd(std::move(_recipientAdd)), amount(_amount), totalPayments(_totalPayments), remainingPayments(_remainingPayments), status(_status) {} - bool operator==(const ProposalInfo& prop2) const - { - return id == prop2.id; - } + bool operator==(const ProposalInfo& prop2) const { return id == prop2.id; } + bool isFinished() { return status == Status::FINISHED; } }; struct VoteInfo { @@ -88,8 +87,9 @@ class GovernanceModel : public QObject ~GovernanceModel() override; void setWalletModel(WalletModel* _walletModel); - // Return proposals ordered by net votes - std::list getProposals(const ProposalInfo::Status* filterByStatus = nullptr); + // Return proposals ordered by net votes. + // By default, do not return zombie finished proposals that haven't been cleared yet (backend removal sources need a cleanup). + std::list getProposals(const ProposalInfo::Status* filterByStatus = nullptr, bool filterFinished = true); // Returns true if there is at least one proposal cached bool hasProposals(); // Whether a visual refresh is needed diff --git a/src/qt/pivx/proposalcard.cpp b/src/qt/pivx/proposalcard.cpp index 362608a67ad6..e0233ba779b2 100644 --- a/src/qt/pivx/proposalcard.cpp +++ b/src/qt/pivx/proposalcard.cpp @@ -34,41 +34,47 @@ ProposalCard::ProposalCard(QWidget *parent) : connect(ui->btnLink, &QPushButton::clicked, this, &ProposalCard::onCopyUrlClicked); } - void ProposalCard::setProposal(const ProposalInfo& _proposalInfo) { proposalInfo = _proposalInfo; ui->labelPropName->setText(QString::fromStdString(proposalInfo.name)); ui->labelPropAmount->setText(GUIUtil::formatBalance(proposalInfo.amount)); - ui->labelPropMonths->setText((proposalInfo.remainingPayments == 0) ? tr("Last month in course") : + ui->labelPropMonths->setText(proposalInfo.remainingPayments < 0 ? tr("Inactive proposal") : + proposalInfo.remainingPayments == 0 ? tr("Last month in course") : tr("%1 of %2 months left").arg(proposalInfo.remainingPayments).arg(proposalInfo.totalPayments)); double totalVotes = _proposalInfo.votesYes + _proposalInfo.votesNo; double percentageNo = (totalVotes == 0) ? 0 : (_proposalInfo.votesNo / totalVotes) * 100; double percentageYes = (totalVotes == 0) ? 0 : (_proposalInfo.votesYes / totalVotes) * 100; - ui->votesBar->setValue((int)percentageNo); ui->labelNo->setText(QString::number(percentageNo) + "% " + tr("No")); ui->labelYes->setText(tr("Yes") + " " + QString::number(percentageYes) + "%"); QString cssClassStatus; if (proposalInfo.status == ProposalInfo::WAITING_FOR_APPROVAL){ cssClassStatus = "card-status-no-votes"; - ui->labelStatus->setText(tr("Waiting")); - ui->votesBar->setValue(50); + setStatusAndVotes(tr("Waiting"), 50); + } else if (proposalInfo.status == ProposalInfo::FINISHED) { + cssClassStatus = "card-status-no-votes"; + setStatusAndVotes(tr("Finished"), 50); } else if (totalVotes == 0) { cssClassStatus = "card-status-no-votes"; - ui->labelStatus->setText(tr("No Votes")); - ui->votesBar->setValue(50); + setStatusAndVotes(tr("No Votes"), 50); } else if (proposalInfo.status == ProposalInfo::NOT_PASSING || proposalInfo.status == ProposalInfo::PASSING_NOT_FUNDED) { cssClassStatus = "card-status-not-passing"; - ui->labelStatus->setText(tr("Not Passing")); + setStatusAndVotes(tr("Not Passing"), (int)percentageNo); } else if (proposalInfo.status == ProposalInfo::PASSING) { cssClassStatus = "card-status-passing"; - ui->labelStatus->setText(tr("Passing")); + setStatusAndVotes(tr("Passing"), (int)percentageNo); } setCssProperty(ui->labelStatus, cssClassStatus, true); } +void ProposalCard::setStatusAndVotes(const QString& msg, int value) +{ + ui->labelStatus->setText(msg); + ui->votesBar->setValue(value); +} + void ProposalCard::onCopyUrlClicked() { GUIUtil::setClipboard(QString::fromStdString(proposalInfo.url)); diff --git a/src/qt/pivx/proposalcard.h b/src/qt/pivx/proposalcard.h index f8c92f3e7d2b..08609a21acf0 100644 --- a/src/qt/pivx/proposalcard.h +++ b/src/qt/pivx/proposalcard.h @@ -41,6 +41,8 @@ public Q_SLOTS: Ui::ProposalCard *ui; ProposalInfo proposalInfo; bool needsUpdate{false}; + + void setStatusAndVotes(const QString& msg, int value); }; #endif // PROPOSALCARD_H From bc01677c2cf9afbe461a4c3e44aa94f6974121bb Mon Sep 17 00:00:00 2001 From: furszy Date: Tue, 14 Sep 2021 18:30:18 -0300 Subject: [PATCH 52/59] GUI: add proposal card menu. --- src/qt/pivx/forms/proposalcard.ui | 26 +++++++-------- src/qt/pivx/governancewidget.cpp | 54 ++++++++++++++++++++++++++++++- src/qt/pivx/governancewidget.h | 9 ++++++ src/qt/pivx/proposalcard.cpp | 5 ++- src/qt/pivx/proposalcard.h | 1 + 5 files changed, 78 insertions(+), 17 deletions(-) diff --git a/src/qt/pivx/forms/proposalcard.ui b/src/qt/pivx/forms/proposalcard.ui index c32bf1530ce3..91712ee2ca2a 100644 --- a/src/qt/pivx/forms/proposalcard.ui +++ b/src/qt/pivx/forms/proposalcard.ui @@ -83,6 +83,19 @@ + + + + Qt::Horizontal + + + + 40 + 20 + + + + @@ -99,19 +112,6 @@ - - - - Qt::Horizontal - - - - 40 - 20 - - - - diff --git a/src/qt/pivx/governancewidget.cpp b/src/qt/pivx/governancewidget.cpp index 81febc2561d2..2e8d64cf4537 100644 --- a/src/qt/pivx/governancewidget.cpp +++ b/src/qt/pivx/governancewidget.cpp @@ -7,7 +7,7 @@ #include "qt/pivx/qtutils.h" #include "qt/pivx/votedialog.h" -#include +#include #include #include @@ -166,6 +166,56 @@ void GovernanceWidget::onCreatePropClicked() dialog->deleteLater(); } +void GovernanceWidget::onMenuClicked(ProposalCard* card) +{ + if (!propMenu) { + propMenu = new TooltipMenu(window, this); + propMenu->setCopyBtnText(tr("Copy Url")); + propMenu->setEditBtnText(tr("Open Url")); + propMenu->setDeleteBtnText(tr("More Info")); + propMenu->setMaximumWidth(propMenu->maximumWidth() + 5); + propMenu->setFixedWidth(propMenu->width() + 5); + connect(propMenu, &TooltipMenu::message, this, &GovernanceWidget::message); + connect(propMenu, &TooltipMenu::onCopyClicked, this, &GovernanceWidget::onCopyUrl); + connect(propMenu, &TooltipMenu::onEditClicked, this, &GovernanceWidget::onOpenClicked); + connect(propMenu, &TooltipMenu::onDeleteClicked, this, &GovernanceWidget::onMoreInfoClicked); + } else { + propMenu->hide(); + } + menuCard = card; + QRect rect = card->geometry(); + QPoint pos = rect.topRight(); + pos.setX(pos.x() - 22); + pos.setY(pos.y() + (isSync ? 100 : 140)); + propMenu->move(pos); + propMenu->show(); +} + +void GovernanceWidget::onCopyUrl() +{ + if (!menuCard) return; + GUIUtil::setClipboard(QString::fromStdString(menuCard->getProposal().url)); + inform(tr("Proposal URL copied to clipboard")); +} + +void GovernanceWidget::onOpenClicked() +{ + if (!menuCard) return; + if (ask(tr("Open Proposal URL"), + tr("The following URL will be opened in the default browser") + "\n\n" + + QString::fromStdString(menuCard->getProposal().url) + "\n\n" + + tr("Are you sure?\n(Always verify the URL validity before opening it)\n"))) { + if (!QDesktopServices::openUrl(QUrl(QString::fromStdString(menuCard->getProposal().url)))) { + inform(tr("Failed to open proposal URL")); + } + } +} + +void GovernanceWidget::onMoreInfoClicked() +{ + // add proposal info dialog. +} + void GovernanceWidget::loadClientModel() { connect(clientModel, &ClientModel::numBlocksChanged, this, &GovernanceWidget::chainHeightChanged); @@ -246,6 +296,7 @@ ProposalCard* GovernanceWidget::newCard() ProposalCard* propCard = new ProposalCard(ui->scrollAreaWidgetContents); connect(propCard, &ProposalCard::voteClicked, this, &GovernanceWidget::onVoteForPropClicked); connect(propCard, &ProposalCard::inform, this, &GovernanceWidget::inform); + connect(propCard, &ProposalCard::onMenuClicked, this, &GovernanceWidget::onMenuClicked); setCardShadow(propCard); return propCard; } @@ -318,6 +369,7 @@ void GovernanceWidget::refreshCardsGrid(bool forceRefresh) } gridLayout->takeAt(gridLayout->indexOf(card)); it = cards.erase(it); + if (card == menuCard) menuCard = nullptr; delete card; } } diff --git a/src/qt/pivx/governancewidget.h b/src/qt/pivx/governancewidget.h index 2e1c2fa55381..24aa122017f5 100644 --- a/src/qt/pivx/governancewidget.h +++ b/src/qt/pivx/governancewidget.h @@ -25,6 +25,7 @@ QT_END_NAMESPACE class MNModel; class PIVXGUI; class GovernanceModel; +class TooltipMenu; class Delegate : public QStyledItemDelegate { @@ -73,6 +74,11 @@ public Q_SLOTS: void chainHeightChanged(int height); void onVoteForPropClicked(const ProposalInfo& proposalInfo); void onCreatePropClicked(); + void onMenuClicked(ProposalCard* card); + // + void onCopyUrl(); + void onOpenClicked(); + void onMoreInfoClicked(); private: Ui::governancewidget *ui; @@ -84,6 +90,9 @@ public Q_SLOTS: int propsPerRow = 0; QTimer* refreshTimer{nullptr}; + TooltipMenu* propMenu{nullptr}; + ProposalCard* menuCard{nullptr}; + // Proposals filter Optional statusFilter{nullopt}; diff --git a/src/qt/pivx/proposalcard.cpp b/src/qt/pivx/proposalcard.cpp index e0233ba779b2..845a655016d2 100644 --- a/src/qt/pivx/proposalcard.cpp +++ b/src/qt/pivx/proposalcard.cpp @@ -20,7 +20,7 @@ ProposalCard::ProposalCard(QWidget *parent) : setCssProperty(ui->labelPropMonths, "card-time"); setCssProperty(ui->labelStatus, "card-status-passing"); setCssProperty(ui->btnVote, "card-btn-vote"); - setCssProperty(ui->btnLink, "btn-link"); + setCssProperty(ui->btnLink, "btn-menu"); setCssProperty(ui->containerVotes, "card-progress-box"); ui->containerVotes->setContentsMargins(1,1,1,1); ui->containerVotes->layout()->setMargin(0); @@ -77,8 +77,7 @@ void ProposalCard::setStatusAndVotes(const QString& msg, int value) void ProposalCard::onCopyUrlClicked() { - GUIUtil::setClipboard(QString::fromStdString(proposalInfo.url)); - Q_EMIT inform(tr("Proposal URL copied to clipboard")); + Q_EMIT onMenuClicked(this); } ProposalCard::~ProposalCard() diff --git a/src/qt/pivx/proposalcard.h b/src/qt/pivx/proposalcard.h index 08609a21acf0..fc03357fff50 100644 --- a/src/qt/pivx/proposalcard.h +++ b/src/qt/pivx/proposalcard.h @@ -36,6 +36,7 @@ public Q_SLOTS: Q_SIGNALS: void voteClicked(const ProposalInfo& proposalInfo); void inform(const QString& text); + void onMenuClicked(ProposalCard* card); private: Ui::ProposalCard *ui; From 6c404df482d2a9440de50adf5c3176d996fda53b Mon Sep 17 00:00:00 2001 From: furszy Date: Tue, 14 Sep 2021 22:25:49 -0300 Subject: [PATCH 53/59] GUI: Check time between MN votes and inform if it didn't pass the minimum vote update time. --- src/qt/pivx/governancemodel.cpp | 7 ++++++- src/qt/pivx/governancemodel.h | 6 +++++- src/qt/pivx/votedialog.cpp | 11 +++++++++++ 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/src/qt/pivx/governancemodel.cpp b/src/qt/pivx/governancemodel.cpp index 61ffa9a6958a..c9a803b31967 100644 --- a/src/qt/pivx/governancemodel.cpp +++ b/src/qt/pivx/governancemodel.cpp @@ -109,6 +109,11 @@ int GovernanceModel::getNumBlocksPerBudgetCycle() const return Params().GetConsensus().nBudgetCycleBlocks; } +int GovernanceModel::getProposalVoteUpdateMinTime() const +{ + return BUDGET_VOTE_UPDATE_MIN; +} + int GovernanceModel::getPropMaxPaymentsCount() const { return Params().GetConsensus().nMaxProposalPayments; @@ -147,7 +152,7 @@ std::vector GovernanceModel::getLocalMNsVotesForProposal(const Proposa for (const auto& it : mapVotes) { for (const auto& mn : vecLocalMn) { if (it.first == mn.first && it.second.IsValid()) { - localVotes.emplace_back(mn.first, (VoteInfo::VoteDirection) it.second.GetDirection(), mn.second); + localVotes.emplace_back(mn.first, (VoteInfo::VoteDirection) it.second.GetDirection(), mn.second, it.second.GetTime()); break; } } diff --git a/src/qt/pivx/governancemodel.h b/src/qt/pivx/governancemodel.h index 3473edf35ce1..5649588d640b 100644 --- a/src/qt/pivx/governancemodel.h +++ b/src/qt/pivx/governancemodel.h @@ -63,10 +63,12 @@ struct VoteInfo { NO=2 }; - explicit VoteInfo(const COutPoint _mnId, VoteDirection _vote, std::string _mnAlias) : mnVoter(_mnId), vote(_vote), mnAlias(_mnAlias) {} + explicit VoteInfo(const COutPoint _mnId, VoteDirection _vote, std::string _mnAlias, int64_t _time) : + mnVoter(_mnId), vote(_vote), mnAlias(_mnAlias), time(_time) {} COutPoint mnVoter; VoteDirection vote; std::string mnAlias; + int64_t time; }; class CBudgetProposal; @@ -96,6 +98,8 @@ class GovernanceModel : public QObject bool isRefreshNeeded() { return refreshNeeded; } // Return the number of blocks per budget cycle int getNumBlocksPerBudgetCycle() const; + // Return the minimum time when an MN can update a vote for a proposal + int getProposalVoteUpdateMinTime() const; // Return the budget maximum available amount for the running chain CAmount getMaxAvailableBudgetAmount() const; // Return the proposal maximum payments count for the running chain diff --git a/src/qt/pivx/votedialog.cpp b/src/qt/pivx/votedialog.cpp index b02dc4466e0a..ab8975178ea6 100644 --- a/src/qt/pivx/votedialog.cpp +++ b/src/qt/pivx/votedialog.cpp @@ -84,6 +84,17 @@ void VoteDialog::onAcceptClicked() return; } + // Check time between votes. + for (const auto& vote : votes) { + auto it = std::find(vecSelectedMn.begin(), vecSelectedMn.end(), vote.mnAlias); + if (it != vecSelectedMn.end()) { + if (vote.time + govModel->getProposalVoteUpdateMinTime() > GetAdjustedTime()) { + inform(tr("Time between votes is too soon, have to wait %1 minutes").arg(govModel->getProposalVoteUpdateMinTime()/60)); + return; + } + } + } + // Craft and broadcast vote auto res = govModel->voteForProposal(*proposal, isPositive, vecSelectedMn); if (!res) { From 162d84381bcbf24a9dc9c8e5ab2cefb353aa75fc Mon Sep 17 00:00:00 2001 From: furszy Date: Thu, 16 Sep 2021 11:51:30 -0300 Subject: [PATCH 54/59] GUI: Implement and connect proposal information dialog. --- src/Makefile.qt.include | 4 + src/qt/CMakeLists.txt | 1 + src/qt/pivx/forms/proposalinfodialog.ui | 804 ++++++++++++++++++++++++ src/qt/pivx/governancemodel.cpp | 23 +- src/qt/pivx/governancemodel.h | 10 +- src/qt/pivx/governancewidget.cpp | 16 +- src/qt/pivx/governancewidget.h | 1 + src/qt/pivx/proposalinfodialog.cpp | 82 +++ src/qt/pivx/proposalinfodialog.h | 40 ++ src/qt/pivx/tooltipmenu.cpp | 8 +- src/qt/pivx/tooltipmenu.h | 9 +- 11 files changed, 983 insertions(+), 15 deletions(-) create mode 100644 src/qt/pivx/forms/proposalinfodialog.ui create mode 100644 src/qt/pivx/proposalinfodialog.cpp create mode 100644 src/qt/pivx/proposalinfodialog.h diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include index 3b6332fdd8af..78ef13d0bb18 100644 --- a/src/Makefile.qt.include +++ b/src/Makefile.qt.include @@ -54,6 +54,7 @@ QT_FORMS_UI = \ qt/pivx/forms/mnselectiondialog.ui \ qt/pivx/forms/votedialog.ui \ qt/pivx/forms/createproposaldialog.ui \ + qt/pivx/forms/proposalinfodialog.ui \ qt/pivx/forms/governancewidget.ui \ qt/pivx/settings/forms/settingsbackupwallet.ui \ qt/pivx/settings/forms/settingsexportcsv.ui \ @@ -145,6 +146,7 @@ QT_MOC_CPP = \ qt/pivx/moc_mnselectiondialog.cpp \ qt/pivx/moc_votedialog.cpp \ qt/pivx/moc_createproposaldialog.cpp \ + qt/pivx/moc_proposalinfodialog.cpp \ qt/pivx/moc_governancewidget.cpp \ qt/pivx/settings/moc_settingsbackupwallet.cpp \ qt/pivx/settings/moc_settingsexportcsv.cpp \ @@ -264,6 +266,7 @@ BITCOIN_QT_H = \ qt/pivx/mnselectiondialog.h \ qt/pivx/votedialog.h \ qt/pivx/createproposaldialog.h \ + qt/pivx/proposalinfodialog.h \ qt/pivx/governancewidget.h \ qt/pivx/settings/settingsbackupwallet.h \ qt/pivx/settings/settingsexportcsv.h \ @@ -604,6 +607,7 @@ BITCOIN_QT_WALLET_CPP = \ qt/pivx/mnselectiondialog.cpp \ qt/pivx/votedialog.cpp \ qt/pivx/createproposaldialog.cpp \ + qt/pivx/proposalinfodialog.cpp \ qt/pivx/governancewidget.cpp \ qt/pivx/settings/settingsbackupwallet.cpp \ qt/pivx/settings/settingsexportcsv.cpp \ diff --git a/src/qt/CMakeLists.txt b/src/qt/CMakeLists.txt index 1fbf344f330c..87eec59fdc8d 100644 --- a/src/qt/CMakeLists.txt +++ b/src/qt/CMakeLists.txt @@ -149,6 +149,7 @@ SET(QT_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/pivx/proposalcard.cpp ${CMAKE_CURRENT_SOURCE_DIR}/pivx/votedialog.cpp ${CMAKE_CURRENT_SOURCE_DIR}/pivx/createproposaldialog.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/pivx/proposalinfodialog.cpp ${CMAKE_CURRENT_SOURCE_DIR}/pivx/governancewidget.cpp ${CMAKE_CURRENT_SOURCE_DIR}/pivx/settings/settingsbackupwallet.cpp ${CMAKE_CURRENT_SOURCE_DIR}/pivx/settings/settingsbittoolwidget.cpp diff --git a/src/qt/pivx/forms/proposalinfodialog.ui b/src/qt/pivx/forms/proposalinfodialog.ui new file mode 100644 index 000000000000..fe2a63b1d349 --- /dev/null +++ b/src/qt/pivx/forms/proposalinfodialog.ui @@ -0,0 +1,804 @@ + + + ProposalInfoDialog + + + + 0 + 0 + 604 + 543 + + + + + 0 + 0 + + + + + 580 + 300 + + + + Proposal Information + + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 0 + 0 + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + 0 + + + 0 + + + + + 0 + + + 20 + + + 10 + + + 20 + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 0 + 40 + + + + + 16777215 + 40 + + + + padding-left:24px; + + + Proposal Information + + + Qt::AlignCenter + + + 7 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 24 + 24 + + + + + 24 + 24 + + + + Qt::NoFocus + + + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 20 + 10 + + + + + + + + + 0 + 450 + + + + + 0 + + + 0 + + + 0 + + + + + #scrollArea { +background:transparent; +} + + + Qt::ScrollBarAlwaysOff + + + QAbstractScrollArea::AdjustIgnored + + + true + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop + + + + + 0 + 0 + 600 + 439 + + + + false + + + #scrollAreaWidgetContents { +background:transparent; +} + + + + 0 + + + QLayout::SetDefaultConstraint + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + 16 + + + 0 + + + 16 + + + + + + 0 + + + 0 + + + + + + + + 16777215 + 16777215 + + + + ID + + + + + + + N/A + + + + + + + + + + 34 + 34 + + + + + 34 + 34 + + + + Qt::NoFocus + + + + + + + 24 + 24 + + + + + + + + + + + + 16777215 + 1 + + + + background-color:#bababa; + + + + + + + + + + + 0 + + + 0 + + + + + + + + 16777215 + 16777215 + + + + Proposal Name + + + + + + + N/A + + + + + + + + + + 34 + 34 + + + + + 34 + 34 + + + + Qt::NoFocus + + + + + + + 24 + 24 + + + + + + + + + + + + 16777215 + 1 + + + + background-color:#bababa; + + + + + + + + + + + 0 + + + 0 + + + + + N/A + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + N/A + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 16777215 + 16777215 + + + + Recipient + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + 16777215 + 16777215 + + + + Amount + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + 16777215 + 1 + + + + background-color:#bababa; + + + + + + + + + + + 0 + + + 0 + + + + + Passing + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 16777215 + 16777215 + + + + End Block + + + Qt::AlignCenter + + + + + + + + 16777215 + 16777215 + + + + Status + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + N/A + + + Qt::AlignCenter + + + + + + + + 16777215 + 16777215 + + + + Start Block + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + N/A + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + + + + 16777215 + 1 + + + + background-color:#bababa; + + + + + + + + + + + 0 + + + 0 + + + + + + + + 16777215 + 16777215 + + + + URL + + + + + + + N/A + + + + + + + + + + 34 + 34 + + + + + 34 + 34 + + + + Qt::NoFocus + + + + + + + 24 + 24 + + + + + + + + + + + + 16777215 + 1 + + + + background-color:#bababa; + + + + + + + + + + 9 + + + 9 + + + + + 0 + + + 0 + + + + + + 16777215 + 16777215 + + + + Positive Votes + + + + + + + N/A + + + + + + + + + + 0 + + + 0 + + + 0 + + + + + + 16777215 + 16777215 + + + + Negative Votes + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + N/A + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + + + 16777215 + 1 + + + + background-color:#bababa; + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/qt/pivx/governancemodel.cpp b/src/qt/pivx/governancemodel.cpp index c9a803b31967..24bc3e158842 100644 --- a/src/qt/pivx/governancemodel.cpp +++ b/src/qt/pivx/governancemodel.cpp @@ -20,6 +20,23 @@ #include #include +std::string ProposalInfo::statusToStr() const +{ + switch(status) { + case WAITING_FOR_APPROVAL: + return _("Waiting"); + case PASSING: + return _("Passing"); + case PASSING_NOT_FUNDED: + return _("Passing not funded"); + case NOT_PASSING: + return _("Not Passing"); + case FINISHED: + return _("Finished"); + } + return ""; +} + GovernanceModel::GovernanceModel(ClientModel* _clientModel, MNModel* _mnModel) : clientModel(_clientModel), mnModel(_mnModel) {} GovernanceModel::~GovernanceModel() {} @@ -46,7 +63,7 @@ ProposalInfo GovernanceModel::buildProposalInfo(const CBudgetProposal* prop, boo } else { if (remainingPayments <= 0) { status = ProposalInfo::FINISHED; - } if (isPassing) { + } else if (isPassing) { status = ProposalInfo::PASSING; } else if (votesYes > votesNo) { status = ProposalInfo::PASSING_NOT_FUNDED; @@ -64,7 +81,9 @@ ProposalInfo GovernanceModel::buildProposalInfo(const CBudgetProposal* prop, boo prop->GetAmount(), prop->GetTotalPaymentCount(), remainingPayments, - status); + status, + prop->GetBlockStart(), + prop->GetBlockEnd()); } std::list GovernanceModel::getProposals(const ProposalInfo::Status* filterByStatus, bool filterFinished) diff --git a/src/qt/pivx/governancemodel.h b/src/qt/pivx/governancemodel.h index 5649588d640b..4b3513f264b6 100644 --- a/src/qt/pivx/governancemodel.h +++ b/src/qt/pivx/governancemodel.h @@ -42,18 +42,24 @@ struct ProposalInfo { int remainingPayments; /** Proposal state */ Status status; + /** Start superblock height */ + int startBlock; + /** End superblock height */ + int endBlock; ProposalInfo() {} explicit ProposalInfo(const uint256& _id, std::string _name, std::string _url, int _votesYes, int _votesNo, std::string _recipientAdd, CAmount _amount, int _totalPayments, int _remainingPayments, - Status _status) : + Status _status, int _startBlock, int _endBlock) : id(_id), name(std::move(_name)), url(std::move(_url)), votesYes(_votesYes), votesNo(_votesNo), recipientAdd(std::move(_recipientAdd)), amount(_amount), totalPayments(_totalPayments), - remainingPayments(_remainingPayments), status(_status) {} + remainingPayments(_remainingPayments), status(_status), startBlock(_startBlock), + endBlock(_endBlock) {} bool operator==(const ProposalInfo& prop2) const { return id == prop2.id; } bool isFinished() { return status == Status::FINISHED; } + std::string statusToStr() const; }; struct VoteInfo { diff --git a/src/qt/pivx/governancewidget.cpp b/src/qt/pivx/governancewidget.cpp index 2e8d64cf4537..1a0114ecb98f 100644 --- a/src/qt/pivx/governancewidget.cpp +++ b/src/qt/pivx/governancewidget.cpp @@ -4,6 +4,7 @@ #include "qt/pivx/createproposaldialog.h" #include "qt/pivx/governancemodel.h" #include "qt/pivx/mnmodel.h" +#include "qt/pivx/proposalinfodialog.h" #include "qt/pivx/qtutils.h" #include "qt/pivx/votedialog.h" @@ -213,7 +214,11 @@ void GovernanceWidget::onOpenClicked() void GovernanceWidget::onMoreInfoClicked() { - // add proposal info dialog. + window->showHide(true); + ProposalInfoDialog* dialog = new ProposalInfoDialog(window); + dialog->setProposal(menuCard->getProposal()); + openDialogWithOpaqueBackgroundY(dialog, window, 4.5, ui->left->height() < 700 ? 12 : 5); + dialog->deleteLater(); } void GovernanceWidget::loadClientModel() @@ -261,6 +266,13 @@ void GovernanceWidget::hideEvent(QHideEvent *event) refreshTimer->stop(); } +void GovernanceWidget::wheelEvent(QWheelEvent* event) +{ + if (propMenu && propMenu->isVisible()) { + propMenu->hide(); + } +} + void GovernanceWidget::resizeEvent(QResizeEvent *event) { if (!isVisible()) return; @@ -394,4 +406,4 @@ void GovernanceWidget::tierTwoSynced(bool sync) if (!isVisible()) return; tryGridRefresh(); } -} \ No newline at end of file +} diff --git a/src/qt/pivx/governancewidget.h b/src/qt/pivx/governancewidget.h index 24aa122017f5..2ebd946d0cf1 100644 --- a/src/qt/pivx/governancewidget.h +++ b/src/qt/pivx/governancewidget.h @@ -62,6 +62,7 @@ class GovernanceWidget : public PWidget void showEvent(QShowEvent *event) override; void hideEvent(QHideEvent *event) override; + void wheelEvent(QWheelEvent* event) override; void resizeEvent(QResizeEvent *event) override; void loadClientModel() override; void loadWalletModel() override; diff --git a/src/qt/pivx/proposalinfodialog.cpp b/src/qt/pivx/proposalinfodialog.cpp new file mode 100644 index 000000000000..d11ec7dfb015 --- /dev/null +++ b/src/qt/pivx/proposalinfodialog.cpp @@ -0,0 +1,82 @@ +// Copyright (c) 2019-2020 The PIVX developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "qt/pivx/proposalinfodialog.h" +#include "qt/pivx/forms/ui_proposalinfodialog.h" + +#include "guiutil.h" +#include "qt/pivx/snackbar.h" +#include "qt/pivx/qtutils.h" + +#include + +ProposalInfoDialog::ProposalInfoDialog(QWidget *parent) : + FocusedDialog(parent), + ui(new Ui::ProposalInfoDialog) +{ + ui->setupUi(this); + this->setStyleSheet(parent->styleSheet()); + setCssProperty(ui->frame, "container-dialog"); + setCssProperty(ui->labelTitle, "text-title-dialog"); + setCssProperty({ui->labelAmount, ui->labelName, ui->labelUrl, ui->labelRecipient, ui->labelPosVotes, ui->labelId, ui->labelNegVotes, ui->labelEndDate, ui->labelDate, ui->labelStatus}, "text-subtitle"); + setCssProperty({ui->labelDividerID, ui->labelDividerChange, ui->labelDividerMemo}, "container-divider"); + setCssProperty({ui->textAmount, ui->textName, ui->textUrl, ui->textRecipient, ui->textPosVotes, ui->textId, ui->textNegVotes, ui->textEndDate, ui->textDate, ui->textStatus} , "text-body3-dialog"); + setCssProperty({ui->pushCopy, ui->btnUrlCopy, ui->btnNameCopy}, "ic-copy-big"); + setCssProperty(ui->btnEsc, "ic-close"); + connect(ui->btnEsc, &QPushButton::clicked, this, &ProposalInfoDialog::close); + connect(ui->pushCopy, &QPushButton::clicked, [this](){ + GUIUtil::setClipboard(QString::fromStdString(info.id.GetHex())); + inform("ID copied to clipboard"); + }); + connect(ui->btnUrlCopy, &QPushButton::clicked, [this](){ + GUIUtil::setClipboard(QString::fromStdString(info.url)); + inform("URL copied to clipboard"); + }); + connect(ui->btnNameCopy, &QPushButton::clicked, [this](){ + GUIUtil::setClipboard(QString::fromStdString(info.name)); + inform("URL copied to clipboard"); + }); +} + +void ProposalInfoDialog::setProposal(const ProposalInfo& _info) +{ + info = _info; + QString id{QString::fromStdString(info.id.GetHex())}; + ui->textId->setText(id.left(20)+"..."+id.right(20)); + ui->textName->setText(QString::fromStdString(info.name)); + ui->textUrl->setText(QString::fromStdString(info.url)); + ui->textRecipient->setText(QString::fromStdString(info.recipientAdd)); + ui->textNegVotes->setText(QString::number(info.votesNo)); + ui->textPosVotes->setText(QString::number(info.votesYes)); + ui->textAmount->setText(GUIUtil::formatBalance(info.amount)); + ui->textDate->setText(QString::number(info.startBlock)); + ui->textEndDate->setText(QString::number(info.endBlock)); + ui->textStatus->setText(info.statusToStr().c_str()); +} + +void ProposalInfoDialog::accept() +{ + if (snackBar && snackBar->isVisible()) snackBar->hide(); + QDialog::accept(); +} + +void ProposalInfoDialog::reject() +{ + if (snackBar && snackBar->isVisible()) snackBar->hide(); + QDialog::reject(); +} + +void ProposalInfoDialog::inform(const QString& msg) +{ + if (!snackBar) snackBar = new SnackBar(nullptr, this); + snackBar->setText(msg); + snackBar->resize(this->width(), snackBar->height()); + openDialog(snackBar, this); +} + +ProposalInfoDialog::~ProposalInfoDialog() +{ + delete snackBar; + delete ui; +} diff --git a/src/qt/pivx/proposalinfodialog.h b/src/qt/pivx/proposalinfodialog.h new file mode 100644 index 000000000000..110c4ffcda51 --- /dev/null +++ b/src/qt/pivx/proposalinfodialog.h @@ -0,0 +1,40 @@ +// Copyright (c) 2019-2020 The PIVX developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef PROPOSALINFODIALOG_H +#define PROPOSALINFODIALOG_H + +#include "qt/pivx/focuseddialog.h" +#include "qt/pivx/governancemodel.h" + +struct ProposalInfo; +class SnackBar; +class WalletModel; + +namespace Ui { +class ProposalInfoDialog; +} + +class ProposalInfoDialog : public FocusedDialog +{ + Q_OBJECT + +public: + explicit ProposalInfoDialog(QWidget *parent = nullptr); + ~ProposalInfoDialog(); + void setProposal(const ProposalInfo& info); + +public Q_SLOTS: + void accept() override; + void reject() override; + +private: + Ui::ProposalInfoDialog* ui; + SnackBar* snackBar{nullptr}; + ProposalInfo info; + + void inform(const QString& msg); +}; + +#endif // PROPOSALINFODIALOG_H diff --git a/src/qt/pivx/tooltipmenu.cpp b/src/qt/pivx/tooltipmenu.cpp index 2ca673048b3f..ab0014755cf9 100644 --- a/src/qt/pivx/tooltipmenu.cpp +++ b/src/qt/pivx/tooltipmenu.cpp @@ -23,19 +23,19 @@ TooltipMenu::TooltipMenu(PIVXGUI *_window, QWidget *parent) : connect(ui->btnLast, &QPushButton::clicked, this, &TooltipMenu::lastClicked); } -void TooltipMenu::setEditBtnText(QString btnText){ +void TooltipMenu::setEditBtnText(const QString& btnText){ ui->btnEdit->setText(btnText); } -void TooltipMenu::setDeleteBtnText(QString btnText){ +void TooltipMenu::setDeleteBtnText(const QString& btnText){ ui->btnDelete->setText(btnText); } -void TooltipMenu::setCopyBtnText(QString btnText){ +void TooltipMenu::setCopyBtnText(const QString& btnText){ ui->btnCopy->setText(btnText); } -void TooltipMenu::setLastBtnText(QString btnText, int minHeight){ +void TooltipMenu::setLastBtnText(const QString& btnText, int minHeight){ ui->btnLast->setText(btnText); ui->btnLast->setMinimumHeight(minHeight); } diff --git a/src/qt/pivx/tooltipmenu.h b/src/qt/pivx/tooltipmenu.h index 8b5d339ee65e..88be0ee3d9df 100644 --- a/src/qt/pivx/tooltipmenu.h +++ b/src/qt/pivx/tooltipmenu.h @@ -28,13 +28,12 @@ class TooltipMenu : public PWidget explicit TooltipMenu(PIVXGUI* _window, QWidget *parent = nullptr); ~TooltipMenu() override; - void setIndex(const QModelIndex &index); virtual void showEvent(QShowEvent *event) override; - void setEditBtnText(QString btnText); - void setDeleteBtnText(QString btnText); - void setCopyBtnText(QString btnText); - void setLastBtnText(QString btnText, int minHeight = 30); + void setEditBtnText(const QString& btnText); + void setDeleteBtnText(const QString& btnText); + void setCopyBtnText(const QString& btnText); + void setLastBtnText(const QString& btnText, int minHeight = 30); void setCopyBtnVisible(bool visible); void setDeleteBtnVisible(bool visible); void setEditBtnVisible(bool visible); From 759036b0787b5fd8fbd89455cae1bf12ac48f9d0 Mon Sep 17 00:00:00 2001 From: furszy Date: Thu, 16 Sep 2021 12:08:13 -0300 Subject: [PATCH 55/59] GUI: Create proposal dialog accepting decimal amounts. --- src/qt/pivx/createproposaldialog.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/qt/pivx/createproposaldialog.cpp b/src/qt/pivx/createproposaldialog.cpp index 7cce6285ec76..a6eddcb8720c 100644 --- a/src/qt/pivx/createproposaldialog.cpp +++ b/src/qt/pivx/createproposaldialog.cpp @@ -93,11 +93,11 @@ void CreateProposalDialog::setupPageTwo() setCssProperty(ui->labelTitleDest, "text-title-dialog"); setCssProperty(ui->labelMessageDest, "dialog-proposal-message"); setEditBoxStyle(ui->labelAmount, ui->lineEditAmount, "e.g 500 PIV"); - + setCssProperty(ui->labelMonths, "text-title"); setEditBoxStyle(ui->labelAddress, ui->lineEditAddress, "e.g D...something.."); setCssProperty(ui->lineEditAddress, "edit-primary-multi-book"); actAddrList = ui->lineEditAddress->addAction(QIcon("://ic-contact-arrow-down"), QLineEdit::TrailingPosition); - ui->lineEditAmount->setValidator(new QIntValidator(1, govModel->getMaxAvailableBudgetAmount() / 100000000, this)); + GUIUtil::setupAmountWidget(ui->lineEditAmount, this); setCssProperty(ui->lineEditMonths, "btn-spin-box"); setShadow(ui->lineEditMonths); ui->lineEditMonths->setAttribute(Qt::WA_MacShowFocusRect, false); @@ -153,6 +153,10 @@ void CreateProposalDialog::propAmountChanged(const QString& newText) return; } auto amount = GUIUtil::parseValue(newText); + if (amount > govModel->getMaxAvailableBudgetAmount()) { + ui->lineEditAmount->setText(newText.left(newText.size() - 1)); + return; + } setCssEditLine(ui->lineEditAmount, govModel->validatePropAmount(amount).getRes(), true); } @@ -337,7 +341,7 @@ void CreateProposalDialog::onAddrListClicked() this ); menuContacts->setWalletModel(walletModel, {AddressTableModel::Send, AddressTableModel::Receive}); - connect(menuContacts, &ContactsDropdown::contactSelected, [this](QString address, QString label) { + connect(menuContacts, &ContactsDropdown::contactSelected, [this](const QString& address, const QString& label) { ui->lineEditAddress->setText(address); }); From dae3767697e1d9b3282799e65cab0f8b727e9822 Mon Sep 17 00:00:00 2001 From: furszy Date: Thu, 16 Sep 2021 15:40:40 -0300 Subject: [PATCH 56/59] GUI: Create proposal dialog, add key event and show event listeners. --- src/qt/pivx/createproposaldialog.cpp | 42 ++++++++++++++++++++++------ src/qt/pivx/createproposaldialog.h | 4 ++- 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/src/qt/pivx/createproposaldialog.cpp b/src/qt/pivx/createproposaldialog.cpp index a6eddcb8720c..e5e7841dff7d 100644 --- a/src/qt/pivx/createproposaldialog.cpp +++ b/src/qt/pivx/createproposaldialog.cpp @@ -28,9 +28,9 @@ CreateProposalDialog::CreateProposalDialog(PIVXGUI* parent, GovernanceModel* _go ui(new Ui::CreateProposalDialog), govModel(_govModel), walletModel(_walletModel), - icConfirm1(new QPushButton()), - icConfirm2(new QPushButton()), - icConfirm3(new QPushButton()) + icConfirm1(new QPushButton(this)), + icConfirm2(new QPushButton(this)), + icConfirm3(new QPushButton(this)) { ui->setupUi(this); this->setStyleSheet(parent->styleSheet()); @@ -337,7 +337,7 @@ void CreateProposalDialog::onAddrListClicked() menuContacts = new ContactsDropdown( width, height, - static_cast(parent()), + dynamic_cast(parent()), this ); menuContacts->setWalletModel(walletModel, {AddressTableModel::Send, AddressTableModel::Receive}); @@ -356,13 +356,39 @@ void CreateProposalDialog::onAddrListClicked() menuContacts->setStyleSheet(this->styleSheet()); menuContacts->adjustSize(); - QPoint pos = ui->containerPage2->rect().bottomLeft(); - pos.setY(pos.y() + rowHeight * 2 - 20); - pos.setX(pos.x() + 74); // Add widget's fixed padding manually - menuContacts->move(pos); + QPoint position = ui->containerPage2->rect().bottomLeft(); + position.setY(position.y() + rowHeight * 2 - 20); + position.setX(position.x() + 74); // Add widget's fixed padding manually + menuContacts->move(position); menuContacts->show(); } +void CreateProposalDialog::keyPressEvent(QKeyEvent *e) +{ + if (e->type() == QEvent::KeyPress) { + QKeyEvent* ke = static_cast(e); + if (ke->key() == Qt::Key_Enter || ke->key() == Qt::Key_Return) onNextClicked(); + if (ke->key() == Qt::Key_Escape) reject(); + } +} + +void CreateProposalDialog::showEvent(QShowEvent *event) +{ + switch (pos) { + case 0: + if (ui->lineEditPropName) ui->lineEditPropName->setFocus(); + break; + case 1: + if (ui->lineEditAddress) ui->lineEditAddress->setFocus(); + break; + case 2: + if (ui->btnNext) ui->btnNext->setFocus(); + break; + default: + return; + } +} + void CreateProposalDialog::inform(const QString& text) { if (!snackBar) snackBar = new SnackBar(nullptr, this); diff --git a/src/qt/pivx/createproposaldialog.h b/src/qt/pivx/createproposaldialog.h index 085d9bde2c04..88f50ea9d281 100644 --- a/src/qt/pivx/createproposaldialog.h +++ b/src/qt/pivx/createproposaldialog.h @@ -25,7 +25,9 @@ class CreateProposalDialog : public QDialog public: explicit CreateProposalDialog(PIVXGUI* parent, GovernanceModel* _govModel, WalletModel* _walletModel); ~CreateProposalDialog() override; - +protected: + void keyPressEvent(QKeyEvent* e) override; + void showEvent(QShowEvent* e) override; public Q_SLOTS: void onNextClicked(); void onBackClicked(); From 1a4acb2e89659f0f8992048713811bea7f9f5bb8 Mon Sep 17 00:00:00 2001 From: furszy Date: Wed, 22 Sep 2021 17:15:44 -0300 Subject: [PATCH 57/59] GUI: Masternode creation wizard, set max MN name length. --- src/qt/pivx/forms/masternodewizarddialog.ui | 3 +++ src/qt/pivx/masternodewizarddialog.cpp | 6 +++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/qt/pivx/forms/masternodewizarddialog.ui b/src/qt/pivx/forms/masternodewizarddialog.ui index 4e650e0502f5..18ae34bd1a1b 100644 --- a/src/qt/pivx/forms/masternodewizarddialog.ui +++ b/src/qt/pivx/forms/masternodewizarddialog.ui @@ -818,6 +818,9 @@ + + 25 + e.g. user_masternode diff --git a/src/qt/pivx/masternodewizarddialog.cpp b/src/qt/pivx/masternodewizarddialog.cpp index db40ce781532..8721e7269d86 100644 --- a/src/qt/pivx/masternodewizarddialog.cpp +++ b/src/qt/pivx/masternodewizarddialog.cpp @@ -30,9 +30,9 @@ static inline QString formatHtmlContent(const QString& str) { MasterNodeWizardDialog::MasterNodeWizardDialog(WalletModel* model, ClientModel* _clientModel, QWidget *parent) : FocusedDialog(parent), ui(new Ui::MasterNodeWizardDialog), - icConfirm1(new QPushButton()), - icConfirm3(new QPushButton()), - icConfirm4(new QPushButton()), + icConfirm1(new QPushButton(this)), + icConfirm3(new QPushButton(this)), + icConfirm4(new QPushButton(this)), walletModel(model), clientModel(_clientModel) { From 36461daccdede14f7d624dcd93736058f7b2596d Mon Sep 17 00:00:00 2001 From: furszy Date: Thu, 23 Sep 2021 11:52:26 -0300 Subject: [PATCH 58/59] GUI: Vote for proposal, add previous votes information in the voters' selection dialog. --- src/qt/pivx/forms/mnselectiondialog.ui | 15 ++++++++----- src/qt/pivx/mnselectiondialog.cpp | 31 ++++++++++++++++++++------ src/qt/pivx/mnselectiondialog.h | 9 ++++++-- 3 files changed, 41 insertions(+), 14 deletions(-) diff --git a/src/qt/pivx/forms/mnselectiondialog.ui b/src/qt/pivx/forms/mnselectiondialog.ui index f2a2ccd1036c..67d5e61130b6 100644 --- a/src/qt/pivx/forms/mnselectiondialog.ui +++ b/src/qt/pivx/forms/mnselectiondialog.ui @@ -181,13 +181,13 @@ 0 - 60 + 50 0 - 60 + 50 0 @@ -286,7 +286,7 @@ - 90 + 92 36 @@ -319,10 +319,10 @@ - 60 + 50 - 60 + 50 @@ -342,6 +342,11 @@ Name + + + Vote + + Status diff --git a/src/qt/pivx/mnselectiondialog.cpp b/src/qt/pivx/mnselectiondialog.cpp index 2cd74a243666..8e7371237d38 100644 --- a/src/qt/pivx/mnselectiondialog.cpp +++ b/src/qt/pivx/mnselectiondialog.cpp @@ -26,11 +26,15 @@ MnSelectionDialog::MnSelectionDialog(QWidget *parent) : ui->treeWidget->setColumnWidth(COLUMN_CHECKBOX, colCheckBoxWidth_treeMode); ui->treeWidget->setColumnWidth(COLUMN_NAME, 110); + ui->treeWidget->setColumnWidth(COLUMN_VOTE, 50); ui->treeWidget->setColumnWidth(COLUMN_STATUS, 60); ui->treeWidget->header()->setStretchLastSection(true); ui->treeWidget->header()->setSectionResizeMode(1, QHeaderView::Stretch); ui->treeWidget->setRootIsDecorated(false); ui->treeWidget->setFocusPolicy(Qt::NoFocus); + ui->treeWidget->model()->setHeaderData(COLUMN_NAME, Qt::Horizontal, Qt::AlignLeft , Qt::TextAlignmentRole); + ui->treeWidget->model()->setHeaderData(COLUMN_VOTE, Qt::Horizontal, Qt::AlignHCenter , Qt::TextAlignmentRole); + ui->treeWidget->model()->setHeaderData(COLUMN_STATUS, Qt::Horizontal, Qt::AlignHCenter , Qt::TextAlignmentRole); connect(ui->btnEsc, &QPushButton::clicked, this, &MnSelectionDialog::close); connect(ui->btnCancel, &QPushButton::clicked, this, &MnSelectionDialog::close); @@ -44,10 +48,10 @@ void MnSelectionDialog::setModel(MNModel* _mnModel) mnModel = _mnModel; } -void MnSelectionDialog::setMnVoters(const std::vector& votes) +void MnSelectionDialog::setMnVoters(const std::vector& _votes) { - for (const auto& voter : votes) { - selectedMnList.emplace_back(voter.mnAlias); + for (const auto& vote : _votes) { + votes.emplace(vote.mnAlias, vote); } } @@ -102,14 +106,17 @@ void MnSelectionDialog::selectAll() void MnSelectionDialog::updateView() { ui->treeWidget->clear(); - ui->treeWidget->setEnabled(false); // performance, otherwise updateLabels would be called for every checked checkbox + ui->treeWidget->setEnabled(false); // performance, otherwise the labels update would be called for every checked checkbox QFlags flgCheckbox = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable; QFlags flgTristate = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable | Qt::ItemIsTristate; for (int i = 0; i < mnModel->rowCount(); ++i) { QString alias = mnModel->index(i, MNModel::ALIAS, QModelIndex()).data().toString(); QString status = mnModel->index(i, MNModel::STATUS, QModelIndex()).data().toString(); - appendItem(flgCheckbox, flgTristate, alias, status); + VoteInfo* ptrVoteInfo{nullptr}; + auto it = votes.find(alias.toStdString()); + if (it != votes.end()) { ptrVoteInfo = &it->second; } + appendItem(flgCheckbox, flgTristate, alias, status, ptrVoteInfo); } // save COLUMN_CHECKBOX width for tree-mode @@ -123,7 +130,8 @@ void MnSelectionDialog::updateView() void MnSelectionDialog::appendItem(QFlags flgCheckbox, QFlags flgTristate, const QString& mnName, - const QString& mnStatus) + const QString& mnStatus, + VoteInfo* ptrVoteInfo) { QTreeWidgetItem* itemOutput = new QTreeWidgetItem(ui->treeWidget); itemOutput->setFlags(flgCheckbox); @@ -131,7 +139,16 @@ void MnSelectionDialog::appendItem(QFlags flgCheckbox, itemOutput->setText(COLUMN_NAME, mnName); itemOutput->setToolTip(COLUMN_NAME, "Masternode name"); itemOutput->setText(COLUMN_STATUS, mnStatus); - itemOutput->setToolTip(COLUMN_STATUS, "Masternode status"); + itemOutput->setToolTip(COLUMN_STATUS, "Masternode status"); // future: add status description + itemOutput->setTextAlignment(COLUMN_STATUS, Qt::AlignHCenter); + itemOutput->setTextAlignment(COLUMN_VOTE, Qt::AlignHCenter); + if (ptrVoteInfo) { + itemOutput->setText(COLUMN_VOTE, ptrVoteInfo->vote == VoteInfo::YES ? tr("Yes") : tr("No")); + itemOutput->setToolTip(COLUMN_VOTE, tr("The direction of the already broadcasted vote")); + } else { + itemOutput->setText(COLUMN_VOTE, "-"); + itemOutput->setToolTip(COLUMN_VOTE, tr("No vote has been emitted from this Masternode")); + } if (std::find(selectedMnList.begin(), selectedMnList.end(), mnName.toStdString()) != selectedMnList.end()) { itemOutput->setCheckState(COLUMN_CHECKBOX, Qt::Checked); diff --git a/src/qt/pivx/mnselectiondialog.h b/src/qt/pivx/mnselectiondialog.h index 9dd1689ffc6c..ee622741395d 100644 --- a/src/qt/pivx/mnselectiondialog.h +++ b/src/qt/pivx/mnselectiondialog.h @@ -6,6 +6,7 @@ #define MN_SELECTION_DEFAULTDIALOG_H #include +#include namespace Ui { class MnSelectionDialog; @@ -27,7 +28,7 @@ Q_OBJECT void setModel(MNModel* _mnModel); void updateView(); // Sets the MNs who already voted for this proposal - void setMnVoters(const std::vector& votes); + void setMnVoters(const std::vector& _votes); // Return the MNs who are going to vote for this proposal std::vector getSelectedMnAlias(); @@ -41,17 +42,21 @@ public Q_SLOTS: int colCheckBoxWidth_treeMode{50}; // selected MNs alias std::vector selectedMnList; + // MN alias -> VoteInfo for a certain proposal + std::map votes; enum { COLUMN_CHECKBOX, COLUMN_NAME, + COLUMN_VOTE, COLUMN_STATUS }; void appendItem(QFlags flgCheckbox, QFlags flgTristate, const QString& mnName, - const QString& mnStats); + const QString& mnStats, + VoteInfo* ptrVoteInfo); }; #endif // MN_SELECTION_DEFAULTDIALOG_H From fccb4cc37a6aa281a26749d758b7f20a07802ec3 Mon Sep 17 00:00:00 2001 From: furszy Date: Thu, 23 Sep 2021 18:13:09 -0300 Subject: [PATCH 59/59] GUI: vote for proposal, disable checkbox of MNs that will not be able to vote until the minimum vote time passes. --- src/qt/pivx/mnselectiondialog.cpp | 12 ++++++++++-- src/qt/pivx/mnselectiondialog.h | 5 ++++- src/qt/pivx/votedialog.cpp | 2 +- 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/src/qt/pivx/mnselectiondialog.cpp b/src/qt/pivx/mnselectiondialog.cpp index 8e7371237d38..526bd4ed8561 100644 --- a/src/qt/pivx/mnselectiondialog.cpp +++ b/src/qt/pivx/mnselectiondialog.cpp @@ -43,9 +43,10 @@ MnSelectionDialog::MnSelectionDialog(QWidget *parent) : connect(ui->treeWidget, &QTreeWidget::itemChanged, this, &MnSelectionDialog::viewItemChanged); } -void MnSelectionDialog::setModel(MNModel* _mnModel) +void MnSelectionDialog::setModel(MNModel* _mnModel, int _minVoteUpdateTimeInSecs) { mnModel = _mnModel; + minVoteUpdateTimeInSecs = _minVoteUpdateTimeInSecs; } void MnSelectionDialog::setMnVoters(const std::vector& _votes) @@ -137,7 +138,6 @@ void MnSelectionDialog::appendItem(QFlags flgCheckbox, itemOutput->setFlags(flgCheckbox); itemOutput->setCheckState(COLUMN_CHECKBOX, Qt::Unchecked); itemOutput->setText(COLUMN_NAME, mnName); - itemOutput->setToolTip(COLUMN_NAME, "Masternode name"); itemOutput->setText(COLUMN_STATUS, mnStatus); itemOutput->setToolTip(COLUMN_STATUS, "Masternode status"); // future: add status description itemOutput->setTextAlignment(COLUMN_STATUS, Qt::AlignHCenter); @@ -159,6 +159,14 @@ void MnSelectionDialog::appendItem(QFlags flgCheckbox, if (mnStatus != "ENABLED") { itemOutput->setDisabled(true); } + + // Disable MNs that will not be able to vote for the proposal until the minimum vote time passes. + if (ptrVoteInfo && ptrVoteInfo->time + minVoteUpdateTimeInSecs > GetAdjustedTime()) { + itemOutput->setDisabled(true); + QString disabledTooltip{tr("Time between votes is too soon, have to wait %1 minutes to change your vote").arg(minVoteUpdateTimeInSecs/60)}; + itemOutput->setToolTip(COLUMN_CHECKBOX, disabledTooltip); + itemOutput->setToolTip(COLUMN_NAME, disabledTooltip); + } } std::vector MnSelectionDialog::getSelectedMnAlias() diff --git a/src/qt/pivx/mnselectiondialog.h b/src/qt/pivx/mnselectiondialog.h index ee622741395d..aba9a1e953a5 100644 --- a/src/qt/pivx/mnselectiondialog.h +++ b/src/qt/pivx/mnselectiondialog.h @@ -25,7 +25,7 @@ Q_OBJECT explicit MnSelectionDialog(QWidget *parent); ~MnSelectionDialog(); - void setModel(MNModel* _mnModel); + void setModel(MNModel* _mnModel, int minVoteUpdateTimeInSecs); void updateView(); // Sets the MNs who already voted for this proposal void setMnVoters(const std::vector& _votes); @@ -39,6 +39,9 @@ public Q_SLOTS: private: Ui::MnSelectionDialog *ui; MNModel* mnModel{nullptr}; + // Consensus param, the minimum time that need to pass + // to be able to broadcast another vote with the same MN. + int minVoteUpdateTimeInSecs{0}; int colCheckBoxWidth_treeMode{50}; // selected MNs alias std::vector selectedMnList; diff --git a/src/qt/pivx/votedialog.cpp b/src/qt/pivx/votedialog.cpp index ab8975178ea6..dc221f67ce88 100644 --- a/src/qt/pivx/votedialog.cpp +++ b/src/qt/pivx/votedialog.cpp @@ -116,7 +116,7 @@ void VoteDialog::onMnSelectionClicked() PIVXGUI* window = dynamic_cast(parent()); if (!mnSelectionDialog) { mnSelectionDialog = new MnSelectionDialog(window); - mnSelectionDialog->setModel(mnModel); + mnSelectionDialog->setModel(mnModel, govModel->getProposalVoteUpdateMinTime()); } mnSelectionDialog->setMnVoters(votes); mnSelectionDialog->updateView();