From 098bb4b73253133de5afea86dfe605cb7b03c9a5 Mon Sep 17 00:00:00 2001 From: johnny9 <985648+johnny9@users.noreply.github.com> Date: Mon, 24 Mar 2025 20:45:09 -0400 Subject: [PATCH 01/11] qml: Update triangle-up/down icons --- src/qml/res/icons/triangle-down.png | Bin 1953 -> 1973 bytes src/qml/res/icons/triangle-up.png | Bin 1925 -> 1950 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/src/qml/res/icons/triangle-down.png b/src/qml/res/icons/triangle-down.png index 60a708fb25270eb4437c12651ea44ad96ef5ffdb..69f708082df162d0ee925c4bede454796c30e664 100644 GIT binary patch delta 1948 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 Bl7s*N diff --git a/src/qml/res/icons/triangle-up.png b/src/qml/res/icons/triangle-up.png index 9b3edc0255e250f78cb018c0c96d7ef32cc7b674..8e8e5dca7b02bf3a9c3c3eb29a9eb823a88deb47 100644 GIT binary patch delta 1925 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 Date: Mon, 24 Mar 2025 20:47:06 -0400 Subject: [PATCH 02/11] qml: Simplify imageprovider --- src/qml/imageprovider.cpp | 184 +------------------------------------- 1 file changed, 3 insertions(+), 181 deletions(-) diff --git a/src/qml/imageprovider.cpp b/src/qml/imageprovider.cpp index aeed8a5b97..19b0b62f04 100644 --- a/src/qml/imageprovider.cpp +++ b/src/qml/imageprovider.cpp @@ -6,6 +6,7 @@ #include +#include #include #include #include @@ -22,194 +23,15 @@ QPixmap ImageProvider::requestPixmap(const QString& id, QSize* size, const QSize return {}; } - if (id == "arrow-down") { - *size = requested_size; - return QIcon(":/icons/arrow-down").pixmap(requested_size); - } - - if (id == "arrow-up") { - *size = requested_size; - return QIcon(":/icons/arrow-up").pixmap(requested_size); - } - - if (id == "bitcoin-circle") { - *size = requested_size; - return QIcon(":/icons/bitcoin-circle").pixmap(requested_size); - } - - if (id == "blockclock-size-compact") { - *size = requested_size; - return QIcon(":/icons/blockclock-size-compact").pixmap(requested_size); - } - - if (id == "blockclock-size-showcase") { - *size = requested_size; - return QIcon(":/icons/blockclock-size-showcase").pixmap(requested_size); - } - - if (id == "blocktime-dark") { - *size = requested_size; - return QIcon(":/icons/blocktime-dark").pixmap(requested_size); - } - - if (id == "blocktime-light") { - *size = requested_size; - return QIcon(":/icons/blocktime-light").pixmap(requested_size); - } - if (id == "app") { *size = requested_size; return m_network_style->getAppIcon().pixmap(requested_size); } - if (id == "caret-left") { - *size = requested_size; - return QIcon(":/icons/caret-left").pixmap(requested_size); - } - - if (id == "caret-right") { - *size = requested_size; - return QIcon(":/icons/caret-right").pixmap(requested_size); - } - - if (id == "coinbase") { - *size = requested_size; - return QIcon(":/icons/coinbase").pixmap(requested_size); - } - - if (id == "check") { - *size = requested_size; - return QIcon(":/icons/check").pixmap(requested_size); - } - - if (id == "copy") { - *size = requested_size; - return QIcon(":/icons/copy").pixmap(requested_size); - } - - if (id == "cross") { - *size = requested_size; - return QIcon(":/icons/cross").pixmap(requested_size); - } - - if (id == "error") { - *size = requested_size; - return QIcon(":/icons/error").pixmap(requested_size); - } - - if (id == "export") { - *size = requested_size; - return QIcon(":/icons/export").pixmap(requested_size); - } - - if (id == "flip-vertical") { - *size = requested_size; - return QIcon(":/icons/flip-vertical").pixmap(requested_size); - } - - if (id == "gear") { - *size = requested_size; - return QIcon(":/icons/gear").pixmap(requested_size); - } - - if (id == "gear-outline") { - *size = requested_size; - return QIcon(":/icons/gear-outline").pixmap(requested_size); - } - - if (id == "info") { + if (QFile::exists(":/icons/" + id)) { *size = requested_size; - return QIcon(":/icons/info").pixmap(requested_size); + return QIcon(":/icons/" + id).pixmap(requested_size); } - if (id == "minus") { - *size = requested_size; - return QIcon(":/icons/minus").pixmap(requested_size); - } - - if (id == "network-dark") { - *size = requested_size; - return QIcon(":/icons/network-dark").pixmap(requested_size); - } - - if (id == "network-light") { - *size = requested_size; - return QIcon(":/icons/network-light").pixmap(requested_size); - } - - if (id == "pending") { - *size = requested_size; - return QIcon(":/icons/pending").pixmap(requested_size); - } - - if (id == "shutdown") { - *size = requested_size; - return QIcon(":/icons/shutdown").pixmap(requested_size); - } - - if (id == "singlesig-wallet") { - *size = requested_size; - return QIcon(":/icons/singlesig-wallet").pixmap(requested_size); - } - - if (id == "storage-dark") { - *size = requested_size; - return QIcon(":/icons/storage-dark").pixmap(requested_size); - } - - if (id == "storage-light") { - *size = requested_size; - return QIcon(":/icons/storage-light").pixmap(requested_size); - } - - if (id == "tooltip-arrow-dark") { - *size = requested_size; - return QIcon(":/icons/tooltip-arrow-dark").pixmap(requested_size); - } - - if (id == "tooltip-arrow-light") { - *size = requested_size; - return QIcon(":/icons/tooltip-arrow-light").pixmap(requested_size); - } - - if (id == "triangle-up") { - *size = requested_size; - return QIcon(":/icons/triangle-up").pixmap(requested_size); - } - - if (id == "triangle-down") { - *size = requested_size; - return QIcon(":/icons/triangle-down").pixmap(requested_size); - } - - if (id == "add-wallet-dark") { - *size = requested_size; - return QIcon(":/icons/add-wallet-dark").pixmap(requested_size); - } - - if (id == "wallet") { - *size = requested_size; - return QIcon(":/icons/wallet").pixmap(requested_size); - } - - if (id == "visible") { - *size = requested_size; - return QIcon(":/icons/visible").pixmap(requested_size); - } - - if (id == "hidden") { - *size = requested_size; - return QIcon(":/icons/hidden").pixmap(requested_size); - } - - if (id == "plus") { - *size = requested_size; - return QIcon(":/icons/plus").pixmap(requested_size); - } - - if (id == "flip-vertical") { - *size = requested_size; - return QIcon(":/icons/flip-vertical").pixmap(requested_size); - } return {}; } From 6519ad60763b3e68558d25ecb3e2c6790cd1f291 Mon Sep 17 00:00:00 2001 From: johnny9 <985648+johnny9@users.noreply.github.com> Date: Mon, 24 Mar 2025 20:48:23 -0400 Subject: [PATCH 03/11] qml: Introduce Lock icon --- src/Makefile.qt.include | 1 + src/qml/bitcoin_qml.qrc | 1 + src/qml/res/icons/lock.png | Bin 0 -> 1461 bytes src/qml/res/src/lock.svg | 4 ++++ 4 files changed, 6 insertions(+) create mode 100644 src/qml/res/icons/lock.png create mode 100644 src/qml/res/src/lock.svg diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include index f2f6e31131..cffdae53d0 100644 --- a/src/Makefile.qt.include +++ b/src/Makefile.qt.include @@ -368,6 +368,7 @@ QML_RES_ICONS = \ qml/res/icons/gear-outline.png \ qml/res/icons/hidden.png \ qml/res/icons/info.png \ + qml/res/icons/lock.png \ qml/res/icons/minus.png \ qml/res/icons/network-dark.png \ qml/res/icons/network-light.png \ diff --git a/src/qml/bitcoin_qml.qrc b/src/qml/bitcoin_qml.qrc index 69a07de568..7de3ba5658 100644 --- a/src/qml/bitcoin_qml.qrc +++ b/src/qml/bitcoin_qml.qrc @@ -111,6 +111,7 @@ res/icons/gear-outline.png res/icons/hidden.png res/icons/info.png + res/icons/lock.png res/icons/minus.png res/icons/network-dark.png res/icons/network-light.png diff --git a/src/qml/res/icons/lock.png b/src/qml/res/icons/lock.png new file mode 100644 index 0000000000000000000000000000000000000000..ebe454a94321da0c151a9454570d1fdf86201761 GIT binary patch literal 1461 zcmZWpdpOg382|pZ=Dx#uB%_teWx3xnv}zG;J06!rE|p^=BVjcY!hR&>nXqNcjv%t9D**8ZvZqu4 zR%H@A1pvzefSEu5_7(!5$tZ3*vQKu9qq&frz{Yo!w^ZiJ6h#Kvj|qU8?)Qdb?+{~T zqI@*P)meTTi2;_%N!thvnS;M6P7YqN1M@>Jq1(K%or_VgUJeZ0<>#szP##;}yg`yp>Dg zMrdm?Q!oR1^o18K*332?yHtFx^%4k-@NLpx$5=A6tNxoQ@@9VS=Yf&t5MZ>5yw~O zf3n}4bXsaU7hd{0K!pdSvmPs)2)2-?9c(7v#kdPSD?3AnHQRl?NCJ2p+GOoby>14D zRik9*Q9kNke~5o7AGfi*MAWQV$(`R_Ghj6y6ld3MpfsEW%xX`(^9OTA4vV<=)fN+u z#;-XdTUcr}HHi^x)ewq>_FKNODR!r&@YasD@H(xAksw~oZ1_HHcSIB-{KnAq;4#sJ z*cul;xEwF=x%rcbjQ;c|4-DDNwGlS$oNr9i<v^b-+g8Nyhh*)%OGFBKjO@3V9VjG*+A%d zg2lOBO)Z#twWRnuA|p<3vQy{<73wsukDxG1zGGjNRp1{nQ^|#-XP|2NYSiAo&+Edz zvHfnfR+ZOjee*=e+PpJW)Ifq5)irv(+t0N&lHHDvcu#rq79IQ{=NdzK-(x&>;(=AY z-T%I!b7(pfnVoj~ z0;6bDz10>|KctrQ7UT&PMfjYn)|sTVkR5AtibxN)T%XB7|3f;*lP}zx+SOAfAE+)q zCb!;a$0bT)TXmFTN@q~Fb%(;bxVPw`{#n@QtdLz@a+o0 z(;}(^*;cI4#hs#Y^&11+PerW_nz*z#kt5B%;JYG*LuwJRzmL`8OT0@9L;N=nC=H?q z6%!E6s&mLBm65ht>#wx*4W(y0kjOd4y6Vo@! n+F6n!t0A=xd + + + From 99d6fb7e08c2ce8f80b0923c98db24403e9c0915 Mon Sep 17 00:00:00 2001 From: johnny9 <985648+johnny9@users.noreply.github.com> Date: Tue, 25 Mar 2025 00:39:31 -0400 Subject: [PATCH 04/11] qml: Introduce Ellipsis icon --- src/Makefile.qt.include | 1 + src/qml/bitcoin_qml.qrc | 1 + src/qml/res/icons/ellipsis.png | Bin 0 -> 341 bytes src/qml/res/src/ellipsis.svg | 5 +++++ 4 files changed, 7 insertions(+) create mode 100644 src/qml/res/icons/ellipsis.png create mode 100644 src/qml/res/src/ellipsis.svg diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include index cffdae53d0..342d36bd12 100644 --- a/src/Makefile.qt.include +++ b/src/Makefile.qt.include @@ -361,6 +361,7 @@ QML_RES_ICONS = \ qml/res/icons/copy.png \ qml/res/icons/coinbase.png \ qml/res/icons/cross.png \ + qml/res/icons/ellipsis.png \ qml/res/icons/error.png \ qml/res/icons/export.png \ qml/res/icons/flip-vertical.png \ diff --git a/src/qml/bitcoin_qml.qrc b/src/qml/bitcoin_qml.qrc index 7de3ba5658..269cc7185d 100644 --- a/src/qml/bitcoin_qml.qrc +++ b/src/qml/bitcoin_qml.qrc @@ -104,6 +104,7 @@ res/icons/copy.png res/icons/coinbase.png res/icons/cross.png + res/icons/ellipsis.png res/icons/error.png res/icons/export.png res/icons/flip-vertical.png diff --git a/src/qml/res/icons/ellipsis.png b/src/qml/res/icons/ellipsis.png new file mode 100644 index 0000000000000000000000000000000000000000..36762f8ae630fe81a456e0ab383c49ca77902636 GIT binary patch literal 341 zcmeAS@N?(olHy`uVBq!ia0vp^6(G#P1|%(0%q{^b&H|6fVg?2sZxCi&YCCNuP>``W z$lZxy-8q?;Kn_c~qpu?a!^VE@KZ&eB{wq%x$B+ufw>LKO9x@PM4LHowb0BR4XU_q* zM$Uxho+62*4Qvk;l;-@feYt8*U7=o5ycbXx4$#}(;k(Xxo!-24JIh{jZ+&j6%AYxV zb^mv{``f%vW}j<+xRGFQaUT;MOzvEA(lWh?s0{$HTKf pf9bp|l6`!d)#+^|4wo_fIVQWHn@mvv4FO#nG4d5Zu5 literal 0 HcmV?d00001 diff --git a/src/qml/res/src/ellipsis.svg b/src/qml/res/src/ellipsis.svg new file mode 100644 index 0000000000..0f9f2607a9 --- /dev/null +++ b/src/qml/res/src/ellipsis.svg @@ -0,0 +1,5 @@ + + + + + From 44276f852b27158fda90caed10c756254ce26b75 Mon Sep 17 00:00:00 2001 From: johnny9 <985648+johnny9@users.noreply.github.com> Date: Mon, 24 Mar 2025 21:09:32 -0400 Subject: [PATCH 05/11] qml: Add CoinsListModel to WalletQmlModel The coins list model will be used to list out of the locked and selectable coins in the coin selection form. A toggle method is provided that will select or unselect the coin in the associated WalletQmlModel. --- src/Makefile.qt.include | 3 + src/qml/models/coinslistmodel.cpp | 131 ++++++++++++++++++++++++ src/qml/models/coinslistmodel.h | 67 ++++++++++++ src/qml/models/walletqmlmodel.cpp | 91 +++++++++++++--- src/qml/models/walletqmlmodel.h | 21 +++- test/lint/lint-circular-dependencies.py | 1 + 6 files changed, 298 insertions(+), 16 deletions(-) create mode 100644 src/qml/models/coinslistmodel.cpp create mode 100644 src/qml/models/coinslistmodel.h diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include index 342d36bd12..c4535736a1 100644 --- a/src/Makefile.qt.include +++ b/src/Makefile.qt.include @@ -39,6 +39,7 @@ QT_MOC_CPP = \ qml/controls/moc_linegraph.cpp \ qml/models/moc_activitylistmodel.cpp \ qml/models/moc_chainmodel.cpp \ + qml/models/moc_coinslistmodel.cpp \ qml/models/moc_networktraffictower.cpp \ qml/models/moc_nodemodel.cpp \ qml/models/moc_options_model.cpp \ @@ -129,6 +130,7 @@ BITCOIN_QT_H = \ qml/controls/linegraph.h \ qml/models/activitylistmodel.h \ qml/models/chainmodel.h \ + qml/models/coinslistmodel.h \ qml/models/networktraffictower.h \ qml/models/nodemodel.h \ qml/models/options_model.h \ @@ -328,6 +330,7 @@ BITCOIN_QML_BASE_CPP = \ qml/controls/linegraph.cpp \ qml/models/activitylistmodel.cpp \ qml/models/chainmodel.cpp \ + qml/models/coinslistmodel.cpp \ qml/models/networktraffictower.cpp \ qml/models/nodemodel.cpp \ qml/models/options_model.cpp \ diff --git a/src/qml/models/coinslistmodel.cpp b/src/qml/models/coinslistmodel.cpp new file mode 100644 index 0000000000..4ce4bc596b --- /dev/null +++ b/src/qml/models/coinslistmodel.cpp @@ -0,0 +1,131 @@ +// Copyright (c) 2025 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include + +#include +#include +#include +#include +#include +#include +#include + +CoinsListModel::CoinsListModel(WalletQmlModel* parent) + : QAbstractListModel(parent), m_wallet_model(parent) +{ +} + +CoinsListModel::~CoinsListModel() = default; + +int CoinsListModel::rowCount(const QModelIndex& parent) const +{ + return m_coins.size(); +} + +QVariant CoinsListModel::data(const QModelIndex& index, int role) const +{ + if (!index.isValid() || index.row() < 0 || index.row() >= static_cast(m_coins.size())) + return QVariant(); + + const auto& [destination, outpoint, coin] = m_coins.at(index.row()); + switch (role) { + case AddressRole: + return QString::fromStdString(EncodeDestination(destination)); + case AmountRole: + return BitcoinUnits::format(BitcoinUnits::Unit::BTC, coin.txout.nValue); + case LabelRole: + return QString::fromStdString(""); + case LockedRole: + return m_wallet_model->isLockedCoin(outpoint); + case SelectedRole: + return m_wallet_model->isSelectedCoin(outpoint); + default: + return QVariant(); + } +} + +QHash CoinsListModel::roleNames() const +{ + QHash roles; + roles[AmountRole] = "amount"; + roles[AddressRole] = "address"; + roles[DateTimeRole] = "date"; + roles[LabelRole] = "label"; + roles[LockedRole] = "locked"; + roles[SelectedRole] = "selected"; + return roles; +} + +void CoinsListModel::update() +{ + if (m_wallet_model == nullptr) { + return; + } + auto coins_map = m_wallet_model->listCoins(); + beginResetModel(); + m_coins.clear(); + for (const auto& [destination, vec] : coins_map) { + for (const auto& [outpoint, tx_out] : vec) { + auto tuple = std::make_tuple(destination, outpoint, tx_out); + m_coins.push_back(tuple); + } + } + endResetModel(); +} + +void CoinsListModel::setSortBy(const QString& roleName) +{ + if (m_sort_by != roleName) { + m_sort_by = roleName; + // sort(RoleNameToIndex(roleName)); + Q_EMIT sortByChanged(roleName); + } +} + +void CoinsListModel::toggleCoinSelection(const int index) +{ + if (index < 0 || index >= static_cast(m_coins.size())) { + return; + } + const auto& [destination, outpoint, coin] = m_coins.at(index); + + if (m_wallet_model->isSelectedCoin(outpoint)) { + m_wallet_model->unselectCoin(outpoint); + m_total_amount -= coin.txout.nValue; + } else { + m_wallet_model->selectCoin(outpoint); + m_total_amount += coin.txout.nValue; + } + Q_EMIT selectedCoinsCountChanged(); +} + +unsigned int CoinsListModel::lockedCoinsCount() const +{ + std::vector lockedCoins; + m_wallet_model->listLockedCoins(lockedCoins); + return lockedCoins.size(); +} + +unsigned int CoinsListModel::selectedCoinsCount() const +{ + return m_wallet_model->listSelectedCoins().size(); +} + +QString CoinsListModel::totalSelected() const +{ + return BitcoinUnits::format(BitcoinUnits::Unit::BTC, m_total_amount); +} + +QString CoinsListModel::changeAmount() const +{ + CAmount change = m_total_amount - m_wallet_model->sendRecipient()->cAmount(); + change = std::abs(change); + return BitcoinUnits::format(BitcoinUnits::Unit::BTC, change); +} + +bool CoinsListModel::overRequiredAmount() const +{ + return m_total_amount > m_wallet_model->sendRecipient()->cAmount(); +} diff --git a/src/qml/models/coinslistmodel.h b/src/qml/models/coinslistmodel.h new file mode 100644 index 0000000000..807571cfbd --- /dev/null +++ b/src/qml/models/coinslistmodel.h @@ -0,0 +1,67 @@ +// Copyright (c) 2025 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_QML_MODELS_COINSLISTMODEL_H +#define BITCOIN_QML_MODELS_COINSLISTMODEL_H + +#include +#include +#include +#include + +#include +#include + +class WalletQmlModel; + +class CoinsListModel : public QAbstractListModel +{ + Q_OBJECT + Q_PROPERTY(int lockedCoinsCount READ lockedCoinsCount NOTIFY lockedCoinsCountChanged) + Q_PROPERTY(int selectedCoinsCount READ selectedCoinsCount NOTIFY selectedCoinsCountChanged) + Q_PROPERTY(QString totalSelected READ totalSelected NOTIFY selectedCoinsCountChanged) + Q_PROPERTY(QString changeAmount READ changeAmount NOTIFY selectedCoinsCountChanged) + Q_PROPERTY(bool overRequiredAmount READ overRequiredAmount NOTIFY selectedCoinsCountChanged) + +public: + explicit CoinsListModel(WalletQmlModel* parent = nullptr); + ~CoinsListModel(); + + enum CoinsRoles { + AddressRole = Qt::UserRole + 1, + AmountRole, + DateTimeRole, + LabelRole, + LockedRole, + SelectedRole + }; + + int rowCount(const QModelIndex& parent = QModelIndex()) const override; + QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; + QHash roleNames() const override; + +public Q_SLOTS: + void update(); + void setSortBy(const QString& roleName); + void toggleCoinSelection(const int index); + unsigned int lockedCoinsCount() const; + unsigned int selectedCoinsCount() const; + QString totalSelected() const; + QString changeAmount() const; + bool overRequiredAmount() const; + +Q_SIGNALS: + void sortByChanged(const QString& roleName); + void lockedCoinsCountChanged(); + void selectedCoinsCountChanged(); + +private: + WalletQmlModel* m_wallet_model; + std::unique_ptr m_handler_transaction_changed; + std::vector> m_coins; + QString m_sort_by; + CAmount m_total_amount; +}; + +#endif // BITCOIN_QML_MODELS_COINSLISTMODEL_H \ No newline at end of file diff --git a/src/qml/models/walletqmlmodel.cpp b/src/qml/models/walletqmlmodel.cpp index 626f029bb7..55df50c8da 100644 --- a/src/qml/models/walletqmlmodel.cpp +++ b/src/qml/models/walletqmlmodel.cpp @@ -4,14 +4,13 @@ #include -#include - -#include -#include - #include +#include #include #include +#include +#include +#include #include #include #include @@ -23,6 +22,7 @@ WalletQmlModel::WalletQmlModel(std::unique_ptr wallet, QObje { m_wallet = std::move(wallet); m_activity_list_model = new ActivityListModel(this); + m_coins_list_model = new CoinsListModel(this); m_current_recipient = new SendRecipient(this); } @@ -30,9 +30,20 @@ WalletQmlModel::WalletQmlModel(QObject* parent) : QObject(parent) { m_activity_list_model = new ActivityListModel(this); + m_coins_list_model = new CoinsListModel(this); m_current_recipient = new SendRecipient(this); } +WalletQmlModel::~WalletQmlModel() +{ + delete m_activity_list_model; + delete m_coins_list_model; + delete m_current_recipient; + if (m_current_transaction) { + delete m_current_transaction; + } +} + QString WalletQmlModel::balance() const { if (!m_wallet) { @@ -76,11 +87,6 @@ bool WalletQmlModel::tryGetTxStatus(const uint256& txid, return m_wallet->tryGetTxStatus(txid, tx_status, num_blocks, block_time); } -WalletQmlModel::~WalletQmlModel() -{ - delete m_activity_list_model; -} - std::unique_ptr WalletQmlModel::handleTransactionChanged(TransactionChangedFn fn) { if (!m_wallet) { @@ -97,8 +103,7 @@ bool WalletQmlModel::prepareTransaction() CScript scriptPubKey = GetScriptForDestination(DecodeDestination(m_current_recipient->address().toStdString())); wallet::CRecipient recipient = {scriptPubKey, m_current_recipient->cAmount(), m_current_recipient->subtractFeeFromAmount()}; - wallet::CCoinControl coinControl; - coinControl.m_feerate = CFeeRate(1000); + m_coin_control.m_feerate = CFeeRate(1000); CAmount balance = m_wallet->getBalance(); if (balance < recipient.nAmount) { @@ -108,7 +113,7 @@ bool WalletQmlModel::prepareTransaction() std::vector vecSend{recipient}; int nChangePosRet = -1; CAmount nFeeRequired = 0; - const auto& res = m_wallet->createTransaction(vecSend, coinControl, true, nChangePosRet, nFeeRequired); + const auto& res = m_wallet->createTransaction(vecSend, m_coin_control, true, nChangePosRet, nFeeRequired); if (res) { if (m_current_transaction) { delete m_current_transaction; @@ -139,3 +144,63 @@ void WalletQmlModel::sendTransaction() interfaces::WalletOrderForm order_form; m_wallet->commitTransaction(newTx, value_map, order_form); } + +interfaces::Wallet::CoinsList WalletQmlModel::listCoins() const +{ + if (!m_wallet) { + return {}; + } + return m_wallet->listCoins(); +} + +bool WalletQmlModel::lockCoin(const COutPoint& output) +{ + if (!m_wallet) { + return false; + } + return m_wallet->lockCoin(output, true); +} + +bool WalletQmlModel::unlockCoin(const COutPoint& output) +{ + if (!m_wallet) { + return false; + } + return m_wallet->unlockCoin(output); +} + +bool WalletQmlModel::isLockedCoin(const COutPoint& output) +{ + if (!m_wallet) { + return false; + } + return m_wallet->isLockedCoin(output); +} + +void WalletQmlModel::listLockedCoins(std::vector& outputs) +{ + if (!m_wallet) { + return; + } + m_wallet->listLockedCoins(outputs); +} + +void WalletQmlModel::selectCoin(const COutPoint& output) +{ + m_coin_control.Select(output); +} + +void WalletQmlModel::unselectCoin(const COutPoint& output) +{ + m_coin_control.UnSelect(output); +} + +bool WalletQmlModel::isSelectedCoin(const COutPoint& output) +{ + return m_coin_control.IsSelected(output); +} + +std::vector WalletQmlModel::listSelectedCoins() const +{ + return m_coin_control.ListSelected(); +} diff --git a/src/qml/models/walletqmlmodel.h b/src/qml/models/walletqmlmodel.h index 252a233482..d97cd0851f 100644 --- a/src/qml/models/walletqmlmodel.h +++ b/src/qml/models/walletqmlmodel.h @@ -5,15 +5,16 @@ #ifndef BITCOIN_QML_MODELS_WALLETQMLMODEL_H #define BITCOIN_QML_MODELS_WALLETQMLMODEL_H -#include #include - +#include #include - +#include #include #include +#include #include +#include #include class ActivityListModel; @@ -24,6 +25,7 @@ class WalletQmlModel : public QObject Q_PROPERTY(QString name READ name NOTIFY nameChanged) Q_PROPERTY(QString balance READ balance NOTIFY balanceChanged) Q_PROPERTY(ActivityListModel* activityListModel READ activityListModel CONSTANT) + Q_PROPERTY(CoinsListModel* coinsListModel READ coinsListModel CONSTANT) Q_PROPERTY(SendRecipient* sendRecipient READ sendRecipient CONSTANT) Q_PROPERTY(WalletQmlModelTransaction* currentTransaction READ currentTransaction NOTIFY currentTransactionChanged) @@ -35,6 +37,7 @@ class WalletQmlModel : public QObject QString name() const; QString balance() const; ActivityListModel* activityListModel() const { return m_activity_list_model; } + CoinsListModel* coinsListModel() const { return m_coins_list_model; } std::set getWalletTxs() const; interfaces::WalletTx getWalletTx(const uint256& hash) const; @@ -51,6 +54,16 @@ class WalletQmlModel : public QObject using TransactionChangedFn = std::function; virtual std::unique_ptr handleTransactionChanged(TransactionChangedFn fn); + interfaces::Wallet::CoinsList listCoins() const; + bool lockCoin(const COutPoint& output); + bool unlockCoin(const COutPoint& output); + bool isLockedCoin(const COutPoint& output); + void listLockedCoins(std::vector& outputs); + void selectCoin(const COutPoint& output); + void unselectCoin(const COutPoint& output); + bool isSelectedCoin(const COutPoint& output); + std::vector listSelectedCoins() const; + Q_SIGNALS: void nameChanged(); void balanceChanged(); @@ -59,8 +72,10 @@ class WalletQmlModel : public QObject private: std::unique_ptr m_wallet; ActivityListModel* m_activity_list_model{nullptr}; + CoinsListModel* m_coins_list_model{nullptr}; SendRecipient* m_current_recipient{nullptr}; WalletQmlModelTransaction* m_current_transaction{nullptr}; + wallet::CCoinControl m_coin_control; }; #endif // BITCOIN_QML_MODELS_WALLETQMLMODEL_H diff --git a/test/lint/lint-circular-dependencies.py b/test/lint/lint-circular-dependencies.py index 6dd55a8ee5..fa98b6fd69 100755 --- a/test/lint/lint-circular-dependencies.py +++ b/test/lint/lint-circular-dependencies.py @@ -16,6 +16,7 @@ "node/blockstorage -> validation -> node/blockstorage", "node/utxo_snapshot -> validation -> node/utxo_snapshot", "qml/models/activitylistmodel -> qml/models/walletqmlmodel -> qml/models/activitylistmodel", + "qml/models/coinslistmodel -> qml/models/walletqmlmodel -> qml/models/coinslistmodel", "qt/addresstablemodel -> qt/walletmodel -> qt/addresstablemodel", "qt/recentrequeststablemodel -> qt/walletmodel -> qt/recentrequeststablemodel", "qt/sendcoinsdialog -> qt/walletmodel -> qt/sendcoinsdialog", From 18681851fa407fb39bc0f52f42e6b603303434b3 Mon Sep 17 00:00:00 2001 From: johnny9 <985648+johnny9@users.noreply.github.com> Date: Tue, 25 Mar 2025 00:32:07 -0400 Subject: [PATCH 06/11] qml: Introduce the CoinSelection page The CoinSelection page is opened up from the Send page. The button to push CoinSelection on the Send page stack is enabled in the ellipsis menu. The toggle in the ellipsis menu can set a QSetting for enabled/disabled. All of these components are added to the DesktopWallets and Send page. --- src/Makefile.qt.include | 9 +- src/qml/bitcoin_qml.qrc | 11 +- src/qml/components/OptionPopup.qml | 24 ++ src/qml/controls/CoreCheckBox.qml | 27 ++ src/qml/controls/EllipsisMenuButton.qml | 48 +++ src/qml/controls/EllipsisMenuToggleItem.qml | 74 ++++ src/qml/controls/LabeledCoinControlButton.qml | 50 +++ src/qml/controls/SendOptionsPopup.qml | 26 ++ src/qml/pages/wallet/CoinSelection.qml | 164 +++++++++ src/qml/pages/wallet/DesktopWallets.qml | 1 + src/qml/pages/wallet/Send.qml | 320 +++++++++++------- 11 files changed, 622 insertions(+), 132 deletions(-) create mode 100644 src/qml/components/OptionPopup.qml create mode 100644 src/qml/controls/CoreCheckBox.qml create mode 100644 src/qml/controls/EllipsisMenuButton.qml create mode 100644 src/qml/controls/EllipsisMenuToggleItem.qml create mode 100644 src/qml/controls/LabeledCoinControlButton.qml create mode 100644 src/qml/controls/SendOptionsPopup.qml create mode 100644 src/qml/pages/wallet/CoinSelection.qml diff --git a/src/Makefile.qt.include b/src/Makefile.qt.include index c4535736a1..c52728fce2 100644 --- a/src/Makefile.qt.include +++ b/src/Makefile.qt.include @@ -400,9 +400,10 @@ QML_RES_QML = \ qml/components/ConnectionSettings.qml \ qml/components/DeveloperOptions.qml \ qml/components/ExternalPopup.qml \ - qml/components/PeersIndicator.qml \ qml/components/NetworkTrafficGraph.qml \ qml/components/NetworkIndicator.qml \ + qml/components/OptionPopup.qml \ + qml/components/PeersIndicator.qml \ qml/components/ProxySettings.qml \ qml/components/Separator.qml \ qml/components/StorageLocations.qml \ @@ -414,8 +415,11 @@ QML_RES_QML = \ qml/controls/AddWalletButton.qml \ qml/controls/CaretRightIcon.qml \ qml/controls/ContinueButton.qml \ + qml/controls/CoreCheckBox.qml \ qml/controls/CoreText.qml \ qml/controls/CoreTextField.qml \ + qml/controls/EllipsisMenuButton.qml \ + qml/controls/EllipsisMenuToggleItem.qml \ qml/controls/ExternalLink.qml \ qml/controls/FocusBorder.qml \ qml/controls/Header.qml \ @@ -424,6 +428,7 @@ QML_RES_QML = \ qml/controls/IPAddressValueInput.qml \ qml/controls/KeyValueRow.qml \ qml/controls/LabeledTextInput.qml \ + qml/controls/LabeledCoinControlButton.qml \ qml/controls/NavButton.qml \ qml/controls/NavigationBar.qml \ qml/controls/NavigationBar2.qml \ @@ -436,6 +441,7 @@ QML_RES_QML = \ qml/controls/ProgressIndicator.qml \ qml/controls/qmldir \ qml/controls/Setting.qml \ + qml/controls/SendOptionsPopup.qml \ qml/controls/TextButton.qml \ qml/controls/Theme.qml \ qml/controls/ToggleButton.qml \ @@ -466,6 +472,7 @@ QML_RES_QML = \ qml/pages/settings/SettingsTheme.qml \ qml/pages/wallet/Activity.qml \ qml/pages/wallet/ActivityDetails.qml \ + qml/pages/wallet/CoinSelection.qml \ qml/pages/wallet/CreateBackup.qml \ qml/pages/wallet/CreateConfirm.qml \ qml/pages/wallet/CreateIntro.qml \ diff --git a/src/qml/bitcoin_qml.qrc b/src/qml/bitcoin_qml.qrc index 269cc7185d..8c4b231ef9 100644 --- a/src/qml/bitcoin_qml.qrc +++ b/src/qml/bitcoin_qml.qrc @@ -4,14 +4,14 @@ components/BlockClock.qml components/BlockClockDisplayMode.qml components/BlockCounter.qml - controls/CaretRightIcon.qml components/ConnectionOptions.qml components/ConnectionSettings.qml - components/PeersIndicator.qml components/DeveloperOptions.qml components/ExternalPopup.qml components/NetworkTrafficGraph.qml components/NetworkIndicator.qml + components/OptionPopup.qml + components/PeersIndicator.qml components/ProxySettings.qml components/StorageLocations.qml components/Separator.qml @@ -21,7 +21,9 @@ components/TotalBytesIndicator.qml components/Tooltip.qml controls/AddWalletButton.qml + controls/CaretRightIcon.qml controls/ContinueButton.qml + controls/CoreCheckBox.qml controls/CoreText.qml controls/CoreTextField.qml controls/ExternalLink.qml @@ -32,6 +34,9 @@ controls/IPAddressValueInput.qml controls/KeyValueRow.qml controls/LabeledTextInput.qml + controls/LabeledCoinControlButton.qml + controls/EllipsisMenuButton.qml + controls/EllipsisMenuToggleItem.qml controls/NavButton.qml controls/NavigationBar.qml controls/NavigationBar2.qml @@ -43,6 +48,7 @@ controls/PageStack.qml controls/ProgressIndicator.qml controls/qmldir + controls/SendOptionsPopup.qml controls/Setting.qml controls/TextButton.qml controls/Theme.qml @@ -74,6 +80,7 @@ pages/settings/SettingsTheme.qml pages/wallet/Activity.qml pages/wallet/ActivityDetails.qml + pages/wallet/CoinSelection.qml pages/wallet/CreateBackup.qml pages/wallet/CreateConfirm.qml pages/wallet/CreateIntro.qml diff --git a/src/qml/components/OptionPopup.qml b/src/qml/components/OptionPopup.qml new file mode 100644 index 0000000000..3742cf2c54 --- /dev/null +++ b/src/qml/components/OptionPopup.qml @@ -0,0 +1,24 @@ +// Copyright (c) 2025 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 + +import "../controls" + +Popup { + id: root + + background: Item { + anchors.fill: parent + Rectangle { + color: Theme.color.neutral0 + border.color: Theme.color.neutral4 + radius: 5 + border.width: 1 + anchors.fill: parent + } + } +} diff --git a/src/qml/controls/CoreCheckBox.qml b/src/qml/controls/CoreCheckBox.qml new file mode 100644 index 0000000000..c7fe6af925 --- /dev/null +++ b/src/qml/controls/CoreCheckBox.qml @@ -0,0 +1,27 @@ +// Copyright (c) 2025 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +import QtQuick 2.15 +import QtQuick.Controls 2.15 + +AbstractButton { + id: root + implicitWidth: 20 + implicitHeight: 20 + + property color borderColor: Theme.color.neutral9 + property color fillColor: Theme.color.neutral9 + + background: null + + checkable: true + hoverEnabled: AppMode.isDesktop + + contentItem: Rectangle { + radius: 3 + border.color: root.checked ? root.fillColor : root.borderColor + border.width: 1 + color: root.checked ? root.fillColor : "transparent" + } +} diff --git a/src/qml/controls/EllipsisMenuButton.qml b/src/qml/controls/EllipsisMenuButton.qml new file mode 100644 index 0000000000..884f990dbb --- /dev/null +++ b/src/qml/controls/EllipsisMenuButton.qml @@ -0,0 +1,48 @@ +// Copyright (c) 2025 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 + +import org.bitcoincore.qt 1.0 + +Button { + id: root + + property color hoverColor: Theme.color.orange + property color activeColor: Theme.color.orange + + hoverEnabled: AppMode.isDesktop + implicitHeight: 35 + implicitWidth: 35 + + MouseArea { + anchors.fill: parent + enabled: false + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + } + + background: null + + contentItem: Icon { + id: ellipsisIcon + anchors.fill: parent + source: "image://images/ellipsis" + color: Theme.color.neutral9 + size: 35 + } + + states: [ + State { + name: "CHECKED"; when: root.checked + PropertyChanges { target: ellipsisIcon; color: activeColor } + }, + State { + name: "HOVER"; when: root.hovered + PropertyChanges { target: ellipsisIcon; color: hoverColor } + } + ] +} diff --git a/src/qml/controls/EllipsisMenuToggleItem.qml b/src/qml/controls/EllipsisMenuToggleItem.qml new file mode 100644 index 0000000000..71e7ec38d6 --- /dev/null +++ b/src/qml/controls/EllipsisMenuToggleItem.qml @@ -0,0 +1,74 @@ +// Copyright (c) 2025 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 +import org.bitcoincore.qt 1.0 + +Button { + property int bgRadius: 5 + property color bgDefaultColor: "transparent" + property color bgHoverColor: Theme.color.neutral2 + property color textColor: Theme.color.neutral7 + property color textHoverColor: Theme.color.neutral9 + property color textActiveColor: Theme.color.neutral7 + + id: root + checkable: true + checked: optionSwitch.checked + hoverEnabled: AppMode.isDesktop + + implicitWidth: 280 + + MouseArea { + anchors.fill: parent + enabled: false + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + } + + onClicked: { + optionSwitch.checked = !optionSwitch.checked + } + + contentItem: RowLayout { + spacing: 7 + anchors.fill: parent + anchors.centerIn: parent + anchors.margins: 10 + CoreText { + Layout.fillWidth: true + Layout.alignment: Qt.AlignVCenter + horizontalAlignment: Text.AlignLeft + font.pixelSize: 15 + text: root.text + } + OptionSwitch { + id: optionSwitch + Layout.alignment: Qt.AlignVCenter + Layout.preferredWidth: 40 + Layout.preferredHeight: 24 + checked: root.checked + } + } + + background: Rectangle { + id: bg + color: root.bgDefaultColor + radius: root.bgRadius + + Behavior on color { + ColorAnimation { duration: 150 } + } + } + + states: [ + State { + name: "HOVER"; when: root.hovered + PropertyChanges { target: bg; color: root.bgHoverColor } + PropertyChanges { target: buttonText; color: root.textHoverColor } + } + ] +} diff --git a/src/qml/controls/LabeledCoinControlButton.qml b/src/qml/controls/LabeledCoinControlButton.qml new file mode 100644 index 0000000000..deb4e61fe4 --- /dev/null +++ b/src/qml/controls/LabeledCoinControlButton.qml @@ -0,0 +1,50 @@ +// Copyright (c) 2025 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 + +Item { + property int coinsSelected: 0 + + signal openCoinControl + + id: root + implicitHeight: label.height + + CoreText { + id: label + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + horizontalAlignment: Text.AlignLeft + width: 110 + color: Theme.color.neutral9 + font.pixelSize: 18 + text: qsTr("Inputs") + } + + CoreText { + anchors.left: label.right + anchors.verticalCenter: parent.verticalCenter + horizontalAlignment: Text.AlignLeft + color: Theme.color.orangeLight1 + font.pixelSize: 18 + text: { + if (coinsSelected === 0) { + qsTr("Select") + } else { + qsTr("%1 input%2 selected") + .arg(coinsSelected) + .arg(coinsSelected === 1 ? "" : "s") + } + } + + MouseArea { + anchors.fill: parent + onClicked: root.openCoinControl() + cursorShape: Qt.PointingHandCursor + } + } +} diff --git a/src/qml/controls/SendOptionsPopup.qml b/src/qml/controls/SendOptionsPopup.qml new file mode 100644 index 0000000000..f67ff139ec --- /dev/null +++ b/src/qml/controls/SendOptionsPopup.qml @@ -0,0 +1,26 @@ +// Copyright (c) 2025 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 + +import "../components" +import "../controls" + +OptionPopup { + id: root + + property alias coinControlEnabled: coinControlToggle.checked + + clip: true + modal: true + dim: false + + EllipsisMenuToggleItem { + id: coinControlToggle + anchors.centerIn: parent + text: qsTr("Enable Coin control") + } +} \ No newline at end of file diff --git a/src/qml/pages/wallet/CoinSelection.qml b/src/qml/pages/wallet/CoinSelection.qml new file mode 100644 index 0000000000..3c401a87bf --- /dev/null +++ b/src/qml/pages/wallet/CoinSelection.qml @@ -0,0 +1,164 @@ +// Copyright (c) 2025 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +import QtQuick 2.15 +import QtQuick.Controls 2.15 +import QtQuick.Layouts 1.15 + +import org.bitcoincore.qt 1.0 + +import "../../controls" +import "../../components" + +Page { + id: root + + property WalletQmlModel wallet: walletController.selectedWallet + + signal done() + + header: NavigationBar2 { + centerItem: Header { + headerBold: true + headerSize: 18 + header: qsTr("Coin Selection") + } + rightItem: NavButton { + text: qsTr("Done") + onClicked: root.done() + } + } + + background: Rectangle { + color: Theme.color.neutral0 + } + + ListView { + id: listView + clip: true + width: Math.min(parent.width - 40, 450) + height: parent.height + anchors.horizontalCenter: parent.horizontalCenter + model: root.wallet.coinsListModel + spacing: 15 + + header: ColumnLayout { + width: listView.width + RowLayout { + Layout.fillWidth: true + spacing: 15 + CoreText { + Layout.alignment: Qt.AlignLeft + Layout.fillWidth: true + Layout.preferredWidth: 0 + font.pixelSize: 18 + color: Theme.color.neutral9 + elide: Text.ElideMiddle + wrapMode: Text.NoWrap + horizontalAlignment: Text.AlignLeft + text: qsTr("Total selected") + } + CoreText { + Layout.alignment: Qt.AlignRight + color: Theme.color.neutral9 + font.pixelSize: 18 + text: root.wallet.coinsListModel.totalSelected + } + } + RowLayout { + Layout.bottomMargin: 30 + CoreText { + Layout.alignment: Qt.AlignLeft + Layout.fillWidth: true + Layout.preferredWidth: 0 + font.pixelSize: 15 + color: Theme.color.neutral7 + elide: Text.ElideMiddle + wrapMode: Text.NoWrap + horizontalAlignment: Text.AlignLeft + text: if (root.wallet.coinsListModel.overRequiredAmount) { + qsTr("Over required amount") + } else { + qsTr("Remaining to select") + } + } + CoreText { + Layout.alignment: Qt.AlignRight + font.pixelSize: 15 + color: Theme.color.neutral7 + text: root.wallet.coinsListModel.changeAmount + } + } + } + + delegate: ItemDelegate { + id: delegate + required property string address; + required property string amount; + required property string label; + required property bool locked; + required property bool selected; + + required property int index; + + readonly property color stateColor: { + if (delegate.down) { + return Theme.color.orange + } else if (delegate.hovered) { + return Theme.color.orangeLight1 + } + return Theme.color.neutral9 + } + + leftPadding: 0 + rightPadding: 0 + topPadding: 14 + bottomPadding: 14 + width: listView.width + + background: Item { + Separator { + anchors.top: parent.top + width: parent.width + } + } + + contentItem: RowLayout { + width: parent.width + CoreCheckBox { + id: checkBox + Layout.minimumWidth: 20 + enabled: !locked + checked: selected + visible: !locked + MouseArea { + anchors.fill: parent + enabled: false + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + } + + onToggled: listView.model.toggleCoinSelection(index) + } + Icon { + source: "qrc:/icons/lock" + color: Theme.color.neutral9 + visible: locked + size: 20 + } + CoreText { + text: amount + font.pixelSize: 18 + } + CoreText { + Layout.fillWidth: true + text: label != "" ? label : address + font.pixelSize: 18 + elide: Text.ElideMiddle + wrapMode: Text.NoWrap + } + } + } + } +} \ No newline at end of file diff --git a/src/qml/pages/wallet/DesktopWallets.qml b/src/qml/pages/wallet/DesktopWallets.qml index d814c89fab..c63dd9f7bb 100644 --- a/src/qml/pages/wallet/DesktopWallets.qml +++ b/src/qml/pages/wallet/DesktopWallets.qml @@ -129,6 +129,7 @@ Page { width: parent.width height: parent.height currentIndex: navigationTabs.checkedButton.index + clip: true Activity { id: activityTab } diff --git a/src/qml/pages/wallet/Send.qml b/src/qml/pages/wallet/Send.qml index a8b234ece2..d691b28208 100644 --- a/src/qml/pages/wallet/Send.qml +++ b/src/qml/pages/wallet/Send.qml @@ -5,176 +5,238 @@ import QtQuick 2.15 import QtQuick.Controls 2.15 import QtQuick.Layouts 1.15 +import Qt.labs.settings 1.0 import org.bitcoincore.qt 1.0 import "../../controls" import "../../components" -Page { +PageStack { id: root - background: null + vertical: true property WalletQmlModel wallet: walletController.selectedWallet property SendRecipient recipient: wallet.sendRecipient signal transactionPrepared() - ScrollView { - clip: true - width: parent.width - height: parent.height - contentWidth: width - - ColumnLayout { - id: columnLayout - width: 450 - anchors.horizontalCenter: parent.horizontalCenter - - spacing: 10 - - CoreText { - id: title - Layout.topMargin: 30 - Layout.bottomMargin: 20 - text: qsTr("Send bitcoin") - font.pixelSize: 21 - bold: true - } + Connections { + target: walletController + function onSelectedWalletChanged() { + root.pop() + } + } - LabeledTextInput { - id: address - Layout.fillWidth: true - labelText: qsTr("Send to") - placeholderText: qsTr("Enter address...") - text: root.recipient.address - onTextEdited: root.recipient.address = address.text - } + initialItem: Page { + background: null - Separator { - Layout.fillWidth: true - } + Settings { + id: settings + property alias coinControlEnabled: sendOptionsPopup.coinControlEnabled + } + + ScrollView { + clip: true + width: parent.width + height: parent.height + contentWidth: width + + ColumnLayout { + id: columnLayout + width: 450 + anchors.horizontalCenter: parent.horizontalCenter - Item { - BitcoinAmount { - id: bitcoinAmount + spacing: 10 + + Item { + id: titleRow + Layout.fillWidth: true + Layout.topMargin: 30 + Layout.bottomMargin: 20 + CoreText { + id: title + anchors.left: parent.left + anchors.verticalCenter: parent.verticalCenter + text: qsTr("Send bitcoin") + font.pixelSize: 21 + bold: true + } + EllipsisMenuButton { + id: menuButton + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + checked: sendOptionsPopup.opened + onClicked: { + sendOptionsPopup.open() + } + } + + SendOptionsPopup { + id: sendOptionsPopup + x: menuButton.x - width + menuButton.width + y: menuButton.y + menuButton.height + width: 300 + height: 50 + } } - height: amountInput.height - Layout.fillWidth: true - CoreText { - id: amountLabel - width: 110 - anchors.left: parent.left - anchors.verticalCenter: parent.verticalCenter - horizontalAlignment: Text.AlignLeft - color: Theme.color.neutral9 - text: "Amount" - font.pixelSize: 18 + LabeledTextInput { + id: address + Layout.fillWidth: true + labelText: qsTr("Send to") + placeholderText: qsTr("Enter address...") + text: root.recipient.address + onTextEdited: root.recipient.address = address.text } - TextField { - id: amountInput - anchors.left: amountLabel.right - anchors.verticalCenter: parent.verticalCenter - leftPadding: 0 - font.family: "Inter" - font.styleName: "Regular" - font.pixelSize: 18 - color: Theme.color.neutral9 - placeholderTextColor: Theme.color.neutral7 - background: Item {} - placeholderText: "0.00000000" - selectByMouse: true - onTextEdited: { - amountInput.text = bitcoinAmount.amount = bitcoinAmount.sanitize(amountInput.text) - root.recipient.amount = bitcoinAmount.satoshiAmount - } + Separator { + Layout.fillWidth: true } + Item { - width: unitLabel.width + flipIcon.width - height: Math.max(unitLabel.height, flipIcon.height) - anchors.right: parent.right - anchors.verticalCenter: parent.verticalCenter - MouseArea { - anchors.fill: parent - onClicked: { - if (bitcoinAmount.unit == BitcoinAmount.BTC) { - amountInput.text = bitcoinAmount.convert(amountInput.text, BitcoinAmount.BTC) - bitcoinAmount.unit = BitcoinAmount.SAT - } else { - amountInput.text = bitcoinAmount.convert(amountInput.text, BitcoinAmount.SAT) - bitcoinAmount.unit = BitcoinAmount.BTC - } - } + BitcoinAmount { + id: bitcoinAmount } + + height: amountInput.height + Layout.fillWidth: true CoreText { - id: unitLabel - anchors.right: flipIcon.left + id: amountLabel + width: 110 + anchors.left: parent.left anchors.verticalCenter: parent.verticalCenter - text: bitcoinAmount.unitLabel + horizontalAlignment: Text.AlignLeft + color: Theme.color.neutral9 + text: qsTr("Amount") font.pixelSize: 18 - color: Theme.color.neutral7 } - Icon { - id: flipIcon + + TextField { + id: amountInput + anchors.left: amountLabel.right + anchors.verticalCenter: parent.verticalCenter + leftPadding: 0 + font.family: "Inter" + font.styleName: "Regular" + font.pixelSize: 18 + color: Theme.color.neutral9 + placeholderTextColor: Theme.color.neutral7 + background: Item {} + placeholderText: "0.00000000" + selectByMouse: true + onTextEdited: { + amountInput.text = bitcoinAmount.amount = bitcoinAmount.sanitize(amountInput.text) + root.recipient.amount = bitcoinAmount.satoshiAmount + } + } + Item { + width: unitLabel.width + flipIcon.width + height: Math.max(unitLabel.height, flipIcon.height) anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter - source: "image://images/flip-vertical" - color: Theme.color.neutral8 - size: 30 + MouseArea { + anchors.fill: parent + onClicked: { + if (bitcoinAmount.unit == BitcoinAmount.BTC) { + amountInput.text = bitcoinAmount.convert(amountInput.text, BitcoinAmount.BTC) + bitcoinAmount.unit = BitcoinAmount.SAT + } else { + amountInput.text = bitcoinAmount.convert(amountInput.text, BitcoinAmount.SAT) + bitcoinAmount.unit = BitcoinAmount.BTC + } + } + } + CoreText { + id: unitLabel + anchors.right: flipIcon.left + anchors.verticalCenter: parent.verticalCenter + text: bitcoinAmount.unitLabel + font.pixelSize: 18 + color: Theme.color.neutral7 + } + Icon { + id: flipIcon + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + source: "image://images/flip-vertical" + color: Theme.color.neutral8 + size: 30 + } } } - } - Separator { - Layout.fillWidth: true - } + Separator { + Layout.fillWidth: true + } - LabeledTextInput { - id: label - Layout.fillWidth: true - labelText: qsTr("Note to self") - placeholderText: qsTr("Enter ...") - onTextEdited: root.recipient.label = label.text - } + LabeledTextInput { + id: label + Layout.fillWidth: true + labelText: qsTr("Note to self") + placeholderText: qsTr("Enter ...") + onTextEdited: root.recipient.label = label.text + } - Separator { - Layout.fillWidth: true - } + Separator { + Layout.fillWidth: true + } + + LabeledCoinControlButton { + visible: settings.coinControlEnabled + Layout.fillWidth: true + coinsSelected: wallet.coinsListModel.selectedCoinsCount + onOpenCoinControl: { + root.wallet.coinsListModel.update() + root.push(coinSelectionPage) + } + } - Item { - height: feeLabel.height + feeValue.height - Layout.fillWidth: true - CoreText { - id: feeLabel - anchors.left: parent.left - anchors.top: parent.top - color: Theme.color.neutral9 - text: "Fee" - font.pixelSize: 15 + Separator { + visible: settings.coinControlEnabled + Layout.fillWidth: true } - CoreText { - id: feeValue - anchors.right: parent.right - anchors.top: parent.top - color: Theme.color.neutral9 - text: qsTr("Default (~2,000 sats)") - font.pixelSize: 15 + Item { + height: feeLabel.height + feeValue.height + Layout.fillWidth: true + CoreText { + id: feeLabel + anchors.left: parent.left + anchors.top: parent.top + color: Theme.color.neutral9 + text: "Fee" + font.pixelSize: 15 + } + + CoreText { + id: feeValue + anchors.right: parent.right + anchors.top: parent.top + color: Theme.color.neutral9 + text: qsTr("Default (~2,000 sats)") + font.pixelSize: 15 + } } - } - ContinueButton { - id: continueButton - Layout.fillWidth: true - Layout.topMargin: 30 - text: qsTr("Review") - onClicked: { - if (root.wallet.prepareTransaction()) { - root.transactionPrepared() + ContinueButton { + id: continueButton + Layout.fillWidth: true + Layout.topMargin: 30 + text: qsTr("Review") + onClicked: { + if (root.wallet.prepareTransaction()) { + root.transactionPrepared() + } } } } } } + + Component { + id: coinSelectionPage + CoinSelection { + onDone: root.pop() + } + } } From 7d34aaf4ce595203c43d913a7877c820ea75e12d Mon Sep 17 00:00:00 2001 From: johnny9 <985648+johnny9@users.noreply.github.com> Date: Wed, 26 Mar 2025 15:44:50 -0400 Subject: [PATCH 07/11] qml: Return 0 when recipient amount QString is empty --- src/qml/models/sendrecipient.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/qml/models/sendrecipient.cpp b/src/qml/models/sendrecipient.cpp index f40ec75456..138bea6559 100644 --- a/src/qml/models/sendrecipient.cpp +++ b/src/qml/models/sendrecipient.cpp @@ -70,6 +70,9 @@ bool SendRecipient::subtractFeeFromAmount() const CAmount SendRecipient::cAmount() const { // TODO: Figure out who owns the parsing of SendRecipient::amount to CAmount + if (m_amount == "") { + return 0; + } return m_amount.toLongLong(); } From dee3530ed5bb4cf25c9daadcf9b0110ae85214b7 Mon Sep 17 00:00:00 2001 From: johnny9 <985648+johnny9@users.noreply.github.com> Date: Wed, 26 Mar 2025 22:47:51 -0400 Subject: [PATCH 08/11] qml: Move CoinSelection list header outside of ListView --- src/qml/pages/wallet/CoinSelection.qml | 106 +++++++++++++------------ 1 file changed, 55 insertions(+), 51 deletions(-) diff --git a/src/qml/pages/wallet/CoinSelection.qml b/src/qml/pages/wallet/CoinSelection.qml index 3c401a87bf..a13141b761 100644 --- a/src/qml/pages/wallet/CoinSelection.qml +++ b/src/qml/pages/wallet/CoinSelection.qml @@ -34,63 +34,67 @@ Page { color: Theme.color.neutral0 } - ListView { - id: listView - clip: true + ColumnLayout { + id: header width: Math.min(parent.width - 40, 450) - height: parent.height anchors.horizontalCenter: parent.horizontalCenter - model: root.wallet.coinsListModel - spacing: 15 - header: ColumnLayout { - width: listView.width - RowLayout { + RowLayout { + Layout.fillWidth: true + spacing: 15 + CoreText { + Layout.alignment: Qt.AlignLeft Layout.fillWidth: true - spacing: 15 - CoreText { - Layout.alignment: Qt.AlignLeft - Layout.fillWidth: true - Layout.preferredWidth: 0 - font.pixelSize: 18 - color: Theme.color.neutral9 - elide: Text.ElideMiddle - wrapMode: Text.NoWrap - horizontalAlignment: Text.AlignLeft - text: qsTr("Total selected") - } - CoreText { - Layout.alignment: Qt.AlignRight - color: Theme.color.neutral9 - font.pixelSize: 18 - text: root.wallet.coinsListModel.totalSelected - } + Layout.preferredWidth: 0 + font.pixelSize: 18 + color: Theme.color.neutral9 + elide: Text.ElideMiddle + wrapMode: Text.NoWrap + horizontalAlignment: Text.AlignLeft + text: qsTr("Total selected") } - RowLayout { - Layout.bottomMargin: 30 - CoreText { - Layout.alignment: Qt.AlignLeft - Layout.fillWidth: true - Layout.preferredWidth: 0 - font.pixelSize: 15 - color: Theme.color.neutral7 - elide: Text.ElideMiddle - wrapMode: Text.NoWrap - horizontalAlignment: Text.AlignLeft - text: if (root.wallet.coinsListModel.overRequiredAmount) { - qsTr("Over required amount") - } else { - qsTr("Remaining to select") - } - } - CoreText { - Layout.alignment: Qt.AlignRight - font.pixelSize: 15 - color: Theme.color.neutral7 - text: root.wallet.coinsListModel.changeAmount + CoreText { + Layout.alignment: Qt.AlignRight + color: Theme.color.neutral9 + font.pixelSize: 18 + text: root.wallet.coinsListModel.totalSelected + } + } + RowLayout { + Layout.bottomMargin: 30 + CoreText { + Layout.alignment: Qt.AlignLeft + Layout.fillWidth: true + Layout.preferredWidth: 0 + font.pixelSize: 15 + color: Theme.color.neutral7 + elide: Text.ElideMiddle + wrapMode: Text.NoWrap + horizontalAlignment: Text.AlignLeft + text: if (root.wallet.coinsListModel.overRequiredAmount) { + qsTr("Over required amount") + } else { + qsTr("Remaining to select") } } + CoreText { + Layout.alignment: Qt.AlignRight + font.pixelSize: 15 + color: Theme.color.neutral7 + text: root.wallet.coinsListModel.changeAmount + } } + } + + ListView { + id: listView + clip: true + width: Math.min(parent.width - 40, 450) + height: parent.height - header.height - 20 + anchors.top: header.bottom + anchors.horizontalCenter: parent.horizontalCenter + model: root.wallet.coinsListModel + spacing: 15 delegate: ItemDelegate { id: delegate @@ -113,13 +117,13 @@ Page { leftPadding: 0 rightPadding: 0 - topPadding: 14 + topPadding: 0 bottomPadding: 14 width: listView.width background: Item { Separator { - anchors.top: parent.top + anchors.bottom: parent.bottom width: parent.width } } From a0ee146e8453d9ef219426acced200ddbc8d2a23 Mon Sep 17 00:00:00 2001 From: johnny9 <985648+johnny9@users.noreply.github.com> Date: Wed, 26 Mar 2025 23:04:47 -0400 Subject: [PATCH 09/11] qml: Put CoinSelection ListView in a ScrollView --- src/qml/pages/wallet/CoinSelection.qml | 144 +++++++++++++------------ 1 file changed, 76 insertions(+), 68 deletions(-) diff --git a/src/qml/pages/wallet/CoinSelection.qml b/src/qml/pages/wallet/CoinSelection.qml index a13141b761..263a30781a 100644 --- a/src/qml/pages/wallet/CoinSelection.qml +++ b/src/qml/pages/wallet/CoinSelection.qml @@ -36,8 +36,9 @@ Page { ColumnLayout { id: header - width: Math.min(parent.width - 40, 450) + anchors.top: parent.top anchors.horizontalCenter: parent.horizontalCenter + width: Math.min(parent.width - 40, 450) RowLayout { Layout.fillWidth: true @@ -86,81 +87,88 @@ Page { } } - ListView { - id: listView - clip: true - width: Math.min(parent.width - 40, 450) + ScrollView { + id: scrollView + width: Math.min(parent.width - 40, 460) height: parent.height - header.height - 20 anchors.top: header.bottom - anchors.horizontalCenter: parent.horizontalCenter - model: root.wallet.coinsListModel - spacing: 15 - - delegate: ItemDelegate { - id: delegate - required property string address; - required property string amount; - required property string label; - required property bool locked; - required property bool selected; - - required property int index; - - readonly property color stateColor: { - if (delegate.down) { - return Theme.color.orange - } else if (delegate.hovered) { - return Theme.color.orangeLight1 - } - return Theme.color.neutral9 - } + anchors.left: header.left + clip: true - leftPadding: 0 - rightPadding: 0 - topPadding: 0 - bottomPadding: 14 - width: listView.width + ScrollBar.horizontal.policy: ScrollBar.AlwaysOff - background: Item { - Separator { - anchors.bottom: parent.bottom - width: parent.width - } - } + ListView { + id: listView + width: parent.width + model: root.wallet.coinsListModel + spacing: 15 - contentItem: RowLayout { - width: parent.width - CoreCheckBox { - id: checkBox - Layout.minimumWidth: 20 - enabled: !locked - checked: selected - visible: !locked - MouseArea { - anchors.fill: parent - enabled: false - hoverEnabled: true - cursorShape: Qt.PointingHandCursor + delegate: ItemDelegate { + id: delegate + required property string address; + required property string amount; + required property string label; + required property bool locked; + required property bool selected; + + required property int index; + + readonly property color stateColor: { + if (delegate.down) { + return Theme.color.orange + } else if (delegate.hovered) { + return Theme.color.orangeLight1 } - - onToggled: listView.model.toggleCoinSelection(index) - } - Icon { - source: "qrc:/icons/lock" - color: Theme.color.neutral9 - visible: locked - size: 20 + return Theme.color.neutral9 } - CoreText { - text: amount - font.pixelSize: 18 + + leftPadding: 0 + rightPadding: 10 + topPadding: 0 + bottomPadding: 14 + width: listView.width + + background: Item { + Separator { + anchors.bottom: parent.bottom + width: parent.width - 10 + } } - CoreText { - Layout.fillWidth: true - text: label != "" ? label : address - font.pixelSize: 18 - elide: Text.ElideMiddle - wrapMode: Text.NoWrap + + contentItem: RowLayout { + width: parent.width + CoreCheckBox { + id: checkBox + Layout.minimumWidth: 20 + enabled: !locked + checked: selected + visible: !locked + MouseArea { + anchors.fill: parent + enabled: false + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + } + + onToggled: listView.model.toggleCoinSelection(index) + } + Icon { + source: "qrc:/icons/lock" + color: Theme.color.neutral9 + visible: locked + size: 20 + } + CoreText { + text: amount + font.pixelSize: 18 + } + CoreText { + Layout.fillWidth: true + text: label != "" ? label : address + font.pixelSize: 18 + elide: Text.ElideMiddle + wrapMode: Text.NoWrap + } } } } From c31ac8cf5305d686e62be27fb71d0605ce6f4299 Mon Sep 17 00:00:00 2001 From: johnny9 <985648+johnny9@users.noreply.github.com> Date: Thu, 3 Apr 2025 12:01:32 -0400 Subject: [PATCH 10/11] qml: Initialize total amount in CoinsListModel --- src/qml/models/coinslistmodel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qml/models/coinslistmodel.cpp b/src/qml/models/coinslistmodel.cpp index 4ce4bc596b..283fa57a3b 100644 --- a/src/qml/models/coinslistmodel.cpp +++ b/src/qml/models/coinslistmodel.cpp @@ -13,7 +13,7 @@ #include CoinsListModel::CoinsListModel(WalletQmlModel* parent) - : QAbstractListModel(parent), m_wallet_model(parent) + : QAbstractListModel(parent), m_wallet_model(parent), m_sort_by("amount"), m_total_amount(0) { } From c9bf7aefa256833ef20384a88d36f3bc2c415980 Mon Sep 17 00:00:00 2001 From: johnny9 <985648+johnny9@users.noreply.github.com> Date: Wed, 23 Apr 2025 23:59:16 -0400 Subject: [PATCH 11/11] qml: Don't allow Coin selection if there are none available --- src/qml/controls/LabeledCoinControlButton.qml | 11 +++++++++-- src/qml/models/coinslistmodel.cpp | 7 +++++++ src/qml/models/coinslistmodel.h | 3 +++ src/qml/pages/wallet/Send.qml | 1 + 4 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/qml/controls/LabeledCoinControlButton.qml b/src/qml/controls/LabeledCoinControlButton.qml index deb4e61fe4..7d82e89e8e 100644 --- a/src/qml/controls/LabeledCoinControlButton.qml +++ b/src/qml/controls/LabeledCoinControlButton.qml @@ -8,6 +8,7 @@ import QtQuick.Layouts 1.15 Item { property int coinsSelected: 0 + property int coinCount: 0 signal openCoinControl @@ -32,7 +33,9 @@ Item { color: Theme.color.orangeLight1 font.pixelSize: 18 text: { - if (coinsSelected === 0) { + if (coinCount === 0) { + qsTr("No coins available") + } else if (coinsSelected === 0) { qsTr("Select") } else { qsTr("%1 input%2 selected") @@ -43,7 +46,11 @@ Item { MouseArea { anchors.fill: parent - onClicked: root.openCoinControl() + onClicked: { + if (coinCount > 0) { + root.openCoinControl() + } + } cursorShape: Qt.PointingHandCursor } } diff --git a/src/qml/models/coinslistmodel.cpp b/src/qml/models/coinslistmodel.cpp index 283fa57a3b..76142e74f3 100644 --- a/src/qml/models/coinslistmodel.cpp +++ b/src/qml/models/coinslistmodel.cpp @@ -15,6 +15,7 @@ CoinsListModel::CoinsListModel(WalletQmlModel* parent) : QAbstractListModel(parent), m_wallet_model(parent), m_sort_by("amount"), m_total_amount(0) { + update(); } CoinsListModel::~CoinsListModel() = default; @@ -73,6 +74,7 @@ void CoinsListModel::update() } } endResetModel(); + Q_EMIT coinCountChanged(); } void CoinsListModel::setSortBy(const QString& roleName) @@ -129,3 +131,8 @@ bool CoinsListModel::overRequiredAmount() const { return m_total_amount > m_wallet_model->sendRecipient()->cAmount(); } + +int CoinsListModel::coinCount() const +{ + return m_coins.size(); +} diff --git a/src/qml/models/coinslistmodel.h b/src/qml/models/coinslistmodel.h index 807571cfbd..ecdc35f3a1 100644 --- a/src/qml/models/coinslistmodel.h +++ b/src/qml/models/coinslistmodel.h @@ -20,6 +20,7 @@ class CoinsListModel : public QAbstractListModel Q_OBJECT Q_PROPERTY(int lockedCoinsCount READ lockedCoinsCount NOTIFY lockedCoinsCountChanged) Q_PROPERTY(int selectedCoinsCount READ selectedCoinsCount NOTIFY selectedCoinsCountChanged) + Q_PROPERTY(int coinCount READ coinCount NOTIFY coinCountChanged) Q_PROPERTY(QString totalSelected READ totalSelected NOTIFY selectedCoinsCountChanged) Q_PROPERTY(QString changeAmount READ changeAmount NOTIFY selectedCoinsCountChanged) Q_PROPERTY(bool overRequiredAmount READ overRequiredAmount NOTIFY selectedCoinsCountChanged) @@ -50,11 +51,13 @@ public Q_SLOTS: QString totalSelected() const; QString changeAmount() const; bool overRequiredAmount() const; + int coinCount() const; Q_SIGNALS: void sortByChanged(const QString& roleName); void lockedCoinsCountChanged(); void selectedCoinsCountChanged(); + void coinCountChanged(); private: WalletQmlModel* m_wallet_model; diff --git a/src/qml/pages/wallet/Send.qml b/src/qml/pages/wallet/Send.qml index d691b28208..b646a60a54 100644 --- a/src/qml/pages/wallet/Send.qml +++ b/src/qml/pages/wallet/Send.qml @@ -185,6 +185,7 @@ PageStack { visible: settings.coinControlEnabled Layout.fillWidth: true coinsSelected: wallet.coinsListModel.selectedCoinsCount + coinCount: wallet.coinsListModel.coinCount onOpenCoinControl: { root.wallet.coinsListModel.update() root.push(coinSelectionPage)