diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include index c7b015da154aa..19da2bc59baec 100755 --- a/src/Makefile.qt.include +++ b/src/Makefile.qt.include @@ -48,6 +48,7 @@ QT_FORMS_UI = \ qt/forms/blockexplorer.ui \ qt/forms/obfuscationconfig.ui \ qt/forms/editaddressdialog.ui \ + qt/forms/governancepage.ui \ qt/forms/helpmessagedialog.ui \ qt/forms/intro.ui \ qt/forms/masternodelist.ui \ @@ -83,6 +84,7 @@ QT_MOC_CPP = \ qt/moc_csvmodelwriter.cpp \ qt/moc_obfuscationconfig.cpp \ qt/moc_editaddressdialog.cpp \ + qt/moc_governancepage.cpp \ qt/moc_guiutil.cpp \ qt/moc_intro.cpp \ qt/moc_macdockiconhandler.cpp \ @@ -101,6 +103,7 @@ QT_MOC_CPP = \ qt/moc_qvaluecombobox.cpp \ qt/moc_receivecoinsdialog.cpp \ qt/moc_privacydialog.cpp \ + qt/moc_proposalframe.cpp \ qt/moc_receiverequestdialog.cpp \ qt/moc_recentrequeststablemodel.cpp \ qt/moc_rpcconsole.cpp \ @@ -157,6 +160,7 @@ BITCOIN_QT_H = \ qt/csvmodelwriter.h \ qt/obfuscationconfig.h \ qt/editaddressdialog.h \ + qt/governancepage.h \ qt/guiconstants.h \ qt/guiutil.h \ qt/intro.h \ @@ -175,6 +179,7 @@ BITCOIN_QT_H = \ qt/paymentserver.h \ qt/peertablemodel.h \ qt/platformstyle.h \ + qt/proposalframe.h \ qt/qvalidatedlineedit.h \ qt/qvaluecombobox.h \ qt/receivecoinsdialog.h \ @@ -247,6 +252,8 @@ RES_ICONS = \ qt/res/icons/remove.png \ qt/res/icons/send.png \ qt/res/icons/send_dark.png \ + qt/res/icons/governance.png \ + qt/res/icons/governance_dark.png \ qt/res/icons/staking_active.png \ qt/res/icons/staking_inactive.png \ qt/res/icons/synced.png \ @@ -263,7 +270,10 @@ RES_ICONS = \ qt/res/icons/unit_upivx.png \ qt/res/icons/unit_tpivx.png \ qt/res/icons/unit_tmpivx.png \ - qt/res/icons/unit_tupivx.png + qt/res/icons/unit_tupivx.png \ + qt/res/icons/yesvote.png \ + qt/res/icons/novote.png \ + qt/res/icons/abstainvote.png BITCOIN_QT_BASE_CPP = \ qt/bantablemodel.cpp \ @@ -302,6 +312,7 @@ BITCOIN_QT_WALLET_CPP = \ qt/coincontroltreewidget.cpp \ qt/obfuscationconfig.cpp \ qt/editaddressdialog.cpp \ + qt/governancepage.cpp \ qt/masternodelist.cpp \ qt/multisenddialog.cpp \ qt/multisigdialog.cpp\ @@ -311,6 +322,7 @@ BITCOIN_QT_WALLET_CPP = \ qt/paymentserver.cpp \ qt/receivecoinsdialog.cpp \ qt/privacydialog.cpp \ + qt/proposalframe.cpp \ qt/receiverequestdialog.cpp \ qt/recentrequeststablemodel.cpp \ qt/sendcoinsdialog.cpp \ diff --git a/src/masternode-budget.cpp b/src/masternode-budget.cpp index f74a7f0fe9c4d..31871e8e626ca 100644 --- a/src/masternode-budget.cpp +++ b/src/masternode-budget.cpp @@ -1623,11 +1623,11 @@ double CBudgetProposal::GetRatio() return ((double)(yeas) / (double)(yeas + nays)); } -int CBudgetProposal::GetYeas() +int CBudgetProposal::GetYeas() const { int ret = 0; - std::map::iterator it = mapVotes.begin(); + std::map::const_iterator it = mapVotes.begin(); while (it != mapVotes.end()) { if ((*it).second.nVote == VOTE_YES && (*it).second.fValid) ret++; ++it; @@ -1636,11 +1636,11 @@ int CBudgetProposal::GetYeas() return ret; } -int CBudgetProposal::GetNays() +int CBudgetProposal::GetNays() const { int ret = 0; - std::map::iterator it = mapVotes.begin(); + std::map::const_iterator it = mapVotes.begin(); while (it != mapVotes.end()) { if ((*it).second.nVote == VOTE_NO && (*it).second.fValid) ret++; ++it; @@ -1649,11 +1649,11 @@ int CBudgetProposal::GetNays() return ret; } -int CBudgetProposal::GetAbstains() +int CBudgetProposal::GetAbstains() const { int ret = 0; - std::map::iterator it = mapVotes.begin(); + std::map::const_iterator it = mapVotes.begin(); while (it != mapVotes.end()) { if ((*it).second.nVote == VOTE_ABSTAIN && (*it).second.fValid) ret++; ++it; diff --git a/src/masternode-budget.h b/src/masternode-budget.h index f19e0fd7efdae..0268a1e7d4057 100644 --- a/src/masternode-budget.h +++ b/src/masternode-budget.h @@ -515,9 +515,9 @@ class CBudgetProposal int GetBlockCurrentCycle(); int GetBlockEndCycle(); double GetRatio(); - int GetYeas(); - int GetNays(); - int GetAbstains(); + int GetYeas() const; + int GetNays() const; + int GetAbstains() const; CAmount GetAmount() { return nAmount; } void SetAllotted(CAmount nAllotedIn) { nAlloted = nAllotedIn; } CAmount GetAllotted() { return nAlloted; } diff --git a/src/qt/askpassphrasedialog.cpp b/src/qt/askpassphrasedialog.cpp index 0c9a5b565a303..3da13dcfed929 100644 --- a/src/qt/askpassphrasedialog.cpp +++ b/src/qt/askpassphrasedialog.cpp @@ -75,7 +75,7 @@ AskPassphraseDialog::AskPassphraseDialog(Mode mode, QWidget* parent, WalletModel } // Set checkbox "For anonymization, automint, and staking only" depending on from where we were called - if (context == Context::Unlock_Menu || context == Context::Mint_zPIV || context == Context::BIP_38) { + if (context == Context::Unlock_Menu || context == Context::Mint_zPIV || context == Context::BIP_38 || context == Context::UI_Vote) { ui->anonymizationCheckBox->setChecked(true); } else { diff --git a/src/qt/askpassphrasedialog.h b/src/qt/askpassphrasedialog.h index 56b940cb64128..fd44ba77a0638 100644 --- a/src/qt/askpassphrasedialog.h +++ b/src/qt/askpassphrasedialog.h @@ -43,7 +43,8 @@ class AskPassphraseDialog : public QDialog Mint_zPIV, /** Mint zPIV */ BIP_38, /** BIP38 menu */ Multi_Sig, /** Multi-Signature dialog */ - Sign_Message /** Sign/verify message dialog */ + Sign_Message, /** Sign/verify message dialog */ + UI_Vote, /** Governance Tab UI Voting */ }; explicit AskPassphraseDialog(Mode mode, QWidget* parent, WalletModel* model, Context context); diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index 3b6f4172ba77b..a61d3b6a60b8b 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -87,6 +87,7 @@ BitcoinGUI::BitcoinGUI(const NetworkStyle* networkStyle, QWidget* parent) : QMai multisigSignAction(0), aboutAction(0), receiveCoinsAction(0), + governanceAction(0), privacyAction(0), optionsAction(0), toggleHideAction(0), @@ -368,6 +369,17 @@ void BitcoinGUI::createActions(const NetworkStyle* networkStyle) connect(masternodeAction, SIGNAL(triggered()), this, SLOT(gotoMasternodePage())); } + governanceAction = new QAction(QIcon(":/icons/governance"), tr("&Governance"), this); + governanceAction->setStatusTip(tr("Show Proposals")); + governanceAction->setToolTip(governanceAction->statusTip()); + governanceAction->setCheckable(true); +#ifdef Q_OS_MAC + governanceAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_7)); +#else + governanceAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_7)); +#endif + tabGroup->addAction(governanceAction); + // These showNormalIfMinimized are needed because Send Coins and Receive Coins // can be triggered from the tray menu, and need to show the GUI to be useful. connect(overviewAction, SIGNAL(triggered()), this, SLOT(showNormalIfMinimized())); @@ -380,6 +392,7 @@ void BitcoinGUI::createActions(const NetworkStyle* networkStyle) connect(privacyAction, SIGNAL(triggered()), this, SLOT(gotoPrivacyPage())); connect(historyAction, SIGNAL(triggered()), this, SLOT(showNormalIfMinimized())); connect(historyAction, SIGNAL(triggered()), this, SLOT(gotoHistoryPage())); + connect(governanceAction, SIGNAL(triggered()), this, SLOT(gotoGovernancePage())); #endif // ENABLE_WALLET quitAction = new QAction(QIcon(":/icons/quit"), tr("E&xit"), this); @@ -565,6 +578,7 @@ void BitcoinGUI::createToolBars() if (settings.value("fShowMasternodesTab").toBool()) { toolbar->addAction(masternodeAction); } + toolbar->addAction(governanceAction); toolbar->setMovable(false); // remove unused icon in upper left corner toolbar->setOrientation(Qt::Vertical); toolbar->setIconSize(QSize(40,40)); @@ -811,6 +825,12 @@ void BitcoinGUI::gotoMasternodePage() } } +void BitcoinGUI::gotoGovernancePage() +{ + governanceAction->setChecked(true); + if (walletFrame) walletFrame->gotoGovernancePage(); +} + void BitcoinGUI::gotoReceiveCoinsPage() { receiveCoinsAction->setChecked(true); diff --git a/src/qt/bitcoingui.h b/src/qt/bitcoingui.h index d47e237d82ed3..ead5fd30ba361 100644 --- a/src/qt/bitcoingui.h +++ b/src/qt/bitcoingui.h @@ -109,6 +109,7 @@ class BitcoinGUI : public QMainWindow QAction* multisigSignAction; QAction* aboutAction; QAction* receiveCoinsAction; + QAction* governanceAction; QAction* privacyAction; QAction* optionsAction; QAction* toggleHideAction; @@ -209,6 +210,8 @@ private slots: void gotoOverviewPage(); /** Switch to history (transactions) page */ void gotoHistoryPage(); + /** Switch to Governance Page */ + void gotoGovernancePage(); /** Switch to Explorer Page */ void gotoBlockExplorerPage(); /** Switch to masternode page */ diff --git a/src/qt/forms/governancepage.ui b/src/qt/forms/governancepage.ui new file mode 100644 index 0000000000000..c6a8b4b2f492c --- /dev/null +++ b/src/qt/forms/governancepage.ui @@ -0,0 +1,385 @@ + + + GovernancePage + + + + 0 + 0 + 968 + 457 + + + + Form + + + + 9 + + + 9 + + + 9 + + + 9 + + + + + 0 + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + 464 + 60 + + + + + 16777215 + 60 + + + + + 20 + 75 + true + + + + GOVERNANCE + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + 464 + 60 + + + + + 16777215 + 60 + + + + + 14 + + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + + + 6 + + + 6 + + + 6 + + + 6 + + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + + 0 + 1 + + + + QFrame::NoFrame + + + QFrame::Plain + + + 0 + + + Qt::ScrollBarAlwaysOff + + + QAbstractScrollArea::AdjustIgnored + + + true + + + + + 0 + 0 + 742 + 282 + + + + + 0 + 1 + + + + + 0 + + + QLayout::SetDefaultConstraint + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + Update Proposals + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + QFrame::StyledPanel + + + QFrame::Raised + + + + + + Next super block: + + + + + + + 0 + + + + + + + Blocks to next super block: + + + + + + + 0 + + + + + + + Days to budget payout (estimate): + + + + + + + 0 + + + + + + + Allotted budget: + + + + + + + 0 + + + + + + + Budget left: + + + + + + + 0 + + + + + + + Masternodes count: + + + + + + + 0 + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/qt/governancepage.cpp b/src/qt/governancepage.cpp new file mode 100644 index 0000000000000..a748acb1262a2 --- /dev/null +++ b/src/qt/governancepage.cpp @@ -0,0 +1,142 @@ +// Copyright (c) 2018 The PIVX developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "governancepage.h" +#include "ui_governancepage.h" + +#include "activemasternode.h" +#include "clientmodel.h" +#include "masternode-budget.h" +#include "masternode-sync.h" +#include "masternodeconfig.h" +#include "masternodeman.h" +#include "utilmoneystr.h" +#include "walletmodel.h" +#include "askpassphrasedialog.h" +#include "proposalframe.h" + +#include +#include +#include +#include + +GovernancePage::GovernancePage(QWidget* parent) : QWidget(parent), + ui(new Ui::GovernancePage), + clientModel(0), + walletModel(0) +{ + ui->setupUi(this); + + timer = new QTimer(this); + connect(timer, SIGNAL(timeout()), this, SLOT(updateProposalList())); + timer->start(100000); + fLockUpdating = false; +} + + +GovernancePage::~GovernancePage() +{ + delete ui; +} + +void GovernancePage::setClientModel(ClientModel* model) +{ + this->clientModel = model; +} + +void GovernancePage::setWalletModel(WalletModel* model) +{ + this->walletModel = model; +} + +void GovernancePage::lockUpdating(bool lock) +{ + fLockUpdating = lock; +} + +struct sortProposalsByVotes +{ + bool operator() (const CBudgetProposal* left, const CBudgetProposal* right) + { + if (left != right) + return (left->GetYeas() - left->GetNays() > right->GetYeas() - right->GetNays()); + return (left->nFeeTXHash > right->nFeeTXHash); + } +}; + +void GovernancePage::updateProposalList() +{ + if (fLockUpdating) return; + + QLayoutItem* child; + while ((child = ui->proposalGrid->takeAt(0)) != 0) { + if (child->widget() != 0) + { + delete child->widget(); + } + delete child; + } + + std::vector allotedProposals = budget.GetBudget(); + CAmount nTotalAllotted = 0; + std::vector proposalsList = budget.GetAllProposals(); + std::sort (proposalsList.begin(), proposalsList.end(), sortProposalsByVotes()); + int nRow = 0; + for (CBudgetProposal* pbudgetProposal : proposalsList) { + if (!pbudgetProposal->fValid) continue; + if (pbudgetProposal->GetRemainingPaymentCount() < 1) continue; + + ProposalFrame* proposalFrame = new ProposalFrame(); + proposalFrame->setWalletModel(walletModel); + proposalFrame->setProposal(pbudgetProposal); + proposalFrame->setGovernancePage(this); + + if (std::find(allotedProposals.begin(), allotedProposals.end(), pbudgetProposal) != allotedProposals.end()) { + nTotalAllotted += pbudgetProposal->GetAllotted(); + proposalFrame->setObjectName(QStringLiteral("proposalFramePassing")); + } else + proposalFrame->setObjectName(QStringLiteral("proposalFrame")); + proposalFrame->setFrameShape(QFrame::StyledPanel); + + if (extendedProposal == pbudgetProposal) + proposalFrame->extend(); + proposalFrame->setMaximumHeight(150); + ui->proposalGrid->addWidget(proposalFrame, nRow); + + ++nRow; + } + + CBlockIndex* pindexPrev = chainActive.Tip(); + int nNext, nLeft; + if (!pindexPrev) { + nNext = 0; + nLeft = 0; + } + else { + nNext = pindexPrev->nHeight - pindexPrev->nHeight % GetBudgetPaymentCycleBlocks() + GetBudgetPaymentCycleBlocks(); + nLeft = nNext - pindexPrev->nHeight; + } + + ui->next_superblock_value->setText(QString::number(nNext)); + ui->blocks_before_super_value->setText(QString::number(nLeft)); + ui->time_before_super_value->setText(QString::number(nLeft/60/24)); + ui->alloted_budget_value->setText(QString::number(nTotalAllotted/COIN)); + ui->unallocated_budget_value->setText(QString::number((budget.GetTotalBudget(pindexPrev->nHeight) - nTotalAllotted)/COIN)); + ui->masternode_count_value->setText(QString::number(mnodeman.stable_size())); +} + +void GovernancePage::setExtendedProposal(CBudgetProposal* proposal) +{ + bool update = false; + if (extendedProposal != proposal) + update = true; + extendedProposal = proposal; + if (update) + updateProposalList(); +} + +void GovernancePage::on_UpdateButton_clicked() +{ + updateProposalList(); +} diff --git a/src/qt/governancepage.h b/src/qt/governancepage.h new file mode 100644 index 0000000000000..95fd70b7def8c --- /dev/null +++ b/src/qt/governancepage.h @@ -0,0 +1,70 @@ +// Copyright (c) 2018 The PIVX developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_QT_GOVERNANCEPAGE_H +#define BITCOIN_QT_GOVERNANCEPAGE_H + +#include "masternode.h" +#include "platformstyle.h" +#include "sync.h" +#include "util.h" + +#include +#include +#include +#include +#include +#include +#include + +class ClientModel; +class WalletModel; +class CBudgetProposal; + +namespace Ui +{ + class GovernancePage; +} + +QT_BEGIN_NAMESPACE +class QModelIndex; +QT_END_NAMESPACE + +class GovernancePage : public QWidget +{ + Q_OBJECT + +public: + explicit GovernancePage(QWidget* parent = 0); + ~GovernancePage(); + + void setClientModel(ClientModel* clientModel); + void setWalletModel(WalletModel* walletModel); + void setExtendedProposal(CBudgetProposal* proposal); + void lockUpdating(bool lock); + +private: + QMenu* contextMenu; + int64_t nTimeFilterUpdated; + bool fFilterUpdated; + bool fLockUpdating; + +public Q_SLOTS: + void updateProposalList(); + +Q_SIGNALS: + +private: + QTimer* timer; + Ui::GovernancePage* ui; + ClientModel* clientModel; + WalletModel* walletModel; + QString strCurrentFilter; + CBudgetProposal* extendedProposal; + +private Q_SLOTS: + void on_UpdateButton_clicked(); +}; + +#endif // BITCOIN_QT_GOVERNANCEPAGE_H diff --git a/src/qt/pivx.qrc b/src/qt/pivx.qrc index e6cd9eb47688b..7ac3563c6c498 100644 --- a/src/qt/pivx.qrc +++ b/src/qt/pivx.qrc @@ -33,6 +33,8 @@ res/icons/history.png res/icons/overview.png res/icons/masternodes.png + res/icons/governance.png + res/icons/governance_dark.png res/icons/export.png res/icons/synced.png res/icons/remove.png @@ -58,6 +60,9 @@ res/icons/automint_active.png res/icons/explorer.png res/icons/automint_inactive.png + res/icons/yesvote.png + res/icons/novote.png + res/icons/abstainvote.png res/icons/onion.png diff --git a/src/qt/proposalframe.cpp b/src/qt/proposalframe.cpp new file mode 100644 index 0000000000000..a61ca8d0b769e --- /dev/null +++ b/src/qt/proposalframe.cpp @@ -0,0 +1,313 @@ +// Copyright (c) 2018 The PIVX developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "proposalframe.h" + +#include "masternode-budget.h" +#include "masternodeconfig.h" +#include "utilmoneystr.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +ProposalFrame::ProposalFrame(QWidget* parent) : QFrame(parent) +{ + extended = false; +} + +ProposalFrame::~ProposalFrame() +{ + if (proposalItem) + delete proposalItem; +} + +void ProposalFrame::setProposal(CBudgetProposal* proposal) +{ + this->proposal = proposal; + + proposalItem = new QVBoxLayout(this); + proposalItem->setSpacing(0); + proposalItem->setObjectName(QStringLiteral("proposalItem")); + + QHBoxLayout* proposalInfo = new QHBoxLayout(); + proposalInfo->setSpacing(0); + proposalInfo->setObjectName(QStringLiteral("proposalInfo")); + + QLabel* strProposalHash = new QLabel(); + strProposalHash->setObjectName(QStringLiteral("strProposalHash")); + strProposalHash->setText(QString::fromStdString(proposal->GetHash().ToString())); + strProposalHash->setVisible(false); + QLabel* strProposalName = new QLabel(); + strProposalName->setObjectName(QStringLiteral("strProposalName")); + strProposalName->setText(QString::fromStdString(proposal->GetName())); + QLabel* strMonthlyPayout = new QLabel(); + strMonthlyPayout->setObjectName(QStringLiteral("strMonthlyPayout")); + strMonthlyPayout->setText(QString::fromStdString(FormatMoney(proposal->GetAmount()))); + strMonthlyPayout->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter); + + proposalInfo->addWidget(strProposalName); + proposalInfo->addWidget(strProposalHash); + proposalInfo->addStretch(); + proposalInfo->addWidget(strMonthlyPayout); + proposalItem->addLayout(proposalInfo); +} + +void ProposalFrame::setWalletModel(WalletModel* model) +{ + this->walletModel = model; +} + +void ProposalFrame::setGovernancePage(GovernancePage* governancePage) +{ + this->governancePage = governancePage; +} + +void ProposalFrame::mousePressEvent(QMouseEvent* event) +{ + +} + +void ProposalFrame::refresh() +{ + if (!proposal) //TODO implement an error + return; + + if (extended) + { + // URL + proposalURL = new QHBoxLayout(); + + QLabel* strProposalURL = new QLabel(); + strProposalURL->setObjectName(QStringLiteral("strProposalURL")); + QString strURL = QString::fromStdString(proposal->GetURL()); + QString strClick = tr("Open proposal page in browser"); + strProposalURL->setText("" + strClick + ""); + strProposalURL->setTextFormat(Qt::RichText); + strProposalURL->setTextInteractionFlags(Qt::TextBrowserInteraction); + strProposalURL->setOpenExternalLinks(false); + connect(strProposalURL, &QLabel::linkActivated, this, &ProposalFrame::proposalLink_clicked); + proposalURL->addWidget(strProposalURL); + + QSpacerItem* spacer = new QSpacerItem(1,1, QSizePolicy::Expanding, QSizePolicy::Fixed); + proposalURL->addSpacerItem(spacer); + + proposalItem->addLayout(proposalURL); + + //CYCLES LEFT + strRemainingPaymentCount = new QLabel(); + strRemainingPaymentCount->setObjectName(QStringLiteral("strRemainingPaymentCount")); + QString strPaymentCount = QString::number(proposal->GetRemainingPaymentCount()) + tr(" remaining payment(s)."); + strRemainingPaymentCount->setText(strPaymentCount); + proposalItem->addWidget(strRemainingPaymentCount); + + //VOTES + int nCountMyMasternodes = masternodeConfig.getCount(); + proposalVotes = new QHBoxLayout(); + + QToolButton* yesButton = new QToolButton(); + yesButton->setIcon(QIcon(":/icons/yesvote")); + if (nCountMyMasternodes < 0) + yesButton->setEnabled(false); + connect(yesButton, &QPushButton::clicked, this, [this]{ voteButton_clicked(1); }); + QLabel* labelYesVotes = new QLabel(); + labelYesVotes->setText(tr("Yes:")); + QLabel* yesVotes = new QLabel(); + yesVotes->setText(QString::number(proposal->GetYeas())); + + QToolButton* abstainButton = new QToolButton(); + abstainButton->setIcon(QIcon(":/icons/abstainvote")); + if (nCountMyMasternodes < 0) + abstainButton->setEnabled(false); + connect(abstainButton, &QPushButton::clicked, this, [this]{ voteButton_clicked(0); }); + QLabel* labelAbstainVotes = new QLabel(); + labelAbstainVotes->setText(tr("Abstain:")); + QLabel* abstainVotes = new QLabel(); + abstainVotes->setText(QString::number(proposal->GetAbstains())); + + QToolButton* noButton = new QToolButton(); + noButton->setIcon(QIcon(":/icons/novote")); + if (nCountMyMasternodes < 0) + noButton->setEnabled(false); + connect(noButton, &QPushButton::clicked, this, [this]{ voteButton_clicked(2); }); + QLabel* labelNoVotes = new QLabel(); + labelNoVotes->setText(tr("No:")); + QLabel* noVotes = new QLabel(); + noVotes->setText(QString::number(proposal->GetNays())); + + proposalVotes->addWidget(yesButton); + proposalVotes->addWidget(labelYesVotes); + proposalVotes->addWidget(yesVotes); + proposalVotes->addStretch(); + proposalVotes->addWidget(abstainButton); + proposalVotes->addWidget(labelAbstainVotes); + proposalVotes->addWidget(abstainVotes); + proposalVotes->addStretch(); + proposalVotes->addWidget(labelNoVotes); + proposalVotes->addWidget(noVotes); + proposalVotes->addWidget(noButton); + proposalItem->addLayout(proposalVotes); + } else + { + delete strRemainingPaymentCount; + QLayoutItem* child; + while ((child = proposalVotes->takeAt(0)) != 0) { + if (child->widget() != 0) + { + delete child->widget(); + } + delete child; + } + delete proposalVotes; + while ((child = proposalURL->takeAt(0)) != 0) { + if (child->widget() != 0) + { + delete child->widget(); + } + delete child; + } + delete proposalURL; + } +} + +void ProposalFrame::proposalLink_clicked(const QString &link) +{ + QMessageBox Msgbox; + QString strMsgBox = tr("A proposal URL can be used for phishing, scams and computer viruses. Open this link only if you trust the following URL.\n"); + strMsgBox += QString::fromStdString(proposal->GetURL()); + Msgbox.setText(strMsgBox); + Msgbox.setStandardButtons(QMessageBox::Cancel); + QAbstractButton *openButton = Msgbox.addButton(tr("Open link"), QMessageBox::ApplyRole); + QAbstractButton *copyButton = Msgbox.addButton(tr("Copy link"), QMessageBox::ApplyRole); + + governancePage->lockUpdating(true); + Msgbox.exec(); + + if (Msgbox.clickedButton() == openButton) { + QDesktopServices::openUrl(QUrl(QString::fromStdString(proposal->GetURL()))); + } + if (Msgbox.clickedButton() == copyButton) { + QGuiApplication::clipboard()->setText(QString::fromStdString(proposal->GetURL())); + } + governancePage->lockUpdating(false); +} + +void ProposalFrame::extend() +{ + extended = true; + refresh(); +} + +void ProposalFrame::close() +{ + extended = false; + refresh(); +} + +void ProposalFrame::mouseReleaseEvent(QMouseEvent* event) +{ + if (!governancePage) //TODO implement an error + return; + extended = !extended; + if (extended) + governancePage->setExtendedProposal(proposal); + else + governancePage->setExtendedProposal(nullptr); +} + +void ProposalFrame::voteButton_clicked(int nVote) +{ + if (!walletModel) return; + + // Request unlock if wallet was locked or unlocked for mixing: + WalletModel::EncryptionStatus encStatus = walletModel->getEncryptionStatus(); + if (encStatus == walletModel->Locked) { + WalletModel::UnlockContext ctx(walletModel->requestUnlock(AskPassphraseDialog::Context::UI_Vote, true)); + if (!ctx.isValid()) { + // Unlock wallet was cancelled + governancePage->lockUpdating(true); + QMessageBox::warning(this, tr("Wallet Locked"), tr("You must unlock your wallet to vote."), QMessageBox::Ok, QMessageBox::Ok); + return; + } + } + + // Display message box + QString questionString = tr("Do you want to vote %1 on").arg(nVote == 1 ? "yes" : (nVote == 2 ? "no" : "abstain")) + " " + QString::fromStdString(proposal->GetName()) + " "; + questionString += tr("using all your masternodes?"); + questionString += "

"; + questionString += tr("Proposal Hash:") + " " + QString::fromStdString(proposal->GetHash().ToString()) + "
"; + questionString += tr("Proposal URL:") + " " + QString::fromStdString(proposal->GetURL()); + governancePage->lockUpdating(true); + QMessageBox::StandardButton retval = QMessageBox::question(this, tr("Confirm Vote"), + questionString, + QMessageBox::Yes | QMessageBox::Cancel, + QMessageBox::Cancel); + + if (retval != QMessageBox::Yes) { + return; + } + + governancePage->lockUpdating(false); + SendVote(proposal->GetHash().ToString(), nVote); +} + +void ProposalFrame::SendVote(std::string strHash, int nVote) +{ + uint256 hash = uint256(strHash); + int failed = 0, success = 0; + std::string mnresult; + for (CMasternodeConfig::CMasternodeEntry mne : masternodeConfig.getEntries()) { + std::string errorMessage; + std::vector vchMasterNodeSignature; + std::string strMasterNodeSignMessage; + + CPubKey pubKeyCollateralAddress; + CKey keyCollateralAddress; + CPubKey pubKeyMasternode; + CKey keyMasternode; + + if (!obfuScationSigner.SetKey(mne.getPrivKey(), errorMessage, keyMasternode, pubKeyMasternode)) { + mnresult += mne.getAlias() + ": " + "Masternode signing error, could not set key correctly: " + errorMessage + "
"; + failed++; + continue; + } + + CMasternode* pmn = mnodeman.Find(pubKeyMasternode); + if (pmn == NULL) { + mnresult += mne.getAlias() + ": " + "Can't find masternode by pubkey" + "
"; + failed++; + continue; + } + + CBudgetVote vote(pmn->vin, hash, nVote); + if (!vote.Sign(keyMasternode, pubKeyMasternode)) { + mnresult += mne.getAlias() + ": " + "Failure to sign" + "
"; + failed++; + continue; + } + + std::string strError = ""; + if (budget.UpdateProposal(vote, NULL, strError)) { + budget.mapSeenMasternodeBudgetVotes.insert(make_pair(vote.GetHash(), vote)); + vote.Relay(); + mnresult += mne.getAlias() + ": " + "Success!" + "
"; + success++; + } else { + mnresult += mne.getAlias() + ": " + "Failed!" + "
"; + failed++; + } + + } + + QString strMessage = QString("Successfully voted with %1 masternode(s), failed with %2").arg(success).arg(failed); + strMessage += "


"; + strMessage += QString::fromStdString(mnresult); + QMessageBox::information(governancePage, tr("Vote Results"), strMessage, QMessageBox::Ok, QMessageBox::Ok); +} diff --git a/src/qt/proposalframe.h b/src/qt/proposalframe.h new file mode 100644 index 0000000000000..8bc2d488c33c5 --- /dev/null +++ b/src/qt/proposalframe.h @@ -0,0 +1,54 @@ +// Copyright (c) 2018 The PIVX developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef PIVX_QT_PROPOSALFRAME_H +#define PIVX_QT_PROPOSALFRAME_H + +#include +#include +#include +#include + +#include "walletmodel.h" +#include "governancepage.h" + +class CBudgetProposal; + +class ProposalFrame : public QFrame +{ + Q_OBJECT + +public: + explicit ProposalFrame(QWidget* parent = 0); + ~ProposalFrame(); + + void setProposal(CBudgetProposal* proposal); + void setWalletModel(WalletModel* walletModel); + void SendVote(std::string strHash, int nVote); + void setGovernancePage(GovernancePage* governancePage); + void refresh(); + void close(); + void extend(); + +protected: + virtual void mouseReleaseEvent(QMouseEvent* event); + virtual void mousePressEvent(QMouseEvent* event); + +private: + bool extended; + CBudgetProposal* proposal; + WalletModel* walletModel; + GovernancePage* governancePage; + + QVBoxLayout* proposalItem; + QHBoxLayout* proposalURL; + QLabel* strRemainingPaymentCount; + QHBoxLayout* proposalVotes; + +private Q_SLOTS: + void voteButton_clicked(int nVote); + void proposalLink_clicked(const QString &link); +}; + +#endif //PIVX_QT_PROPOSALFRAME_H diff --git a/src/qt/res/css/default.css b/src/qt/res/css/default.css index b0b2f8e498cb9..5aac5448cccfe 100755 --- a/src/qt/res/css/default.css +++ b/src/qt/res/css/default.css @@ -223,7 +223,7 @@ background-color:#f2f2f2; /***************************** Global Qt Objects ***********************************************************/ QPushButton { /* Global Button Style */ -background-color: #5c4c7c; +background-color: #5c4c7c; border-width: 1px; border-style: outset; border-color: #382f44; @@ -1345,7 +1345,7 @@ QDialog#SendCoinsDialog .QFrame#frameCoinControl .QWidget#widgetCoinControl > .Q padding:2px; } -/* QDialog#SendCoinsDialog .QFrame#frameCoinControl .QCheckBox#checkBoxCoinControlChange { /* Custom Change Label */ +/* QDialog#SendCoinsDialog .QFrame#frameCoinControl .QCheckBox#checkBoxCoinControlChange { /* Custom Change Label */ /* QDialog#SendCoinsDialog .QFrame#frameCoinControl .QValidatedLineEdit#lineEditCoinControlChange { /* Custom Change Address */ /* QDialog#SendCoinsDialog .QFrame#frameCoinControl .QValidatedLineEdit#lineEditCoinControlChange:!focus { /* Custom Change Address */ /* QDialog#SendCoinsDialog .QFrame#frameCoinControl .QLineEdit#splitBlockLineEdit { */ @@ -1635,7 +1635,7 @@ QLabel#transactionSumLabel { /* Example of setObjectName for widgets without nam color:#333333; font-weight:bold; } - + QLabel#transactionSum { /* Example of setObjectName for widgets without name */ color:#333333; } @@ -1701,3 +1701,40 @@ color:#333; QCalendarWidget QAbstractItemView:disabled { /* Calendar widget days not in month */ color:#818181; } + +QWidget#GovernancePage .QFrame#frame_Content { + background-color:#ffffff; +} + +QWidget#GovernancePage .ProposalFrame { + border-radius:5px; + margin:2px; + max-height:250px; +} + +QWidget#GovernancePage .ProposalFrame#proposalFrame { + background-color:qlineargradient(x1: 0, y1: 0, x2: 1, y2: 0, stop: 0 #9179c7, stop: 1 #b396f6); + border:1px solid #b396f6; +} + +QWidget#GovernancePage .ProposalFrame#proposalFramePassing { + background-color:qlineargradient(x1: 0, y1: 0, x2: 1, y2: 0, stop: 0 #00cc00, stop: 1 #00ef00); + border:1px solid #00ef00; +} + +QWidget#GovernancePage .ProposalFrame > QLabel#strProposalName, QLabel#strMonthlyPayout, QLabel#strRemainingPaymentCount { + font-size:18pt; +} + +QWidget#GovernancePage .ProposalFrame > QLabel#strProposalURL { + font-size:14pt; +} + +QWidget#GovernancePage .QScrollArea, .QWidget#scrollAreaWidgetContents { + background-color:#fff; +} + +QWidget#GovernancePage .QGridLayout#proposalGrid { + margin-right:35px!important; + padding-right:35px!important; +} \ No newline at end of file diff --git a/src/qt/res/icons/abstainvote.png b/src/qt/res/icons/abstainvote.png new file mode 100644 index 0000000000000..25633b8a2e4d2 Binary files /dev/null and b/src/qt/res/icons/abstainvote.png differ diff --git a/src/qt/res/icons/governance.png b/src/qt/res/icons/governance.png new file mode 100644 index 0000000000000..35f092cbcaf6a Binary files /dev/null and b/src/qt/res/icons/governance.png differ diff --git a/src/qt/res/icons/governance_dark.png b/src/qt/res/icons/governance_dark.png new file mode 100644 index 0000000000000..55b233bfb96fe Binary files /dev/null and b/src/qt/res/icons/governance_dark.png differ diff --git a/src/qt/res/icons/novote.png b/src/qt/res/icons/novote.png new file mode 100644 index 0000000000000..82e8795cf9ee7 Binary files /dev/null and b/src/qt/res/icons/novote.png differ diff --git a/src/qt/res/icons/yesvote.png b/src/qt/res/icons/yesvote.png new file mode 100644 index 0000000000000..5f9faae6472f6 Binary files /dev/null and b/src/qt/res/icons/yesvote.png differ diff --git a/src/qt/walletframe.cpp b/src/qt/walletframe.cpp index bb9c7f79a97f2..2c5db1ebbaff6 100644 --- a/src/qt/walletframe.cpp +++ b/src/qt/walletframe.cpp @@ -119,6 +119,13 @@ void WalletFrame::gotoHistoryPage() i.value()->gotoHistoryPage(); } +void WalletFrame::gotoGovernancePage() +{ + QMap::const_iterator i; + for (i = mapWalletViews.constBegin(); i != mapWalletViews.constEnd(); ++i) + i.value()->gotoGovernancePage(); +} + void WalletFrame::gotoMasternodePage() // Masternode list { QMap::const_iterator i; diff --git a/src/qt/walletframe.h b/src/qt/walletframe.h index 8669d497c5c1a..75a7e86ddb9e3 100644 --- a/src/qt/walletframe.h +++ b/src/qt/walletframe.h @@ -57,6 +57,8 @@ public slots: void gotoOverviewPage(); /** Switch to history (transactions) page */ void gotoHistoryPage(); + /** Switch to governance page */ + void gotoGovernancePage(); /** Switch to masternode page */ void gotoMasternodePage(); /** Switch to receive coins page */ diff --git a/src/qt/walletview.cpp b/src/qt/walletview.cpp index 03457f468efd4..2d0e1368793e4 100644 --- a/src/qt/walletview.cpp +++ b/src/qt/walletview.cpp @@ -10,6 +10,7 @@ #include "bitcoingui.h" #include "blockexplorer.h" #include "clientmodel.h" +#include "governancepage.h" #include "guiutil.h" #include "masternodeconfig.h" #include "multisenddialog.h" @@ -116,12 +117,14 @@ WalletView::WalletView(QWidget* parent) : QStackedWidget(parent), transactionsPage->setLayout(vbox); privacyPage = new PrivacyDialog(); + governancePage = new GovernancePage(); receiveCoinsPage = new ReceiveCoinsDialog(); sendCoinsPage = new SendCoinsDialog(); addWidget(overviewPage); addWidget(transactionsPage); addWidget(privacyPage); + addWidget(governancePage); addWidget(receiveCoinsPage); addWidget(sendCoinsPage); addWidget(explorerWindow); @@ -182,6 +185,7 @@ void WalletView::setClientModel(ClientModel* clientModel) if (settings.value("fShowMasternodesTab").toBool()) { masternodeListPage->setClientModel(clientModel); } + governancePage->setClientModel(clientModel); } void WalletView::setWalletModel(WalletModel* walletModel) @@ -198,6 +202,7 @@ void WalletView::setWalletModel(WalletModel* walletModel) privacyPage->setModel(walletModel); receiveCoinsPage->setModel(walletModel); sendCoinsPage->setModel(walletModel); + governancePage->setWalletModel(walletModel); if (walletModel) { // Receive and pass through messages from wallet model @@ -249,6 +254,10 @@ void WalletView::gotoHistoryPage() setCurrentWidget(transactionsPage); } +void WalletView::gotoGovernancePage() +{ + setCurrentWidget(governancePage); +} void WalletView::gotoBlockExplorerPage() { diff --git a/src/qt/walletview.h b/src/qt/walletview.h index 7cbc0c6c59289..755434932f5f2 100644 --- a/src/qt/walletview.h +++ b/src/qt/walletview.h @@ -18,6 +18,7 @@ class ClientModel; class OverviewPage; class ReceiveCoinsDialog; class PrivacyDialog; +class GovernancePage; class SendCoinsDialog; class SendCoinsRecipient; class TransactionView; @@ -67,6 +68,7 @@ class WalletView : public QStackedWidget QWidget* transactionsPage; ReceiveCoinsDialog* receiveCoinsPage; PrivacyDialog* privacyPage; + GovernancePage* governancePage; SendCoinsDialog* sendCoinsPage; BlockExplorer* explorerWindow; MasternodeList* masternodeListPage; @@ -81,6 +83,8 @@ public slots: void gotoOverviewPage(); /** Switch to history (transactions) page */ void gotoHistoryPage(); + /** Switch to governance page */ + void gotoGovernancePage(); /** Switch to masternode page */ void gotoMasternodePage(); /** Switch to explorer page */