Skip to content

Commit 2d8f583

Browse files
authored
Merge pull request #2111 from jamescowens/ui_consolidate_unspent
gui: Implement GUI version of consolidateunspent (coin control part)
2 parents 8827b9a + a142fd7 commit 2d8f583

11 files changed

+736
-105
lines changed

src/Makefile.qt.include

+4
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ QT_TS = \
7474
QT_FORMS_UI = \
7575
qt/forms/aboutdialog.ui \
7676
qt/forms/coincontroldialog.ui \
77+
qt/forms/consolidateunspentdialog.ui \
7778
qt/forms/diagnosticsdialog.ui \
7879
qt/forms/optionsdialog.ui \
7980
qt/forms/rpcconsole.ui \
@@ -111,6 +112,7 @@ QT_MOC_CPP = \
111112
qt/moc_clientmodel.cpp \
112113
qt/moc_coincontroldialog.cpp \
113114
qt/moc_coincontroltreewidget.cpp \
115+
qt/moc_consolidateunspentdialog.cpp \
114116
qt/moc_csvmodelwriter.cpp \
115117
qt/moc_diagnosticsdialog.cpp \
116118
qt/moc_editaddressdialog.cpp \
@@ -181,6 +183,7 @@ GRIDCOINRESEARCH_QT_H = \
181183
qt/clientmodel.h \
182184
qt/coincontroldialog.h \
183185
qt/coincontroltreewidget.h \
186+
qt/consolidateunspentdialog.h \
184187
qt/csvmodelwriter.h \
185188
qt/decoration.h \
186189
qt/diagnosticsdialog.h \
@@ -243,6 +246,7 @@ GRIDCOINRESEARCH_QT_CPP = \
243246
qt/clientmodel.cpp \
244247
qt/coincontroldialog.cpp \
245248
qt/coincontroltreewidget.cpp \
249+
qt/consolidateunspentdialog.cpp \
246250
qt/csvmodelwriter.cpp \
247251
qt/decoration.cpp \
248252
qt/diagnosticsdialog.cpp \

src/qt/coincontroldialog.cpp

+268-16
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@
33

44
#include "init.h"
55
#include "bitcoinunits.h"
6-
#include "walletmodel.h"
76
#include "addresstablemodel.h"
87
#include "optionsmodel.h"
98
#include "policy/fees.h"
109
#include "validation.h"
1110
#include "wallet/coincontrol.h"
11+
#include "consolidateunspentdialog.h"
1212

1313
#include <QApplication>
1414
#include <QCheckBox>
@@ -29,6 +29,7 @@ CCoinControl* CoinControlDialog::coinControl = new CCoinControl();
2929

