Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/interfaces/wallet.h
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,12 @@ class Wallet
//! List locked coins.
virtual std::vector<COutPoint> listLockedCoins() = 0;

//! Lock the provided coins in a single batch.
virtual bool lockCoins(const std::vector<COutPoint>& outputs) = 0;

//! Unlock the provided coins in a single batch.
virtual bool unlockCoins(const std::vector<COutPoint>& outputs) = 0;

//! List protx coins.
virtual std::vector<COutPoint> listProTxCoins() = 0;

Expand Down
66 changes: 34 additions & 32 deletions src/qt/coincontroldialog.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -114,8 +114,8 @@ CoinControlDialog::CoinControlDialog(CCoinControl& coin_control, WalletModel* _m
// (un)select all
connect(ui->pushButtonSelectAll, &QPushButton::clicked, this, &CoinControlDialog::buttonSelectAllClicked);

// Toggle lock state
connect(ui->pushButtonToggleLock, &QPushButton::clicked, this, &CoinControlDialog::buttonToggleLockClicked);
// (un)lock all
connect(ui->pushButtonLockAll, &QPushButton::clicked, this, &CoinControlDialog::buttonLockAllClicked);

ui->treeWidget->setColumnWidth(COLUMN_CHECKBOX, 94);
ui->treeWidget->setColumnWidth(COLUMN_AMOUNT, 100);
Expand Down Expand Up @@ -186,41 +186,43 @@ void CoinControlDialog::buttonSelectAllClicked()
CoinControlDialog::updateLabels(m_coin_control, model, this);
}

