diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include index 582f293ad853..88ed7bd13ba0 100644 --- a/src/Makefile.qt.include +++ b/src/Makefile.qt.include @@ -20,62 +20,73 @@ QT_FORMS_UI = \ qt/forms/askpassphrasedialog.ui \ qt/forms/coincontroldialog.ui \ qt/forms/createwalletdialog.ui \ + qt/forms/debugwindow.ui \ + qt/forms/descriptiondialog.ui \ qt/forms/editaddressdialog.ui \ + qt/forms/informationwidget.ui \ + qt/forms/governanceinfo.ui \ qt/forms/governancelist.ui \ - qt/forms/proposalwizard.ui \ qt/forms/helpmessagedialog.ui \ qt/forms/intro.ui \ - qt/forms/modaloverlay.ui \ qt/forms/masternodelist.ui \ qt/forms/mnemonicverificationdialog.ui \ - qt/forms/qrdialog.ui \ + qt/forms/modaloverlay.ui \ + qt/forms/networkwidget.ui \ qt/forms/openuridialog.ui \ qt/forms/optionsdialog.ui \ qt/forms/overviewpage.ui \ + qt/forms/proposalcreate.ui \ + qt/forms/proposalresume.ui \ qt/forms/psbtoperationsdialog.ui \ + qt/forms/qrdialog.ui \ qt/forms/receivecoinsdialog.ui \ qt/forms/receiverequestdialog.ui \ - qt/forms/debugwindow.ui \ qt/forms/sendcoinsdialog.ui \ qt/forms/sendcoinsentry.ui \ - qt/forms/signverifymessagedialog.ui \ - qt/forms/transactiondescdialog.ui + qt/forms/signverifymessagedialog.ui QT_MOC_CPP = \ qt/moc_addressbookpage.cpp \ qt/moc_addresstablemodel.cpp \ qt/moc_appearancewidget.cpp \ qt/moc_askpassphrasedialog.cpp \ - qt/moc_createwalletdialog.cpp \ qt/moc_bantablemodel.cpp \ qt/moc_bitcoin.cpp \ qt/moc_bitcoinaddressvalidator.cpp \ qt/moc_bitcoinamountfield.cpp \ qt/moc_bitcoingui.cpp \ qt/moc_bitcoinunits.cpp \ + qt/moc_donutchart.cpp \ qt/moc_clientmodel.cpp \ qt/moc_coincontroldialog.cpp \ qt/moc_coincontroltreewidget.cpp \ + qt/moc_createwalletdialog.cpp \ qt/moc_csvmodelwriter.cpp \ + qt/moc_descriptiondialog.cpp \ qt/moc_editaddressdialog.cpp \ + qt/moc_governanceinfo.cpp \ qt/moc_governancelist.cpp \ - qt/moc_proposalwizard.cpp \ qt/moc_guiutil.cpp \ + qt/moc_informationwidget.cpp \ qt/moc_initexecutor.cpp \ qt/moc_intro.cpp \ qt/moc_macdockiconhandler.cpp \ qt/moc_macnotificationhandler.cpp \ - qt/moc_modaloverlay.cpp \ qt/moc_masternodelist.cpp \ qt/moc_mnemonicverificationdialog.cpp \ + qt/moc_modaloverlay.cpp \ + qt/moc_networkwidget.cpp \ qt/moc_notificator.cpp \ qt/moc_openuridialog.cpp \ qt/moc_optionsdialog.cpp \ qt/moc_optionsmodel.cpp \ qt/moc_overviewpage.cpp \ + qt/moc_paymentserver.cpp \ qt/moc_peertablemodel.cpp \ qt/moc_peertablesortproxy.cpp \ - qt/moc_paymentserver.cpp \ + qt/moc_proposalmodel.cpp \ + qt/moc_proposalcreate.cpp \ + qt/moc_proposalresume.cpp \ qt/moc_psbtoperationsdialog.cpp \ qt/moc_qrdialog.cpp \ qt/moc_qrimagewidget.cpp \ @@ -91,7 +102,6 @@ QT_MOC_CPP = \ qt/moc_splashscreen.cpp \ qt/moc_trafficgraphwidget.cpp \ qt/moc_transactiondesc.cpp \ - qt/moc_transactiondescdialog.cpp \ qt/moc_transactionfilterproxy.cpp \ qt/moc_transactionoverviewwidget.cpp \ qt/moc_transactiontablemodel.cpp \ @@ -129,26 +139,30 @@ BITCOIN_QT_H = \ qt/bitcoinamountfield.h \ qt/bitcoingui.h \ qt/bitcoinunits.h \ + qt/donutchart.h \ qt/clientmodel.h \ qt/coincontroldialog.h \ qt/coincontroltreewidget.h \ qt/createwalletdialog.h \ qt/csvmodelwriter.h \ + qt/descriptiondialog.h \ qt/editaddressdialog.h \ + qt/governanceinfo.h \ qt/governancelist.h \ - qt/proposalwizard.h \ qt/guiconstants.h \ qt/guiutil.h \ qt/guiutil_font.h \ + qt/informationwidget.h \ qt/initexecutor.h \ qt/intro.h \ qt/macdockiconhandler.h \ qt/macnotificationhandler.h \ qt/macos_appnap.h \ - qt/modaloverlay.h \ qt/masternodelist.h \ qt/mnemonicverificationdialog.h \ + qt/modaloverlay.h \ qt/networkstyle.h \ + qt/networkwidget.h \ qt/notificator.h \ qt/openuridialog.h \ qt/optionsdialog.h \ @@ -157,6 +171,9 @@ BITCOIN_QT_H = \ qt/paymentserver.h \ qt/peertablemodel.h \ qt/peertablesortproxy.h \ + qt/proposalcreate.h \ + qt/proposalmodel.h \ + qt/proposalresume.h \ qt/psbtoperationsdialog.h \ qt/qrdialog.h \ qt/qrimagewidget.h \ @@ -174,7 +191,6 @@ BITCOIN_QT_H = \ qt/trafficgraphdata.h \ qt/trafficgraphwidget.h \ qt/transactiondesc.h \ - qt/transactiondescdialog.h \ qt/transactionfilterproxy.h \ qt/transactionoverviewwidget.h \ qt/transactionrecord.h \ @@ -189,36 +205,36 @@ BITCOIN_QT_H = \ qt/winshutdownmonitor.h QT_RES_ICONS = \ - qt/res/icons/dash.ico \ - qt/res/icons/dash_testnet.ico \ - qt/res/icons/dash.png \ - qt/res/icons/warning.png \ qt/res/icons/address-book.png \ - qt/res/icons/transaction0.png \ - qt/res/icons/transaction1.png \ - qt/res/icons/transaction2.png \ - qt/res/icons/transaction3.png \ - qt/res/icons/transaction4.png \ - qt/res/icons/transaction5.png \ - qt/res/icons/transaction_abandoned.png \ - qt/res/icons/transaction_locked.png \ qt/res/icons/connect1_16.png \ qt/res/icons/connect2_16.png \ qt/res/icons/connect3_16.png \ qt/res/icons/connect4_16.png \ + qt/res/icons/dash.ico \ + qt/res/icons/dash.png \ + qt/res/icons/dash_testnet.ico \ qt/res/icons/editcopy.png \ qt/res/icons/editpaste.png \ qt/res/icons/eye.png \ qt/res/icons/eye_minus.png \ qt/res/icons/eye_plus.png \ + qt/res/icons/fontbigger.png \ + qt/res/icons/fontsmaller.png \ qt/res/icons/hd_enabled.png \ qt/res/icons/lock_closed.png \ qt/res/icons/lock_open.png \ qt/res/icons/proxy.png \ qt/res/icons/remove.png \ qt/res/icons/synced.png \ - qt/res/icons/fontbigger.png \ - qt/res/icons/fontsmaller.png + qt/res/icons/transaction0.png \ + qt/res/icons/transaction1.png \ + qt/res/icons/transaction2.png \ + qt/res/icons/transaction3.png \ + qt/res/icons/transaction4.png \ + qt/res/icons/transaction5.png \ + qt/res/icons/transaction_abandoned.png \ + qt/res/icons/transaction_locked.png \ + qt/res/icons/warning.png BITCOIN_QT_BASE_CPP = \ qt/appearancewidget.cpp \ @@ -228,14 +244,18 @@ BITCOIN_QT_BASE_CPP = \ qt/bitcoinamountfield.cpp \ qt/bitcoingui.cpp \ qt/bitcoinunits.cpp \ + qt/donutchart.cpp \ qt/clientmodel.cpp \ qt/csvmodelwriter.cpp \ + qt/governanceinfo.cpp \ qt/guiutil.cpp \ qt/guiutil_font.cpp \ + qt/informationwidget.cpp \ qt/initexecutor.cpp \ qt/intro.cpp \ qt/modaloverlay.cpp \ qt/networkstyle.cpp \ + qt/networkwidget.cpp \ qt/notificator.cpp \ qt/optionsdialog.cpp \ qt/optionsmodel.cpp \ @@ -258,14 +278,17 @@ BITCOIN_QT_WALLET_CPP = \ qt/coincontroldialog.cpp \ qt/coincontroltreewidget.cpp \ qt/createwalletdialog.cpp \ + qt/descriptiondialog.cpp \ qt/editaddressdialog.cpp \ qt/governancelist.cpp \ - qt/proposalwizard.cpp \ qt/masternodelist.cpp \ qt/mnemonicverificationdialog.cpp \ qt/openuridialog.cpp \ qt/overviewpage.cpp \ qt/paymentserver.cpp \ + qt/proposalcreate.cpp \ + qt/proposalmodel.cpp \ + qt/proposalresume.cpp \ qt/psbtoperationsdialog.cpp \ qt/qrdialog.cpp \ qt/qrimagewidget.cpp \ @@ -276,7 +299,6 @@ BITCOIN_QT_WALLET_CPP = \ qt/sendcoinsentry.cpp \ qt/signverifymessagedialog.cpp \ qt/transactiondesc.cpp \ - qt/transactiondescdialog.cpp \ qt/transactionfilterproxy.cpp \ qt/transactionoverviewwidget.cpp \ qt/transactionrecord.cpp \ @@ -301,50 +323,50 @@ QT_RES_IMAGES = \ qt/res/images/arrow_down_light.png \ qt/res/images/arrow_left_dark.png \ qt/res/images/arrow_left_light.png \ - qt/res/images/arrow_right_dark.png \ - qt/res/images/arrow_right_light.png \ - qt/res/images/arrow_up_dark.png \ - qt/res/images/arrow_up_light.png \ - qt/res/images/arrow_light_down_normal.png \ qt/res/images/arrow_light_down_hover.png \ - qt/res/images/arrow_light_left_normal.png \ + qt/res/images/arrow_light_down_normal.png \ qt/res/images/arrow_light_left_hover.png \ - qt/res/images/arrow_light_right_normal.png \ + qt/res/images/arrow_light_left_normal.png \ qt/res/images/arrow_light_right_hover.png \ - qt/res/images/arrow_light_up_normal.png \ + qt/res/images/arrow_light_right_normal.png \ qt/res/images/arrow_light_up_hover.png \ - qt/res/images/checkbox_normal_dark.png \ - qt/res/images/checkbox_normal_hover_dark.png \ - qt/res/images/checkbox_normal_disabled_dark.png \ + qt/res/images/arrow_light_up_normal.png \ + qt/res/images/arrow_right_dark.png \ + qt/res/images/arrow_right_light.png \ + qt/res/images/arrow_up_dark.png \ + qt/res/images/arrow_up_light.png \ qt/res/images/checkbox_checked_dark.png \ - qt/res/images/checkbox_checked_hover_dark.png \ qt/res/images/checkbox_checked_disabled_dark.png \ + qt/res/images/checkbox_checked_disabled_light.png \ + qt/res/images/checkbox_checked_hover_dark.png \ + qt/res/images/checkbox_checked_hover_light.png \ + qt/res/images/checkbox_checked_light.png \ + qt/res/images/checkbox_normal_dark.png \ + qt/res/images/checkbox_normal_disabled_dark.png \ + qt/res/images/checkbox_normal_disabled_light.png \ + qt/res/images/checkbox_normal_hover_dark.png \ + qt/res/images/checkbox_normal_hover_light.png \ + qt/res/images/checkbox_normal_light.png \ qt/res/images/checkbox_partly_checked_dark.png \ - qt/res/images/checkbox_partly_checked_hover_dark.png \ qt/res/images/checkbox_partly_checked_disabled_dark.png \ - qt/res/images/checkbox_normal_light.png \ - qt/res/images/checkbox_normal_hover_light.png \ - qt/res/images/checkbox_normal_disabled_light.png \ - qt/res/images/checkbox_checked_light.png \ - qt/res/images/checkbox_checked_hover_light.png \ - qt/res/images/checkbox_checked_disabled_light.png \ - qt/res/images/checkbox_partly_checked_light.png \ - qt/res/images/checkbox_partly_checked_hover_light.png \ qt/res/images/checkbox_partly_checked_disabled_light.png \ + qt/res/images/checkbox_partly_checked_hover_dark.png \ + qt/res/images/checkbox_partly_checked_hover_light.png \ + qt/res/images/checkbox_partly_checked_light.png \ qt/res/images/dash_logo_toolbar.png \ qt/res/images/dash_logo_toolbar_blue.png \ - qt/res/images/radio_normal_dark.png \ - qt/res/images/radio_normal_hover_dark.png \ qt/res/images/radio_checked_dark.png \ - qt/res/images/radio_checked_hover_dark.png \ - qt/res/images/radio_normal_disabled_dark.png \ qt/res/images/radio_checked_disabled_dark.png \ - qt/res/images/radio_normal_light.png \ - qt/res/images/radio_normal_hover_light.png \ - qt/res/images/radio_checked_light.png \ + qt/res/images/radio_checked_disabled_light.png \ + qt/res/images/radio_checked_hover_dark.png \ qt/res/images/radio_checked_hover_light.png \ + qt/res/images/radio_checked_light.png \ + qt/res/images/radio_normal_dark.png \ + qt/res/images/radio_normal_disabled_dark.png \ qt/res/images/radio_normal_disabled_light.png \ - qt/res/images/radio_checked_disabled_light.png \ + qt/res/images/radio_normal_hover_dark.png \ + qt/res/images/radio_normal_hover_light.png \ + qt/res/images/radio_normal_light.png \ qt/res/images/splash.png QT_RES_CSS = \ diff --git a/src/chainlock/chainlock.cpp b/src/chainlock/chainlock.cpp index bbfb3c9f83af..9695b32bdef4 100644 --- a/src/chainlock/chainlock.cpp +++ b/src/chainlock/chainlock.cpp @@ -365,7 +365,7 @@ void CChainLocksHandler::EnforceBestChainLock() } GetMainSignals().NotifyChainLock(currentBestChainLockBlockIndex, clsig, clsig->ToString()); - uiInterface.NotifyChainLock(clsig->getBlockHash().ToString(), clsig->getHeight()); + uiInterface.NotifyChainLock(clsig->getBlockHash().ToString(), clsig->getHeight(), currentBestChainLockBlockIndex->GetBlockTime()); ::g_stats_client->gauge("chainlocks.blockHeight", clsig->getHeight(), 1.0f); } diff --git a/src/governance/object.cpp b/src/governance/object.cpp index fb4f75548d05..08a6dbae4c04 100644 --- a/src/governance/object.cpp +++ b/src/governance/object.cpp @@ -590,6 +590,20 @@ int CGovernanceObject::GetAbstainCount(const CDeterministicMNList& tip_mn_list, return CountMatchingVotes(tip_mn_list, eVoteSignalIn, VOTE_OUTCOME_ABSTAIN); } +std::pair CGovernanceObject::GetUniqueVoterCount(const CDeterministicMNList& tip_mn_list, vote_signal_enum_t eVoteSignalIn) const +{ + LOCK(cs); + uint16_t regular{0}, evo{0}; + for (const auto& [outpoint, rec_vote] : mapCurrentMNVotes) { + if (auto it = rec_vote.mapInstances.find(eVoteSignalIn); it != rec_vote.mapInstances.end()) { + if (auto dmn = tip_mn_list.GetMNByCollateral(outpoint)) { + dmn->nType == MnType::Evo ? evo++ : regular++; + } + } + } + return {regular, evo}; +} + bool CGovernanceObject::GetCurrentMNVotes(const COutPoint& mnCollateralOutpoint, vote_rec_t& voteRecord) const { LOCK(cs); diff --git a/src/governance/object.h b/src/governance/object.h index 05eb940bf275..64e3779c804e 100644 --- a/src/governance/object.h +++ b/src/governance/object.h @@ -226,6 +226,8 @@ class CGovernanceObject EXCLUSIVE_LOCKS_REQUIRED(!cs); int GetAbstainCount(const CDeterministicMNList& tip_mn_list, vote_signal_enum_t eVoteSignalIn) const EXCLUSIVE_LOCKS_REQUIRED(!cs); + std::pair GetUniqueVoterCount(const CDeterministicMNList& tip_mn_list, vote_signal_enum_t eVoteSignalIn) const + EXCLUSIVE_LOCKS_REQUIRED(!cs); bool GetCurrentMNVotes(const COutPoint& mnCollateralOutpoint, vote_rec_t& voteRecord) const EXCLUSIVE_LOCKS_REQUIRED(!cs); diff --git a/src/init.cpp b/src/init.cpp index f97d98f8a766..88f55215d2e9 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -2391,9 +2391,9 @@ bool AppInitMain(NodeContext& node, interfaces::BlockAndHeaderTipInfo* tip_info) } const std::string chainlock_notify = args.GetArg("-chainlocknotify", ""); if (!chainlock_notify.empty()) { - uiInterface.NotifyChainLock_connect([chainlock_notify](const std::string& bestChainLockHash, int bestChainLockHeight) { + uiInterface.NotifyChainLock_connect([chainlock_notify](const std::string& best_hash, int /* best_height */, int64_t /* best_block_time */) { std::string command = chainlock_notify; - ReplaceAll(command, "%s", bestChainLockHash); + ReplaceAll(command, "%s", best_hash); std::thread t(runCommand, command); t.detach(); // thread runs free }); diff --git a/src/instantsend/instantsend.cpp b/src/instantsend/instantsend.cpp index 495eefd68119..6b999594b3d4 100644 --- a/src/instantsend/instantsend.cpp +++ b/src/instantsend/instantsend.cpp @@ -721,6 +721,24 @@ size_t CInstantSendManager::GetInstantSendLockCount() const return db.GetInstantSendLockCount(); } +size_t CInstantSendManager::GetPendingInstantSendLockCount() const +{ + LOCK(cs_pendingLocks); + return pendingInstantSendLocks.size(); +} + +size_t CInstantSendManager::GetPendingNoTxInstantSendLockCount() const +{ + LOCK(cs_pendingLocks); + return pendingNoTxInstantSendLocks.size(); +} + +size_t CInstantSendManager::GetNonLockedTxCount() const +{ + LOCK(cs_nonLocked); + return nonLockedTxs.size(); +} + void CInstantSendManager::CacheBlockHeightInternal(const CBlockIndex* const block_index) const { AssertLockHeld(cs_height_cache); diff --git a/src/instantsend/instantsend.h b/src/instantsend/instantsend.h index 22421c8fc34f..dee4da674383 100644 --- a/src/instantsend/instantsend.h +++ b/src/instantsend/instantsend.h @@ -178,6 +178,9 @@ class CInstantSendManager final : public instantsend::InstantSendSignerParent EXCLUSIVE_LOCKS_REQUIRED(!cs_pendingLocks); size_t GetInstantSendLockCount() const; + size_t GetPendingInstantSendLockCount() const EXCLUSIVE_LOCKS_REQUIRED(!cs_pendingLocks); + size_t GetPendingNoTxInstantSendLockCount() const EXCLUSIVE_LOCKS_REQUIRED(!cs_pendingLocks); + size_t GetNonLockedTxCount() const EXCLUSIVE_LOCKS_REQUIRED(!cs_nonLocked); void CacheBlockHeight(const CBlockIndex* const block_index) const EXCLUSIVE_LOCKS_REQUIRED(!cs_height_cache); std::optional GetBlockHeight(const uint256& hash) const override EXCLUSIVE_LOCKS_REQUIRED(!cs_height_cache); diff --git a/src/interfaces/node.h b/src/interfaces/node.h index 3a4d59d55b29..356d5e1c9d7c 100644 --- a/src/interfaces/node.h +++ b/src/interfaces/node.h @@ -135,7 +135,11 @@ class GOV virtual ~GOV() {} virtual void getAllNewerThan(std::vector &objs, int64_t nMoreThanTime) = 0; virtual int32_t getObjAbsYesCount(const CGovernanceObject& obj, vote_signal_enum_t vote_signal) = 0; + virtual int32_t getObjYesCount(const CGovernanceObject& obj, vote_signal_enum_t vote_signal) = 0; + virtual int32_t getObjNoCount(const CGovernanceObject& obj, vote_signal_enum_t vote_signal) = 0; + virtual int32_t getObjAbstainCount(const CGovernanceObject& obj, vote_signal_enum_t vote_signal) = 0; virtual bool getObjLocalValidity(const CGovernanceObject& obj, std::string& error, bool check_collateral) = 0; + virtual bool existsObj(const uint256& hash) = 0; virtual bool isEnabled() = 0; virtual bool processVoteAndRelay(const CGovernanceVote& vote, std::string& error) = 0; struct GovernanceInfo { @@ -150,8 +154,10 @@ class GOV int requiredConfs{6}; }; virtual GovernanceInfo getGovernanceInfo() = 0; + virtual std::optional getProposalFundedHeight(const uint256& proposal_hash) = 0; virtual std::optional createProposal(int32_t revision, int64_t created_time, const std::string& data_hex, std::string& error) = 0; + virtual std::pair getObjUniqueVoters(const CGovernanceObject& obj, vote_signal_enum_t vote_signal) = 0; virtual bool submitProposal(const uint256& parent, int32_t revision, int64_t created_time, const std::string& data_hex, const uint256& fee_txid, std::string& out_object_hash, std::string& error) = 0; virtual void setContext(node::NodeContext* context) {} @@ -161,8 +167,23 @@ class GOV class LLMQ { public: + struct QuorumInfo { + std::string name; + size_t count{0}; + double health{0.0}; + }; + virtual ~LLMQ() {} + virtual CAmount getCreditPoolDiff() = 0; + virtual CAmount getCreditPoolLimit() = 0; + virtual CAmount getCreditPoolLocked() = 0; virtual size_t getInstantSentLockCount() = 0; + virtual size_t getInstantSendPendingLocks() = 0; + virtual size_t getInstantSendUnprotectedTxs() = 0; + virtual size_t getInstantSendWaitingForTx() = 0; + virtual size_t getPendingAssetUnlocks() = 0; + virtual std::vector getQuorumStats() = 0; + virtual void setContext(node::NodeContext* context) {} }; @@ -457,7 +478,7 @@ class Node //! Register handler for chainlock messages. using NotifyChainLockFn = - std::function; + std::function; virtual std::unique_ptr handleNotifyChainLock(NotifyChainLockFn fn) = 0; //! Register handler for header tip messages. diff --git a/src/net.cpp b/src/net.cpp index f62ebb16820d..346efea28a84 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -4200,6 +4200,7 @@ void CConnman::StopNodes() } m_nodes_disconnected.clear(); vhListenSocket.clear(); + WITH_LOCK(m_reconnections_mutex, m_reconnections.clear()); semOutbound.reset(); semAddnode.reset(); /** diff --git a/src/net.h b/src/net.h index 1da1093f187b..43423766a4ba 100644 --- a/src/net.h +++ b/src/net.h @@ -1261,8 +1261,8 @@ friend class CNode; EXCLUSIVE_LOCKS_REQUIRED(!m_total_bytes_sent_mutex, !m_added_nodes_mutex, !m_addr_fetches_mutex, !mutexMsgProc); void StopThreads(); - void StopNodes() EXCLUSIVE_LOCKS_REQUIRED(!m_nodes_mutex, !cs_mapSocketToNode, !cs_sendable_receivable_nodes); - void Stop() EXCLUSIVE_LOCKS_REQUIRED(!m_nodes_mutex, !cs_mapSocketToNode, !cs_sendable_receivable_nodes) + void StopNodes() EXCLUSIVE_LOCKS_REQUIRED(!m_nodes_mutex, !cs_mapSocketToNode, !cs_sendable_receivable_nodes, !m_reconnections_mutex); + void Stop() EXCLUSIVE_LOCKS_REQUIRED(!m_nodes_mutex, !cs_mapSocketToNode, !cs_sendable_receivable_nodes, !m_reconnections_mutex) { StopThreads(); StopNodes(); diff --git a/src/node/interface_ui.cpp b/src/node/interface_ui.cpp index ed4e4dea45f2..5651c8093061 100644 --- a/src/node/interface_ui.cpp +++ b/src/node/interface_ui.cpp @@ -59,7 +59,7 @@ void CClientUIInterface::NotifyNetworkActiveChanged(bool networkActive) { return void CClientUIInterface::NotifyAlertChanged() { return g_ui_signals.NotifyAlertChanged(); } void CClientUIInterface::ShowProgress(const std::string& title, int nProgress, bool resume_possible) { return g_ui_signals.ShowProgress(title, nProgress, resume_possible); } void CClientUIInterface::NotifyBlockTip(SynchronizationState s, const CBlockIndex* i) { return g_ui_signals.NotifyBlockTip(s, i); } -void CClientUIInterface::NotifyChainLock(const std::string& bestChainLockHash, int bestChainLockHeight) { return g_ui_signals.NotifyChainLock(bestChainLockHash, bestChainLockHeight); } +void CClientUIInterface::NotifyChainLock(const std::string& best_hash, int best_height, int64_t best_block_time) { return g_ui_signals.NotifyChainLock(best_hash, best_height, best_block_time); } void CClientUIInterface::NotifyHeaderTip(SynchronizationState s, const CBlockIndex* i) { return g_ui_signals.NotifyHeaderTip(s, i); } void CClientUIInterface::NotifyMasternodeListChanged(const CDeterministicMNList& list, const CBlockIndex* i) { return g_ui_signals.NotifyMasternodeListChanged(list, i); } void CClientUIInterface::NotifyAdditionalDataSyncProgressChanged(double nSyncProgress) { return g_ui_signals.NotifyAdditionalDataSyncProgressChanged(nSyncProgress); } diff --git a/src/node/interface_ui.h b/src/node/interface_ui.h index a2e5ab62301d..87da40e7118a 100644 --- a/src/node/interface_ui.h +++ b/src/node/interface_ui.h @@ -107,7 +107,7 @@ class CClientUIInterface ADD_SIGNALS_DECL_WRAPPER(NotifyBlockTip, void, SynchronizationState, const CBlockIndex*); /** New chainlock block has been accepted */ - ADD_SIGNALS_DECL_WRAPPER(NotifyChainLock, void, const std::string& bestChainLockHash, int bestChainLockHeight); + ADD_SIGNALS_DECL_WRAPPER(NotifyChainLock, void, const std::string& best_hash, int best_height, int64_t best_block_time); /** Best header has changed */ ADD_SIGNALS_DECL_WRAPPER(NotifyHeaderTip, void, SynchronizationState, const CBlockIndex*); diff --git a/src/node/interfaces.cpp b/src/node/interfaces.cpp index 88b7edf3bb56..fa14510424eb 100644 --- a/src/node/interfaces.cpp +++ b/src/node/interfaces.cpp @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -24,7 +25,11 @@ #include #include #include +#include #include +#include +#include +#include #include #include #include @@ -236,6 +241,34 @@ class GOVImpl : public GOV } return 0; } + int32_t getObjAbstainCount(const CGovernanceObject& obj, vote_signal_enum_t vote_signal) override + { + if (context().govman != nullptr && context().dmnman != nullptr) { + return obj.GetAbstainCount(context().dmnman->GetListAtChainTip(), vote_signal); + } + return 0; + } + int32_t getObjYesCount(const CGovernanceObject& obj, vote_signal_enum_t vote_signal) override + { + if (context().govman != nullptr && context().dmnman != nullptr) { + return obj.GetYesCount(context().dmnman->GetListAtChainTip(), vote_signal); + } + return 0; + } + int32_t getObjNoCount(const CGovernanceObject& obj, vote_signal_enum_t vote_signal) override + { + if (context().govman != nullptr && context().dmnman != nullptr) { + return obj.GetNoCount(context().dmnman->GetListAtChainTip(), vote_signal); + } + return 0; + } + bool existsObj(const uint256& hash) override + { + if (context().govman != nullptr) { + return context().govman->HaveObjectForHash(hash); + } + return false; + } bool getObjLocalValidity(const CGovernanceObject& obj, std::string& error, bool check_collateral) override { if (context().govman != nullptr && context().chainman != nullptr && context().dmnman != nullptr) { @@ -271,13 +304,7 @@ class GOVImpl : public GOV const Consensus::Params& consensusParams = Params().GetConsensus(); if (ctx.chainman) { - const CBlockIndex* tip = WITH_LOCK(::cs_main, return ctx.chainman->ActiveChain().Tip()); - int last = 0; - int next = 0; - const int height = tip ? tip->nHeight : 0; - CSuperblock::GetNearestSuperblocksHeights(height, last, next); - info.lastsuperblock = last; - info.nextsuperblock = next; + CSuperblock::GetNearestSuperblocksHeights(ctx.chainman->ActiveHeight(), info.lastsuperblock, info.nextsuperblock); } info.proposalfee = GOVERNANCE_PROPOSAL_FEE_TX; info.superblockcycle = consensusParams.nSuperblockCycle; @@ -292,6 +319,21 @@ class GOVImpl : public GOV } return info; } + std::optional getProposalFundedHeight(const uint256& proposal_hash) override + { + if (context().govman != nullptr && context().chainman != nullptr) { + const int32_t nTipHeight = context().chainman->ActiveHeight(); + for (const auto& trigger : context().govman->GetActiveTriggers()) { + if (!trigger || trigger->GetBlockHeight() > nTipHeight) continue; + for (const auto& hash : trigger->GetProposalHashes()) { + if (hash == proposal_hash) { + return trigger->GetBlockHeight(); + } + } + } + } + return std::nullopt; + } std::optional createProposal(int32_t revision, int64_t created_time, const std::string& data_hex, std::string& error) override { @@ -316,7 +358,13 @@ class GOVImpl : public GOV } return govobj; } - + std::pair getObjUniqueVoters(const CGovernanceObject& obj, vote_signal_enum_t vote_signal) override + { + if (context().govman != nullptr && context().dmnman != nullptr) { + return obj.GetUniqueVoterCount(context().dmnman->GetListAtChainTip(), vote_signal); + } + return {0, 0}; + } bool submitProposal(const uint256& parent, int32_t revision, int64_t created_time, const std::string& data_hex, const uint256& fee_txid, std::string& out_object_hash, std::string& error) override { @@ -364,13 +412,100 @@ class LLMQImpl : public LLMQ NodeContext& context() { return *Assert(m_context); } public: + CAmount getCreditPoolDiff() override + { + if (context().chainman && context().cpoolman) { + LOCK(::cs_main); + if (const auto* pindexTip = context().chainman->ActiveChain().Tip(); pindexTip && pindexTip->pprev) { + return context().cpoolman->GetCreditPool(pindexTip).locked - context().cpoolman->GetCreditPool(pindexTip->pprev).locked; + } + } + return 0; + } + CAmount getCreditPoolLimit() override + { + if (context().chainman && context().cpoolman) { + LOCK(::cs_main); + if (const auto* pindexTip = context().chainman->ActiveChain().Tip(); pindexTip) { + return context().cpoolman->GetCreditPool(pindexTip).currentLimit; + } + } + return 0; + } + CAmount getCreditPoolLocked() override + { + if (context().chainman && context().cpoolman) { + LOCK(::cs_main); + if (const auto* pindexTip = context().chainman->ActiveChain().Tip(); pindexTip) { + return context().cpoolman->GetCreditPool(pindexTip).locked; + } + } + return 0; + } size_t getInstantSentLockCount() override { - if (context().llmq_ctx->isman != nullptr) { + if (context().llmq_ctx && context().llmq_ctx->isman != nullptr) { return context().llmq_ctx->isman->GetInstantSendLockCount(); } return 0; } + size_t getInstantSendPendingLocks() override + { + if (context().llmq_ctx && context().llmq_ctx->isman) { + return context().llmq_ctx->isman->GetPendingInstantSendLockCount(); + } + return 0; + } + size_t getInstantSendUnprotectedTxs() override + { + if (context().llmq_ctx && context().llmq_ctx->isman) { + return context().llmq_ctx->isman->GetNonLockedTxCount(); + } + return 0; + } + size_t getInstantSendWaitingForTx() override + { + if (context().llmq_ctx && context().llmq_ctx->isman) { + return context().llmq_ctx->isman->GetPendingNoTxInstantSendLockCount(); + } + return 0; + } + size_t getPendingAssetUnlocks() override + { + size_t count{0}; + if (context().mempool) { + LOCK(context().mempool->cs); + for (const auto& entry : context().mempool->mapTx) { + if (entry.GetTx().IsPlatformTransfer()) { + ++count; + } + } + } + return count; + } + std::vector getQuorumStats() override + { + std::vector stats{}; + if (context().llmq_ctx && context().llmq_ctx->qman && context().chainman) { + LOCK(::cs_main); + if (const auto* pindexTip = context().chainman->ActiveChain().Tip(); pindexTip) { + for (const auto& type : llmq::GetEnabledQuorumTypes(*context().chainman, pindexTip)) { + if (const auto llmq_params = Params().GetLLMQ(type)) { + const auto quorums = context().llmq_ctx->qman->ScanQuorums(type, pindexTip, llmq_params->signingActiveQuorumCount); + double health = 0.0; + for (const auto& q : quorums) { + size_t numMembers = q->members.size(); + size_t numValidMembers = q->qc->CountValidMembers(); + health += (numMembers > 0) ? (double(numValidMembers) / double(numMembers)) : 0.0; + } + health = quorums.empty() ? 0.0 : (health / quorums.size()); + stats.emplace_back(std::string(llmq_params->name), quorums.size(), health); + } + } + } + } + return stats; + } void setContext(NodeContext* context) override { m_context = context; @@ -845,8 +980,8 @@ class NodeImpl : public Node } std::unique_ptr handleNotifyChainLock(NotifyChainLockFn fn) override { - return MakeHandler(::uiInterface.NotifyChainLock_connect([fn](const std::string& bestChainLockHash, int bestChainLockHeight) { - fn(bestChainLockHash, bestChainLockHeight); + return MakeHandler(::uiInterface.NotifyChainLock_connect([fn](const std::string& best_hash, int best_height, int64_t best_block_time) { + fn(best_hash, best_height, best_block_time); })); } std::unique_ptr handleNotifyHeaderTip(NotifyHeaderTipFn fn) override diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp index 3bebbb4c40b7..cfa6cffab4cf 100644 --- a/src/qt/bitcoin.cpp +++ b/src/qt/bitcoin.cpp @@ -80,6 +80,7 @@ Q_DECLARE_METATYPE(bool*) Q_DECLARE_METATYPE(CAmount) Q_DECLARE_METATYPE(SynchronizationState) Q_DECLARE_METATYPE(uint256) +Q_DECLARE_METATYPE(std::vector) static void RegisterMetaTypes() { @@ -97,6 +98,7 @@ static void RegisterMetaTypes() qRegisterMetaType>("std::function"); qRegisterMetaType("QMessageBox::Icon"); qRegisterMetaType("interfaces::BlockAndHeaderTipInfo"); + qRegisterMetaType>("std::vector"); #if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) qRegisterMetaTypeStreamOperators("BitcoinUnit"); diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index 249e4d25d751..467d41fcecb0 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -729,30 +729,23 @@ void BitcoinGUI::createToolBars() coinJoinCoinsButton->setStatusTip(coinJoinCoinsAction->statusTip()); tabGroup->addButton(coinJoinCoinsButton); - QSettings settings; - if (settings.value("fShowMasternodesTab").toBool()) { - masternodeButton = new QToolButton(this); - masternodeButton->setText(tr("&Masternodes")); - masternodeButton->setStatusTip(tr("Browse masternodes")); - tabGroup->addButton(masternodeButton); - connect(masternodeButton, &QToolButton::clicked, this, &BitcoinGUI::gotoMasternodePage); - masternodeButton->setEnabled(true); - } + masternodeButton = new QToolButton(this); + masternodeButton->setText(tr("&Masternodes")); + masternodeButton->setStatusTip(tr("Browse masternodes")); + tabGroup->addButton(masternodeButton); - 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); - } + governanceButton = new QToolButton(this); + governanceButton->setText(tr("&Governance")); + governanceButton->setStatusTip(tr("View Governance Proposals")); + tabGroup->addButton(governanceButton); connect(overviewButton, &QToolButton::clicked, this, &BitcoinGUI::gotoOverviewPage); connect(sendCoinsButton, &QToolButton::clicked, [this]{ gotoSendCoinsPage(); }); connect(coinJoinCoinsButton, &QToolButton::clicked, [this]{ gotoCoinJoinCoinsPage(); }); connect(receiveCoinsButton, &QToolButton::clicked, this, &BitcoinGUI::gotoReceiveCoinsPage); connect(historyButton, &QToolButton::clicked, this, &BitcoinGUI::gotoHistoryPage); + connect(governanceButton, &QToolButton::clicked, this, &BitcoinGUI::gotoGovernancePage); + connect(masternodeButton, &QToolButton::clicked, this, &BitcoinGUI::gotoMasternodePage); // Give the selected tab button a bolder font. connect(tabGroup, qOverload(&QButtonGroup::buttonToggled), this, &BitcoinGUI::highlightTabButton); @@ -762,7 +755,10 @@ void BitcoinGUI::createToolBars() button->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred); button->setToolTip(button->statusTip()); button->setCheckable(true); - toolbar->addWidget(button); + QAction* action = toolbar->addWidget(button); + if (button == coinJoinCoinsButton) { m_coinjoin_action = action; } + else if (button == governanceButton) { m_governance_action = action; } + else if (button == masternodeButton) { m_masternode_action = action; } } overviewButton->setChecked(true); @@ -877,20 +873,24 @@ void BitcoinGUI::setClientModel(ClientModel *_clientModel, interfaces::BlockAndH walletFrame->setClientModel(_clientModel); } #endif // ENABLE_WALLET - unitDisplayControl->setOptionsModel(_clientModel->getOptionsModel()); OptionsModel* optionsModel = _clientModel->getOptionsModel(); - if (optionsModel && trayIcon) { - // be aware of the tray icon disable state change reported by the OptionsModel object. - connect(optionsModel, &OptionsModel::showTrayIconChanged, trayIcon, &QSystemTrayIcon::setVisible); + if (optionsModel) { + unitDisplayControl->setOptionsModel(optionsModel); + m_mask_values_action->setChecked(optionsModel->getOption(OptionsModel::OptionID::MaskValues).toBool()); - // initialize the disable state of the tray icon with the current value in the model. - trayIcon->setVisible(optionsModel->getShowTrayIcon()); + connect(optionsModel, &OptionsModel::showCoinJoinChanged, this, &BitcoinGUI::updateCoinJoinVisibility); + connect(optionsModel, &OptionsModel::showGovernanceChanged, this, &BitcoinGUI::updateGovernanceVisibility); + connect(optionsModel, &OptionsModel::showMasternodesChanged, this, &BitcoinGUI::updateMasternodesVisibility); - connect(optionsModel, &OptionsModel::coinJoinEnabledChanged, this, &BitcoinGUI::updateCoinJoinVisibility); - } + if (trayIcon) { + // be aware of the tray icon disable state change reported by the OptionsModel object. + connect(optionsModel, &OptionsModel::showTrayIconChanged, trayIcon, &QSystemTrayIcon::setVisible); - m_mask_values_action->setChecked(_clientModel->getOptionsModel()->getOption(OptionsModel::OptionID::MaskValues).toBool()); + // initialize the disable state of the tray icon with the current value in the model. + trayIcon->setVisible(optionsModel->getShowTrayIcon()); + } + } } else { if(trayIconMenu) { @@ -917,6 +917,8 @@ void BitcoinGUI::setClientModel(ClientModel *_clientModel, interfaces::BlockAndH } updateCoinJoinVisibility(); + updateGovernanceVisibility(); + updateMasternodesVisibility(); } #ifdef ENABLE_WALLET @@ -1248,8 +1250,7 @@ void BitcoinGUI::highlightTabButton(QAbstractButton *button, bool checked) void BitcoinGUI::gotoGovernancePage() { - QSettings settings; - if (settings.value("fShowGovernanceTab").toBool() && governanceButton) { + if (governanceButton) { governanceButton->setChecked(true); if (walletFrame) walletFrame->gotoGovernancePage(); } @@ -1269,8 +1270,7 @@ void BitcoinGUI::gotoHistoryPage() void BitcoinGUI::gotoMasternodePage() { - QSettings settings; - if (settings.value("fShowMasternodesTab").toBool() && masternodeButton) { + if (masternodeButton) { masternodeButton->setChecked(true); if (walletFrame) walletFrame->gotoMasternodePage(); } @@ -1449,21 +1449,50 @@ void BitcoinGUI::updateProgressBarVisibility() void BitcoinGUI::updateCoinJoinVisibility() { + const bool fEnabled{ #ifdef ENABLE_WALLET - bool fEnabled = m_node.coinJoinOptions().isEnabled(); + m_node.coinJoinOptions().isEnabled() #else - bool fEnabled = false; -#endif - // CoinJoin button is the third QToolButton, show/hide the underlying QAction - // Hiding the QToolButton itself doesn't work for the GUI part - // but is still needed for shortcuts to work properly. - if (appToolBar != nullptr) { - appToolBar->actions()[4]->setVisible(fEnabled); - coinJoinCoinsButton->setVisible(fEnabled); - GUIUtil::updateButtonGroupShortcuts(tabGroup); - } - coinJoinCoinsAction->setVisible(fEnabled); - showCoinJoinHelpAction->setVisible(fEnabled); + false +#endif // ENABLE_WALLET + }; + + // Show/hide the underlying QAction, hiding the QToolButton itself doesn't + // work for the GUI part but is still needed for shortcuts to work properly. + if (m_coinjoin_action) m_coinjoin_action->setVisible(fEnabled); + if (coinJoinCoinsButton) coinJoinCoinsButton->setVisible(fEnabled); + if (coinJoinCoinsAction) coinJoinCoinsAction->setVisible(fEnabled); + if (showCoinJoinHelpAction) showCoinJoinHelpAction->setVisible(fEnabled); + + GUIUtil::updateButtonGroupShortcuts(tabGroup); + updateWidth(); +} + +void BitcoinGUI::updateGovernanceVisibility() +{ + if (!clientModel || !clientModel->getOptionsModel()) return; + const bool fShow = clientModel->getOptionsModel()->getShowGovernanceTab(); + + // Show/hide the underlying QAction, hiding the QToolButton itself doesn't + // work for the GUI part but is still needed for shortcuts to work properly. + if (m_governance_action) m_governance_action->setVisible(fShow); + if (governanceButton) governanceButton->setVisible(fShow); + + GUIUtil::updateButtonGroupShortcuts(tabGroup); + updateWidth(); +} + +void BitcoinGUI::updateMasternodesVisibility() +{ + if (!clientModel || !clientModel->getOptionsModel()) return; + const bool fShow = clientModel->getOptionsModel()->getShowMasternodesTab(); + + // Show/hide the underlying QAction, hiding the QToolButton itself doesn't + // work for the GUI part but is still needed for shortcuts to work properly. + if (m_masternode_action) m_masternode_action->setVisible(fShow); + if (masternodeButton) masternodeButton->setVisible(fShow); + + GUIUtil::updateButtonGroupShortcuts(tabGroup); updateWidth(); } diff --git a/src/qt/bitcoingui.h b/src/qt/bitcoingui.h index 576605443e7c..7c8af4f63386 100644 --- a/src/qt/bitcoingui.h +++ b/src/qt/bitcoingui.h @@ -149,6 +149,9 @@ class BitcoinGUI : public QMainWindow QAction* usedReceivingAddressesAction = nullptr; QAction* signMessageAction = nullptr; QAction* verifyMessageAction = nullptr; + QAction* m_coinjoin_action = nullptr; + QAction* m_governance_action = nullptr; + QAction* m_masternode_action = nullptr; QAction* m_load_psbt_action = nullptr; QAction* m_load_psbt_clipboard_action = nullptr; QAction* aboutAction = nullptr; @@ -388,6 +391,8 @@ public Q_SLOTS: void showModalOverlay(); void updateCoinJoinVisibility(); + void updateGovernanceVisibility(); + void updateMasternodesVisibility(); void updateWidth(); }; diff --git a/src/qt/clientmodel.cpp b/src/qt/clientmodel.cpp index 81978e0da16f..768c78714e1a 100644 --- a/src/qt/clientmodel.cpp +++ b/src/qt/clientmodel.cpp @@ -56,7 +56,15 @@ ClientModel::ClientModel(interfaces::Node& node, OptionsModel *_optionsModel, QO // no locking required at this point // the following calls will acquire the required lock Q_EMIT mempoolSizeChanged(m_node.getMempoolSize(), m_node.getMempoolDynamicUsage(), m_node.getMempoolMaxUsage()); - Q_EMIT islockCountChanged(m_node.llmq().getInstantSentLockCount()); + Q_EMIT statsCreditPoolChanged(m_node.llmq().getCreditPoolDiff(), + m_node.llmq().getCreditPoolLimit(), + m_node.llmq().getCreditPoolLocked(), + m_node.llmq().getPendingAssetUnlocks()); + Q_EMIT statsISChanged(m_node.llmq().getInstantSentLockCount(), + m_node.llmq().getInstantSendPendingLocks(), + m_node.llmq().getInstantSendWaitingForTx(), + m_node.llmq().getInstantSendUnprotectedTxs()); + Q_EMIT statsQuorumChanged(m_node.llmq().getQuorumStats()); }); connect(m_thread, &QThread::finished, timer, &QObject::deleteLater); connect(m_thread, &QThread::started, [timer] { timer->start(); }); @@ -314,8 +322,8 @@ void ClientModel::subscribeToCoreSignals() Q_EMIT additionalDataSyncProgressChanged(nSyncProgress); })); m_event_handlers.emplace_back(m_node.handleNotifyChainLock( - [this](const std::string& best_hash, int best_height) { - Q_EMIT chainLockChanged(QString::fromStdString(best_hash), best_height); + [this](const std::string& best_hash, int best_height, int64_t best_block_time) { + Q_EMIT chainLockChanged(QString::fromStdString(best_hash), best_height, best_block_time); })); m_event_handlers.emplace_back(m_node.handleNotifyMasternodeListChanged( [this](const CDeterministicMNList& newList, const CBlockIndex* pindex) { diff --git a/src/qt/clientmodel.h b/src/qt/clientmodel.h index fbf325fbd8b4..46550dd44e8c 100644 --- a/src/qt/clientmodel.h +++ b/src/qt/clientmodel.h @@ -129,11 +129,13 @@ class ClientModel : public QObject Q_SIGNALS: void numConnectionsChanged(int count); void masternodeListChanged() const; - void chainLockChanged(const QString& bestChainLockHash, int bestChainLockHeight); + void chainLockChanged(const QString& best_hash, int best_height, int64_t best_block_time); void numBlocksChanged(int count, const QDateTime& blockDate, const QString& blockHash, double nVerificationProgress, bool header, SynchronizationState sync_state); void additionalDataSyncProgressChanged(double nSyncProgress); void mempoolSizeChanged(long count, size_t mempoolSizeInBytes, size_t mempoolMaxSizeInBytes); - void islockCountChanged(size_t count); + void statsCreditPoolChanged(CAmount diff, CAmount limit, CAmount locked, size_t pending); + void statsISChanged(size_t verified, size_t unverified, size_t awaitingTx, size_t unprotectedTx); + void statsQuorumChanged(const std::vector& quorums); void networkActiveChanged(bool networkActive); void alertsChanged(const QString &warnings); diff --git a/src/qt/descriptiondialog.cpp b/src/qt/descriptiondialog.cpp new file mode 100644 index 000000000000..164939d3f284 --- /dev/null +++ b/src/qt/descriptiondialog.cpp @@ -0,0 +1,27 @@ +// Copyright (c) 2011-2021 The Bitcoin Core developers +// Copyright (c) 2026 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 + +#include +#include + +#include + +DescriptionDialog::DescriptionDialog(const QString& title, const QString& html, QWidget* parent) : + QDialog{parent, GUIUtil::dialog_flags}, + ui{new Ui::DescriptionDialog} +{ + ui->setupUi(this); + setWindowTitle(title); + GUIUtil::registerWidget(ui->detailText, html); + GUIUtil::updateFonts(); + GUIUtil::handleCloseWindowShortcut(this); +} + +DescriptionDialog::~DescriptionDialog() +{ + delete ui; +} diff --git a/src/qt/descriptiondialog.h b/src/qt/descriptiondialog.h new file mode 100644 index 000000000000..161983a3e8d9 --- /dev/null +++ b/src/qt/descriptiondialog.h @@ -0,0 +1,28 @@ +// Copyright (c) 2011-2020 The Bitcoin Core developers +// Copyright (c) 2026 The Dash 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_DESCRIPTIONDIALOG_H +#define BITCOIN_QT_DESCRIPTIONDIALOG_H + +#include + +namespace Ui { +class DescriptionDialog; +} // namespace Ui + +/** Generic dialog showing detailed text description. */ +class DescriptionDialog : public QDialog +{ + Q_OBJECT + +public: + explicit DescriptionDialog(const QString& title, const QString& html, QWidget* parent = nullptr); + ~DescriptionDialog(); + +private: + Ui::DescriptionDialog* ui; +}; + +#endif // BITCOIN_QT_DESCRIPTIONDIALOG_H diff --git a/src/qt/donutchart.cpp b/src/qt/donutchart.cpp new file mode 100644 index 000000000000..1536d7391eb7 --- /dev/null +++ b/src/qt/donutchart.cpp @@ -0,0 +1,220 @@ +// Copyright (c) 2026 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 + +#include + +#include +#include +#include + +namespace { +constexpr int CHART_MARGIN{10}; +constexpr int TEXT_PADDING{20}; +} // anonymous namespace + +DonutChart::DonutChart(QWidget* parent) + : QWidget(parent) +{ + setMouseTracking(true); + setAttribute(Qt::WA_Hover); +} + +DonutChart::~DonutChart() = default; + +void DonutChart::setData(std::vector&& slices, double total_capacity, CenterText&& default_text) +{ + m_slices = std::move(slices); + m_total_capacity = total_capacity; + m_default_center_text = std::move(default_text); + m_total_allocated = 0; + for (const auto& slice : m_slices) { + m_total_allocated += slice.value; + } + m_hovered_slice = -1; + update(); +} + +void DonutChart::clear() +{ + m_slices.clear(); + m_total_capacity = 0; + m_total_allocated = 0; + m_hovered_slice = -1; + m_default_center_text = {}; + update(); +} + +QSize DonutChart::sizeHint() const +{ + return QSize(200, 200); +} + +QSize DonutChart::minimumSizeHint() const +{ + return QSize(150, 150); +} + +void DonutChart::paintEvent(QPaintEvent* /*event*/) +{ + QPainter painter{this}; + painter.setRenderHint(QPainter::Antialiasing); + + // Add 10% vertical padding (reduce effective height by 20%) + const int effective_height{static_cast(height() * 0.8)}; + const int side{qMin(width(), effective_height) - 2 * CHART_MARGIN}; + if (side <= 0) return; + + // Scale donut thickness with size: 25% of outer radius, with min/max bounds + const int outer_radius{side / 2}; + const int donut_thickness{qBound(25, outer_radius / 4, 60)}; + const int inner_radius{outer_radius - donut_thickness}; + const QPoint center{width() / 2, height() / 2}; + + // Draw background circle (unallocated portion) + painter.setPen(Qt::NoPen); + painter.setBrush(GUIUtil::getThemedQColor(GUIUtil::ThemedColor::BORDER_WIDGET)); + painter.drawEllipse(center, outer_radius, outer_radius); + + // Draw slices + if (m_total_capacity > 0 && !m_slices.empty()) { + int start_angle{90 * 16}; // Start from top (Qt uses 1/16th of a degree) + for (size_t idx{0}; idx < m_slices.size(); idx++) { + const auto& slice = m_slices[idx]; + const double fraction{slice.value / m_total_capacity}; + const int span_angle{static_cast(-fraction * 360 * 16)}; // Negative for clockwise + QColor color{slice.color}; + if (static_cast(idx) == m_hovered_slice) { + color = color.lighter(130); + } + painter.setBrush(color); + painter.drawPie(center.x() - outer_radius, center.y() - outer_radius, + outer_radius * 2, outer_radius * 2, start_angle, span_angle); + start_angle += span_angle; + } + } + + // Draw inner circle + painter.setBrush(GUIUtil::getThemedQColor(GUIUtil::ThemedColor::BACKGROUND_WIDGET)); + painter.drawEllipse(center, inner_radius, inner_radius); + + // Draw center text + painter.setPen(GUIUtil::getThemedQColor(GUIUtil::ThemedColor::DEFAULT)); + + QString line1, line2, line3; + if (m_hovered_slice >= 0 && m_hovered_slice < static_cast(m_slices.size())) { + const auto& slice = m_slices[m_hovered_slice]; + line1 = slice.donut_center_label; + line2 = slice.donut_sub_label1; + line3 = slice.donut_sub_label2; + } else { + line1 = m_default_center_text.donut_center_label; + line2 = m_default_center_text.donut_sub_label1; + line3 = m_default_center_text.donut_sub_label2; + } + + // Calculate font sizes to fit in the inner circle - scale with widget size + const int max_text_width{inner_radius * 2 - TEXT_PADDING}; + QFont font{painter.font()}; + + // Scale font sizes based on inner radius (min 8pt, max reasonable sizes) + const int primary_font_size{qBound(9, inner_radius * 11 / 60, 20)}; + const int secondary_font_size{qBound(8, inner_radius * 11 / 80, 15)}; + + // Line 1 (name or allocated amount) - larger + font.setPointSize(primary_font_size); + font.setBold(true); + painter.setFont(font); + QFontMetrics fm1{font}; + QString elided_line1{fm1.elidedText(line1, Qt::ElideRight, max_text_width)}; + + // Lines 2 and 3 - smaller + font.setPointSize(secondary_font_size); + font.setBold(false); + painter.setFont(font); + QFontMetrics fm2{font}; + + const int line_height{fm2.height()}; + const int total_height{fm1.height() + line_height * 2 + 4}; + int y{center.y() - total_height / 2}; + + // Draw line 1 + font.setPointSize(primary_font_size); + font.setBold(true); + painter.setFont(font); + painter.drawText(QRect(center.x() - max_text_width / 2, y, max_text_width, fm1.height()), + Qt::AlignCenter, elided_line1); + y += fm1.height() + 2; + + // Draw lines 2 and 3 + font.setPointSize(secondary_font_size); + font.setBold(false); + painter.setFont(font); + painter.drawText(QRect(center.x() - max_text_width / 2, y, max_text_width, line_height), + Qt::AlignCenter, line2); + y += line_height; + painter.drawText(QRect(center.x() - max_text_width / 2, y, max_text_width, line_height), + Qt::AlignCenter, line3); +} + +int DonutChart::sliceAtPosition(const QPoint& pos) const +{ + if (m_slices.empty() || m_total_capacity <= 0) { + return -1; + } + + // Must match paintEvent calculations + const int effective_height{static_cast(height() * 0.8)}; + const int side{qMin(width(), effective_height) - 2 * CHART_MARGIN}; + const int outer_radius{side / 2}; + const int donut_thickness{qBound(25, outer_radius / 4, 60)}; + const int inner_radius{outer_radius - donut_thickness}; + const QPoint center{width() / 2, height() / 2}; + + // Check if point is within the donut ring + const int dx{pos.x() - center.x()}; + const int dy{pos.y() - center.y()}; + const double dist{qSqrt(dx * dx + dy * dy)}; + + if (dist < inner_radius || dist > outer_radius) { + return -1; + } + + // Calculate angle (0 = top, clockwise) + double angle{qAtan2(dx, -dy)}; // Note: swapped and negated for top=0, clockwise + if (angle < 0) angle += 2 * M_PI; + + // Find which slice this angle falls into + double current_angle{0}; + for (size_t idx{0}; idx < m_slices.size(); idx++) { + const double fraction{m_slices[idx].value / m_total_capacity}; + const double slice_angle{fraction * 2 * M_PI}; + if (angle >= current_angle && angle < current_angle + slice_angle) { + return static_cast(idx); + } + current_angle += slice_angle; + } + + return -1; +} + +void DonutChart::mouseMoveEvent(QMouseEvent* event) +{ + const int slice{sliceAtPosition(event->pos())}; + if (slice != m_hovered_slice) { + m_hovered_slice = slice; + update(); + } + QWidget::mouseMoveEvent(event); +} + +void DonutChart::leaveEvent(QEvent* event) +{ + if (m_hovered_slice != -1) { + m_hovered_slice = -1; + update(); + } + QWidget::leaveEvent(event); +} diff --git a/src/qt/donutchart.h b/src/qt/donutchart.h new file mode 100644 index 000000000000..ef49b15b58e6 --- /dev/null +++ b/src/qt/donutchart.h @@ -0,0 +1,56 @@ +// Copyright (c) 2026 The Dash 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_DONUTCHART_H +#define BITCOIN_QT_DONUTCHART_H + +#include + +#include + +class DonutChart : public QWidget +{ + Q_OBJECT + +public: + struct Slice { + double value; + QColor color; + QString donut_center_label; + QString donut_sub_label1; + QString donut_sub_label2; + }; + + struct CenterText { + QString donut_center_label; + QString donut_sub_label1; + QString donut_sub_label2; + }; + + explicit DonutChart(QWidget* parent = nullptr); + ~DonutChart() override; + + void setData(std::vector&& slices, double total_capacity, CenterText&& default_text); + void clear(); + + QSize sizeHint() const override; + QSize minimumSizeHint() const override; + +protected: + void paintEvent(QPaintEvent* event) override; + void mouseMoveEvent(QMouseEvent* event) override; + void leaveEvent(QEvent* event) override; + +private: + int sliceAtPosition(const QPoint& pos) const; + +private: + CenterText m_default_center_text; + double m_total_allocated{0}; + double m_total_capacity{0}; + int m_hovered_slice{-1}; + std::vector m_slices; +}; + +#endif // BITCOIN_QT_DONUTCHART_H diff --git a/src/qt/forms/debugwindow.ui b/src/qt/forms/debugwindow.ui index 16a749a87418..27209e0e37da 100644 --- a/src/qt/forms/debugwindow.ui +++ b/src/qt/forms/debugwindow.ui @@ -102,464 +102,50 @@ 0 - - - 12 - - - - - General - - - - - - - Client version - - - - - - - IBeamCursor - - - N/A - - - Qt::PlainText - - - Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse - - - - - - - User Agent - - - 10 - - - - - - - IBeamCursor - - - N/A - - - Qt::PlainText - - - Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse - - - - - - - Datadir - - - - - - - IBeamCursor - - - To specify a non-default location of the data directory use the '%1' option. - - - N/A - - - Qt::PlainText - - - true - - - Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse - - - - - - - Blocksdir - - - - - - - IBeamCursor - - - To specify a non-default location of the blocks directory use the '%1' option. - - - N/A - - - Qt::PlainText - - - true - - - Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse - - - - - - - Startup time - - - - - - - IBeamCursor - - - N/A - - - Qt::PlainText - - - Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse - - - - - - - Network - - - - - - - Name - - - - - - - IBeamCursor - - - N/A - - - Qt::PlainText - - - Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse - - - - - - - Number of connections - - - - - - - IBeamCursor - - - N/A - - - Qt::PlainText - - - Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse - - - - - - - Local Addresses - - - - - - - IBeamCursor - - - true - - - - 0 - 0 - - - - N/A - - - Qt::PlainText - - - Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse - - - Network addresses that your Dash node is currently using to communicate with other nodes. - - - - - - - Number of regular Masternodes - - - - - - - IBeamCursor - - - N/A - - - Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse - - - - - - - Number of EvoNodes - - - - - - - IBeamCursor - - - N/A - - - Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse - - - - - - - Block chain - - - - - - - Current block height - - - - - - - IBeamCursor - - - N/A - - - Qt::PlainText - - - Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse - - - - - - - Last block time - - - - - - - IBeamCursor - - - N/A - - - Qt::PlainText - - - Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse - - - - - - - Last block hash - - - - - - - IBeamCursor - - - N/A - - - Qt::PlainText - - - Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse - - - - - - - Latest ChainLocked block hash - - - - - - - IBeamCursor - - - N/A - - - Qt::PlainText - - - Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse - - - - - - - Latest ChainLocked block height - - - - - - - IBeamCursor - - - N/A - - - Qt::PlainText - - - Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse - - - - - - - Memory Pool - - - - - - - Current number of transactions - - - - - - - IBeamCursor - - - N/A - - - Qt::PlainText - - - Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse - - - - - - - Memory usage - - - - - - - IBeamCursor - - - N/A - - - Qt::PlainText - - - Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + 0 + + + - - - - 3 - + + + + + + QComboBox::AdjustToContents + + + + General + + + + + Network + + + + + Governance + + + + - + - Qt::Vertical + Qt::Horizontal - 10 - 5 + 40 + 20 @@ -586,42 +172,6 @@ - - - - InstantSend locks - - - - - - - IBeamCursor - - - N/A - - - Qt::PlainText - - - Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - @@ -1826,6 +1376,24 @@ + + InformationWidget + QWidget +
qt/informationwidget.h
+ 1 +
+ + NetworkWidget + QWidget +
qt/networkwidget.h
+ 1 +
+ + GovernanceInfo + QWidget +
qt/governanceinfo.h
+ 1 +
TrafficGraphWidget QWidget diff --git a/src/qt/forms/transactiondescdialog.ui b/src/qt/forms/descriptiondialog.ui similarity index 81% rename from src/qt/forms/transactiondescdialog.ui rename to src/qt/forms/descriptiondialog.ui index 3a698cfd1d40..0918e5a6174a 100644 --- a/src/qt/forms/transactiondescdialog.ui +++ b/src/qt/forms/descriptiondialog.ui @@ -1,7 +1,7 @@ - TransactionDescDialog - + DescriptionDialog + 0 @@ -11,13 +11,13 @@ - Transaction details + Details - This pane shows a detailed description of the transaction + This pane shows detailed information true @@ -41,7 +41,7 @@ buttonBox accepted() - TransactionDescDialog + DescriptionDialog accept() @@ -57,7 +57,7 @@ buttonBox rejected() - TransactionDescDialog + DescriptionDialog reject() diff --git a/src/qt/forms/governanceinfo.ui b/src/qt/forms/governanceinfo.ui new file mode 100644 index 000000000000..e7f67b31df75 --- /dev/null +++ b/src/qt/forms/governanceinfo.ui @@ -0,0 +1,337 @@ + + + GovernanceInfo + + + + 0 + 0 + 700 + 400 + + + + + + + + 1 + 0 + + + + + 12 + + + + + + General + + + + + + + Voting cycles + + + + + + + IBeamCursor + + + N/A + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + Last superblock + + + + + + + IBeamCursor + + + N/A + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + Next superblock + + + + + + + IBeamCursor + + + N/A + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + Voting cutoff block + + + + + + + IBeamCursor + + + N/A + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + + Participation + + + + + + + Masternodes voting + + + + + + + IBeamCursor + + + N/A + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + EvoNodes voting + + + + + + + IBeamCursor + + + N/A + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + Passing threshold + + + + + + + IBeamCursor + + + N/A + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + + Proposals + + + + + + + Proposal Count + + + + + + + IBeamCursor + + + N/A + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + Budget allocated + + + + + + + IBeamCursor + + + N/A + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + Passing Proposals + + + + + + + IBeamCursor + + + N/A + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + Unfunded proposals + + + 10 + + + + + + + IBeamCursor + + + N/A + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + Failing Proposals + + + + + + + IBeamCursor + + + N/A + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + + 150 + 150 + + + + + 1 + 0 + + + + + + + + + DonutChart + QWidget +
qt/donutchart.h
+
+
+ + +
diff --git a/src/qt/forms/governancelist.ui b/src/qt/forms/governancelist.ui index 58fad21b9106..73e4803509e0 100644 --- a/src/qt/forms/governancelist.ui +++ b/src/qt/forms/governancelist.ui @@ -32,91 +32,132 @@ 0
- - - Qt::Horizontal + + + 0 - - - 40 - 20 - - - + + + + Select proposals to display + + + + + + + + 1 + 0 + + + + Filter proposal list + + + Filter by Title + + + + + + + Masternode Count: + + + Number of masternodes this wallet can vote with (masternodes for which this wallet holds the voting key) + + + + + + + 0 + + + +
- - - - - 0 - + + + + + - - - Filter List: - - - - - - - Filter proposal list - - - Filter by Title - - - - - + - Qt::Horizontal - - - QSizePolicy::Expanding + Qt::Vertical - 40 - 20 + 20 + 40 - + - Masternode Count: + No active proposals on the network. - - Number of masternodes this wallet can vote with (masternodes for which this wallet holds the voting key) + + Qt::AlignCenter - - - 0 - - - - - + - Qt::Horizontal - - - QSizePolicy::Fixed + Qt::Vertical 20 - 20 + 40 + + + + + + + + + + Create Proposal + + + + + + + Resume Proposal + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + 3 + @@ -131,35 +172,8 @@ - - - - Qt::Horizontal - - - QSizePolicy::Expanding - - - - 40 - 20 - - - - - - - - Create Proposal - - - - - - - diff --git a/src/qt/forms/informationwidget.ui b/src/qt/forms/informationwidget.ui new file mode 100644 index 000000000000..8180076b9b0f --- /dev/null +++ b/src/qt/forms/informationwidget.ui @@ -0,0 +1,388 @@ + + + InformationWidget + + + + 0 + 0 + 700 + 500 + + + + + 12 + + + + + General + + + + + + + Client version + + + + + + + IBeamCursor + + + N/A + + + Qt::PlainText + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + User Agent + + + 10 + + + + + + + IBeamCursor + + + N/A + + + Qt::PlainText + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + Datadir + + + + + + + IBeamCursor + + + To specify a non-default location of the data directory use the '%1' option. + + + N/A + + + Qt::PlainText + + + true + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + Blocksdir + + + + + + + IBeamCursor + + + To specify a non-default location of the blocks directory use the '%1' option. + + + N/A + + + Qt::PlainText + + + true + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + Startup time + + + + + + + IBeamCursor + + + N/A + + + Qt::PlainText + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + Network + + + + + + + Name + + + + + + + IBeamCursor + + + N/A + + + Qt::PlainText + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + Number of connections + + + + + + + IBeamCursor + + + N/A + + + Qt::PlainText + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + Local Addresses + + + + + + + IBeamCursor + + + true + + + + 0 + 0 + + + + N/A + + + Qt::PlainText + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + Network addresses that your Dash node is currently using to communicate with other nodes. + + + + + + + Block chain + + + + + + + Current block height + + + + + + + IBeamCursor + + + N/A + + + Qt::PlainText + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + Last block time + + + + + + + IBeamCursor + + + N/A + + + Qt::PlainText + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + Last block hash + + + + + + + IBeamCursor + + + N/A + + + Qt::PlainText + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + Memory Pool + + + + + + + Current number of transactions + + + + + + + IBeamCursor + + + N/A + + + Qt::PlainText + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + Memory usage + + + + + + + IBeamCursor + + + N/A + + + Qt::PlainText + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + diff --git a/src/qt/forms/networkwidget.ui b/src/qt/forms/networkwidget.ui new file mode 100644 index 000000000000..2296930537cf --- /dev/null +++ b/src/qt/forms/networkwidget.ui @@ -0,0 +1,377 @@ + + + NetworkWidget + + + + 0 + 0 + 700 + 500 + + + + + + + 12 + + + + + ChainLocks + + + + + + + Current block height + + + + + + + IBeamCursor + + + N/A + + + Qt::PlainText + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + Last block time + + + + + + + IBeamCursor + + + N/A + + + Qt::PlainText + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + Last block hash + + + + + + + IBeamCursor + + + N/A + + + Qt::PlainText + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + Credit Pool + + + + + + + Last block change + + + + + + + IBeamCursor + + + N/A + + + Qt::PlainText + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + Total locked + + + + + + + IBeamCursor + + + N/A + + + Qt::PlainText + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + Pending unlocks + + + + + + + IBeamCursor + + + N/A + + + Qt::PlainText + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + Withdrawal limit + + + + + + + IBeamCursor + + + N/A + + + Qt::PlainText + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + InstantSend + + + + + + + Verified locks + + + + + + + IBeamCursor + + + N/A + + + Qt::PlainText + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + Unverified locks + + + + + + + IBeamCursor + + + N/A + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + Locks awaiting transaction + + + + + + + IBeamCursor + + + N/A + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + Unprotected transactions + + + + + + + IBeamCursor + + + N/A + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + Masternodes + + + + + + + Masternode count + + + + + + + IBeamCursor + + + N/A + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + EvoNode count + + + + + + + IBeamCursor + + + N/A + + + Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + 12 + + + + + Quorums + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + + + diff --git a/src/qt/forms/optionsdialog.ui b/src/qt/forms/optionsdialog.ui index eda0ce5cdd69..fbfcd6248196 100644 --- a/src/qt/forms/optionsdialog.ui +++ b/src/qt/forms/optionsdialog.ui @@ -325,18 +325,18 @@
- - - - Whether to set subtract fee from amount as default or not. - - - Subtract &fee from amount by default - - - + + + + Whether to set subtract fee from amount as default or not. + + + Subtract &fee from amount by default + + + diff --git a/src/qt/forms/proposalcreate.ui b/src/qt/forms/proposalcreate.ui new file mode 100644 index 000000000000..bc673aab86b6 --- /dev/null +++ b/src/qt/forms/proposalcreate.ui @@ -0,0 +1,449 @@ + + + ProposalCreate + + + + 0 + 0 + 860 + 380 + + + + + 860 + 380 + + + + New proposal + + + + 12 + + + 0 + + + 12 + + + 10 + + + 6 + + + + + + + + + 4 + + + 4 + + + 4 + + + + + Proposal &name + + + editName + + + + + + + + 0 + 0 + + + + short-unique-name + + + 24 + + + + + + + + + 4 + + + 4 + + + 4 + + + + + &Description URL + + + editUrl + + + + + + + + 0 + 0 + + + + https://example.com/my-proposal + + + 24 + + + + + + + + + 4 + + + 4 + + + 12 + + + 4 + + + + + &Payment date + + + comboFirstPayment + + + + + + + Pa&yments + + + spinPayments + + + + + + + Payment &address + + + editPayAddr + + + + + + + + 0 + 0 + + + + 140 + + + 24 + + + + + + + + 0 + 0 + + + + 80 + + + 24 + + + 1 + + + 12 + + + 1 + + + + + + + + 0 + 0 + + + + 200 + + + 24 + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 0 + 4 + + + + + + + + Payment &amount + + + paymentAmount + + + + + + + To&tal amount + + + labelTotalValue + + + + + + + + 0 + 0 + + + + 140 + + + The amount to request in a single payment + + + + + + + Qt::Horizontal + + + QSizePolicy::Ignored + + + + 0 + 0 + + + + + + + + 0 + + + + 0 + 0 + + + + 190 + + + 24 + + + Qt::AlignLeft|Qt::AlignVCenter + + + + + + + + + Qt::Vertical + + + + 0 + 0 + + + + + + + + + 8 + + + + + View JSON + + + + 0 + 0 + + + + + 100 + 28 + + + + false + + + + + + + View Payload + + + + 0 + 0 + + + + + 100 + 28 + + + + false + + + + + + + + + + + 1 + 0 + + + + Qt::AlignLeft|Qt::AlignVCenter + + + margin-left: 8px; + + + + + + + Create Proposal + + + + 0 + 0 + + + + + 120 + 28 + + + + true + + + + + + + + + + + + + + QValidatedLineEdit + QLineEdit +
qt/qvalidatedlineedit.h
+ 0 +
+ + BitcoinAmountField + QLineEdit +
qt/bitcoinamountfield.h
+
+
+ + editName + editUrl + comboFirstPayment + spinPayments + editPayAddr + paymentAmount + btnCreate + + + +
diff --git a/src/qt/forms/proposalresume.ui b/src/qt/forms/proposalresume.ui new file mode 100644 index 000000000000..c631a92322bc --- /dev/null +++ b/src/qt/forms/proposalresume.ui @@ -0,0 +1,113 @@ + + + ProposalResume + + + + 0 + 0 + 720 + 400 + + + + + 720 + 400 + + + + Resume Proposals + + + + 16 + + + 16 + + + 16 + + + 0 + + + 0 + + + + + + 0 + 1 + + + + true + + + QFrame::NoFrame + + + + + 16 + + + 16 + + + 16 + + + 16 + + + 16 + + + + + + + + + 0 + + + 8 + + + 0 + + + 16 + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Close + + + + + + + + + + diff --git a/src/qt/forms/proposalwizard.ui b/src/qt/forms/proposalwizard.ui deleted file mode 100644 index 992929f64ed3..000000000000 --- a/src/qt/forms/proposalwizard.ui +++ /dev/null @@ -1,758 +0,0 @@ - - - ProposalWizard - - - - 0 - 0 - 640 - 420 - - - - Create Governance Proposal - - - - 12 - - - 0 - - - 12 - - - 10 - - - 6 - - - - - - - - - Enter proposal details - - - 0 - - - Qt::AlignHCenter|Qt::AlignVCenter - - - true - - - - - - - A fee will be burned when you prepare the proposal. - - - Qt::AlignHCenter|Qt::AlignVCenter - - - true - - - - - - - 8 - - - 6 - - - QFormLayout::ExpandingFieldsGrow - - - Qt::AlignRight|Qt::AlignVCenter - - - - - Proposal &name - - - editName - - - - - - - - 0 - 0 - - - - short-unique-name - - - 24 - - - - - - - &Description URL - - - editUrl - - - - - - - - 0 - 0 - - - - https://example.com/my-proposal - - - 24 - - - - - - - Payment &address - - - editPayAddr - - - - - - - - 0 - 0 - - - - 24 - - - - - - - Payment &amount - - - paymentAmount - - - - - - - - 0 - 0 - - - - The amount to request in a single payment - - - - - - - &First payment - - - comboFirstPayment - - - - - - - 8 - - - - - - 0 - 0 - - - - 24 - - - - - - - Pa&yments - - - comboPayments - - - - - - - - 0 - 0 - - - - 24 - - - - - - - - - To&tal amount - - - labelTotalValue - - - - - - - 0 - - - - - - - Proposal &fee - - - labelFeeValue - - - - - - - - - - - 20 - - - - - - - - - 8 - - - - - Qt::Horizontal - - - - 20 - 10 - - - - - - - - Next - - - - 0 - 28 - - - - - - - - - - - - - - Review proposal JSON and validate. - - - - - - - 120 - - - true - - - - - - - Hex-encoded JSON - - - true - - - - - - - - - - - - - - - 8 - - - - - Qt::Horizontal - - - - 20 - 10 - - - - - - - - Back - - - - 0 - 28 - - - - - - - - Next - - - false - - - - 0 - 28 - - - - - - - - - - - - - - Prepare (burn fee) and wait for confirmations. - - - - - - - 8 - - - - - TxID: - - - - - - - true - - - - - - - 24 - - - - - - - Copy - - - - 0 - 28 - - - - - - - - - - - - - - - - - - - At 1/6 confirmations: can be relayed and queued. At 6/6: accepted and processed. - - - true - - - - - - - 0 - - - 6 - - - 0 - - - false - - - 10 - - - Confirmations progress - - - Shows progress toward the required number of confirmations for the proposal fee transaction. - - - - - - - Estimated time remaining: - - - - - - - - - - 8 - - - - - Qt::Horizontal - - - - 20 - 10 - - - - - - - - Back - - - - 0 - 28 - - - - - - - - Prepare Proposal - - - - 0 - 28 - - - - - - - - Next - - - false - - - - 0 - 28 - - - - - - - - - - - - - - You can submit after 1 confirmation. At 6 confirmations it is accepted and processed. - - - true - - - - - - - 0 - - - 0 - - - 0 - - - 6 - - - - - - - - - - - - 0 - - - 6 - - - 0 - - - false - - - 10 - - - Confirmations progress - - - Shows progress toward the required number of confirmations for the proposal fee transaction. - - - - - - - Estimated time remaining: - - - - - - - - - - 8 - - - - - Proposal ID: - - - - - - - true - - - - - - - 24 - - - - - - - Copy - - - - 0 - 28 - - - - - - - - - - 8 - - - - - Qt::Horizontal - - - - 20 - 10 - - - - - - - - Submit Proposal - - - false - - - - 0 - 28 - - - - - - - - Close - - - - 0 - 28 - - - - - - - - - - - - - - - QValidatedLineEdit - QLineEdit -
qt/qvalidatedlineedit.h
- 0 -
- - BitcoinAmountField - QLineEdit -
qt/bitcoinamountfield.h
-
-
- - editName - editUrl - editPayAddr - paymentAmount - comboFirstPayment - comboPayments - btnNext1 - plainJson - editHex - btnBack1 - btnNext2 - editTxid - btnCopyTxid - btnBack2 - btnPrepare - btnNext3 - editGovObjId - btnCopyGovId - btnSubmit - btnClose - - - -
- - diff --git a/src/qt/governanceinfo.cpp b/src/qt/governanceinfo.cpp new file mode 100644 index 000000000000..3c61fa9d478e --- /dev/null +++ b/src/qt/governanceinfo.cpp @@ -0,0 +1,234 @@ +// Copyright (c) 2026 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 +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +namespace { +constexpr int64_t MIN_IN_SECONDS{60}; +constexpr int64_t HOUR_IN_SECONDS{60 * MIN_IN_SECONDS}; +constexpr int64_t DAY_IN_SECONDS{24 * HOUR_IN_SECONDS}; + +uint16_t GetCycleCount(const interfaces::GOV::GovernanceInfo& gov_info, const Consensus::Params& consensus_params) +{ + const auto first_offset{(gov_info.superblockcycle - consensus_params.nSuperblockStartBlock % gov_info.superblockcycle) % gov_info.superblockcycle}; + const auto first_height{consensus_params.nSuperblockStartBlock + first_offset}; + return gov_info.lastsuperblock >= first_height ? ((gov_info.lastsuperblock - first_height) / gov_info.superblockcycle + 1) : 0; +} + +QString FormatAmountWithUnit(CAmount amount, BitcoinUnit unit) +{ + const qint64 factor = BitcoinUnits::factor(unit); + const double value = static_cast(amount) / factor; + return QObject::tr("%1 %2").arg(value, 0, 'f', 2).arg(BitcoinUnits::name(unit)); +} + +QString FormatBlocksWithTime(int blocks, int64_t m_block_spacing, bool past = false) +{ + if (blocks <= 0) { + return past ? QObject::tr("now") : QObject::tr("now"); + } + const int64_t seconds = blocks * m_block_spacing; + const double days = static_cast(seconds) / static_cast(DAY_IN_SECONDS); + + if (days < 1.0) { + const int hours = static_cast(seconds / HOUR_IN_SECONDS); + if (hours < 1) { + const int minutes = static_cast(seconds / MIN_IN_SECONDS); + return past ? QObject::tr("%1 min ago").arg(minutes) + : QObject::tr("%1 min left").arg(minutes); + } + return past ? QObject::tr("%1 hr ago").arg(hours) + : QObject::tr("%1 hr left").arg(hours); + } + return past ? QObject::tr("%1 days ago").arg(days, 0, 'f', 1) + : QObject::tr("%1 days left").arg(days, 0, 'f', 1); +} +} // anonymous namespace + +GovernanceInfo::GovernanceInfo(QWidget* parent) : + QWidget(parent), + ui(new Ui::GovernanceInfo), + m_block_spacing{Params().GetConsensus().nPowTargetSpacing}, + m_gov_min_quorum{Params().GetConsensus().nGovernanceMinQuorum} +{ + ui->setupUi(this); + GUIUtil::setFont({ui->labelGovGeneral, + ui->labelGovParticipation, + ui->labelGovProposals}, + {GUIUtil::g_font_registry.GetWeightBold(), 16}); + + for (auto* element : {ui->labelGovParticipation, ui->labelGovProposals}) { + element->setContentsMargins(0, 10, 0, 0); + } + + m_update_timer = new QTimer(this); + m_update_timer->setSingleShot(true); + m_update_timer->setInterval(1000); + connect(m_update_timer, &QTimer::timeout, this, &GovernanceInfo::updateGovernanceInfo); +} + +GovernanceInfo::~GovernanceInfo() +{ + delete ui; +} + +void GovernanceInfo::changeEvent(QEvent* e) +{ + if (e->type() == QEvent::StyleChange) { + updateGovernanceInfo(); + } + QWidget::changeEvent(e); +} + +void GovernanceInfo::setClientModel(ClientModel* model) +{ + clientModel = model; + + if (clientModel) { + connect(clientModel, &ClientModel::numBlocksChanged, this, [this]() { m_update_timer->start(); }); + updateGovernanceInfo(); + } +} + +void GovernanceInfo::updateGovernanceInfo() +{ + if (!clientModel || !clientModel->getOptionsModel()) { + return; + } + + const auto [dmn, _] = clientModel->getMasternodeList(); + if (!dmn) { + return; + } + + const auto& consensus_params = Params().GetConsensus(); + const auto gov_info{clientModel->node().gov().getGovernanceInfo()}; + ui->labelGovSuperblockCycle->setText(QString::number(GetCycleCount(gov_info, consensus_params))); + ui->labelGovPassingThreshold->setText(QObject::tr("%1 votes").arg(std::max(m_gov_min_quorum, gov_info.fundingthreshold))); + + const auto block_height{clientModel->getNumBlocks()}; + ui->labelGovLastSuperblock->setText(QObject::tr("%1 (%2)").arg(gov_info.lastsuperblock) + .arg(FormatBlocksWithTime(block_height - gov_info.lastsuperblock, m_block_spacing, /*past=*/true))); + ui->labelGovNextSuperblock->setText(QObject::tr("%1 (%2)").arg(gov_info.nextsuperblock) + .arg(FormatBlocksWithTime(gov_info.nextsuperblock - block_height, m_block_spacing, /*past=*/false))); + + const auto voting_cutoff_height{gov_info.nextsuperblock - gov_info.superblockmaturitywindow}; + ui->labelGovVotingDeadline->setText(QObject::tr("%1 (%2)").arg(voting_cutoff_height) + .arg(FormatBlocksWithTime(voting_cutoff_height - block_height, m_block_spacing, /*past=*/false))); + + size_t valid_mn{dmn->getValidMNsCount()}, valid_evo{dmn->getValidEvoCount()}; + uint16_t voters_mn{0}, voters_evo{0}; + valid_mn = (valid_mn > valid_evo) ? (valid_mn - valid_evo) : 0; + + std::vector objs; + clientModel->getAllGovernanceObjects(objs); + for (const auto& obj : objs) { + if (obj.GetObjectType() == GovernanceObject::PROPOSAL) { + const auto [obj_mn, obj_evo] = clientModel->node().gov().getObjUniqueVoters(obj, VOTE_SIGNAL_FUNDING); + voters_mn = std::max(voters_mn, obj_mn); + voters_evo = std::max(voters_evo, obj_evo); + } + } + + ui->labelGovMasternodesVoting->setText(QObject::tr("%1 (%2 eligible)").arg(voters_mn).arg(valid_mn)); + ui->labelGovEvoNodesVoting->setText(QObject::tr("%1 (%2 eligible)").arg(voters_evo).arg(valid_evo)); + + ProposalList proposals; + for (const auto& obj : objs) { + if (obj.GetObjectType() == GovernanceObject::PROPOSAL) { + proposals.push_back(std::make_unique(clientModel, obj)); + } + } + // Sort passing proposals by vote count to determine proposals that don't have enough funding + std::sort(proposals.begin(), proposals.end(), + [](const std::unique_ptr& a, const std::unique_ptr& b) { + return a->GetAbsoluteYesCount() > b->GetAbsoluteYesCount(); + }); + + const auto passing_threshold{std::max(m_gov_min_quorum, gov_info.fundingthreshold)}; + const BitcoinUnit display_unit{clientModel->getOptionsModel()->getDisplayUnit()}; + const CAmount budget_avail{gov_info.governancebudget}; + const QColor base_blue{GUIUtil::getThemedQColor(GUIUtil::ThemedColor::BLUE, /*force_dark=*/true).darker(160)}; + + CAmount budget_leftover{budget_avail}, budget_used{0}, unfunded_shortfall{0}; + std::vector chart_slices{}; + uint16_t proposals_fail{0}, proposals_pass{0}, proposals_total{0}, unfunded_count{0}; + + for (size_t idx{0}; idx < proposals.size(); idx++) { + const auto& prop{proposals[idx]}; + proposals_total++; + if (prop->GetAbsoluteYesCount() < passing_threshold) { + // Insufficient votes to be funded, out of contention + proposals_fail++; + continue; + } + proposals_pass++; + const CAmount proposed_amount{static_cast(prop->paymentAmount() * COIN)}; + budget_used += proposed_amount; + if (proposed_amount <= budget_leftover) { + budget_leftover -= proposed_amount; + } else { + unfunded_count++; + unfunded_shortfall += proposed_amount; + } + + const double slice_pct = (budget_avail <= 0) ? 0.0 : (static_cast(proposed_amount) / static_cast(budget_avail)) * 100.0; + chart_slices.emplace_back( + static_cast(proposed_amount), + /*color=*/(idx == 0) ? base_blue : base_blue.lighter(100 + static_cast(idx * 20)), + prop->title().isEmpty() ? QObject::tr("Proposal %1").arg(idx + 1) : prop->title(), + FormatAmountWithUnit(proposed_amount, display_unit), + QObject::tr("%1% of budget").arg(slice_pct, 0, 'f', 1) + ); + } + ui->labelGovProposalCount->setText(QString::number(proposals_total)); + ui->labelGovPassingProposals->setText(QString::number(proposals_pass)); + ui->labelGovFailingProposals->setText(QString::number(proposals_fail)); + + const CAmount budget_funded{budget_avail - budget_leftover}; + const double alloc_pct_chart = budget_avail <= 0 ? 0.0 + : (static_cast(budget_funded) / static_cast(budget_avail)) * 100.0; + DonutChart::CenterText default_text{ + FormatAmountWithUnit(budget_funded, display_unit), + QObject::tr("/ %1").arg(FormatAmountWithUnit(budget_avail, display_unit)), + QObject::tr("%1% allocated").arg(alloc_pct_chart, 0, 'f', 1) + }; + ui->budgetChart->setData(std::move(chart_slices), static_cast(budget_avail), std::move(default_text)); + + if (gov_info.governancebudget > 0) { + const double alloc_pct = (static_cast(budget_used) / static_cast(budget_avail)) * 100.0; + ui->labelGovBudgetAllocated->setText( + QObject::tr("%1 / %2 (%3%)").arg(FormatAmountWithUnit(budget_used, display_unit)) + .arg(FormatAmountWithUnit(budget_avail, display_unit)) + .arg(alloc_pct, 0, 'f', 2)); + } else { + ui->labelGovBudgetAllocated->setText(QObject::tr("N/A")); + } + + if (unfunded_count > 0) { + ui->labelGovUnfundedProposals->setText( + QObject::tr("%1 (short %2)").arg(unfunded_count) + .arg(FormatAmountWithUnit(unfunded_shortfall, display_unit))); + } else { + ui->labelGovUnfundedProposals->setText(QString::number(0)); + } +} diff --git a/src/qt/governanceinfo.h b/src/qt/governanceinfo.h new file mode 100644 index 000000000000..7ce7fde49163 --- /dev/null +++ b/src/qt/governanceinfo.h @@ -0,0 +1,45 @@ +// Copyright (c) 2026 The Dash 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_GOVERNANCEINFO_H +#define BITCOIN_QT_GOVERNANCEINFO_H + +#include + +#include + +class ClientModel; + +class QEvent; +class QTimer; +namespace Ui { +class GovernanceInfo; +} // namespace Ui + +class GovernanceInfo : public QWidget +{ + Q_OBJECT + +public: + explicit GovernanceInfo(QWidget* parent = nullptr); + ~GovernanceInfo() override; + + void setClientModel(ClientModel* model); + +public Q_SLOTS: + void updateGovernanceInfo(); + +protected: + void changeEvent(QEvent* e) override; + +private: + Ui::GovernanceInfo* ui; + ClientModel* clientModel{nullptr}; + + const int64_t m_block_spacing; + const int32_t m_gov_min_quorum; + QTimer* m_update_timer; +}; + +#endif // BITCOIN_QT_GOVERNANCEINFO_H diff --git a/src/qt/governancelist.cpp b/src/qt/governancelist.cpp index 32706b33b1a7..6ef05a7060fc 100644 --- a/src/qt/governancelist.cpp +++ b/src/qt/governancelist.cpp @@ -2,301 +2,50 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include #include -#include -#include -#include -#include -#include #include +#include #include #include +#include + +#include +#include +#include +#include +#include +#include + +#include #include #include -#include -#include -#include -#include -#include -#include #include