mirror of
https://github.com/dashpay/dash.git
synced 2024-12-25 03:52:49 +01:00
Qt: Adds Governance tab (#4351)
* Qt: Adds settings option to showGovernanceTab * Qt: Adds not-yet-functional Governance tab to UI * library: adds hook into governance rom node interface * Qt: WIP: puts CGovernanceObject hashes on Governance tab WIP: basically ready to be filled out, most of the infra in place now. * Qt: Populates Governance table values Governance table now contains real values for current columns. Display columns may potentially change. - want url in table if right click option opens url? - What set of cols wanted? Show yes/no votes? - Decide refresh functionality. - Do we even want filter func? (checkbox show: proposal, trigger, active coming) * qt: Shows Voting Status on Governance page Towards DMT like Governance page, now shows voting status, but needs to look at super blocks to determine if proposal period has passed and show final funded or unfunded for past proposals. * Qt: refactor governance tab to use Model-View arch Refactors the QT Governance tab to use a Model-View architecture. The list of Proposals is stored in a QAbstractItemModel, and a QTableView is used to display. This makes a much cleaner separation of concerns in the implementation. Also, can now use the QSortFilterProxyModel to get responsive filtering. Less internal state inside the GovernanceList, critical sections removed. - Needs update loop implemented. - Needs Proposal detail widget implemented. * qt: adds periodic update to gov proposal list Periodically update list of governance related proposals. * qt: populates governancelist voting status column * qt: governancelist Porposal shows extra info on doubleClick * qt: governancelist: reorder .cpp impl to match .h * qt: governancelist: fixup based on CI - Adds LOCK(cs_main) for call to pGovObj->IsValidLocally. - retab, removes trailing whitespace in governancelist.{cpp,h} * qt: clang-format for governancelist * Fixes - drop unused include - use proper univalue "get_" functions - shorter/translatable statuses - shorter dates - add missing css styles * qt: addresses governancelist code review items * qt: use enum to name columns of governancelist * fix: clear context menu before adding new items * fix: skip potential nullptr proposals * improve: show proposal url in context menu * refactor: tweak enum name, make sure it's int starting from 0, use full name * fix: column count * improve: make it sortable, sort by start_date by default * qt: use full col name in voting status col change signal emit * better sorting * use mapToSource to fix data fetching Co-authored-by: UdjinM6 <UdjinM6@users.noreply.github.com>
This commit is contained in:
parent
5781bd5ee3
commit
f0d49e4f69
@ -6,6 +6,7 @@ FORMS += \
|
||||
../src/qt/forms/coincontroldialog.ui \
|
||||
../src/qt/forms/debugwindow.ui \
|
||||
../src/qt/forms/editaddressdialog.ui \
|
||||
../src/qt/forms/governancelist.ui \
|
||||
../src/qt/forms/helpmessagedialog.ui \
|
||||
../src/qt/forms/intro.ui \
|
||||
../src/qt/forms/masternodelist.ui \
|
||||
|
@ -36,6 +36,7 @@ QT_FORMS_UI = \
|
||||
qt/forms/askpassphrasedialog.ui \
|
||||
qt/forms/coincontroldialog.ui \
|
||||
qt/forms/editaddressdialog.ui \
|
||||
qt/forms/governancelist.ui \
|
||||
qt/forms/helpmessagedialog.ui \
|
||||
qt/forms/intro.ui \
|
||||
qt/forms/modaloverlay.ui \
|
||||
@ -68,6 +69,7 @@ QT_MOC_CPP = \
|
||||
qt/moc_csvmodelwriter.cpp \
|
||||
qt/moc_dash.cpp \
|
||||
qt/moc_editaddressdialog.cpp \
|
||||
qt/moc_governancelist.cpp \
|
||||
qt/moc_guiutil.cpp \
|
||||
qt/moc_intro.cpp \
|
||||
qt/moc_macdockiconhandler.cpp \
|
||||
@ -146,6 +148,7 @@ BITCOIN_QT_H = \
|
||||
qt/csvmodelwriter.h \
|
||||
qt/dash.h \
|
||||
qt/editaddressdialog.h \
|
||||
qt/governancelist.h \
|
||||
qt/guiconstants.h \
|
||||
qt/guiutil.h \
|
||||
qt/intro.h \
|
||||
@ -257,6 +260,7 @@ BITCOIN_QT_WALLET_CPP = \
|
||||
qt/coincontroldialog.cpp \
|
||||
qt/coincontroltreewidget.cpp \
|
||||
qt/editaddressdialog.cpp \
|
||||
qt/governancelist.cpp \
|
||||
qt/masternodelist.cpp \
|
||||
qt/openuridialog.cpp \
|
||||
qt/overviewpage.cpp \
|
||||
|
@ -10,6 +10,7 @@
|
||||
#include <chain.h>
|
||||
#include <chainparams.h>
|
||||
#include <evo/deterministicmns.h>
|
||||
#include <governance/governance.h>
|
||||
#include <governance/object.h>
|
||||
#include <init.h>
|
||||
#include <interfaces/chain.h>
|
||||
@ -66,6 +67,15 @@ public:
|
||||
}
|
||||
};
|
||||
|
||||
class GOVImpl : public GOV
|
||||
{
|
||||
public:
|
||||
std::vector<const CGovernanceObject*> getAllNewerThan(int64_t nMoreThanTime) override
|
||||
{
|
||||
return governance.GetAllNewerThan(nMoreThanTime);
|
||||
}
|
||||
};
|
||||
|
||||
class LLMQImpl : public LLMQ
|
||||
{
|
||||
public:
|
||||
@ -162,6 +172,7 @@ public:
|
||||
NodeImpl() { m_interfaces.chain = MakeChain(); }
|
||||
|
||||
EVOImpl m_evo;
|
||||
GOVImpl m_gov;
|
||||
LLMQImpl m_llmq;
|
||||
MasternodeSyncImpl m_masternodeSync;
|
||||
CoinJoinOptionsImpl m_coinjoin;
|
||||
@ -385,6 +396,7 @@ public:
|
||||
}
|
||||
|
||||
EVO& evo() override { return m_evo; }
|
||||
GOV& gov() override { return m_gov; }
|
||||
LLMQ& llmq() override { return m_llmq; }
|
||||
Masternode::Sync& masternodeSync() override { return m_masternodeSync; }
|
||||
CoinJoin::Options& coinJoinOptions() override { return m_coinjoin; }
|
||||
|
@ -22,6 +22,7 @@ class BanMan;
|
||||
class CCoinControl;
|
||||
class CDeterministicMNList;
|
||||
class CFeeRate;
|
||||
class CGovernanceObject;
|
||||
class CNodeStats;
|
||||
class Coin;
|
||||
class RPCTimerInterface;
|
||||
@ -41,6 +42,14 @@ public:
|
||||
virtual CDeterministicMNList getListAtChainTip() = 0;
|
||||
};
|
||||
|
||||
//! Interface for the src/governance part of a dash node (dashd process).
|
||||
class GOV
|
||||
{
|
||||
public:
|
||||
virtual ~GOV() {}
|
||||
virtual std::vector<const CGovernanceObject*> getAllNewerThan(int64_t nMoreThanTime) = 0;
|
||||
};
|
||||
|
||||
//! Interface for the src/llmq part of a dash node (dashd process).
|
||||
class LLMQ
|
||||
{
|
||||
@ -261,6 +270,9 @@ public:
|
||||
//! Return interface for accessing evo related handler.
|
||||
virtual EVO& evo() = 0;
|
||||
|
||||
//! Return interface for accessing governance related handler.
|
||||
virtual GOV& gov() = 0;
|
||||
|
||||
//! Return interface for accessing llmq related handler.
|
||||
virtual LLMQ& llmq() = 0;
|
||||
|
||||
|
@ -33,9 +33,10 @@
|
||||
#include <chainparams.h>
|
||||
#include <interfaces/handler.h>
|
||||
#include <interfaces/node.h>
|
||||
#include <qt/governancelist.h>
|
||||
#include <qt/masternodelist.h>
|
||||
#include <ui_interface.h>
|
||||
#include <util/system.h>
|
||||
#include <qt/masternodelist.h>
|
||||
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
@ -688,6 +689,15 @@ void BitcoinGUI::createToolBars()
|
||||
masternodeButton->setEnabled(true);
|
||||
}
|
||||
|
||||
if (settings.value("fShowGovernanceTab").toBool()) {
|
||||
governanceButton = new QToolButton(this);
|
||||
governanceButton->setText(tr("&Governance"));
|
||||
governanceButton->setStatusTip(tr("View Governance Proposals"));
|
||||
tabGroup->addButton(governanceButton);
|
||||
connect(governanceButton, &QToolButton::clicked, this, &BitcoinGUI::gotoGovernancePage);
|
||||
governanceButton->setEnabled(true);
|
||||
}
|
||||
|
||||
connect(overviewButton, &QToolButton::clicked, this, &BitcoinGUI::gotoOverviewPage);
|
||||
connect(sendCoinsButton, &QToolButton::clicked, [this]{ gotoSendCoinsPage(); });
|
||||
connect(coinJoinCoinsButton, &QToolButton::clicked, [this]{ gotoCoinJoinCoinsPage(); });
|
||||
@ -1120,6 +1130,15 @@ void BitcoinGUI::highlightTabButton(QAbstractButton *button, bool checked)
|
||||
GUIUtil::updateFonts();
|
||||
}
|
||||
|
||||
void BitcoinGUI::gotoGovernancePage()
|
||||
{
|
||||
QSettings settings;
|
||||
if (settings.value("fShowGovernanceTab").toBool() && governanceButton) {
|
||||
governanceButton->setChecked(true);
|
||||
if (walletFrame) walletFrame->gotoGovernancePage();
|
||||
}
|
||||
}
|
||||
|
||||
void BitcoinGUI::gotoOverviewPage()
|
||||
{
|
||||
overviewButton->setChecked(true);
|
||||
|
@ -132,6 +132,7 @@ private:
|
||||
QToolButton* receiveCoinsButton = nullptr;
|
||||
QToolButton* historyButton = nullptr;
|
||||
QToolButton* masternodeButton = nullptr;
|
||||
QToolButton* governanceButton = nullptr;
|
||||
QAction* appToolBarLogoAction = nullptr;
|
||||
QAction* quitAction = nullptr;
|
||||
QAction* sendCoinsMenuAction = nullptr;
|
||||
@ -298,6 +299,8 @@ private:
|
||||
|
||||
public Q_SLOTS:
|
||||
#ifdef ENABLE_WALLET
|
||||
/** Switch to governance page */
|
||||
void gotoGovernancePage();
|
||||
/** Switch to overview (home) page */
|
||||
void gotoOverviewPage();
|
||||
/** Switch to history (transactions) page */
|
||||
|
@ -15,14 +15,15 @@
|
||||
#include <chain.h>
|
||||
#include <chainparams.h>
|
||||
#include <clientversion.h>
|
||||
#include <governance/object.h>
|
||||
#include <interfaces/handler.h>
|
||||
#include <interfaces/node.h>
|
||||
#include <validation.h>
|
||||
#include <net.h>
|
||||
#include <netbase.h>
|
||||
#include <txmempool.h>
|
||||
#include <ui_interface.h>
|
||||
#include <util/system.h>
|
||||
#include <validation.h>
|
||||
#include <warnings.h>
|
||||
|
||||
#include <stdint.h>
|
||||
@ -122,6 +123,11 @@ int64_t ClientModel::getHeaderTipTime() const
|
||||
return cachedBestHeaderTime;
|
||||
}
|
||||
|
||||
std::vector<const CGovernanceObject*> ClientModel::getAllGovernanceObjects()
|
||||
{
|
||||
return m_node.gov().getAllNewerThan(0);
|
||||
}
|
||||
|
||||
void ClientModel::updateTimer()
|
||||
{
|
||||
// no locking required at this point
|
||||
|
@ -40,6 +40,7 @@ enum NumConnections {
|
||||
};
|
||||
|
||||
class CDeterministicMNList;
|
||||
class CGovernanceObject;
|
||||
typedef std::shared_ptr<CDeterministicMNList> CDeterministicMNListPtr;
|
||||
|
||||
/** Model for Dash network client. */
|
||||
@ -67,6 +68,8 @@ public:
|
||||
CDeterministicMNList getMasternodeList() const;
|
||||
void refreshMasternodeList();
|
||||
|
||||
std::vector<const CGovernanceObject*> getAllGovernanceObjects();
|
||||
|
||||
//! Returns enum BlockSource of the current importing/syncing state
|
||||
enum BlockSource getBlockSource() const;
|
||||
//! Return warnings to be displayed in status bar
|
||||
|
109
src/qt/forms/governancelist.ui
Normal file
109
src/qt/forms/governancelist.ui
Normal file
@ -0,0 +1,109 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>GovernanceList</class>
|
||||
<widget class="QWidget" name="GovernanceList">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>762</width>
|
||||
<height>457</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="topLayout">
|
||||
<property name="leftMargin">
|
||||
<number>20</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>20</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<layout class="QVBoxLayout" name="verticalLayout">
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer0">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>40</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QGridLayout" name="gridLayout_3">
|
||||
<item row="0" column="0">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_4">
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_filter_2">
|
||||
<property name="text">
|
||||
<string>Filter List:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="filterLineEdit">
|
||||
<property name="toolTip">
|
||||
<string>Filter propsal list</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="horizontalSpacer_4">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>10</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_count_2">
|
||||
<property name="text">
|
||||
<string>Proposal Count:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="countLabel">
|
||||
<property name="text">
|
||||
<string>0</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QTableView" name="govTableView">
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
@ -328,6 +328,16 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="showGovernanceTab">
|
||||
<property name="toolTip">
|
||||
<string>Show additional tab listing governance proposals.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Show Governance Tab</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="spendZeroConfChange">
|
||||
<property name="toolTip">
|
||||
|
402
src/qt/governancelist.cpp
Normal file
402
src/qt/governancelist.cpp
Normal file
@ -0,0 +1,402 @@
|
||||
#include <qt/forms/ui_governancelist.h>
|
||||
#include <qt/governancelist.h>
|
||||
|
||||
#include <chainparams.h>
|
||||
#include <clientversion.h>
|
||||
#include <coins.h>
|
||||
#include <evo/deterministicmns.h>
|
||||
#include <netbase.h>
|
||||
#include <qt/clientmodel.h>
|
||||
#include <qt/guiutil.h>
|
||||
|
||||
#include <univalue.h>
|
||||
|
||||
#include <QAbstractItemView>
|
||||
#include <QDesktopServices>
|
||||
#include <QMessageBox>
|
||||
#include <QTableWidgetItem>
|
||||
#include <QUrl>
|
||||
#include <QtGui/QClipboard>
|
||||
|
||||
///
|
||||
/// Proposal wrapper
|
||||
///
|
||||
|
||||
Proposal::Proposal(const CGovernanceObject* p, QObject* parent) :
|
||||
QObject(parent),
|
||||
pGovObj(p)
|
||||
{
|
||||
UniValue prop_data;
|
||||
if (prop_data.read(pGovObj->GetDataAsPlainString())) {
|
||||
if (UniValue titleValue = find_value(prop_data, "name"); titleValue.isStr()) {
|
||||
m_title = QString::fromStdString(titleValue.get_str());
|
||||
}
|
||||
|
||||
if (UniValue paymentStartValue = find_value(prop_data, "start_epoch"); paymentStartValue.isNum()) {
|
||||
m_startDate = QDateTime::fromSecsSinceEpoch(paymentStartValue.get_int64());
|
||||
}
|
||||
|
||||
if (UniValue paymentEndValue = find_value(prop_data, "end_epoch"); paymentEndValue.isNum()) {
|
||||
m_endDate = QDateTime::fromSecsSinceEpoch(paymentEndValue.get_int64());
|
||||
}
|
||||
|
||||
if (UniValue amountValue = find_value(prop_data, "payment_amount"); amountValue.isNum()) {
|
||||
m_paymentAmount = amountValue.get_real();
|
||||
}
|
||||
|
||||
if (UniValue urlValue = find_value(prop_data, "url"); urlValue.isStr()) {
|
||||
m_url = QString::fromStdString(urlValue.get_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QString Proposal::title() const { return m_title; }
|
||||
|
||||
QString Proposal::hash() const { return QString::fromStdString(pGovObj->GetHash().ToString()); }
|
||||
|
||||
QDateTime Proposal::startDate() const { return m_startDate; }
|
||||
|
||||
QDateTime Proposal::endDate() const { return m_endDate; }
|
||||
|
||||
float Proposal::paymentAmount() const { return m_paymentAmount; }
|
||||
|
||||
QString Proposal::url() const { return m_url; }
|
||||
|
||||
bool Proposal::isActive() const
|
||||
{
|
||||
std::string strError;
|
||||
LOCK(cs_main);
|
||||
return pGovObj->IsValidLocally(strError, false);
|
||||
}
|
||||
|
||||
QString Proposal::votingStatus(const int nAbsVoteReq) const
|
||||
{
|
||||
// Voting status...
|
||||
// TODO: determine if voting is in progress vs. funded or not funded for past proposals.
|
||||
// see CSuperblock::GetNearestSuperblocksHeights(nBlockHeight, nLastSuperblock, nNextSuperblock);
|
||||
const int absYesCount = pGovObj->GetAbsoluteYesCount(VOTE_SIGNAL_FUNDING);
|
||||
QString qStatusString;
|
||||
if (absYesCount >= nAbsVoteReq) {
|
||||
// Could use pGovObj->IsSetCachedFunding here, but need nAbsVoteReq to display numbers anyway.
|
||||
return tr("Passing +%1").arg(absYesCount - nAbsVoteReq);
|
||||
} else {
|
||||
return tr("Needs additional %1 votes").arg(nAbsVoteReq - absYesCount);
|
||||
}
|
||||
}
|
||||
|
||||
int Proposal::GetAbsoluteYesCount() const
|
||||
{
|
||||
return pGovObj->GetAbsoluteYesCount(VOTE_SIGNAL_FUNDING);
|
||||
}
|
||||
|
||||
void Proposal::openUrl() const
|
||||
{
|
||||
QDesktopServices::openUrl(QUrl(m_url));
|
||||
}
|
||||
|
||||
QString Proposal::toJson() const
|
||||
{
|
||||
const auto json = pGovObj->ToJson();
|
||||
return QString::fromStdString(json.write(2));
|
||||
}
|
||||
|
||||
///
|
||||
/// Proposal Model
|
||||
///
|
||||
|
||||
|
||||
int ProposalModel::rowCount(const QModelIndex& index) const
|
||||
{
|
||||
return m_data.count();
|
||||
}
|
||||
|
||||
int ProposalModel::columnCount(const QModelIndex& index) const
|
||||
{
|
||||
return Column::_COUNT;
|
||||
}
|
||||
|
||||
QVariant ProposalModel::data(const QModelIndex& index, int role) const
|
||||
{
|
||||
if (role != Qt::DisplayRole && role != Qt::EditRole) return {};
|
||||
const auto proposal = m_data[index.row()];
|
||||
switch(role) {
|
||||
case Qt::DisplayRole:
|
||||
{
|
||||
switch (index.column()) {
|
||||
case Column::HASH:
|
||||
return proposal->hash();
|
||||
case Column::TITLE:
|
||||
return proposal->title();
|
||||
case Column::START_DATE:
|
||||
return proposal->startDate().date();
|
||||
case Column::END_DATE:
|
||||
return proposal->endDate().date();
|
||||
case Column::PAYMENT_AMOUNT:
|
||||
return proposal->paymentAmount();
|
||||
case Column::IS_ACTIVE:
|
||||
return proposal->isActive() ? "Y" : "N";
|
||||
case Column::VOTING_STATUS:
|
||||
return proposal->votingStatus(nAbsVoteReq);
|
||||
default:
|
||||
return {};
|
||||
};
|
||||
break;
|
||||
}
|
||||
case Qt::EditRole:
|
||||
{
|
||||
// Edit role is used for sorting, so return the raw values where possible
|
||||
switch (index.column()) {
|
||||
case Column::HASH:
|
||||
return proposal->hash();
|
||||
case Column::TITLE:
|
||||
return proposal->title();
|
||||
case Column::START_DATE:
|
||||
return proposal->startDate();
|
||||
case Column::END_DATE:
|
||||
return proposal->endDate();
|
||||
case Column::PAYMENT_AMOUNT:
|
||||
return proposal->paymentAmount();
|
||||
case Column::IS_ACTIVE:
|
||||
return proposal->isActive();
|
||||
case Column::VOTING_STATUS:
|
||||
return proposal->GetAbsoluteYesCount();
|
||||
default:
|
||||
return {};
|
||||
};
|
||||
break;
|
||||
}
|
||||
};
|
||||
return {};
|
||||
}
|
||||
|
||||
QVariant ProposalModel::headerData(int section, Qt::Orientation orientation, int role) const
|
||||
{
|
||||
if (orientation != Qt::Horizontal || role != Qt::DisplayRole) return {};
|
||||
switch (section) {
|
||||
case Column::HASH:
|
||||
return "Hash";
|
||||
case Column::TITLE:
|
||||
return "Title";
|
||||
case Column::START_DATE:
|
||||
return "Start";
|
||||
case Column::END_DATE:
|
||||
return "End";
|
||||
case Column::PAYMENT_AMOUNT:
|
||||
return "Amount";
|
||||
case Column::IS_ACTIVE:
|
||||
return "Active";
|
||||
case Column::VOTING_STATUS:
|
||||
return "Status";
|
||||
default:
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
int ProposalModel::columnWidth(int section)
|
||||
{
|
||||
switch (section) {
|
||||
case Column::HASH:
|
||||
return 80;
|
||||
case Column::TITLE:
|
||||
return 220;
|
||||
case Column::START_DATE:
|
||||
case Column::END_DATE:
|
||||
case Column::PAYMENT_AMOUNT:
|
||||
return 110;
|
||||
case Column::IS_ACTIVE:
|
||||
return 80;
|
||||
case Column::VOTING_STATUS:
|
||||
return 220;
|
||||
default:
|
||||
return 80;
|
||||
}
|
||||
}
|
||||
|
||||
void ProposalModel::append(const Proposal* proposal)
|
||||
{
|
||||
beginInsertRows({}, m_data.count(), m_data.count());
|
||||
m_data.append(proposal);
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
void ProposalModel::remove(int row)
|
||||
{
|
||||
beginRemoveRows({}, row, row);
|
||||
m_data.removeAt(row);
|
||||
endRemoveRows();
|
||||
}
|
||||
|
||||
void ProposalModel::reconcile(const std::vector<const Proposal*>& proposals)
|
||||
{
|
||||
// Vector of m_data.count() false values. Going through new proposals,
|
||||
// set keep_index true for each old proposal found in the new proposals.
|
||||
// After going through new proposals, remove any existing proposals that
|
||||
// weren't found (and are still false).
|
||||
std::vector<bool> keep_index(m_data.count(), false);
|
||||
for (const auto proposal : proposals) {
|
||||
bool found = false;
|
||||
for (unsigned int i = 0; i < m_data.count(); ++i) {
|
||||
if (m_data.at(i)->hash() == proposal->hash()) {
|
||||
found = true;
|
||||
keep_index.at(i) = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
append(proposal);
|
||||
}
|
||||
}
|
||||
for (unsigned int i = keep_index.size(); i > 0; --i) {
|
||||
if (!keep_index.at(i - 1)) {
|
||||
remove(i - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void ProposalModel::setVotingParams(int newAbsVoteReq)
|
||||
{
|
||||
if (this->nAbsVoteReq != newAbsVoteReq) {
|
||||
this->nAbsVoteReq = newAbsVoteReq;
|
||||
// Changing either of the voting params may change the voting status
|
||||
// column. Emit signal to force recalculation.
|
||||
Q_EMIT dataChanged(createIndex(0, Column::VOTING_STATUS), createIndex(columnCount(), Column::VOTING_STATUS));
|
||||
}
|
||||
}
|
||||
|
||||
const Proposal* ProposalModel::getProposalAt(const QModelIndex& index) const
|
||||
{
|
||||
return m_data[index.row()];
|
||||
}
|
||||
|
||||
//
|
||||
// Governance Tab main widget.
|
||||
//
|
||||
|
||||
GovernanceList::GovernanceList(QWidget* parent) :
|
||||
QWidget(parent),
|
||||
ui(new Ui::GovernanceList),
|
||||
clientModel(nullptr),
|
||||
proposalModel(new ProposalModel(this)),
|
||||
proposalModelProxy(new QSortFilterProxyModel(this)),
|
||||
proposalContextMenu(new QMenu(this)),
|
||||
timer(new QTimer(this))
|
||||
{
|
||||
ui->setupUi(this);
|
||||
|
||||
GUIUtil::setFont({ui->label_count_2, ui->countLabel}, GUIUtil::FontWeight::Bold, 14);
|
||||
GUIUtil::setFont({ui->label_filter_2}, GUIUtil::FontWeight::Normal, 15);
|
||||
|
||||
proposalModelProxy->setSourceModel(proposalModel);
|
||||
ui->govTableView->setModel(proposalModelProxy);
|
||||
ui->govTableView->setSelectionBehavior(QAbstractItemView::SelectRows);
|
||||
ui->govTableView->horizontalHeader()->setStretchLastSection(true);
|
||||
|
||||
for (int i = 0; i < proposalModel->columnCount(); ++i) {
|
||||
ui->govTableView->setColumnWidth(i, proposalModel->columnWidth(i));
|
||||
}
|
||||
|
||||
// Set up sorting.
|
||||
proposalModelProxy->setSortRole(Qt::EditRole);
|
||||
ui->govTableView->setSortingEnabled(true);
|
||||
ui->govTableView->sortByColumn(ProposalModel::Column::START_DATE, Qt::DescendingOrder);
|
||||
|
||||
// Set up filtering.
|
||||
proposalModelProxy->setFilterKeyColumn(ProposalModel::Column::TITLE); // filter by title column...
|
||||
ui->filterLineEdit->setPlaceholderText(tr("Filter by Title"));
|
||||
connect(ui->filterLineEdit, &QLineEdit::textChanged, proposalModelProxy, &QSortFilterProxyModel::setFilterFixedString);
|
||||
|
||||
// Changes to number of rows should update proposal count display.
|
||||
connect(proposalModelProxy, &QSortFilterProxyModel::rowsInserted, this, &GovernanceList::updateProposalCount);
|
||||
connect(proposalModelProxy, &QSortFilterProxyModel::rowsRemoved, this, &GovernanceList::updateProposalCount);
|
||||
connect(proposalModelProxy, &QSortFilterProxyModel::layoutChanged, this, &GovernanceList::updateProposalCount);
|
||||
|
||||
// Enable CustomContextMenu on the table to make the view emit customContextMenuRequested signal.
|
||||
ui->govTableView->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||
connect(ui->govTableView, &QTableView::customContextMenuRequested, this, &GovernanceList::showProposalContextMenu);
|
||||
connect(ui->govTableView, &QTableView::doubleClicked, this, &GovernanceList::showAdditionalInfo);
|
||||
|
||||
connect(timer, &QTimer::timeout, this, &GovernanceList::updateProposalList);
|
||||
|
||||
GUIUtil::updateFonts();
|
||||
}
|
||||
|
||||
GovernanceList::~GovernanceList()
|
||||
{
|
||||
delete ui;
|
||||
}
|
||||
|
||||
void GovernanceList::setClientModel(ClientModel* model)
|
||||
{
|
||||
this->clientModel = model;
|
||||
updateProposalList();
|
||||
}
|
||||
|
||||
void GovernanceList::updateProposalList()
|
||||
{
|
||||
if (this->clientModel) {
|
||||
// A proposal is considered passing if (YES votes - NO votes) >= (Total Number of Masternodes / 10),
|
||||
// count total valid (ENABLED) masternodes to determine passing threshold.
|
||||
// Need to query number of masternodes here with access to clientModel.
|
||||
const int nMnCount = clientModel->getMasternodeList().GetValidMNsCount();
|
||||
const int nAbsVoteReq = std::max(Params().GetConsensus().nGovernanceMinQuorum, nMnCount / 10);
|
||||
proposalModel->setVotingParams(nAbsVoteReq);
|
||||
|
||||
const std::vector<const CGovernanceObject*> govObjList = clientModel->getAllGovernanceObjects();
|
||||
std::vector<const Proposal*> newProposals;
|
||||
for (const auto pGovObj : govObjList) {
|
||||
if (pGovObj->GetObjectType() != GOVERNANCE_OBJECT_PROPOSAL) {
|
||||
continue; // Skip triggers.
|
||||
}
|
||||
|
||||
newProposals.emplace_back(new Proposal(pGovObj, proposalModel));
|
||||
}
|
||||
proposalModel->reconcile(newProposals);
|
||||
}
|
||||
|
||||
// Schedule next update.
|
||||
timer->start(GOVERNANCELIST_UPDATE_SECONDS * 1000);
|
||||
}
|
||||
|
||||
void GovernanceList::updateProposalCount() const
|
||||
{
|
||||
ui->countLabel->setText(QString::number(proposalModelProxy->rowCount()));
|
||||
}
|
||||
|
||||
void GovernanceList::showProposalContextMenu(const QPoint& pos)
|
||||
{
|
||||
const auto index = ui->govTableView->indexAt(pos);
|
||||
|
||||
if (!index.isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto proposal = proposalModel->getProposalAt(proposalModelProxy->mapToSource(index));
|
||||
if (proposal == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
// right click menu with option to open proposal url
|
||||
QAction* openProposalUrl = new QAction(proposal->url(), this);
|
||||
proposalContextMenu->clear();
|
||||
proposalContextMenu->addAction(openProposalUrl);
|
||||
connect(openProposalUrl, &QAction::triggered, proposal, &Proposal::openUrl);
|
||||
proposalContextMenu->exec(QCursor::pos());
|
||||
}
|
||||
|
||||
void GovernanceList::showAdditionalInfo(const QModelIndex& index)
|
||||
{
|
||||
if (!index.isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto proposal = proposalModel->getProposalAt(proposalModelProxy->mapToSource(index));
|
||||
if (proposal == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto windowTitle = tr("Proposal Info: %1").arg(proposal->title());
|
||||
const auto json = proposal->toJson();
|
||||
|
||||
QMessageBox::information(this, windowTitle, json);
|
||||
}
|
116
src/qt/governancelist.h
Normal file
116
src/qt/governancelist.h
Normal file
@ -0,0 +1,116 @@
|
||||
#ifndef BITCOIN_QT_GOVERNANCELIST_H
|
||||
#define BITCOIN_QT_GOVERNANCELIST_H
|
||||
|
||||
#include <governance/object.h>
|
||||
#include <primitives/transaction.h>
|
||||
#include <sync.h>
|
||||
#include <util/system.h>
|
||||
|
||||
#include <QAbstractTableModel>
|
||||
#include <QDateTime>
|
||||
#include <QMenu>
|
||||
#include <QSortFilterProxyModel>
|
||||
#include <QTimer>
|
||||
#include <QWidget>
|
||||
|
||||
inline constexpr int GOVERNANCELIST_UPDATE_SECONDS = 10;
|
||||
|
||||
namespace Ui {
|
||||
class GovernanceList;
|
||||
}
|
||||
|
||||
class CDeterministicMNList;
|
||||
class ClientModel;
|
||||
|
||||
class Proposal : public QObject
|
||||
{
|
||||
private:
|
||||
Q_OBJECT
|
||||
|
||||
const CGovernanceObject* pGovObj;
|
||||
QString m_title;
|
||||
QDateTime m_startDate;
|
||||
QDateTime m_endDate;
|
||||
float m_paymentAmount;
|
||||
QString m_url;
|
||||
|
||||
public:
|
||||
Proposal(const CGovernanceObject* p, QObject* parent = nullptr);
|
||||
QString title() const;
|
||||
QString hash() const;
|
||||
QDateTime startDate() const;
|
||||
QDateTime endDate() const;
|
||||
float paymentAmount() const;
|
||||
QString url() const;
|
||||
bool isActive() const;
|
||||
QString votingStatus(const int nAbsVoteReq) const;
|
||||
int GetAbsoluteYesCount() const;
|
||||
|
||||
void openUrl() const;
|
||||
|
||||
QString toJson() const;
|
||||
};
|
||||
|
||||
class ProposalModel : public QAbstractTableModel
|
||||
{
|
||||
private:
|
||||
QList<const Proposal*> m_data;
|
||||
int nAbsVoteReq = 0;
|
||||
|
||||
public:
|
||||
explicit ProposalModel(QObject* parent = nullptr) :
|
||||
QAbstractTableModel(parent){};
|
||||
|
||||
enum Column : int {
|
||||
HASH = 0,
|
||||
TITLE,
|
||||
START_DATE,
|
||||
END_DATE,
|
||||
PAYMENT_AMOUNT,
|
||||
IS_ACTIVE,
|
||||
VOTING_STATUS,
|
||||
_COUNT // for internal use only
|
||||
};
|
||||
|
||||
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
|
||||
int columnCount(const QModelIndex& parent = QModelIndex()) const override;
|
||||
QVariant data(const QModelIndex& index, int role) const override;
|
||||
QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
|
||||
static int columnWidth(int section);
|
||||
void append(const Proposal* proposal);
|
||||
void remove(int row);
|
||||
void reconcile(const std::vector<const Proposal*>& proposals);
|
||||
void setVotingParams(int nAbsVoteReq);
|
||||
|
||||
const Proposal* getProposalAt(const QModelIndex& index) const;
|
||||
};
|
||||
|
||||
/** Governance Manager page widget */
|
||||
class GovernanceList : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit GovernanceList(QWidget* parent = nullptr);
|
||||
~GovernanceList() override;
|
||||
void setClientModel(ClientModel* clientModel);
|
||||
|
||||
private:
|
||||
ClientModel* clientModel;
|
||||
|
||||
Ui::GovernanceList* ui;
|
||||
ProposalModel* proposalModel;
|
||||
QSortFilterProxyModel* proposalModelProxy;
|
||||
|
||||
QMenu* proposalContextMenu;
|
||||
QTimer* timer;
|
||||
|
||||
private Q_SLOTS:
|
||||
void updateProposalList();
|
||||
void updateProposalCount() const;
|
||||
void showProposalContextMenu(const QPoint& pos);
|
||||
void showAdditionalInfo(const QModelIndex& index);
|
||||
};
|
||||
|
||||
|
||||
#endif // BITCOIN_QT_GOVERNANCELIST_H
|
@ -247,7 +247,7 @@ void OptionsDialog::setModel(OptionsModel *_model)
|
||||
connect(ui->threadsScriptVerif, static_cast<void (QSpinBox::*)(int)>(&QSpinBox::valueChanged), this, &OptionsDialog::showRestartWarning);
|
||||
/* Wallet */
|
||||
connect(ui->showMasternodesTab, &QCheckBox::clicked, this, &OptionsDialog::showRestartWarning);
|
||||
connect(ui->spendZeroConfChange, &QCheckBox::clicked, this, &OptionsDialog::showRestartWarning);
|
||||
connect(ui->showGovernanceTab, &QCheckBox::clicked, this, &OptionsDialog::showRestartWarning);
|
||||
connect(ui->spendZeroConfChange, &QCheckBox::clicked, this, &OptionsDialog::showRestartWarning);
|
||||
/* Network */
|
||||
connect(ui->allowIncoming, &QCheckBox::clicked, this, &OptionsDialog::showRestartWarning);
|
||||
@ -302,6 +302,7 @@ void OptionsDialog::setMapper()
|
||||
/* Wallet */
|
||||
mapper->addMapping(ui->coinControlFeatures, OptionsModel::CoinControlFeatures);
|
||||
mapper->addMapping(ui->showMasternodesTab, OptionsModel::ShowMasternodesTab);
|
||||
mapper->addMapping(ui->showGovernanceTab, OptionsModel::ShowGovernanceTab);
|
||||
mapper->addMapping(ui->showAdvancedCJUI, OptionsModel::ShowAdvancedCJUI);
|
||||
mapper->addMapping(ui->showCoinJoinPopups, OptionsModel::ShowCoinJoinPopups);
|
||||
mapper->addMapping(ui->lowKeysWarning, OptionsModel::LowKeysWarning);
|
||||
|
@ -395,6 +395,8 @@ QVariant OptionsModel::data(const QModelIndex & index, int role) const
|
||||
return settings.value("bSpendZeroConfChange");
|
||||
case ShowMasternodesTab:
|
||||
return settings.value("fShowMasternodesTab");
|
||||
case ShowGovernanceTab:
|
||||
return settings.value("fShowGovernanceTab");
|
||||
case CoinJoinEnabled:
|
||||
return settings.value("fCoinJoinEnabled");
|
||||
case ShowAdvancedCJUI:
|
||||
@ -556,6 +558,12 @@ bool OptionsModel::setData(const QModelIndex & index, const QVariant & value, in
|
||||
setRestartRequired(true);
|
||||
}
|
||||
break;
|
||||
case ShowGovernanceTab:
|
||||
if (settings.value("fShowGovernanceTab") != value) {
|
||||
settings.setValue("fShowGovernanceTab", value);
|
||||
setRestartRequired(true);
|
||||
}
|
||||
break;
|
||||
case CoinJoinEnabled:
|
||||
if (settings.value("fCoinJoinEnabled") != value) {
|
||||
settings.setValue("fCoinJoinEnabled", value.toBool());
|
||||
|
@ -61,6 +61,7 @@ public:
|
||||
DatabaseCache, // int
|
||||
SpendZeroConfChange, // bool
|
||||
ShowMasternodesTab, // bool
|
||||
ShowGovernanceTab, // bool
|
||||
CoinJoinEnabled, // bool
|
||||
ShowAdvancedCJUI, // bool
|
||||
ShowCoinJoinPopups, // bool
|
||||
|
@ -818,6 +818,14 @@ EditAddressDialog
|
||||
|
||||
/***** No dark.css specific coloring here yet *****/
|
||||
|
||||
/******************************************************
|
||||
GovernanceList
|
||||
******************************************************/
|
||||
|
||||
GovernanceList QTableView {
|
||||
color: #c7c7c7;
|
||||
}
|
||||
|
||||
/******************************************************
|
||||
HelpMessageDialog
|
||||
******************************************************/
|
||||
|
@ -803,6 +803,14 @@ EditAddressDialog
|
||||
|
||||
/***** No light.css specific coloring here yet *****/
|
||||
|
||||
/******************************************************
|
||||
GovernanceList
|
||||
******************************************************/
|
||||
|
||||
GovernanceList QTableView {
|
||||
color: #555;
|
||||
}
|
||||
|
||||
/******************************************************
|
||||
HelpMessageDialog
|
||||
******************************************************/
|
||||
|
@ -6,6 +6,7 @@
|
||||
#include <qt/walletmodel.h>
|
||||
|
||||
#include <qt/bitcoingui.h>
|
||||
#include <qt/governancelist.h>
|
||||
#include <qt/masternodelist.h>
|
||||
#include <qt/walletview.h>
|
||||
|
||||
@ -32,6 +33,9 @@ WalletFrame::WalletFrame(BitcoinGUI* _gui) :
|
||||
|
||||
masternodeListPage = new MasternodeList();
|
||||
walletStack->addWidget(masternodeListPage);
|
||||
|
||||
governanceListPage = new GovernanceList();
|
||||
walletStack->addWidget(governanceListPage);
|
||||
}
|
||||
|
||||
WalletFrame::~WalletFrame()
|
||||
@ -43,6 +47,7 @@ void WalletFrame::setClientModel(ClientModel *_clientModel)
|
||||
this->clientModel = _clientModel;
|
||||
|
||||
masternodeListPage->setClientModel(_clientModel);
|
||||
governanceListPage->setClientModel(_clientModel);
|
||||
|
||||
for (auto i = mapWalletViews.constBegin(); i != mapWalletViews.constEnd(); ++i) {
|
||||
i.value()->setClientModel(_clientModel);
|
||||
@ -120,6 +125,21 @@ void WalletFrame::showOutOfSyncWarning(bool fShow)
|
||||
i.value()->showOutOfSyncWarning(fShow);
|
||||
}
|
||||
|
||||
void WalletFrame::gotoGovernancePage()
|
||||
{
|
||||
QMap<WalletModel*, WalletView*>::const_iterator i;
|
||||
|
||||
if (mapWalletViews.empty()) {
|
||||
walletStack->setCurrentWidget(governanceListPage);
|
||||
return;
|
||||
}
|
||||
|
||||
for (i = mapWalletViews.constBegin(); i != mapWalletViews.constEnd(); ++i) {
|
||||
i.value()->gotoGovernancePage();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void WalletFrame::gotoOverviewPage()
|
||||
{
|
||||
QMap<WalletModel*, WalletView*>::const_iterator i;
|
||||
|
@ -14,6 +14,7 @@ class SendCoinsRecipient;
|
||||
class WalletModel;
|
||||
class WalletView;
|
||||
class MasternodeList;
|
||||
class GovernanceList;
|
||||
|
||||
QT_BEGIN_NAMESPACE
|
||||
class QStackedWidget;
|
||||
@ -55,6 +56,7 @@ private:
|
||||
ClientModel *clientModel;
|
||||
QMap<WalletModel*, WalletView*> mapWalletViews;
|
||||
MasternodeList* masternodeListPage;
|
||||
GovernanceList* governanceListPage;
|
||||
|
||||
bool bOutOfSync;
|
||||
|
||||
@ -63,6 +65,8 @@ public:
|
||||
WalletModel* currentWalletModel() const;
|
||||
|
||||
public Q_SLOTS:
|
||||
/** Switch to governance page */
|
||||
void gotoGovernancePage();
|
||||
/** Switch to overview (home) page */
|
||||
void gotoOverviewPage();
|
||||
/** Switch to history (transactions) page */
|
||||
|
@ -89,6 +89,10 @@ WalletView::WalletView(QWidget* parent) :
|
||||
masternodeListPage = new MasternodeList();
|
||||
addWidget(masternodeListPage);
|
||||
}
|
||||
if (settings.value("fShowGovernanceTab").toBool()) {
|
||||
governanceListPage = new GovernanceList();
|
||||
addWidget(governanceListPage);
|
||||
}
|
||||
|
||||
// Clicking on a transaction on the overview pre-selects the transaction on the transaction history page
|
||||
connect(overviewPage, &OverviewPage::transactionClicked, transactionView, static_cast<void (TransactionView::*)(const QModelIndex&)>(&TransactionView::focusTransaction));
|
||||
@ -156,6 +160,9 @@ void WalletView::setClientModel(ClientModel *_clientModel)
|
||||
if (settings.value("fShowMasternodesTab").toBool()) {
|
||||
masternodeListPage->setClientModel(_clientModel);
|
||||
}
|
||||
if (settings.value("fShowGovernanceTab").toBool()) {
|
||||
governanceListPage->setClientModel(_clientModel);
|
||||
}
|
||||
}
|
||||
|
||||
void WalletView::setWalletModel(WalletModel *_walletModel)
|
||||
@ -227,6 +234,14 @@ void WalletView::processNewTransaction(const QModelIndex& parent, int start, int
|
||||
Q_EMIT incomingTransaction(date, walletModel->getOptionsModel()->getDisplayUnit(), amount, type, address, label, walletModel->getWalletName());
|
||||
}
|
||||
|
||||
void WalletView::gotoGovernancePage()
|
||||
{
|
||||
QSettings settings;
|
||||
if (settings.value("fShowGovernanceTab").toBool()) {
|
||||
setCurrentWidget(governanceListPage);
|
||||
}
|
||||
}
|
||||
|
||||
void WalletView::gotoOverviewPage()
|
||||
{
|
||||
setCurrentWidget(overviewPage);
|
||||
|
@ -6,6 +6,7 @@
|
||||
#define BITCOIN_QT_WALLETVIEW_H
|
||||
|
||||
#include <amount.h>
|
||||
#include <qt/governancelist.h>
|
||||
#include <qt/masternodelist.h>
|
||||
|
||||
#include <QStackedWidget>
|
||||
@ -68,6 +69,7 @@ private:
|
||||
AddressBookPage *usedSendingAddressesPage;
|
||||
AddressBookPage *usedReceivingAddressesPage;
|
||||
MasternodeList *masternodeListPage;
|
||||
GovernanceList* governanceListPage;
|
||||
|
||||
TransactionView *transactionView;
|
||||
|
||||
@ -75,6 +77,8 @@ private:
|
||||
QLabel *transactionSum;
|
||||
|
||||
public Q_SLOTS:
|
||||
/** Switch to governance page */
|
||||
void gotoGovernancePage();
|
||||
/** Switch to overview (home) page */
|
||||
void gotoOverviewPage();
|
||||
/** Switch to history (transactions) page */
|
||||
|
Loading…
Reference in New Issue
Block a user