// Toggle lock state
void CoinControlDialog::buttonToggleLockClicked()
// (un)lock all
void CoinControlDialog::buttonLockAllClicked()
{
QTreeWidgetItem *item;
// Works in list-mode only
if(ui->radioListMode->isChecked()){
ui->treeWidget->setEnabled(false);
for (int i = 0; i < ui->treeWidget->topLevelItemCount(); i++){
item = ui->treeWidget->topLevelItem(i);
COutPoint outpt(uint256S(item->data(COLUMN_ADDRESS, TxHashRole).toString().toStdString()), item->data(COLUMN_ADDRESS, VOutRole).toUInt());
// Don't toggle the lock state of partially mixed coins if they are not hidden in CoinJoin mode
if (m_coin_control.IsUsingCoinJoin() && !fHideAdditional && !model->isFullyMixed(outpt)) {
continue;
}
if (model->wallet().isLockedCoin(outpt)) {
model->wallet().unlockCoin(outpt);
item->setDisabled(false);
item->setIcon(COLUMN_CHECKBOX, QIcon());
}
else{
model->wallet().lockCoin(outpt, /*write_to_db=*/true);
item->setDisabled(true);
item->setIcon(COLUMN_CHECKBOX, GUIUtil::getIcon("lock_closed", GUIUtil::ThemedColor::RED));
// Collect all visible UTXOs; track locked ones separately for the unlock path
// (works in both tree and list modes)
std::vector<COutPoint> outputs;
std::vector<COutPoint> lockedOutputs;
for (int i = 0; i < ui->treeWidget->topLevelItemCount(); i++) {
QTreeWidgetItem* item = ui->treeWidget->topLevelItem(i);
if (item->data(COLUMN_ADDRESS, TxHashRole).toString().length() == 64) {
// List mode: top-level item is a UTXO
const COutPoint outpt(uint256S(item->data(COLUMN_ADDRESS, TxHashRole).toString().toStdString()),
item->data(COLUMN_ADDRESS, VOutRole).toUInt());
outputs.emplace_back(outpt);
if (model->wallet().isLockedCoin(outpt)) lockedOutputs.emplace_back(outpt);
} else {
// Tree mode: top-level item is an address group; collect children
for (int j = 0; j < item->childCount(); j++) {
QTreeWidgetItem* child = item->child(j);
const COutPoint outpt(uint256S(child->data(COLUMN_ADDRESS, TxHashRole).toString().toStdString()),
child->data(COLUMN_ADDRESS, VOutRole).toUInt());
outputs.emplace_back(outpt);
if (model->wallet().isLockedCoin(outpt)) lockedOutputs.emplace_back(outpt);
}
updateLabelLocked();
}
ui->treeWidget->setEnabled(true);
CoinControlDialog::updateLabels(m_coin_control, model, this);
}
else{
QMessageBox msgBox(this);
msgBox.setObjectName("lockMessageBox");
msgBox.setText(tr("Please switch to \"List mode\" to use this function."));
msgBox.exec();
bool should_lock = lockedOutputs.empty();
bool success = should_lock ? model->wallet().lockCoins(outputs)
: model->wallet().unlockCoins(lockedOutputs);
if (!success) {
QMessageBox::warning(this, tr("Wallet error"),
should_lock ? tr("Failed to lock some coins.")
: tr("Failed to unlock some coins."));
}
updateView();
updateLabelLocked();
CoinControlDialog::updateLabels(m_coin_control, model, this);
}

// context menu
Expand Down
2 changes: 1 addition & 1 deletion src/qt/coincontroldialog.h
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ private Q_SLOTS:
void headerSectionClicked(int);
void buttonBoxClicked(QAbstractButton*);
void buttonSelectAllClicked();
void buttonToggleLockClicked();
void buttonLockAllClicked();
void updateLabelLocked();
void on_hideButton_clicked();
};
Expand Down
4 changes: 2 additions & 2 deletions src/qt/forms/coincontroldialog.ui
Original file line number Diff line number Diff line change
Expand Up @@ -275,15 +275,15 @@
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButtonToggleLock">
<widget class="QPushButton" name="pushButtonLockAll">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>toggle lock state</string>
<string>(un)lock all</string>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I think "lock / unlock all" is a bit more translation friendly.
@coderabbitai, can you think about possible better name which is clear for translator out of context and can be easily translated to multiple languages?

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@knst Great question — here are a few options ranked by translation friendliness:


Option 1: Dynamic button text ⭐ (most translation-friendly)

Change the button label based on current state:

  • Show "Lock All" when no outputs are locked
  • Show "Unlock All" when any outputs are locked

Each state uses a short, unambiguous imperative verb — easy to translate in any language. This removes all ambiguity for translators and users alike. The tradeoff is a small code change to update the button text in buttonLockAllClicked() and during view refresh.


Option 2: Lock / Unlock All (knst's suggestion)

Better than (un)lock all because:

  • Uses full words (no parenthetical English shorthand)
  • The / separator is understood internationally as "or"
  • Still gives translators context that it's a dual-action button

The downside is it still implies simultaneous actions, which may confuse translators into providing two separate translations joined awkwardly.


Option 3: Toggle Lock All or Toggle All Locks

Avoids the slash/parenthetical issue, but "toggle" lacks direct equivalents in many languages (e.g., Slavic, East Asian languages) and translators may struggle with it out of context.


Recommendation

Option 1 (dynamic text) is the most translator-friendly — each state gives the translator a single clear imperative sentence. If that's too much change, "Lock / Unlock All" (Option 2) is a solid static compromise, matching the same pattern used for the existing (un)select all button for UI consistency.

It's also worth noting the existing (un)select all button has the same internationalization concern — if dynamic text is adopted here, it could be considered for that button too (out of scope for this PR, but worth a follow-up).


🧠 Learnings used
Learnt from: kwvg
Repo: dashpay/dash PR: 7072
File: src/qt/walletcontroller.cpp:520-528
Timestamp: 2025-12-17T13:58:26.891Z
Learning: In Dash Qt wallet code, when leveraging upstream Bitcoin Core wallet capabilities (especially for operations like rescanning), prefer using the inherited signal-based mechanisms (e.g., ShowProgress signals) over adding explicit Qt progress dialogs to minimize upstream deviation and simplify future backports.

Learnt from: kwvg
Repo: dashpay/dash PR: 6543
File: src/wallet/receive.cpp:240-251
Timestamp: 2025-02-06T14:34:30.466Z
Learning: Pull request `#6543` is focused on move-only changes and refactoring, specifically backporting from Bitcoin. Behavior changes should be proposed in separate PRs.

</property>
<property name="autoDefault">
<bool>false</bool>
Expand Down
18 changes: 18 additions & 0 deletions src/wallet/interfaces.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,24 @@ class WalletImpl : public Wallet
LOCK(m_wallet->cs_wallet);
return m_wallet->ListLockedCoins();
}
bool lockCoins(const std::vector<COutPoint>& outputs) override
{
LOCK(m_wallet->cs_wallet);
WalletBatch batch(m_wallet->GetDatabase());
for (const auto& output : outputs) {
if (!m_wallet->LockCoin(output, &batch)) return false;
}
return true;
}
bool unlockCoins(const std::vector<COutPoint>& outputs) override
{
LOCK(m_wallet->cs_wallet);
WalletBatch batch(m_wallet->GetDatabase());
for (const auto& output : outputs) {
if (!m_wallet->UnlockCoin(output, &batch)) return false;
}
return true;
}
std::vector<COutPoint> listProTxCoins() override
{
LOCK(m_wallet->cs_wallet);
Expand Down
Loading