3
3
4
4
#include " init.h"
5
5
#include " bitcoinunits.h"
6
- #include " walletmodel.h"
7
6
#include " addresstablemodel.h"
8
7
#include " optionsmodel.h"
9
8
#include " policy/fees.h"
10
9
#include " validation.h"
11
10
#include " wallet/coincontrol.h"
11
+ #include " consolidateunspentdialog.h"
12
12
13
13
#include < QApplication>
14
14
#include < QCheckBox>
@@ -29,6 +29,7 @@ CCoinControl* CoinControlDialog::coinControl = new CCoinControl();
29
29
30
30
CoinControlDialog::CoinControlDialog (QWidget *parent) :
31
31
QDialog(parent),
32
+ m_inputSelectionLimit(600 ),
32
33
ui(new Ui::CoinControlDialog),
33
34
model(0 )
34
35
{
@@ -106,17 +107,38 @@ CoinControlDialog::CoinControlDialog(QWidget *parent) :
106
107
// (un)select all
107
108
connect (ui->selectAllPushButton , SIGNAL (clicked ()), this , SLOT (buttonSelectAllClicked ()));
108
109
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 );
112
125
ui->treeWidget ->setColumnWidth (COLUMN_ADDRESS, 290 );
113
126
ui->treeWidget ->setColumnWidth (COLUMN_DATE, 110 );
114
127
ui->treeWidget ->setColumnWidth (COLUMN_CONFIRMATIONS, 100 );
115
128
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 ();
120
142
121
143
// default view is sorted by amount desc
122
144
sortView (COLUMN_AMOUNT_INT64, Qt::DescendingOrder);
@@ -153,26 +175,222 @@ void CoinControlDialog::buttonBoxClicked(QAbstractButton* button)
153
175
{
154
176
if (ui->buttonBox ->buttonRole (button) == QDialogButtonBox::AcceptRole)
155
177
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 ();
156
199
}
157
200
158
201
// (un)select all
159
202
void CoinControlDialog::buttonSelectAllClicked ()
160
203
{
161
- Qt::CheckState state = Qt::Checked ;
204
+ ui-> treeWidget -> setEnabled ( false ) ;
162
205
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)
163
211
{
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
165
297
{
166
- state = Qt::Unchecked;
167
- break ;
298
+ return (a > b);
168
299
}
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;
169
328
}
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.
174
353
ui->treeWidget ->setEnabled (true );
354
+
175
355
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 ();
176
394
}
177
395
178
396
// context menu
@@ -379,6 +597,8 @@ void CoinControlDialog::viewItemChanged(QTreeWidgetItem* item, int column)
379
597
if (ui->treeWidget ->isEnabled ()) // do not update on every click for (un)select all
380
598
CoinControlDialog::updateLabels (model, this );
381
599
}
600
+
601
+ showHideConsolidationReadyToSend ();
382
602
}
383
603
384
604
// helper function, return human readable label for priority number
@@ -656,6 +876,7 @@ void CoinControlDialog::updateView()
656
876
// tooltip from where the change comes from
657
877
itemOutput->setToolTip (COLUMN_LABEL, tr (" change from %1 (%2)" ).arg (sWalletLabel ).arg (sWalletAddress ));
658
878
itemOutput->setText (COLUMN_LABEL, tr (" (change)" ));
879
+ itemOutput->setText (COLUMN_CHANGE_BOOL, QString::number (1 ));
659
880
}
660
881
else if (!treeMode)
661
882
{
@@ -740,3 +961,34 @@ void CoinControlDialog::updateView()
740
961
sortView (sortColumn, sortOrder);
741
962
ui->treeWidget ->setEnabled (true );
742
963
}
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