From bb8dbc59eaac6e742fa62ccb0f0591cec5f717b4 Mon Sep 17 00:00:00 2001 From: devfsc <30779666+devfsc@users.noreply.github.com> Date: Tue, 4 Sep 2018 12:47:24 -0500 Subject: [PATCH] Proposal Monitor Section (#41) Proposals Monitor Section --- configure.ac | 4 +- src/Makefile.qt.include | 20 +- src/clientversion.h | 4 +- src/qt/bitcoingui.cpp | 22 + src/qt/bitcoingui.h | 4 + src/qt/columnalignedlayout.cpp | 38 ++ src/qt/columnalignedlayout.h | 21 + src/qt/paccoin.qrc | 4 + src/qt/proposalfilterproxy.cpp | 112 +++++ src/qt/proposalfilterproxy.h | 51 ++ src/qt/proposallist.cpp | 625 +++++++++++++++++++++++++ src/qt/proposallist.h | 126 +++++ src/qt/proposalrecord.cpp | 6 + src/qt/proposalrecord.h | 45 ++ src/qt/proposaltablemodel.cpp | 242 ++++++++++ src/qt/proposaltablemodel.h | 66 +++ src/qt/res/icons/crownium/proposal.png | Bin 0 -> 1103 bytes src/qt/res/icons/drkblue/proposal.png | Bin 0 -> 1103 bytes src/qt/res/icons/light/proposal.png | Bin 0 -> 1103 bytes src/qt/res/icons/trad/proposal.png | Bin 0 -> 1103 bytes src/qt/walletframe.cpp | 7 + src/qt/walletframe.h | 2 + src/qt/walletview.cpp | 8 + src/qt/walletview.h | 4 + 24 files changed, 1406 insertions(+), 5 deletions(-) create mode 100644 src/qt/columnalignedlayout.cpp create mode 100644 src/qt/columnalignedlayout.h create mode 100644 src/qt/proposalfilterproxy.cpp create mode 100644 src/qt/proposalfilterproxy.h create mode 100644 src/qt/proposallist.cpp create mode 100644 src/qt/proposallist.h create mode 100644 src/qt/proposalrecord.cpp create mode 100644 src/qt/proposalrecord.h create mode 100644 src/qt/proposaltablemodel.cpp create mode 100644 src/qt/proposaltablemodel.h create mode 100644 src/qt/res/icons/crownium/proposal.png create mode 100644 src/qt/res/icons/drkblue/proposal.png create mode 100644 src/qt/res/icons/light/proposal.png create mode 100644 src/qt/res/icons/trad/proposal.png diff --git a/configure.ac b/configure.ac index a2362adccd02d..a65a48da11236 100644 --- a/configure.ac +++ b/configure.ac @@ -2,8 +2,8 @@ dnl require autoconf 2.60 (AS_ECHO/AS_ECHO_N) AC_PREREQ([2.60]) define(_CLIENT_VERSION_MAJOR, 0) define(_CLIENT_VERSION_MINOR, 12) -define(_CLIENT_VERSION_REVISION, 3) -define(_CLIENT_VERSION_BUILD, 1) +define(_CLIENT_VERSION_REVISION, 4) +define(_CLIENT_VERSION_BUILD, 0) define(_CLIENT_VERSION_IS_RELEASE, true) define(_COPYRIGHT_YEAR, 2018) AC_INIT([Paccoin Core],[_CLIENT_VERSION_MAJOR._CLIENT_VERSION_MINOR._CLIENT_VERSION_REVISION],[https://github.com/paccoinpay/paccoin/issues],[paccoincore]) diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include index 761a8aa7e42a9..8bb365e99f6e7 100644 --- a/src/Makefile.qt.include +++ b/src/Makefile.qt.include @@ -69,6 +69,10 @@ QT_MOC_CPP = \ qt/moc_overviewpage.cpp \ qt/moc_peertablemodel.cpp \ qt/moc_paymentserver.cpp \ + qt/moc_columnalignedlayout.cpp \ + qt/moc_proposalfilterproxy.cpp \ + qt/moc_proposaltablemodel.cpp \ + qt/moc_proposallist.cpp \ qt/moc_qvalidatedlineedit.cpp \ qt/moc_qvaluecombobox.cpp \ qt/moc_receivecoinsdialog.cpp \ @@ -142,6 +146,11 @@ BITCOIN_QT_H = \ qt/paymentserver.h \ qt/peertablemodel.h \ qt/platformstyle.h \ + qt/columnalignedlayout.h \ + qt/proposalfilterproxy.h \ + qt/proposallist.h \ + qt/proposalrecord.h \ + qt/proposaltablemodel.h \ qt/qvalidatedlineedit.h \ qt/qvaluecombobox.h \ qt/receivecoinsdialog.h \ @@ -226,6 +235,7 @@ RES_ICONS = \ qt/res/icons/drkblue/fontsmaller.png \ qt/res/icons/drkblue/transaction_abandoned.png \ qt/res/icons/drkblue/network_disabled.png \ + qt/res/icons/drkblue/proposal.png \ qt/res/icons/crownium/add.png \ qt/res/icons/crownium/address-book.png \ qt/res/icons/crownium/browse.png \ @@ -280,6 +290,7 @@ RES_ICONS = \ qt/res/icons/crownium/fontsmaller.png \ qt/res/icons/crownium/transaction_abandoned.png \ qt/res/icons/crownium/network_disabled.png \ + qt/res/icons/crownium/proposal.png \ qt/res/icons/light/add.png \ qt/res/icons/light/address-book.png \ qt/res/icons/light/browse.png \ @@ -334,6 +345,7 @@ RES_ICONS = \ qt/res/icons/light/fontsmaller.png \ qt/res/icons/light/transaction_abandoned.png \ qt/res/icons/light/network_disabled.png \ + qt/res/icons/light/proposal.png \ qt/res/icons/trad/add.png \ qt/res/icons/trad/address-book.png \ qt/res/icons/trad/browse.png \ @@ -387,7 +399,8 @@ RES_ICONS = \ qt/res/icons/trad/fontbigger.png \ qt/res/icons/trad/fontsmaller.png \ qt/res/icons/trad/transaction_abandoned.png \ - qt/res/icons/trad/network_disabled.png + qt/res/icons/trad/network_disabled.png \ + qt/res/icons/trad/proposal.png BITCOIN_QT_CPP = \ qt/bantablemodel.cpp \ @@ -432,6 +445,11 @@ BITCOIN_QT_CPP += \ qt/overviewpage.cpp \ qt/paymentrequestplus.cpp \ qt/paymentserver.cpp \ + qt/columnalignedlayout.cpp \ + qt/proposalfilterproxy.cpp \ + qt/proposallist.cpp \ + qt/proposalrecord.cpp \ + qt/proposaltablemodel.cpp \ qt/receivecoinsdialog.cpp \ qt/receiverequestdialog.cpp \ qt/recentrequeststablemodel.cpp \ diff --git a/src/clientversion.h b/src/clientversion.h index 87755e11be5ca..24bca9157eeb1 100644 --- a/src/clientversion.h +++ b/src/clientversion.h @@ -16,8 +16,8 @@ //! These need to be macros, as clientversion.cpp's and paccoin*-res.rc's voodoo requires it #define CLIENT_VERSION_MAJOR 0 #define CLIENT_VERSION_MINOR 12 -#define CLIENT_VERSION_REVISION 3 -#define CLIENT_VERSION_BUILD 1 +#define CLIENT_VERSION_REVISION 4 +#define CLIENT_VERSION_BUILD 0 //! Set to true for release, false for prerelease or test build #define CLIENT_VERSION_IS_RELEASE true diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index a0b66483fd210..de0c9f0cede77 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -91,6 +91,7 @@ BitcoinGUI::BitcoinGUI(const PlatformStyle *platformStyle, const NetworkStyle *n overviewAction(0), historyAction(0), masternodeAction(0), + proposalAction(0), quitAction(0), sendCoinsAction(0), sendCoinsMenuAction(0), @@ -354,6 +355,17 @@ void BitcoinGUI::createActions() connect(masternodeAction, SIGNAL(triggered()), this, SLOT(gotoMasternodePage())); } + proposalAction = new QAction(QIcon(":/icons/" + theme + "/proposal"), tr("&Proposals"), this); + proposalAction->setStatusTip(tr("Browse proposals")); + proposalAction->setToolTip(proposalAction->statusTip()); + proposalAction->setCheckable(true); +#ifdef Q_OS_MAC + proposalAction->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_6)); +#else + proposalAction->setShortcut(QKeySequence(Qt::ALT + Qt::Key_6)); +#endif + tabGroup->addAction(proposalAction); + // 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())); @@ -368,6 +380,8 @@ void BitcoinGUI::createActions() connect(receiveCoinsMenuAction, SIGNAL(triggered()), this, SLOT(gotoReceiveCoinsPage())); connect(historyAction, SIGNAL(triggered()), this, SLOT(showNormalIfMinimized())); connect(historyAction, SIGNAL(triggered()), this, SLOT(gotoHistoryPage())); + connect(proposalAction, SIGNAL(triggered()), this, SLOT(showNormalIfMinimized())); + connect(proposalAction, SIGNAL(triggered()), this, SLOT(gotoProposalPage())); #endif // ENABLE_WALLET quitAction = new QAction(QIcon(":/icons/" + theme + "/quit"), tr("E&xit"), this); @@ -573,6 +587,7 @@ void BitcoinGUI::createToolBars() { toolbar->addAction(masternodeAction); } + toolbar->addAction(proposalAction); toolbar->setMovable(false); // remove unused icon in upper left corner overviewAction->setChecked(true); @@ -713,6 +728,7 @@ void BitcoinGUI::setWalletActionsEnabled(bool enabled) receiveCoinsAction->setEnabled(enabled); receiveCoinsMenuAction->setEnabled(enabled); historyAction->setEnabled(enabled); + proposalAction->setEnabled(enabled); QSettings settings; if (settings.value("fShowMasternodesTab").toBool() && masternodeAction) { masternodeAction->setEnabled(enabled); @@ -892,6 +908,12 @@ void BitcoinGUI::gotoMasternodePage() } } +void BitcoinGUI::gotoProposalPage() +{ + proposalAction->setChecked(true); + if (walletFrame) walletFrame->gotoProposalPage(); +} + void BitcoinGUI::gotoReceiveCoinsPage() { receiveCoinsAction->setChecked(true); diff --git a/src/qt/bitcoingui.h b/src/qt/bitcoingui.h index 87ea2d9624f1b..36d42753d688a 100644 --- a/src/qt/bitcoingui.h +++ b/src/qt/bitcoingui.h @@ -32,6 +32,7 @@ class WalletModel; class HelpMessageDialog; class ModalOverlay; class MasternodeList; +class ProposalList; class CWallet; @@ -97,6 +98,7 @@ class BitcoinGUI : public QMainWindow QAction *overviewAction; QAction *historyAction; QAction *masternodeAction; + QAction *proposalAction; QAction *quitAction; QAction *sendCoinsAction; QAction *sendCoinsMenuAction; @@ -219,6 +221,8 @@ private Q_SLOTS: void gotoHistoryPage(); /** Switch to masternode page */ void gotoMasternodePage(); + /** Switch to proposal page */ + void gotoProposalPage(); /** Switch to receive coins page */ void gotoReceiveCoinsPage(); /** Switch to send coins page */ diff --git a/src/qt/columnalignedlayout.cpp b/src/qt/columnalignedlayout.cpp new file mode 100644 index 0000000000000..6336c26b9a5c5 --- /dev/null +++ b/src/qt/columnalignedlayout.cpp @@ -0,0 +1,38 @@ +#include "columnalignedlayout.h" +#include + +ColumnAlignedLayout::ColumnAlignedLayout() + : QHBoxLayout() +{ + +} + +ColumnAlignedLayout::ColumnAlignedLayout(QWidget *parent) + : QHBoxLayout(parent) +{ + +} + +void ColumnAlignedLayout::setGeometry(const QRect &r) +{ + QHBoxLayout::setGeometry(r); + + if (!headerView) { + return; + } + + int widgetX = parentWidget()->mapToGlobal(QPoint(0, 0)).x(); + int headerX = headerView->mapToGlobal(QPoint(0, 0)).x(); + int delta = headerX - widgetX; + + for (int ii = 0; ii < headerView->count(); ++ii) { + int pos = headerView->sectionViewportPosition(ii); + int size = headerView->sectionSize(ii); + + auto item = itemAt(ii); + auto r = item->geometry(); + r.setLeft(pos + delta); + r.setWidth(size); + item->setGeometry(r); + } +} \ No newline at end of file diff --git a/src/qt/columnalignedlayout.h b/src/qt/columnalignedlayout.h new file mode 100644 index 0000000000000..0390a77af1d70 --- /dev/null +++ b/src/qt/columnalignedlayout.h @@ -0,0 +1,21 @@ +#ifndef COLUMNALIGNEDLAYOUT_H +#define COLUMNALIGNEDLAYOUT_H + +#include + +class QHeaderView; + +class ColumnAlignedLayout : public QHBoxLayout +{ + Q_OBJECT +public: + ColumnAlignedLayout(); + explicit ColumnAlignedLayout(QWidget *parent); + void setTableColumnsToTrack(QHeaderView *view) { headerView = view; } + +private: + void setGeometry(const QRect &r); + QHeaderView *headerView; +}; + +#endif // COLUMNALIGNEDLAYOUT_H \ No newline at end of file diff --git a/src/qt/paccoin.qrc b/src/qt/paccoin.qrc index 878153183380a..08951f3a1454a 100644 --- a/src/qt/paccoin.qrc +++ b/src/qt/paccoin.qrc @@ -26,6 +26,7 @@ res/icons/drkblue/eye_plus.png res/icons/drkblue/configure.png res/icons/drkblue/receive.png + res/icons/drkblue/proposal.png res/icons/drkblue/editpaste.png res/icons/drkblue/editcopy.png res/icons/drkblue/add.png @@ -82,6 +83,7 @@ res/icons/crownium/eye_plus.png res/icons/crownium/configure.png res/icons/crownium/receive.png + res/icons/crownium/proposal.png res/icons/crownium/editpaste.png res/icons/crownium/editcopy.png res/icons/crownium/add.png @@ -138,6 +140,7 @@ res/icons/light/eye_plus.png res/icons/light/configure.png res/icons/light/receive.png + res/icons/light/proposal.png res/icons/light/editpaste.png res/icons/light/editcopy.png res/icons/light/add.png @@ -194,6 +197,7 @@ res/icons/trad/eye_plus.png res/icons/trad/configure.png res/icons/trad/receive.png + res/icons/trad/proposal.png res/icons/trad/editpaste.png res/icons/trad/editcopy.png res/icons/trad/add.png diff --git a/src/qt/proposalfilterproxy.cpp b/src/qt/proposalfilterproxy.cpp new file mode 100644 index 0000000000000..010bfa6b7c16e --- /dev/null +++ b/src/qt/proposalfilterproxy.cpp @@ -0,0 +1,112 @@ +// Copyright (c) 2011-2013 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "proposalfilterproxy.h" +#include "proposaltablemodel.h" + +#include + +#include + +const QDateTime ProposalFilterProxy::MIN_DATE = QDateTime::fromTime_t(0); +const QDateTime ProposalFilterProxy::MAX_DATE = QDateTime::fromTime_t(0xFFFFFFFF); + +ProposalFilterProxy::ProposalFilterProxy(QObject *parent) : + QSortFilterProxyModel(parent), + startDate(MIN_DATE), + endDate(MAX_DATE), + proposalName(), + minAmount(0), + minPercentage(-100), + minYesVotes(0), + minNoVotes(0), + minAbsoluteYesVotes(INT_MIN) +{ +} + +bool ProposalFilterProxy::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const +{ + QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent); + + int proposalStartDate = index.data(ProposalTableModel::StartDateRole).toInt(); + int proposalEndDate = index.data(ProposalTableModel::EndDateRole).toInt(); + QString propName = index.data(ProposalTableModel::ProposalRole).toString(); + qint64 amount = llabs(index.data(ProposalTableModel::AmountRole).toLongLong()); + int yesVotes = index.data(ProposalTableModel::YesVotesRole).toInt(); + int noVotes = index.data(ProposalTableModel::NoVotesRole).toInt(); + int absoluteYesVotes = index.data(ProposalTableModel::AbsoluteYesVotesRole).toInt(); + int percentage = index.data(ProposalTableModel::PercentageRole).toInt(); + + if(proposalStartDate < (startDate.toMSecsSinceEpoch() / 1000)) + return false; + if(proposalEndDate > (endDate.toMSecsSinceEpoch() / 1000)) + return false; + if(!propName.contains(proposalName, Qt::CaseInsensitive)) + return false; + if(amount < minAmount) + return false; + if(yesVotes < minYesVotes) + return false; + if(noVotes < minNoVotes) + return false; + if(absoluteYesVotes < minAbsoluteYesVotes) + return false; + if(percentage < minPercentage) + return false; + + return true; +} + +void ProposalFilterProxy::setProposalStart(const QDateTime &date) +{ + this->startDate = date; + invalidateFilter(); +} + +void ProposalFilterProxy::setProposalEnd(const QDateTime &date) +{ + this->endDate = date; + invalidateFilter(); +} + +void ProposalFilterProxy::setProposal(const QString &proposal) +{ + this->proposalName = proposal; + invalidateFilter(); +} + +void ProposalFilterProxy::setMinAmount(const CAmount& minimum) +{ + this->minAmount = minimum; + invalidateFilter(); +} + +void ProposalFilterProxy::setMinPercentage(const CAmount& minimum) +{ + this->minPercentage = minimum; + invalidateFilter(); +} + +void ProposalFilterProxy::setMinYesVotes(const CAmount& minimum) +{ + this->minYesVotes = minimum; + invalidateFilter(); +} + +void ProposalFilterProxy::setMinNoVotes(const CAmount& minimum) +{ + this->minNoVotes = minimum; + invalidateFilter(); +} + +void ProposalFilterProxy::setMinAbsoluteYesVotes(const CAmount& minimum) +{ + this->minAbsoluteYesVotes = minimum; + invalidateFilter(); +} + +int ProposalFilterProxy::rowCount(const QModelIndex &parent) const +{ + return QSortFilterProxyModel::rowCount(parent); +} \ No newline at end of file diff --git a/src/qt/proposalfilterproxy.h b/src/qt/proposalfilterproxy.h new file mode 100644 index 0000000000000..d8630d382f1f2 --- /dev/null +++ b/src/qt/proposalfilterproxy.h @@ -0,0 +1,51 @@ +// Copyright (c) 2011-2013 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_QT_PROPOSALFILTERPROXY_H +#define BITCOIN_QT_PROPOSALFILTERPROXY_H + +#include "amount.h" + +#include +#include + +class ProposalFilterProxy : public QSortFilterProxyModel +{ + Q_OBJECT + +public: + explicit ProposalFilterProxy(QObject *parent = 0); + + + static const QDateTime MIN_DATE; + + static const QDateTime MAX_DATE; + + void setProposalStart(const QDateTime &date); + void setProposalEnd(const QDateTime &date); + void setProposal(const QString &proposal); + + void setMinAmount(const CAmount& minimum); + void setMinPercentage(const CAmount& minimum); + void setMinYesVotes(const CAmount& minimum); + void setMinNoVotes(const CAmount& minimum); + void setMinAbsoluteYesVotes(const CAmount& minimum); + + int rowCount(const QModelIndex &parent = QModelIndex()) const; + +protected: + bool filterAcceptsRow(int source_row, const QModelIndex & source_parent) const; + +private: + QDateTime startDate; + QDateTime endDate; + QString proposalName; + CAmount minAmount; + CAmount minPercentage; + CAmount minYesVotes; + CAmount minNoVotes; + CAmount minAbsoluteYesVotes; +}; + +#endif // BITCOIN_QT_PROPOSALFILTERPROXY_H diff --git a/src/qt/proposallist.cpp b/src/qt/proposallist.cpp new file mode 100644 index 0000000000000..fc8de0232bf9a --- /dev/null +++ b/src/qt/proposallist.cpp @@ -0,0 +1,625 @@ +// Copyright (c) 2011-2015 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "proposallist.h" +#include "guiutil.h" +#include "platformstyle.h" +#include "optionsmodel.h" +#include "proposalfilterproxy.h" +#include "proposalrecord.h" +#include "proposaltablemodel.h" +#include "masternodeman.h" +#include "masternodeconfig.h" +#include "masternode.h" +#include "messagesigner.h" +#include "util.h" +#include "governance.h" + +#include "ui_interface.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** Date format for persistence */ +static const char* PERSISTENCE_DATE_FORMAT = "yyyy-MM-dd"; + +ProposalList::ProposalList(const PlatformStyle *platformStyle, QWidget *parent) : + QWidget(parent), proposalTableModel(0), proposalProxyModel(0), + proposalList(0), columnResizingFixer(0) +{ + proposalTableModel = new ProposalTableModel(platformStyle, this); + QSettings settings; + + setContentsMargins(0,0,0,0); + + hlayout = new ColumnAlignedLayout(); + hlayout->setContentsMargins(0,0,0,0); + hlayout->setSpacing(0); + + proposalWidget = new QLineEdit(this); +#if QT_VERSION >= 0x040700 + proposalWidget->setPlaceholderText(tr("Enter proposal name")); +#endif + proposalWidget->setObjectName("proposalWidget"); + hlayout->addWidget(proposalWidget); + + amountWidget = new QLineEdit(this); +#if QT_VERSION >= 0x040700 + amountWidget->setPlaceholderText(tr("Min amount")); +#endif + amountWidget->setValidator(new QDoubleValidator(0, 1e20, 8, this)); + amountWidget->setObjectName("amountWidget"); + hlayout->addWidget(amountWidget); + + startDateWidget = new QComboBox(this); + startDateWidget->addItem(tr("All"), All); + startDateWidget->addItem(tr("Today"), Today); + startDateWidget->addItem(tr("This week"), ThisWeek); + startDateWidget->addItem(tr("This month"), ThisMonth); + startDateWidget->addItem(tr("Last month"), LastMonth); + startDateWidget->addItem(tr("This year"), ThisYear); + startDateWidget->addItem(tr("Range..."), Range); + startDateWidget->setCurrentIndex(settings.value("proposalStartDateIndex").toInt()); + hlayout->addWidget(startDateWidget); + + endDateWidget = new QComboBox(this); + endDateWidget->addItem(tr("All"), All); + endDateWidget->addItem(tr("Today"), Today); + endDateWidget->addItem(tr("This week"), ThisWeek); + endDateWidget->addItem(tr("This month"), ThisMonth); + endDateWidget->addItem(tr("Last month"), LastMonth); + endDateWidget->addItem(tr("This year"), ThisYear); + endDateWidget->addItem(tr("Range..."), Range); + endDateWidget->setCurrentIndex(settings.value("proposalEndDateIndex").toInt()); + hlayout->addWidget(endDateWidget); + + yesVotesWidget = new QLineEdit(this); +#if QT_VERSION >= 0x040700 + yesVotesWidget->setPlaceholderText(tr("Min yes votes")); +#endif + yesVotesWidget->setValidator(new QIntValidator(0, INT_MAX, this)); + yesVotesWidget->setObjectName("yesVotesWidget"); + hlayout->addWidget(yesVotesWidget); + + noVotesWidget = new QLineEdit(this); +#if QT_VERSION >= 0x040700 + noVotesWidget->setPlaceholderText(tr("Min no votes")); +#endif + noVotesWidget->setValidator(new QIntValidator(0, INT_MAX, this)); + noVotesWidget->setObjectName("noVotesWidget"); + hlayout->addWidget(noVotesWidget); + + + absoluteYesVotesWidget = new QLineEdit(this); +#if QT_VERSION >= 0x040700 + absoluteYesVotesWidget->setPlaceholderText(tr("Min abs. yes votes")); +#endif + absoluteYesVotesWidget->setValidator(new QIntValidator(INT_MIN, INT_MAX, this)); + absoluteYesVotesWidget->setObjectName("absoluteYesVotesWidget"); + hlayout->addWidget(absoluteYesVotesWidget); + + percentageWidget = new QLineEdit(this); +#if QT_VERSION >= 0x040700 + percentageWidget->setPlaceholderText(tr("Min percentage")); +#endif + percentageWidget->setValidator(new QIntValidator(-100, 100, this)); + percentageWidget->setObjectName("percentageWidget"); + hlayout->addWidget(percentageWidget); + + QVBoxLayout *vlayout = new QVBoxLayout(this); + vlayout->setSpacing(0); + + QTableView *view = new QTableView(this); + vlayout->addLayout(hlayout); + vlayout->addWidget(createStartDateRangeWidget()); + vlayout->addWidget(createEndDateRangeWidget()); + vlayout->addWidget(view); + vlayout->setSpacing(0); + int width = view->verticalScrollBar()->sizeHint().width(); + hlayout->addSpacing(width); + hlayout->setTableColumnsToTrack(view->horizontalHeader()); + + connect(view->horizontalHeader(), SIGNAL(sectionResized(int,int,int)), SLOT(invalidateAlignedLayout())); + connect(view->horizontalScrollBar(), SIGNAL(valueChanged(int)), SLOT(invalidateAlignedLayout())); + + view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); + view->setTabKeyNavigation(false); + view->setContextMenuPolicy(Qt::CustomContextMenu); + + proposalList = view; + + QHBoxLayout *actionBar = new QHBoxLayout(); + actionBar->setSpacing(11); + actionBar->setContentsMargins(0,20,0,20); + + QPushButton *voteYesButton = new QPushButton(tr("Vote Yes"), this); + voteYesButton->setToolTip(tr("Vote Yes on the selected proposal")); + actionBar->addWidget(voteYesButton); + + QPushButton *voteAbstainButton = new QPushButton(tr("Vote Abstain"), this); + voteAbstainButton->setToolTip(tr("Vote Abstain on the selected proposal")); + actionBar->addWidget(voteAbstainButton); + + QPushButton *voteNoButton = new QPushButton(tr("Vote No"), this); + voteNoButton->setToolTip(tr("Vote No on the selected proposal")); + actionBar->addWidget(voteNoButton); + + secondsLabel = new QLabel(); + actionBar->addWidget(secondsLabel); + actionBar->addStretch(); + + vlayout->addLayout(actionBar); + + QAction *voteYesAction = new QAction(tr("Vote yes"), this); + QAction *voteAbstainAction = new QAction(tr("Vote abstain"), this); + QAction *voteNoAction = new QAction(tr("Vote no"), this); + QAction *openUrlAction = new QAction(tr("Visit proposal website"), this); + QAction *openStatisticsAction = new QAction(tr("Visit statistics website"), this); + + contextMenu = new QMenu(this); + contextMenu->addAction(voteYesAction); + contextMenu->addAction(voteAbstainAction); + contextMenu->addAction(voteNoAction); + contextMenu->addSeparator(); + contextMenu->addAction(openUrlAction); + contextMenu->addAction(openStatisticsAction); + + connect(voteYesButton, SIGNAL(clicked()), this, SLOT(voteYes())); + connect(voteAbstainButton, SIGNAL(clicked()), this, SLOT(voteAbstain())); + connect(voteNoButton, SIGNAL(clicked()), this, SLOT(voteNo())); + + connect(proposalWidget, SIGNAL(textChanged(QString)), this, SLOT(changedProposal(QString))); + connect(startDateWidget, SIGNAL(activated(int)), this, SLOT(chooseStartDate(int))); + connect(endDateWidget, SIGNAL(activated(int)), this, SLOT(chooseEndDate(int))); + connect(yesVotesWidget, SIGNAL(textChanged(QString)), this, SLOT(changedYesVotes(QString))); + connect(noVotesWidget, SIGNAL(textChanged(QString)), this, SLOT(changedNoVotes(QString))); + connect(absoluteYesVotesWidget, SIGNAL(textChanged(QString)), this, SLOT(changedAbsoluteYesVotes(QString))); + connect(yesVotesWidget, SIGNAL(textChanged(QString)), this, SLOT(changedYesVotes(QString))); + connect(amountWidget, SIGNAL(textChanged(QString)), this, SLOT(changedAmount(QString))); + connect(percentageWidget, SIGNAL(textChanged(QString)), this, SLOT(changedPercentage(QString))); + + connect(view, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(openProposalUrl())); + connect(view, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(contextualMenu(QPoint))); + + connect(voteYesAction, SIGNAL(triggered()), this, SLOT(voteYes())); + connect(voteNoAction, SIGNAL(triggered()), this, SLOT(voteNo())); + connect(voteAbstainAction, SIGNAL(triggered()), this, SLOT(voteAbstain())); + + connect(openUrlAction, SIGNAL(triggered()), this, SLOT(openProposalUrl())); + connect(openStatisticsAction, SIGNAL(triggered()), this, SLOT(openStatisticsUrl())); + + proposalProxyModel = new ProposalFilterProxy(this); + proposalProxyModel->setSourceModel(proposalTableModel); + proposalProxyModel->setDynamicSortFilter(true); + proposalProxyModel->setSortCaseSensitivity(Qt::CaseInsensitive); + proposalProxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive); + + proposalProxyModel->setSortRole(Qt::EditRole); + + proposalList->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); + proposalList->setModel(proposalProxyModel); + proposalList->setAlternatingRowColors(true); + proposalList->setSelectionBehavior(QAbstractItemView::SelectRows); + proposalList->setSortingEnabled(true); + proposalList->sortByColumn(ProposalTableModel::StartDate, Qt::DescendingOrder); + proposalList->verticalHeader()->hide(); + + proposalList->setColumnWidth(ProposalTableModel::Proposal, PROPOSAL_COLUMN_WIDTH); + proposalList->setColumnWidth(ProposalTableModel::StartDate, START_DATE_COLUMN_WIDTH); + proposalList->setColumnWidth(ProposalTableModel::EndDate, END_DATE_COLUMN_WIDTH); + proposalList->setColumnWidth(ProposalTableModel::YesVotes, YES_VOTES_COLUMN_WIDTH); + proposalList->setColumnWidth(ProposalTableModel::NoVotes, NO_VOTES_COLUMN_WIDTH); + proposalList->setColumnWidth(ProposalTableModel::AbsoluteYesVotes, ABSOLUTE_YES_COLUMN_WIDTH); + proposalList->setColumnWidth(ProposalTableModel::Amount, AMOUNT_COLUMN_WIDTH); + proposalList->setColumnWidth(ProposalTableModel::Percentage, PERCENTAGE_COLUMN_WIDTH); + + connect(proposalList->selectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)), this, SLOT(computeSum())); + + columnResizingFixer = new GUIUtil::TableViewLastColumnResizingFixer(proposalList, PERCENTAGE_COLUMN_WIDTH, MINIMUM_COLUMN_WIDTH, this); + + chooseStartDate(settings.value("proposalStartDate").toInt()); + chooseEndDate(settings.value("proposalEndDate").toInt()); + + nLastUpdate = GetTime(); + + timer = new QTimer(this); + connect(timer, SIGNAL(timeout()), this, SLOT(refreshProposals())); + timer->start(1000); + + setLayout(vlayout); +} + +void ProposalList::invalidateAlignedLayout() { + hlayout->invalidate(); +} + +void ProposalList::refreshProposals(bool force) { + int64_t secondsRemaining = nLastUpdate - GetTime() + PROPOSALLIST_UPDATE_SECONDS; + + QString secOrMinutes = ((((double)(secondsRemaining / 60))) >= 1) ? tr("minute(s)") : tr("second(s)"); + secondsLabel->setText(tr("List will be updated in %1 %2").arg((secondsRemaining >= 60) ? QString::number(secondsRemaining / 60) : QString::number(secondsRemaining), secOrMinutes)); + + if(secondsRemaining > 0 && !force) return; + nLastUpdate = GetTime(); + + proposalTableModel->refreshProposals(); + + secondsLabel->setText(tr("List will be updated in 0 second(s)")); +} + +void ProposalList::chooseStartDate(int idx) +{ + if(!proposalProxyModel) + return; + + QSettings settings; + QDate current = QDate::currentDate(); + startDateRangeWidget->setVisible(false); + switch(startDateWidget->itemData(idx).toInt()) + { + case All: + proposalProxyModel->setProposalStart( + ProposalFilterProxy::MIN_DATE); + break; + case Today: + proposalProxyModel->setProposalStart( + QDateTime(current)); + break; + case ThisWeek: { + QDate startOfWeek = current.addDays(-(current.dayOfWeek()-1)); + proposalProxyModel->setProposalStart( + QDateTime(startOfWeek)); + + } break; + case ThisMonth: + proposalProxyModel->setProposalStart( + QDateTime(QDate(current.year(), current.month(), 1))); + break; + case LastMonth: + proposalProxyModel->setProposalStart( + QDateTime(QDate(current.year(), current.month(), 1).addMonths(-1))); + break; + case ThisYear: + proposalProxyModel->setProposalStart( + QDateTime(QDate(current.year(), 1, 1))); + break; + case Range: + startDateRangeWidget->setVisible(true); + startDateRangeChanged(); + break; + } + + settings.setValue("proposalStartDateIndex", idx); + if (startDateWidget->itemData(idx).toInt() == Range) + settings.setValue("proposalStartDate", proposalStartDate->date().toString(PERSISTENCE_DATE_FORMAT)); +} + +void ProposalList::chooseEndDate(int idx) +{ + if(!proposalProxyModel) + return; + + QSettings settings; + QDate current = QDate::currentDate(); + endDateRangeWidget->setVisible(false); + switch(endDateWidget->itemData(idx).toInt()) + { + case All: + proposalProxyModel->setProposalEnd( + ProposalFilterProxy::MAX_DATE); + break; + case Today: + proposalProxyModel->setProposalEnd( + QDateTime(current)); + break; + case ThisWeek: { + QDate startOfWeek = current.addDays(-(current.dayOfWeek()-1)); + proposalProxyModel->setProposalEnd( + QDateTime(startOfWeek)); + + } break; + case ThisMonth: + proposalProxyModel->setProposalEnd( + QDateTime(QDate(current.year(), current.month(), 1))); + break; + case LastMonth: + proposalProxyModel->setProposalEnd( + QDateTime(QDate(current.year(), current.month(), 1).addMonths(-1))); + break; + case ThisYear: + proposalProxyModel->setProposalEnd( + QDateTime(QDate(current.year(), 1, 1))); + break; + case Range: + endDateRangeWidget->setVisible(true); + endDateRangeChanged(); + break; + } + + settings.setValue("proposalEndDateIndex", idx); + if (endDateWidget->itemData(idx).toInt() == Range) + settings.setValue("proposalEndDate", proposalEndDate->date().toString(PERSISTENCE_DATE_FORMAT)); +} + + +void ProposalList::changedAmount(const QString &minAmount) +{ + if(!proposalProxyModel) + return; + + proposalProxyModel->setMinAmount(minAmount.toInt()); +} + +void ProposalList::changedPercentage(const QString &minPercentage) +{ + if(!proposalProxyModel) + return; + + int value = minPercentage == "" ? -100 : minPercentage.toInt(); + + proposalProxyModel->setMinPercentage(value); +} + +void ProposalList::changedProposal(const QString &proposal) +{ + if(!proposalProxyModel) + return; + + proposalProxyModel->setProposal(proposal); +} + +void ProposalList::changedYesVotes(const QString &minYesVotes) +{ + if(!proposalProxyModel) + return; + + proposalProxyModel->setMinYesVotes(minYesVotes.toInt()); +} + +void ProposalList::changedNoVotes(const QString &minNoVotes) +{ + if(!proposalProxyModel) + return; + + proposalProxyModel->setMinNoVotes(minNoVotes.toInt()); +} + +void ProposalList::changedAbsoluteYesVotes(const QString &minAbsoluteYesVotes) +{ + if(!proposalProxyModel) + return; + + int value = minAbsoluteYesVotes == "" ? INT_MIN : minAbsoluteYesVotes.toInt(); + + proposalProxyModel->setMinAbsoluteYesVotes(value); +} + +void ProposalList::contextualMenu(const QPoint &point) +{ + QModelIndex index = proposalList->indexAt(point); + QModelIndexList selection = proposalList->selectionModel()->selectedRows(0); + if (selection.empty()) + return; + + if(index.isValid()) + contextMenu->exec(QCursor::pos()); +} + +void ProposalList::voteYes() +{ + vote_click_handler("yes"); +} + +void ProposalList::voteNo() +{ + vote_click_handler("no"); +} + +void ProposalList::voteAbstain() +{ + vote_click_handler("abstain"); +} + +void ProposalList::vote_click_handler(const std::string voteString) +{ + if(!proposalList->selectionModel()) + return; + + QModelIndexList selection = proposalList->selectionModel()->selectedRows(); + if(selection.empty()) + return; + + QString proposalName = selection.at(0).data(ProposalTableModel::ProposalRole).toString(); + + QMessageBox::StandardButton retval = QMessageBox::question(this, tr("Confirm vote"), + tr("Are you sure you want to vote %1 on the proposal %2?").arg(QString::fromStdString(voteString), proposalName), + QMessageBox::Yes | QMessageBox::Cancel, + QMessageBox::Cancel); + + if(retval != QMessageBox::Yes) return; + + uint256 hash; + hash.SetHex(selection.at(0).data(ProposalTableModel::ProposalHashRole).toString().toStdString()); + + vote_signal_enum_t eVoteSignal = CGovernanceVoting::ConvertVoteSignal("funding"); + vote_outcome_enum_t eVoteOutcome = CGovernanceVoting::ConvertVoteOutcome(voteString); + + int nSuccessful = 0; + int nFailed = 0; + + std::vector mnEntries; + mnEntries = masternodeConfig.getEntries(); + + UniValue resultsObj(UniValue::VOBJ); + + BOOST_FOREACH(CMasternodeConfig::CMasternodeEntry mne, masternodeConfig.getEntries()) { + CPubKey pubKeyMasternode; + CKey keyMasternode; + + UniValue statusObj(UniValue::VOBJ); + + if(!CMessageSigner::GetKeysFromSecret(mne.getPrivKey(), keyMasternode, pubKeyMasternode)){ + nFailed++; + continue; + } + + uint256 nTxHash; + nTxHash.SetHex(mne.getTxHash()); + + int nOutputIndex = 0; + if(!ParseInt32(mne.getOutputIndex(), &nOutputIndex)) { + continue; + } + + COutPoint outpoint(nTxHash, nOutputIndex); + + CMasternode mn; + bool fMnFound = mnodeman.Get(outpoint, mn); + + if(!fMnFound) { + nFailed++; + continue; + } + + CGovernanceVote vote(mn.vin.prevout, hash, eVoteSignal, eVoteOutcome); + if(!vote.Sign(keyMasternode, pubKeyMasternode)){ + nFailed++; + continue; + } + + CGovernanceException exception; + if(governance.ProcessVoteAndRelay(vote, exception, *g_connman)) { + nSuccessful++; + } else { + nFailed++; + } + } + + QMessageBox::information(this, tr("Voting"), + tr("You voted %1 %2 time(s) successfully and failed %3 time(s) on %4").arg(QString::fromStdString(voteString), QString::number(nSuccessful), QString::number(nFailed), proposalName)); + + refreshProposals(true); +} + +void ProposalList::openProposalUrl() +{ + if(!proposalList || !proposalList->selectionModel()) + return; + + QModelIndexList selection = proposalList->selectionModel()->selectedRows(0); + if(!selection.isEmpty()) + QDesktopServices::openUrl(selection.at(0).data(ProposalTableModel::ProposalUrlRole).toString()); +} + +void ProposalList::openStatisticsUrl() +{ + if(!proposalList || !proposalList->selectionModel()) + return; + + QModelIndexList selection = proposalList->selectionModel()->selectedRows(0); + if(!selection.isEmpty()) + QDesktopServices::openUrl(QString("http://stats.foxrtb.com/proposal.html?id=%1&title=%2").arg(selection.at(0).data(ProposalTableModel::ProposalHashRole).toString(), selection.at(0).data(ProposalTableModel::ProposalRole).toString())); +} + +QWidget *ProposalList::createStartDateRangeWidget() +{ + QString defaultDate = QDate::currentDate().toString(PERSISTENCE_DATE_FORMAT); + QSettings settings; + + startDateRangeWidget = new QFrame(); + startDateRangeWidget->setFrameStyle(QFrame::Panel | QFrame::Raised); + startDateRangeWidget->setContentsMargins(1,1,1,1); + QHBoxLayout *layout = new QHBoxLayout(startDateRangeWidget); + layout->setContentsMargins(0,0,0,0); + layout->addSpacing(23); + layout->addWidget(new QLabel(tr("Start Date:"))); + + proposalStartDate = new QDateTimeEdit(this); + proposalStartDate->setCalendarPopup(true); + proposalStartDate->setMinimumWidth(100); + + proposalStartDate->setDate(QDate::fromString(settings.value("proposalStartDate", defaultDate).toString(), PERSISTENCE_DATE_FORMAT)); + + layout->addWidget(proposalStartDate); + layout->addStretch(); + + startDateRangeWidget->setVisible(false); + + connect(proposalStartDate, SIGNAL(dateChanged(QDate)), this, SLOT(startDateRangeChanged())); + + return startDateRangeWidget; +} + +QWidget *ProposalList::createEndDateRangeWidget() +{ + QString defaultDate = QDate::currentDate().toString(PERSISTENCE_DATE_FORMAT); + QSettings settings; + + endDateRangeWidget = new QFrame(); + endDateRangeWidget->setFrameStyle(QFrame::Panel | QFrame::Raised); + endDateRangeWidget->setContentsMargins(1,1,1,1); + QHBoxLayout *layout = new QHBoxLayout(endDateRangeWidget); + layout->setContentsMargins(0,0,0,0); + layout->addSpacing(23); + layout->addWidget(new QLabel(tr("End Date:"))); + + proposalEndDate = new QDateTimeEdit(this); + proposalEndDate->setCalendarPopup(true); + proposalEndDate->setMinimumWidth(100); + + proposalEndDate->setDate(QDate::fromString(settings.value("proposlEndDate", defaultDate).toString(), PERSISTENCE_DATE_FORMAT)); + + layout->addWidget(proposalEndDate); + layout->addStretch(); + + endDateRangeWidget->setVisible(false); + + connect(proposalEndDate, SIGNAL(dateChanged(QDate)), this, SLOT(endDateRangeChanged())); + + return endDateRangeWidget; +} + +void ProposalList::startDateRangeChanged() +{ + if(!proposalProxyModel) + return; + + QSettings settings; + settings.setValue("proposalStartDate", proposalStartDate->date().toString(PERSISTENCE_DATE_FORMAT)); + + proposalProxyModel->setProposalStart( + QDateTime(proposalStartDate->date())); +} + +void ProposalList::endDateRangeChanged() +{ + if(!proposalProxyModel) + return; + + QSettings settings; + settings.setValue("proposalEndDate", proposalEndDate->date().toString(PERSISTENCE_DATE_FORMAT)); + + proposalProxyModel->setProposalEnd( + QDateTime(proposalEndDate->date())); +} + +void ProposalList::resizeEvent(QResizeEvent* event) +{ + QWidget::resizeEvent(event); + columnResizingFixer->stretchColumnWidth(ProposalTableModel::Proposal); +} diff --git a/src/qt/proposallist.h b/src/qt/proposallist.h new file mode 100644 index 0000000000000..3e166012e7cf1 --- /dev/null +++ b/src/qt/proposallist.h @@ -0,0 +1,126 @@ +// Copyright (c) 2011-2015 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_QT_PROPOSALLIST_H +#define BITCOIN_QT_PROPOSALLIST_H + +#include "guiutil.h" +#include "proposaltablemodel.h" +#include "columnalignedlayout.h" +#include +#include +#include + +class PlatformStyle; +class ProposalFilterProxy; + +QT_BEGIN_NAMESPACE +class QComboBox; +class QDateTimeEdit; +class QFrame; +class QItemSelectionModel; +class QLineEdit; +class QMenu; +class QModelIndex; +class QSignalMapper; +class QTableView; +QT_END_NAMESPACE + +#define PROPOSALLIST_UPDATE_SECONDS 300 + +class ProposalList : public QWidget +{ + Q_OBJECT + +public: + explicit ProposalList(const PlatformStyle *platformStyle, QWidget *parent = 0); + + void setModel(); + + enum DateEnum + { + All, + Today, + ThisWeek, + ThisMonth, + LastMonth, + ThisYear, + Range + }; + + enum ColumnWidths { + PROPOSAL_COLUMN_WIDTH = 380, + START_DATE_COLUMN_WIDTH = 110, + END_DATE_COLUMN_WIDTH = 110, + YES_VOTES_COLUMN_WIDTH = 60, + NO_VOTES_COLUMN_WIDTH = 60, + ABSOLUTE_YES_COLUMN_WIDTH = 60, + AMOUNT_COLUMN_WIDTH = 100, + PERCENTAGE_COLUMN_WIDTH = 80, + MINIMUM_COLUMN_WIDTH = 23 + }; + +private: + ProposalTableModel *proposalTableModel; + ProposalFilterProxy *proposalProxyModel; + QTableView *proposalList; + int64_t nLastUpdate = 0; + + QLineEdit *proposalWidget; + QComboBox *startDateWidget; + QComboBox *endDateWidget; + QTimer *timer; + + QLineEdit *yesVotesWidget; + QLineEdit *noVotesWidget; + QLineEdit *absoluteYesVotesWidget; + QLineEdit *amountWidget; + QLineEdit *percentageWidget; + QLabel *secondsLabel; + + QMenu *contextMenu; + + QFrame *startDateRangeWidget; + QDateTimeEdit *proposalStartDate; + + QFrame *endDateRangeWidget; + QDateTimeEdit *proposalEndDate; + ColumnAlignedLayout *hlayout; + + QWidget *createStartDateRangeWidget(); + QWidget *createEndDateRangeWidget(); + void vote_click_handler(const std::string voteString); + + GUIUtil::TableViewLastColumnResizingFixer *columnResizingFixer; + + virtual void resizeEvent(QResizeEvent* event); + +private Q_SLOTS: + void contextualMenu(const QPoint &); + void startDateRangeChanged(); + void endDateRangeChanged(); + void voteYes(); + void voteNo(); + void voteAbstain(); + void openProposalUrl(); + void openStatisticsUrl(); + void invalidateAlignedLayout(); + +Q_SIGNALS: + void doubleClicked(const QModelIndex&); + +public Q_SLOTS: + void refreshProposals(bool force = false); + void changedProposal(const QString &proposal); + void chooseStartDate(int idx); + void chooseEndDate(int idx); + void changedYesVotes(const QString &minYesVotes); + void changedNoVotes(const QString &minNoVotes); + void changedAbsoluteYesVotes(const QString &minAbsoluteYesVotes); + void changedPercentage(const QString &minPercentage); + void changedAmount(const QString &minAmount); + +}; + +#endif // BITCOIN_QT_PROPOSALLIST_H diff --git a/src/qt/proposalrecord.cpp b/src/qt/proposalrecord.cpp new file mode 100644 index 0000000000000..518c47b69dd47 --- /dev/null +++ b/src/qt/proposalrecord.cpp @@ -0,0 +1,6 @@ +// Copyright (c) 2011-2015 The Bitcoin Core developers +// Copyright (c) 2014-2017 The Dash Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "proposalrecord.h" diff --git a/src/qt/proposalrecord.h b/src/qt/proposalrecord.h new file mode 100644 index 0000000000000..733fc000792d9 --- /dev/null +++ b/src/qt/proposalrecord.h @@ -0,0 +1,45 @@ +// Copyright (c) 2011-2013 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_QT_PROPOSALRECORD_H +#define BITCOIN_QT_PROPOSALRECORD_H + +#include "amount.h" +#include "uint256.h" + +#include +#include + +class CWallet; + +class ProposalRecord +{ +public: + ProposalRecord(): + hash(""), start_epoch(0), end_epoch(0), url(""), name(""), yesVotes(0), noVotes(0), absoluteYesVotes(0), amount(0), percentage(0) + { + } + + ProposalRecord(QString hash, qint64 start_epoch, qint64 end_epoch, + QString url, QString name, + const CAmount& yesVotes, const CAmount& noVotes, const CAmount& absoluteYesVotes, + const CAmount& amount, const CAmount& percentage): + hash(hash), start_epoch(start_epoch), end_epoch(end_epoch), url(url), name(name), yesVotes(yesVotes), noVotes(noVotes), + absoluteYesVotes(absoluteYesVotes), amount(amount), percentage(percentage) + { + } + + QString hash; + qint64 start_epoch; + qint64 end_epoch; + QString url; + QString name; + CAmount yesVotes; + CAmount noVotes; + CAmount absoluteYesVotes; + CAmount amount; + CAmount percentage; +}; + +#endif // BITCOIN_QT_PROPOSALRECORD_H diff --git a/src/qt/proposaltablemodel.cpp b/src/qt/proposaltablemodel.cpp new file mode 100644 index 0000000000000..a70a59b307b37 --- /dev/null +++ b/src/qt/proposaltablemodel.cpp @@ -0,0 +1,242 @@ +// Copyright (c) 2011-2015 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include "proposaltablemodel.h" + +#include "governance.h" +#include "governance-vote.h" +#include "governance-classes.h" +#include "governance-validators.h" +#include "guiconstants.h" +#include "guiutil.h" +#include "optionsmodel.h" +#include "platformstyle.h" +#include "proposalrecord.h" +#include "masternodeman.h" + +#include "core_io.h" +#include "validation.h" +#include "sync.h" +#include "uint256.h" +#include "util.h" + +#include +#include +#include +#include +#include +#include + +#include + +static int column_alignments[] = { + Qt::AlignLeft|Qt::AlignVCenter, + Qt::AlignRight|Qt::AlignVCenter, + Qt::AlignRight|Qt::AlignVCenter, + Qt::AlignRight|Qt::AlignVCenter, + Qt::AlignRight|Qt::AlignVCenter, + Qt::AlignRight|Qt::AlignVCenter, + Qt::AlignRight|Qt::AlignVCenter, + Qt::AlignRight|Qt::AlignVCenter + }; + +ProposalTableModel::ProposalTableModel(const PlatformStyle *platformStyle, QObject *parent): + QAbstractTableModel(parent), + platformStyle(platformStyle) +{ + columns << tr("Proposal") << tr("Amount") << tr("Start Date") << tr("End Date") << tr("Yes") << tr("No") << tr("Abs. Yes") << tr("Percentage"); + + refreshProposals(); +} + +ProposalTableModel::~ProposalTableModel() +{ +} + +void ProposalTableModel::refreshProposals() { + beginResetModel(); + proposalRecords.clear(); + + int mnCount = mnodeman.CountEnabled(); + + std::vector objs = governance.GetAllNewerThan(0); + + BOOST_FOREACH(CGovernanceObject* pGovObj, objs) + { + if(pGovObj->GetObjectType() != GOVERNANCE_OBJECT_PROPOSAL) continue; + + UniValue objResult(UniValue::VOBJ); + UniValue dataObj(UniValue::VOBJ); + objResult.read(pGovObj->GetDataAsString()); + + std::vector arr1 = objResult.getValues(); + std::vector arr2 = arr1.at( 0 ).getValues(); + dataObj = arr2.at( 1 ); + + int percentage = 0; + if(mnCount > 0) percentage = round(pGovObj->GetAbsoluteYesCount(VOTE_SIGNAL_FUNDING) * 100 / mnCount); + + proposalRecords.append(new ProposalRecord( + QString::fromStdString(pGovObj->GetHash().ToString()), + dataObj["start_epoch"].get_int(), + dataObj["end_epoch"].get_int(), + QString::fromStdString(dataObj["url"].get_str()), + QString::fromStdString(dataObj["name"].get_str()), + pGovObj->GetYesCount(VOTE_SIGNAL_FUNDING), + pGovObj->GetNoCount(VOTE_SIGNAL_FUNDING), + pGovObj->GetAbsoluteYesCount(VOTE_SIGNAL_FUNDING), + dataObj["payment_amount"].get_int(), + percentage)); + } + endResetModel(); +} + +int ProposalTableModel::rowCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent); + return proposalRecords.size(); +} + +int ProposalTableModel::columnCount(const QModelIndex &parent) const +{ + Q_UNUSED(parent); + return columns.length(); +} + +QVariant ProposalTableModel::data(const QModelIndex &index, int role) const +{ + if(!index.isValid()) + return QVariant(); + ProposalRecord *rec = static_cast(index.internalPointer()); + + switch(role) + { + case Qt::DisplayRole: + switch(index.column()) + { + case Proposal: + return rec->name; + case YesVotes: + return QVariant::fromValue(rec->yesVotes); + case NoVotes: + return QVariant::fromValue(rec->noVotes); + case AbsoluteYesVotes: + return QVariant::fromValue(rec->absoluteYesVotes); + case StartDate: + return (QDateTime::fromTime_t((qint32)rec->start_epoch)).date().toString(Qt::SystemLocaleLongDate); + case EndDate: + return (QDateTime::fromTime_t((qint32)rec->end_epoch)).date().toString(Qt::SystemLocaleLongDate); + case Percentage: + return QString("%1\%").arg(rec->percentage); + case Amount: + return BitcoinUnits::format(BitcoinUnits::duffs, rec->amount); + } + break; + case Qt::EditRole: + switch(index.column()) + { + case Proposal: + return rec->name; + case StartDate: + return rec->start_epoch; + case EndDate: + return rec->end_epoch; + case YesVotes: + return QVariant::fromValue(rec->yesVotes); + case NoVotes: + return QVariant::fromValue(rec->noVotes); + case AbsoluteYesVotes: + return QVariant::fromValue(rec->absoluteYesVotes); + case Amount: + return qint64(rec->amount); + case Percentage: + return QVariant::fromValue(rec->percentage); + } + break; + case Qt::TextAlignmentRole: + return column_alignments[index.column()]; + case Qt::ForegroundRole: + if(index.column() == Percentage) { + if(rec->percentage < 10) { + return COLOR_NEGATIVE; + } else { + return QColor(23, 168, 26); + } + } + + return COLOR_BAREADDRESS; + break; + case ProposalRole: + return rec->name; + case AmountRole: + return QVariant::fromValue(rec->amount); + case StartDateRole: + return rec->start_epoch; + case EndDateRole: + return rec->end_epoch; + case YesVotesRole: + return QVariant::fromValue(rec->yesVotes); + case NoVotesRole: + return QVariant::fromValue(rec->noVotes); + case AbsoluteYesVotesRole: + return QVariant::fromValue(rec->absoluteYesVotes); + case PercentageRole: + return QVariant::fromValue(rec->percentage); + case ProposalUrlRole: + return rec->url; + case ProposalHashRole: + return rec->hash; + } + return QVariant(); +} + +QVariant ProposalTableModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if(orientation == Qt::Horizontal) + { + if(role == Qt::DisplayRole) + { + return columns[section]; + } + else if (role == Qt::TextAlignmentRole) + { + return Qt::AlignCenter; + } + else if (role == Qt::ToolTipRole) + { + switch(section) + { + case Proposal: + return tr("Proposal Name"); + case StartDate: + return tr("Date and time that the proposal starts."); + case EndDate: + return tr("Date and time that the proposal ends."); + case YesVotes: + return tr("Obtained yes votes."); + case NoVotes: + return tr("Obtained no votes."); + case AbsoluteYesVotes: + return tr("Obtained absolute yes votes."); + case Amount: + return tr("Proposed amount."); + case Percentage: + return tr("Current vote percentage."); + } + } + } + return QVariant(); +} + +QModelIndex ProposalTableModel::index(int row, int column, const QModelIndex &parent) const +{ + Q_UNUSED(parent); + + if(row >= 0 && row < proposalRecords.size()) { + ProposalRecord *rec = proposalRecords[row]; + return createIndex(row, column, rec); + } + + return QModelIndex(); +} \ No newline at end of file diff --git a/src/qt/proposaltablemodel.h b/src/qt/proposaltablemodel.h new file mode 100644 index 0000000000000..ff294f2506cc7 --- /dev/null +++ b/src/qt/proposaltablemodel.h @@ -0,0 +1,66 @@ +// Copyright (c) 2011-2015 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_QT_PROPOSALTABLEMODEL_H +#define BITCOIN_QT_PROPOSALTABLEMODEL_H + +#include "bitcoinunits.h" + +#include +#include +#include +#include +#include +#include + +class PlatformStyle; +class ProposalRecord; +class ProposalTablePriv; + +class ProposalTableModel : public QAbstractTableModel +{ + Q_OBJECT + +public: + explicit ProposalTableModel(const PlatformStyle *platformStyle, QObject *parent = 0); + ~ProposalTableModel(); + + enum ColumnIndex { + Proposal = 0, + Amount = 1, + StartDate = 2, + EndDate = 3, + YesVotes = 4, + NoVotes = 5, + AbsoluteYesVotes = 6, + Percentage = 7 + }; + + enum RoleIndex { + ProposalRole = Qt::UserRole, + AmountRole, + StartDateRole, + EndDateRole, + YesVotesRole, + NoVotesRole, + AbsoluteYesVotesRole, + PercentageRole, + ProposalUrlRole, + ProposalHashRole, + }; + + int rowCount(const QModelIndex &parent) const; + int columnCount(const QModelIndex &parent) const; + void refreshProposals(); + QVariant data(const QModelIndex &index, int role) const; + QVariant headerData(int section, Qt::Orientation orientation, int role) const; + QModelIndex index(int row, int column, const QModelIndex & parent = QModelIndex()) const; + +private: + QList proposalRecords; + QStringList columns; + const PlatformStyle *platformStyle; +}; + +#endif // BITCOIN_QT_PROPOSALTABLEMODEL_H diff --git a/src/qt/res/icons/crownium/proposal.png b/src/qt/res/icons/crownium/proposal.png new file mode 100644 index 0000000000000000000000000000000000000000..73475989c45b0b27323bad7dbcf9268d3e67e47f GIT binary patch literal 1103 zcmV-V1hD&wP)@V_<-xTPi`=8!2rT zq<~tr5tSTORaH@mp;J20NJL0f9xBl-fDjfDqz`juH5(n|CB$S|LN*j?nv zvh#cQ-uHYn`8Cy^5}O+^d5=$b_b^(cltLSWQY!mj8w~JdW1k;>{JG>lmOVC-TCc!k zm4`9Z9JtW_me7Bw|BoSvqU4o8InI4m;SmU-u%91aIhcXqga`%Cus<0z$A zUjCcfS|4L$WBl{%S;>Sh<^Yh^g|rX|@rsm9O-=On_Tu;Zn4O(vXJ;>G^ZWg@wY33| z$>?+IE=@vR5P-@kw9YyU1m0nEbd-UC0RR>j9utor;&!{ae*JyM$8XWx+?)+SYlNtf z140T4Iq3Czsjsic?RH~X4%XJza<;*b25~x_?C_|>gwvKu6{dDnN$gHd3D zCkqRYNhXtdM^|v;#s>@y4PjY%uC+!wtSgj2XJH!{9OREjk2pCwp|i7-gM$OMwzlwk zy@Wy`q?9x^HqzbQO@DtsRaI35y}=-)s8k0WD6Ie|;c%F#sVP=gR(S9r%J%j)06jfD zOiWCWNF?z2e7Ib$lD@Rc*pg6Q1_6MMjt;`%Fh@uK=H{GECmxT7Kp^ZrmL%qNF+ie5-F;+J(5>A1%($2fR>gP zCMPE=hFKe9E5-8SW2j$CJ2qF6Vj(bR2#gF5UwX5dX!P9sGtnr<7=)Bp=fF%fT2aQO z-mkbD3i(nADayzWky5sD!CMz!nM)&ZkyAU{6*wSqP@V&JS-+f{iAJ-1u+kD}t?~P6 zE(K#L`?vS+=hV^T@nTGc?N}MXlZ{vl~NqU4|CrI#%T1J zJ$wEohexNRwW#dNX#TG)^V6@tVF_FGa#@)I!OB-Y1EkW1Pd*;tvq*<{^D$aD{sY)N VvKq+3O@V_<-xTPi`=8!2rT zq<~tr5tSTORaH@mp;J20NJL0f9xBl-fDjfDqz`juH5(n|CB$S|LN*j?nv zvh#cQ-uHYn`8Cy^5}O+^d5=$b_b^(cltLSWQY!mj8w~JdW1k;>{JG>lmOVC-TCc!k zm4`9Z9JtW_me7Bw|BoSvqU4o8InI4m;SmU-u%91aIhcXqga`%Cus<0z$A zUjCcfS|4L$WBl{%S;>Sh<^Yh^g|rX|@rsm9O-=On_Tu;Zn4O(vXJ;>G^ZWg@wY33| z$>?+IE=@vR5P-@kw9YyU1m0nEbd-UC0RR>j9utor;&!{ae*JyM$8XWx+?)+SYlNtf z140T4Iq3Czsjsic?RH~X4%XJza<;*b25~x_?C_|>gwvKu6{dDnN$gHd3D zCkqRYNhXtdM^|v;#s>@y4PjY%uC+!wtSgj2XJH!{9OREjk2pCwp|i7-gM$OMwzlwk zy@Wy`q?9x^HqzbQO@DtsRaI35y}=-)s8k0WD6Ie|;c%F#sVP=gR(S9r%J%j)06jfD zOiWCWNF?z2e7Ib$lD@Rc*pg6Q1_6MMjt;`%Fh@uK=H{GECmxT7Kp^ZrmL%qNF+ie5-F;+J(5>A1%($2fR>gP zCMPE=hFKe9E5-8SW2j$CJ2qF6Vj(bR2#gF5UwX5dX!P9sGtnr<7=)Bp=fF%fT2aQO z-mkbD3i(nADayzWky5sD!CMz!nM)&ZkyAU{6*wSqP@V&JS-+f{iAJ-1u+kD}t?~P6 zE(K#L`?vS+=hV^T@nTGc?N}MXlZ{vl~NqU4|CrI#%T1J zJ$wEohexNRwW#dNX#TG)^V6@tVF_FGa#@)I!OB-Y1EkW1Pd*;tvq*<{^D$aD{sY)N VvKq+3O@V_<-xTPi`=8!2rT zq<~tr5tSTORaH@mp;J20NJL0f9xBl-fDjfDqz`juH5(n|CB$S|LN*j?nv zvh#cQ-uHYn`8Cy^5}O+^d5=$b_b^(cltLSWQY!mj8w~JdW1k;>{JG>lmOVC-TCc!k zm4`9Z9JtW_me7Bw|BoSvqU4o8InI4m;SmU-u%91aIhcXqga`%Cus<0z$A zUjCcfS|4L$WBl{%S;>Sh<^Yh^g|rX|@rsm9O-=On_Tu;Zn4O(vXJ;>G^ZWg@wY33| z$>?+IE=@vR5P-@kw9YyU1m0nEbd-UC0RR>j9utor;&!{ae*JyM$8XWx+?)+SYlNtf z140T4Iq3Czsjsic?RH~X4%XJza<;*b25~x_?C_|>gwvKu6{dDnN$gHd3D zCkqRYNhXtdM^|v;#s>@y4PjY%uC+!wtSgj2XJH!{9OREjk2pCwp|i7-gM$OMwzlwk zy@Wy`q?9x^HqzbQO@DtsRaI35y}=-)s8k0WD6Ie|;c%F#sVP=gR(S9r%J%j)06jfD zOiWCWNF?z2e7Ib$lD@Rc*pg6Q1_6MMjt;`%Fh@uK=H{GECmxT7Kp^ZrmL%qNF+ie5-F;+J(5>A1%($2fR>gP zCMPE=hFKe9E5-8SW2j$CJ2qF6Vj(bR2#gF5UwX5dX!P9sGtnr<7=)Bp=fF%fT2aQO z-mkbD3i(nADayzWky5sD!CMz!nM)&ZkyAU{6*wSqP@V&JS-+f{iAJ-1u+kD}t?~P6 zE(K#L`?vS+=hV^T@nTGc?N}MXlZ{vl~NqU4|CrI#%T1J zJ$wEohexNRwW#dNX#TG)^V6@tVF_FGa#@)I!OB-Y1EkW1Pd*;tvq*<{^D$aD{sY)N VvKq+3O@V_<-xTPi`=8!2rT zq<~tr5tSTORaH@mp;J20NJL0f9xBl-fDjfDqz`juH5(n|CB$S|LN*j?nv zvh#cQ-uHYn`8Cy^5}O+^d5=$b_b^(cltLSWQY!mj8w~JdW1k;>{JG>lmOVC-TCc!k zm4`9Z9JtW_me7Bw|BoSvqU4o8InI4m;SmU-u%91aIhcXqga`%Cus<0z$A zUjCcfS|4L$WBl{%S;>Sh<^Yh^g|rX|@rsm9O-=On_Tu;Zn4O(vXJ;>G^ZWg@wY33| z$>?+IE=@vR5P-@kw9YyU1m0nEbd-UC0RR>j9utor;&!{ae*JyM$8XWx+?)+SYlNtf z140T4Iq3Czsjsic?RH~X4%XJza<;*b25~x_?C_|>gwvKu6{dDnN$gHd3D zCkqRYNhXtdM^|v;#s>@y4PjY%uC+!wtSgj2XJH!{9OREjk2pCwp|i7-gM$OMwzlwk zy@Wy`q?9x^HqzbQO@DtsRaI35y}=-)s8k0WD6Ie|;c%F#sVP=gR(S9r%J%j)06jfD zOiWCWNF?z2e7Ib$lD@Rc*pg6Q1_6MMjt;`%Fh@uK=H{GECmxT7Kp^ZrmL%qNF+ie5-F;+J(5>A1%($2fR>gP zCMPE=hFKe9E5-8SW2j$CJ2qF6Vj(bR2#gF5UwX5dX!P9sGtnr<7=)Bp=fF%fT2aQO z-mkbD3i(nADayzWky5sD!CMz!nM)&ZkyAU{6*wSqP@V&JS-+f{iAJ-1u+kD}t?~P6 zE(K#L`?vS+=hV^T@nTGc?N}MXlZ{vl~NqU4|CrI#%T1J zJ$wEohexNRwW#dNX#TG)^V6@tVF_FGa#@)I!OB-Y1EkW1Pd*;tvq*<{^D$aD{sY)N VvKq+3OgotoMasternodePage(); } +void WalletFrame::gotoProposalPage() +{ + QMap::const_iterator i; + for (i = mapWalletViews.constBegin(); i != mapWalletViews.constEnd(); ++i) + i.value()->gotoProposalPage(); +} + void WalletFrame::gotoReceiveCoinsPage() { QMap::const_iterator i; diff --git a/src/qt/walletframe.h b/src/qt/walletframe.h index 2325c034721f5..1a4e96b2491ea 100644 --- a/src/qt/walletframe.h +++ b/src/qt/walletframe.h @@ -61,6 +61,8 @@ public Q_SLOTS: void gotoHistoryPage(); /** Switch to masternode page */ void gotoMasternodePage(); + /** Switch to proposal page */ + void gotoProposalPage(); /** Switch to receive coins page */ void gotoReceiveCoinsPage(); /** Switch to send coins page */ diff --git a/src/qt/walletview.cpp b/src/qt/walletview.cpp index b42573cc734f6..49ee9fd794613 100644 --- a/src/qt/walletview.cpp +++ b/src/qt/walletview.cpp @@ -87,6 +87,9 @@ WalletView::WalletView(const PlatformStyle *platformStyle, QWidget *parent): addWidget(masternodeListPage); } + proposalList = new ProposalList(platformStyle); + addWidget(proposalList); + // Clicking on a transaction on the overview pre-selects the transaction on the transaction history page connect(overviewPage, SIGNAL(transactionClicked(QModelIndex)), transactionView, SLOT(focusTransaction(QModelIndex))); connect(overviewPage, SIGNAL(outOfSyncWarningClicked()), this, SLOT(requestedSyncWarningInfo())); @@ -209,6 +212,11 @@ void WalletView::gotoOverviewPage() setCurrentWidget(overviewPage); } +void WalletView::gotoProposalPage() +{ + setCurrentWidget(proposalList); +} + void WalletView::gotoHistoryPage() { setCurrentWidget(transactionsPage); diff --git a/src/qt/walletview.h b/src/qt/walletview.h index 2fa9435052fa7..e3fa368b9906a 100644 --- a/src/qt/walletview.h +++ b/src/qt/walletview.h @@ -7,6 +7,7 @@ #include "amount.h" #include "masternodelist.h" +#include "proposallist.h" #include @@ -69,6 +70,7 @@ class WalletView : public QStackedWidget MasternodeList *masternodeListPage; TransactionView *transactionView; + ProposalList *proposalList; QProgressDialog *progressDialog; QLabel *transactionSum; @@ -81,6 +83,8 @@ public Q_SLOTS: void gotoHistoryPage(); /** Switch to masternode page */ void gotoMasternodePage(); + /** Switch to proposal page */ + void gotoProposalPage(); /** Switch to receive coins page */ void gotoReceiveCoinsPage(); /** Switch to send coins page */