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
3030CoinControlDialog::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
159202void 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