3030
CoinControlDialog::CoinControlDialog(QWidget *parent) :
3131
QDialog(parent),
32+
m_inputSelectionLimit(600),
3233
ui(new Ui::CoinControlDialog),
3334
model(0)
3435
{
@@ -106,17 +107,38 @@ CoinControlDialog::CoinControlDialog(QWidget *parent) :
106107
// (un)select all
107108
connect(ui->selectAllPushButton, SIGNAL(clicked()), this, SLOT(buttonSelectAllClicked()));
108109

109-
ui->treeWidget->setColumnWidth(COLUMN_CHECKBOX, 84);
110-
ui->treeWidget->setColumnWidth(COLUMN_AMOUNT, 100);
111-
ui->treeWidget->setColumnWidth(COLUMN_LABEL, 170);
110+
// filter/consolidate button interaction
111+
connect(ui->maxMinOutputValue, SIGNAL(textChanged()), this, SLOT(maxMinOutputValueChanged()));
112+
113+
// filter mode
114+
connect(ui->filterModePushButton, SIGNAL(clicked()), this, SLOT(buttonFilterModeClicked()));
115+
116+
// filter
117+
connect(ui->filterPushButton, SIGNAL(clicked()), this, SLOT(buttonFilterClicked()));
118+
119+
// consolidate
120+
connect(ui->consolidateButton, SIGNAL(clicked()), this, SLOT(buttonConsolidateClicked()));
121+
122+
ui->treeWidget->setColumnWidth(COLUMN_CHECKBOX, 150);
123+
ui->treeWidget->setColumnWidth(COLUMN_AMOUNT, 170);
124+
ui->treeWidget->setColumnWidth(COLUMN_LABEL, 200);
112125
ui->treeWidget->setColumnWidth(COLUMN_ADDRESS, 290);
113126
ui->treeWidget->setColumnWidth(COLUMN_DATE, 110);
114127
ui->treeWidget->setColumnWidth(COLUMN_CONFIRMATIONS, 100);
115128
ui->treeWidget->setColumnWidth(COLUMN_PRIORITY, 100);
116-
ui->treeWidget->setColumnHidden(COLUMN_TXHASH, true); // store transacton hash in this column, but dont show it
117-
ui->treeWidget->setColumnHidden(COLUMN_VOUT_INDEX, true); // store vout index in this column, but dont show it
118-
ui->treeWidget->setColumnHidden(COLUMN_AMOUNT_INT64, true); // store amount int64_t in this column, but dont show it
119-
ui->treeWidget->setColumnHidden(COLUMN_PRIORITY_INT64, true); // store priority int64_t in this column, but dont show it
129+
ui->treeWidget->setColumnHidden(COLUMN_TXHASH, true); // store transacton hash in this column, but don't show it
130+
ui->treeWidget->setColumnHidden(COLUMN_VOUT_INDEX, true); // store vout index in this column, but don't show it
131+
ui->treeWidget->setColumnHidden(COLUMN_AMOUNT_INT64, true); // store amount int64_t in this column, but don't show it
132+
ui->treeWidget->setColumnHidden(COLUMN_PRIORITY_INT64, true); // store priority int64_t in this column, but don't show it
133+
ui->treeWidget->setColumnHidden(COLUMN_CHANGE_BOOL, true); // store change flag but don't show it
134+
135+
ui->filterModePushButton->setToolTip(tr("Flips the filter mode between selecting inputs less than or equal to the "
136+
"provided value (<=) and greater than or equal to the provided value (>=). "
137+
"The filter also automatically limits the number of inputs to %1, in "
138+
"ascending order for <= and descending order for >=."
139+
).arg(m_inputSelectionLimit));
140+
141+
ui->consolidateSendReadyLabel->hide();
120142

121143
// default view is sorted by amount desc
122144
sortView(COLUMN_AMOUNT_INT64, Qt::DescendingOrder);
@@ -153,26 +175,222 @@ void CoinControlDialog::buttonBoxClicked(QAbstractButton* button)
153175
{
154176
if (ui->buttonBox->buttonRole(button) == QDialogButtonBox::AcceptRole)
155177
done(QDialog::Accepted); // closes the dialog
178+
179+
if (m_consolidationAddress.second.size())
180+
{
181+
SendCoinsRecipient consolidationRecipient;
182+
183+
qint64 amount = 0;
184+
bool parse_status = false;
185+
186+
consolidationRecipient.label = m_consolidationAddress.first;
187+
consolidationRecipient.address = m_consolidationAddress.second;
188+
parse_status = BitcoinUnits::parse(model->getOptionsModel()->getDisplayUnit(),
189+
ui->coinControlAfterFeeLabel->text()
190+
.left(ui->coinControlAfterFeeLabel->text().indexOf(" ")),
191+
&amount);
192+
193+
if (parse_status) consolidationRecipient.amount = amount;
194+
195+
emit selectedConsolidationRecipientSignal(consolidationRecipient);
196+
}
197+
198+
showHideConsolidationReadyToSend();
156199
}
157200

158201
// (un)select all
159202
void CoinControlDialog::buttonSelectAllClicked()
160203
{
161-
Qt::CheckState state = Qt::Checked;
204+
ui->treeWidget->setEnabled(false);
162205
for (int i = 0; i < ui->treeWidget->topLevelItemCount(); i++)
206+
if (ui->treeWidget->topLevelItem(i)->checkState(COLUMN_CHECKBOX) != m_ToState)
207+
ui->treeWidget->topLevelItem(i)->setCheckState(COLUMN_CHECKBOX, m_ToState);
208+
ui->treeWidget->setEnabled(true);
209+
210+
if (m_ToState == Qt::Checked)
163211
{
164-
if (ui->treeWidget->topLevelItem(i)->checkState(COLUMN_CHECKBOX) != Qt::Unchecked)
212+
m_ToState = Qt::Unchecked;
213+
}
214+
else
215+
{
216+
m_ToState = Qt::Checked;
217+
}
218+
219+
if (m_ToState == Qt::Checked)
220+
{
221+
ui->selectAllPushButton->setText("Select All");
222+
}
223+
else
224+
{
225+
ui->selectAllPushButton->setText("Select None");
226+
}
227+
228+
CoinControlDialog::updateLabels(model, this);
229+
showHideConsolidationReadyToSend();
230+
}
231+
232+
void CoinControlDialog::maxMinOutputValueChanged()
233+
{
234+
235+
bool maxMinOutputValueValid = false;
236+
237+
ui->maxMinOutputValue->value(&maxMinOutputValueValid);
238+
239+
// If someone has put a value in the filter amount field, then consolidate should be disabled until the
240+
// filter button is pressed to apply the filter. If the field is empty, then the consolidation can work
241+
// without the filter application first, (i.e. consolidation is enabled), because the idea is to select
242+
// up to the m_inputSelectionLimit number of inputs either from smallest upward or largest downward by
243+
// following the <= or >= filter mode button. This shortcut is mainly for convenience.
244+
if (maxMinOutputValueValid)
245+
{
246+
ui->consolidateButton->setEnabled(false);
247+
}
248+
else
249+
{
250+
ui->consolidateButton->setEnabled(true);
251+
}
252+
253+
showHideConsolidationReadyToSend();
254+
}
255+
256+
void CoinControlDialog::buttonFilterModeClicked()
257+
{
258+
if (m_FilterMode)
259+
{
260+
m_FilterMode = false;
261+
ui->filterModePushButton->setText(">=");
262+
}
263+
else
264+
{
265+
m_FilterMode = true;
266+
ui->filterModePushButton->setText("<=");
267+
}
268+
}
269+
270+
void CoinControlDialog::buttonFilterClicked()
271+
{
272+
// Don't limit the number of outputs for the filter only operation.
273+
filterInputsByValue(m_FilterMode, ui->maxMinOutputValue->value(), std::numeric_limits<unsigned int>::max());
274+
275+
ui->consolidateButton->setEnabled(true);
276+
showHideConsolidationReadyToSend();
277+
}
278+
279+
bool CoinControlDialog::filterInputsByValue(const bool& less, const CAmount& inputFilterValue,
280+
const unsigned int& inputSelectionLimit)
281+
{
282+
// Disable generating update signals unnecessarily during this filter operation.
283+
ui->treeWidget->setEnabled(false);
284+
285+
QTreeWidgetItemIterator iter(ui->treeWidget);
286+
287+
// If less is true, then we are choosing the smallest inputs upward, and so the map comparator needs to be "less than".
288+
// If less is false, then we are choosing the largest inputs downward, and so the map comparator needs to be "greater
289+
// than".
290+
auto comp = [less](CAmount a, CAmount b)
291+
{
292+
if (less)
293+
{
294+
return (a < b);
295+
}
296+
else
165297
{
166-
state = Qt::Unchecked;
167-
break;
298+
return (a > b);
168299
}
300+
};
301+
302+
std::multimap<CAmount, std::pair<QTreeWidgetItem*, COutPoint>, decltype(comp)> input_map(comp);
303+
304+
bool culled_inputs = false;
305+
306+
while (*iter)
307+
{
308+
CAmount input_value = (*iter)->text(COLUMN_AMOUNT_INT64).toLongLong();
309+
COutPoint outpoint(uint256S((*iter)->text(COLUMN_TXHASH).toStdString()), (*iter)->text(COLUMN_VOUT_INDEX).toUInt());
310+
311+
if ((*iter)->checkState(COLUMN_CHECKBOX) == Qt::Checked)
312+
{
313+
if ((*iter)->text(COLUMN_TXHASH).length() == 64)
314+
{
315+
if ((less && input_value <= inputFilterValue) || (!less && input_value >= inputFilterValue))
316+
{
317+
input_map.insert(std::make_pair(input_value, std::make_pair(*iter, outpoint)));
318+
}
319+
else
320+
{
321+
(*iter)->setCheckState(COLUMN_CHECKBOX, Qt::Unchecked);
322+
coinControl->UnSelect(outpoint);
323+
}
324+
}
325+
}
326+
327+
++iter;
169328
}
170-
ui->treeWidget->setEnabled(false);
171-
for (int i = 0; i < ui->treeWidget->topLevelItemCount(); i++)
172-
if (ui->treeWidget->topLevelItem(i)->checkState(COLUMN_CHECKBOX) != state)
173-
ui->treeWidget->topLevelItem(i)->setCheckState(COLUMN_CHECKBOX, state);
329+
330+
// The second loop is to limit the number of selected outputs to the inputCountLimit.
331+
unsigned int input_count = 0;
332+
333+
for (auto& input : input_map)
334+
{
335+
if (input_count >= inputSelectionLimit)
336+
{
337+
LogPrint(BCLog::LogFlags::MISC, "INFO: %s: Culled input %u with value %f.",
338+
__func__, input_count, (double) input.first / COIN);
339+
340+
if (coinControl->IsSelected(input.second.second.hash, input.second.second.n))
341+
{
342+
input.second.first->setCheckState(COLUMN_CHECKBOX, Qt::Unchecked);
343+
344+
culled_inputs = true;
345+
coinControl->UnSelect(input.second.second);
346+
}
347+
}
348+
349+
++input_count;
350+
}
351+
352+
// Reenable update signals.
174353
ui->treeWidget->setEnabled(true);
354+
175355
CoinControlDialog::updateLabels(model, this);
356+
357+
// If the number of inputs selected was limited, then true is returned.
358+
return culled_inputs;
359+
}
360+
361+
void CoinControlDialog::buttonConsolidateClicked()
362+
{
363+
ConsolidateUnspentDialog consolidateUnspentDialog(this, m_inputSelectionLimit);
364+
365+
std::map<QString, QString> addressList;
366+
367+
bool culled_inputs = false;
368+
369+
// Note that we are applying the filter here to limit the number of inputs only to ensure the m_inputSelectionLimit
370+
// input maximum is not exceeded for the purpose of consolidation.
371+
CAmount outputFilterValue = 0;
372+
373+
outputFilterValue = m_FilterMode ? MAX_MONEY: 0;
374+
375+
culled_inputs = filterInputsByValue(m_FilterMode, outputFilterValue, m_inputSelectionLimit);
376+
377+
for (int i = 0; i < ui->treeWidget->topLevelItemCount(); ++i)
378+
{
379+
QString label = ui->treeWidget->topLevelItem(i)->text(COLUMN_LABEL);
380+
QString address = ui->treeWidget->topLevelItem(i)->text(COLUMN_ADDRESS);
381+
QString change = ui->treeWidget-> topLevelItem(i)->text(COLUMN_CHANGE_BOOL);
382+
383+
if (!change.toInt()) addressList[address] = label;
384+
}
385+
386+
if (!addressList.empty()) consolidateUnspentDialog.SetAddressList(addressList);
387+
388+
if (culled_inputs) consolidateUnspentDialog.SetOutputWarningVisible(true);
389+
390+
connect(&consolidateUnspentDialog, SIGNAL(selectedConsolidationAddressSignal(std::pair<QString, QString>)),
391+
this, SLOT(selectedConsolidationAddressSlot(std::pair<QString, QString>)));
392+
393+
consolidateUnspentDialog.exec();
176394
}
177395

178396
// context menu
@@ -379,6 +597,8 @@ void CoinControlDialog::viewItemChanged(QTreeWidgetItem* item, int column)
379597
if (ui->treeWidget->isEnabled()) // do not update on every click for (un)select all
380598
CoinControlDialog::updateLabels(model, this);
381599
}
600+
601+
showHideConsolidationReadyToSend();
382602
}
383603

