diff --git a/src/qt/bitcoin.qrc b/src/qt/bitcoin.qrc index fed373e551c..c3a68966bd2 100644 --- a/src/qt/bitcoin.qrc +++ b/src/qt/bitcoin.qrc @@ -3,6 +3,7 @@ res/icons/bitcoin.png res/icons/address-book.png res/icons/send.png + res/icons/coins.png res/icons/connect0.png res/icons/connect1.png res/icons/connect2.png diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index 9413356b412..a65d7709bde 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -277,6 +277,13 @@ void BitcoinGUI::createActions() historyAction->setShortcut(QKeySequence(QStringLiteral("Alt+4"))); tabGroup->addAction(historyAction); + showCoinsAction = new QAction(platformStyle->SingleColorIcon(":/icons/coins"), tr("&Coins"), this); + showCoinsAction->setStatusTip(tr("View wallet coins (UTXOs)")); + showCoinsAction->setToolTip(showCoinsAction->statusTip()); + showCoinsAction->setCheckable(true); + showCoinsAction->setShortcut(QKeySequence(QStringLiteral("Alt+5"))); + tabGroup->addAction(showCoinsAction); + #ifdef ENABLE_WALLET // These showNormalIfMinimized are needed because Send Coins and Receive Coins // can be triggered from the tray menu, and need to show the GUI to be useful. @@ -288,6 +295,7 @@ void BitcoinGUI::createActions() connect(receiveCoinsAction, &QAction::triggered, this, &BitcoinGUI::gotoReceiveCoinsPage); connect(historyAction, &QAction::triggered, [this]{ showNormalIfMinimized(); }); connect(historyAction, &QAction::triggered, this, &BitcoinGUI::gotoHistoryPage); + connect(showCoinsAction, &QAction::triggered, this, &BitcoinGUI::showCoins); #endif // ENABLE_WALLET quitAction = new QAction(tr("E&xit"), this); @@ -601,8 +609,12 @@ void BitcoinGUI::createToolBars() toolbar->addAction(sendCoinsAction); toolbar->addAction(receiveCoinsAction); toolbar->addAction(historyAction); + toolbar->addAction(showCoinsAction); overviewAction->setChecked(true); + showCoinsAction->setVisible(false); + showCoinsAction->setEnabled(false); + #ifdef ENABLE_WALLET QWidget *spacer = new QWidget(); spacer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); @@ -676,6 +688,14 @@ void BitcoinGUI::setClientModel(ClientModel *_clientModel, interfaces::BlockAndH } m_mask_values_action->setChecked(_clientModel->getOptionsModel()->getOption(OptionsModel::OptionID::MaskValues).toBool()); + + // watch for runtime changes and hide/show the coins tab accordingly + updateCoinsTabVisibility(); + if (optionsModel) { + connect(optionsModel, &OptionsModel::coinControlFeaturesChanged, this, [this](bool) { + updateCoinsTabVisibility(); + }); + } } else { // Shutdown requested, disable menus if (trayIconMenu) @@ -822,6 +842,7 @@ void BitcoinGUI::setWalletActionsEnabled(bool enabled) sendCoinsAction->setEnabled(enabled); receiveCoinsAction->setEnabled(enabled); historyAction->setEnabled(enabled); + showCoinsAction->setEnabled(enabled); encryptWalletAction->setEnabled(enabled); backupWalletAction->setEnabled(enabled); changePassphraseAction->setEnabled(enabled); @@ -832,6 +853,8 @@ void BitcoinGUI::setWalletActionsEnabled(bool enabled) openAction->setEnabled(enabled); m_close_wallet_action->setEnabled(enabled); m_close_all_wallets_action->setEnabled(enabled); + m_wallet_actions_enabled = enabled; + updateCoinsTabVisibility(); } void BitcoinGUI::createTrayIcon() @@ -1293,6 +1316,7 @@ void BitcoinGUI::changeEvent(QEvent *e) sendCoinsAction->setIcon(platformStyle->SingleColorIcon(QStringLiteral(":/icons/send"))); receiveCoinsAction->setIcon(platformStyle->SingleColorIcon(QStringLiteral(":/icons/receiving_addresses"))); historyAction->setIcon(platformStyle->SingleColorIcon(QStringLiteral(":/icons/history"))); + showCoinsAction->setIcon(platformStyle->SingleColorIcon(QStringLiteral(":/icons/coins"))); } QMainWindow::changeEvent(e); @@ -1468,6 +1492,24 @@ void BitcoinGUI::updateWalletStatus() } #endif // ENABLE_WALLET +#ifdef ENABLE_WALLET +void BitcoinGUI::showCoins() +{ + if (!(clientModel && clientModel->getOptionsModel() && clientModel->getOptionsModel()->getCoinControlFeatures())) { + if (overviewAction) overviewAction->setChecked(true); + // if user disables coin control features while this view is active, switch to overview + if (walletFrame) walletFrame->gotoOverviewPage(); + return; + } + + if (showCoinsAction) showCoinsAction->setChecked(true); + if (!walletFrame) return; + if (WalletView* wv = walletFrame->currentWalletView()) { + wv->gotoCoinsPage(); + } +} +#endif // ENABLE_WALLET + void BitcoinGUI::updateProxyIcon() { std::string ip_port; @@ -1680,3 +1722,27 @@ void UnitDisplayStatusBarControl::onMenuSelection(QAction* action) optionsModel->setDisplayUnit(action->data()); } } + +void BitcoinGUI::updateCoinsTabVisibility() +{ + if (!showCoinsAction) return; + + bool coin_control_enabled = false; + if (clientModel && clientModel->getOptionsModel()) { + coin_control_enabled = clientModel->getOptionsModel()->getCoinControlFeatures(); + } + + // Visible only when wallet is compiled/enabled and coin control is enabled + const bool visible = enableWallet && coin_control_enabled; + showCoinsAction->setVisible(visible); + + // Enabled only when visible and wallet actions are globally enabled + showCoinsAction->setEnabled(visible && m_wallet_actions_enabled); + +#ifdef ENABLE_WALLET + // If the Coins tab is active but we turn it off, switch to Overview page + if (!visible && showCoinsAction->isChecked()) { + gotoOverviewPage(); + } +#endif +} diff --git a/src/qt/bitcoingui.h b/src/qt/bitcoingui.h index 32fb7488fb0..2d3711fef46 100644 --- a/src/qt/bitcoingui.h +++ b/src/qt/bitcoingui.h @@ -136,6 +136,7 @@ class BitcoinGUI : public QMainWindow QAction* historyAction = nullptr; QAction* quitAction = nullptr; QAction* sendCoinsAction = nullptr; + QAction* showCoinsAction = nullptr; QAction* usedSendingAddressesAction = nullptr; QAction* usedReceivingAddressesAction = nullptr; QAction* signMessageAction = nullptr; @@ -213,6 +214,12 @@ class BitcoinGUI : public QMainWindow /** Open the OptionsDialog on the specified tab index */ void openOptionsDialogWithTab(OptionsDialog::Tab tab); + // Track whether wallet actions are globally enabled + bool m_wallet_actions_enabled{false}; + + /** Update the visibility and enabled state of the Coins tab/action based on options. */ + void updateCoinsTabVisibility(); + Q_SIGNALS: void quitRequested(); /** Signal raised when a URI was entered or dragged to the GUI */ @@ -283,6 +290,8 @@ public Q_SLOTS: void gotoReceiveCoinsPage(); /** Switch to send coins page */ void gotoSendCoinsPage(QString addr = ""); + /** Switch to show coins page*/ + void showCoins(); /** Show Sign/Verify Message dialog and switch to sign message tab */ void gotoSignMessageTab(QString addr = ""); diff --git a/src/qt/coincontroldialog.cpp b/src/qt/coincontroldialog.cpp index febf1ee82fa..50867b80fba 100644 --- a/src/qt/coincontroldialog.cpp +++ b/src/qt/coincontroldialog.cpp @@ -550,6 +550,39 @@ void CoinControlDialog::changeEvent(QEvent* e) QDialog::changeEvent(e); } +void CoinControlDialog::setViewOnly(bool view_only) +{ + m_view_only = view_only; + + ui->pushButtonSelectAll->setVisible(true); + ui->treeWidget->setColumnHidden(COLUMN_CHECKBOX, false); + ui->frame->setVisible(true); + + ui->radioListMode->setVisible(true); + ui->radioTreeMode->setVisible(true); + ui->radioListMode->setEnabled(true); + ui->radioTreeMode->setEnabled(true); + + ui->labelCoinControlQuantity->setVisible(true); + ui->labelCoinControlAmount->setVisible(true); + ui->labelCoinControlFee->setVisible(!view_only); + ui->labelCoinControlAfterFee->setVisible(!view_only); + ui->labelCoinControlBytes->setVisible(!view_only); + ui->labelCoinControlChange->setVisible(!view_only); + + ui->labelCoinControlQuantityText->setVisible(true); + ui->labelCoinControlAmountText->setVisible(true); + ui->labelCoinControlFeeText->setVisible(!view_only); + ui->labelCoinControlAfterFeeText->setVisible(!view_only); + ui->labelCoinControlBytesText->setVisible(!view_only); + ui->labelCoinControlChangeText->setVisible(!view_only); + + if (view_only) { + lockAction->setVisible(false); + unlockAction->setVisible(false); + } +} + void CoinControlDialog::updateView() { if (!model || !model->getOptionsModel() || !model->getAddressTableModel()) @@ -560,8 +593,9 @@ void CoinControlDialog::updateView() ui->treeWidget->clear(); ui->treeWidget->setEnabled(false); // performance, otherwise updateLabels would be called for every checked checkbox ui->treeWidget->setAlternatingRowColors(!treeMode); - QFlags flgCheckbox = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable; - QFlags flgTristate = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable | Qt::ItemIsAutoTristate; + + QFlags flgCheckbox = (Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable); + QFlags flgTristate = (Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsUserCheckable | Qt::ItemIsAutoTristate); BitcoinUnit nDisplayUnit = model->getOptionsModel()->getDisplayUnit(); @@ -578,6 +612,7 @@ void CoinControlDialog::updateView() itemWalletAddress = new CCoinControlWidgetItem(ui->treeWidget); itemWalletAddress->setFlags(flgTristate); + itemWalletAddress->setCheckState(COLUMN_CHECKBOX, Qt::Unchecked); // label @@ -599,7 +634,7 @@ void CoinControlDialog::updateView() if (treeMode) itemOutput = new CCoinControlWidgetItem(itemWalletAddress); else itemOutput = new CCoinControlWidgetItem(ui->treeWidget); itemOutput->setFlags(flgCheckbox); - itemOutput->setCheckState(COLUMN_CHECKBOX,Qt::Unchecked); + itemOutput->setCheckState(COLUMN_CHECKBOX, Qt::Unchecked); // address CTxDestination outputAddress; diff --git a/src/qt/coincontroldialog.h b/src/qt/coincontroldialog.h index 5fc2cdca9b7..8878c148fa6 100644 --- a/src/qt/coincontroldialog.h +++ b/src/qt/coincontroldialog.h @@ -53,6 +53,8 @@ class CoinControlDialog : public QDialog static QList payAmounts; static bool fSubtractFeeFromAmount; + void setViewOnly(bool view_only); + protected: void changeEvent(QEvent* e) override; @@ -71,6 +73,8 @@ class CoinControlDialog : public QDialog const PlatformStyle *platformStyle; + bool m_view_only{false}; + void sortView(int, Qt::SortOrder); void updateView(); diff --git a/src/qt/res/icons/coins.png b/src/qt/res/icons/coins.png new file mode 100644 index 00000000000..34a8f017d8f Binary files /dev/null and b/src/qt/res/icons/coins.png differ diff --git a/src/qt/res/src/coins.svg b/src/qt/res/src/coins.svg new file mode 100644 index 00000000000..8835674bdd7 --- /dev/null +++ b/src/qt/res/src/coins.svg @@ -0,0 +1,15 @@ + + + + coin_2_line + + + + + + + + + + + \ No newline at end of file diff --git a/src/qt/walletview.cpp b/src/qt/walletview.cpp index 39e9dcadeed..40c8e05a1da 100644 --- a/src/qt/walletview.cpp +++ b/src/qt/walletview.cpp @@ -17,6 +17,9 @@ #include #include #include +#include +#include +#include #include #include @@ -69,10 +72,26 @@ WalletView::WalletView(WalletModel* wallet_model, const PlatformStyle* _platform usedReceivingAddressesPage = new AddressBookPage(platformStyle, AddressBookPage::ForEditing, AddressBookPage::ReceivingTab, this); usedReceivingAddressesPage->setModel(walletModel->getAddressTableModel()); + // Create embedded Coins (UTXOs) page + coinsPage = new QWidget(this); + { + QVBoxLayout* coinsLayout = new QVBoxLayout(coinsPage); + coinsWidget = new CoinControlDialog(coinsPageCoinControl, walletModel, platformStyle, coinsPage); + coinsWidget->setViewOnly(true); + // Make it behave as a plain widget in-page + coinsWidget->setWindowFlags(Qt::Widget); + // Hide the dialog button box when embedded + if (auto bb = coinsWidget->findChild("buttonBox")) { + bb->setVisible(false); + } + coinsLayout->addWidget(coinsWidget); + } + addWidget(overviewPage); addWidget(transactionsPage); addWidget(receiveCoinsPage); addWidget(sendCoinsPage); + addWidget(coinsPage); connect(overviewPage, &OverviewPage::transactionClicked, this, &WalletView::transactionClicked); // Clicking on a transaction on the overview pre-selects the transaction on the transaction history page @@ -166,6 +185,17 @@ void WalletView::gotoSendCoinsPage(QString addr) sendCoinsPage->setAddress(addr); } +void WalletView::gotoCoinsPage() +{ + setCurrentWidget(coinsPage); +} + +void WalletView::showCoinsDialog() +{ + if (!walletModel) return; + gotoCoinsPage(); +} + void WalletView::gotoSignMessageTab(QString addr) { // calls show() in showTab_SM() diff --git a/src/qt/walletview.h b/src/qt/walletview.h index 475085044db..119459b24b5 100644 --- a/src/qt/walletview.h +++ b/src/qt/walletview.h @@ -10,6 +10,8 @@ #include +#include + class ClientModel; class OverviewPage; class PlatformStyle; @@ -19,6 +21,7 @@ class SendCoinsRecipient; class TransactionView; class WalletModel; class AddressBookPage; +class CoinControlDialog; QT_BEGIN_NAMESPACE class QModelIndex; @@ -65,6 +68,10 @@ class WalletView : public QStackedWidget AddressBookPage *usedSendingAddressesPage; AddressBookPage *usedReceivingAddressesPage; + QWidget* coinsPage{nullptr}; + CoinControlDialog* coinsWidget{nullptr}; + wallet::CCoinControl coinsPageCoinControl; + TransactionView *transactionView; QProgressDialog* progressDialog{nullptr}; @@ -79,6 +86,10 @@ public Q_SLOTS: void gotoReceiveCoinsPage(); /** Switch to send coins page */ void gotoSendCoinsPage(QString addr = ""); + /** Switch to embedded Coins (UTXOs) page */ + void gotoCoinsPage(); + /** Switch to show coins page*/ + void showCoinsDialog(); /** Show Sign/Verify Message dialog and switch to sign message tab */ void gotoSignMessageTab(QString addr = "");