384604
// helper function, return human readable label for priority number
@@ -656,6 +876,7 @@ void CoinControlDialog::updateView()
656876
// tooltip from where the change comes from
657877
itemOutput->setToolTip(COLUMN_LABEL, tr("change from %1 (%2)").arg(sWalletLabel).arg(sWalletAddress));
658878
itemOutput->setText(COLUMN_LABEL, tr("(change)"));
879+
itemOutput->setText(COLUMN_CHANGE_BOOL, QString::number(1));
659880
}
660881
else if (!treeMode)
661882
{
@@ -740,3 +961,34 @@ void CoinControlDialog::updateView()
740961
sortView(sortColumn, sortOrder);
741962
ui->treeWidget->setEnabled(true);
742963
}
964+
965+
void CoinControlDialog::selectedConsolidationAddressSlot(std::pair<QString, QString> address)
966+
{
967+
m_consolidationAddress = address;
968+
showHideConsolidationReadyToSend();
969+
}
970+
971+
void CoinControlDialog::showHideConsolidationReadyToSend()
972+
{
973+
if (m_consolidationAddress.second.size() && coinControl->HasSelected() && ui->consolidateButton->isEnabled())
974+
{
975+
// This is more expensive. Only do if it passes the first two conditions above. We want to check
976+
// and make sure that the number of inputs is less than m_inputSelectionLimit for consolidation purposes.
977+
std::vector<COutPoint> selectionList;
978+
979+
coinControl->ListSelected(selectionList);
980+
981+
if (selectionList.size() <= m_inputSelectionLimit)
982+
{
983+
ui->consolidateSendReadyLabel->show();
984+
}
985+
else
986+
{
987+
ui->consolidateSendReadyLabel->hide();
988+
}
989+
}
990+
else
991+
{
992+
ui->consolidateSendReadyLabel->hide();
993+
}
994+
}

0 commit comments

Comments
 (0)